前言
TabLayout
我相信大家都有使用過(guò)越平,但是官方并沒(méi)有對(duì)Indicator
可以設(shè)置為圖片的支持,只能是簡(jiǎn)單的水平線锈遥,也就是滑動(dòng)桿那個(gè)玩意勘究,那么如何做到可以用圖片來(lái)做指示器呢?作為一個(gè)喜歡探索新穎解決方案的我,現(xiàn)在給大家?guī)?lái)了大家都很期待的問(wèn)題的解決方案刹衫,下面我來(lái)講講我的思路和解決方案
先上效果
這是我將要做到的事情醋寝,實(shí)現(xiàn)TabLayout
的Indicator
可自定義image
分析源碼
既然TabLayout
能夠和ViewPager
手勢(shì)聯(lián)動(dòng)那就說(shuō)明Tablayout
和ViewPager
有關(guān)聯(lián),先來(lái)看一下他們是如何關(guān)聯(lián)的
tab_layout.setupWithViewPager(viewpager)
通過(guò)這個(gè)方法Tablayout
和ViewPager
相遇了,繼續(xù)跟進(jìn)這個(gè)方法執(zhí)行最終會(huì)執(zhí)行到這里
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
...
if (viewPager != null) {
//保存一個(gè)成員變量引用
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
}
mPageChangeListener.reset();
//關(guān)注點(diǎn)1
viewPager.addOnPageChangeListener(mPageChangeListener);
//關(guān)注點(diǎn)2
// Now we'll add a tab selected listener to set ViewPager's current item
//注釋很明顯翻譯過(guò)來(lái)就是添加一個(gè)選項(xiàng)選擇監(jiān)聽(tīng)器來(lái)設(shè)置ViewPager的當(dāng)前項(xiàng)
mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
addOnTabSelectedListener(mCurrentVpSelectedListener);
}
...
}
關(guān)注點(diǎn)1添加監(jiān)聽(tīng)ViewPager
的滑動(dòng)事件带迟,關(guān)注點(diǎn)2添加Tablayout
選擇事件來(lái)設(shè)置ViewPager
的當(dāng)前項(xiàng)音羞,關(guān)注點(diǎn)2注釋已經(jīng)解釋得很清楚,重點(diǎn)分析關(guān)注點(diǎn)1ViewPager
滑動(dòng)事件仓犬,我們lou一眼
public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
//Viewpager滑動(dòng)偏移量回調(diào)
@Override
public void onPageScrolled(final int position, final float positionOffset,
final int positionOffsetPixels) {
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null) {
// Only update the text selection if we're not settling, or we are settling after
// being dragged
final boolean updateText = mScrollState != SCROLL_STATE_SETTLING ||
mPreviousScrollState == SCROLL_STATE_DRAGGING;
// Update the indicator if we're not settling after being idle. This is caused
// from a setCurrentItem() call and will be handled by an animation from
// onPageSelected() instead.
final boolean updateIndicator = !(mScrollState == SCROLL_STATE_SETTLING
&& mPreviousScrollState == SCROLL_STATE_IDLE);
//將ViewPager的滑動(dòng)信息設(shè)置給tablayout嗅绰,這是一個(gè)關(guān)鍵的信息!!!
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
}
}
...
//在這個(gè)方法里面調(diào)用了SlidingTabStrip的setIndicatorPositionFromTabPosition方法獲取滑動(dòng)數(shù)據(jù)
void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
boolean updateIndicatorPosition) {
···
// Set the indicator position, if enabled
if (updateIndicatorPosition) {
//設(shè)置Indicator的位置
mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
}
···
}
//用變量保存參數(shù)并執(zhí)行 updateIndicatorPosition方法
void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}
mSelectedPosition = position;
mSelectionOffset = positionOffset;
//更新Indicator位置
updateIndicatorPosition();
}
//通過(guò)viewpager的滑動(dòng)偏移量計(jì)算選項(xiàng)卡左邊和右邊的位置
private void updateIndicatorPosition() {
final View selectedTitle = getChildAt(mSelectedPosition);
int left, right;
if (selectedTitle != null && selectedTitle.getWidth() > 0) {
left = selectedTitle.getLeft();
right = selectedTitle.getRight();
if (mSelectionOffset > 0f && mSelectedPosition < getChildCount() - 1) {
// Draw the selection partway between the tabs
View nextTitle = getChildAt(mSelectedPosition + 1);
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
} else {
left = right = -1;
}
setIndicatorPosition(left, right);
}
void setIndicatorPosition(int left, int right) {
if (left != mIndicatorLeft || right != mIndicatorRight) {
// If the indicator's left/right has changed, invalidate
mIndicatorLeft = left;
mIndicatorRight = right;
//執(zhí)行這個(gè)方法最終會(huì)執(zhí)行view.postInvalidate()觸發(fā)view的重繪
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Thick colored underline below the current selection
// 根據(jù)前面計(jì)算過(guò)后的位置信息繪制indicator滑動(dòng)桿
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
}
看到這里那么恭喜你Tablayout
的Indicator
和ViewPager
聯(lián)動(dòng)的流程你已經(jīng)掌握了婶肩。我來(lái)總結(jié)一下流程
1.通過(guò)調(diào)用setupWithViewPager
為ViewPager
添加滑動(dòng)監(jiān)聽(tīng)
2.并在監(jiān)聽(tīng)回調(diào)里面把數(shù)據(jù)傳遞給Tablayout
的內(nèi)部類SlidingTabStrip
就是指示器
3.SlidingTabStrip
接收到數(shù)據(jù)之后調(diào)用 ViewCompat.postInvalidateOnAnimation(this);
這個(gè)方法將會(huì)執(zhí)行view.postInvalidate()
觸發(fā)view
的重繪
實(shí)現(xiàn)想法
自己造個(gè)輪子實(shí)現(xiàn)將Tablayout
的Indecator
換成圖片办陷,說(shuō)干就干
遇到問(wèn)題
問(wèn)題來(lái)了,Tablayout
的的滑動(dòng)桿是在SlidingTabStrip
這個(gè)類中的draw
方法里面進(jìn)行的繪制的律歼,在外部不能對(duì)其進(jìn)行修改民镜,于是我就想既然不能動(dòng)你,那我就干掉你险毁,另辟蹊徑找其他方法代替你制圈,所以我決定自己寫(xiě)個(gè)Indecator
類去監(jiān)聽(tīng)viewpager
的滑動(dòng)事件们童,并更新顯示位置
解決思路
從源碼分析中我們已經(jīng)知道要更新Indecator
的位置就是要拿到viewPager
的滑動(dòng)偏移量,計(jì)算出他的左右位置然后通知view
重繪鲸鹦,那我也自己寫(xiě)個(gè)Indecator
來(lái)做同樣的事代碼如下
/**
* Created by xwc on 2018/6/6
*
*/
public class ImageIndicator extends View {
public ImageIndicator(Context context) {
super(context);
}
public ImageIndicator(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public ImageIndicator(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
private float mIndicatorLeft = -1;
private int mIndicatorRight = -1;
//滑動(dòng)的圖片
private Bitmap mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.progressbar);
private Paint mSelectedIndicatorPaint = new Paint();
int mSelectedPosition = -1;
float mSelectionOffset;
LinearLayout tab;
TabLayout tabs;
public void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
mSelectedPosition = position;
mSelectionOffset = positionOffset;
updateIndicatorPosition();
}
public void setupWithTabLayout(TabLayout tabs) {
this.tabs = tabs;
if (tab == null) {
tab = getTabStrip();
}
}
//計(jì)算滑動(dòng)桿位置
private void updateIndicatorPosition() {
if (tab == null) {
return;
}
final View selectedTitle = tab.getChildAt(mSelectedPosition);
int left, right;
if (selectedTitle != null && selectedTitle.getWidth() > 0) {
left = selectedTitle.getLeft();
right = selectedTitle.getRight();
if (mSelectionOffset > 0f && mSelectedPosition < tab.getChildCount() - 1) {
View nextTitle = tab.getChildAt(mSelectedPosition + 1);
left = (int) (mSelectionOffset * nextTitle.getLeft() +
(1.0f - mSelectionOffset) * left);
right = (int) (mSelectionOffset * nextTitle.getRight() +
(1.0f - mSelectionOffset) * right);
}
} else {
left = right = -1;
}
setIndicatorPosition(left, right);
}
void setIndicatorPosition(int left, int right) {
if (left != mIndicatorLeft || right != mIndicatorRight) {
mIndicatorLeft = left;
mIndicatorRight = right;
//通知view重繪
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//繪制圖片
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawBitmap(mBitmap, mIndicatorLeft, getHeight() - mBitmap.getHeight(), mSelectedIndicatorPaint);
}
}
/**
* tabs : TabLayout
* 通過(guò)反射TabLayout拿到SlidingTabStrip這個(gè)內(nèi)部類
*/
public LinearLayout getTabStrip() {
Class<?> tabLayout = tabs.getClass();
Field tabStrip = null;
try {
tabStrip = tabLayout.getDeclaredField("mTabStrip");
} catch (NoSuchFieldException e) {
e.printStackTrace();
}
tabStrip.setAccessible(true);
try {
tab = (LinearLayout) tabStrip.get(tabs);
return tab;
} catch (Exception e) {
e.printStackTrace();
}
return tab;
}
}
使用:
viewPager.setAdapter(new MyViewPagerAdapter());
tabLayout.setupWithViewPager(viewPager);
imageIndicator.setupWithTabLayout(tabLayout);
viewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// 將滑動(dòng)偏移量傳給Indicator
imageIndicator.setIndicatorPositionFromTabPosition(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
番外
如果你反射了Tablayout慧库,想要改變他屬性或者他的內(nèi)部類屬性,請(qǐng)?jiān)陧?xiàng)目發(fā)布時(shí)加上混淆馋嗜,避免出現(xiàn)反射無(wú)效齐板!
-keep public class android.support.design.widget.TabLayout{
*;
}
-keep class android.support.design.widget.TabLayout$* {
*;
}
總結(jié)
借鑒老羅的一句話我們?cè)陧?xiàng)目中碰到問(wèn)題的時(shí)候,通常第一反應(yīng)都是到網(wǎng)上去搜索答案葛菇。但是有時(shí)候有些問(wèn)題甘磨,網(wǎng)絡(luò)并不能給出滿意的答案。這時(shí)候就千萬(wàn)不要忘了你所擁有的一個(gè)大招——從代碼中找答案眯停!以上就是我的解決方案济舆,查看源碼從中獲得靈感,解決實(shí)際問(wèn)題莺债!堅(jiān)決不做碼農(nóng)滋觉,我是工程師
具體使用請(qǐng)查看 Demo
歡迎評(píng)論點(diǎn)贊交流