Xamarin.Forms 是一个开放源代码 UI 框架。
通过 Xamarin.Forms,开发人员可从单个共享基本代码生成 Xamarin.Android、Xamarin.iOS 和 Windows 应用程序。
Xamarin.Forms 优点 :可以跨平台的构建用户交互相关的东西,简而言之就是写一套代码三个平台通用,在此基础上也能够结合各平台特有的Xamarin本地代码混合一起。
Forms除了跨平台的好处之外,还支持Xaml编写用户界面,不用借助Goft.Mvvm.light等第三方框架原生支持数据绑定等。
这些项目如下:
- Notes - 此项目是 .NET Standard 库项目,其中包含所有共享逻辑代码和共享 UI。
- Notes.Android - 此项目包含 Android 特定代码,是 Android 应用程序的入口点。
- Notes.iOS - 此项目包含 iOS 特定代码,是 iOS 应用程序的入口点。
- Notes.UWP - 此项目包含通用 Windows 平台特定代码,是 UWP 应用程序的入口点。
Xamarin.Forms 应用程序采用与传统跨平台应用程序相同的构建方式。 共享代码通常位于 .NET Standard 库中,平台特定应用程序将使用此共享代码。 下图概要演示了 Notes 应用程序的这种关系:
若要最大限度重用启动代码,Xamarin.Forms 应用程序需有一个名为 App 的单个类,该类负责实例化应用程序在每个平台上将显示的首页,如以下代码示例所示:
using Xamarin.Forms;
namespace Notes
{
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new NotesPage());
}
...
}
}
页面导航
本质是将页面放入堆栈 每次pop栈顶元素
await Navigation.PopAsync();
控件
4 个主要控件组创建 Xamarin.Forms 应用程序的用户界面
- 页面
- 视图
- 布局
- 单元格
数据绑定
数据绑定连接两个对象,即源和目标。 源对象提供数据。 目标对象使用(并经常显示)来自源对象的数据。
例如,Editor(目标对象)通常会将其 Text 属性绑定到源对象中的公共 string 属性。 下图说明了这种绑定关系:
幕后的绑定框架源会将源对象中的更改自动推送到目标对象,且目标对象中的更改可选择性地推送回源对象。
创建数据绑定只需两个步骤:
- 目标对象的 BindingContext 属性必须设置为 源。
- 必须在目标和源之间建立绑定。 在 XAML 中,此过程可通过使用 Binding 标记扩展实现。
在 Notes 应用程序中,绑定目标是显示便笺的 Editor,而设置为 NoteEntryPage 的 BindingContext 的 Note 实例是绑定源。
NoteEntryPage 的 BindingContext 在页面导航过程中进行设置,如下面的代码示例所示:
async void OnNoteAddedClicked(object sender, EventArgs e)
{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = new Note()
});
}
async void OnListViewItemSelected(object sender, SelectedItemChangedEventArgs e)
{
if (e.SelectedItem != null)
{
await Navigation.PushAsync(new NoteEntryPage
{
BindingContext = e.SelectedItem as Note
});
}
}
NoteEntryPage 中的 Editor 随后会绑定到 Note 对象的 Text 属性:
<Editor Placeholder="Enter your note"
Text="{Binding Text}"
... />
在 Editor.Text 属性和源 对象的 Text 属性之间建立绑定。
Editor 中所做的更改将自动传播到 Note 对象。 同样,如果更改了 Note.Text 属性,Xamarin.Forms 绑定引擎也会更新 Editor 的内容。 这称为双向绑定。
数据存储
首先定义一个 Note 模型,该模型将在应用程序中存储有关每个便笺的数据。 使用 PrimaryKey 和 AutoIncrement 特性标记 ID 属性,以确保 SQLite.NET 数据库中的每个 Note 实例均具有 SQLite.NET 提供的唯一 ID。
using System;
using SQLite;
namespace Notes.Models
{
public class Note
{
[PrimaryKey, AutoIncrement]
public int ID { get; set; }
public string Text { get; set; }
public DateTime Date { get; set; }
}
}
接下来创建数据库、从中读取数据、向其写入数据以及从中删除数据
代码使用将数据库操作移动到后台线程的异步 SQLite.NET API。
using System.Collections.Generic;
using System.Threading.Tasks;
using SQLite;
using Notes.Models;
namespace Notes.Data
{
public class NoteDatabase
{
readonly SQLiteAsyncConnection _database;
//此外,NoteDatabase 构造函数将数据库文件的路径作为参数。
public NoteDatabase(string dbPath)
{
_database = new SQLiteAsyncConnection(dbPath);
_database.CreateTableAsync<Note>().Wait();
}
public Task<List<Note>> GetNotesAsync()
{
return _database.Table<Note>().ToListAsync();
}
public Task<Note> GetNoteAsync(int id)
{
return _database.Table<Note>()
.Where(i => i.ID == id)
.FirstOrDefaultAsync();
}
public Task<int> SaveNoteAsync(Note note)
{
if (note.ID != 0)
{
return _database.UpdateAsync(note);
}
else
{
return _database.InsertAsync(note);
}
}
public Task<int> DeleteNoteAsync(Note note)
{
return _database.DeleteAsync(note);
}
}
}
以下代码定义一个 Database 属性,该属性以单一实例的形式创建新 NoteDatabase 实例,将数据库的文件名作为参数传入 NoteDatabase 构造函数。
以单一实例的形式公开数据库的优势是,所创建的单一数据库连接在应用程序运行时保持打开状态,因此避免了每次执行数据库操作时打开和关闭数据库文件所产生的费用。类似于设计模式中的单例模式
using System;
using System.IO;
using Xamarin.Forms;
using Notes.Data;
namespace Notes
{
public partial class App : Application
{
static NoteDatabase database;
public static NoteDatabase Database
{
get
{
if (database == null)
{
database = new NoteDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Notes.db3"));
}
return database;
}
}
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new NotesPage());
}
protected override void OnStart()
{
// Handle when your app starts
}
protected override void OnSleep()
{
// Handle when your app sleeps
}
protected override void OnResume()
{
// Handle when your app resumes
}
}
}
StackLayout
StackLayout 中子视图的大小和位置取决于子视图 属性的值。
HorizontalOptions 使用从父布局的 X 轴中的剩余空间
VerticalOptions 使用从父布局在 Y 轴的剩余空间。
如果多个子级的布局设置为展开,按比例分配额外的空间。
HeightRequest WidthRequest 分别代表高度和宽度
Lable
设置更改 Label 视觉外观的属性。
TextColor 属性设置 Label 文本的颜色。 FontAttributes 属性将标签的字体设置为斜体,FontSize 属性设置字号。
此外,通过设置 TextDecorations 属性向 Label 应用下划线文本效果,并通过将 HorizontalOptions 属性设置为 Center 使其水平居中。
<Label Text="Welcome to Xamarin.Forms!"
TextColor="Blue"
FontAttributes="Italic"
FontSize="22"
TextDecorations="Underline"
HorizontalOptions="Center" />
Button
此代码将 Clicked 事件设置为将在下一步中创建的名为 OnButtonClicked 的事件处理程序。
<Button Text="Click me"
Clicked="OnButtonClicked" />
点击 Button 时,将执行 OnButtonClicked 方法。 sender 参数是负责触发 Clicked 事件的 Button 对象,还可用于访问 Button 对象。 此事件处理程序更新 Button 显示的文本。
void OnButtonClicked(object sender, EventArgs e)
{
(sender as Button).Text = "Click me again!";
}
修改 Button 声明,改变其视觉外观
此代码设置更改 Button 视觉外观的属性。 TextColor 属性设置 Button 文本的颜色,BackgroundColor 属性设置文本背景的颜色。 BorderColor 属性设置 Button 周围区域的颜色,BorderWidth 属性设置边框的宽度。 默认情况下,Button 是矩形,但可以通过将 CornerRadius 属性设置为合适的值来设定圆角。 另外,通过设置 WidthRequest 和 HeightRequest 属性来改变 Button 的大小。
<Button Text="Click me"
Clicked="OnButtonClicked"
TextColor="Blue"
BackgroundColor="Aqua"
BorderColor="Red"
BorderWidth="5"
CornerRadius="5"
WidthRequest="150"
HeightRequest="75" />
Entry
TextChanged 事件设置为名为 OnEntryTextChanged 的事件处理程序,将 Completed 事件设置为名为 OnEntryCompleted 的事件处理程序。
<Entry Placeholder="Enter text"
TextChanged="OnEntryTextChanged"
Completed="OnEntryCompleted" />
当 Entry 中的文本更改时,将执行 OnEntryTextChanged 方法。 sender 参数是负责触发 TextChanged 事件的 Entry 对象,还可用于访问 Entry 对象。 TextChangedEventArgs 参数提供了文本更改之前和之后的旧文本值和新文本值。
使用返回键完成 Entry 中的文本时,将执行 OnEntryCompleted 方法。 sender 参数是负责触发 TextChanged 事件的 Entry 对象,还可用于访问 Entry 对象。
向 Entry 输入的任何文本都将存储在 Text 属性中。
void OnEntryTextChanged(object sender, TextChangedEventArgs e)
{
string oldText = e.OldTextValue;
string newText = e.NewTextValue;
}
void OnEntryCompleted(object sender, EventArgs e)
{
string text = ((Entry)sender).Text;
}
自定义 Entry 行为的属性。 MaxLength 属性限制允许的 Entry 输入长度,将 IsSpellCheckEnabled 属性设置为 false 以禁用拼写检查。 同样,将 IsTextPredictionEnabled 属性设置为 false 以禁用文本预测和自动文本预测。 此外,IsPassword 属性确保使用密码字符(黑色圆圈)对输入的字符进行掩码。
<Entry Placeholder="Enter password"
MaxLength="15"
IsSpellCheckEnabled="false"
IsTextPredictionEnabled="false"
IsPassword="true" />
create app with xaml and c#
同样的UI 用xaml和c# 只是在共享代码段不同
同样的MVVM、Navigation 用xaml和c# 只是在公用代码段的 Views 不同 models 和 viewModels 都是一样的
Xaml
xaml 用来定义UI 像xml 但是多出了binding 和command 功能 和创造 出色的UI界面
除此之外 xaml 支持 hot-Reload 热加载 和 预览器
Xamarin 的XAML是 热重载的 改动save后 会立即显示 官网的热重载解释
hot-reload 好处是当我们修改xaml后 不需要restart project
xamarin.form UI with xaml
最外层是Grid控件,这个在wpf中是一个布局控件,就跟将窗体设置单元格差不过,可以依据它的row和column属性来设置行和列。 如下图 “*”=star 代表比例
//span跨越两列,因为一共2列所以是居中
xamarin.form MVVM in xaml
model = data view = UI viewModal 连接data 和UI层的 并且对它绑定的操作做出反应的逻辑层
如下图所示 INotifyPropertyChanged 用来监视 其绑定的属性值是否更改
在view 层我们设置binding 的command 当点击此button 会跳转到 viewmodel 进行相应逻辑操作
C#
single page UI in c#
Editor
noteEditor = new Editor
{
Placeholder = "Enter Note", //text无填写的情况下默认显示
BackgroundColor = Color.White,
Margin = new Thickness(10) //离左右边界的厚度
};
属性说明:
star 数值2:1 表示 不同的列所占用该行空间的比例
absolute 定义一个精确的像素或点密度
auto 自动 (不推荐)
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(2, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
grid.Children.Add(xamagonImage, 0, 0); //将image放在col=0 row=0
Grid.SetColumnSpan(xamagonImage, 2); //span跨越两列因为一共2列所以是居中
MVVM
using Xamarin.Forms;
namespace CodedUIMvvm
{
public class MainPage: ContentPage
{
public MainPage()
{
BackgroundColor = Color.PowderBlue;
BindingContext = new MainPageViewModel();
var xamagonImage = new Image
{
Source = "xamagon.png"
};
var noteEditor = new Editor
{
Placeholder = "Enter Note",
BackgroundColor = Color.White,
Margin = new Thickness(10)
};
noteEditor.SetBinding(Editor.TextProperty, nameof(MainPageViewModel.NoteText));
var saveButton = new Button
{
Text = "Save",
TextColor = Color.White,
BackgroundColor = Color.Green,
Margin = new Thickness(10)
};
saveButton.SetBinding(Button.CommandProperty, nameof(MainPageViewModel.SaveNoteCommand));
var deleteButton = new Button
{
Text = "Delete",
TextColor = Color.White,
BackgroundColor = Color.Red,
Margin = new Thickness(10)
};
deleteButton.SetBinding(Button.CommandProperty, nameof(MainPageViewModel.EraseNotesCommand));
var collectionView = new CollectionView
{
ItemTemplate = new NotesTemplate()
};
collectionView.SetBinding(CollectionView.ItemsSourceProperty, nameof(MainPageViewModel.Notes));
var grid = new Grid
{
Margin = new Thickness(20, 40),
ColumnDefinitions =
{
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) },
new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }
},
RowDefinitions =
{
new RowDefinition { Height = new GridLength(1.0, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(2.5, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(1.0, GridUnitType.Star) },
new RowDefinition { Height = new GridLength(2.0, GridUnitType.Star) },
}
};
grid.Children.Add(xamagonImage, 0, 0);
Grid.SetColumnSpan(xamagonImage, 2);
grid.Children.Add(noteEditor, 0, 1);
Grid.SetColumnSpan(noteEditor, 2);
grid.Children.Add(saveButton, 0, 2);
grid.Children.Add(deleteButton, 1, 2);
grid.Children.Add(collectionView, 0, 3);
Grid.SetColumnSpan(collectionView, 2);
Content = grid;
}
class NotesTemplate : DataTemplate
{
public NotesTemplate() : base(LoadTemplate)
{
}
static StackLayout LoadTemplate()
{
var textLabel = new Label();
textLabel.SetBinding(Label.TextProperty, nameof(NoteModel.Text));
var frame = new Frame
{
VerticalOptions = LayoutOptions.Center,
Content = textLabel
};
return new StackLayout
{
Children = { frame },
Padding = new Thickness(10)
};
}
}
}
}
Navigation in C#
当 view层有更改 会传递到 modelview
view层 MainPage.cs
MainPageViewModel.cs
例如我们选择goodbye 会看到selectNote .text中拿到了goodbye
然后将拿到的text 放入new 出来的DetailPage 并调用MainPage.navigation.pushAsync(new DetailPage(detailViewModel)) 将刚才的detailviewModel push到MainPage,MainPage相当于是一个后进先出的栈
var detailViewModel = new DetailPageViewModel{
NoteText = SelectedNote.Text
};
await Application.Current.MainPage.Navigation.PushAsync(new DetailPage(detailViewModel));
同理当我们进入detailpage 界面点击pop view界面会检测到pop按钮被点击 并调用viewModel层相应的方法
如下图 viewModel层exit方法就是将MainPage 的栈顶page pop出
exitButton.SetBinding(Button.CommandProperty, nameof(DetailPageViewModel.ExitCommand));