欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android自定义Tablayout下划线指示器Indicator:设置宽高、圆角、渐变颜色

程序员文章站 2022-06-01 07:50:11
...
Android自定义Tablayout下划线指示器Indicator:设置宽高、圆角、渐变颜色


Android原生的Tablayout下面有一个指示器(指示线、下划线),如图所示:
Android自定义Tablayout下划线指示器Indicator:设置宽高、圆角、渐变颜色
详情见附录1。

但是Android原生的Tablayout指示器Indicator自定义空间很有限,能设置颜色,如果想把Tablayout指示器Indicator的宽和高做调整适应自己产品开发的UI设计要求,就很难办到了,更是如果要求的Indicator的指示器呈现渐变颜色、圆角,就难上加难了。就必须自己想办法解决这些自定义问题。


比如下面的这个设计要求:

Android自定义Tablayout下划线指示器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>

代码运行的最终结果如图:
Android自定义Tablayout下划线指示器Indicator:设置宽高、圆角、渐变颜色


实现对MagicIndicator的增强,满足了UI设计师的颜色渐变需求。

附录:
1,《Android Material Design TabLayout属性app:tabMode和app: tabGravity》链接:https://blog.csdn.net/zhangphil/article/details/48931483 

相关标签: Android