Android 渐变的‘TabLayout’ , (含免费( java / kotlin) Demo)

Posted android超级兵

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 渐变的‘TabLayout’ , (含免费( java / kotlin) Demo)相关的知识,希望对你有一定的参考价值。

先来看看完成的效果:


简单解释: 在滑动的过程中,渐变文字会随着ViewPager的滑动而变化!!

绘制文字与BaseLine思考

先来看看最初版代码:

public class GradualChangeTv extends AppCompatTextView {
    public Paint mPaint = new Paint();

    public final String text = "android 超级兵";

    public GradualChangeTv(Context context) {
        this(context, null);
    }

    public GradualChangeTv(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public GradualChangeTv(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mPaint.setColor(Color.RED);
        //抗锯齿
        mPaint.setAntiAlias(true);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, 0, 0, mPaint);
    }
}

就是简单的绘制了一行字

疑问:

  • 为什么这里要继承自AppCompatTextView 而不是View

    答: 偷个懒而已,因为不用在我来测量View,直接用父类的就行

来看看效果顺便也看看布局:

出现问题: 文字并没有显示
答:因为文字坐标系和屏幕坐标系不一样,文字坐标系是从BaseLine线开始计算的

先来回顾一下屏幕的坐标系


在来看看文字的坐标系
(图片来自于网络)

再来思考一下文字是为什么不显示的:

  • 虚线为BaseLine

如果此时我把字体放大到100,看一看我能不能看到文字


再一次证明了文字是从BaseLine线开始绘制

文字居中

可以用两条辅助线,水平线与垂直线.然后在来看文字是否居中

代码:

⚠️ : 底部会给出完整代码.这里看思路即可,不用复制代码

@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //获取当前控件的宽高
        int viewWidth = getWidth() / 2;
        int viewHeight = getHeight() / 2;
        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, viewWidth, viewHeight, mPaint);

        //绘制居中线
        drawCenterLine(canvas, viewWidth, viewHeight);
    }

    private void drawCenterLine(Canvas canvas, int viewWidth, int viewHeight) {
        //垂直线
        canvas.drawLine(viewWidth, 0, viewWidth, getHeight(), mPaint);

        //水平线
        canvas.drawLine(0,viewHeight,getWidth(),viewHeight,mPaint);
    }

效果图:


可以看出,还是上面说的那个问题,文字绘制是基于baseLine线来绘制的.

文字居中思路:

  • 通过mPaint.measureText(text) 获取文字宽
  • 通过mPaint.descent() + mPaint.ascent(); 获取文字高
  • 然后控件各取一半,让控件减去即可

这里的descent和ascent可以参考上面文字绘制图

相关代码:

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //文字宽度
        float textWidth = mPaint.measureText(text);
        //文字高度
        float textHeight = mPaint.descent() + mPaint.ascent();

        //获取当前控件的宽高
        int viewWidth = getWidth() / 2;
        int viewHeight = getHeight() / 2;

        canvas.drawText(text, viewWidth - textWidth / 2, viewHeight - textHeight / 2, mPaint);

        //绘制居中线
        drawCenterLine(canvas, viewWidth, viewHeight);
    }

效果图:


裁剪

 @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //文字宽度
        float textWidth = mPaint.measureText(text);
        //文字高度
        float textHeight = mPaint.descent() + mPaint.ascent();

        //获取当前控件的宽高的一半
        int viewWidth = getWidth() / 2;
        int viewHeight = getHeight() / 2;

        //裁剪
        drawClip(canvas, viewWidth, viewHeight, textWidth, textHeight);

        //绘制居中线
        drawCenterLine(canvas, viewWidth, viewHeight);
    }

    private void drawClip(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
        mPaint.setColor(Color.BLACK);
        canvas.save();
        //绘制文字X轴的位置
        float left = viewWidth - textWidth / 2;

        //绘制文字Y轴的位置
        float right = viewHeight - textHeight / 2;

        //裁剪
        canvas.clipRect((int) left, 0, (int) left + 300, getHeight());

        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, left, right, mPaint);

        canvas.restore();
    }

裁剪(clipRect)参数分析:

  • 参数一: 从文字开始位置绘制
  • 参数二: 顶部裁剪为0
  • 参数三: 裁剪宽度
  • 参数四: 绘制高度

canvas.save(),和canvas.restore();方法

可以理解为:将当前绘制的东西当作一个新的图层!

来看看效果图:

代码注释很清晰;就不过多解释了

从左到右渐变文字

众所周知.在android中,是不能够将文字绘制一般的

思路分析:

  • 绘制两层(两层颜色不同),两层叠加起来
  • 然后通过裁剪将上面一层给裁剪掉

在来看看现在代码是什么样子的:

   //用来记录当前进度 【0-1】
    float progress = 0.3f;
    
@Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //文字宽度
        float textWidth = mPaint.measureText(text);
        //文字高度
        float textHeight = mPaint.descent() + mPaint.ascent();

        //获取当前控件的宽高的一半
        int viewWidth = getWidth() / 2;
        int viewHeight = getHeight() / 2;

        //绘制底层
        drawBottom(canvas, viewWidth, viewHeight, textWidth, textHeight);

        //绘制上层【颜色渐变的】
        drawUp(canvas, viewWidth, viewHeight, textWidth, textHeight);

        //绘制居中线
        drawCenterLine(canvas, viewWidth, viewHeight);
    }

    //绘制上层【渐变的】
    private void drawUp(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
        mPaint.setColor(Color.BLACK);
        canvas.save();
        //绘制文字X轴的位置
        float left = viewWidth - textWidth / 2;

        //绘制文字Y轴的位置
        float right = viewHeight - textHeight / 2;

        //裁剪
        canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());

        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, left, right, mPaint);

        canvas.restore();
    }

    //绘制下层 不动的
    private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
        mPaint.setColor(Color.RED);  //文字颜色
        canvas.save();
		//文字开始位置
        float left = viewWidth - textWidth / 2;

        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, left, viewHeight - textHeight / 2, mPaint);
        canvas.restore();
    }

这里重点解释一下上层[需要裁剪的]参数:

 //裁剪
canvas.clipRect((int) left, 0, (int) left + textWidth * progress, getHeight());
  • textWidth 需要绘制文字的宽度
  • viewWidth 控件宽度的一半
  • 文字开始的位置:left = viewWidth - textWidth / 2;
  • 文字需要裁剪的位置: 文字的宽度 * progress

通过手势滑动来控制:

这段代码并没有实质性作用,只是来看看效果:

@SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            progress = event.getX() / getWidth();
            invalidate();
        }
        return true;
    }

效果图:


从右到左渐变文字

思路和从左到右绘制是一样的直接看关键代码:

private void drawRightToLeft(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
        mPaint.setColor(Color.GREEN);
        /*
         * 这里 left和right能够在此抽取出来,不过这样写很易懂,有需求自己弄吧!!!
         */
        canvas.save();
        //绘制文字X轴的位置 【文字开始的位置】
        float textX = viewWidth - textWidth / 2;

        //绘制文字Y轴的位置
        float textY = viewHeight - textHeight / 2;

        //文字结束的位置
        float end = viewWidth + mPaint.measureText(text) / 2;

        canvas.clipRect(end, 0, textX + textWidth * (1 - progress), getHeight());

        canvas.drawText(text, textX, textY, mPaint);
        canvas.restore();
    }

@SuppressLint("ClickableViewAccessibility")
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_MOVE) {
            if (type == GradualChangeTextView.GRADUAL_CHANGE_RIGHT) {
                //从右到左滑动
                progress = 1 - event.getX() / getWidth();
            } else if (type == GradualChangeTextView.GRADUAL_CHANGE_LEFT) {
                //从左到右滑动
                progress = event.getX() / getWidth();
            }
            invalidate();
        }
        return true;
    }

效果图:


最后在添加两个按钮来完全测试一下代码有没有问题:

完完全全没有问题!

最终实现效果(渐变滑动)

先来看看布局:


布局简单的很,就是文字和ViewPager

大致看看ViewPager代码:

 //text1 .. text4 是控件id
        val textList = listOf(text1, text2, text3, text4)

        val list = listOf(HomeFragment(), MyFragment(), TestFragment(), SettingFragment())

        val viewPagerAdapter = ViewPagerAdapter(supportFragmentManager, list)

        viewPager.adapter = viewPagerAdapter

        //默认选择第一页
        viewPager.currentItem = 1

        //默认选中
        textList[viewPager.currentItem].percent = 1f

这段代码,只要学过就懂,不细说了!

重中之重来了:

viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
            override fun onPageScrolled(
                position: Int,
                positionOffset: Float,
                positionOffsetPixels: Int,
            ) {
                if (positionOffset > 0) {
                    val left = textList[position]
                    val right = textList[position + 1]
                    //从右到左滑动
                    left.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_RIGHT)
                    //从左到右滑动
                    right.setSlidingPosition(GradualChangeTextView.GRADUAL_CHANGE_LEFT)

					//当前页面取反[从右到左]
                    left.percent = 1 - positionOffset
                    //下一个页面正常[从左到右]
                    right.percent = positionOffset
                }
            }

            override fun onPageSelected(position: Int) { }

            override fun onPageScrollStateChanged(state: Int) {
                //当 ViewPage结束的时候,重新设置一下状态 [不设置的话滑动太快,会导致'残影']
                textList.forEach {
                    if (it.tag == textList[viewPager.currentItem].tag) {
                        it.percent = 1f
                    } else {
                        it.percent = 0f
                    }
                }
            }
        })

来看看效果:


过度绘制极限优化

什么是过度绘制:

参考文档

重点总结:

  • 1.原色 – 没有被过度绘制 – 这部分的像素点只在屏幕上绘制了一次。
  • 2.蓝色 – 1次过度绘制– 这部分的像素点只在屏幕上绘制了两次。
  • 3.绿色 – 2次过度绘制 – 这部分的像素点只在屏幕上绘制了三次。
  • 4.粉色 – 3次过度绘制 – 这部分的像素点只在屏幕上绘制了四次。
  • 5.红色 – 4次过度绘制 – 这部分的像素点只在屏幕上绘制了五次。

先来看看没有优化的效果:

可以看到,在绘制的过程中,因为是两层,那么就绘制了2次,

优化思路:
当黑色[上层]从左到右滑动的时候,红色[下层]跟随着从左到右裁剪

来看看下层绘制的代码:

//绘制下层 不动的
    private void drawBottom(Canvas canvas, int viewWidth, int viewHeight, float textWidth, float textHeight) {
        mPaint.setColor(Color.RED);
        canvas.save();
        //绘制文字X轴的位置 [文字开始的位置]
        float textX = viewWidth - textWidth / 2;

        //绘制文字Y轴的位置
        float textY = viewHeight - textHeight / 2;

        //跟随者上层裁剪
        canvas.clipRect((int) textX + textWidth * progress, 0, textWidth + viewWidth, getHeight());
        /*
         * 绘制文字
         * 参数一: 绘制文字
         * 参数二: x轴开始位置
         * 参数三: y 轴开始位置
         * 参数四: 画笔
         */
        canvas.drawText(text, textX, textY, mPaint);
        canvas.restore();
    }

效果图:

完整代码

原创不易,您的点赞就是对我最大的支持!

以上是关于Android 渐变的‘TabLayout’ , (含免费( java / kotlin) Demo)的主要内容,如果未能解决你的问题,请参考以下文章

记录TabLayout的一些用法(android)

android设计TabLayout标签的文字大小

TabLayout(Android 设计库)文本颜色

android ——Tablayout

Android - MD之TabLayout的使用

Android Material Design-TabLayout的使用