在开发中,我们常常需要ViewPager结合Fragment一起使用,如下图:
我们可以使用Design support library库的TabLayout去实现,但是TabLayout只能用横线指示器,如果想要其他指示器,比如三角下标,该控件就不能用了。
我们可以找网上成熟的轮子进行修改,比如:PagerSlidingTabStrip
实现了一个类似于今日头条的可滑动tab,带三角下标,可绑定ViewPager进行联动。
效果图如下
整个控件是基于HorizontalScrollView,把三角下标绘制在tab下方,控件的滑动可以分解成tab容器的滑动和三
角下标的滑动,关键在于计算好二者滑动的距离。
想要做到三角下标跟随手指滑动的效果,需要用到ViewPager的onPageScrolled
接口:
mViewPager.addOnPageChangeListener(new OnPageChangeListener() { @Override public void onPageSelected(int position) { // 设置字体颜色高亮 resetTextViewColor(); highLightTextView(position); // 回调 if (onPageChangeListener != null) { onPageChangeListener.onPageSelected(position); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // 滚动 scroll(position, positionOffset); // 回调 if (onPageChangeListener != null) { onPageChangeListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageScrollStateChanged(int state) { // 回调 if (onPageChangeListener != null) { onPageChangeListener.onPageScrollStateChanged(state); } } });
其中,onPageScrolled的三个参数,重点介绍前两个:
position:如果从0向1滑动,position就是0;
positionOffset:是介于0和1之间的float型参数,如果从0向1滑动,指的是已滑的比列;
下面看看滑动过程的核心函数scroll:
public void scroll(int position, float positionOffset) { int tabWidth = mItemsLayout.getChildAt(position).getWidth(); mTranslationX = (int) ((position + positionOffset) * tabWidth); // 容器滚动,当移动到倒数最后一个的时候,开始滚动 if (positionOffset > 0 && position >= (VISIBLE_TAB_COUNT - START_SCROLL) && mTotalItemsCount > VISIBLE_TAB_COUNT) { //注意这里是整体滑动,使得tabs跟指示器保持相对静止 this.scrollTo((position - (VISIBLE_TAB_COUNT - START_SCROLL)) * tabWidth + (int) (tabWidth * positionOffset), 0); } invalidate(); }
mTranslationX :三角下标滑动的距离。
这里的滑动分为两部分:tab容器的滑动和三角下标的滑动。
这里定义,每个页面可见4个按钮,当选中到倒数第二个按钮时,tab容器开始滑动,即当position >=
(VISIBLE_TAB_COUNT - START_SCROLL) 时,容器开始滑动。此时整个容器会先整体滑动(包括三角下标,三角下标和tab保持相对静止),接着
开始invalidate(),调用绘制三角下标,绘制过程如下:
@Override protected void dispatchDraw(Canvas canvas) { canvas.save(); // 平移到正确的位置 canvas.translate(mInitTranslationX + mTranslationX, this.mInitTranslationY);//修正tabs的平移量 canvas.drawBitmap(this.mSlideIcon, 0, 0, null); canvas.restore(); super.dispatchDraw(canvas); }
这里用的是dispatchDraw
,关于dispatchDraw和onDraw的区别,这里再提一下:
绘制View本身的内容,通过调用View.onDraw(canvas)函数实现
绘制自己的孩子通过dispatchDraw(canvas)实现
ViewGroup容器组件的绘制,当它没有背景时直接调用的是dispatchDraw()方法, 而绕过了draw()方法,当它有
背景的时候就调用draw()方法,而draw()方法里包含了dispatchDraw()方法的调用。因此要在ViewGroup上绘
制东西的时候往往重写的是dispatchDraw()方法而不是onDraw()方法。
回到绘制的过程,绘制这里用的是移动画布,再绘制,其实大可以直接绘制,即
@Override protected void dispatchDraw(Canvas canvas) { canvas.drawBitmap(this.mSlideIcon, mInitTranslationX + mTranslationX, this.mInitTranslationY, null); super.dispatchDraw(canvas); }
最后说下我在此过程中踩得坑。
首先最开始我并没有意识到可以直接在HorizontalScrollView绘制三角下标,所以我最开始是继承LinearLayout写的,三角下标在一个独立的LinearLayout里,结构如下:
在处理容器的滑动时,我并不是整体滑动,而是让HorizontalScrollView滑动,即在scroll函数中:
this.scrollTo((position - (VISIBLE_TAB_COUNT - START_SCROLL)) * tabWidth + (int) (tabWidth * positionOffset), 0);
变成了
HorizontalScrollView对象.scrollTo((position - (VISIBLE_TAB_COUNT - START_SCROLL)) * tabWidth + (int) (tabWidth * positionOffset), 0);
这样的后果是,tab容器和三角下标没有保持相对静止,容器先向一方滑动了一部分,三角下标又向另一方滑了
一部分,导致出现三角下标越滑越多的情形。