PagerSlidingTabStrip,酷炫的ViewPager的滑动导航,使用及解析
2015-07-20 11:05
621 查看
最近在github发现了一个酷炫的UI,一个viewpager的导航栏,不仅使用简单,而且还能定义各种样式,使用风格如下图。
我们只需要在布局文件定义这个自定义控件
然后当我们在activity初始化的时候,只需要调用如下就可以了
注意,PagerSlidingTabStrip 调用了viewpager的OnPageChangeListener()的监听方法,我们要是也想实现它的话可以选择调用setOnPageChangeListener来实现viewpager监听器
tab部分使用scrollview并且在OnPageChangeListener监听器里面实时滑动更新
指示器部分在ondraw()中画一个rect,并且在OnPageChangeListener监听器里面实时滑动更新
知道了原理之后分析起来就简单了
PagerSlidingTabStrip 控件的初始化
控件初始化完成之后我们会调用tabs.setViewPager(pager);下面我们就来分析一下它的实现.
该方法主要有两行pager.setOnPageChangeListener(pageListener);和notifyDataSetChanged();
我们来逐个分析它的实现
首先是PageListener,他实现了viewpager的OnPageChangeListener,所以这个viewpager已经定义了监听器,我们注意不要在activity里面再次定义viewpager的监听器了
控件就是通过viewpager的滚动监听来同步更新tab和指示器的
然后我们继续查看scrollToChild方法是如何滑动scrollview(tab)的
在onPageScrolled的时候频繁调用invalidate();来重绘,那就来看看重绘的究竟是什么东西
好了,已经通过滑动监听来实现实时更新了,接下来就是初始化tab信息了,就是在刚才还有没说的notifyDataSetChanged()中。
总算是研究完了,其实只要理解了实现原理,读起来就不费劲了。。。
使用方法
列表内容我们只需要在布局文件定义这个自定义控件
<com.astuetz.PagerSlidingTabStrip android:id="@+id/tabs" android:layout_width="match_parent" android:layout_height="48dip" //控件的自定义属性,可不写 app1:pstsTextAllCaps=""//tab文本是否全部大写 app1:pstsShouldExpand=""//tab是设置weight为1还是设置为wrap_content app1:pstsTabBackground="" app1:pstsScrollOffset=""//tab刚开始多长不滚动,当为0时,viewpager一滚动它就跟着滚动 app1:pstsTabPaddingLeftRight="" app1:pstsDividerPadding="" app1:pstsUnderlineHeight="" app1:pstsIndicatorHeight="" app1:pstsDividerColor="" app1:pstsUnderlineColor="" app1:pstsIndicatorColor=""/>
然后当我们在activity初始化的时候,只需要调用如下就可以了
PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs); tabs.setViewPager(pager);//pager是viewpager对象
注意,PagerSlidingTabStrip 调用了viewpager的OnPageChangeListener()的监听方法,我们要是也想实现它的话可以选择调用setOnPageChangeListener来实现viewpager监听器
tabs.setOnPageChangeListener(mPageChangeListener);
解析
下面我们来看一下他是怎么实现这么些功能的,其实也就是一个类PagerSlidingTabStrip ,他实现了HorizontalScrollView,具体原理就是tab部分使用scrollview并且在OnPageChangeListener监听器里面实时滑动更新
指示器部分在ondraw()中画一个rect,并且在OnPageChangeListener监听器里面实时滑动更新
知道了原理之后分析起来就简单了
PagerSlidingTabStrip 控件的初始化
public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); //这两行代码意义可以参考http://www.aiuxian.com/article/p-638915.html setFillViewport(true); setWillNotDraw(false); //定义tab容器 tabsContainer = new LinearLayout(context); tabsContainer.setOrientation(LinearLayout.HORIZONTAL); tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); addView(tabsContainer); ...... TypedArray a = context.obtainStyledAttributes(attrs, ATTRS); ...... // 这是在xml文件中设置的自定义属性,我们也可以在代码中设置它 a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip); indicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsIndicatorColor, indicatorColor); //tab下面的与viewpager分隔的一条线 underlineColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsUnderlineColor, underlineColor); //tab之间的分隔线 dividerColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsDividerColor, dividerColor); //指示器宽度 indicatorHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsIndicatorHeight, indicatorHeight); //下划线的高度 underlineHeight = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsUnderlineHeight, underlineHeight); dividerPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsDividerPadding, dividerPadding); //设置tab的左右內边距 tabPadding = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsTabPaddingLeftRight, tabPadding); tabBackgroundResId = a.getResourceId(R.styleable.PagerSlidingTabStrip_pstsTabBackground, tabBackgroundResId); //设置tab布局,false时tab宽度为wrap_content,true时weight设为1 shouldExpand = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsShouldExpand, shouldExpand); //scrollview根据viewpager滚动的多少而滚动,当scrollOffset设置为n时,scrollview会先让viewpager滚动n之后,再和它同步滚动。 scrollOffset = a.getDimensionPixelSize(R.styleable.PagerSlidingTabStrip_pstsScrollOffset, scrollOffset); //是否全部大写 textAllCaps = a.getBoolean(R.styleable.PagerSlidingTabStrip_pstsTextAllCaps, textAllCaps); a.recycle(); //设置指示器画笔 rectPaint = new Paint(); rectPaint.setAntiAlias(true); rectPaint.setStyle(Style.FILL); //tab之间的分割线画笔 dividerPaint = new Paint(); dividerPaint.setAntiAlias(true); dividerPaint.setStrokeWidth(dividerWidth); //设置两种布局,当tab项过少时应该调用第二种满屏幕看起来才顺眼 defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f); ...... }
控件初始化完成之后我们会调用tabs.setViewPager(pager);下面我们就来分析一下它的实现.
public void setViewPager(ViewPager pager) { this.pager = pager; if (pager.getAdapter() == null) { throw new IllegalStateException("ViewPager does not have adapter instance."); } pager.setOnPageChangeListener(pageListener); notifyDataSetChanged(); }
该方法主要有两行pager.setOnPageChangeListener(pageListener);和notifyDataSetChanged();
我们来逐个分析它的实现
首先是PageListener,他实现了viewpager的OnPageChangeListener,所以这个viewpager已经定义了监听器,我们注意不要在activity里面再次定义viewpager的监听器了
控件就是通过viewpager的滚动监听来同步更新tab和指示器的
private class PageListener implements OnPageChangeListener { //viewpager滑动中的时候会调用 @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { currentPosition = position;//tab的位置,第一张时为0,注意以0开始算的 currentPositionOffset = positionOffset;//一个tab滑动距离的比例,为0到1之间 //这个方法是更新tab的 scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth())); //滑动的时候要一直调用onDraw()重绘指示器, invalidate(); //回调OnPageChangeListener if (delegatePageListener != null) { delegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageScrollStateChanged(int state) { //每次滑动完了的时候调用scrollToChild,再更新一次 if (state == ViewPager.SCROLL_STATE_IDLE) { scrollToChild(pager.getCurrentItem(), 0); } //回调OnPageChangeListener if (delegatePageListener != null) { delegatePageListener.onPageScrollStateChanged(state); } } @Override public void onPageSelected(int position) { //回调OnPageChangeListener if (delegatePageListener != null) { delegatePageListener.onPageSelected(position); } } }
然后我们继续查看scrollToChild方法是如何滑动scrollview(tab)的
/** * 将tab根据viewpager的滚动进度而滚动 * @param position 当前viewpager页码,第一张为0 * @param offset viewpager滚动的比例*tab项的长度=tab项应该滚动的距离 */ private void scrollToChild(int position, int offset) { if (tabCount == 0) { return; } //scrollview应该滚动的长度 int newScrollX = tabsContainer.getChildAt(position).getLeft() + offset; if (position > 0 || offset > 0) { //scrollview根据viewpager滚动的多少而滚动,当scrollOffset设置为n时,scrollview会先让viewpager滚动n之后,再和它同步滚动。 //比如设置100dp的时候,viewpager要滑动了100dp距离,scrollview才开始滚动 newScrollX -= scrollOffset; } //scrollview滑动 scrollTo(newScrollX, 0); }
在onPageScrolled的时候频繁调用invalidate();来重绘,那就来看看重绘的究竟是什么东西
protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (isInEditMode() || tabCount == 0) { return; } //获取控件高度 final int height = getHeight(); //设置指示器画笔颜色 rectPaint.setColor(indicatorColor); //获取当前页对应的tab的信息 View currentTab = tabsContainer.getChildAt(currentPosition); float lineLeft = currentTab.getLeft(); float lineRight = currentTab.getRight(); //当开始滑动且有下一个tab时调用 if (currentPositionOffset > 0f && currentPosition < tabCount - 1) { //获取要滑动到的那个tab的信息 View nextTab = tabsContainer.getChildAt(currentPosition + 1); final float nextTabLeft = nextTab.getLeft(); final float nextTabRight = nextTab.getRight(); //实时更新新的指示器的左右坐标,由于每个tab长度可能不同,这个算法可以根据tab项的大小来实时放大缩小到最后和tab长度一样大 lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft); lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight); } //画指示器 canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint); // draw underline //画下划线 rectPaint.setColor(underlineColor); canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint); // draw divider //画tab间的分割线 dividerPaint.setColor(dividerColor); for (int i = 0; i < tabCount - 1; i++) { View tab = tabsContainer.getChildAt(i); canvas.drawLine(tab.getRight(), dividerPadding, tab.getRight(), height - dividerPadding, dividerPaint); } }
好了,已经通过滑动监听来实现实时更新了,接下来就是初始化tab信息了,就是在刚才还有没说的notifyDataSetChanged()中。
public void notifyDataSetChanged() { //清空所以组件 tabsContainer.removeAllViews(); //获取所有tab数 tabCount = pager.getAdapter().getCount(); for (int i = 0; i < tabCount; i++) { if (pager.getAdapter() instanceof IconTabProvider) { //pageAdapter实现IconTabProvider接口时,tab用图片 addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i)); } else { //设置tab的文本 addTextTab(i, pager.getAdapter().getPageTitle(i).toString()); } } //主要是设置tab文本风格,就不介绍了 updateTabStyles(); //初始化tab getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @SuppressWarnings("deprecation") @SuppressLint("NewApi") @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { getViewTreeObserver().removeGlobalOnLayoutListener(this); } else { getViewTreeObserver().removeOnGlobalLayoutListener(this); } currentPosition = pager.getCurrentItem(); scrollToChild(currentPosition, 0); } }); }
总算是研究完了,其实只要理解了实现原理,读起来就不费劲了。。。
相关文章推荐
- CXF+Spring 整合
- 最大最小堆的插入与删除
- IOS工程自动打包并发布脚本实现
- Java常用排序算法/程序员必须掌握的8大排序算法
- SQL Server表分区的NULL值问题
- 构建高并发高可用的电商平台架构大纲
- 错误:The request sent by the client was syntactically incorrect的解决
- Android学习笔记----解决“com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536”问题
- 2012年5月SAT香港真题解析
- GRE写作必备句型
- 高仿一元云购IOS应用源码项目
- HTML5 APP应用实现图片上传及拍照上传功能
- ArcGIS版本编辑流程
- 指定函数、变量到某个特定的section段
- Android学习
- poj 3620 Avoid The Lakes(dfs)
- 【AngularJS】Yeoman安装
- Android异步任务处理框架AsyncTask源码分析
- Oracle12c功能增强 新特性之管理功能的增强
- 线性排序之基数排序,桶排序,计数排序