Android自定义控件

自定义View步骤

  1. 在values->attrs.xml中自定义控件的样式
  2. 创建自定义View,在构造方法中获取自定义属性
  3. [ 重写onMesure ]
  4. 重写onDraw

说明:onDraw必须要重写,onMesure可以不重写

实践

自定义View,实现如下图进度条

这里写图片描述

  1. 先画一个矩形边框
  2. 在边框内再画一个矩形,用红色充满整个矩形
  3. 把文字画到矩形中间

第一步:自定义View的属性

<declare-styleable name="CustomView">
     <attr name="text_size" format="dimension"/>
declare-styleable>

第二步:在自定义View的构造方法中获取我们自定义的属性

1、在布局文件中添加我们的自定义View
<com.cn.liuyz.customviewdemo.CustomView
        android:id="@+id/cv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        custom:text_size="30sp"/>
2、在自定义View中获取自定义属性
public class CustomView extends View {

    private int mTestSize;

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

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

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView(context, attrs);
    }

    private Paint mPaint;
    private Rect mBound;

    private void initView(Context context, AttributeSet attrs) {
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CustomView, 0, 0);
        mTestSize = typedArray.getDimensionPixelOffset(R.styleable.CustomView_text_size, 0);
        typedArray.recycle();
        //创建画笔
        mPaint = new Paint();
        //抗锯齿
        mPaint.setAntiAlias(true);
        //设置字体大小
        mPaint.setTextSize(mTestSize);

        mBound = new Rect();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    .....省略.....
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
    .....省略.....
    }
}

第三步:重写onDraw

private String curPro ="0%";
    private int progress = 0;

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //设置画笔颜色
        mPaint.setColor(Color.YELLOW);
        //只画轮廓STROKE 边框
        mPaint.setStrokeWidth(20);
        mPaint.setStyle(Paint.Style.STROKE);
        //画矩形
        canvas.drawRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), mPaint);

        float curProgress = (float) progress / 100;
        curPro = progress + "%";

        mPaint.setColor(Color.RED);
        mPaint.setStrokeWidth(0);
        //全部填充FILL
        mPaint.setStyle(Paint.Style.FILL);

        int current = (int) (curProgress * (getMeasuredWidth() - 10));
        canvas.drawRect(10, 10, current, getMeasuredHeight()-10, mPaint);

        mPaint.getTextBounds(curPro, 0, curPro.length(), mBound);
        mPaint.setColor(Color.BLACK);
        mPaint.setStrokeWidth(0);
        mPaint.setStyle(Paint.Style.FILL);
        canvas.drawText(curPro, getWidth() / 2 - mBound.width() / 2, getHeight() / 2 + mBound.height() / 2, mPaint);
    }

通过setProgress方法传进来进度,再通过invalidate方法刷新onDraw

public void setProgress(int progress) {
        this.progress = progress;
        invalidate();
    }

创建一个线程并通过Handler机制刷新进度条

    private boolean isTrue = true;
    private Handler handler = new Handler();
    private int prograss = 0;

    private void btn2Click() {
        prograss = 0;
        isTrue = true;
        new Thread(new Runnable() {
            @Override
            public void run() {
                while (isTrue) {
                    prograss++;
                    //休息100毫秒
                    SystemClock.sleep(100);
                    //通过Handler更新进度条
                    handler.post(new Runnable() {
                        @Override
                        public void run() {
                            mCustomView.setProgress(prograss);
                        }
                    });
                    if (prograss == 100) {
                        isTrue = false;
                    }
                }
            }
        }).start();
    }

重写onMesure条件

  • 当在布局中设置了明确的宽度、高度时,系统帮我们测量就是我们设置的值
  • 当设置为MATCH_PARENT时,系统帮我们测量就是MATCH_PARENT的长度
  • 当设置为WRAP_CONTENT时,系统帮我们测量也是MATCH_PARENT的长度

所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法:
重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        curPro = progress + "%";
        Log.d("liuyz:::", "onMeasure");

        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        int width;
        int height;
        if (widthMode == MeasureSpec.EXACTLY){
            width = widthSize;
        } else{
            mPaint.getTextBounds(curPro, 0, curPro.length(), mBound);
            float textWidth = mBound.width();
            int wrapWidth = (int) (getPaddingLeft() + textWidth + getPaddingRight());
            width = wrapWidth;
        }

        if (heightMode == MeasureSpec.EXACTLY){
            height = heightSize;
        } else{
            mPaint.getTextBounds(curPro, 0, curPro.length(), mBound);
            float textHeight = mBound.height();
            int wrapHeight = (int) (getPaddingTop() + textHeight + getPaddingBottom());
            height = wrapHeight;
        }
        setMeasuredDimension(width, height);
    }

你可能感兴趣的:(普通)