Catalyst

Endless 开发笔记(1):基础布局

新的一天新的 Flag,突然想整 F#,做不来就回退。

软件原型

一个快速笔记软件。点击进软件就是写作界面,关闭软件或按下保存键则归档进入保存卡片。

原型图:

原型图

安装

  1. 装 vs
  2. 按照官网的方法,打开 vs 命令行,输入 dotnet new -i Fabulous.XamarinForms.Templates
  3. cd 进想要创建工程的文件夹,输入 dotnet new fabulous-xf-app -n <工程名字> 。默认创建 android 和 ios 程序,不想要/添加新的需要加参数。例:创建 wpf 程序dotnet new fabulous-xf-app -n <工程名字> --iOS=false --Android=false --WPF

这次软件名设为 Endless,有 Android 端。则初始化命令为dotnet new fabulous-xf-app -n Endless --iOS=false

布局

使用字体图标

  1. 字体放入共用工程内(和工程名相同,此处为 Endless),然后在字体的属性栏中,将生成操作设置为嵌入的资源(Build Action: EmbeddedResource)。

  2. 在共用工程下,建立AssemblyInfo.fs(或者在别的文件里),注册资源:

    namespace Endless
    
    open Xamarin.Forms
    
    [<assembly:ExportFont("MaterialIcons-Regular.ttf", Alias = "MaterialIcons")>]
    do ()
    
  3. 引用字体的时候直接写。例:从字体图标中找到“笔”并将其作为图片。Glyph是图标的 unicode 码。

    let penImg = Image.ImageFont(FontImageSource(Glyph="\ue254",FontFamily="MaterialIcons"))
    

    然后

    View.TabbedPage(children=[
            View.ContentPage(icon =penImg, title="笔记")
            View.ContentPage(icon=archiveImg, title="归档")])
    

标题栏

为了显示标题栏,给每个页面套了个NavigationPage

标题栏上的每个物件属于ToolbarItem,可以附着在Page型的toolbaritems下。

ToolbarItem可定义的有:

  • Command 绑定用户动作
  • CommandParameter 传送给控件的变量
  • Icon 图标
  • Text 显示的文字
  • Order 枚举参数,决定物品显示在一级菜单还是二级菜单中
  • Priority 显示顺序

例:

  let writePage = View.NavigationPage
            (icon = penImg, title = "笔记",
             toolbarItems=[View.ToolbarItem(icon=saveImg)],  //saveImg也是一个图片
             pages = <>)

  let archivePage = <>

而原TabbedPage变成:

View.TabbedPage(children = [ writePage; archivePage ])

输入框

自动改变大小

View.Editor(placeholder = "等待输入", autoSize = EditorAutoSizeOption.TextChanges)

归档页面

卡片布局,没什么特别要写的。实现主要是FlexLayout + StackLayout和一个Frame.

Frame不设置边框属性的话不显示阴影,有点奇怪。

交互

1. 点击卡片

卡片内容显示在输入界面里。

这里用的是假数据,后面再连数据库。

//在卡片里设置点击反应
gestureRecognizers = ([ View.TapGestureRecognizer(command = (fun () -> dispatch (Open entry.Id))) ]),
...

//update
| Open id -> let e=(search id model.Data) in {model with  NowWrite= e.Content; NowId = e.Id},Cmd.none

2. 使用代码切换 Tab

上文中“点击卡片返回输入界面”的实现。观察发现TabbedPage有属性currentPage可以get,set。所以尝试通过改变currentPage来改变当前页面的值。

引用TabbedPage使用了ViewRefViewRef是弱引用,初始为空,view函数被调用并显示后才填充值,所以将对ViewRef的处理放到了update函数中。

//声明
let tabRef = ViewRef<TabbedPage>()

...

//设置
View.TabbedPage
        (ref = tabRef, children = [ writePage; archivePage ])

修改上文点击反应,发送切换页面的消息。

gestureRecognizers =
    ([ View.TapGestureRecognizer
        (command =
            (fun () ->
                dispatch (Open entry.Id)
                dispatch (Switch 0))) ])

update 中处理页面切换。

    let update (msg: Msg) (model: Model) =
        match msg with
        | Switch index ->
            match tabRef.TryValue with
            | None -> ()
            | Some page -> page.CurrentPage <- page.GetPageByIndex(index)
            model, Cmd.none
        |  <>

3. 提取输入的标题

设计:输入笔记的第一行如果是# 标题,那么将第一行设置为标题。 //非常简陋

let cardParser (str:string) =
    let res = Regex("#(.*)").Match(str)
    if res.Success then (res.Value.[1..].Trim(), str.[res.Length..].Trim())
    else ("",str)

4. 弹窗

弹窗:Pop-ups

根据文档里的说法:

Pop-upsFabulous for Xamarin.Forms中的一种特殊情况:他们是视图的一部分,但是不和 UI 的其他部分共享生命周期。在Xamarin.Forms中,Pop-ups用当前页面的两种方法暴露:DisplayAlertDisplayActionSheet。 在Fabulous for Xamarin.Forms中我们仅描述页面该是什么样,并不访问 UI 控件本身,所以没有直接的方法使用这两个函数。不过我们可以使用Xamarin.Forms暴露的静态属性 Application.Current.MainPage

所以,点击删除时 (其实在原型图里忘画了)

| DeleteAlert id ->
            let alertResult =
                async {
                    let! alert =
                        Application.Current.MainPage.DisplayAlert("警告", "删除这条信息?", "是", "否")
                        |> Async.AwaitTask

                    return Delete (alert,id)
                }

            model, Cmd.ofAsyncMsg alertResult
| Delete (alert,id) -> <后续处理>