背景简述


自动轮播视图(CarouselView)在现在App中的地位不言而喻,绝大多数的App中都有类似的视图,无论是WebApp还是Native App。在安卓、iOS以及Windows(UWP)开发中,有一些控件可以很方便的来实现类似的效果。

  1. ViewPager(安卓)
  2. UIScrollView(iOS)
  3. FlipView(UWP)

Xamarin.Forms怎么实现自动轮播视图呢?


Xamarin.Forms有自己的一套布局系统,结合各平台特性,也可以实现一个比较好的自动轮播视图。

上次介绍我实现的一个多页面水平切换布局中,提到我使用了一个叫做ViewPanel的自定义布局,他与自动轮播视图相比,只是缺少了无线滚动和自动轮播,这次也以这个布局为基础,来实现自动轮播视图。

核心依然是ViewPanel在各个平台中的具体实现:

Portable:

...
public static readonly BindableProperty ChildrenProperty = BindableProperty.Create("Children", typeof(IList), typeof(ViewPanel), propertyChanged: OnChildrenChanged);
public IList Children
{
get { return (IList)this.GetValue(ChildrenProperty); }
set { SetValue(ChildrenProperty, value); }
}
...

依赖属性Children是一个集合类型,它用来存储需要在ViewPanel中显示的视图,一般子视图的都从Xamarin.Forms.View派生或者是他本身

其次,ViewPanel能交互,需要实现一个事件,一个方法

  • event EventHandler SelectChanged:当ViewPanel中显示的元素改变时提供通知,并且提供OnSelectChanged()来触发该事件

    * void select():用于设置ViewPanel需要显示的子视图(实际Select会是一个委托,因为ViewPanel并不能设置当前显示的内容,需要调用各平台一些特定的方法实现)

安卓:

直接利用Renderer实现

ViewPanelRenderer : ViewRenderer<ViewPanel, ViewPager>

在安卓平台上,ViewPanel直接利用ViewPager来实现,所以ViewPanel对子元素的布局等方法都会无效,所有的子元素布局,显示状态都由ViewPager来管理,ViewPanel的作用只限于提供子视图。而ViewPager中子视图的创建删除都由相应的Adapter来实现,这儿用到的是ViewPagerAdapter

ViewPagerAdpter需要的子视图的类型是Android.Views.View,而上面提到,ViewPanel提供的子视图类型是Xamarin.Forms.View,所以在添加Xamarin.Forms.View类型视图到ViewPagerAdpter中的时候,需要完成一次转换,实则是获取Xamarin.Forms.View类型对象对应在安卓平台中的Renderer,实现方法如下:

//view is Xamarin.Forms.View
var renderer = Platform.CreateRenderer(view);
var viewGroup = renderer.ViewGroup; //viewGroup is Android.Views.View

需要注意,虽然子试图的布局直接由ViewPager来管理,但是ViewPager本身的位置,大小是可以由ViewPanel自己或者他的上层布局决定的。如果它的父布局没有约束他的位置大小,那么他可以通过在ViewPanel中重写的OnMeasure方法来自定义自己的大小:

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{
...
//最简单的就是返回固定尺寸,但通常不这么写,一般根据它的子视图位置大小等信息,来相应的设置他自己的尺寸,测量子元素的尺寸可以调用`Measure()`方法;
return new SizeRequest(new Size(385, 400));
}

完成了ViewPanel视图的显示,还需要实现交互部分:

  • 订阅ViewPagerPageSelected事件,再订阅方法中调用ViewPanelOnSelectChanged()方法,用于通知订阅了ViewPanelSelectChanged事件的所有对象;

  • ViewPanel的属性Select是委托类型,通过为该属性赋值,真正设置ViewPanel显示的子视图(调用ViewPager的SetCurrentItem()方法);

iOS:

iOS的ViewPanel实际是利用iOS中UIScrollView实现,唯一需要用Renderer实现的,就是设置UIScrollViewPagingEnabled属性为Ture,这样该滚动条就可以按页滚动了

实现逻辑如下:

ViewPanel继承自ScrolView,设置为水平方向滚动,然后设置其Content为一个水平方向的StackLayout,把要显示的子试图添加到StackLayout中。这样,只要StackLayout的宽度超出ScrolView的显示宽度后,就会出现水平滚动条,通过实现Renderer设置滚动条的PagingEnabled属性,就能每次滚动都完整的滚动一个子视图的宽度,如果子视图的宽度恰好为页面宽度,那就有了轮播图的效果。

为了让子视图的宽度就是ScrollView的可视宽度,需要重写该ScrollViewOnMeasureLayoutChildren方法。可以自定义一个继承自StackLayoutHorizentalStackLayout来重写以上两个方法。

protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
{ var measuredList = new List<SizeRequest>();
foreach (var item in this.Children)
{
measuredList.Add(item.Measure(ViewPanel.MeasureWidth, double.PositiveInfinity));
}
if (Children == null || Children.Count <= 0)
{
return new SizeRequest(new Size(ViewPanel.MeasureWidth, 0));
}
//ViewPanel.Panel.Width就是滚动条可视宽度
Size size = new Size(ViewPanel.Panel.Width * Children.Count(), measuredList.Select(m => m.Request.Height).OrderByDescending(m => m).First());
return new SizeRequest(size, size);
} protected override void LayoutChildren(double x, double y, double width, double height)
{
double posX = 0;
foreach (var item in this.Children)
{
item.Layout(new Rectangle(posX, y, ViewPanel.MeasureWidth, height));
posX += ViewPanel.MeasureWidth;
}
}

现在有一个问题,在ViewPanel中,我们定义了Children属性,用来存放子视图,但是在iOS中,StackLayout的属性Children和她并不相同,所以我们要做一次他们的同步,同步发生在ViewPanelChildren属性改变的时候,如下:

static void OnChildrenChanged(BindableObject sender, Object oldValue, Object newValue)
{
...
var viewPanel = sender as ViewPanel;
var stackLayout = viewPanel.Content as StackLayout;
stackLayout.Children.Clear();
foreach (View item in viewPanel.Children)
{
stackLayout.Children.Add(item);
}...
}

至此,同样视图显示部分就完成了,还剩交互部分,和安卓中一样,设计两个部分:

  • 订阅UIScrollViewScrollView_DecelerationEnded事件,再订阅方法中计算当前选中的索引,然后调用ViewPanel的OnSelectChanged()方法,用于通知订阅了ViewPanelSelectChanged事件的所有对象;

      private void ScrollView_DecelerationEnded(object sender, EventArgs e)
    {
    var index = (int)(_viewPanel.ScrollX / _viewPanel.Width); if (_viewPanel.Width / 2 < (_viewPanel.ScrollX % _viewPanel.Width))
    {
    index++;
    } _viewPanel.CurrentIndex = index;
    _viewPanel.OnSelectChanged();
    }
  • ViewPanel的属性Select是委托类型,通过为该属性赋值,真正设置ViewPanel显示的子视图(根据索引来计算滚动条的水平位置,并设置他);

      public void Select(int index, bool animate = true)
    {
    var perWidth = _viewPanel.Width;
    _viewPanel.CurrentIndex = index;
    _viewPanel.ScrollToAsync(index * perWidth, _viewPanel.ScrollY, animate);
    }

实现了ViewPanel,如何利用他实现自动轮播?


之前介绍到,ViewPanel就是阉割版的自动轮播视图,相比自动轮播,只少了两块儿

  1. 无限滚动

    逻辑如下图:实际添加到显示ViewPanel中的子视图比设定的多两个,第一个设置为设定子视图的最后一个,最后一个设置为设定子视图的第一个。结合下图以向右滚动为例(红色),当滚动到索引为3(黑色标号)的子视图,也就是设定子视图的最后一个,此时继续向右滚动,滚动到索引为4的子视图,他和索引为1的子视图显示内容相同,当滚动完成后,继续滚动到索引为1的子视图,这次滚动很特殊,没有任何动画效果,直接跳转,因为滚动前后显示的视图相同,所以肉眼看不出任何区别,给人以无限滚动的假象。

  2. 自动轮播

    这个简单,设置Timer即可。

总结


自动轮播视图(CarouselView)的核心思想就是这些,其他具体代码就不在这儿贴出,文末留出GitHub地址。在实现中,遇到一些问题或是新的,总结如下:

  • 在自定义布局中,OnMeasure方法不是100%会被调用的,这个布局的大小是否已经被约束;

    下面是我摘抄的一段话,来解释这个:

As you’ve seen, it is not guaranteed that the OnSizeRequest override will be called. The method doesn’t need to be called if the size of the layout is governed by its parent rather than its children. The method definitely will be called if one or both of the constraints are infinite, or if the layout class has nondefault settings of VerticalOptions or HorizontalOptions. Otherwise, a call to OnSizeRequest is not guaranteed and you shouldn’t rely on it.

  • Renderer实现中,可以利用Xamarin已经为我们提供的Renderer,而不是自己利用ViewRenderer去自定义,这样很大程度上能避免去写一些iOS、安卓和UWP中相关的代码。这次实践中iOS平台下的ViewPanel就直接派生自ScrollViewRenderer

  • 依赖属性,自定义布局的知识在自定义一个控件,Renderer的时候是非常重要的

  • ······

本次实践相关连接:

GitHub项目地址:cjw1115/PivotPage

Xamarin自定义布局系列——支持无限滚动的自动轮播视图CarouselView的更多相关文章

  1. Xamarin自定义布局系列——ListView的一个自定义实现ItemsControl(横向列表)

    在以前写UWP程序的时候,了解到在ListView或者ListBox这类的列表空间中,有一个叫做ItemsPannel的属性,它是所有列表中子元素实际的容器,如果要让列表进行横向排列,只需要在Xaml ...

  2. Xamarin自定义布局系列——瀑布流布局

    Xamarin.Forms以Xamarin.Android和Xamarin.iOS等为基础,自己实现了一整套比较完整的UI框架,包含了绝大多数常用的控件,如下图 虽然XF(Xamarin.Forms简 ...

  3. Xamarin自定义布局系列——PivotPage,多页面切换控件

    PivotPage ---- 多页面切换控件 PivotPage是一个多页面切换控件,类似安卓中的ViewPager和UWP中的Pivot枢轴控件. 起初打算直接通过ScrollView+StackL ...

  4. viewpager循环滚动和自动轮播的问题

    ViewPager是一个常用的android组件,不过通常我们使用ViewPager的时候不能实现左右无限循环滑动,在滑到边界的时候会看到一个不能翻页的动画,可能影响用户体验.此外,某些区域性的Vie ...

  5. iOS 简易无限滚动的图片轮播器-SDCycleScrollView

    @interface ViewController () <</span>SDCycleScrollViewDelegate> @end @implementation Vie ...

  6. 10 个 jQuery 的无限滚动的插件:

    很多社交网站都使用了一些新技术来提高用户体验,而无限滚动的翻页技术就是其中一项,当你页面滑到列表底部时候无需点击就自动加载更多的内容. 下面为你推荐 10 个 jQuery 的无限滚动的插件: 1.  ...

  7. Android 高级UI设计笔记09:Android如何实现无限滚动列表

    ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们提供了一个良好,整洁的用户体验 ...

  8. Android 高级UI设计笔记09:Android实现无限滚动列表

    1. 无限滚动列表应用场景: ListView和GridView已经成为原生的Android应用实现中两个最流行的设计模式.目前,这些模式被大量的开发者使用,主要是因为他们是简单而直接的实现,同时他们 ...

  9. Xamarin.Forms: 无限滚动的ListView(懒加载方式)

    说明 在本博客中,学习如何在Xamarin.Forms应用程序中设计一个可扩展的无限滚动的ListView.这个无限滚动函数在默认的Xamarin.Forms不存在,因此我们需要为此添加插件.在这里我 ...

随机推荐

  1. vue学习笔记(一)关于事件冒泡和键盘事件 以及与Angular的区别

    一.事件冒泡 方法一.使用event.cancelBubble = true来组织冒泡 <div @click="show2()"> <input type=&q ...

  2. Videojs视频插件在React中的应用

    1.介绍video.js视频插件 1.1 简单介绍 Video.js是一个通用的在网页上嵌入视频播放器的JS库,支持电脑端和移动端.Video.js自动检测浏览器对Html5的支持情况,如果不支持Ht ...

  3. quartz任务时间调度入门使用

    Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现. 作为一个优秀的开源调度框架,Quartz 具有以下特点: 强大的调度功能,例如支持丰富多样 ...

  4. ORACLE获取表信息方法

    获取表: select table_name from user_tables; //当前用户的表 select table_name from all_tables; //所有用户的表 select ...

  5. 【iOS】7.4 定位服务->2.1.3.2 定位 - 官方框架CoreLocation 功能2:地理编码和反地理编码

    本文并非最终版本,如果想要关注更新或更正的内容请关注文集,联系方式详见文末,如有疏忽和遗漏,欢迎指正. 本文相关目录: ================== 所属文集:[iOS]07 设备工具 === ...

  6. nmap安装过程

    nmap是一个网络扫描和主机检测工具. 功能:1.扫描目标主机开放的端口 2.扫描目标主机特定端口是否关闭 3.路由跟踪(到目标主机所经过的网络节点及其通过时间) 4.扫描一个网段下的所有IP 5.探 ...

  7. iOS开发之UIView的常见属性

    1.所有控件都继承自UIView,UIView的常见属性如下: @property(nonatomic,readonly) UIView *superview;获得自己的父控件对象 @property ...

  8. ATS来了,网页HTTP访问怎么办?

    推荐理由 ATS(App Transport Security),是苹果在WWDC 15提出的,苹果将收紧http的访问,这样会造成我们周边的许多站点和应用均不能正常访问,这里就对ATS进行了简单分析 ...

  9. 模块化规范Common.js,AMD,CMD

    随着网站规模的不断扩大,嵌入网页中的javascript代码越来越大,开发过程中存在大量问题,如:协同开发,代码复用,大量文件引入,命名冲突,文件依赖. 模块化编程称为迫切的需求. 所谓的模块,就是实 ...

  10. lucene原理及源码解析--核心类

    马云说:大家还没搞清PC时代的时候,移动互联网来了,还没搞清移动互联网的时候,大数据时代来了. 然而,我看到的是:在PC时代搞PC的,移动互联网时代搞移动互联网的,大数据时代搞大数据的,都是同一伙儿人 ...