文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android自定义View实现仿驾考宝典显示分数效果(收藏)

2022-06-06 11:47

关注

小编最近发现,一些炫酷的view效果,通过需要自定义view和属性动画结合在一起,才能更容易的实现。

实现的效果图如下:

效果图

所用的知识有:

(1)自定义View中的 path ,主要用来绘制指示块。

(2)属性动画-ValueAnimator,并设置属性动画的监听器。

(3)根据属性动画是否结束的标志,决定是否绘制分数对应的描述文本内容。

实现步骤:

继承自View,在构造函数中获取自定义属性和初始化操作(初始化画笔)


private void obtainAttrs(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
    lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
    lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
    typedArray.recycle();
  }
  private void init() {
    arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
    arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
    bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
    reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
    arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
    scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
    descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
  }

其中初始化画笔抽取到一个函数中:


private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(color);
    paint.setStrokeWidth(strokeWidth);
    paint.setStyle(style);
    paint.setStrokeJoin(Paint.Join.ROUND);
    paint.setStrokeCap(Paint.Cap.ROUND);
    paint.setTextSize(textSize);
    return paint;
  }

覆盖 onSizeChanged() ,得到控件的宽高,并决定要绘制区域的大小(控件默认设置了内边距)


@Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    viewWidth = w;
    viewHeight = h;
    halfView = Math.min(viewWidth, viewHeight) / 2;  //宽度或高度中最小值的一半,即决定圆心的位置。
    radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //绘制园的半径是宽高除去默认内边距
  }

核心绘制代码,覆盖onDraw()方法,根据动画是否结束的标志,决定是否绘制分数对应的文本。


@Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawArcBackground(canvas);
    drawArcProgress(canvas);
    drawScoreText(canvas);
    if (isAnimEnd) {
      drawDescText(canvas);
    }
  }

(1)绘制圆弧背景和灰色刻度背景。


 private void drawArcBackground(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    //绘制圆弧
    RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
    canvas.drawArc(rectF, 120, 300, false, arcPaint);
    //绘制刻度线
    canvas.rotate(30);
    for (int i = 0; i < 100; i++) {
      canvas.drawLine(0, radius - dp2Px(15), 0,
          radius - dp2Px(15) - lineLength,
          bgPaint);
      canvas.rotate(degree);
    }
    canvas.restore();
  }

(2) 绘制刻度,根据ValueAnimator进行动画的当前值curValue,来动态改变绘制指示块和已达进度圆弧,从而实现从0开始移动到设定值的动画效果。


private void drawArcProgress(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    //绘制圆弧
    RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
    canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
    //绘制指示方块,方块是从0开始移动某一个位置的
    canvas.rotate(30 + degree * curValue);
    Path path = new Path();
    path.moveTo(dp2Px(5), radius);
    path.lineTo(dp2Px(5), radius - dp2Px(10));
    path.lineTo(0, radius - dp2Px(15));
    path.lineTo(-dp2Px(5), radius - dp2Px(10));
    path.lineTo(-dp2Px(5), radius);
    path.close();
    canvas.drawPath(path, arrowPaint);
    //绘制已经达到的刻度
    canvas.restore();
    canvas.save();
    canvas.translate(halfView, halfView);
    canvas.rotate(30);
    for (int i = 0; i < curValue; i++) {
      canvas.drawLine(0, radius - dp2Px(15), 0,
          radius - dp2Px(15) - lineLength,
          reachProgressPaint);
      canvas.rotate(degree);
    }
    canvas.restore();
  }

(3) 绘制分数文本。


private void drawScoreText(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    String scoreText = curValue + "分";
    float textLength = scoreTextPaint.measureText(scoreText);
    canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
    canvas.restore();
  }

(4) 动画结束时,绘制最终分数对应的提示信息,该信息只有在动画结束后,才会显示出来。


private void drawDescText(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    String desc = "";
    if (curValue >= 90) {
      desc = "车神";
    } else {
      desc = "马路杀手";
    }
    float descLength = descTextPaint.measureText(desc);
    canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
    canvas.restore();
    isAnimEnd = false; // isAnimEnd置为false,防止再次点击start时,就显示动画结束时才能显示的内容
  }

(5)提供对外设置最大值的接口,决定最后的分数。


 
  public void setMaxValue(int value) {
    if (valueAnimator == null) {
      valueAnimator = ValueAnimator.ofInt(0, value);
    }
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.setDuration(30 * value);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        curValue = (int) animation.getAnimatedValue();
        Log.i("debug", "curValue=" + curValue);
        invalidate();
      }
    });
    valueAnimator.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        isAnimEnd = true; //标记动画结束
        Log.i("debug", "onAnimationEnd");
        Log.i("debug", "onAnimationEnd curValue = " + curValue);
        invalidate();
      }
    });
    valueAnimator.start();
  }

完整代码:


public class ScoreView extends View {
  private final int DEFAULT_PADDING = dp2Px(5);
  private final int DEFAULT_WIDTH = dp2Px(200);  //默认宽度为200dp
  private final int DEFAULT_HEIGHT = dp2Px(200); //默认高度为200dp
  private int viewWidth; //View宽度
  private int viewHeight;  //View高度
  private int halfView; //view宽度或高度的一半
  private int radius;  //绘制圆形区域的半径
  private Paint bgPaint;
  private Paint arrowPaint; //指示块画笔
  private Paint arcPaint; //圆弧画笔
  private Paint arcReachPaint; //圆弧画笔
  private Paint reachProgressPaint; //已达刻度
  private Paint scoreTextPaint;  //绘制分数文本
  private Paint descTextPaint;  //绘制描述文本
  private float degree = 3f; //相邻刻度之间夹角大小为3度,角度制,不是弧度制
  private float lineLength;
  private int lineColor;
  private int curValue;  //动画进行的当前值
  private ValueAnimator valueAnimator;
  private boolean isAnimEnd = false;
  public ScoreView(Context context) {
    this(context, null);
  }
  public ScoreView(Context context, AttributeSet attrs) {
    super(context, attrs);
    obtainAttrs(context, attrs);
    init();
  }
  private void obtainAttrs(Context context, AttributeSet attrs) {
    TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ScoreView);
    lineLength = typedArray.getDimension(R.styleable.ScoreView_lineLength, dp2Px(10));
    lineColor = typedArray.getColor(R.styleable.ScoreView_lineColor, Color.WHITE);
    typedArray.recycle();
  }
  private void init() {
    arrowPaint = createPaint(Color.WHITE, 0, Paint.Style.FILL, 0);
    arcPaint = createPaint(lineColor, dp2Px(1), Paint.Style.STROKE, 0);
    bgPaint = createPaint(lineColor, dp2Px(1), Paint.Style.FILL, 0);
    reachProgressPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.FILL, 0);
    arcReachPaint = createPaint(Color.WHITE, dp2Px(1), Paint.Style.STROKE, 0);
    scoreTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(26));
    descTextPaint = createPaint(Color.WHITE, 0, Paint.Style.STROKE, dp2Px(16));
  }
  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    setMeasuredDimension(measureSize(widthMeasureSpec, DEFAULT_WIDTH), measureSize(heightMeasureSpec, DEFAULT_HEIGHT));
  }
  private int measureSize(int measureSpec, int defaultSize) {
    int measureSize = defaultSize;
    int mode = MeasureSpec.getMode(measureSpec);
    int size = MeasureSpec.getSize(measureSpec);
    if (mode == MeasureSpec.EXACTLY) {
      measureSize = size;
    } else {
      if (mode == MeasureSpec.AT_MOST) {
        measureSize = Math.min(defaultSize, size);
      }
    }
    return measureSize;
  }
  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    viewWidth = w;
    viewHeight = h;
    halfView = Math.min(viewWidth, viewHeight) / 2;  //宽度或高度中最小值的一半,即圆形的位置
    radius = (Math.min(viewWidth, viewHeight) - 2 * DEFAULT_PADDING) / 2; //半径是宽高除去默认内边距的
  }
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    drawArcBackground(canvas);
    drawArcProgress(canvas);
    drawScoreText(canvas);
    if (isAnimEnd) {
      drawDescText(canvas);
    }
  }
  private void drawScoreText(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    String scoreText = curValue + "分";
    float textLength = scoreTextPaint.measureText(scoreText);
    canvas.drawText(scoreText, -textLength / 2, 0, scoreTextPaint);
    canvas.restore();
  }
  
  private void drawDescText(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    String desc = "";
    if (curValue >= 90) {
      desc = "车神";
    } else {
      desc = "马路杀手";
    }
    float descLength = descTextPaint.measureText(desc);
    canvas.drawText(desc, -descLength / 2, dp2Px(30), descTextPaint);
    canvas.restore();
    isAnimEnd = false; // isAnimEnd置为false,防止再次点击start时,就显示动画结束时才能显示的内容
  }
  
  private void drawArcProgress(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    //绘制圆弧
    RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
    canvas.drawArc(rectF, 120, curValue * degree, false, arcReachPaint);
    //绘制指示方块,方块是从0开始移动某一个位置的
    canvas.rotate(30 + degree * curValue);
    Path path = new Path();
    path.moveTo(dp2Px(5), radius);
    path.lineTo(dp2Px(5), radius - dp2Px(10));
    path.lineTo(0, radius - dp2Px(15));
    path.lineTo(-dp2Px(5), radius - dp2Px(10));
    path.lineTo(-dp2Px(5), radius);
    path.close();
    canvas.drawPath(path, arrowPaint);
    //绘制已经达到的刻度
    canvas.restore();
    canvas.save();
    canvas.translate(halfView, halfView);
    canvas.rotate(30);
    for (int i = 0; i < curValue; i++) {
      canvas.drawLine(0, radius - dp2Px(15), 0,
          radius - dp2Px(15) - lineLength,
          reachProgressPaint);
      canvas.rotate(degree);
    }
    canvas.restore();
  }
  
  private void drawArcBackground(Canvas canvas) {
    canvas.save();
    canvas.translate(halfView, halfView);
    //绘制圆弧
    RectF rectF = new RectF(dp2Px(5) - radius, dp2Px(5) - radius, radius - dp2Px(5), radius - dp2Px(5));
    canvas.drawArc(rectF, 120, 300, false, arcPaint);
    //绘制刻度线
    canvas.rotate(30);
    for (int i = 0; i < 100; i++) {
      canvas.drawLine(0, radius - dp2Px(15), 0,
          radius - dp2Px(15) - lineLength,
          bgPaint);
      canvas.rotate(degree);
    }
    canvas.restore();
  }
  
  private Paint createPaint(int color, int strokeWidth, Paint.Style style, float textSize) {
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setColor(color);
    paint.setStrokeWidth(strokeWidth);
    paint.setStyle(style);
    paint.setStrokeJoin(Paint.Join.ROUND);
    paint.setStrokeCap(Paint.Cap.ROUND);
    paint.setTextSize(textSize);
    return paint;
  }
  
  private int dp2Px(int dpValue) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
        dpValue, getResources().getDisplayMetrics());
  }
  
  public void setMaxValue(int value) {
    if (valueAnimator == null) {
      valueAnimator = ValueAnimator.ofInt(0, value);
    }
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.setDuration(30 * value);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        curValue = (int) animation.getAnimatedValue();
        Log.i("debug", "curValue=" + curValue);
        invalidate();
      }
    });
    valueAnimator.addListener(new AnimatorListenerAdapter() {
      @Override
      public void onAnimationEnd(Animator animation) {
        super.onAnimationEnd(animation);
        isAnimEnd = true; //标记动画结束
        Log.i("debug", "onAnimationEnd");
        Log.i("debug", "onAnimationEnd curValue = " + curValue);
        invalidate();
      }
    });
    valueAnimator.start();
  }
}

以上所述是小编给大家介绍的Android自定义View实现仿驾考宝典显示分数效果(收藏),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程网网站的支持!

您可能感兴趣的文章:Android view自定义实现动态进度条Android自定义View系列之Path绘制仿支付宝支付成功动画Android 自定义view和属性动画实现充电进度条效果Android自定义View 实现闹钟唤起播放闹钟铃声功能Android自定义TextView实现文字倾斜效果Android自定义view实现阻尼效果的加载动画


阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-移动开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯