剖析项目名称: Android PagerSlidingTabStrip (default Material Design)
剖析原项目地址:https://codeload.github.com/jpardogo/PagerSlidingTabStrip/zip/master
剖析理由:只知其然而不知其所以然,如此不好。想要快速的进阶,不走寻常路,剖析开源项目,深入理解扩展知识,仅仅这样还不够,还需要如此:左手爱哥的设计模式,右手重构改善既有设计,如此漫长打坐,回过头再看来时的路,书已成山,相信翔哥说的,量变引起质变。
话不多说,献上Gif(没有Gif就是耍流氓了O(∩_∩)O~)
Android studio 导入项目里面一键搞定(AS的魅力真的很大,我都把持不住啦)
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
作为一个依赖导入到自己项目,xml布局调用实例:
<com.astuetz.PagerSlidingTabStrip
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary" />
PagerSlidingTabStrip配合ViewPager一起使用,当ViewPager的onPagerChangeListener回调时,PagerSlidingTabStrip也一起随之变动,具体做法都已封装到了PagerSlidingTabStrip.setViewPager()方法里,使用时调用实例如下:
// Initialize the ViewPager and set an adapter
ViewPager pager = (ViewPager) findViewById(R.id.pager)
pager.setAdapter(new TestAdapter(getSupportFragmentManager()))
// Bind the tabs to the ViewPager
PagerSlidingTabStrip tabs = (PagerSlidingTabStrip) findViewById(R.id.tabs)
tabs.setViewPager(pager)
如果你需要监听PagerSlidingTabStrip的check状态变化,只需要如此:
tabs.setOnPageChangeListener(mPageChangeListener);
下面是一些关于自定义属性的简短说明,如果你需要定制化的UI可以通过引用这些属性帮助你实现你想要的效果。
- android:textColorPrimary 文字颜色
- pstsIndicatorColor 滑动指示器颜色
- pstsIndicatorHeight 滑动指示器高度
- pstsUnderlineColor 视图的底部的全宽线的颜色
- pstsUnderlineHeight 视图的底部的全宽线的高度
- pstsDividerColor 选项卡之间的分隔线的颜色
- pstsDividerWidth 选项卡之间的分隔线的宽度
- pstsDividerPadding 选项卡之间的分隔线的Pading填充
- pstsShouldExpand 如果设置为true,每个选项卡都是相同的weight,即LinearLayout的权重一样,默认为false
- pstsScrollOffset 滑动Tab的偏移量
- pstsPaddingMiddle 如果true,视图的标签会以居中显示的方式呈现。
- pstsTabPaddingLeftRight 每个标签左右填充宽度
- pstsTabBackground 每个标签的背景,应该是一个selector,checked=”true/false”对应不同的背景
- pstsTabTextSize Tab标签的文字大小
- pstsTabTextColor Tab标签的文字颜色
- pstsTabTextStyle
- pstsTabTextAllCaps 如果为true,所有标签都是大写字母,默认为true
- pstsTabTextAlpha 设置文本alpha透明度不选中的选项卡。0 . . 255之间,150是默认值。
- pstsTabTextFontFamily Tab标签文字的字体
- setTypeface(Typeface typeface, int style) 设置字体,把字体放入assets里通过该方法调用
先看看github上作者提供的compile:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile "com.android.support:appcompat-v7:22.2.0"
compile 'com.android.support:cardview-v7:22.2.0'
compile 'com.jakewharton:butterknife:6.0.0'
compile 'com.readystatesoftware.systembartint:systembartint:1.0.3'
compile 'com.nineoldandroids:library:2.4.0'
compile 'com.jpardogo.materialtabstrip:library:1.1.0'
}
用了V7包,状态栏和标题栏的颜色修改需要,cardView稍后细说,butterknife第三方注解框架,我比较喜欢用xutils,用法都差不多,这里就不细说,不解就看官方文档说明,systembartint这个是一个关于沉浸式状态栏的兼容包,向下兼容到4.4,nineoldandroids动画库。这里简单的提一下,沉浸式状态栏,在不同的手机和系统上可能存在兼容性问题,这里以5.1系统的手机为实例,做个简单的demo
<style name="AppBaseTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!
<item name="android:windowBackground">@color/background_window</item>
<!
<!
<item name="colorPrimary">@color/colorPrimary</item>
<!
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<!
<item name="colorAccent">@color/colorAccent</item>
<!
<item name="android:textColorPrimary">@android:color/white</item>
</style>
<style name="AppTheme" parent="AppBaseTheme" />
Value-v19里面设置透明属性并在activity_main里面设置一个属性fitsSystemWindows(给状态栏预留空间,如果开发中遇到兼容问题建议这里通过style引用该属性):
<style name="AppTheme" parent="AppBaseTheme">
<item name="android:windowTranslucentStatus">true</item>
</style>
android:fitsSystemWindows="true"
Activity测试类调用systembartint设置沉浸式状态栏相关属性
public class MainActivity extends ActionBarActivity {
private SystemBarTintManager mTintManager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTintManager = new SystemBarTintManager(this);
mTintManager.setStatusBarTintEnabled(true);
mTintManager.setTintColor(getResources().getColor(R.color.colorPrimary));
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
demo太简单了,就不上传资源了,作者github上demo底部动态控制沉浸式状态栏,通过onClick获取颜色值,调用changeColor重新赋值,在开发中结合抽屉控件可以达到意想不到的效果。不过这些都不是本文重点,还是回到主题PagerSlidingTabStrip.Java
public class PagerSlidingTabStrip extends HorizontalScrollView {
public static final int DEF_VALUE_TAB_TEXT_ALPHA = 150;
private int mTabBackgroundResId = R.drawable.psts_background_tab;
public PagerSlidingTabStrip(Context context) {
this(context, null);
}
public PagerSlidingTabStrip(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PagerSlidingTabStrip(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setFillViewport(true);
setWillNotDraw(false);
mTabsContainer = new LinearLayout(context);
mTabsContainer.setOrientation(LinearLayout.HORIZONTAL);
addView(mTabsContainer);
mRectPaint = new Paint();
mRectPaint.setAntiAlias(true);
mRectPaint.setStyle(Style.FILL);
DisplayMetrics dm = getResources().getDisplayMetrics();
mScrollOffset = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, mScrollOffset, dm);
mDividerPaint = new Paint();
mDividerPaint.setAntiAlias(true);
mDividerPaint.setStrokeWidth(mDividerWidth);
TypedArray a = context.obtainStyledAttributes(attrs, ANDROID_ATTRS);
int textPrimaryColor = a.getColor(TEXT_COLOR_PRIMARY, getResources().getColor(android.R.color.black));
mUnderlineColor = textPrimaryColor;
mDividerColor = textPrimaryColor;
mIndicatorColor = textPrimaryColor;
int padding = a.getDimensionPixelSize(PADDING_INDEX, 0);
mPaddingLeft = padding > 0 ? padding : a.getDimensionPixelSize(PADDING_LEFT_INDEX, 0);
mPaddingRight = padding > 0 ? padding : a.getDimensionPixelSize(PADDING_RIGHT_INDEX, 0);
a.recycle();
String tabTextTypefaceName = "sans-serif";
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tabTextTypefaceName = "sans-serif-medium";
mTabTextTypefaceStyle = Typeface.NORMAL;
}
a = context.obtainStyledAttributes(attrs, R.styleable.PagerSlidingTabStrip);
mIndicatorColor = a.getColor(R.styleable.PagerSlidingTabStrip_pstsIndicatorColor, mIndicatorColor);
a.recycle();
if (mTabTextColor == null) {
mTabTextColor = createColorStateList(
textPrimaryColor,
textPrimaryColor,
Color.argb(tabTextAlpha,
Color.red(textPrimaryColor),
Color.green(textPrimaryColor),
Color.blue(textPrimaryColor)));
}
if (fontFamily != null) {
tabTextTypefaceName = fontFamily;
}
mTabTextTypeface = Typeface.create(tabTextTypefaceName, mTabTextTypefaceStyle);
setTabsContainerParentViewPaddings();
mTabLayoutParams = isExpandTabs ?
new LinearLayout.LayoutParams(0, LayoutParams.MATCH_PARENT, 1.0f) :
new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
onAttachedToWindow方法给传入的ViewPager的Adapter注册观察者,onDetachedFromWindow则注销观察者,当Adapter数据发生变化时,通过观察者调用onChanged方法调用该类里定义的刷新方法,当数据集失效时,会调用DataSetObserver的onINvalidated()方法。
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
if (mPager != null) {
if (!mAdapterObserver.isAttached()) {
mPager.getAdapter().registerDataSetObserver(mAdapterObserver);
mAdapterObserver.setAttached(true);
}
}
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (mPager != null) {
if (mAdapterObserver.isAttached()) {
mPager.getAdapter().unregisterDataSetObserver(mAdapterObserver);
mAdapterObserver.setAttached(false);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
View视图的测量,onLayout重新计算width,并添加OnGlobalLayoutListener
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (isPaddingMiddle || mPaddingLeft > 0 || mPaddingRight > 0) {
int width;
if (isPaddingMiddle) {
width = getWidth();
} else {
width = getWidth() - mPaddingLeft - mPaddingRight;
}
mTabsContainer.setMinimumWidth(width);
setClipToPadding(false);
}
if (mTabsContainer.getChildCount() > 0) {
mTabsContainer
.getChildAt(0)
.getViewTreeObserver()
.addOnGlobalLayoutListener(firstTabGlobalLayoutListener);
}
super.onLayout(changed, l, t, r, b);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
再来看看大功臣onDraw方法
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (isInEditMode() || mTabCount == 0) {
return;
}
final int height = getHeight();
if (mDividerWidth > 0) {
mDividerPaint.setStrokeWidth(mDividerWidth);
mDividerPaint.setColor(mDividerColor);
for (int i = 0; i < mTabCount - 1; i++) {
View tab = mTabsContainer.getChildAt(i);
canvas.drawLine(tab.getRight(), mDividerPadding, tab.getRight(), height - mDividerPadding, mDividerPaint);
}
}
if (mUnderlineHeight > 0) {
mRectPaint.setColor(mUnderlineColor);
canvas.drawRect(mPaddingLeft, height - mUnderlineHeight, mTabsContainer.getWidth() + mPaddingRight, height, mRectPaint);
}
if (mIndicatorHeight > 0) {
mRectPaint.setColor(mIndicatorColor);
Pair<Float, Float> lines = getIndicatorCoordinates();
canvas.drawRect(lines.first + mPaddingLeft, height - mIndicatorHeight, lines.second + mPaddingLeft, height, mRectPaint);
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
对外公开的方法setViewPager(),该方法的调用必须在ViewPager设置Adapter之后不然会抛出异常。
public void setViewPager(ViewPager pager) {
this.mPager = pager;
if (pager.getAdapter() == null) {
throw new IllegalStateException("ViewPager does not have adapter instance.");
}
isCustomTabs = pager.getAdapter() instanceof CustomTabProvider;
pager.setOnPageChangeListener(mPageListener);
pager.getAdapter().registerDataSetObserver(mAdapterObserver);
mAdapterObserver.setAttached(true);
notifyDataSetChanged();
}
这里的PagerChangerListener代码如下:
private class PageListener implements OnPageChangeListener {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
mCurrentPosition = position;
mCurrentPositionOffset = positionOffset;
int offset = mTabCount > 0 ? (int) (positionOffset * mTabsContainer.getChildAt(position).getWidth()) : 0;
scrollToChild(position, offset);
invalidate();
if (mDelegatePageListener != null) {
mDelegatePageListener.onPageScrolled(position, positionOffset, positionOffsetPixels);
}
}
@Override
public void onPageScrollStateChanged(int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
scrollToChild(mPager.getCurrentItem(), 0);
}
View currentTab = mTabsContainer.getChildAt(mPager.getCurrentItem());
select(currentTab);
if (mPager.getCurrentItem() - 1 >= 0) {
View prevTab = mTabsContainer.getChildAt(mPager.getCurrentItem() - 1);
unSelect(prevTab);
}
if (mPager.getCurrentItem() + 1 <= mPager.getAdapter().getCount() - 1) {
View nextTab = mTabsContainer.getChildAt(mPager.getCurrentItem() + 1);
unSelect(nextTab);
}
if (mDelegatePageListener != null) {
mDelegatePageListener.onPageScrollStateChanged(state);
}
}
@Override
public void onPageSelected(int position) {
updateSelection(position);
if (mDelegatePageListener != null) {
mDelegatePageListener.onPageSelected(position);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
自定义方法notifyDataSetChanged做了三件事,先移除现有的childView,再遍历addView,设置每个Tab的属性(TextView)
public void notifyDataSetChanged() {
mTabsContainer.removeAllViews();
mTabCount = mPager.getAdapter().getCount();
View tabView;
for (int i = 0; i < mTabCount; i++) {
if (isCustomTabs) {
tabView = ((CustomTabProvider) mPager.getAdapter()).getCustomTabView(this, i);
} else {
tabView = LayoutInflater.from(getContext()).inflate(R.layout.psts_tab, this, false);
}
CharSequence title = mPager.getAdapter().getPageTitle(i);
addTab(i, title, tabView);
}
updateTabStyles();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
在调用addTab方法传入了tab的位置position,当点击了当前Tab即当前TextView响应了onClick事件,选中了该Tab,就会掉当前position
private void addTab(final int position, CharSequence title, View tabView) {
TextView textView = (TextView) tabView.findViewById(R.id.psts_tab_title);
if (textView != null) {
if (title != null) textView.setText(title);
}
tabView.setFocusable(true);
tabView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
if (mPager.getCurrentItem() != position) {
View tab = mTabsContainer.getChildAt(mPager.getCurrentItem());
unSelect(tab);
mPager.setCurrentItem(position);
} else if (mTabReselectedListener != null) {
mTabReselectedListener.onTabReselected(position);
}
}
});
mTabsContainer.addView(tabView, position, mTabLayoutParams);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
看完这个开源项目,大致理顺思路:自定义横向ScrollView,构造参数设置ScrollView属性,解析自定义属性,onLayout测量视图绑定OnGlobalLayoutListener监听,在其回调函数里调用notifyDataSetChanged,onDraw方法绘制分割线指示器等,在我们调用setViewPager是通过notifyDataSetChanged添加子视图,并绑定监听事件。
总结:这个开源项目自定义属性比较多,定制扩展方便比较MaterialTabs开源项目,虽然原理都差不多。通过这个项目,加深了我对ViewTreeObserver、OnGlobalLayoutListener等相关类的理解createColorStateList方法可以写一个工具类封装类似的selector。
参考资料:
http://www.aiuxian.com/article/p-638915.html
http://blog.csdn.net/caesardadi/article/details/8307449