拉拉勾勾:拉勾客户端 山寨有意思

都说学以致用,怎么我入了 Xamarin 这个坑,就用不上呢?

闲了几个月,就憋了这个东西出来。

截图

Android 截图:

拉拉勾勾:拉勾客户端 山寨有意思_第1张图片拉拉勾勾:拉勾客户端 山寨有意思_第2张图片拉拉勾勾:拉勾客户端 山寨有意思_第3张图片

 

UWP 截图:

拉拉勾勾:拉勾客户端 山寨有意思_第4张图片拉拉勾勾:拉勾客户端 山寨有意思_第5张图片拉拉勾勾:拉勾客户端 山寨有意思_第6张图片拉拉勾勾:拉勾客户端 山寨有意思_第7张图片拉拉勾勾:拉勾客户端 山寨有意思_第8张图片

 

从截图来看, UWP 的界面已经处理的和 Android 的高度相似了. 这可是我花了无数时 精雕细琢 的 (虽然还是丑).

 

APP 下载

看完图,上几个包,仅供预览……还有些BUG,所以……

Android 4.4 以上:

ARM : http://pan.baidu.com/s/1eQT6MJo

X86 (VS Android Emulator 可用): http://pan.baidu.com/s/1sjZqe7b

UWP (仅限手机版) : http://pan.baidu.com/s/1o6RUATk

 

 

源码

https://github.com/gruan01/Xamarin.Forms.Lagou

 

API

拉勾官方 APP 使用的数据接口只有 ProtoBuf 格式,也就是说,除非你有它的实体类,否则要想解开这个 ProtoBuf 数据,那基本上是不可能的。

幸好,拉勾有手机版网站, 部分数据是 JSON 格式, 但部份数据还是要解析HTML才能得到。

因为使用的是手机版网站的数据,所以,用户体验不要指望和官方APP相比。

 

架构

1, Xamarin.Forms

Xamarin.Forms 2.0 开始支持 UWP , 所以选用了2.0.1.6492-pre1 ,但是当前对 UWP 的支持仅仅是 “预览”, 这个版本上还充斥着大量的 BUG,比如 (以下仅指 UWP 手机版,桌面版没试):

A: 页面上如果有 ToolbarItems , 程序会直接Crash。

B:Image 在 Grid 中有很奇葩的表现。

C: Grid 对齐问题。

目前这几个BUG的影响比较大,非常期待下次更新能解决这些问题。但是 Xamarin 团队最近好像并不着急,都一个月多了, 还没有放出下一个版本。

 

2,Caliburn.Micro

当前使用的是3.0 beta-2, 基本上很稳定。遇到的问题只有一个,在 UWP 下, BindableCollection.AddRange 导致 ListView 数据发生重置(Reset , 会直接从第一条开始重新显示),这个问题在 WP8 SL 上也有。

不知道是 CM 的问题还是 微软的问题。

 

3, XamlSpy

XamlSpy 目前提供的测试版,可以很方便的探测到呈现出来的结构,在UWP下帮了我不少忙。Android 下虽然也可以用,但是作用不大。

 

TabbedPage 数据绑定

之前是把 ViewModel 列表做为 ItemsSource 绑定到 TabbedPage 上,然后通过 CM 的 ViewLocator 功能,查找出 ViewModel 对应的视图,然后呈现。但是这样做有缺陷。

现在换一种全新的方法:Attached Property (BindableProperty.CreateAttached), 通过 Attached Property 的 PropertyChanged 事件对 TabbedPage 的 Children 直接进行修改:

 1     public class TabbedPageVMLocatorBinder {
 2 
 3         public static readonly BindableProperty VMsProperty =
 4             BindableProperty.CreateAttached<TabbedPageVMLocatorBinder, INotifyCollectionChanged>(
 5                 o => GetVMsProperty(o),
 6                 null,
 7                 propertyChanged: VMSChanged);
 8 
 9 
10         public static INotifyCollectionChanged GetVMsProperty(BindableObject obj) {
11             if (null == (TabbedPage)obj)
12                 throw new Exception("TabbedPageVMLocatorBinder only used for TabbedPage");
13 
14             return (INotifyCollectionChanged)obj.GetValue(VMsProperty);
15         }
16 
17         private static void VMSChanged(BindableObject bindable, INotifyCollectionChanged oldValue, INotifyCollectionChanged newValue) {
18             var tab = (TabbedPage)bindable;
19             var tmp = new Tmp(tab, newValue);
20         }
21 
22         private class Tmp : IDisposable {
23 
24             private TabbedPage Tab = null;
25 
26             public Tmp(TabbedPage tab, INotifyCollectionChanged vms) {
27                 vms.CollectionChanged += Vms_CollectionChanged;
28                 this.Tab = tab;
29                 Vms_CollectionChanged(vms, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
30             }
31 
32             private void Vms_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
33                 var collection = (IList)sender;
34                 e.Apply(
35                     //insert
36                     (o, i, b) => {
37                         Insert(o, i, b);
38                     },
39                     //remove
40                     (o, i) => {
41                         Remove(o, i);
42                     },
43                     //reset
44                     () => {
45                         Reset(collection);
46                     });
47             }
48 
49             private void Insert(object vm, int idx, bool b) {
50                 var page = LocatePage(vm);
51                 Tab.Children.Insert(idx, page);
52             }
53 
54             private void Remove(object vm, int idx) {
55                 Tab.Children.RemoveAt(idx);
56             }
57 
58             private void Reset(IList items) {
59                 Tab.Children.Clear();
60                 foreach (var o in items) {
61                     Tab.Children.Add(LocatePage(o));
62                 }
63             }
64 
65             private static Page LocatePage(object o) {
66                 if (o == null)
67                     throw new ArgumentNullException("o");
68 
69                 var vmView = ViewLocator.LocateForModel(o, null, null);
70                 if (vmView == null)
71                     throw new Exception("没有找到视图");
72                 ViewModelBinder.Bind(o, vmView, null);
73 
74                 var activator = o as IActivate;
75                 if (activator != null)
76                     activator.Activate();
77 
78                 var page = (Page)vmView;
79                 if (page != null) {
80                     page.Title = ((Screen)o)?.DisplayName;
81                     return page;
82                 } else
83                     return null;
84             }
85 
86             public void Dispose() {
87                 
88             }
89         }
90     }

 

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <TabbedPage xmlns="http://xamarin.com/schemas/2014/forms"
 3              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
 4              x:Class="Lagou.Views.TabView"
 5             xmlns:local="clr-namespace:Lagou;assembly=Lagou"
 6             local:TabbedPageVMLocatorBinder.VMs="{Binding Datas}"
 7             BackgroundColor="#666666"
 8             >
 9   
10 </TabbedPage>

 

 

说到 CreateAttached 必须指出一个奇葩, 使用的时候,要记住绕过它:

(错误)

1 public static readonly BindableProperty FontSizeProperty =
2     BindableProperty.CreateAttached<AttachedFontIcon, double>(
3         (double)o.GetValue(FontSizeProperty), 4         12
5     );

 如果写成这样,在获取这个 Property 的值的时候, 返回的将是一个 string 类型的数据,而非 double 类型。

 

除非你这样写:

(正确)

1 public static readonly BindableProperty FontSizeProperty =
2     BindableProperty.CreateAttached<AttachedFontIcon, double>(
3         o => GetFontSize(o),
4         12);
5         
6 public static double GetFontSize(BindableObject bindable) {
7     return (double)bindable.GetValue(FontSizeProperty);
8 }        

是不是有些奇葩?

 

Caliburn.Micro 的多视图功能

我在 http://www.cnblogs.com/xling/p/xamarin-forms-uwp-caliburn-micro.html 做过介绍, 但是伴随而来另外一个问题:View 无法找到对应的 ViewModel, 这里对它进行修正, 以使:

XXX.Views.Windows.XXXView 也可以对应到 XXX.ViewModels.XXXViewModel

做法就是通过获取 Type ,然后进行相应的替换。

 1         private void FixCM(SimpleContainer container) {
 2             var f = ViewLocator.LocateTypeForModelType;
 3             ViewLocator.LocateTypeForModelType = (type, bindable, context) => {
 4                 return f(type, bindable, context ?? Device.OS) ?? f(type, bindable, context);
 5             };
 6 
 7             var ps = string.Join("|", Enum.GetNames(typeof(TargetPlatform)).Select(p => string.Format(@"\.{0}", p)));
 8             var rx = new Regex(string.Format("({0})$", ps));
 9             var f2 = ViewModelLocator.LocateForViewType;
10             ViewModelLocator.LocateForViewType = viewType => {
11                 var vm = f2(viewType);
12                 if (vm == null) {
13                     if (rx.IsMatch(viewType.FullName)) {
14 var vmTypeName = rx.Replace(viewType.FullName, "ViewModel") 15 .Replace(".Views.", ".ViewModels."); 16 var vmType = Type.GetType(vmTypeName); 17 if (vmType != null) { 18 return container.GetInstance(vmType, null); 19 } 20 } 21 } 22 return vm; 23 }; 24 }

 

详见: https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou/App.cs

 

Android TabLayout 的 Tab 如何显示在底部

如果直接写的 Android 项目,完成这个功能, 只需要改动一下布局文件,分分钟钟的事。但是在 XF 中, 要想达到这个目的, 只能通过自定义 Renderer 来实现。

Xamarin.Forms 1.5.1 之后,添加了对 Material Design 的支持:

http://www.cnblogs.com/xling/p/Material-Design-For-Xamarin-Forms.html

在 Material Design 下, TabbedPage 是通过 Tablayout + ViewPager 实现的, 这就给我提供了改动的机会(之前是能过 ActionBar 实现的, 不好改):

我们只需要获取 TabbedPageRenderer 中的 TabLayout 和 ViewPager , 在 Layout 的时候,交换一下它俩的显示位置就可以了:

[assembly: ExportRenderer(typeof(TabbedPage), typeof(TabbedPageRender))]
namespace Lagou.Droid.Renders {
    public class TabbedPageRender : TabbedPageRenderer {

        private Android.Views.View formViewPager = null;
        private TabLayout tabLayout = null;

        protected override void OnElementChanged(ElementChangedEventArgs<TabbedPage> e) {
            base.OnElementChanged(e);

            this.formViewPager = this.GetChildAt(0); this.tabLayout = (TabLayout)this.GetChildAt(1); this.UpdateTabIcons();
            //this.UpdateTabHeader();
        }

        protected override void OnLayout(bool changed, int l, int t, int r, int b) {
            base.OnLayout(changed, l, t, r, b);

            // update layout , let tab on the bottom of the page
            // formViewPager upon tab.
            var w = r - 1;
            var h = b - t;
            if (w > 0 && h > 0) {
                int ypos = Math.Min(h, Math.Max(this.tabLayout.MeasuredHeight, this.tabLayout.MinimumHeight));
                this.formViewPager.Layout(0, -ypos, r, b - ypos);
                this.tabLayout.Layout(l, h - ypos, r, b);
            }
        }

 

注意, 要使用 Material Design, MainActivity 必须继承自 FormsAppCompatActivity。

 

说到 FormsAppCompatActivity , 这又里又延伸到另外一个问题:

自定义的 Renderer 没有执行 

如上面代码所示,扩展了 TabbedPageRenderer ,但是结果发现这个自定义的 Renderer 并没有执行。这是什么情况?

先看一下 Xamarin.Forms.Platform.Android.dll 这个 dll 的结构:

拉拉勾勾:拉勾客户端 山寨有意思_第9张图片

它包括 Xamarin.Platform.Android 和 Xamarin.Platform.Android.AppCompat 这两个 namespace

AppCompat 下的这几个 Renderer ,在另外一个 namespace 下,也同样存在一份。

 

在看一下 Renderer 是怎么注册的:

MainActivity  的OnCreate 法中:

global::Xamarin.Forms.Forms.Init(this, bundle);

 ->>>

拉拉勾勾:拉勾客户端 山寨有意思_第10张图片

->>>

拉拉勾勾:拉勾客户端 山寨有意思_第11张图片

如最后这张图所示,RegisterAll 首先做的是:排序,保证 Xamarin.Forms.Platform.Android.dll 的 assembly 是 array 中的第一个。

然后遍历这个 assembly array, 对 assembly 中的自定义 Renderer 进行注册。

也就是说:后出现的 Renderer 会覆盖前面注册过的 Renderer.

我觉得这个地方设计的不合理。

假如引用了两个 DLL, 这两个DLL都有自定义的 ButtonRenderer, 运行时到底会执行哪个 Renderer , 我想那只有听天由命了!!!

 

上面我说 Material Design 的 MainActivity 必须继承自 FormsAppCompatActivity ,

这个 FormsAppCompatActivity 的 LoadApplication 方法中, 会调用 RegisterHandlerForDefaultRenderer ,将 AppCompat 中的这几个 Renderer 做为默认的 Renderer.

 1     protected void LoadApplication(Xamarin.Forms.Application application)
 2     {
 3       if (!this.renderersAdded)
 4       {
 5         this.RegisterHandlerForDefaultRenderer(typeof (NavigationPage), typeof (NavigationPageRenderer), typeof (NavigationRenderer));
 6         this.RegisterHandlerForDefaultRenderer(typeof (TabbedPage), typeof (TabbedPageRenderer), typeof (TabbedRenderer));
 7         this.RegisterHandlerForDefaultRenderer(typeof (MasterDetailPage), typeof (MasterDetailPageRenderer), typeof (MasterDetailRenderer));
 8         this.RegisterHandlerForDefaultRenderer(typeof (Xamarin.Forms.Button), typeof (Xamarin.Forms.Platform.Android.AppCompat.ButtonRenderer), typeof (ButtonRenderer));
 9         this.RegisterHandlerForDefaultRenderer(typeof (Xamarin.Forms.Switch), typeof (Xamarin.Forms.Platform.Android.AppCompat.SwitchRenderer), typeof (SwitchRenderer));
10         this.RegisterHandlerForDefaultRenderer(typeof (Picker), typeof (Xamarin.Forms.Platform.Android.AppCompat.PickerRenderer), typeof (PickerRenderer));
11         this.RegisterHandlerForDefaultRenderer(typeof (Frame), typeof (Xamarin.Forms.Platform.Android.AppCompat.FrameRenderer), typeof (FrameRenderer));
12         this.RegisterHandlerForDefaultRenderer(typeof (CarouselPage), typeof (Xamarin.Forms.Platform.Android.AppCompat.CarouselPageRenderer), typeof (CarouselPageRenderer));
13       }
14 ...

 

而 LoadApplication 又必须出现在 Forms.Init 后面:

 1     public class MainActivity : FormsAppCompatActivity {
 2         protected override void OnCreate(Bundle bundle) {
 3             base.OnCreate(bundle);
 4 
 5             global::Xamarin.Forms.Forms.Init(this, bundle);
 6 
 7 。。。
 8 。。。
 9 
10             LoadApplication(new App(IoC.Get<SimpleContainer>()));

 

也就是说, 如果不巧你定义的就是 NavigationPage / TabbedPage / MasterDetailPage / Button / Switch / Picker / Frame / CarouselPage 的 Renderer, 那对不起, 在 FormsAppCompatActivity 下, 你写的 Renderer 会被取代!!!所以就出现了这个问题:

自定义的 Renderer 没有执行!

 

那如何解决呢? 当然是在调用 RegisterHandlerForDefaultRenderer 把自定义的 Renderer 给注册回去了!

只不过,这个方法是 Private 的, 无法直接调用, 但是不妨碍你使用反射。

 

使用 FontAwesome 做为 TabbedPage 的图标

看到图示中的图标没有? 全是字体!为啥用字体而不用图片,我就不说了。

TabbedPage 的图标,为其子 Page 的 Icon 属性, 当前并不支持字体。怎么办呢? 你可以从 TabbedPage 派生出一个控件,然后写对的 Renderer 就行了。

我不这样做,直接用 Attached Property:

 1     public class AttachedFontIcon {
 2 
 3         public static readonly BindableProperty GlyphProperty =
 4             BindableProperty.CreateAttached<AttachedFontIcon, string>(
 5                 o => (string)o.GetValue(GlyphProperty),
 6                 string.Empty);
 7 
 8         public static readonly BindableProperty FontFamilyProperty =
 9             BindableProperty.CreateAttached<AttachedFontIcon, string>(
10                 o => GetFontFamily(o),
11                 string.Empty);
12 
13         public static readonly BindableProperty FontSizeProperty =
14             BindableProperty.CreateAttached<AttachedFontIcon, double>(
15                 //Bug, If direct GetValue without Function, xaml value can't convert to target type.
16                 // Use Function will not have this issue.
17                 o => GetFontSize(o), //(double)o.GetValue(FontSizeProperty),
18                 12);
19 
20         public static readonly BindableProperty ColorProperty =
21             BindableProperty.CreateAttached<AttachedFontIcon, Color>(
22                 o => GetColor(o),//(Color)o.GetValue(ColorProperty),
23                 Color.Default);
24 
25         public static Color GetColor(BindableObject bindable) {
26             var color = (Color)bindable.GetValue(ColorProperty);
27             return color;
28         }
29 
30         public static double GetFontSize(BindableObject bindable) {
31             return (double)bindable.GetValue(FontSizeProperty);
32         }
33 
34         public static string GetFontFamily(BindableObject obj) {
35             return obj.GetValue(FontFamilyProperty) as string;
36         }
37     }

 

 

 1 <?xml version="1.0" encoding="utf-8" ?>
 2 <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
 3              xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
 4              x:Class="Lagou.Views.IndexView"
 5 
 6              xmlns:local="clr-namespace:Lagou;assembly=Lagou"
 7              local:AttachedFontIcon.Glyph="&#xf015;"
 8              local:AttachedFontIcon.FontFamily="Fonts/FontAwesome.otf"
 9              local:AttachedFontIcon.Color="Green"
10              local:AttachedFontIcon.FontSize="20"
11   >

 

 

然后在自定义的 TabbedPageRenderer 中,获取这些 Attached Property, 如果获取的到, 就根据这些值生成对应的字体图标。

 1 private void UpdateTabIcons() {
 2     var tabLayout = this.tabLayout;
 3     if (tabLayout.TabCount != this.Element.Children.Count)
 4         return;
 5 
 6     for (int i = 0; i < this.Element.Children.Count; ++i) {
 7         var page = this.Element.Children[i];
 8         if (string.IsNullOrEmpty(page.Icon)) {
 9             var glyph = (string)page.GetValue(AttachedFontIcon.GlyphProperty); 10             if (!string.IsNullOrWhiteSpace(glyph)) {
11 
12                 SpannableStringBuilder sb = new SpannableStringBuilder();
13                 sb.Append(glyph);
14 
15                 var font = (string)page.GetValue(AttachedFontIcon.FontFamilyProperty);
16                 if (!string.IsNullOrWhiteSpace(font)) {
17                     var span = new CustomTypefaceSpan(font);
18                     sb.SetSpan(span, 0, glyph.Length, SpanTypes.ExclusiveExclusive);
19                 }
20 
21                 //var size = (int)(double)page.GetValue(AttachedFontIcon.FontSizeProperty);
22                 sb.SetSpan(new AbsoluteSizeSpan(40), 0, glyph.Length, SpanTypes.ExclusiveExclusive);
23 
24                 sb.Append("\n");
25                 sb.Append(page.Title);
26 
27                 //Must set app:tabTextAppearance textAllCaps to false.
28                 // See : tabs.xml,
29                 //if not do this, span will not work not work
30                 tabLayout.GetTabAt(i).SetText(sb); 31             }
32         }
33     }
34 }

 

可以看到, 这个图标,其实是通过 SpannableString 配合 TypefaceSpan 做出来的,它的本质就是一个文本而而。

需要注意的是, 如果要使 TypefaceSpan 在 TabLayout 的 Text  起作用,必须修改 textAllCaps 为 false.

具体参见:

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.Droid/Renders/TabbedPageRender.cs

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.Droid/Resources/layout/tabs.xml

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.Droid/Resources/values/styles.xml

 

 

修改 UWP 的长相

拉拉勾勾:拉勾客户端 山寨有意思_第12张图片

这是未加修饰的 UWP 的长相,坑爹是吧。

 

1,在标题栏上添加一个 Toogle Button

拉拉勾勾:拉勾客户端 山寨有意思_第13张图片

XF 都会有一个 NavigationPage ,所有的其它的 Page 都在这个 NavigationPage 中显示。

这个 NavigationPage 在 UWP 项目中,对应的是一个 自定义控件:PageControl ,派生自 ContentControl 。

但是 UWP 的 Xaml 文件都被编译成 XBF (Xaml Binary File) 了。 当前,还没有一个有效的工具能将 XBF 文件反编译成 XAML 文件。

所以无法得知这个 PageControl 到底包含些什么,还好,有 XamlSpy 这个工具: 从结构上可以看出,其实这个 PageControl 就一个标题,一个内容, 大胆写了一下它的样式:

 1     <Style TargetType="xm:PageControl">
 2         <Setter Property="Template">
 3             <Setter.Value>
 4                 <ControlTemplate>
 5                     <Grid>
 6                         <Grid.RowDefinitions>
 7                             <RowDefinition Height="auto" />
 8                             <RowDefinition MinHeight="100" />
 9                         </Grid.RowDefinitions>
10 
11                         <Grid Grid.Row="0" Background="#00BCD4">
12                             <Grid.ColumnDefinitions>
13                                 <ColumnDefinition Width="auto" />
14                                 <ColumnDefinition Width="auto" />
15                                 <ColumnDefinition />
16                             </Grid.ColumnDefinitions>
17 
18                             <ToggleButton x:Name="splitViewToggle" Style="{StaticResource SplitViewTogglePaneButtonStyle}" Grid.Row="0" Grid.Column="0" />
19                             <ToggleButton x:Name="backToggle" Style="{StaticResource BackToggleButtonStyle}" Grid.Row="0" Grid.Column="1" Visibility="Collapsed" />
20                             <TextBlock Name="title" Text="{Binding Title}" Grid.Row="0" Grid.Column="2" VerticalAlignment="Center" />
21                         </Grid>
22 
23                         <ContentPresenter x:Name="presenter" Grid.Row="1" Grid.ColumnSpan="2" />
24 
25                     </Grid>
26                 </ControlTemplate>
27             </Setter.Value>
28         </Setter>
29     </Style>

 

标题,内容,外加两个 ToggleButton, 上图的效果就出来了。

 

 

2,移除 MasterDetailPage 上的 Toogle Button , 因为 NavigationPage 上已经加了。

拉拉勾勾:拉勾客户端 山寨有意思_第14张图片

用反编译工具可以看到 MasterDetailPage 的 NativeControl 是一个自定义的控件: MasterDetailControl。

同样不知道它怎么布局的。用 XamlSpy 查看,就发现两个有用的, 一个 SplitView 和一个 ToogleButton,  我们的目标就是把这个 ToogleButton 去掉。

 1     <Style TargetType="xm:MasterDetailControl">
 2         <Style.Setters>
 3             <Setter Property="Template">
 4                 <Setter.Value>
 5                     <ControlTemplate>
 6                         <SplitView 
 7                             x:Name="masterDetailSplitView" 
 8                             OpenPaneLength="300"
 9                             >
10                             <SplitView.Pane>
11                                 <Grid>
12                                     <ContentPresenter 
13                                     x:Name="MasterPresenter"
14                                     HorizontalAlignment="Stretch"
15                                         VerticalAlignment="Stretch"
16                                     Content="{Binding Master, Converter={StaticResource renderConverter}}" />
17                                 </Grid>
18                             </SplitView.Pane>
19 
20                             <ContentPresenter 
21                                 x:Name="DetailPresenter" 
22                                 Content="{Binding Detail, Converter={StaticResource renderConverter}}" />
23 
24                         </SplitView>
25                     </ControlTemplate>
26                 </Setter.Value>
27             </Setter>
28         </Style.Setters>
29     </Style>

 

注意 renderConverter, 

<xm:ViewToRendererConverter x:Key="renderConverter" />

 

因为 Master, Detail 这两个数据只是 XF 的视图,UWP 并不识别它们,需要把它们用 ViewToRendererConverter 进行转换。

到此,第二个目标完成, 但是点那个 ToggleButton , SplitView 并没有反应。

 

3, 响应第1步中添加的那两个 ToogleButton

在 MainPage 的构造函数添加它的 Load 事件处理程序:

 1         private void MainPage_Loaded(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
 2             var btn = this.FindChildControl<ToggleButton>("splitViewToggle");
 3             if (btn != null)
 4                 btn.Click += Btn_Click;
 5 
 6             var backBtn = this.FindChildControl<ToggleButton>("backToggle");
 7             if (backBtn != null)
 8                 backBtn.Click += BackBtn_Click;
 9         }
10 
11         private async void BackBtn_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
12             await Lagou.App.Current.MainPage.Navigation.PopAsync();
13         }
14 
15         private void Btn_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e) {
16             var spv = this.FindChildControl<SplitView>("masterDetailSplitView");
17             if (spv != null)
18                 spv.IsPaneOpen = !spv.IsPaneOpen;
19         }

 

这两个 ToogleButton 什么时候显示、什么时候隐藏,在 自定义的 NavigationPageRender 中处理:

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.UWP/Renders/NavigationPageRender.cs

 

4,将 TabbedPage 的 Tab 显示在底部

TabbedPage 在 UWP 中的 NativeControl 就是 Pivot.

所以先重写 Pivot 的样式,太长,就不贴了:

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.UWP/Pivot.xaml

然后把这个 Pivot.xaml 加入 ResourceDictionary 中,运行,结果发现,根本就没有应用这个新加的样式。

看一下 TabbedPageRenderer , 原来是指定了 Style 为 TabbedPageStyle。我尝试重新指定 Style , 但是运行就报什么 DesiredSize 异常。

没有办法,只好照着原来的 Renderer 重新写了一个:

https://github.com/gruan01/Xamarin.Forms.Lagou/blob/master/Lagou/Lagou.UWP/Renders/TabbedPageRender.cs

并指定 ItemTemplate 和 HeaderTemplate

 1     <DataTemplate x:Key="TabbedPageItemDataTemplate">
 2         <render:TabbedPagePresenter Content="{Binding Converter={StaticResource pageRenderConverter}}" />
 3     </DataTemplate>
 4 
 5     <DataTemplate x:Key="TabbedPageHeaderDataTemplate">
 6         <StackPanel>
 7             <ContentControl Content="{Binding Converter={StaticResource tabbedPageIconConverter}}" HorizontalContentAlignment="Center" />
 8             <TextBlock Text="{Binding Title}" FontSize="12" />
 9         </StackPanel>
10     </DataTemplate>

 

至于为什么使用 TabbedPagePresenter, 我没有看太懂, 只是原样抄出来而已(这个类为 internal 的类)

 

 

吐槽

Renderer 的设计显的有些封闭, 不够开放,太多 private / internal 了,要想自定义 Renderer 可不是件容易的事!

---------------

好啦, 让我看到你们的膝盖!

 

 

另外:

求职:架构师 、开发经理

10年工作经验

C# / MVC / WPF / WCF / JS / CSS  / SQL 应有尽有

你可能感兴趣的:(拉拉勾勾:拉勾客户端 山寨有意思)