前言
TabLayout
我相信大家都有使用过,但是官方并没有对Indicator
可以设置为图片的支持,只能是简单的水平线,也就是滑动杆那个玩意,那么如何做到可以用图片来做指示器呢?作为一个喜欢探索新颖解决方案的我,现在给大家带来了大家都很期待的问题的解决方案,下面我来讲讲我的思路和解决方案
先上效果
这是我将要做到的事情,实现TabLayout
的Indicator
可自定义image
分析源码
既然TabLayout
能够和ViewPager
手势联动那就说明Tablayout
和ViewPager
有关联,先来看一下他们是如何关联的
tab_layout.setupWithViewPager(viewpager)
通过这个方法Tablayout
和ViewPager
相遇了,继续跟进这个方法执行最终会执行到这里
private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
boolean implicitSetup) {
...
if (viewPager != null) {
//保存一个成员变量引用
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
if (mPageChangeListener == null) {
mPageChangeListener = new TabLayoutOnPageChangeListener(this);
}
mPageChangeListener.reset();
//关注点1
viewPager.addOnPageChangeListener(mPageChangeListener);
//关注点2
// Now we'll add a tab selected listener to set ViewPager's current item
//注释很明显翻译过来就是添加一个选项选择监听器来设置ViewPager的当前项
mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
addOnTabSelectedListener(mCurrentVpSelectedListener);
}
...
}
关注点1添加监听ViewPager
的滑动事件,关注点2添加Tablayout
选择事件来设置ViewPager
的当前项,关注点2注释已经解释得很清楚,重点分析关注点1ViewPager
滑动事件,我们lou一眼
public static class TabLayoutOnPageChangeListener implements ViewPager.OnPageChangeListener {
//Viewpager滑动偏移量回调
@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的滑动信息设置给tablayout,这是一个关键的信息!!!
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
}
}
...
//在这个方法里面调用了SlidingTabStrip的setIndicatorPositionFromTabPosition方法获取滑动数据
void setScrollPosition(int position, float positionOffset, boolean updateSelectedText,
boolean updateIndicatorPosition) {
···
// Set the indicator position, if enabled
if (updateIndicatorPosition) {
//设置Indicator的位置
mTabStrip.setIndicatorPositionFromTabPosition(position, positionOffset);
}
···
}
//用变量保存参数并执行 updateIndicatorPosition方法
void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
if (mIndicatorAnimator != null && mIndicatorAnimator.isRunning()) {
mIndicatorAnimator.cancel();
}
mSelectedPosition = position;
mSelectionOffset = positionOffset;
//更新Indicator位置
updateIndicatorPosition();
}
//通过viewpager的滑动偏移量计算选项卡左边和右边的位置
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;
//执行这个方法最终会执行view.postInvalidate()触发view的重绘
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
// Thick colored underline below the current selection
// 根据前面计算过后的位置信息绘制indicator滑动杆
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
canvas.drawRect(mIndicatorLeft, getHeight() - mSelectedIndicatorHeight,
mIndicatorRight, getHeight(), mSelectedIndicatorPaint);
}
}
看到这里那么恭喜你Tablayout
的Indicator
和ViewPager
联动的流程你已经掌握了。我来总结一下流程
1.通过调用setupWithViewPager
为ViewPager
添加滑动监听
2.并在监听回调里面把数据传递给Tablayout
的内部类SlidingTabStrip
就是指示器
3.SlidingTabStrip
接收到数据之后调用 ViewCompat.postInvalidateOnAnimation(this);
这个方法将会执行view.postInvalidate()
触发view
的重绘
实现想法
自己造个轮子实现将Tablayout
的Indecator
换成图片,说干就干
遇到问题
问题来了,Tablayout
的的滑动杆是在SlidingTabStrip
这个类中的draw
方法里面进行的绘制的,在外部不能对其进行修改,于是我就想既然不能动你,那我就干掉你,另辟蹊径找其他方法代替你,所以我决定自己写个Indecator
类去监听viewpager
的滑动事件,并更新显示位置
解决思路
从源码分析中我们已经知道要更新Indecator
的位置就是要拿到viewPager
的滑动偏移量,计算出他的左右位置然后通知view
重绘,那我也自己写个Indecator
来做同样的事代码如下
/**
* 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;
//滑动的图片
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();
}
}
//计算滑动杆位置
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
* 通过反射TabLayout拿到SlidingTabStrip这个内部类
*/
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) {
// 将滑动偏移量传给Indicator
imageIndicator.setIndicatorPositionFromTabPosition(position, positionOffset);
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
});
番外
如果你反射了Tablayout,想要改变他属性或者他的内部类属性,请在项目发布时加上混淆,避免出现反射无效!
-keep public class android.support.design.widget.TabLayout{
*;
}
-keep class android.support.design.widget.TabLayout$* {
*;
}
总结
借鉴老罗的一句话我们在项目中碰到问题的时候,通常第一反应都是到网上去搜索答案。但是有时候有些问题,网络并不能给出满意的答案。这时候就千万不要忘了你所拥有的一个大招——从代码中找答案!以上就是我的解决方案,查看源码从中获得灵感,解决实际问题!坚决不做码农,我是工程师
具体使用请查看 Demo
欢迎评论点赞交流