您的位置:首页 > 其它

PagerSlidingTabStrip,酷炫的ViewPager的滑动导航,使用及解析

2015-07-20 11:05 621 查看
最近在github发现了一个酷炫的UI,一个viewpager的导航栏,不仅使用简单,而且还能定义各种样式,使用风格如下图。





使用方法

列表内容

我们只需要在布局文件定义这个自定义控件

<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);
}
});

}


总算是研究完了,其实只要理解了实现原理,读起来就不费劲了。。。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: