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