Android自定义Tablayout下划线指示器Indicator:设置宽高、圆角、渐变颜色
Android原生的Tablayout下面有一个指示器(指示线、下划线),如图所示:
详情见附录1。
但是Android原生的Tablayout指示器Indicator自定义空间很有限,能设置颜色,如果想把Tablayout指示器Indicator的宽和高做调整适应自己产品开发的UI设计要求,就很难办到了,更是如果要求的Indicator的指示器呈现渐变颜色、圆角,就难上加难了。就必须自己想办法解决这些自定义问题。
UI设计师对指示器Indicator的宽、高、圆角都提出了规范要求。对于能自定义Indicator的宽、高、圆角及颜色的第三方开源项目很多。但是这次设计要求Indicator必须呈现出一定的颜色渐变,这就比较困难了,因为很多第三方开源的项目仅仅支持对Indicator设计一个单一的颜色,不支持多种颜色的渐变。
找来找去,我在github上找到了一个支持自定义Indicator的开源项目:
https://github.com/hackware1993/MagicIndicator
但是若直接基于该项目对Indicator进行渐变颜色还是比较麻烦,比如,如果我想对Indicator进行自定义,就需要继承该项目的CommonNavigatorAdapter,然后在CommonNavigatorAdapter里面重写getIndicator,然后在getIndicator里面返回一个自定义的IPagerIndicator,麻烦就麻烦在继承实现IPagerIndicator对于底部下划线Indicator的onDraw方法,该方法将直接对Indicator底部的下划线指示器Indicator进行画线画圆操作等等,但是我的需求只是对一个圆角线性下划线指示器Indicator颜色渐变即可。我仔细看了原作者的代码时候,终于找到一个简单的做法实现,就是在自定义IPagerIndicator的onDraw里面,对mPaint设置一个线性渐变LinearGradient即可,但是设置LinearGradient需要当前渐变的宽高坐标,而这部分宽高坐标取决于原项目中的mLineRect(mLineRect是一个RectF),该mLineRect代表了真正要绘制下划线指示器Indicator的区域,然而,如果有这个对象,我直接就可以在onDraw里面根据mLineRect获取四个空间位置点,然后构造线性渐变LinearGradient,进而设置给mPaint,就轻松实现颜色渐变了,这样的实现方案最大好处就是无须对原项目做二次复杂定制化开发。
但是遗憾的是,原作者在原项目中把mLineRect设置为私有private性质,且没有提供public的get方法,因此,我把原作者的LinePagerIndicator重新拉下来,增加了一个公开方法,把mLineRect返回,
/**
* 返回底部画线的RectF mLineRect。
*
* @return
*/
public RectF getLineRect() {
return mLineRect;
}
作为对LinePagerIndicator的扩展,最终的LinePagerIndicatorEx.java:
package zhangphil.test;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.view.View;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
import net.lucode.hackware.magicindicator.FragmentContainerHelper;
import net.lucode.hackware.magicindicator.buildins.ArgbEvaluatorHolder;
import net.lucode.hackware.magicindicator.buildins.UIUtil;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.model.PositionData;
import java.util.Arrays;
import java.util.List;
public class LinePagerIndicatorEx extends View implements IPagerIndicator {
public static final int MODE_MATCH_EDGE = 0; // 直线宽度 == title宽度 - 2 * mXOffset
public static final int MODE_WRAP_CONTENT = 1; // 直线宽度 == title内容宽度 - 2 * mXOffset
public static final int MODE_EXACTLY = 2; // 直线宽度 == mLineWidth
private int mMode; // 默认为MODE_MATCH_EDGE模式
// 控制动画
private Interpolator mStartInterpolator = new LinearInterpolator();
private Interpolator mEndInterpolator = new LinearInterpolator();
private float mYOffset; // 相对于底部的偏移量,如果你想让直线位于title上方,设置它即可
private float mLineHeight;
private float mXOffset;
private float mLineWidth;
private float mRoundRadius;
private Paint mPaint;
private List<PositionData> mPositionDataList;
private List<Integer> mColors;
private RectF mLineRect = new RectF();
public LinePagerIndicatorEx(Context context) {
super(context);
init(context);
}
private void init(Context context) {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mPaint.setStyle(Paint.Style.FILL);
mLineHeight = UIUtil.dip2px(context, 3);
mLineWidth = UIUtil.dip2px(context, 10);
}
@Override
protected void onDraw(Canvas canvas) {
canvas.drawRoundRect(mLineRect, mRoundRadius, mRoundRadius, mPaint);
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (mPositionDataList == null || mPositionDataList.isEmpty()) {
return;
}
// 计算颜色
if (mColors != null && mColors.size() > 0) {
int currentColor = mColors.get(Math.abs(position) % mColors.size());
int nextColor = mColors.get(Math.abs(position + 1) % mColors.size());
int color = ArgbEvaluatorHolder.eval(positionOffset, currentColor, nextColor);
mPaint.setColor(color);
}
// 计算锚点位置
PositionData current = FragmentContainerHelper.getImitativePositionData(mPositionDataList, position);
PositionData next = FragmentContainerHelper.getImitativePositionData(mPositionDataList, position + 1);
float leftX;
float nextLeftX;
float rightX;
float nextRightX;
if (mMode == MODE_MATCH_EDGE) {
leftX = current.mLeft + mXOffset;
nextLeftX = next.mLeft + mXOffset;
rightX = current.mRight - mXOffset;
nextRightX = next.mRight - mXOffset;
} else if (mMode == MODE_WRAP_CONTENT) {
leftX = current.mContentLeft + mXOffset;
nextLeftX = next.mContentLeft + mXOffset;
rightX = current.mContentRight - mXOffset;
nextRightX = next.mContentRight - mXOffset;
} else { // MODE_EXACTLY
leftX = current.mLeft + (current.width() - mLineWidth) / 2;
nextLeftX = next.mLeft + (next.width() - mLineWidth) / 2;
rightX = current.mLeft + (current.width() + mLineWidth) / 2;
nextRightX = next.mLeft + (next.width() + mLineWidth) / 2;
}
mLineRect.left = leftX + (nextLeftX - leftX) * mStartInterpolator.getInterpolation(positionOffset);
mLineRect.right = rightX + (nextRightX - rightX) * mEndInterpolator.getInterpolation(positionOffset);
mLineRect.top = getHeight() - mLineHeight - mYOffset;
mLineRect.bottom = getHeight() - mYOffset;
invalidate();
}
@Override
public void onPageSelected(int position) {
}
@Override
public void onPageScrollStateChanged(int state) {
}
@Override
public void onPositionDataProvide(List<PositionData> dataList) {
mPositionDataList = dataList;
}
public float getYOffset() {
return mYOffset;
}
public void setYOffset(float yOffset) {
mYOffset = yOffset;
}
public float getXOffset() {
return mXOffset;
}
public void setXOffset(float xOffset) {
mXOffset = xOffset;
}
public float getLineHeight() {
return mLineHeight;
}
public void setLineHeight(float lineHeight) {
mLineHeight = lineHeight;
}
public float getLineWidth() {
return mLineWidth;
}
public void setLineWidth(float lineWidth) {
mLineWidth = lineWidth;
}
public float getRoundRadius() {
return mRoundRadius;
}
public void setRoundRadius(float roundRadius) {
mRoundRadius = roundRadius;
}
public int getMode() {
return mMode;
}
public void setMode(int mode) {
if (mode == MODE_EXACTLY || mode == MODE_MATCH_EDGE || mode == MODE_WRAP_CONTENT) {
mMode = mode;
} else {
throw new IllegalArgumentException("mode " + mode + " not supported.");
}
}
public Paint getPaint() {
return mPaint;
}
/**
* 返回底部画线的RectF mLineRect。
*
* @return
*/
public RectF getLineRect() {
return mLineRect;
}
public List<Integer> getColors() {
return mColors;
}
public void setColors(Integer... colors) {
mColors = Arrays.asList(colors);
}
public Interpolator getStartInterpolator() {
return mStartInterpolator;
}
public void setStartInterpolator(Interpolator startInterpolator) {
mStartInterpolator = startInterpolator;
if (mStartInterpolator == null) {
mStartInterpolator = new LinearInterpolator();
}
}
public Interpolator getEndInterpolator() {
return mEndInterpolator;
}
public void setEndInterpolator(Interpolator endInterpolator) {
mEndInterpolator = endInterpolator;
if (mEndInterpolator == null) {
mEndInterpolator = new LinearInterpolator();
}
}
}
然后再写一个HXLinePagerIndicator继承自LinePagerIndicatorEx:
package zhangphil.test;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.LinearGradient;
public class HXLinePagerIndicator extends LinePagerIndicatorEx {
public HXLinePagerIndicator(Context context) {
super(context);
}
@Override
protected void onDraw(Canvas canvas) {
LinearGradient lg = new LinearGradient(getLineRect().left, getLineRect().top, getLineRect().right, getLineRect().bottom, new int[]{0xFFD3B151, 0xFFF6DD99}, null, LinearGradient.TileMode.CLAMP);
getPaint().setShader(lg);
canvas.drawRoundRect(getLineRect(), getRoundRadius(), getRoundRadius(), getPaint());
}
}
在HXLinePagerIndicator,要做到事情仅仅就是在onDraw时候,针对mLineRect进行一个颜色渐变处理。对于其他属性控制如圆角、宽、高、动画等等,不做特殊处理。
最终抽象完毕后,可以在上层直接使用:
package zhangphil.test;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.TextView;
import net.lucode.hackware.magicindicator.MagicIndicator;
import net.lucode.hackware.magicindicator.ViewPagerHelper;
import net.lucode.hackware.magicindicator.buildins.UIUtil;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.CommonNavigator;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.CommonNavigatorAdapter;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerIndicator;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.abs.IPagerTitleView;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.indicators.LinePagerIndicator;
import net.lucode.hackware.magicindicator.buildins.commonnavigator.titles.SimplePagerTitleView;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class TabActivity extends AppCompatActivity {
private ArrayList<String> mTitles;
private static final String[] CHANNELS = new String[]{"zhang", "phil", "zhang phil", "csdn", "zhang phil csdn", "zhang phil @ csdn", "blog.csdn.net/zhangphil", "android"};
private List<String> mDataList = Arrays.asList(CHANNELS);
private ExamplePagerAdapter mExamplePagerAdapter = new ExamplePagerAdapter(mDataList);
private ViewPager mViewPager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tab_avtivity);
mTitles = new ArrayList<>();
for (int i = 0; i < 10; i++) {
mTitles.add(String.valueOf(i));
}
mViewPager = findViewById(R.id.view_pager);
mViewPager.setAdapter(mExamplePagerAdapter);
MagicIndicator magicIndicator = findViewById(R.id.magic_indicator);
magicIndicator.setBackgroundColor(Color.WHITE);
CommonNavigator commonNavigator = new CommonNavigator(this);
//true 选项卡均分父布局宽度。这种情况仅仅在选项卡较少且文字比较短时候合适
// 如果文字很长,且选项卡很多,那么久会挤在一起密密麻麻,视觉上比较难看。
//通常设置false。
commonNavigator.setAdjustMode(false);
commonNavigator.setAdapter(new CommonNavigatorAdapter() {
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public IPagerTitleView getTitleView(Context context, final int index) {
SimplePagerTitleView simplePagerTitleView = new SimplePagerTitleView(context);
simplePagerTitleView.setText(mDataList.get(index));
simplePagerTitleView.setNormalColor(getResources().getColor(android.R.color.holo_orange_light));
simplePagerTitleView.setSelectedColor(getResources().getColor(android.R.color.holo_red_light));
simplePagerTitleView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mViewPager.setCurrentItem(index);
}
});
return simplePagerTitleView;
}
@Override
public IPagerIndicator getIndicator(Context context) {
HXLinePagerIndicator indicator = new HXLinePagerIndicator(context);
indicator.setMode(LinePagerIndicator.MODE_EXACTLY);
indicator.setLineHeight(UIUtil.dip2px(context, 6));
indicator.setLineWidth(UIUtil.dip2px(context, 50));
indicator.setRoundRadius(UIUtil.dip2px(context, 3));
indicator.setStartInterpolator(new AccelerateInterpolator());
indicator.setEndInterpolator(new DecelerateInterpolator(2.0f));
return indicator;
}
});
magicIndicator.setNavigator(commonNavigator);
ViewPagerHelper.bind(magicIndicator, mViewPager);
}
public class ExamplePagerAdapter extends PagerAdapter {
private List<String> mDataList;
public ExamplePagerAdapter(List<String> dataList) {
mDataList = dataList;
}
@Override
public int getCount() {
return mDataList == null ? 0 : mDataList.size();
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == object;
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
TextView textView = new TextView(container.getContext());
textView.setText(mDataList.get(position));
textView.setGravity(Gravity.CENTER);
textView.setTextColor(Color.BLACK);
textView.setTextSize(24);
container.addView(textView);
return textView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((View) object);
}
@Override
public int getItemPosition(Object object) {
TextView textView = (TextView) object;
String text = textView.getText().toString();
int index = mDataList.indexOf(text);
if (index >= 0) {
return index;
}
return POSITION_NONE;
}
@Override
public CharSequence getPageTitle(int position) {
return mDataList.get(position);
}
}
}
Xml布局文件:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<net.lucode.hackware.magicindicator.MagicIndicator
android:id="@+id/magic_indicator"
android:layout_width="match_parent"
android:layout_height="50dp" />
<android.support.v4.view.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
代码运行的最终结果如图:
实现对MagicIndicator的增强,满足了UI设计师的颜色渐变需求。
附录:1,《Android Material Design TabLayout属性app:tabMode和app: tabGravity》链接:https://blog.csdn.net/zhangphil/article/details/48931483