Android 自定义控件实现 (多行选择条)

by 吴思博

一、实现思路(如何实现?)

二、读源码:TabLayout

三、自定义实现主要过程

四、该页面其他一些相关问题(Fragment销毁后RadioGroup恢复)

五、小结

虽然Android提供了一套GUI库,里面有很多控件,但是很多时候我们并不满足于系统的控件,通过自定义view,我们可以实现各种五花八门的效果,但是自定义控件有一定难度,尤其是复杂的自定义控件。

在云阅读新版本(5.4.3)中,交互需要实现多行的选择条,平常我们看到的基本上都是网易新闻这种单行的TabLayout。 那我们改如何实现各种定制化的控件呢? 先上图:

Android 自定义控件实现 (多行选择条)_第1张图片

图1.网易新闻单行选择条

Android 自定义控件实现 (多行选择条)_第2张图片

图2.网易云阅读多行选择条

Android 自定义控件实现 (多行选择条)_第3张图片
Android 自定义控件实现 (多行选择条)_第4张图片

图3.网易云阅读界面预览

一、实现思路(如何实现)

a、首先… …、没错、、、第一个思路就是网上去搜搜看,有没有类似控件,去网上找了一圈没有发现类似的多行的选择器。

Android 自定义控件实现 (多行选择条)_第5张图片

发现了两个单行的开源指示器还不错,有兴趣可以看看

https://github.com/hackware1993/MagicIndicator、http://blog.csdn.net/analyzesystem/article/details/51426473

b、既然网上没有,就只能自己实现了。实现自定义View一般有4种思路。

1.继承特定的View(比如TextView)。例如定制的TextView可以继承TextView再添加或修改一些特定的方法。

2.继承View重写onDraw的方法。(例如实现圆形view)

3.继承特定的ViewGroup。比如LinearLayout,几种常见View组合在一起的时候,可以采用此方法。不需要自己处理ViewGroup的测量和布局的两个过程。

4.继承ViewGroup,需要自己处理ViewGroup的测量和布局的两个过程。

思路2、3都不适合当前方法,剩下思路1、和2。一个是继承Tablayout重写onMeasure和onLayout对子view重写测量和排列,另一种是自己继承viewGroup,自己处理ViewGroup的测量和布局的两个过程。第一种好像更简单,但是我们可以先看看TabLayout源码。

二、读源码:TabLayout

1、内部类及分析其关系:

Android 自定义控件实现 (多行选择条)_第6张图片

lTab类和TabView类和SlidingTabStrip类为TabLayout提供了三个基本的元素。

lTabLayoutOnPageChangeListener和ViewPagerOnTabSelectedListener实现了ViewPager类的两个接口,作用是监听ViewPager页面改变和Tab选中状态。

lPagerAdapterObserver为观察者监控PagerAdapter数据变化。

2、TabLayout常用的方法如下:

Android 自定义控件实现 (多行选择条)_第7张图片

3、源码中选择tab的关键实现:

Android 自定义控件实现 (多行选择条)_第8张图片

Tab是每个item的mode类。Tab内部类定义了item的成员变量,并set和get方法,然后又封装了Tab的属性设置方法。

Android 自定义控件实现 (多行选择条)_第9张图片

TabView是每个item的View,继承自LinearLayout。它的作用是让Tab同时显示文字和图片,也可以通过mCustomview设置自定义item。

Android 自定义控件实现 (多行选择条)_第10张图片

TabLayout前两个个构造函数都是调用了自己的第三个构造函数,第三个构造函数里面添加了一个私有内部类SlidingTabStrip作为子view。

Android 自定义控件实现 (多行选择条)_第11张图片

这个自定义view SlidingTabStrip是私有内部类,它继承自LinearLayout,作用为获取tab的最宽宽度,设置SlidingTabStrip的宽度,并设置一个动画,随着tab的改变绘制SlidingTabStrip。

Android 自定义控件实现 (多行选择条)_第12张图片

在将选项添加到选项卡的addTabView方法中,看到每个item其实是加到SlidingTabStrip中。

三、自定义实现主要过程

通过读TabLayout源码,我们发现思路1,直接继承Tablayout重写onLayout和onMeasure对子view重写排列和测量不行,因为TabLayout里是加了一个私有的SlidingTabStrip作为子类,Item的view都是加在SlidingTabStrip中,没有办法通过重写私有SlidingTabStrip的onLayout和onMeasure。

所以现在只能继承ViewGroup,需要自己处理ViewGroup的测量和布局的两个过程。我们可以通过仿写TabLayout实现MyTabLayout,最简单的办法就是先把TabLayout拷贝出来到MyTabLayout,再进行定制化修改。

实现:

1.先把TabLayout拷贝出来到MyTabLayout,先跑通。

2.TabLayout包裹了一个SlidingTabStrip,我们只需要重写SlidingTabStrip的onLayout和onMeasure, 再重写TabLayout的onMeasure,让他的宽高为唯一子类SlidingTabStrip的宽高。

ViewGroup常用的生命周期回调:initView(构造方法)、onFinishInflate(当布局加载完成调用)、onMeasure(当测量时调用)、onSizeChanged(当尺寸改变调用)、onLayout(当布局时调用)、onDraw和dispatchDraw(绘制背景以及绘制子View)。

初始化ViewGroup的流程大致为:构造方法创建对象->从布局加载(xml中定义时)->第一遍测量->开始改变尺寸->第一遍布局->第二遍测量->第二遍布局

重载onMeasure()方法

为什么要重载onMeasure()方法这里就不赘述了。测算间距space如果间距小于3dp按顺序排列,否则每行N个(item为4个字N=5,2个字N=7)。setMeasuredDimension(resolveSize(mScreenWidth, widthMeasureSpec), Hight);设置自身宽高(宽为屏幕宽,高为子View的排数)。

Android 自定义控件实现 (多行选择条)_第13张图片
Android 自定义控件实现 (多行选择条)_第14张图片
Android 自定义控件实现 (多行选择条)_第15张图片

由于ViewGroup的定位就是一个容器,用来盛放子控件的,所以就必须定义要以什么的方式来盛放,比如LinearLayout就是以横向或者纵向顺序存放,而RelativeLayout则以相对位置来摆放子控件,同样,我们的自定义ViewGroup也必须给出我们期望的布局方式,而这个定义就通过onLayout()函数来实现。

Android 自定义控件实现 (多行选择条)_第16张图片

childView.layout(mPainterPosX + space, mPainterPosY, mPainterPosX + width + space, mPainterPosY + height);执行ChildView的绘制。

最后重写,TabLayout的onMeasure方法。

Android 自定义控件实现 (多行选择条)_第17张图片

到目前为止,已经基本实现了,多层的选择器。

另外:

1、可以看到原来的TabLayout继承自HorizontalScrollView,所以需要禁止滑动功能,可以在构造函数添加如下代码:

Android 自定义控件实现 (多行选择条)_第18张图片

2、细节的处理,还有很多间距什么需要处理,通过View的自定义参数的方式实现,这里不详叙述了。

Android 自定义控件实现 (多行选择条)_第19张图片
Android 自定义控件实现 (多行选择条)_第20张图片
Android 自定义控件实现 (多行选择条)_第21张图片

3、如果每个item可以实现自定义view,处理好如下方法即可。

Android 自定义控件实现 (多行选择条)_第22张图片

四、该页面其他一些相关问题(Fragment销毁后RadioGroup恢复)

由一个个单独的Fragment改成了TabLayout的形式,往后翻几个之前的Fragment容易被回收,这里就需要恢复Fragment了,通onSaveInstanceState()方法实现。

RadioGroup的选择项恢复有很多坑。发现RadioGroup恢复后不会按设定存的选项进行设置,主要就是需要注意以下的两个问题:

1、一组RadioGroup设置选中时候,要以RadioGroup为单位设置check(),不要给单个RadioButton设置button.setChecked(true),否则恢复很容易出问题。应该以组为单位使用check()。

Android 自定义控件实现 (多行选择条)_第23张图片
Android 自定义控件实现 (多行选择条)_第24张图片

2、RadioButton设置id的时候应该设置一个独一无二的id,否则恢复也会出现巨坑。改成随机数后恢复正常、不再随便乱选中。

五、小结:

1.一种自定义View可能有多种实现方式、我们要找到代价最小、最高效的方式。

2.通过实践有了更深的体会。

你可能感兴趣的:(Android 自定义控件实现 (多行选择条))