最近项目需求,多个tab切换显示不同的页面,但是tab的下划线是一个带有圆角阴影的下划线,看过Tablayout源码的小伙伴可能会知道,通过原生的TabLayout是无法实现的
刚开始参考了几位大佬的方法:
1、Android TabLayout的Indicator如何设置为图片
第一种方法经过实线,发现当tab较多,超出屏幕范围,需要滑动的时候,指示器的位置就会出错;
2、可自定义图片指示器并支持自定义Tab宽度的TabLayout
第二种方法,大佬把每个tab的宽度固定死了,这样如果每个tab的文字数量不一样的话,显示效果就会比较差。
所以综合上面两种方法,我的解决办法是:
1、自己写一个View,继承TabLayout,然后重写dispatchDraw方法,绘制图片,然后根据源码里面指示器的处理,放在我新绘制的图片这里,就OK。
下面贴出代码:
public class SlidingTabLayout extends TabLayout {
private static final String TAG = "SlidingTabLayout";
* 指示器左边坐标
private float mIndicatorLeft = -1;
* 指示器右边坐标
private int mIndicatorRight = -1;
* 选中tab的位置
private int mSelectedPosition = -1;
* 选中tab的偏移量
private float mSelectionOffset;
// private LinearLayout mTabStrip;
private Paint mSelectedIndicatorPaint = new Paint();
* 自定义指示器
private Bitmap mSlideIcon;
* 指示器初始X偏移量
private int mInitTranslationX;
* 指示器初始Y偏移量
private int mInitTranslationY;
public SlidingTabLayout(Context context) {
super(context);
public SlidingTabLayout(Context context, AttributeSet attrs) {
super(context, attrs);
this.mSlideIcon = BitmapFactory.decodeResource(getResources(), R.mipmap.m_guide_tab_video_service_selected);
this.mInitTranslationY = (getBottom() - getTop() - this.mSlideIcon.getHeight());
public void setIndicatorPositionFromTabPosition(int position, float positionOffset) {
mSelectedPosition = position;
mSelectionOffset = positionOffset;
updateIndicatorPosition();
* 计算滑动杆位置
private void updateIndicatorPosition() {
LinearLayout mTabStrip = getTabStrip();
if (mTabStrip == null) {
return;
final View selectedTitle = mTabStrip.getChildAt(mSelectedPosition);
int left, right;
if (selectedTitle != null && selectedTitle.getWidth() > 0) {
left = selectedTitle.getLeft();
right = selectedTitle.getRight();
if (mSelectionOffset > 0f && mSelectedPosition < mTabStrip.getChildCount() - 1) {
View nextTitle = mTabStrip.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 dispatchDraw(Canvas canvas) {
if (mSlideIcon == null) {
return;
//绘制指示器
canvas.save();
if (mIndicatorLeft >= 0 && mIndicatorRight > mIndicatorLeft) {
// 平移到正确的位置,修正tabs的平移量
canvas.translate(mIndicatorLeft, getBottom() - getTop() - this.mSlideIcon.getHeight());
Matrix matrix = new Matrix();
//设置指示器的长度与tab文字长度相同
matrix.postScale((mIndicatorRight - mIndicatorLeft) / mSlideIcon.getWidth(), 1);
canvas.drawBitmap(mSlideIcon, matrix, mSelectedIndicatorPaint);
canvas.restore();
super.dispatchDraw(canvas);
* tab的父容器,注意空指针
@Nullable
public LinearLayout getTabStrip() {
Class<?> tabLayout = TabLayout.class;
Field tabStrip = null;
try {
//这里是通过反射的获取SlidingTabStrip实例对象,不同的api,这里映射的方法名可能不一样
tabStrip = tabLayout.getDeclaredField("mTabStrip");
// API 28
// tabStrip = tabLayout.getDeclaredField("slidingTabIndicator");
} catch (NoSuchFieldException e) {
LogUtils.e(TAG, "NoSuchFieldException of mTabStrip", e);
LinearLayout mTabStripLLayout = null;
if (tabStrip != null) {
tabStrip.setAccessible(true);
try {
mTabStripLLayout = (LinearLayout) tabStrip.get(this);
if (mTabStripLLayout != null) {
mTabStripLLayout.setClipChildren(false);
} catch (IllegalAccessException e) {
LogUtils.e(TAG, "IllegalAccessException", e);
return mTabStripLLayout;
}
布局配置:
<com.guahao.android.wyt.moduleguide.videoservice.widget.SlidingTabLayout
android:id="@+id/m_guide_fragment_video_service_tl"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="start"
android:paddingTop="@dimen/m_guide_20dp"
android:paddingBottom="@dimen/m_guide_10dp"
app:tabIndicatorColor="@color/m_guide_23CBD6"
app:tabIndicatorHeight="@dimen/m_guide_0dp"
app:tabMode="scrollable"
app:tabSelectedTextColor="@color/m_guide_23CBD6"
app:tabTextAppearance="@style/TabLayoutTextAppearance"
app:tabTextColor="@color/m_guide_9AA5BB"
app:tabMaxWidth="@dimen/m_guide_200dp"
app:tabMinWidth="@dimen/m_guide_20dp"
app:tabPaddingStart="@dimen/m_guide_20dp"
app:tabPaddingEnd="@dimen/m_guide_20dp"/>
代码中的使用,这里主要是设置PageChangeListener监听,然后在onPageScrolled方法中将移动位置和移动偏移量传递给SliddingTabLayout。
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mViewPager = bindView(R.id.m_fragment_vp);
mTabLayout = bindView(R.id.m_fragment_tl);
//将tabLayout与viewPager关联起来
mTabLayout.setupWithViewPager(mViewPager, true);
mViewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(mTabLayout) {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels);
//关键点
mTabLayout.setIndicatorPositionFromTabPosition(position, positionOffset);
@Override
public void onPageScrollStateChanged(int state) {
super.onPageScrollStateChanged(state);
.....
}
到这里就结束了,
注意点:
2、代码中将图片写死了,如果想替换图片的话将SliddingTabLayout中mSlideIcon的值改成自己的就可以了,或者自己添加一个设置图片的方法也可以的。
3、 这里tabStrip = tabLayout.getDeclaredField("mTabStrip");如果获取不到的话,请用下面的这句
// API 28
// tabStrip = tabLayout.getDeclaredField("slidingTabIndicator");
因为不同的API可能会有不同
其它就没有什么,比较简单,大部分的计算都是从源码中拷过来的,如果读者有什么问题欢迎来提问?