天天看点

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

本节书摘来自异步社区《android开发进阶:从小工到专家》一书中的第2章,第2.1节重要的view控件,作者 何红辉,更多章节内容可以访问云栖社区“异步社区”公众号查看

第2章 创造出丰富多彩的ui—view与动画

android开发进阶:从小工到专家

在第一章中,我们说到android的用户界面构成,实际上就是activity由一个搭载着视图树的window构成。作为与用户直接交互的元素,ui控件变得尤为重要。本章将介绍部分常用且重要的控件、自定义控件、动画等内容,使我们进一步认识view,进入更丰富多彩的视图世界。

2.1 重要的view控件

通常来说用户界面都是由activity组成,activity中关联了一个phonewindow创建,在这个窗口下则管理了一颗视图树。这颗视图树的顶级视图就是一个viewgroup类型的decorview,decorview下就是各个视图控件。这样一来就组成了android丰富多彩的ui元素。如图2-1所示。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

本章我们并不会从textview、button等最基本控件谈起,正如我们前文所说的,会介绍部分重要的ui控件以及它们的基本原理,本章的重点是掌握自定义view以及动画。通过了解重要控件的基本原理以及自定义view,使我们能够深入了解view系统,并且能够有能力创建出自己所需的view。

2.1.1 listview与gridview

对于用android开发来说,最重要的控件应该非listview莫属。它以列表的形式展示具体内容,并且能够根据数据的长度自适应显示。如图2-2和图2-3所示。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

列表数据的显示需要4个元素,分别为:

(1)用来展示列表的listview;

(2)用来把数据映射到listview上的adapter;

(3)需要展示的数据集;

(4)数据展示的view模板。

首先自然是listview控件,但是该控件只负责加载、管理视图(每项数据称为item view),至于有多少项数据、每一项数据是如何显示的它并不关心。而这一切都是交给adapter类实现,通过adapter模式,用户只需覆写特定的几个函数就可以将listview的每项数据构建出来。需要实现的adapter函数为:

(1)getcount()函数——获取数据的个数;

(2)getitem(int)函数——获取position位置的数据;

(3)getitemid(int)函数——获取position位置的数据id,一般直接返回position即可;

(4)getview(int, view,viewgroup)函数——获取position位置上的item view视图。

因为adapter中含有要显示的数据集合,数据集合中的元素个数也就是要展示的item view的个数,通过adapter的getcount()函数返回;而每个数据的获取则通过adapter的getitem(int)函数实现,根据索引直接访问集合中的元素即可;每个item view则是通过getview函数实现,在这个函数中用户必须构建item view,然后将该position位置上数据绑定到item view上。这样一来,数据就和视图结合在一起了。

当listview加载时会根据数据的个数来创建item view,然后根据该view的索引从数据集合中获取数据,调用getview获取具体的视图,并且与数据绑定。但是,并不是有多少数据项就会产生多少item view,android采用了视图复用的形式来避免创建过多的item view,这样能够非常有效地提升性能和降低内存占用率。具体的设计如图2-4所示。

在处理数据量较大时,listview会构建铺满屏幕所需的item view个数,当屏幕向下滚动时,第一项数据将会滚出屏幕的可见范围之内,并且进入listview的一个recycler中,recycler会将该视图缓存,如图2-4中的item 1。而此时第8项也需要加载,listview首先会从recycler中获取视图,如果视图存在,那么用户可以直接使用该缓存视图,或者重新创建新的视图。当然,这些步骤都是在adapter中完成的,一个典型的getview函数大致如下:

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

getview函数的position就表示该视图是第几项数据,convertview就表示缓存的item view,parent表示该item view的父视图,对于listview来说这个parent就代表listview本身。这里最重要的就是convertview参数,如果有缓存那么该参数不为空,此时直接复用该视图;否则需要重新创建一个新的视图,最后绑定数据并且将该item view返回。

我们说到listview只会展示有限数量的item view,例如8个item view就能够铺满屏幕,那么即使数据项有1000个,通过复用机制item view可以只产生8个,这样既节约内存又能很大程度上提高运行效率。复用item view机制也是优化listview等集合组件最重要的手段。

那么当某个数据源发生变化之后如何更新listview呢?

我们知道listview运用了adapter模式,但是,在adapter类中却还运用了观察者模式,adapter内部有一个可观察者类,listview则作为它的其中一个观察者。在将adapter设置给listview时,listview会被注册到这个观察者对象中。代码如下:

从以上程序中我们看到,设置adapter时创建了一个adapterdatasetobserver对象,然后注册到madapter中。刚才不是说listview是观察者吗?这会儿怎么成了adapterdatasetobserver对象?我们先放下这个疑问,继续往下看。

首先看我们常用的adapter基类-baseadapter,部分代码如下:

从以上程序中可以看到,注册观察者实际上是调用了datasetobservable对应的函数。datasetobservable拥有一个观察者集合,当可观察者发生变更时,就会通知观察者做出相应的处理。代码如下:

当adapter的数据源发生变化时,我们会调用adapter的notifydatasetchanged函数,在该函数中又会调用datasetobservable对象的notifychanged函数通知所有观察者数据发生了变化,使观察者进行相应的操作。代码如下:

对于listview来说,这个观察者就是adapterdatasetobserver对象,该类声明在adapterview类中,也是listview中的一个父类。adapterdatasetobserver的代码如下:

从以上程序中可以看到,在adapterdatasetobserver的onchanged函数中会调用viewgroup的requestlayout()函数进行重新策略、布局、绘制整个listview的item view,执行完这个过程之后listview的元素就发生了变化,因此,此时会根据新的数据来加载item view。

现在我们回到上面提到的问题,也就是listview并不是观察者,而adapterdatasetobserver对象才是观察者的问题。在adapterdata setobserver的onchanged函数中,实际上调用的却是adapterview或者viewgroup类中的属性或者函数完成功能,因此,adapterdataset observer只是在外层做了一下包装,真正核心的功能应该是listview,确切地说应该是adapterview。listview就是通过adapter模式、观察者模式、item view复用机制实现了高效的列表显示。

与listview 相似,gridview 同样继承自 abslistview,而abslistview 又是 adapterview 的子类。gridview因此同样集成了abslistview的adapter模式、观察者模式、item view复用机制等特性,它与listview不同的就是布局方式。listview以列表形式展示,而gridview与它的名字一样则是通过网格布局形式展示。如图2-5所示。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

本是同根生使得listview和gridview拥有了很好的兼容性,同一个adapter可以设置给listview或者gridview,不需要半点修改。当然也可以同时设置给这两个视图,这样一来,两个视图都作为该adapter的观察者,数据会同时显示到这两个视图上。我想这就是为什么要运用观察者模式的缘由吧。

2.1.2 数据展示更好的实现——recyclerview

观察者模式、adapter模式赋予了listview、gridview等视图良好的可扩展性,但是从另一个角度看,它们似乎太过于相似了,以至于让我们不禁思考,这样做真的是最好的吗?

从上文中我们知道,listview、gridview基本上只有布局方式不一样而已,其他的机制基本一致。那么有没有更好的实现方式呢?答案是肯定的。

recyclerview就是作为listview、gridview的替代者出现的。它的设计与listview、gridview类似,也使用了adapter,不过该adapter并不是listview中的adapter,而是recyclerview的一个静态内部类。该adapter有一个泛型参数vh,代表的就是viewholder。recyclerview还封装了一个viewholder类型,该类型中有一个itemview字段,代表的就是每一项数据的根视图,需要在构造函数中传递给viewholder对象。recyclerview这么设计相当于android团队将listview的adapter进行了再次封装,把getview函数中判断是否含有缓存的代码段封装到recyclerview内部,使这部分逻辑对用户不可见。用户只需要告诉recyclerview每项数据是怎么样的以及将数据绑定到每项数据上,分别对应的函数为oncreateviewholder函数、onbindviewholder函数,当然还需要通过getitemcount告诉recyclerview有多少项数据,以往适用于listview的 adapter中的getview函数中的逻辑就不需要用户来处理了。一个recyclerview的adapter大致如下:

从recycleradapter中可以看到代码量比listview的adapter要少了很多,尤其是不需要用户判断是否使用item view缓存,用户只需要完成具体的viewholder构造以及数据绑定即可。

光这点改进还不足以让recyclerview如此光芒四射,它的另一大特点就是将布局方式抽象为layoutmanager,默认提供了linearlayoutmanager、gridlayoutmanager、staggeredgridlayoutmanager 3种布局,对应为线性布局、网格布局、交错网格布局,如果这些都无法满足你的需求,你还可以定制布局管理器实现特定的布局方式。如图2-6所示分别为线性布局、网格布局、自定义布局。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

recyclerview通过桥接的方式将布局职责抽离出去,使得recyclerview变得更灵活。例如,如果用户只需要修改recyclerview的布局方式,只需要修改layoutmanager即可,而不需要操作复杂的recyclerview类型。而listview、gridview正好是相反的,它们只是布局方式不一样,但却是两个类型,它们覆写了基类abslistview的layoutchildren函数来实现不同的布局。显然,通过组合的形式要好于通过继承,因此,recyclerview在设计上也要好于abslistview类族。

除此之外,recyclerview对于item view的控制也更为精细,可以通过itemdecotation为item view添加装饰,也就是在item view上进行二次加工;又可以用过itemanimator为item view添加动画。职责分明、结构清晰使得recyclerview具有了非常好的扩展性,这也是它成为未来几年最重要控件的重要原因。

2.1.3 让页面显示更流畅——viewpager

一个应用中通常都会有页面导航,用户根据页面导航进入到不同的功能界面,这几乎是每个应用的必备功能。由于android设备都是触摸屏,因此,通过滑动来进行页面导航再适合不过。viewpager就是为这种场景而生的,尤其是它与fragment结合在一起使用时简直可称为“黑白双煞”,android也深知其的重要性,因此,提供了几个适用于fragment的adapter。

没错,又是adapter!通常来说,定制含有item view类型的控件都应该使用adapter模式,因为你不知道用户的item view是怎样的,你只能通过一个adapter来进行抽象,让用户将具体的视图、数据通过adapter进行操作。例如,通过getitem获取某个数据、通过getview获取每个item view,这样一来变化的部分就交给用户来实现,控件只需关注自身的逻辑,然后通过adapter的getview来获取每个item view即可。

viewpager内部同样也是维护了一个视图集合,这些视图集合横向布局,用户可以通过左右滑动来进行页面切换。如图2-7所示。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

如前文所说,viewpager通常都用于显示fragment,而viewpager与fragment组合时通常会有一个指示器(viewpagerindicator)表明当前显示的是哪个页面。如图2-8所示。

《Android开发进阶:从小工到专家》——第2章,第2.1节重要的View控件

指示器与viewpager实际上是两个视图,指示器根据viewpager的页面数量以及提供的数据生成特定的指示器项,当viewpager进行滑动时,指示器上的当前页面标识会随之变化。

图2-8的视图布局程序如下:

viewpagerindicator会与viewpager进行管理,并且通过viewpager的adapter获取到页面数量、每个页面的标题等信息,然后绘制出指示器视图。当viewpager滚动时,指示器视图也会发生相应的变化,以此达到指示页面切换的效果。

继续阅读