挣扎着写 FlipView For Xamarin.Form

Xamarin.Form 中没有 FlipView, 也没有 CarouselView , 有的只是一个 CarouselPage, 它是一个 Page, 不是一个 View !

Windows Phone 8.1 下有个控件叫 FlipView, 但是它不存于在 WP Siliverlight ! 而Xamarin.Form 的 WP 项目又是基于 WP Siliverlight 的.

Android 原生也没有 FlipView 类似的控件. IOS 下冒似也没有.

我急切的想弄一个这样的东西出来, 陆陆续续的花费了好几个周末的时间来搞它了, 怎奈相关文档等于零, 所有的所有基本靠:

  • 反编译 (期间用了 JustDecompirer ,感觉免费的东西还不错, 可这玩意要求你必须注册成它的用户啊! 我一堆用户名密码, 就是不记得在它上面注册的是什么啊! 三番五次搞的头大, 索兴用了 dotPeek , 感觉爽多了)
  • 猜, 没错, 是猜!

 

源码

https://github.com/gruan01/FlipView

目前在 Android 下的 ScrollTo 有点小问题, 不影响大局, 以后有时间改.

IOS 的还没有写, 要等下个周末.

 

约定:

  • 以下将 Xamarin.Form 简称为 XF.

 

效果图:

WP

挣扎着写 FlipView For Xamarin.Form_第1张图片

 

Android:

挣扎着写 FlipView For Xamarin.Form_第2张图片

 

源码大致说明:

1, WP (FlipView.WinPhone) 项目:

文件结构:

  • 在 Controls 目录下有个 FlipView.cs , 这个是 WP 的控件.
  • 在 Render 目录下有个 FlipViewRender.cs , 这个是 XF 的控件 FlipView 的渲染器.
  • 在 Themes 目录下有个 Generic.xaml , WP 近件 FlipView 的 xaml 结构在它里面定义. 这个文件是WP预定义文件, 和 WPF 一样.

 

Xaml 结构简单,不在赘述.

WP 控件 FlipView 需要注意一下, 这个控件继承自 ItemsControl, 但是如果直接将 XF 的控件做为 ItemsControl 的子控件, 只会渲染出一串文本(XF控件的类型名称) . 像这样:

挣扎着写 FlipView For Xamarin.Form_第3张图片

要解决这个问题,必须重写 PrepareContainerForItemOverride 方法:

1         protected override void PrepareContainerForItemOverride(DependencyObject element, object item) {
2             var ct = (ContentControl)element;
3             var render = RendererFactory.GetRenderer((View)item);
4             ct.Content = render;//render == render.ContainerElement;
5         }

 

即: 先获取 XF 控件的 Renderer, 在将这个 renderer 做为 ItemsControl 的子控件.

FlipViewRenderer 相对简单一些, 只需要 SetNativeControl 为这个 FlipView 就可以了.

1         protected override void OnElementChanged(ElementChangedEventArgs<Flip> e) {
2             base.OnElementChanged(e);
3 
4             var fv = new Controls.FlipView();
5 
6             fv.Orientation = System.Windows.Controls.Orientation.Horizontal;
7             fv.ItemsSource = this.Element.Children;
8             this.SetNativeControl(fv);
9         }

 

不需要设置 NativeControl 的 Width / Height, XF 会自动维护.

注意 fv.ItemsSource 为 XF 控件的 Children , 而不是 ItemsSource. 

我在这个地方停留了很长时间,试图找出将 XF 的 DataTemplete 转换为 WP 的 DataTemplate 的方法, 但均无效, 两个 DataTemplate 根本就不扯 !

看一下 XF 控件的 Children 是怎样生成的:

 1         private static void ItemsSourceChanged(BindableObject bindable, object oldValue, object newValue) {
 2             var flip = (Flip)bindable;
 3             flip.CalcChild();
 4         }
 5 
 6         private void CalcChild() {
 7             var children = new List<View>();
 8 
 9             if (this.ItemsSource == null || this.ItemTemplate == null)
10                 return;
11 
12             foreach (var o in this.ItemsSource) {
13                 var view = (View)this.ItemTemplate.CreateContent();
14                 view.BindingContext = o;
15                 children.Add(view);
16                 view.Parent = this;///Must , 如果不设置 Parent , 如果是 StackLayout , 不会显示
17             }
18 
19             this.Children = children;
20         }

 

var view = this.ItemTemplate.CreateContent()

这个 view 即上面所说的 PrepareContainerForItemOverride 方法的参数 item.

 

 

2, Android (FlipView.Droid) 项目

文件结构: 就一个 Renders/FlipViewRender.cs , 没有其它.

做 C# 好多年, 根本就没有想过做 JAVA, 所以对 Android 一直很陌生. Android SDK 提供多少原生控件都是现查资料的, 可想而知要我写一个在 Android 下使用的控件有多难!!!

一开始想通过 Android 的 ListView 控件来写这个渲染器, 但是 ListView 只有垂直的, 没有水平的. 试着按网上的示例写水平的 ListView , 但是又要涉及到 Adapter , 这些都不懂啊. 没过多长时间, 就放弃了.

最终是用  RelativeLayout + HorizontalScrollView + LinearLayout 写了个简单的.

代码也挺简单的, 只是有几点需要说明一下:

1, AddView

这个方法有个 LayoutParamas 类型的参数, 如果想让子View 适应父容器的 Width / Height 需要这样设:

root.AddView(this.Scroller, new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.MatchParent, LayoutParams.MatchParent));

 

LayoutParams.MatchParent 即适应父容器.

如果想指定高度为 20, 要这样写:

root.AddView(this.Scroller, new Android.Widget.RelativeLayout.LayoutParams(LayoutParams.MatchParent, 20));

 

除了 MatchParent 外, 还有个 WrapContent , 它的意思是适应内容(而不是适应父容器)

这部份感觉很奇葩啊.

 

2, 屏幕密度 Density

一开始直接在 Render 里获取到 XF 控件的 WidthRequest / HeightRequest布局子控件(Layout), 结果渲染出来的控件比子控件大出来不止一圈啊!

我TMD的还上色, 截屏, 扣图, 用 PS 来测量到底是哪个控件的尺寸不对啊! 搞的很崩溃啊! 想来想去都不知道是哪个地方出错了!

后来想起上个项目中, 我有用到 Density 这个东西, 试了一下, 果然是它引起的啊!

1             //Form 中的大小转换到 Android 下, 要跟据 密度(Density) 转换, 最终结果可能并不是 Form 中指定的
2             //var density = this.Context.Resources.DisplayMetrics.Density;
3             //var w = this.Element.WidthRequest * density;
4             //var h = this.Element.HeightRequest * density;

 

用 WidthRequest * density 获取到的数据才是最终要渲染的宽度啊!

还好, WidthRequest / HeightRequest 最终并没有使用, OnLayout 事件处理的时候,会把最终要渲染的SIZE 给出来.

1         protected override void OnLayout(bool changed, int l, int t, int r, int b) {
2             base.OnLayout(changed, l, t, r, b);
3             if (changed) {
4                 this.SetItems(r, b);
5                 this.SetPoints();
6                 this.SetPointColor(0, Color.White);
7             }
8         }

 

r : right 即宽度

b : bottom 即高度

 

3, XF 控件 (FlipView 项目) 

翻开 Controls/Flip.cs

里面重写了 OnSizeAllocated 方法:

1         protected override void OnSizeAllocated(double width, double height) {
2             base.OnSizeAllocated(width, height);
3 
4             foreach (var c in this.Children) {
5                 c.Layout(new Rectangle(0, 0, width, height));
6             }
7         }

 

c.Layout(new Rectangle(0,0, width, height))

即当给 XF 控件 Flip 分配大小的时候, 给它的子视图设定显示范围.

这一步很重要, 如果不设置的话, 你看到的将会是这样的:

挣扎着写 FlipView For Xamarin.Form_第4张图片

没错, 一片空白, 那几个小圆点是在 Renderer 里添加的.

 

还有另外一点, 在 CalcChild (上面有贴出) 方法中, 有一句:

view.Parent = this;///Must , 如果不设置 Parent , 如果是 StackLayout , 不会显示

 

这一句也搞了我整整一个周末!

如果不设置 Parent , Flip 的 DataTemplate 的根容器如果是 StackLayout, 它里面的任何东西都不显示, 无论你 ForceLayout, UpdateLayout , 统统都没用!

 

 

谢谢围观. 请点个赞!

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

好了,不扯了.

此方仅做为你 Xamarin.Form 路上的一个参考!

 

另: 想换换环境, 现寻一合适的职位, 架构师 / 开发经理.

MVC / EF / WCF / JS / CSS / ORACLE , 不敢说精通, 不相信我水平,你可以翻翻我的博客.

 

你可能感兴趣的:(form)