Android 自定义View

自定义View

1.extends View
2.extends ViewGroup(布局)
3.extends TextView

构造函数

四种.

一、在代码中new,时调用

二、在布局文件xml中,时调用

在这里插入图片描述

三、在布局文件xml中,并且使用style属性,时调用

在这里插入图片描述
style设置属性
在这里插入图片描述
在style文件设置属性
在这里插入图片描述

onMeasure方法(计算View宽高)

在这里插入图片描述

onDraw方法

在这里插入图片描述

onTouch方法(难点)

在这里插入图片描述

自定义属性

即为:在xml文件中设置的属性

一、创建attrs.xml文件设计属性

注意:系统以及处理的属性是不能自定义的,如图 background
在这里插入图片描述
有 background 属性报错,无则可以正常运行
在这里插入图片描述

二、使用设计好的属性

1、声明命名控件,"app"可以修改(?)
在这里插入图片描述
2、在自定义View中,使用自定义属性
在这里插入图片描述
3、在自定义View中获取属性(在构造方法中使用)
注意,最后需要回收
在这里插入图片描述

实现自定义TextView的

一、继承View

在这里插入图片描述

二、设置自定义属性
1、在values文件夹中新建或使用已有的attrs.xml文件

在这里插入图片描述

2、设置自定义的属性
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="TextView">
        <attr name="text" format="string"/>
        <attr name="textColor" format="color"/>
        <attr name="textSize" format="dimension"/>
        <attr name="maxLength" format="integer"/>
<!--        background自定义View都是继承自View,背景是由View管理的-->
<!--        <attr name="background" format="reference|color"/>-->

        <!--枚举-->
        <attr name="inputType">
            <enum name="number" value="1"/>
            <enum name="text" value="2"/>
            <enum name="password" value="3"/>
        </attr>
    </declare-styleable>
</resources>
三、使用自定义属性

1、声明使用 app
2、用app调用attrs.xml文件定义的属性
在这里插入图片描述

四、实现自定义的TextView
public class TextView extends View {
    private String mText = "默认值";
    private int mTextType = 2; //1 number , 2 text ,3 password
    private int mTextSize = 15;
    private int mTextColor = Color.BLACK;

    private Paint mPaint;

    //在代码里面new的时候调用
    public TextView(Context context) {
        this(context,null);
    }

    //在布局xml中使用时调用
    public TextView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    //在布局layout中使用调用,但是会有style(样式)
    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.TextView);

        //String 竟然,不需要默认值
        mText = array.getString(R.styleable.TextView_text);
        mTextColor = array.getColor(R.styleable.TextView_textColor,mTextColor);
        //获取的值是什么单位的呢? sp? dp? px?
        // 答案是 px 像素
        mTextSize = array.getDimensionPixelSize(R.styleable.TextView_textSize,sp2px(mTextSize));
        mTextType = array.getInt(R.styleable.TextView_inputType,mTextType);

        //回收
        array.recycle();

        mPaint = new Paint();
        //抗锯齿
        mPaint.setAntiAlias(true);
        //设置字体的大小和颜色
        mPaint.setTextSize(mTextSize);
        mPaint.setColor(mTextColor);
    }

    //将sp转px
    private int sp2px(int sp) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,sp,
                getResources().getDisplayMetrics());
    }

    //不讲?报错了?
//    public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
//        super(context, attrs, defStyleAttr, defStyleRes);
//    }


    //自定义View的测量方法
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        // 布局的宽高都是由这个方法指定
        // 指定控件的宽高,需要测量

        // 获取宽高的模式
        int widthModel = MeasureSpec.getMode(widthMeasureSpec);
        int heighModel = MeasureSpec.getMode(heightMeasureSpec);

        // Model类型
        //MeasureSpec.AT_MOST; 在布局中设置,wrap_content
        //MeasureSpec.EXACTLY; 在不居中指定了确切的值, 100dp match_parent fill_parent
        //MeasureSpec.UNSPECTEIED; 尽可能的大,开发很少用到,系统自己用
        // ListView在ScrollView显示不全原因,在测量子布局的时候会用UNSPECTEIED

        // widthMeasureSpec 会包含两个信息是一个32位的值,第一个信息是模式,2位值

        //画文字
        //1、确认的值,这个时候不需要计算,给的多少就是多少
        int width = MeasureSpec.getSize(widthMeasureSpec);
        //2、给的是wrap_content 需要计算
        if (widthModel == MeasureSpec.AT_MOST){
            // 计算的宽度 与 字体的长度有关 与字体的大小
            // 用画笔来测量
            Rect bounds = new Rect();
            // 获取文本的Rect
            // ?设置文本给 画笔与矩阵 然后再通过矩阵获取长度?
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            //加上 padding 值
            width = bounds.width() + getPaddingLeft() + getPaddingRight();
        }

        int height = MeasureSpec.getSize(heightMeasureSpec);
        if (heighModel == MeasureSpec.AT_MOST){
            // 计算的高度 与 字体的长度有关 与字体的大小
            // 用画笔来测量
            Rect bounds = new Rect();
            // 获取文本的Rect
            // ?设置文本给 画笔与矩阵 然后再通过矩阵获取长度?
            mPaint.getTextBounds(mText,0,mText.length(),bounds);
            //加上 padding 值
            height = bounds.height() + getPaddingBottom() + getPaddingTop();
        }

        //设置控件宽高
        setMeasuredDimension(width,height);
    }

    //用于绘制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
//        //画文本
//        canvas.drawText();
//        //画弧
//        canvas.drawArc();

        //画文本
        //text x y paint
        //x 开始的位置
        //y 基线 baseLine
        //dy 为高度一半到 baseLine 的距离
        Paint.FontMetricsInt fontMetrics = mPaint.getFontMetricsInt();
        // top 为负数,bottom 为正数
        int dy = (fontMetrics.bottom - fontMetrics.top)/2 - fontMetrics.bottom;
        int baseline = getHeight()/2 + dy;

        //设置padding值
        int x = getPaddingLeft();
        canvas.drawText(mText,x,baseline,mPaint);
    }

    //事件处理
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch(event.getAction()){
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:
                break;
            default:
                break;
        }
        return super.onTouchEvent(event);
    }
}

自定义码表计步器

实现步骤
一、需求分析

//1、分析效果
在这里插入图片描述

二、设置自定义属性

//2、确定自定义属性,编写attrs.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="QQStepView">
        <attr name="outerColor" format="color"/>
        <attr name="innerColor" format="color"/>
        <attr name="stepTextColor" format="color"/>

        <attr name="borderWidth" format="dimension"/>
        <attr name="stepTextSize" format="dimension"/>
    </declare-styleable>
</resources>
三、使用自定义控件

//3、在布局中使用

四、实现自定义控件

//4、在自定义View中获取自定义属性
//5、onMeasuer() 计算View大小
//6、onDraw() 画圆弧
//7、其他处理 动画

public class QQStepView extends View {
    //1、分析效果
    //2、确定自定义属性,编写attrs.xml
    //3、在布局中使用
    private int mOuterColor = Color.RED;
    private int mInnerColor = Color.BLACK;

    private int mBorderWidth = 20; //px
    private int mStepTextSize;
    private int mStepTextColor = Color.BLACK;

    //步数
    private int mStepMax = 100; //最大值
    private int mCurrentStep = 30; //当前值

    private Paint mOutPaint,mInnerPaint,mTextPaint;

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

    public QQStepView(Context context, @Nullable AttributeSet attrs) {
        this(context,attrs,0);
    }

    public QQStepView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //4、在自定义View中获取自定义属性
        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.QQStepView);
        mOuterColor = array.getColor(R.styleable.QQStepView_outerColor,mOuterColor);
        mInnerColor = array.getColor(R.styleable.QQStepView_innerColor,mInnerColor);
        mBorderWidth = (int) array.getDimension(R.styleable.QQStepView_borderWidth,mBorderWidth);
        mStepTextSize = array.getDimensionPixelSize(R.styleable.QQStepView_stepTextSize,mStepTextSize);
        mStepTextColor = array.getColor(R.styleable.QQStepView_stepTextColor,mStepTextColor);

        //回收
        array.recycle();

        //初始化 外圆弧
        mOutPaint = new Paint();
        mOutPaint.setAntiAlias(true);
        mOutPaint.setStrokeWidth(mBorderWidth);
        mOutPaint.setColor(mOuterColor);
        mOutPaint.setStrokeCap(Paint.Cap.ROUND); //画笔为圆形?
        mOutPaint.setStyle(Paint.Style.STROKE);//画笔空心

        //初始化 内圆弧
        mInnerPaint = new Paint();
        mInnerPaint.setAntiAlias(true);
        mInnerPaint.setStrokeWidth(mBorderWidth);
        mInnerPaint.setColor(mInnerColor);
        mInnerPaint.setStrokeCap(Paint.Cap.ROUND); //画笔为圆形?
        mInnerPaint.setStyle(Paint.Style.STROKE);//画笔空心

        //初始化 文本
        mTextPaint = new Paint();
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(mStepTextSize);
        mTextPaint.setColor(mStepTextColor);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //5、onMeasuer() 计算View大小
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);

        int min = width>height?height:width;

        // 宽度高度不一致 取最小值,确保是个正方形
        setMeasuredDimension(min,min);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //6、onDraw() 画圆弧
        //画外圆弧 left top right bottom 设置矩阵的范围
        //因为设置了Paint的宽度,所以直接画会导致超出,需要减去超出部分
        RectF rectF = new RectF(mBorderWidth/2,mBorderWidth/2,getWidth() - mBorderWidth/2,getHeight() - mBorderWidth/2);
        //范围 开始角度 结束角度 是否连接? 画笔
        canvas.drawArc(rectF,135,270,false,mOutPaint);

        //画内圆弧
        //不能写死,需要数值
        // 第一次调用不执行
        if (mStepMax == 0) return;
        // 圆弧的长度,百分比
        float sweepAngle = (float) mCurrentStep/mStepMax;
        canvas.drawArc(rectF,135,sweepAngle*270,false,mInnerPaint);

        //画文字
        String stepText = mCurrentStep + ""; // 文本
        Rect textBounds = new Rect();
        mTextPaint.getTextBounds(stepText,0,stepText.length(),textBounds); // 获取文本高宽
        int dx = getWidth()/2 - textBounds.width()/2; // 文本x轴,位置
        Paint.FontMetricsInt fontMetricsInt = mTextPaint.getFontMetricsInt();
        int dy = (fontMetricsInt.bottom - fontMetricsInt.top)/2 - fontMetricsInt.bottom;
        int baseLine = getHeight()/2 + dy; // 文本y轴,基线位置
        canvas.drawText(stepText,dx,baseLine,mTextPaint);
    }

    //7、其他处理 动画
    // 设置最大值
    public void setmStepMax(int mStepMax) {
        this.mStepMax = mStepMax;
    }
    // 设置当前值
    public void setmCurrentStep(int mCurrentStep) {
        this.mCurrentStep = mCurrentStep;
        // 重绘
        invalidate();
    }
}

invalidate() 该方法会调用onDraw()方法

五、其他(动画实现)
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final QQStepView qqStepView = findViewById(R.id.stepView);
        qqStepView.setmStepMax(1000); // 最大值
        qqStepView.setmCurrentStep(0); // 当前值

        ValueAnimator valueAnimator = ObjectAnimator.ofInt(0,666); // 设置动画遍历值,从0到30
        valueAnimator.setDuration(1000); // 动画执行的间隔
        valueAnimator.setInterpolator(new DecelerateInterpolator()); // 差值器,先快后慢
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int currentStep = (int) animation.getAnimatedValue();
                qqStepView.setmCurrentStep(currentStep);
            }
        });
        valueAnimator.start();
    }
}

自定义进度条

1、需求分析

在这里插入图片描述

2、设置自定义属性在这里插入图片描述
3、实现效果

动画

在这里插入图片描述
在这里插入图片描述

clipRect()方法

canvas.clipRect()

面试题讲解

一、为什么继承ViewGroup会画不出东西
原因:

View 的构造函数中 dirtyOpaue 来决定的,在ViewGroup中的 initViewGroup 中赋值让 dirtyQpaue 设置为不执行 onDraw()方法。

解决办法:

1、将onDraw方法改为dispatchDraw方法
2、设置背景(透明)
3、调用setWillNotDraw(false) 方法改变 dirtyQpaue的值

二、过度渲染

如何查看自己的界面有没有过度渲染
1、打开开发者选项
2、打开调试GPU过度绘制

1.网上的解决方案

尽量不要嵌套
能不设置背景不要设置背景

2.最好的解决方案

自己画

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值