Xamarin.Form 中没有 FlipView, 也没有 CarouselView , 有的只是一个 CarouselPage, 它是一个 Page, 不是一个 View !
Windows Phone 8.1 下有个控件叫 FlipView, 但是它不存于在 WP Siliverlight ! 而Xamarin.Form 的 WP 项目又是基于 WP Siliverlight 的.
Android 原生也没有 FlipView 类似的控件. IOS 下冒似也没有.
我急切的想弄一个这样的东西出来, 陆陆续续的花费了好几个周末的时间来搞它了, 怎奈相关文档等于零, 所有的所有基本靠:
源码
https://github.com/gruan01/FlipView
目前在 Android 下的 ScrollTo 有点小问题, 不影响大局, 以后有时间改.
IOS 的还没有写, 要等下个周末.
约定:
效果图:
WP
Android:
源码大致说明:
文件结构:
Xaml 结构简单,不在赘述.
WP 控件 FlipView 需要注意一下, 这个控件继承自 ItemsControl, 但是如果直接将 XF 的控件做为 ItemsControl 的子控件, 只会渲染出一串文本(XF控件的类型名称) . 像这样:
要解决这个问题,必须重写 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.
文件结构: 就一个 Renders/FlipViewRender.cs , 没有其它.
做 C# 好多年, 根本就没有想过做 JAVA, 所以对 Android 一直很陌生. Android SDK 提供多少原生控件都是现查资料的, 可想而知要我写一个在 Android 下使用的控件有多难!!!
一开始想通过 Android 的 ListView 控件来写这个渲染器, 但是 ListView 只有垂直的, 没有水平的. 试着按网上的示例写水平的 ListView , 但是又要涉及到 Adapter , 这些都不懂啊. 没过多长时间, 就放弃了.
最终是用 RelativeLayout + HorizontalScrollView + LinearLayout 写了个简单的.
代码也挺简单的, 只是有几点需要说明一下:
这个方法有个 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 , 它的意思是适应内容(而不是适应父容器)
这部份感觉很奇葩啊.
一开始直接在 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 即高度
翻开 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 分配大小的时候, 给它的子视图设定显示范围.
这一步很重要, 如果不设置的话, 你看到的将会是这样的:
没错, 一片空白, 那几个小圆点是在 Renderer 里添加的.
还有另外一点, 在 CalcChild (上面有贴出) 方法中, 有一句:
view.Parent = this;///Must , 如果不设置 Parent , 如果是 StackLayout , 不会显示
这一句也搞了我整整一个周末!
如果不设置 Parent , Flip 的 DataTemplate 的根容器如果是 StackLayout, 它里面的任何东西都不显示, 无论你 ForceLayout, UpdateLayout , 统统都没用!
谢谢围观. 请点个赞!
--------------------
好了,不扯了.
此方仅做为你 Xamarin.Form 路上的一个参考!
另: 想换换环境, 现寻一合适的职位, 架构师 / 开发经理.
MVC / EF / WCF / JS / CSS / ORACLE , 不敢说精通, 不相信我水平,你可以翻翻我的博客.