Android自定义view变色字体实现

效果如图所示:
在这里插入图片描述
在这里插入图片描述
可见要实现次效果,需要自定义View继承TextView(因为有文字,继承这个会好一些)。
外圈是一个橙色的背景,形状为圆角矩形,以及底部有一个小直角三角形倒过来的形状。里面是变色文字。这里我们先实现外圈的橙色背景。

绿色背景:

  1. 在res文件夹中的values,新建一个文件attrs.xml, 这个是对于自定义view的声明文件。
  2. 属性包括,圆角矩形的圆角半径长度、小直角三角形对角线一半的长度,以及颜色、圆角矩形高度、标题栏滑动方向。
    在这里插入图片描述
    代码如下:
<declare-styleable name="LoryMessageTitle">
        <attr name="roudedRadius" format="dimension"></attr><!--矩形圆角半径径-->
        <attr name="triangleLengthHalf" format="dimension"></attr><!--直角三角形对角线长度一半-->
        <attr name="rectHeight" format="dimension"></attr><!--圆角矩形高度-->
        <attr name="colorAll" format="color"></attr><!--控件颜色-->
        <attr name="changeDirection">   <!--控件变化方向-->
            <enum name="LEFT_TO_RIGHT" value="1"></enum>
            <enum name="RIGHT_TO_LEFT" value="2"></enum>
        </attr>
    </declare-styleable>

3.自定义view名 LoryMessageTitle继承Textview,代码如下:

public class LoryMessageTitle extends androidx.appcompat.widget.AppCompatTextView {
    
    
    //背景图案
    private float progress = 1f;//移动比例参数progress,默认是0
    private int mRoundedRadius = 3;//圆角矩形直径默认长度
    private int mTraigleLength = 4;//直角三角形对角线边默认一半长度
    private int mRectHeight = 20;//圆角矩形默认高度
    private int mRectWidth = 20;//圆角矩形默认长度
    private int mColor = getResources().getColor(R.color.colorGreener,null);//控件默认颜色
    private int mDirection = 1; //默认变化从左到右
    private Paint mPaint;//控件绘制的画笔,由于控件背景色颜色统一,所以用一支画笔就可以了
    public LoryMessageTitle(Context context){
    
    
        this(context,null);
    }
    public LoryMessageTitle(Context context, AttributeSet attrs){
    
    
        this(context,attrs,0);
    }
    public LoryMessageTitle(Context context,AttributeSet attrs,int delStyleAttr){
    
    
        super(context,attrs,delStyleAttr);
        //初始化
        init(context,attrs);
    }
    //初始化函数
    public void init(Context context,AttributeSet attrs){
    
    
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoryMessageTitle);
        mRoundedRadius = array.getDimensionPixelSize(R.styleable.LoryMessageTitle_roudedRadius,sp2px(mRoundedRadius));//圆角半径大小
        mTraigleLength = array.getDimensionPixelSize(R.styleable.LoryMessageTitle_triangleLengthHalf,sp2px(mTraigleLength));//三角形对角线长度一半
        mRectHeight = array.getDimensionPixelSize(R.styleable.LoryMessageTitle_rectHeight,sp2px(mRectHeight));//圆角矩形高度
        mColor = array.getColor(R.styleable.LoryMessageTitle_colorAll,mColor);//控件颜色
          mDirection = array.getInteger(R.styleable.LoryMessageTitle_changeDirection,mDirection);//控件移动方向
        mPaint = new Paint();
        mPaint.setAntiAlias(true);//抗锯齿
        mPaint.setDither(true);
        mPaint.setColor(mColor);
        mDirection = array.getInteger(R.styleable.LoryMessageTitle_changeDirection,mDirection);
    }
    //sp  to  px  不多说,就是 sp转化为 px,要是控件使用者用dp怎么办,那就让他自己添加个dp转sp的函数,就你事多
    public int sp2px(int sp){
    
    
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.getResources().getDisplayMetrics());
    }
    //测量方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        int widthmode = MeasureSpec.getMode(widthMeasureSpec);//宽度模式
        int heightmode = MeasureSpec.getMode(heightMeasureSpec);//高度模式
        int widthsize = MeasureSpec.getSize(widthMeasureSpec);//宽度大小
        int heightsize = MeasureSpec.getSize(heightMeasureSpec);//高度大小
        //要是用户填的wrap_content,宽度、高度至少时
        if (widthmode==MeasureSpec.AT_MOST){
    
    
            int widthsize = sp2px(mRectWidth)+getPaddingLeft()+getPaddingRight();//背景宽度
        }
        if (heightmode==MeasureSpec.AT_MOST){
    
    
            int heightsize = mRectHeight+mTraigleLength+getPaddingTop()+getPaddingBottom();
        }
        setMeasuredDimension(widthsize,heightsize);
    }
    //绘制方法,一开始绘制一个静态的控件,然后这个控件会移动,有个0-1的参数,决定这个控件移动的比例,引入这个参数叫 progress。默认是0;

    @Override
    protected void onDraw(Canvas canvas) {
    
    
        //静态控件如下
//        //首先绘制静态的圆角矩形
//        { canvas.save();
//        RectF rectf = new RectF(getPaddingLeft(), getPaddingTop(), getWidth() - getPaddingRight(), getHeight() - getPaddingBottom() - mTraigleLength);
//        canvas.drawRoundRect(rectf, mRoundedRadius, mRoundedRadius, mPaint);
//        canvas.restore();
//        //然后绘制三角形,是一个倒过来的直角三角形,可以用一个倒过来的正方形代替,对角线和矩形重合即可,也可以通过路径绘制
//        canvas.save();
//        Path path = new Path();
//        path.moveTo(getWidth() / 2 - mTraigleLength, getHeight() - getPaddingBottom() - mTraigleLength);//起点
//        path.lineTo(getWidth() / 2, getHeight() - getPaddingBottom());//往下移动
//        path.lineTo(getWidth() / 2 + mTraigleLength, getHeight() - getPaddingBottom() - mTraigleLength);//往右移动
//        path.close();//路径闭合
//        canvas.drawPath(path, mPaint);
//        canvas.restore();
//    }
        //颜色更改后,画笔也要及时更新哈
        mPaint.setColor(mColor);
       //绘制动态圆角矩形,随着progress运动,圆角矩形的左边x参数在增加,右边参数也在增加,由于控件长度固定,
       //所以可以出现圆角矩形右侧被吞噬的样子
       float x1;//圆角矩形左侧坐标
       float x2;//圆角矩形右侧坐标
        Rect bounds = new Rect();
        if (mDirection==2){
    
    //如果移动方向是从右到左
           x1 = (1-progress)*(getWidth())+getPaddingLeft();
           x2 = (2-progress)*(getWidth())-getPaddingRight();
        }else {
    
    //移动方向为从左到右
            x1 = (progress-1)*(getWidth())+getPaddingLeft();
           x2 = progress*(getWidth())-getPaddingRight();
        }
        canvas.save();
        RectF rectf =new RectF(x1,getPaddingTop(),
                x2,getHeight()-getPaddingBottom()-mTraigleLength);//矩形
        canvas.drawRoundRect(rectf,mRoundedRadius,mRoundedRadius,mPaint);//绘制圆角矩形
        //然后绘制三角形,是一个倒过来的直角三角形,可以用一个旋转的正方形代替,对角线和矩形重合即可,也可以通过路径绘制
        //我个人更喜欢路径Path,关于Path的方法百度上有
        canvas.save();
        Path path = new Path();
        path.moveTo(getWidth()/2-mTraigleLength+x1-getPaddingLeft(),getHeight()-getPaddingBottom()-mTraigleLength);//起点,三角形左边起点
        path.lineTo(getWidth()/2+x1-getPaddingLeft(),getHeight()-getPaddingBottom());//往下移动
        path.lineTo(getWidth()/2+mTraigleLength+x1-getPaddingLeft(),getHeight()-getPaddingBottom()-mTraigleLength);//往右移动,三角形右边的点
        path.close();//路径闭合,将绘制的三个点闭合
        canvas.drawPath(path,mPaint);//绘制三角形
        canvas.restore();
    }
//  最后就是这个控件随着progress变化而变化,每次变化都要重新绘制,
//invalidate方法会再次调用ondraw方法
    public void changeProgress(float progresser){
    
    //设置改变进度的方法
        progress = progresser;
        invalidate();//重新绘制
    }
// 也有人说,我不想在xml文件中设置控件状态,我想java文件中改变控件的圆角矩形高度、圆角大小,小三角形对角线宽一半,颜色等
    //你丫的需求真多呀,就你多事
  
    public void setSate(int roundedRadius,int roundedheight,int traigleLengthHalf,int color){
    
    //改变控件状态
        mRoundedRadius = roundedRadius;
        mRectHeight = roundedheight;
        mTraigleLength = traigleLengthHalf;
        mColor = color;
        invalidate();
    }
 
    public void setdirction(int c_direction){
    
    //设置控件变化方向
        mDirection = c_direction;
        invalidate();
    }
    public void setmColor(int color){
    
    //设置控件颜色
        mColor = color;
        invalidate();
    }
}

好了这个控件定义完了,那么效果如何?就把它当作textview就测试一下,要是能够绘制出这个效果就说明可用了。
接下来就是字体的绘制,我们再造一个自定view,也是继承TextView.

可变色字体的绘制:
在这里插入图片描述
两种颜色的字体,而且可以随着progress变化而变化,那么如何实现呢?

很简单,只要将画布Canvas分割,裁成两个区域,一个是灰色,一个是白色,然后区域会随着progress变化而变化。

  1. 在attrs.xml文件中声明一个字体的自定义view
 <declare-styleable name="ChangeColorText">
        <attr name="changeColor" format="color"></attr><!--文字改变后的颜色-->
        <attr name="orignColor" format="color"></attr><!--文字原颜色-->
        <attr name="changeDirection">   <!--控件变化方向-->
            <enum name="LEFT_TO_RIGHT" value="1"></enum>
            <enum name="RIGHT_TO_LEFT" value="2"></enum>
        </attr>
    </declare-styleable>
  1. 自定义view, ChangeColorText继承TextView:
public class ChangeColorText extends androidx.appcompat.widget.AppCompatTextView {
    
    

    private float progress = 1f;//移动比例参数progress,默认是0
    //字体
    private int mOrignColor = getResources().getColor(R.color.colorChange,null);//默认字体原始颜色
    private int mChangeColor = Color.WHITE;//默认字体变化颜色
    private Paint mOrignPaint,mChangePaint;//绘制两种字体的画笔
    private int textsize = 50;//默认原字体大小px
    private int textsize1 = 55;//默认字体变化的大小
    private int mDirection = 1; //默认变化从左到右
    public ChangeColorText(Context context){
    
    
        this(context,null);
    }
    public ChangeColorText(Context context, AttributeSet attrs){
    
    
        this(context,attrs,0);
    }
    public ChangeColorText(Context context,AttributeSet attrs,int delStyleAttr){
    
    
        super(context,attrs,delStyleAttr);
        //初始化
        init(context,attrs);
    }
    //初始化函数
    public void init(Context context,AttributeSet attrs){
    
    
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoryMessageTitle);
       
        //字体
        mOrignColor = array.getColor(R.styleable.LoryMessageTitle_orignColor,mOrignColor);//原始颜色
        mChangeColor = array.getColor(R.styleable.LoryMessageTitle_changeColor,mChangeColor);//变化颜色
        mDirection = array.getInteger(R.styleable.LoryMessageTitle_changeDirection,mDirection);//变化方向
        //orignPaint画笔初始化
        mOrignPaint = new Paint();
        mOrignPaint.setColor(mOrignColor);
        mOrignPaint.setAntiAlias(true);
        mOrignPaint.setDither(true);
        mOrignPaint.setTextSize(textsize);
        //changePaint画笔初始化
        mChangePaint = new Paint();
        mChangePaint.setColor(mChangeColor);
        mChangePaint.setAntiAlias(true);
        mChangePaint.setDither(true);
        mChangePaint.setTextSize(textsize1);

    }
    //sp  to  px  不多说,就是 sp转化为 px,要是控件使用者用dp怎么办,那就让他自己添加个dp转sp的函数,就你事多
    public int sp2px(int sp){
    
    
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, this.getResources().getDisplayMetrics());
    }
    //测量方法

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    
    
        int widthmode = MeasureSpec.getMode(widthMeasureSpec);
        int heightmode = MeasureSpec.getMode(heightMeasureSpec);
        int widthsize = MeasureSpec.getSize(widthMeasureSpec);
        int heightsize = MeasureSpec.getSize(heightMeasureSpec);
        //文字的长宽,用画笔测量文字的宽度,长度
        Rect bounds = new Rect();
        mChangePaint.getTextBounds(getText().toString(),0,getText().length(),bounds);
        //要是用户填的wrap_content,宽度、高度至少
        if (widthmode==MeasureSpec.AT_MOST){
    
    
            int widthsize = bounds.width() + getPaddingLeft()+getPaddingRight();//文字宽度
        }
        if (heightmode==MeasureSpec.AT_MOST){
    
    
            int heightsize = bounds.height() + +getPaddingTop()+getPaddingBottom();
        }
        setMeasuredDimension(widthsize,heightsize);
    }
    
    @Override
    protected void onDraw(Canvas canvas) {
    
    
     //文字的横坐标
        int x = getWidth()/2-bounds.width()/2;
        Paint.FontMetricsInt fontMetricsInt = mOrignPaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top)/2 - fontMetricsInt.bottom;
        int baseline = getHeight()/2+dy;//基线
        //在绘制变色文字
        if(mDirection==1){
    
    //从左到右
            canvas.save();
            canvas.clipRect(0,0,progress*getWidth()-getPaddingLeft(),getHeight());//裁剪坐左边的区域
          //绘制左边文字
           canvas.drawText(getText().toString(),x,baseline,mChangePaint);
            canvas.restore();
            canvas.save();
            canvas.clipRect(progress*getWidth()-getPaddingLeft(),0,getWidth(),getHeight());//裁剪右边的区域
            canvas.drawText(getText().toString(),x,baseline,mOrignPaint);
            canvas.restore();//绘制右边的文字
        }else{
    
    //从右到左
            canvas.save();
            canvas.clipRect(0,0,(1-progress)*getWidth()+getPaddingRight(),getHeight());
            canvas.drawText(getText().toString(),x,baseline,mOrignPaint);
            canvas.restore();
            canvas.save();
            canvas.clipRect((1-progress)*getWidth()+getPaddingRight(),0,getWidth(),getHeight());
            canvas.drawText(getText().toString(),x,baseline,mChangePaint);
            canvas.restore();
        }

    }
//  最后就是这个控件随progress变化,而重新绘制。
    public void changeProgress(float progresser){
    
    
        progress = progresser;
        invalidate();//重新绘制
    }
    public void setmOrignColor(int c_color){
    
    //原文字颜色
        this.mOrignColor = c_color;
        invalidate();
    }
    public void setmChangeColor(int c_color){
    
    //变化文字颜色
        this.mChangeColor = c_color;
        invalidate();
    }
    //设置绘制时字体 大小 粗细于一体
    public void setmWordSize(int orignSize,int changSize){
    
    
        this.textsize = orignSize;
        this.textsize1 = changSize;
        invalidate();
    }
    public void setdirction(int c_direction){
    
    
        mDirection = c_direction;
        invalidate();
    }
 
}

文字也绘制出来了,那么如何将两种效果放在一个控件中呢?简单呀,将两种控件的属性放到一个控件就可以了。这个工作就交给读者吧,我想你通过这两个自定义view的绘制后,应该不难。当然,期间你肯定也会有一些和我当初学的时候的坑。

大坑汇集:

  1. 标题1在过度标题2 的过程中, 标题1的颜色由白色逐渐变为灰色,标题2的颜色由灰色逐渐变为白色,但是这两个标题的OriginColor和ChangeColor代码中都写成了一样的,导致标题1的颜色由白色逐渐变为灰色,标题2的颜色由白色逐渐变为灰色,这样并没有文字变色移动的效果,理论上 标题1的OriginColor本应该为白色,而标题2的OriginColor应该为灰色,如何解决这个冲突呢?

答:OriginColor为灰色,ChangeColor为白色。 标题1的移动方向为“从右往左”,而标题2的移动方向设置为“从左到右”。因为代码中,direction的变化会让原色和变色转换。从左边往右边,标题1是由白色变灰色,而从右边往左边,标题1是由灰色变白色。并且, 标题1的进度=1-progress,标题2的进度为 progress.

  1. 可变颜色字体和橙色背景结合时,控件的宽高测量冲突怎么解决?

答: 可变颜色字体的宽高测量是通过画笔测量单个文字后给出的Bounds决定的,而橙色背景的宽高是人为给定的。要让文字和背景的放置比例位置合理,是一个头疼的问题。作者的方法是基于文字的宽高,再加上固定的长度。至于固定的长度是多长,这个需要读者自己测试把握。作者并没有好的办法。

  1. 标题1移动到标题2过程中,颜色变化和橙色背景移动都是由参数progress决定的,那这个progress的数据来源从哪来呢?换句话说,我怎样可以用手滑动屏幕时,progress随滑动比例而变化呢?

答: 可滑动标题栏下方的内容是一个ViewPager, viewPager可以适应多个适配器Adapter,至于用哪个Adapter看你个人习惯,不过由于ViewPager内放的是Fragment,所以我建议用FragmentPageAdapter(名字有没有打错哈)。ViewPager有3个关于滑动方法:
在这里插入图片描述
看到第一个方法,那个postionOffset,这个就是ViewPager监听滑动比例,范围由(0—1),progress的数据来源就是它。

以上就是我遇到的坑,其他问题要是读者遇到了,那就评论区留言吧

猜你喜欢

转载自blog.csdn.net/qq_41904106/article/details/114449087