新的一天新的 Flag,突然想整 F#,做不来就回退。
软件原型
一个快速笔记软件。点击进软件就是写作界面,关闭软件或按下保存键则归档进入保存卡片。
原型图:
安装
- 装 vs
- 按照官网的方法,打开 vs 命令行,输入
dotnet new -i Fabulous.XamarinForms.Templates
- 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
布局
使用字体图标
-
字体放入共用工程内(和工程名相同,此处为 Endless),然后在字体的属性栏中,将生成操作设置为
嵌入的资源
(Build Action:EmbeddedResource
)。 -
在共用工程下,建立
AssemblyInfo.fs
(或者在别的文件里),注册资源:namespace Endless open Xamarin.Forms [<assembly:ExportFont("MaterialIcons-Regular.ttf", Alias = "MaterialIcons")>] do ()
-
引用字体的时候直接写。例:从字体图标中找到“笔”并将其作为图片。
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
使用了ViewRef。ViewRef
是弱引用,初始为空,在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-ups
是Fabulous for Xamarin.Forms
中的一种特殊情况:他们是视图的一部分,但是不和 UI 的其他部分共享生命周期。在Xamarin.Forms
中,Pop-ups
用当前页面的两种方法暴露:DisplayAlert
和DisplayActionSheet
。 在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) -> <后续处理>