在 MasterPage.xaml 添加如下代码

  MasterPage 为”大纲“视图,即左侧显示 ListView
的页面。本项目的 MasterPage 分为两栏,分一级菜单与二级菜单,即置顶一个
ListView 与置底一个 ListView 。 ListView 的 ItemTemplate 与 UWP
稍有不同,左侧的填充矩形换成了 BoxView,二级菜单的上边线由 Border
换成了高度为1的 BoxView。代码如下

为了将WP8.1版本的项目快速升级到UWP版本,我没有使用官方示例的方式,而是在MainPage里放了两个Frame,在左侧的Frame里放一个列表Page,右侧Frame里放一个详情Page,这样之前的页面的UI和ViewModel都可以原封不动的拿过来,只需要单独处理VisualState的切换就可以了。

  3. MainPage.xaml

  待项目创建完成后,解决方案共包含四个项目:共享代码项目、 Android
项目、 iOS 项目、 UWP
项目。共享代码项目为存放共享页面的地方,个人觉得和类库还是有点区别的。

在开发XX新闻的过程中,UI部分使用了Master/Detail(大纲/细节)布局样式。Win10系统中的邮件App就是这种样式,左侧一个列表,右侧是详情页面。关于这种
样式的说明可参看MSDN文档:

  1. 字体设置

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HamburgerMenuDemo.MasterPage"
             Icon="hamburger.png"
             Title=" ">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--一级菜单-->
        <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!--BoxView 充当 Border-->
        <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />

        <!--二级菜单-->
        <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

这个例子可以拿来直接用。处理这种布局,主要是需要处理在PC/Mobile不同屏幕宽度下的具体显示内容,可以使用VisualState来实现不同状态的切换。

  弄了好久,Xamarin 太坑了,plist 的编辑器很不和谐。。。

<!--安卓空出状态栏的宽度-->
<ContentPage.Resources>
    <ResourceDictionary>
        <OnPlatform x:Key="padding"
              x:TypeArguments="Thickness"
              iOS="0,20,0,-6"
              Android="0,25,0,-6"
              WinPhone="0" />
    </ResourceDictionary>
</ContentPage.Resources>

下面以一个简单的例子来说一下是如何实现的。最近关于Win10
UWP的内容写了不少,都是在开发的过程中,把能单独拿出来的部分再重新做一遍demo,所以如果大家有兴趣的话可以照着动手敲一遍,自己实现出来才会理解的更深入。

图片 1

  同样的 MainPage.xaml.cs 中的代码也很简单,注释很详细

在微软官方的Sample里,有这种样式的代码示例,下载地址:

图片 2

  (2)更改 segmdl2.ttf 属性,复制到输出目录 =》 始终复制,生成操作
=》 BundleResource

二、页面布局

在MainPage中放置一个Grid控件,分为两列,左侧和右侧分别放两个Frame控件:

<Grid x:Name="gridMain" >
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="0" />
    </Grid.ColumnDefinitions>
    <Frame x:Name="masterFrame" Grid.Column="0" mvvm:StageManager.Beacon="masterFrame" x:FieldModifier="public"/>
    <Frame x:Name="detailFrame" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">
    </Frame>
</Grid>            

 

 

然后添加两个页面,MasterPage和DetailPage。

MasterPage中放置一个ListView控件,调用刚才的ItemsDataSource类,把数据绑定到ListView上,这个就不用详述了吧。还要给ListView设置项模板。这部分代码就不贴了。

现在让MainPage页面载入时,左侧的Frame自动显示MasterPage。

打开MainPage_Model.cs文件,取消对OnBindedViewLoad方法的注释,修改为以下代码:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    await base.OnBindedViewLoad(view);
    await StageManager["masterFrame"].Show(new MasterPage_Model());

}

 

 

好了,现在当MainPage页面加载完成后,名为masterFrame的Frame会显示MasterPage的内容,像下面这样:

图片 3

然后要实现点击项的时候,要在右侧的Frame里显示DetailPage。

打开MasterPage.xaml,在头部引入以下几个命名空间:

xmlns:Interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:Core="using:Microsoft.Xaml.Interactions.Core"
xmlns:Behaviors="using:MVVMSidekick.Behaviors"

 

然后修改项模板,使用SendToEventRouterAction,这个东东在以前的Blog里说过,在项模板的Grid里添加以下代码:

<Interactivity:Interaction.Behaviors>
    <Core:EventTriggerBehavior EventName="Tapped">
        <Behaviors:SendToEventRouterAction EventRoutingName="NewsItemTapped" EventData="{Binding}" IsEventFiringToAllBaseClassesChannels="True" />
    </Core:EventTriggerBehavior>
</Interactivity:Interaction.Behaviors>    

 

 

在MainPage加载的时候,注册NewsItemTapped事件,来处理点击事件。打开MainPage_Model.cs文件,在最后添加一个RegisterCommand方法:

private void RegisterCommand()
{
     MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
.Where(x => x.EventName == "NewsItemTapped")
.Subscribe(
e =>
    {
        NewsItem item = e.EventData as NewsItem;
        await StageManager["detailFrame"].Show(new DetailPage_Model(item));
    }
    ).DisposeWith(this);
}    

 

别忘了在Loaded事件里调用这个方法:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    this.RegisterCommand();
    await base.OnBindedViewLoad(view);
    await StageManager["masterFrame"].Show(new MasterPage_Model());
}

 

因为DetailPage_Model还没有可接收参数的构造函数,所以需要在DetailPage_Model里加两个构造函数,一个是无参的,一个是可接收参数的,同时还需要加一个可绑定的属性,用来显示内容:

public DetailPage_Model()
{ }


public DetailPage_Model(NewsItem item)
{
    CurrentNewsItem = item;
}



public NewsItem CurrentNewsItem
{
get { return _CurrentNewsItemLocator(this).Value; }
set { _CurrentNewsItemLocator(this).SetValueAndTryNotify(value); }
}

#region Property NewsItem CurrentNewsItem Setup

protected Property<NewsItem> _CurrentNewsItem = new Property<NewsItem> { LocatorFunc = _CurrentNewsItemLocator };

static Func<BindableBase, ValueContainer<NewsItem>> _CurrentNewsItemLocator = RegisterContainerLocator<NewsItem>("CurrentNewsItem", model => model.Initialize("CurrentNewsItem", ref model._CurrentNewsItem, ref _CurrentNewsItemLocator, _CurrentNewsItemDefaultValueFactory));

static Func<NewsItem> _CurrentNewsItemDefaultValueFactory = () => { return default(NewsItem); };

#endregion

 

这样在DetailPage里就可以接收到点击的是哪个NewsItem了,再绑定到界面上,我就随便放了个TextBlock:

<StackPanel x:Name="RootPanel" Grid.Row="1">
<TextBlock
Margin="8,0"
Style="{ThemeResource TitleTextBlockStyle}"
HorizontalAlignment="Left"
Text="{Binding CurrentNewsItem.Title}" />
<TextBlock
Margin="12,8"
HorizontalAlignment="Left"
MaxWidth="560"
Style="{ThemeResource BodyTextBlockStyle}"
Text="{Binding CurrentNewsItem.Text}"
EntranceNavigationTransitionInfo.IsTargetElement="True" />
</StackPanel>

 

运行一下看看,怎么点了没反应呢,原来在MainPage的Grid里,第一列就把宽度占满了,第二列无法显示了,来给两个列设置个宽度吧,第一列设置为2*,第二列设置为3*:

现在可以显示了:

图片 4

  同时,由于修改了样式,变成了状态栏覆盖 DrawerLayout
,需要给 MasterPage.xaml 中的根 Grid 赋值一个 Padding=”0,25,0,-6″,但
UWP 项目却不需要,这点我会在文末给出代码。

  ”大纲“的默认效果是 DrawerLayout
覆盖状态栏的,不太美观,需要修改样式。在 style.xml 中添加

图片 5

  

  和安卓一样,需要给 MasterPage.xaml 中的根 Grid 赋值一个
Padding=”0,20,0,-6″,我会在文末给出代码。

样式如下:

  和安卓一样,需要给 MasterPage.xaml 中的根 Grid 赋值一个
Padding=”0,20,0,-6″,我会在文末给出代码。

  MasterPage.xaml 最终代码

五、添加切换动画效果

我们还可以做的更美观一点。UWP默认的Page切换是有动画效果的,但这里因为只使用StateTrigger设置了Grid的列宽,当从DetailPage返回MasterPage的时候MasterPage一下子就显示出来了,感觉有点生硬。现在给切换加一个动画。

在NarrowAndBlankDetail的VisualState里,添加一段StoryBoard:

<Storyboard >
    <DoubleAnimation Storyboard.TargetName="gridMain" Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:0.6">
        <DoubleAnimation.EasingFunction>
            <CircleEase EasingMode="EaseOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
    <DoubleAnimation Storyboard.TargetName="gridMain" Storyboard.TargetProperty="(UIElement.RenderTransform).(CompositeTransform.TranslateX)" From="-100" To="0" Duration="0:0:0.3">
        <DoubleAnimation.EasingFunction>
            <CircleEase EasingMode="EaseOut" />
        </DoubleAnimation.EasingFunction>
    </DoubleAnimation>
</Storyboard>

 

设置透明度从0到1,同时有一个移动的效果。注意这里的StoryBoard.TargetProperty的写法,详细说明可以参考MSDN文档:

再次吐槽一下MSDN文档真是太难找了。版本太多。

在<VisualStateManager.VisualStateGroups>里添加Transitions:

<VisualStateGroup.Transitions>
    <VisualTransition From="NarrowAndNoBlankDetail" To="NarrowAndBlankDetail" ></VisualTransition>
</VisualStateGroup.Transitions>

 

同时要在gridMain里添加以下代码:

<Grid.RenderTransform>
    <CompositeTransform />
</Grid.RenderTransform>

 

不然动画无法起作用。

现在运行一下看看,返回的时候MasterPage也是从左侧渐变滑入的,效果好了不少。

这种方式基本可以把WP8.1的代码直接拿过来用,页面改动不大。如果您有更好的实现方式,欢迎留言讨论。

这篇基本就写到这里了。最近WP圈一片哀嚎,很多无奈的事情。但作为普通个人开发者来说,抱怨也没用,能做多少就做多少吧,总好过只吐槽。行动的意义永远大于口头空讲。

本文得到了礼物说开发者郑大神的鼎力支持。希望大家下载他的礼物说,做的非常漂亮。

预祝大家新春快乐!

最后给出demo下载:链接: 密码:ilar

 

public MainPage()
{
    InitializeComponent();

    // ListView 点击事件
    masterPage.primaryListView.ItemSelected += MasterPageItemSelected;
    masterPage.secondaryListView.ItemSelected += MasterPageItemSelected;

    // 设置 Windows 平台的“大纲”显示模式为折叠
    if (Device.RuntimePlatform == Device.Windows)
    {
        MasterBehavior = MasterBehavior.Popover;
    }
}

private void MasterPageItemSelected(object sender, SelectedItemChangedEventArgs e)
{
    var item = e.SelectedItem as MasterPageItem;

    if (item != null)
    {
        // 遍历 ListView 数据源,将选中项矩形显示,字体颜色设置成未选中
        foreach (MasterPageItem mpi in masterPage.primaryListView.ItemsSource)
        {
            mpi.Selected = false;
            mpi.Color = Color.Black;
        }
        foreach (MasterPageItem mpi in masterPage.secondaryListView.ItemsSource)
        {
            mpi.Selected = false;
            mpi.Color = Color.Black;
        }

        // 设置选中项
        item.Selected = true;
        item.Color = Color.DeepSkyBlue;

        // 跳转
        Detail = new NavigationPage((Page)Activator.CreateInstance(item.DestPage));

        // 取消 ListView 默认选中样式
        masterPage.primaryListView.SelectedItem = null;
        masterPage.secondaryListView.SelectedItem = null;

        // 关闭“大纲”
        IsPresented = false;
    }
}

代码如下

三、自定义StateTrigger

但是,这只是第一步,接下来需要处理在不同屏幕宽度下的适配问题,我们可以打开UWP版的邮件,拖动窗口缩放大小,观察页面内容变化,可以得出以下特性:

在PC上:

1、当宽度大于一定宽度时,Master和Detail是可以同时显示的,在刚打开程序没有点击邮件的时候,右侧的Detail实际上显示了一个空页面(有背景图片);

当逐步缩小宽度时,又分为两种情况:

2、如果Detail页为空页面时,缩小到一定宽度后,窗口只显示Master页面;

3、如果Detail页不为空,即显示邮件正文的时候,缩小到一定宽度后,窗口只显示Detail页面;

在Mobile上:

程序打开时,显示Master页面,相当于2;

点击邮件后,显示Detail页面,相当于3;

这样我们可以得出,不管是PC还是Mobile,需要处理三种状态的切换,我命名为:

NarrowAndBlankDetail

NarrowAndNoBlankDetail

Wide

通过处理这三种状态的切换,就可以实现类似邮件UWP版的效果了。有些同学可能会问,为什么不直接使用自带的AdaptiveTrigger呢,主要是这个AdaptiveTrigger只能根据宽度来设置,而目前的需求还需要根据Detail页面是否为空来处理,所以需要自定义一个Trigger了。

邮件UWP默认载入的时候有一个空页面,所以还需要添加一个BlankPage,这个页面相当于一个空页面,里面可以随便放点什么东西,比如背景图片啊,logo啊,或者广告什么的,Trigger会根据Detail页面是否显示这个BlankPage来进行处理。

先让MainPage载入时,默认左侧加载MasterPage,右侧加载BlankPage:

protected override async Task OnBindedViewLoad(MVVMSidekick.Views.IView view)
{
    this.RegisterCommand();
    await base.OnBindedViewLoad(view);
    StageManager["detailFrame"].Show(new BlankPage_Model());
    await StageManager["masterFrame"].Show(new MasterPage_Model());
}

 

好,运行效果是这样:

图片 6

现在来处理状态切换。关于StateTrigger,国外已经有人写了一个项目,实现了多种Trigger,见:

使用介绍见:http://www.sharpgis.net/post/2015/03/24/Using-Custom-Visual-State-Triggers

这个项目实现了n个实用的Trigger,但好可惜没有能满足我的要求的,还是自己动手吧。我参考了他的代码,继承了他的接口ITriggerValue,继承此接口的话可以用在CompositeStateTrigger里,为了方便以后使用按照这个接口来吧。

说一下主要的代码实现思路。

首先要定义一个枚举:

public enum MasterDetailState
{
/// <summary>
/// narrow and a blank detail page
/// </summary>
NarrowAndBlankDetail,

/// <summary>
/// narrow and detail page is not blank
/// </summary>
NarrowAndNoBlankDetail,

/// <summary>
/// wide
/// </summary>
Wide
}

 

页面宽度的变化,通过订阅ApplicationView.GetForCurrentView().VisibleBoundsChanged事件来处理,如果宽度大于720时如何,小于720时如何。当然这个720也可以传递属性进来,我懒得弄就写死在里面了。

同时,还需要一个DetailContent属性,这个属性需要绑定到第二个Frame的Content上,这样Trigger可以知道当前是不是BlankPage,通过以下代码来判断:

MVVMPage detailPage = (MVVMSidekick.Views.MVVMPage)DetailContent;
if (detailPage != null)
{
if (detailPage.BaseUri.ToString() == "ms-appx:///BlankPage.xaml")
{
System.Diagnostics.Debug.WriteLine("触发NarrowAndBlankDetail模式");
//CommonContext.Instance.CurrentBackRequestedHandlerType = BackRequestedHandlerType.MasterPage;
return MasterDetailState.NarrowAndBlankDetail;
}
else
{
System.Diagnostics.Debug.WriteLine("触发NarrowAndNoBlankDetail模式");
//CommonContext.Instance.CurrentBackRequestedHandlerType = BackRequestedHandlerType.DetailPage;
return MasterDetailState.NarrowAndNoBlankDetail;
}
}

 

全部代码如下:

图片 7图片 8

public class MasterDetailStateTrigger : StateTriggerBase, ITriggerValue

{





public MasterDetailStateTrigger()

{

if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)

{



var weakEvent =

new WeakEventListener<MasterDetailStateTrigger, ApplicationView, object>(this)

{

OnEventAction = (instance, source, eventArgs) => MasterDetailStatetateTrigger_MasterDetailStateChanged(source, eventArgs),

OnDetachAction = (instance, weakEventListener) => ApplicationView.GetForCurrentView().VisibleBoundsChanged -= weakEventListener.OnEvent

};

ApplicationView.GetForCurrentView().VisibleBoundsChanged += weakEvent.OnEvent;



}

}



private void MasterDetailStatetateTrigger_MasterDetailStateChanged(ApplicationView sender, object args)

{

UpdateTrigger();

}



private void UpdateTrigger()

{

IsActive = GetMasterDetailState() == MasterDetailState;



}













public MasterDetailState MasterDetailState

{

get { return (MasterDetailState)GetValue(MasterDetailStateProperty); }

set { SetValue(MasterDetailStateProperty, value); }

}



// Using a DependencyProperty as the backing store for MasterDetailState. This enables animation, styling, binding, etc...

public static readonly DependencyProperty MasterDetailStateProperty =

DependencyProperty.Register("MasterDetailState", typeof(MasterDetailState), typeof(MasterDetailStateTrigger), new PropertyMetadata(MasterDetailState.Wide, OnMasterDetailStatePropertyChanged));



private static void OnMasterDetailStatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{



var obj = (MasterDetailStateTrigger)d;

if (!Windows.ApplicationModel.DesignMode.DesignModeEnabled)

{

obj.UpdateTrigger();

}

}











public object DetailContent

{

get { return (object)GetValue(DetailContentProperty); }

set { SetValue(DetailContentProperty, value); }

}



// Using a DependencyProperty as the backing store for DetailContent. This enables animation, styling, binding, etc...

public static readonly DependencyProperty DetailContentProperty =

DependencyProperty.Register("DetailContent", typeof(object), typeof(MasterDetailStateTrigger), new PropertyMetadata(null, new PropertyChangedCallback(OnValuePropertyChanged)));



private static void OnValuePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)

{

var obj = (MasterDetailStateTrigger)d;

obj.UpdateTrigger();

}









internal MasterDetailState GetMasterDetailState()

{

System.Diagnostics.Debug.WriteLine("DetailContent为空:" + (DetailContent == null).ToString());

//第一种 窄屏模式 DetailFrame为空

if (Window.Current.Bounds.Width < 720)

{

System.Diagnostics.Debug.WriteLine("VisibleBounds.Width:" + ApplicationView.GetForCurrentView().VisibleBounds.Width.ToString());

System.Diagnostics.Debug.WriteLine("Window.Current.Bounds:" + Window.Current.Bounds.Width.ToString());

MVVMPage detailPage = (MVVMSidekick.Views.MVVMPage)DetailContent;

if (detailPage != null)

{

if (detailPage.BaseUri.ToString() == "ms-appx:///BlankPage.xaml")

{

System.Diagnostics.Debug.WriteLine("触发NarrowAndBlankDetail模式");

return MasterDetailState.NarrowAndBlankDetail;

}

else

{

System.Diagnostics.Debug.WriteLine("触发NarrowAndNoBlankDetail模式");

return MasterDetailState.NarrowAndNoBlankDetail;

}

}

else

{

return MasterDetailState.NarrowAndBlankDetail;

}

}

else

{

System.Diagnostics.Debug.WriteLine("触发Wide模式");

return MasterDetailState.Wide;

}

}







#region ITriggerValue



private bool m_IsActive;



/// <summary>

/// Gets a value indicating whether this trigger is active.

/// </summary>

/// <value><c>true</c> if this trigger is active; otherwise, <c>false</c>.</value>

public bool IsActive

{

get { return m_IsActive; }

private set

{

if (m_IsActive != value)

{

m_IsActive = value;

base.SetActive(value);

if (IsActiveChanged != null)

IsActiveChanged(this, EventArgs.Empty);

}

}

}



/// <summary>

/// Occurs when the <see cref="IsActive" /> property has changed.

/// </summary>

public event EventHandler IsActiveChanged;



#endregion ITriggerValue







}



public enum MasterDetailState

{

/// <summary>

/// narrow and a blank detail page

/// </summary>

NarrowAndBlankDetail,

/// <summary>

/// narrow and detail page is not blank

/// </summary>

NarrowAndNoBlankDetail,

/// <summary>

/// wide

/// </summary>

Wide

}

View Code

 

我在代码里输出了一些信息,调试的时候可以观察各种状态是在什么时候切换的。

然后在MainPage.xaml里
应用这个StateTrigger,首先,要在MainPage的ViewModel里添加一个object,用于绑定DetailFrame的内容:

/// <summary>
/// detailFrame的内容
/// </summary>
public object DetailContent
{
get { return _DetailContentLocator(this).Value; }
set { _DetailContentLocator(this).SetValueAndTryNotify(value); }
}

#region Property object DetailContent Setup

protected Property<object> _DetailContent = new Property<object> { LocatorFunc = _DetailContentLocator };

static Func<BindableBase, ValueContainer<object>> _DetailContentLocator = RegisterContainerLocator<object>("DetailContent", model => model.Initialize("DetailContent", ref model._DetailContent, ref _DetailContentLocator, _DetailContentDefaultValueFactory));

static Func<object> _DetailContentDefaultValueFactory = () => default(object);

#endregion

 

MainPage.xaml里的第二个Frame的Content绑定到这个DetailContent上:

<Frame x:Name="detailFrame" Content="{Binding DetailContent,Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">

 

注意Mode要设置为TwoWay,这样才可以让Trigger知道DetaiFrame的内容。在MainPage.xaml的根Grid里添加以下Trigger:

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState x:Name="NarrowAndBlankDetail">
            <VisualState.StateTriggers>
                <triggers:MasterDetailStateTrigger MasterDetailState="NarrowAndBlankDetail" DetailContent="{Binding DetailContent}" />
            </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="*" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="0" />
        </VisualState.Setters>
    </VisualState>
    <VisualState x:Name="NarrowAndNoBlankDetail">
        <VisualState.StateTriggers>
            <triggers:MasterDetailStateTrigger MasterDetailState="NarrowAndNoBlankDetail" DetailContent="{Binding DetailContent}" />
           </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="0" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="*" />
        </VisualState.Setters>
        </VisualState>
        <VisualState x:Name="Wide">
            <VisualState.StateTriggers>
                <triggers:MasterDetailStateTrigger MasterDetailState="Wide" DetailContent="{Binding DetailContent}" />
            </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="gridMain.ColumnDefinitions[0].Width" Value="2*" />
            <Setter Target="gridMain.ColumnDefinitions[1].Width" Value="3*" />
        </VisualState.Setters>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>    

 

还要把默认的gridMain的两列的宽度默认值分别改为*和0:

<Grid x:Name="gridMain" >
    <Grid.RenderTransform>
        <CompositeTransform />
    </Grid.RenderTransform>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="0" />
    </Grid.ColumnDefinitions>
    <Frame x:Name="masterFrame" Grid.Column="0" mvvm:StageManager.Beacon="masterFrame" x:FieldModifier="public"/>
    <Frame x:Name="detailFrame" Content="{Binding DetailContent,Mode=TwoWay}" HorizontalAlignment="Stretch" Grid.Column="1" mvvm:StageManager.Beacon="detailFrame" x:FieldModifier="public">
</Frame>

</Grid>

 

Trigger的意义很清楚了,Setter会根据不同的状态去设置gridMain两列的宽度来控制MasterPage和DetailPage的显示和隐藏:

当刚开始进入程序,左侧显示列表,右侧显示BlankPage,这时候如果宽度大于720,两个页面正常展示,如果页面宽度小于720,则只显示列表页;

如果页面宽度大于720的时候,点击列表,右侧正常显示详情;

如果页面宽度小于720,点击列表,列表会隐藏,只显示详情;

基本达到了文章开头提出的目的。

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HamburgerMenuDemo.MasterPage"
             Icon="hamburger.png"
             Title=" ">

    <!--安卓空出状态栏的宽度-->
    <ContentPage.Resources>
        <ResourceDictionary>
            <OnPlatform x:Key="padding"
                  x:TypeArguments="Thickness"
                  iOS="0,20,0,0"
                  Android="0,20,0,0"
                  WinPhone="0" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid Padding="{StaticResource padding}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--一级菜单-->
        <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!--BoxView 充当 Border-->
        <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />

        <!--二级菜单-->
        <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

  里面的属性有页面的标题 Title,左侧的图标 Icon,图标的字体
FontFamily,目的页面 DestPage,还有左侧的矩形显示 Selected 与 颜色
Color。由于要实现双向绑定,还要实现接口 INotifyPropertyChanged。要注意的是,Color
类型为 Xamarin.Forms 中的。

一、新建项目及Model

首先新建一个MVVM-Sidekick项目,命名为MasterDetailDemo。

添加Models目录,新建一个NewsItem:

public class NewsItem
{
public int Id { get; set; }
public DateTime DateCreated { get; set; }
public string Title { get; set; }
public string Text { get; set; }
}

 

 

新建一个ItemsDataSource类,用于模拟数据,可以返回一些数据。具体代码看Demo里的。

private static List<NewsItem> _items = new List<NewsItem>()

{

new NewsItem()
{}……
}

public static IList<NewsItem> GetAllItems()
{
return _items;
}

public static NewsItem GetItemById(int id)
{
return _items[id];
}

 

 

  四、Android
项目 HamburgerMenuDemo.Android

  三、共享代码项目
HamburgerMenuDemo 

四、处理返回键

当在手机上运行的时候,就会发现当点击列表显示DetailPage后,再按返回键直接退出程序了。因为还没有处理返回键事件。PC上也一样,程序左上角应该有个返回按钮。下面来处理返回事件。

基本思路是,点击返回后,应该先判断DetailPage是否可GoBack,如果可以就GoBack,直到返回最开始的BlankPage为止,这样StateTrigger会自动触发NarrowAndBlankDetail状态,显示MasterPage。

返回是处理SystemNavigationManager.GetForCurrentView().BackRequested这个事件,打开MainPage.xaml.cs文件,在OnNavigatedTo里订阅这个事件:

protected override void OnNavigatedTo(NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().BackRequested += CurrentView_BackRequested;
        //SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
    base.OnNavigatedTo(e);
}



protected override void OnNavigatedFrom(NavigationEventArgs e)
{
    SystemNavigationManager.GetForCurrentView().BackRequested -= CurrentView_BackRequested;
    base.OnNavigatedFrom(e);
}



private void CurrentView_BackRequested(object sender, BackRequestedEventArgs e)
{
    //判断DetailPage能否GoBack,如果可以GoBack则GoBack 显示BlankPage
    //其次判断MasterPage能否GoBack,如果可以GoBack则GoBack
    //如果不能GoBack,则提示是否退出
    if     (StrongTypeViewModel.StageManager["detailFrame"].CanGoBack)
    {
        e.Handled = true;
        StrongTypeViewModel.StageManager["detailFrame"].Frame.GoBack();
    }
    else if (StrongTypeViewModel.StageManager["masterFrame"].CanGoBack)
    {
        e.Handled = true;
        StrongTypeViewModel.StageManager["masterFrame"].Frame.GoBack();
    }
    else
    {
        //TODO 隐藏回退键 提示退出
        SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Collapsed;
    }
}

 

用户点击返回键的时候,首先看DetailPage能否GoBack,再看MasterPage能否GoBack,当没有可GoBack的时候就把返回键隐藏。

在PC上的返回键默认是隐藏的,还需要在导航到详情页的时候将其展示出来,修改MainPage_Model.cs文件里的RegisterCommand方法:

private void RegisterCommand()
{
MVVMSidekick.EventRouting.EventRouter.Instance.GetEventChannel<Object>()
.Where(x => x.EventName == "NewsItemTapped")
.Subscribe(
async e =>
{
NewsItem item = e.EventData as NewsItem;
SystemNavigationManager.GetForCurrentView().AppViewBackButtonVisibility = AppViewBackButtonVisibility.Visible;
await StageManager["detailFrame"].Show(new DetailPage_Model(item));
}
).DisposeWith(this);
}

 

现在运行一下,PC上也可以返回了。当第一次打开的时候,是这样 的:

图片 9

如果拖动窗口缩小,则只会显示MasterPage:

图片 10

当点击列表项时,会只显示DetailPage:

图片 11

点击左上角返回键,又只显示MasterPage了。

具体切换动画我不会截图,大家可以下载demo自己试试。

<dict>
    <key>UIAppFonts</key>
    <array>
      <string>segmdl2.ttf</string>
    </array>
  </dict>

  2. 修改 style.xml

代码如下

 

Padding="{StaticResource padding}"

  下面来修改一下 MainPage.xaml 。MainPage.xaml 为应用的入口页面,可在
App.xaml.cs 中更改。将 MainPage 中的根元素替换为 MasterDetailPage
。注释很详细,不多说了

  2. 修改 style.xml

<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:HamburgerMenuDemo"
             x:Class="HamburgerMenuDemo.MainPage"
             xmlns:views="clr-namespace:HamburgerMenuDemo.Views">

    <!--大纲视图-->
    <MasterDetailPage.Master>
        <!--引入 MasterPage 并给个名称,用于后台设置 MasterPage 传递过来的 ListView-->
        <local:MasterPage x:Name="masterPage" />
    </MasterDetailPage.Master>

    <!--细节视图-->
    <MasterDetailPage.Detail>
        <NavigationPage>
            <x:Arguments>
                <!--默认显示的页面-->
                <views:Page1 />
            </x:Arguments>
        </NavigationPage>
    </MasterDetailPage.Detail>

</MasterDetailPage>
<MasterDetailPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:HamburgerMenuDemo"
             x:Class="HamburgerMenuDemo.MainPage"
             xmlns:views="clr-namespace:HamburgerMenuDemo.Views">

    <!--大纲视图-->
    <MasterDetailPage.Master>
        <!--引入 MasterPage 并给个名称,用于后台设置 MasterPage 传递过来的 ListView-->
        <local:MasterPage x:Name="masterPage" />
    </MasterDetailPage.Master>

    <!--细节视图-->
    <MasterDetailPage.Detail>
        <NavigationPage>
            <x:Arguments>
                <!--默认显示的页面-->
                <views:Page1 />
            </x:Arguments>
        </NavigationPage>
    </MasterDetailPage.Detail>

</MasterDetailPage>

  和 UWP 的汉堡菜单一样,首先要创建一个类,作为导航的项目,用来绑定
ListView 。名字叫 MasterPageItem.cs 。

  但怎样通过 Xamarin.Forms ,将这一样式的汉堡菜单带入到 Android 与 iOS
中呢?

  2. MasterPage.xaml

  讲代码前首先来说说这种导航模式,官方称“大纲-细节模式”(MasterDetail)。左侧的汉堡菜单称为“大纲”(Master),右侧的页面称为“细节”(Detail)。Xamarin.Froms
为项目提供了若干种导航模式,“大纲-细节”为其中一种。

 

 

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterPage : ContentPage
{
    // 向 MainPage 传递控件
    public ListView primaryListView { get { return PrimaryListView; } }
    public ListView secondaryListView { get { return SecondaryListView; } }

    public MasterPage()
    {
        InitializeComponent();

        // 设置不同平台的字体路径
        string fontFamily;
        switch (Device.RuntimePlatform)
        {
            case "Android":
                fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                break;

            case "iOS":
                fontFamily = "Segoe MDL2 Assets";
                break;

            case "Windows":
                fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                break;

            case "WinPhone":
                fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                break;

            default:
                fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                break;
        }

        // 列表项
        var primaryItems = new List<MasterPageItem>() {
                new MasterPageItem
                {
                    Title = "Page1",
                    FontFamily = fontFamily,
                    Icon = "\xE10F",
                    Color = Color.DeepSkyBlue,
                    Selected = true,
                    DestPage = typeof(Page1)
                },
                new MasterPageItem
                {
                    Title = "Page2",
                    FontFamily = fontFamily,
                    Icon = "\xE11F",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(Page2)
                },
                new MasterPageItem
                {
                    Title = "Page3",
                    FontFamily = fontFamily,
                    Icon = "\xE12F",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(Page2)
                }
            };

        var secondaryItems = new List<MasterPageItem>() {
                new MasterPageItem
                {
                    Title = "设置",
                    FontFamily = fontFamily,
                    Icon = "\xE713",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(SettingPage)
                },
                new MasterPageItem
                {
                    Title = "关于",
                    FontFamily = fontFamily,
                    Icon = "\xE783",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(AboutPage)
                }
            };

        // ListView 数据绑定
        PrimaryListView.ItemsSource = primaryItems;
        SecondaryListView.ItemsSource = secondaryItems;

        // 设置二级菜单高度
        SecondaryListView.HeightRequest = 48 * secondaryItems.Count;
    }
}

  一、大纲-细节模式简介

public MainPage()
{
    InitializeComponent();

    // ListView 点击事件
    masterPage.primaryListView.ItemSelected += MasterPageItemSelected;
    masterPage.secondaryListView.ItemSelected += MasterPageItemSelected;

    // 设置 Windows 平台的“大纲”显示模式为折叠
    if (Device.RuntimePlatform == Device.Windows)
    {
        MasterBehavior = MasterBehavior.Popover;
    }
}

private void MasterPageItemSelected(object sender, SelectedItemChangedEventArgs e)
{
    var item = e.SelectedItem as MasterPageItem;

    if (item != null)
    {
        // 遍历 ListView 数据源,将选中项矩形显示,字体颜色设置成未选中
        foreach (MasterPageItem mpi in masterPage.primaryListView.ItemsSource)
        {
            mpi.Selected = false;
            mpi.Color = Color.Black;
        }
        foreach (MasterPageItem mpi in masterPage.secondaryListView.ItemsSource)
        {
            mpi.Selected = false;
            mpi.Color = Color.Black;
        }

        // 设置选中项
        item.Selected = true;
        item.Color = Color.DeepSkyBlue;

        // 跳转
        Detail = new NavigationPage((Page)Activator.CreateInstance(item.DestPage));

        // 取消 ListView 默认选中样式
        masterPage.primaryListView.SelectedItem = null;
        masterPage.secondaryListView.SelectedItem = null;

        // 关闭“大纲”
        IsPresented = false;
    }
}

  同样的 MainPage.xaml.cs 中的代码也很简单,注释很详细

图片 12

  2. MasterPage.xaml

图片 1

  ”大纲“的默认效果是 DrawerLayout
覆盖状态栏的,不太美观,需要修改样式。在 style.xml 中添加

 

<!--安卓空出状态栏的宽度-->
<ContentPage.Resources>
    <ResourceDictionary>
        <OnPlatform x:Key="padding"
              x:TypeArguments="Thickness"
              iOS="0,20,0,-6"
              Android="0,25,0,-6"
              WinPhone="0" />
    </ResourceDictionary>
</ContentPage.Resources>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HamburgerMenuDemo.MasterPage"
             Icon="hamburger.png"
             Title=" ">

    <!--安卓空出状态栏的宽度-->
    <ContentPage.Resources>
        <ResourceDictionary>
            <OnPlatform x:Key="padding"
                  x:TypeArguments="Thickness"
                  iOS="0,20,0,0"
                  Android="0,20,0,0"
                  WinPhone="0" />
        </ResourceDictionary>
    </ContentPage.Resources>

    <Grid Padding="{StaticResource padding}">
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--一级菜单-->
        <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!--BoxView 充当 Border-->
        <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />

        <!--二级菜单-->
        <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>

 

  MasterPage.xaml.cs 代码也需要讲下,不知是怎么回事,以上 Xaml
代码直接运行时两个菜单会显示不正常,只显示一个菜单,<RowDefinition
Height=”Auto” /> 在这个 ContentPage
里好像无效。因此我在后台代码设置了二级菜单的高度,也就是48 *
secondaryItems.Count。两个 ListView 需要通过属性的方式,向 MainPage
传递控件。字体路径各个项目不同,需要单独设置,我后面会说。MasterPage.xaml.cs
代码如下

  1. MasterPageItem.cs

  所谓 UWP 样式的汉堡菜单,我曾在“张高兴的 UWP
开发笔记:汉堡菜单进阶”里说过,也就是使用
Segoe MDL2 Assets 字体作为左侧 Icon,并且左侧使用填充颜色的矩形用来表示
ListView 的选中。如下图

  首先添加几个页面,根目录下添加一个 MasterPage.xaml
页面,用于”大纲视图“。添加一个 Views
文件夹,用于存放子页面,向其中添加3个界面:Page1、Page2、Page3。添加一个 MasterPageItem.cs
类。

  (1)将 segmdl2.ttf 字体文件直接放入 Resources 文件夹

  本示例是使用 Visual Studio 2017 创建的 Cross-Platform
项目,项目名为”HamburgerMenuDemo“,模板为空白项目。(GitHub:)

  1. 字体设置

  下面来修改一下 MainPage.xaml 。MainPage.xaml 为应用的入口页面,可在
App.xaml.cs 中更改。将 MainPage 中的根元素替换为 MasterDetailPage
。注释很详细,不多说了

<dict>
    <key>UIAppFonts</key>
    <array>
      <string>segmdl2.ttf</string>
    </array>
  </dict>

 

<item name="android:fitsSystemWindows">true</item>

  MasterPage.xaml.cs 代码也需要讲下,不知是怎么回事,以上 Xaml
代码直接运行时两个菜单会显示不正常,只显示一个菜单,<RowDefinition
Height=”Auto” /> 在这个 ContentPage
里好像无效。因此我在后台代码设置了二级菜单的高度,也就是48 *
secondaryItems.Count。两个 ListView 需要通过属性的方式,向 MainPage
传递控件。字体路径各个项目不同,需要单独设置,我后面会说。MasterPage.xaml.cs
代码如下

  在 MasterPage.xaml 添加如下代码

 

  本示例是使用 Visual Studio 2017 创建的 Cross-Platform
项目,项目名为”HamburgerMenuDemo“,模板为空白项目。(GitHub:)

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="HamburgerMenuDemo.MasterPage"
             Icon="hamburger.png"
             Title=" ">

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="1" />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>

        <!--一级菜单-->
        <ListView x:Name="PrimaryListView" VerticalOptions="StartAndExpand" SeparatorVisibility="None">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>

        <!--BoxView 充当 Border-->
        <BoxView BackgroundColor="Gray" Grid.Row="1" HorizontalOptions="FillAndExpand" />

        <!--二级菜单-->
        <ListView x:Name="SecondaryListView" Grid.Row="2" VerticalOptions="End" SeparatorVisibility="None" Margin="0,-6,0,0">
            <ListView.ItemTemplate>
                <DataTemplate>
                    <ViewCell>
                        <Grid HeightRequest="48">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="48"/>
                                <ColumnDefinition Width="*" />
                            </Grid.ColumnDefinitions>

                            <BoxView BackgroundColor="{Binding Color}" WidthRequest="5" HeightRequest="26" HorizontalOptions="Start" VerticalOptions="Center" IsVisible="{Binding Selected}" />
                            <Label x:Name="IconLabel" Text="{Binding Icon}" FontFamily="{Binding FontFamily}" TextColor="{Binding Color}" FontSize="16" HorizontalOptions="Center" VerticalOptions="Center" />
                            <Label Grid.Column="1" Text="{Binding Title}" TextColor="{Binding Color}" FontSize="14" VerticalOptions="Center" />
                        </Grid>
                    </ViewCell>
                </DataTemplate>
            </ListView.ItemTemplate>
        </ListView>
    </Grid>
</ContentPage>
public class MasterPageItem : INotifyPropertyChanged
{
    // 字体路径,用于引入 Segoe MDL2 Assets 字体
    public string FontFamily { get; set; }

    // 字体图标转义
    public string Icon { get; set; }

    // 标题
    public string Title { get; set; }

    // 目的页
    public Type DestPage { get; set; }

    // 用于显示左侧填充矩形,双向绑定
    private bool selected = false;
    public bool Selected
    {
        get { return selected; }
        set
        {
            selected = value;
            this.OnPropertyChanged("Selected");
        }
    }

    // 选中颜色,双向绑定 ( using Xamarin.Forms )
    private Color color = new Color();
    public Color Color
    {
        get { return color; }
        set
        {
            color = value;
            this.OnPropertyChanged("Color");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}
public class MasterPageItem : INotifyPropertyChanged
{
    // 字体路径,用于引入 Segoe MDL2 Assets 字体
    public string FontFamily { get; set; }

    // 字体图标转义
    public string Icon { get; set; }

    // 标题
    public string Title { get; set; }

    // 目的页
    public Type DestPage { get; set; }

    // 用于显示左侧填充矩形,双向绑定
    private bool selected = false;
    public bool Selected
    {
        get { return selected; }
        set
        {
            selected = value;
            this.OnPropertyChanged("Selected");
        }
    }

    // 选中颜色,双向绑定 ( using Xamarin.Forms )
    private Color color = new Color();
    public Color Color
    {
        get { return color; }
        set
        {
            color = value;
            this.OnPropertyChanged("Color");
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    public void OnPropertyChanged(string propertyName)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

  效果图:

  1. 字体设置

  不多说废话,看代码实在些。

  (1)将 segmdl2.ttf 字体文件直接放入 Resources 文件夹

  讲代码前首先来说说这种导航模式,官方称“大纲-细节模式”(MasterDetail)。左侧的汉堡菜单称为“大纲”(Master),右侧的页面称为“细节”(Detail)。Xamarin.Froms
为项目提供了若干种导航模式,“大纲-细节”为其中一种。

  MasterPage.xaml 最终代码

  1. 字体设置

 

 

  (2)更改 segmdl2.ttf 属性,复制到输出目录 =》 始终复制,生成操作
=》 BundleResource

图片 14

  2. Padding

  

  待项目创建完成后,解决方案共包含四个项目:共享代码项目、 Android
项目、 iOS 项目、 UWP
项目。共享代码项目为存放共享页面的地方,个人觉得和类库还是有点区别的。

  六、Padding 代码

图片 12

  (2)不要双击,右击 Info.plist ,查看代码,添加如下内容

  所谓 UWP 样式的汉堡菜单,我曾在“张高兴的 UWP
开发笔记:汉堡菜单进阶”里说过,也就是使用
Segoe MDL2 Assets 字体作为左侧 Icon,并且左侧使用填充颜色的矩形用来表示
ListView 的选中。如下图

  如果要添加其他的资源,可以自己新建一个 .plist
文件,新建的文件是正常显示资源列表的,添加完成后,复制代码到 Info.plist
即可。

  效果图:

  别忘了在 Grid 中引用资源

  里面的属性有页面的标题 Title,左侧的图标 Icon,图标的字体
FontFamily,目的页面 DestPage,还有左侧的矩形显示 Selected 与 颜色
Color。由于要实现双向绑定,还要实现接口 INotifyPropertyChanged。要注意的是,Color
类型为 Xamarin.Forms 中的。

  四、Android
项目 HamburgerMenuDemo.Android

  将 segmdl2.ttf 字体文件直接放入 Assets 文件夹下即可

  2. Padding

<item name="android:fitsSystemWindows">true</item>

 

  别忘了在 Grid 中引用资源

  五、iOS
项目 HamburgerMenuDemo.iOS

  (2)不要双击,右击 Info.plist ,查看代码,添加如下内容

  首先添加几个页面,根目录下添加一个 MasterPage.xaml
页面,用于”大纲视图“。添加一个 Views
文件夹,用于存放子页面,向其中添加3个界面:Page1、Page2、Page3。添加一个 MasterPageItem.cs
类。

  六、Padding 代码

  但怎样通过 Xamarin.Forms ,将这一样式的汉堡菜单带入到 Android 与 iOS
中呢?

  三、共享代码项目
HamburgerMenuDemo 

  一、大纲-细节模式简介

  和 UWP 的汉堡菜单一样,首先要创建一个类,作为导航的项目,用来绑定
ListView 。名字叫 MasterPageItem.cs 。

  同时,由于修改了样式,变成了状态栏覆盖 DrawerLayout
,需要给 MasterPage.xaml 中的根 Grid 赋值一个 Padding=”0,25,0,-6″,但
UWP 项目却不需要,这点我会在文末给出代码。

[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class MasterPage : ContentPage
{
    // 向 MainPage 传递控件
    public ListView primaryListView { get { return PrimaryListView; } }
    public ListView secondaryListView { get { return SecondaryListView; } }

    public MasterPage()
    {
        InitializeComponent();

        // 设置不同平台的字体路径
        string fontFamily;
        switch (Device.RuntimePlatform)
        {
            case "Android":
                fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                break;

            case "iOS":
                fontFamily = "Segoe MDL2 Assets";
                break;

            case "Windows":
                fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                break;

            case "WinPhone":
                fontFamily = "/Assets/segmdl2.ttf#Segoe MDL2 Assets";
                break;

            default:
                fontFamily = "segmdl2.ttf#Segoe MDL2 Assets";
                break;
        }

        // 列表项
        var primaryItems = new List<MasterPageItem>() {
                new MasterPageItem
                {
                    Title = "Page1",
                    FontFamily = fontFamily,
                    Icon = "\xE10F",
                    Color = Color.DeepSkyBlue,
                    Selected = true,
                    DestPage = typeof(Page1)
                },
                new MasterPageItem
                {
                    Title = "Page2",
                    FontFamily = fontFamily,
                    Icon = "\xE11F",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(Page2)
                },
                new MasterPageItem
                {
                    Title = "Page3",
                    FontFamily = fontFamily,
                    Icon = "\xE12F",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(Page2)
                }
            };

        var secondaryItems = new List<MasterPageItem>() {
                new MasterPageItem
                {
                    Title = "设置",
                    FontFamily = fontFamily,
                    Icon = "\xE713",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(SettingPage)
                },
                new MasterPageItem
                {
                    Title = "关于",
                    FontFamily = fontFamily,
                    Icon = "\xE783",
                    Color = Color.Black,
                    Selected = false,
                    DestPage = typeof(AboutPage)
                }
            };

        // ListView 数据绑定
        PrimaryListView.ItemsSource = primaryItems;
        SecondaryListView.ItemsSource = secondaryItems;

        // 设置二级菜单高度
        SecondaryListView.HeightRequest = 48 * secondaryItems.Count;
    }
}

  二、项目简介

  不多说废话,看代码实在些。

  1. MasterPageItem.cs

图片 14

  弄了好久,Xamarin 太坑了,plist 的编辑器很不和谐。。。

  如果要添加其他的资源,可以自己新建一个 .plist
文件,新建的文件是正常显示资源列表的,添加完成后,复制代码到 Info.plist
即可。

图片 2

  五、iOS
项目 HamburgerMenuDemo.iOS

  将 segmdl2.ttf 字体文件直接放入 Assets 文件夹下即可

 

  要注意的是 MasterPage.xaml 页面中的 Title
一定要给,要不然会报错,可以在后台 cs 文件中修改 Title 属性,也可以在
Xaml 根元素中修改 Title。Views 中的几个页面 Title
不给可以,但标题栏不会显示页面的 Title,不好看。

  二、项目简介

 

  要注意的是 MasterPage.xaml 页面中的 Title
一定要给,要不然会报错,可以在后台 cs 文件中修改 Title 属性,也可以在
Xaml 根元素中修改 Title。Views 中的几个页面 Title
不给可以,但标题栏不会显示页面的 Title,不好看。

  3. MainPage.xaml

  MasterPage 为”大纲“视图,即左侧显示 ListView
的页面。本项目的 MasterPage 分为两栏,分一级菜单与二级菜单,即置顶一个
ListView 与置底一个 ListView 。 ListView 的 ItemTemplate 与 UWP
稍有不同,左侧的填充矩形换成了 BoxView,二级菜单的上边线由 Border
换成了高度为1的 BoxView。代码如下

Padding="{StaticResource padding}"

相关文章