ViewPager中自定义title indicator
需求
继续死磕Launcher的文件夹模式,按照UI的设计(韩国团队,果然思考的非主流),对类APUS文件夹滑动显示的要求是:当前的文件夹名字要居中高亮大字体(如果是第一个文件夹也是要居中,左侧空白;如果不是第一个文件夹,那么左右显示的文件夹名字都是小字体,而且距离当前文件夹名字的间距相同)。如下图所示:
folder indicator demo
话说看到这个设计的时候,我的内心是崩溃的,为啥不用主流的显示方式,类似头条新闻,APUS之类的,顺序排列,哪个当前显示,哪个下面有下划线即可。现在有很多写好的控件,拿来直接用即可。但是,还是要实现这个效果。
分析
需要实现如下的功能:
- 当前展示的文件夹名字居中,其他文件夹跟它等距离相间
- 点击当前显示的文件夹名字可以改变文件夹名
- 点击非当前的文件夹名字,显示这个干文件夹的内容,并且此文件夹名字居中高亮(有下划线)
文件夹名字显示的实现
参考了github上ViewPagerIndicator其中TitlePageIndicator的实现,即通过直接继承View来实现上诉的需求。因为现有的控件不足于让我们完成title的绘制和展示。
首先我们自定义的TitlePageIndicator要继承View并且实现ViewPager的接口OnPageChangeListener,这样viewpager滑动的时候,我们可以通过回调函数来控制相关title的滑动显示。
public class TitlePageIndicator extends View implements OnPageChangeListener {
因为本身TitlePageIndicator是个自定义的view,所以我们把它加到我们的布局文件中
<com.android.launcher3.TitlePageIndicator android:id="@+id/folder_group_indicator" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="10.0dp" > </com.android.launcher3.TitlePageIndicator> <com.android.launcher3.FolderViewPager android:id="@+id/viewpager" android:layout_below="@id/folder_group" android:layout_width="match_parent" android:layout_height="match_parent" > </com.android.launcher3.FolderViewPager>
这样,在TitlePageIndicator的onDraw函数里面就可以展示我们的文件夹名称了。
所以重点在onDraw里面怎么画出这些titles。
- 首先要先算出各个title的left/right/top/bottom(主要就是left和right,因为在文件夹名称之间我们要加入固定的padding,这样left/right都是绝对的坐标,即有可能是在屏幕外),然后存在一个ArrayList上。这个操作每次调用onDraw的时候都要重新算,因为当前显示的title不一样,那么以这个title为中心,其他title的位置就要算出来,另外随着手指的滑动,这些值是动态变化的。难点1是这个。
- 然后是要判断当前的真实位置(即有可能是在拖拽滑动中),这用要从OnPageChangeListener里面的回调函数中获得:onPageScrollStateChanged中获得是否在滑动isScrolling;onPageScrolled中获得当前的页码mCurrentPage,和偏移量mPageOffset。当然,在这些函数的最后都要调用invalidate来触发onDraw来更新UI。
- 这样我们的onDraw里面就有了当前的状态:是否在滑动isScrolling,当前显示的页码mCurrentPage,偏移量mPageOffset。利用这几个变量,我们要算出来各个title的相对之前算出来的坐标的偏移量,即得到实际要在哪里显示title。这里分三种情况:
1. 是否是在滑动,如果不滑动,直接按照之前算出来的坐标显示即可。
2. 如果滑动,当前页不是最后一页,要通过当前页的下一页来算偏移量。
3. 如果滑动,当前页为最后一页,那么要通过当前页的前一页来算偏移量。
最后调用canvas.drawText来完成title的绘制。
canvas.drawText(pageTitle, 0, pageTitle.length(), bound.left + offset, bound.bottom + mTopPadding, mPaintText);
我们最后关心的只有offset这个参数(不滑动的时候为0,不表),需要单独算出来。
- 非最后一页的offset
gap = (rightBound.right + rightBound.left) / 2 - halfWidth(屏幕宽的一半);
offset = (int)(gap * mPageOffset);
- 最后一页的offset
float gap = (halfWidth - w / 2) - leftBound.left;
offset = (int)(gap * (1 - mPageOffset));
Note:
关于ViewPager左右滑动,mCurrentPage的变化是不一样的。所以通过gap算offset的公式也不一样。
- 手指向左滑动,页面向右走的时候,mCurrentPage是不变的。直到滑到下个页面的时候,才变化。
- 手指向右滑动,页面向左走的时候,mCurrentPage立刻减一。
关于点击文件夹名字切换文件夹的实现
重写了onTouchEvent函数,当ACTION_UP的时候,说明完成了点击事件。那么这个时候判断点击的位置落到哪个title上,然后调用viewpager的setCurrentItem函数来完成切换。比较简单。
最后的效果
现有的效果后记
实现的还是比较粗糙,后面还要继续优化。感觉好的UI控件还是要慢慢雕琢才可以。