Android进阶——自定义View之扩展系统控件的另一种思路实现渐变文字动画的TextView

引言

前面几篇文章

继承或组合系统现有控件实现新控件,扩展新功能都是在对应的构造方法中去扩展的,但千万不要把思路局限于只能在构造方法中去扩展,这篇就简单地分享另一种思路,通过重写对应的周期方法实现扩展。

一、View中几种重要的方法

  • onFinishInflate:从XML加载了控件之后自动回调的

  • onSizeChanged:组件大小改变时回调,当第一次完成控件的测量也会触发回调

  • onMeasure:通过回调该方法来进行测量,具体参见我的另一篇文章

  • onLayout:通过回调该方法来确定显示的位置,即布局

  • onTouchEvent:当控件监听到触摸事件的时候回调

  • onDraw:控件最终呈现的效果都是由onDraw决定的

这里写图片描述
虽然我们在自定义View的时候不一定什么情况下都必须重写这所有的方法,我们克根据各自的业务来重写对应的方法,同时这也是Android控件架构强大灵活性的体现。

二、dp、sp和px的互相转换

  • dp(dip): device independent pixels(设备独立像素).
    不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持WVGA、HVGA和QVGA 推荐使用这个,不依赖像素。如果设置表示长度、高度等属性时可以使用dp。dp是与密度无关,sp除了与密度无关外,还与scale无关。如果屏幕密度为160,这时dp和sp和px是一
    样的。1dp=1sp=1px,但如果使用px作单位,如果屏幕大小不变(假设还是3.2寸),而屏幕密度变成了320。那么原来TextView的宽度设成160px,在密度为320的3.2寸屏幕里看要比在密度为160的3.2寸屏幕上看短了一半。但如果设置成160dp或160sp的话。系统会自动将width属性值设置成320px的。也就是160 * 320 / 160。其中320 /160可称为密度比例因子。也就是说,如果使用dp和sp,系统会根据屏幕密度的变化自动进行转换。
  • px: pixels(像素)。不同设备显示效果相同,一般我们HVGA代表320x480像素,这个用的比较多。
  • pt: point,是一个标准的长度单位,1pt=1/72英寸,用于印刷业,非常简单易用;
  • sp: scaled pixels(放大像素).
    主要用于字体显示best for textsize。

以下附一个转换工具类

public class ScreenUtil {
    /**
     * 将px值转换为dip或dp值,保证尺寸大小不变
     *
     * @param pxValue
     * @param scale   (DisplayMetrics类中属性density)
     * @return
     */
    public static int px2dip(Context context, float pxValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }

    /**
     * 将dip或dp值转换为px值,保证尺寸大小不变
     *
     * @param dipValue
     * @param scale    (DisplayMetrics类中属性density)
     * @return
     */
    public static int dip2px(Context context, float dipValue) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }

    /**
     * 将px值转换为sp值,保证文字大小不变
     *
     * @param pxValue
     * @param fontScale (DisplayMetrics类中属性scaledDensity)
     * @return
     */
    public static int px2sp(Context context, float pxValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    /**
     * 将sp值转换为px值,保证文字大小不变
     *
     * @param spValue
     * @param fontScale (DisplayMetrics类中属性scaledDensity)
     * @return
     */
    public static int sp2px(Context context, float spValue) {
        final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
        return (int) (spValue * fontScale + 0.5f);
    }
}

二、重写系统控件的成员方法扩展新的功能

重写系统控件的成员方法扩展新的功能这也是一种自定义View的重要思想,很多时候原生的控件的功能对于我们来说只是欠缺了一点点,此时我们就可以把大部分的工作交给原生控件,然后再在原生的基础上进行再开发,而不一定要事必躬亲,完全继承View去绘制。以下直接通过两个简单的例子来说明。

1、实现自带边框和背景颜色图层的TextView

这里写图片描述
如上图所示,外边框为红色内部背景为绿色的简单TextView,可能利用Span家族也可以实现,但那不是重点,举这个例子仅仅是分享一种思想。首先,了解View的绘制流程的话,应该都知道onDraw是绘制UI效果的,那么TextView里的onDraw自然充当的是绘制要显示的文字的职责,这里顺道补充下常常在重写系统的一些方法的时候,默认地实现会去调用父类对应的方法,并不是所有的父类方法都需要手动去调用的,也不是所有的父类方法都可以不调用的,比如说这个小例子中的onDraw

   @Override
    protected void onDraw(Canvas canvas) {

        drawOutsideRec(canvas);
        drawInsideRec(canvas);
        canvas.save();
        Log.e("onDraw2","10px:"+ScreenUtil.px2dip(getContext(),30));
       // canvas.translate(ScreenUtil.px2dip(getContext(),10),0);
        canvas.translate(10,0);
        //在回调父类方法之前,实现对应的逻辑,此例指的是在文字绘制之前
        super.onDraw(canvas);
        //在回调父类方法之后,实现对应的逻辑,此例指的是在文字绘制之后
        canvas.restore();
    }

总之,继承系统控件时候父类成员方法的调用与否,呈现的UI效果也不尽相同。接下来就实现这个带边框和带背景的TextView

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/5/5 10:20
 * Summary:
 */
public class ColorfulTextView extends TextView {
    private Paint paintInside,paintOutside;
    public ColorfulTextView(Context context) {
        super(context);
        init();
    }

    public ColorfulTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public ColorfulTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init(){
        paintInside=new Paint();
        paintInside.setColor(getResources().getColor(android.R.color.holo_green_dark));
        paintInside.setStyle(Paint.Style.FILL);
        paintInside.setAntiAlias(true);
        paintInside.setDither(true);

        paintOutside=new Paint();
        paintOutside.setColor(getResources().getColor(android.R.color.holo_red_light));
        paintOutside.setStyle(Paint.Style.STROKE);
        paintOutside.setAntiAlias(true);
        paintOutside.setDither(true);
    }

    private void drawInsideRec(Canvas canvas){
        //canvas.drawRect(ScreenUtil.px2dip(getContext(),10),ScreenUtil.px2dip(getContext(),10),getMeasuredWidth()+ScreenUtil.px2dip(getContext(),10),getMeasuredHeight(),paintInside);
        canvas.drawRect(10,10,getMeasuredWidth()-10,getMeasuredHeight()-10,paintInside);
    }

    private void drawOutsideRec(Canvas canvas){
       // canvas.drawRect(0,0,ScreenUtil.px2dip(getContext(),getMeasuredWidth()),ScreenUtil.px2dip(getContext(),getMeasuredHeight()+20),paintOutside);
        canvas.drawRect(0,0,getMeasuredWidth(),getMeasuredHeight(),paintOutside);
        Log.e("onDraw2",ScreenUtil.px2dip(getContext(),getMeasuredWidth())+"measureWidth"+getMeasuredWidth()+"measureHeight"+getMeasuredHeight()+20);
    }

    @Override
    protected void onDraw(Canvas canvas) {

        drawOutsideRec(canvas);
        drawInsideRec(canvas);
        canvas.save();
        Log.e("onDraw2","10px:"+ScreenUtil.px2dip(getContext(),30));
       // canvas.translate(ScreenUtil.px2dip(getContext(),10),0);
        canvas.translate(10,0);
        super.onDraw(canvas);
        canvas.restore();
    }
}

2、实现文字闪烁动画的TextView

这里写图片描述
原理很简单,主要就是重写onSizeChanged方法中利用在getPaint()方法并拿到当前的Paint对象,再给Paint对象设置相应的Shader产生渐变效果,接着利用矩阵Matrix实现平移的动画效果,最后在onDraw里不断手动刷新即可。

package crazymo.training.colorfultextview.widget;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.TextView;

/**
 * Auther: Crazy.Mo
 * DateTime: 2017/5/5 10:20
 * Summary:
 */
public class ColorfulTextView extends TextView {
    private Paint shineyPaint;
    private LinearGradient linearGradient;
    private Matrix shinerMatrix;
    private int width,translate;
    public ColorfulTextView(Context context) {
        super(context);
        Log.e("ViewRun","构造方法");
    }

    public ColorfulTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        Log.e("ViewRun","构造方法");
    }

    public ColorfulTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        Log.e("ViewRun","构造方法");
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        Log.e("ViewRun","onLayout"+changed+"left:"+left+"top:"+ top+"right:"+ right+"bottom"+ bottom);
        super.onLayout(changed, left, top, right, bottom);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        Log.e("ViewRun","onMeasure");
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onFinishInflate() {
        Log.e("ViewRun","onFinishInflate");
        super.onFinishInflate();
    }



    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        Log.e("ViewRun","onSizeChanged"+"w"+w+"h"+h+"oldw"+oldw+"oldh"+oldh);
        super.onSizeChanged(w, h, oldw, oldh);
        if(width==0){//在View第一次呈现前初始化渐变对象,并拿到当前的Paint
            width=getMeasuredWidth();
            if(width>0){
                shineyPaint=getPaint();//获取当前的Paint对象
                linearGradient=new LinearGradient(0,0,width,0,new int[]{Color.GREEN,0xffffffff,Color.GREEN},null, Shader.TileMode.CLAMP);//构造和TextView一样宽度的渐变对象
                shineyPaint.setShader(linearGradient);
                shinerMatrix=new Matrix();
            }
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        Log.e("ViewRun","onDraw");
        super.onDraw(canvas);
        if(shinerMatrix!=null){
            translate+=width/5;
            if(translate>width*2){
                translate=-width;
            }
            shinerMatrix.setTranslate(translate,0);
            linearGradient.setLocalMatrix(shinerMatrix);
            postInvalidateDelayed(100);
        }
    }
}

小结

以上例子虽然很简单,主要是分享一种思路,在自定义View的时候不要只拘泥于重写构造方法,甚至是拘泥于onDraw方法,理论上任何成员方法都可以是被我们再开发的,前提是你得懂得基本的逻辑,重写View的成员方法扩展功能这就是我说的另一种思路。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CrazyMo_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值