在开发中,我们有时会遇到顶部导航栏滑动切换页面的设计,如网易新闻。实现的方式有很多种,今天我们使用PagerSlidingTabStrip配合ViewPager实现顶部导航栏。
效果图如下。
PagerSlidingTabStrip是github上的一个开源项目,项目地址如下。
https://github.com/astuetz/PagerSlidingTabStrip
(一)PagerSlidingTabStrip的使用
在使用之前,我们先来看一下PagerSlidingTabStrip中的自定义属性。
[html] view plain copy
- <declare-styleable name="PagerSlidingTabStrip">
- <attr name="pstsIndicatorColor" format="color" />
- <attr name="pstsUnderlineColor" format="color" />
- <attr name="pstsDividerColor" format="color" />
- <attr name="pstsIndicatorHeight" format="dimension" />
- <attr name="pstsUnderlineHeight" format="dimension" />
- <attr name="pstsDividerPadding" format="dimension" />
- <attr name="pstsTabPaddingLeftRight" format="dimension" />
- <attr name="pstsScrollOffset" format="dimension" />
- <attr name="pstsTabBackground" format="reference" />
- <attr name="pstsShouldExpand" format="boolean" />
- <attr name="pstsTextAllCaps" format="boolean" />
- </declare-styleable>
各属性的详细介绍如下。
pstsIndicatorColor:滑动条的颜色。
pstsIndicatorHeight:滑动条的高度。
pstsUnderlineColor:底部线条的颜色。(底部线条会填充屏幕宽度)
pstsUnderlineHeight:底部线条的高度。
pstsDividerColor:tab之间的竖直分割线的颜色。
pstsDividerPadding:tab之间的竖直分割线,距离顶部和底部的距离,即它的paddingTop和paddingBottom。
pstsTabPaddingLeftRight:单个tab内部的左间距和右间距,即它的paddingLeft和paddingRight。
pstsTabBackground:单个tab的背景。
pstsScrollOffset:当前tab滚动的偏移量。
pstsShouldExpand:设置为ture,每个tab的权重一样,均分屏幕宽度,默认值false。
pstsTextAllCaps:是否将tab中的字母转换成大写,默认值true。
下面,我们将PagerSlidingTabStrip使用到具体项目中。
首先将PagerSlidingTabStrip添加到工程module的gradle中。
[html] view plain copy
- dependencies {
- compile ‘com.astuetz:pagerslidingtabstrip:1.0.1‘
- }
接下来添加布局文件。
[html] view plain copy
- <com.astuetz.PagerSlidingTabStrip xmlns:tab="http://schemas.android.com/apk/res-auto"
- android:id="@+id/tab"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:background="#fafafa"
- android:paddingBottom="10dp"
- android:paddingTop="10dp"
- android:textColor="#333333"
- android:textSize="13sp"
- tab:pstsDividerColor="@android:color/transparent"
- tab:pstsIndicatorColor="#ed5955"
- tab:pstsIndicatorHeight="2dp"
- tab:pstsShouldExpand="true"
- tab:pstsTabBackground="@android:color/transparent"
- tab:pstsUnderlineColor="@android:color/transparent"/>
最后实现逻辑代码。
[java] view plain copy
- package net.csdn.blog.ruancoder;
- import android.os.Bundle;
- import android.support.v4.app.Fragment;
- import android.support.v4.app.FragmentActivity;
- import android.support.v4.view.ViewPager;
- import android.view.Window;
- import com.astuetz.PagerSlidingTabStrip;
- public class MainActivity extends FragmentActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- requestWindowFeature(Window.FEATURE_NO_TITLE);
- setContentView(R.layout.activity_main);
- final PagerSlidingTabStrip tabStrip = (PagerSlidingTabStrip) findViewById(R.id.tab);
- ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
- Fragment[] fragments = {new NewsFragment(), new TechFragment(), new FinanceFragment(), new InternetFragment(), new
- PhoneFragment()};
- String[] titles = {"头条", "科技", "财经", "互联网", "手机"};
- TabPagerAdapter adapter = new TabPagerAdapter(getSupportFragmentManager(), fragments, titles);
- viewPager.setAdapter(adapter);
- tabStrip.setViewPager(viewPager);
- }
- }
Activity中使用到的TabPagerAdapter类:
[java] view plain copy
- package net.csdn.blog.ruancoder;
- import android.support.v4.app.Fragment;
- import android.support.v4.app.FragmentManager;
- import android.support.v4.app.FragmentPagerAdapter;
- public class TabPagerAdapter extends FragmentPagerAdapter {
- private Fragment[] mFragments;
- private String[] mTitles;
- public TabPagerAdapter(FragmentManager fm, Fragment[] fragments, String[] titles) {
- super(fm);
- this.mFragments = fragments;
- this.mTitles = titles;
- }
- @Override
- public Fragment getItem(int position) {
- return mFragments[position];
- }
- @Override
- public int getCount() {
- return mFragments.length;
- }
- @Override
- public CharSequence getPageTitle(int position) {
- return mTitles[position];
- }
- }
(二)PagerSlidingTabStrip的源码分析
(1).类的声明。
[java] view plain copy
- package com.astuetz;
- public class PagerSlidingTabStrip extends HorizontalScrollView {
- }
PagerSlidingTabStrip继承自HorizontalScrillView,当tab数量较多超出屏幕时,可以横向滚动。
(2).构造方法。
[java] view plain copy
- public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- tabsContainer = new LinearLayout(context);
- tabsContainer.setOrientation(LinearLayout.HORIZONTAL);
- tabsContainer.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
- addView(tabsContainer);
- // 这里省略了初始化各参数的代码
- rectPaint = new Paint();
- rectPaint.setAntiAlias(true);
- rectPaint.setStyle(Style.FILL);
- dividerPaint = new Paint();
- dividerPaint.setAntiAlias(true);
- dividerPaint.setStrokeWidth(dividerWidth);
- defaultTabLayoutParams = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
- expandedTabLayoutParams = new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f);
- }
在构造方法中,先是创建了一个水平线性布局tabsContainer,并将其添加到PagerSlidingTabStrip中。因为PagerSlidingTabStrip继承自HorizontalScrillView,而HorizontalScrillView内部只能有一个子View,所以之后的tab都将添加到tabsContainer布局中。
接下来创建了两个画笔rectPaint和dividerPaint,分别用来绘制滑动条和竖直分隔线。
最后创建了两个布局参数LayoutParams,默认为defaultTabLayoutParams,自适应tab的宽度,以及expandedTabLayoutParams,tab均分屏幕宽度。当自定义属性pstsShouldExpand声明为true时,使用expandedTabLayoutParams,相反则使用defaultTabLayoutParams。
(3).setViewPager(ViewPager pager)方法。
[java] view plain copy
- 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();
- }
当我们获取到PagerSlidingTabStrip对象后,会调用它的setViewPager(ViewPager pager)方法。
在该方法内部,为ViewPager对象添加了滑动监听PageListener,并调用了notifyDataSetChanged()方法。
(4).notifyDataSetChanged()方法。
[java] view plain copy
- public void notifyDataSetChanged() {
- tabsContainer.removeAllViews();
- tabCount = pager.getAdapter().getCount();
- for (int i = 0; i < tabCount; i++) {
- if (pager.getAdapter() instanceof IconTabProvider) {
- addIconTab(i, ((IconTabProvider) pager.getAdapter()).getPageIconResId(i));
- } else {
- addTextTab(i, pager.getAdapter().getPageTitle(i).toString());
- }
- }
- updateTabStyles();
- getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.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);
- }
- });
- }
方法内部,遍历ViewPager的Adapter中的标题,生成Tab并添加到tabsContainer布局中。然后将滑动条滚动到第一个Tab下。
(5).OnPageChangeListener的实现类,PageListener类。
[java] view plain copy
- private class PageListener implements ViewPager.OnPageChangeListener {
- @Override
- public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
- currentPosition = position;
- currentPositionOffset = positionOffset;
- scrollToChild(position, (int) (positionOffset * tabsContainer.getChildAt(position).getWidth()));
- invalidate();
- // ......
- }
- @Override
- public void onPageScrollStateChanged(int state) {
- if (state == ViewPager.SCROLL_STATE_IDLE) {
- scrollToChild(pager.getCurrentItem(), 0);
- }
- // ......
- }
- @Override
- public void onPageSelected(int position) {
- // ......
- }
- }
PageListener类监听ViewPager的滑动。
这里重点是onPageScrolled()方法,当ViewPager在滑动过程中该方法不断被回调。在其中执行了2行代码,scrollToChild(),移动tab;invalidate(),触发View的onDraw()的调用。
(6).核心方法onDraw()。
[java] view plain copy
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- if (isInEditMode() || tabCount == 0) {
- return;
- }
- final int height = getHeight();
- // draw indicator line
- rectPaint.setColor(indicatorColor);
- // 默认在当前被选中的tab底部绘制滑动条
- View currentTab = tabsContainer.getChildAt(currentPosition);
- float lineLeft = currentTab.getLeft();
- float lineRight = currentTab.getRight();
- // 如果正在滑动,在当前tab和下一个tab之间绘制滑动条
- if (currentPositionOffset > 0f && currentPosition < tabCount - 1) {
- View nextTab = tabsContainer.getChildAt(currentPosition + 1);
- final float nextTabLeft = nextTab.getLeft();
- final float nextTabRight = nextTab.getRight();
- lineLeft = (currentPositionOffset * nextTabLeft + (1f - currentPositionOffset) * lineLeft);
- lineRight = (currentPositionOffset * nextTabRight + (1f - currentPositionOffset) * lineRight);
- }
- // 绘制滑动条,滑动条宽度为lineRight - lineLeft,高度为indicatorHeight
- canvas.drawRect(lineLeft, height - indicatorHeight, lineRight, height, rectPaint);
- // 绘制底部线条,线条宽度为PagerSlidingTabStrip的宽度,高度为underlineHeight
- rectPaint.setColor(underlineColor);
- canvas.drawRect(0, height - underlineHeight, tabsContainer.getWidth(), height, rectPaint);
- // 在每个tab的右侧,绘制竖直分割线,分割线宽度为dividerWidth,高度为height - dividerPadding * 2
- 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);
- }
- }
通过在onPageScrolled()回调方法中得到的currentPosition和currentPositionOffset,实时绘制滑动条的位置,实现滑动条跟随手势移动。
最后附上完整工程下载链接。
http://download.csdn.net/detail/ruancoder/9582974
原文地址:https://www.cnblogs.com/wushanmanong/p/8877210.html