我的自定义View之旅(四)

今天用自己这几天的理解完全的写了一个自定义VIew,成就感满满

效果

  • 大概实现进度条样式功能
  • 进度条的颜色,样式,等
  • 进度条的拖动,点击回调
  • 上一下效果图吧,虽然丑额,但是好歹功能大概实现了
    这里写图片描述

思路

  • 先定义出我们这个View需要的属性文件
  • 然后在自定义View中,获取属性,测量相关宽高,绘制View
  • 然后添加点击事件,或者自定义我们的回调接口,当进度条进度改变等一系列事件的回调接口
  • 然后重写onTouchEvent(),处理各种事件,并在适当的时候回调我们的接口方法

代码实现

先来看一下属性文件的内容,连着好几篇了,我就不解释了,直接上代码

<declare-styleable name="MyProgressView">
   <attr name="myProgressBackColor" format="color"/>
    <attr name="guageModel">
        <enum name="Circle" value="1"/>
        <enum name="Five_Point_Star" value="2"/>
    </attr>
    <attr name="guageColor" format="color"/>
    <attr name="lineColor" format="color"/>

</declare-styleable>
  • 构造方法中得到属性
public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);

    TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyProgressView);
    for(int i = 0;i < array.getIndexCount(); i++){
        int attr = array.getIndex(i);
        switch (attr){
            case R.styleable.MyProgressView_myProgressBackColor:
                backColor = array.getColor(attr,Color.GRAY);
                break;
            case R.styleable.MyProgressView_guageColor:
                guageColor = array.getColor(attr,Color.RED);
                break;
            case R.styleable.MyProgressView_guageModel:
                guageModel = array.getInt(attr,1);
                break;
            case R.styleable.MyProgressView_lineColor:
                lineColor = array.getColor(attr,Color.GREEN);
                break;
        }
    }
    mPaint = new Paint();
}

测量

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);

        if (widthMeasureMode == MeasureSpec.EXACTLY){
            width = widthMeasureSize;
        }else {
            ViewGroup parenGroup = (ViewGroup) getParent();
            if(parenGroup != null){
                width = parenGroup.getMeasuredWidth();
            }
        }

        if(heightMeasureMode == MeasureSpec.EXACTLY){
            height = heightMeasureSize;
        }else {
            ViewGroup parenGroup = (ViewGroup) getParent();
            if(parenGroup != null){
                height = parenGroup.getMeasuredHeight()/2/2/2;
            }
        }

        switch (guageModel){
            case CIRCLE:
                guageRadius = (int) (((float)((height - getPaddingTop() - getPaddingBottom())/2))*0.25);
                break;
            case FIVE_POINTS_STAR:
                break;
                default:break;
        }
        guageMinX = getPaddingLeft() + guageRadius;
        guageLineMinX = getPaddingLeft();
        guageMaxX = width - getPaddingRight() - guageRadius;
        guageLineMaxX = guageMaxX + guageRadius;

        setMeasuredDimension(width,height);
    }
  • 注释写的少,大概说一下,先判断宽高模式,然后如果是wrap_Content的话,就取父布局宽的全部,和高的八分之一,这个自己写的话根据自己的情况而定
  • 接下来更新进度条的最大值和最小值,以及那个进度球中心的最大值和最小值

绘制

protected void onDraw(Canvas canvas) {
        //画最外边框
        Rect rect = new Rect();
        rect.set(getPaddingLeft(),getPaddingTop(),width - getPaddingRight(),height -getPaddingBottom());
        mPaint.setColor(backColor);
        canvas.drawRect(rect,mPaint);

        //检查进度
        if(guageX <= 0){
            guageX = guageMinX;
        }
        //画已经走过的进度线
        //测量进度线y坐标
        int y = (height - getPaddingTop() - getPaddingBottom())/2 + getPaddingTop();

        mPaint.setColor(lineColor);
        Rect rect1 = new Rect(guageLineMinX,y-height/2/2/2,guageX,y+height/2/2/2);
        canvas.drawRect(rect1,mPaint);

        //画未走的进度条
        rect1.set(guageX,y-height/2/2/2,guageLineMaxX,y+height/2/2/2);
        mPaint.setColor(Color.RED);
        canvas.drawRect(rect1,mPaint);

        mPaint.setColor(guageColor);
        switch (guageModel){
            case CIRCLE:
                canvas.drawCircle(guageX,y,guageRadius,mPaint);
                break;
            case FIVE_POINTS_STAR:
                break;
        }
    }

添加回调接口

public interface MyProgressListener{
        void moveing(MyProgressView progress);
        void moveStart(MyProgressView progress);
        void moveEnd(MyProgressView progress);
    }
  • 这里我给每个接口方法都传进来了我们这个VIew自身,以便于在回调的时候可以调用相关方法,比方说,我添加一个可以获取进度的方法
public float getProgress(){
        return (guageX - guageMinX)*1.0f/(guageMaxX - guageMinX);
    }
  • 添加设置回调事件的方法
public void setProgressChangeListener(MyProgressListener listener){
        this.listener = listener;
    }
  • 然后在onTouchEvent方法中去调用我们的接口方法
public boolean onTouchEvent(MotionEvent event) {
        int x = (int) getX();
        int y = (int) getY();
        int downX;
        int downY;

        if((x < getPaddingLeft() || x > width - getPaddingRight()) &&
                y < getPaddingTop() || y > height - getPaddingBottom()){
            return false;
        }
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) getY();

                if(downX > (guageX - guageRadius) && downX < (guageX + guageRadius)){
                    canMove = true;
                    Log.d("--- ","设置canMove为true");
                    listener.moveStart(this);
                }

                Log.d("---down == ","guageX = "+guageX + ",guageRadius = "+guageRadius+",downX = "+downX);
                Log.d("---canMove == ",canMove+"");
                guageX+=10;

                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                if(canMove){
                    Log.d("---Move == ","正在移动,x = "+event.getX()+",guageX = "+guageX);
                    int nowX = (int) event.getX();
                    if(nowX > guageMaxX ){
                        guageX = guageMaxX;
                    }else if(nowX < guageMinX){
                        guageX = guageMinX;
                    }else {
                        guageX = nowX;
                    }
                    invalidate();
                    listener.moveing(this);
                }

                break;

            case MotionEvent.ACTION_UP:
                listener.moveEnd(this);
                canMove = false;
                break;
        }
        return true;
    }
  • 在activity中使用接口
var progressView = findViewById<MyProgressView>(R.id.main_MyProgressView)
        progressView!!.setProgressChangeListener(object :MyProgressView.MyProgressListener {
            override fun moveEnd(progress: MyProgressView?) {

            }

            override fun moveStart(progress: MyProgressView?) {

            }

            override fun moveing(progress: MyProgressView?) {

            }
        })
  • 到这里这个定义的View主要功能就完成了,至于一些细节啥东西,可以根据需求自行添加,在这里我主要是叙述自定义View的原理
  • 到这里自定义View的原理篇基本就这样了,接下来,我会去学一下自定义Viewgroup,敬请期待吧
  • 因个人水平能力有限,各位小伙伴又发现我错误的地方欢迎指出,有什么问题也可以随便提问,谢谢
  • 最后再贴一下我的整体代码
public class MyProgressView extends View {

    private final static int CIRCLE = 1;
    private final static int FIVE_POINTS_STAR = 2;


    private int backColor = Color.GRAY;//背景色
    private int guageColor = Color.RED;//进度条颜色
    private int guageModel = CIRCLE;//进度模式
    private int lineColor = Color.GREEN;//进度线颜色

    private MyProgressListener listener;  //进度改变监听

    private Paint mPaint;  //画笔

    private int width;  //控件总宽度
    private int height; // 空间总高度

    private int guageRadius;  //进度圆中心
    private int guageX = 0;   //进度圆中心此时的x值
    private int guageMinX;    //进度圆中心x坐标最小值
    private int guageMaxX;     //进度圆中心x坐标最大值

    private int guageLineMinX; //进度线最小x值
    private int guageLineMaxX; //进度线最大x值


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

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

    public MyProgressView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.MyProgressView);
        for(int i = 0;i < array.getIndexCount(); i++){
            int attr = array.getIndex(i);
            switch (attr){
                case R.styleable.MyProgressView_myProgressBackColor:
                    backColor = array.getColor(attr,Color.GRAY);
                    break;
                case R.styleable.MyProgressView_guageColor:
                    guageColor = array.getColor(attr,Color.RED);
                    break;
                case R.styleable.MyProgressView_guageModel:
                    guageModel = array.getInt(attr,1);
                    break;
                case R.styleable.MyProgressView_lineColor:
                    lineColor = array.getColor(attr,Color.GREEN);
                    break;
            }
        }
        mPaint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMeasureSize = MeasureSpec.getSize(widthMeasureSpec);
        int widthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);

        int heightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightMeasureSize = MeasureSpec.getSize(heightMeasureSpec);

        //判断宽模式
        if (widthMeasureMode == MeasureSpec.EXACTLY){
            width = widthMeasureSize;
        }else {
            ViewGroup parenGroup = (ViewGroup) getParent();
            if(parenGroup != null){
                width = parenGroup.getMeasuredWidth();
            }
        }
        //判断高模式
        if(heightMeasureMode == MeasureSpec.EXACTLY){
            height = heightMeasureSize;
        }else {
            ViewGroup parenGroup = (ViewGroup) getParent();
            if(parenGroup != null){
                height = parenGroup.getMeasuredHeight()/2/2/2;
            }
        }

        switch (guageModel){
            case CIRCLE:
                guageRadius = (int) (((float)((height - getPaddingTop() - getPaddingBottom())/2))*0.25);
                break;
            case FIVE_POINTS_STAR:
                break;
                default:break;
        }
        guageMinX = getPaddingLeft() + guageRadius;
        guageLineMinX = getPaddingLeft();
        guageMaxX = width - getPaddingRight() - guageRadius;
        guageLineMaxX = guageMaxX + guageRadius;

        setMeasuredDimension(width,height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //画最外边框
        Rect rect = new Rect();
        rect.set(getPaddingLeft(),getPaddingTop(),width - getPaddingRight(),height -getPaddingBottom());
        mPaint.setColor(backColor);
        canvas.drawRect(rect,mPaint);

        //检查进度
        if(guageX <= 0){
            guageX = guageMinX;
        }
        //画已经走过的进度线
        //测量进度线y坐标
        int y = (height - getPaddingTop() - getPaddingBottom())/2 + getPaddingTop();

        mPaint.setColor(lineColor);
        Rect rect1 = new Rect(guageLineMinX,y-height/2/2/2,guageX,y+height/2/2/2);
        canvas.drawRect(rect1,mPaint);

        //画未走的进度条
        rect1.set(guageX,y-height/2/2/2,guageLineMaxX,y+height/2/2/2);
        mPaint.setColor(Color.RED);
        canvas.drawRect(rect1,mPaint);

        mPaint.setColor(guageColor);
        switch (guageModel){
            case CIRCLE:
                canvas.drawCircle(guageX,y,guageRadius,mPaint);
                break;
            case FIVE_POINTS_STAR:
                break;
        }
    }

    boolean canMove = false;
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) getX();
        int y = (int) getY();
        int downX;
        int downY;

        if((x < getPaddingLeft() || x > width - getPaddingRight()) &&
                y < getPaddingTop() || y > height - getPaddingBottom()){
            return false;
        }
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                downX = (int) event.getX();
                downY = (int) getY();

                if(downX > (guageX - guageRadius) && downX < (guageX + guageRadius)){
                    canMove = true;
                    Log.d("--- ","设置canMove为true");
                    listener.moveStart(this);
                }

                Log.d("---down == ","guageX = "+guageX + ",guageRadius = "+guageRadius+",downX = "+downX);
                Log.d("---canMove == ",canMove+"");
                guageX+=10;

                invalidate();
                break;
            case MotionEvent.ACTION_MOVE:
                if(canMove){
                    Log.d("---Move == ","正在移动,x = "+event.getX()+",guageX = "+guageX);
                    int nowX = (int) event.getX();
                    if(nowX > guageMaxX ){
                        guageX = guageMaxX;
                    }else if(nowX < guageMinX){
                        guageX = guageMinX;
                    }else {
                        guageX = nowX;
                    }
                    invalidate();
                    listener.moveing(this);
                }

                break;

            case MotionEvent.ACTION_UP:
                listener.moveEnd(this);
                Log.d("----","设置可点击为false" + canMove);
                canMove = false;
                break;
        }
        return true;
    }

    public float getProgress(){
        return (guageX - guageMinX)*1.0f/(guageMaxX - guageMinX);
    }

    public void setProgressChangeListener(MyProgressListener listener){
        this.listener = listener;
    }

    public interface MyProgressListener{
        void moveing(MyProgressView progress);
        void moveStart(MyProgressView progress);
        void moveEnd(MyProgressView progress);
    }
}

总结一下

  • 在获取父布局宽度的时候一定要用getMeasureWidth()方法,不然还有一个getWidth()这个方法是获取不到宽度的
  • 进度条的x值我们需要去给他设置最大最小值
  • 最后贴上git地址

猜你喜欢

转载自blog.csdn.net/asffghfgfghfg1556/article/details/80298881