自定义View练习

本文介绍了自定义View的难点和一个实际案例,包括XML属性、测量、绘图、事件处理等步骤。通过实例展示了如何绘制非均匀进度条、圆圈和文字,并实现了点击与侧滑交互功能。在实践中不断优化适配和细节处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

自定义View一直是一个很头疼的问题,之前想写了很久就是写不出来,今天来写点入门的东西。

首先我们为什么会觉得自定义view很难呢?

  • 第一是我们不知道怎么绘制,画线?画文字?
  • 第二各种距离的测量计算
  • 第三个是事件的处理,点击?滑动?

先来看看我们的需求!

!这是要实现的效果1
效果2

最终我做的效果

GIF.gif

大概分析一下就开干,然后边做边思考解决出现的问题!看着很简单,上面有一个进度条,但是不是均匀分布,中间还有两个圆圈可以点击,用来更改状态。最后还有个斜着的的已放弃!

1)我们一般会考虑XML的属性,我们需要什么参数?这个还是边做边加吧!
MV(4M4334E]JRTQ9DJZ12U4.png

这个仅仅是测试的,需要什么加什么,获取XML 的值实在构造里面处理的,如上!

2)进入测量阶段!
我们在测量的时候最担心的是不同的mode带来的问题,比如说我设置了wrap_content
的高度,那我们是不是应该给一个默认的值呢,如果设置的是精准的高度那么我们就可以直接赋值了!
当然我们还应该考虑padding值,这里我们先不管,先把功能实现,后续的一些细节问题,如果前期懂了那些都不是啥问题!

这里的几个点注意用处

当然我们看到的这个代码是我写的第二次,为嘛呢,因为我们还有一个适配问题没有考虑,本人比较懒,这个view的模式基本是死的,也就是说两个圆圈的位置是死的,所以我就用了百分比布局方式,这样就可以解决适配问题了!

3)
开始画画啦!

  1. 首先我们要划线,那么从什么位置开始画画到什么位置,第一条线当然是从0到第一个圆圈位置,你看到我定义的是point_1,那么我们就开始画第一条线

      linePaint.setColor(COLOR_SELECT);
     canvas.drawLine(50, BASIC_HEIGHT, point_1, BASIC_HEIGHT, linePaint);
    

    Paint用来设置我们需要的颜色、线的粗细,等等属性,,canvas负责绘画

    BASIC_HEIGHT是我定义的一个基本的高度,这样第一天条线就画完了

  2. 我们开始绘制第一个圆,当然这里我画的其实是一个Icon,这个是阿里云的把图片转文字的一种方式,所以 我画上去的是文字

         rectF = new Rect();
        textPaint.getTextBounds(UNSELECT_ICON, 0, UNSELECT_ICON.length(), rectF);
        canvas.drawText(UNSELECT_ICON, point_1 - 3, BASIC_HEIGHT + rectF.height() / 2, textPaint);
    

    我们现用 RectF来测量文字的宽高,这样rectf就有了文字宽高的值,便于我们是用。

    为了让这个圆圈正好在中间,我们是不是要知道圆的高,让后把高度提高一半才正好落 在中轴线上,所以看我这里就是这么做的!

  3. 后面再画圆画直线就一样了,

  4. 绘制文字,绘制文字的时候我们仅需要知道在什么位置绘制,也是根据我设置的那几个点来的,文字关于点对称,那么要求文字的起始点比我设置的点要小文字长度的一半
    textPaint.getTextBounds(proStrings[1], 0, proStrings[1].length(), rectF5);
    canvas.drawText(proStrings[1], point_1 + rectF.width() / 2 - rectF5.width() / 2,
    BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);

    我们这里加了一个文字的高度?因为文字的坐标实在坐下角的,所以罗。。
    point_1 + rectF.width() / 2 - rectF5.width() / 2 这个宽度还要包括圆的一半对吧,位 置其实就这么算出来的!

    private void drawPro(Canvas canvas, int progress) {
    linePaint.setColor(COLOR_UNSELECT);
    linePaint.setStrokeWidth(LINE_HEIGHT);
    linePaint.setAntiAlias(true);
    textPaint.setColor(COLOR_UNSELECT);
    textPaint.setStrokeWidth(3);
    textPaint.setTextSize(textSize);
    textPaint.setTypeface(iconfont);
    textPaint.setAntiAlias(true);
    paddingLeft = getPaddingLeft();
    paddingTop = getPaddingTop();
    paddingRight = getPaddingRight();
    paddingBottom = getPaddingBottom();
    width = getWidth() - paddingLeft - paddingRight;
    height = getHeight() - paddingTop - paddingBottom;
    BASIC_HEIGHT = BASIC_HEIGHT + paddingTop;
    if (progress == 0) {
        linePaint.setColor(COLOR_SELECT);
        canvas.drawLine(50, BASIC_HEIGHT, point_1, BASIC_HEIGHT, linePaint);
        rectF = new Rect();
        textPaint.getTextBounds(UNSELECT_ICON, 0, UNSELECT_ICON.length(), rectF);
        canvas.drawText(UNSELECT_ICON, point_1 - 3, BASIC_HEIGHT + rectF.height() / 2, textPaint);
        linePaint.setColor(COLOR_UNSELECT);
        canvas.drawLine(point_1 - 3 + rectF.width(), BASIC_HEIGHT, point_2, BASIC_HEIGHT, linePaint);
        canvas.drawText(UNSELECT_ICON, point_2 - 3, BASIC_HEIGHT + rectF.height() / 2, textPaint);
        canvas.drawLine(point_2 - 3 + rectF.width(), BASIC_HEIGHT, screeenWidth - 50, BASIC_HEIGHT, linePaint);
    } else if (progress == 1) {
        linePaint.setColor(COLOR_SELECT);
        canvas.drawLine(50, BASIC_HEIGHT, point_2, BASIC_HEIGHT, linePaint);
        rectF = new Rect();
        textPaint.getTextBounds(UNSELECT_ICON, 0, UNSELECT_ICON.length(), rectF);
        canvas.drawText(UNSELECT_ICON, point_2 - 3, BASIC_HEIGHT + rectF.height() / 2, textPaint);
        linePaint.setColor(COLOR_UNSELECT);
        canvas.drawLine(point_2 - 3 + rectF.width(), BASIC_HEIGHT, screeenWidth - 50, BASIC_HEIGHT, linePaint);
    } else if (progress == 2) {
        linePaint.setColor(COLOR_SELECT);
        canvas.drawLine(50, BASIC_HEIGHT, point_2, BASIC_HEIGHT, linePaint);
        rectF = new Rect();
        textPaint.setColor(COLOR_SELECT);
        textPaint.getTextBounds(SELECT_ICON, 0, SELECT_ICON.length(), rectF);
        canvas.drawText(SELECT_ICON, point_2 - 3, BASIC_HEIGHT + rectF.height() / 2, textPaint);
        linePaint.setColor(COLOR_UNSELECT);
        canvas.drawLine(point_2 - 1 + rectF.width(), BASIC_HEIGHT, screeenWidth - 50, BASIC_HEIGHT, linePaint);
    } else if (progress == 3) {
        linePaint.setColor(COLOR_SELECT);
        rectF = new Rect();
        textPaint.setColor(COLOR_SELECT);
        textPaint.getTextBounds(SELECT_ICON, 0, SELECT_ICON.length(), rectF);
        canvas.drawText(SELECT_ICON, point_3 - rectF.width() / 2 - 2, BASIC_HEIGHT + rectF.height() / 2, textPaint);
        canvas.drawLine(50, BASIC_HEIGHT, point_3 - rectF.width() / 2 - 2, BASIC_HEIGHT, linePaint);
        linePaint.setColor(COLOR_UNSELECT);
        canvas.drawLine(point_3 + rectF.width() / 2, BASIC_HEIGHT, screeenWidth - 50, BASIC_HEIGHT, linePaint);
    } else if (progress == 4) {
        linePaint.setColor(COLOR_SELECT);
        rectF = new Rect();
        textPaint.setColor(COLOR_SELECT);
        textPaint.getTextBounds(SELECT_ICON, 0, SELECT_ICON.length(), rectF);
        canvas.drawText(SELECT_ICON, screeenWidth - rectF.width(), BASIC_HEIGHT + rectF.height() / 2, textPaint);
        canvas.drawLine(50, BASIC_HEIGHT, screeenWidth - rectF.width(), BASIC_HEIGHT, linePaint);
    
    }
    
    textPaint.setColor(mContext.getResources().getColor(R.color.color_editText));
    textPaint.setTextSize(40);
    textPaint.setAntiAlias(true);
    Rect rectF5 = new Rect();
    
    textPaint.getTextBounds(proStrings[0], 0, proStrings[0].length(), rectF5);
    canvas.drawText(proStrings[0], 50, BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);
    
    textPaint.getTextBounds(proStrings[1], 0, proStrings[1].length(), rectF5);
    canvas.drawText(proStrings[1], point_1 + rectF.width() / 2 - rectF5.width() / 2, BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);
    
    textPaint.getTextBounds(proStrings[2], 0, proStrings[2].length(), rectF5);
    canvas.drawText(proStrings[2], point_2 + rectF.width() / 2 - rectF5.width() / 2, BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);
    
    textPaint.getTextBounds(proStrings[3], 0, proStrings[3].length(), rectF5);
    canvas.drawText(proStrings[3], point_3 - rectF5.width() / 2, BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);
    
    textPaint.getTextBounds(proStrings[4], 0, proStrings[4].length(), rectF5);
    canvas.drawText(proStrings[4], screeenWidth - 60 - rectF5.width(), BASIC_HEIGHT + rectF5.height() + TOP_HEIGHT, textPaint);
    
    if (isGiveUp) {
        canvas.rotate(-10, point_3, BASIC_HEIGHT);
        Rect rect = new Rect();
        textPaint.getTextBounds(GIVE_UP_STR, 0, GIVE_UP_STR.length(), rect);
        textPaint.setColor(mContext.getResources().getColor(R.color.color_textView));
        canvas.drawText(GIVE_UP_STR, point_3 - rect.width() - 20, BASIC_HEIGHT + rect.height() / 2, textPaint);
        rect.right = (int) point_3;
        rect.left = (int) (point_3 - 150);
        rect.top = BASIC_HEIGHT - 30;
        rect.bottom = BASIC_HEIGHT + 30;
        RectF rectf = new RectF(rect.left, rect.top, rect.right, rect.bottom);
        linePaint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(rectf, 10, 10, linePaint);
    }
    

    }

这里我用了了一个progress进度来控制绘画哪一步的样式

5.
那我们如何画放弃呢?一个圆角矩形和文字,同时旋转一定的角度!其实就是对画布 进行旋转,如果我们画完这个还需要画其他的需要canvas的save方法保存状态,不然后 面画的就都是斜着的了。

  if (isGiveUp) {
        canvas.rotate(-10, point_3, BASIC_HEIGHT);
        Rect rect = new Rect();
        textPaint.getTextBounds(GIVE_UP_STR, 0, GIVE_UP_STR.length(), rect);
        textPaint.setColor(mContext.getResources().getColor(R.color.color_textView));
        canvas.drawText(GIVE_UP_STR, point_3 - rect.width() - 20, BASIC_HEIGHT + rect.height() / 2, textPaint);
        rect.right = (int) point_3;
        rect.left = (int) (point_3 - 150);
        rect.top = BASIC_HEIGHT - 30;
        rect.bottom = BASIC_HEIGHT + 30;
        RectF rectf = new RectF(rect.left, rect.top, rect.right, rect.bottom);
        linePaint.setStyle(Paint.Style.STROKE);
        canvas.drawRoundRect(rectf, 10, 10, linePaint);
    }

6.现在貌似一个毛坯已经出来了,还很毛,没有任何的细节处理!
先来点击事件吧。
我们所有的点击处理都早ontouchEvent里面处理的,我点击圆,我怎么知道点击在圆上? 我们想这个是一个整体的view没办法区分我是不是点在圆上,那么我们能不能根据圆的坐标点呢,稍微的放大!!

     case MotionEvent.ACTION_UP:
            EndTime = System.currentTimeMillis();
            float dy = event.getY() - y;
            float dx = event.getX() - x;
            if (EndTime - StartTime < 200) {
                if (Math.abs(dx) < 3 && Math.abs(dy) < 3) {
                    if(!isGiveUp){
                        if (mOnClickListener != null) {
                            if (event.getX() > point_1 - 50 && event.getX() < point_1 + 50) {
                                mOnClickListener.onClick(1);
                            }
                            if (event.getX() > point_2 - 50 && event.getX() < point_2 + 50) {
                                mOnClickListener.onClick(2);
                            }
                        }
                    }
                }
            }
            break;

你看点击事件有了,这里用的就是一个简单的回调,当然我没有限制y的范围,这个根据自己愿意吧!

7.最后我们还有一个功能侧滑!
我们要侧滑,也就是把自己向右移动某个距离,怎么移动。我这里就懒一点,用一个动画搞定吧,你可以根据滑动的距离来决定view的滑动多少!

   case MotionEvent.ACTION_MOVE:
 if (Math.abs(event.getY() - y) < 10 && event.getX() - x < -20) {
                if (!isOpen && !isGiveUp&&mProgress<3) {
                    open();
                }
            } else {
                if (Math.abs(event.getY() - y) < 10 && event.getX() - x > 30&&isOpen) {
                    close();
                }
            }
 public void open() {
    ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationX", 0, -CommonUtils.dip2px(mContext, 60));
    animator.setDuration(200);
    animator.start();
    isOpen = true;
}

public void close() {
    ObjectAnimator animator = ObjectAnimator.ofFloat(this, "translationX", -CommonUtils.dip2px(mContext, 60), 0);
    animator.setDuration(200);
    animator.start();
    isOpen = false;
}

这个时候再回去看开始的东西

我们是不是需要一个进度、还有能否侧滑,是不是放弃状态的、字体大小,颜色,线的颜色,高度等等值都可以做成属性值!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值