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

小编最近发现,一些炫酷的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)提供对外设置最大值的接口,决定最后的分数。

 /**
   * 对外提供的接口,用于设置进度的最大值
   *
   * @param value
   */
  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();
  }
  /**
   * 绘制文本
   *
   * @param canvas
   */
  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时,就显示动画结束时才能显示的内容
  }
  /**
   * 绘制进度
   *
   * @param canvas
   */
  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();
  }
  /**
   * 绘制背景
   *
   * @param canvas
   */
  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();
  }
  /**
   * 创建画笔
   *
   * @param color
   * @param strokeWidth
   * @param style
   * @param textSize
   * @return
   */
  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;
  }
  /**
   * dp转换成px
   *
   * @param dpValue
   * @return
   */
  private int dp2Px(int dpValue) {
    return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
        dpValue, getResources().getDisplayMetrics());
  }
  /**
   * 对外提供的接口,用于设置进度的最大值
   *
   * @param value
   */
  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实现仿驾考宝典显示分数效果(收藏),希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • Android自定义TextView实现文字倾斜效果

    前言 由于Android自带的TextView控件没有提供倾斜的(我暂时没有找到),我们可以自定义控件来实现,下面首先来看我们实现的效果图. TextView文字倾斜 其实实现很简单,下面我们来看实现步骤: 1.新建一个类 LeanTextView继承TextView public class LeanTextView extends TextView { public int getmDegrees() { return mDegrees; } public void setmDegrees(

  • Android自定义view实现阻尼效果的加载动画

    效果: 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动.衰减振动.[1] 不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来.这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运

  • Android view自定义实现动态进度条

    Android  自定义view实现动态进度条 效果图: 这个是看了梁肖的demo,根据他的思路自己写了一个,但是我写的这个貌似计算还是有些问题,从上面的图就可以看出来,左侧.顶部.右侧的线会有被截掉的部分,有懂得希望能给说一下,改进一下,这个过程还是有点曲折的,不过还是觉得收获挺多的.比如通动画来进行动态的展示(之前做的都是通过Handler进行更新的所以现在换一种思路觉得特别好),还有圆弧的起止角度,矩形区域的计算等!关于绘制我们可以循序渐进,比如最开始先画圆,然后再画周围的线,最后设置动画

  • Android自定义View 实现闹钟唤起播放闹钟铃声功能

    先上图看一下闹钟唤期页面的效果 实现的功能: 1:转动的图片根据天气情况更换 2:转动时间可以设置,转动结束,闹铃声音就结束 3:光圈颜色渐变效果 直接上代码啦: package com.yuekong.sirius.extension.customview; import android.animation.Animator; import android.animation.ValueAnimator; import android.content.Context; import andro

  • Android自定义View系列之Path绘制仿支付宝支付成功动画

    前言 使用支付宝付款时,我们可以看到成功或者失败都会有个动画提示,如果我们需要做这样的效果的话,当然,你可以让设计师给你做个GIF,但是我们知道图像比较耗内存的,我们自己可以用代码实现还是代码实现好点吧. 效果 实现方法 首先我们需要了解PathMeasure这个类,这个类我们可以理解为用来管理Path.我们主要看几个方法. PathMeasure(): 构造方法 ,实例化一个对象 PathMeasure(Path path,boolean isClosed):传入Path对象和是否闭合,pat

  • Android 自定义view和属性动画实现充电进度条效果

    近期项目中需要使用到一种类似手机电池充电进度的动画效果,以前没学属性动画的时候,是用图片+定时器的方式来完成的,最近一直在学习动画这一块,再加上复习一下自定义view的相关知识点,所以打算用属性动画和自定义view的方式来完成这个功能,将它开源出来,供有需要的人了解一下相关的内容. 本次实现的功能类似下面的效果: 接下来便详细解析一下如何完成这个功能,了解其中的原理,这样就能举一反三,实现其他类似的动画效果了. 详细代码请看大屏幕 https://github.com/crazyandcoder

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

    小编最近发现,一些炫酷的view效果,通过需要自定义view和属性动画结合在一起,才能更容易的实现. 实现的效果图如下: 所用的知识有: (1)自定义View中的 path ,主要用来绘制指示块. (2)属性动画-ValueAnimator,并设置属性动画的监听器. (3)根据属性动画是否结束的标志,决定是否绘制分数对应的描述文本内容. 实现步骤: 继承自View,在构造函数中获取自定义属性和初始化操作(初始化画笔) private void obtainAttrs(Context contex

  • Android自定义View实现仿GitHub的提交活跃表格

    说明 本文可能需要一些基础知识点,如Canvas,Paint,Path,Rect等类的基本使用,建议不熟悉的同学可以学习GcsSloop安卓自定义View教程目录,会帮助很大. 上图就是github的提交表格,直观来看可以分为几个部分进行绘制: (1)各个月份的小方格子,并且色彩根据提交次数变化,由浅到深 (2)右下边的颜色标志,我们右对齐就可以了 (3)左边的星期,原图是从周日画到周六,我们从周一画到周日 (4)上面的月份,我们只画出1-12月 (5)点击时候弹出当天的提交情况,由一个小三角和

  • Android自定义View实现仿1号店垂直滚动广告条代码

    效果图展示,图片有点卡,耐心看会,原程序是很流畅的 实现步骤: 声明变量 初始化画笔.文本大小和坐标 onMeasure()适配wrap_content的宽高 onDraw()画出根据坐标画出两段Text 监听点击事件 在Activity中实现点击事件 实现原理(坐标变换原理):整个过程都是基于坐标Y的增加和交换进行处理的,Y值都会一直增加到endY,然后进行交换逻辑 步骤一:声明变量 由于1号店是两句话的滚动,所以我们也是使用两句话来实现的 private Paint mPaint; priv

  • Android自定义view实现仿抖音点赞效果

    前言 学习自定义view,想找点东西耍一下,刚好看到抖音的点赞效果不错,尝试一下. 抖音效果: 话不多说,先上代码: public class Love extends RelativeLayout { private Context mContext; float[] num = {-30, -20, 0, 20, 30};//随机心形图片角度 public Love(Context context) { super(context); initView(context); } public

  • Android自定义view之仿支付宝芝麻信用仪表盘示例

    自定义view练习 仿支付宝芝麻信用的仪表盘 对比图: 首先是自定义一些属性,可自己再添加,挺基础的,上代码 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="RoundIndicatorView"> <!--最大数值--> <attr name="maxNum" form

  • android自定义view之模拟qq消息拖拽删除效果

    这个模拟功能的实现主要依靠了PATH和二阶贝塞尔曲线.首先上一张图来简单看一下: 这个模拟功能有以下几个特点: 在开始的时候点击圆以外的区域不会触发拖动事件 点击圆的时候可以拖拽,此时会有一个拉伸效果,连接大圆和小圆 拉伸到一定距离(自己设定)以后两个圆会断开,此时即使再拖拽进距离之内的时候也不会再产生已经断开的连接 在距离之内松手的时候会回弹会原位置,并伴有一个弹跳动画 介绍了这么多,看过我前边文章的朋友应该会有一个基本思路. 暴露接口 这个模拟功能共分为三部分,一个是那个小圆,固定的位置,一

  • Android自定义View实现仿网易音乐唱片播放效果

    本文实例为大家分享了Android实现仿网易音乐唱片播放效果的具体代码,供大家参考,具体内容如下 效果图: 在values中创建attrs.xml文件 <?xml version="1.0" encoding="utf-8"?> <resources> <declare-styleable name="GramophoneView"> <attr name="picture_radiu"

  • Android自定义View 仿QQ侧滑菜单的实现代码

    先看看QQ的侧滑效果 分析一下 先上原理图(不知道能否表达的清楚 ==) -首先这里使用了 Android 的HorizontalScrollView 水平滑动布局作为容器,当然我们需要继承它自定义一个侧滑视图 - 这个容器里面有一个父布局(一般用LinerLayout,本demo用的是),这个父布局里面有且只有两个子控件(布局),初始状态菜单页的位置在Y轴上存在偏移这样可以就可以形成主页叠在菜单页的上方的视觉效果:然后在滑动的过程程中 逐渐修正偏移,最后菜单页和主页并排排列.原理搞清了实现起来

  • Android自定义View仿IOS圆盘时间选择器

    通过自定义view实现仿iOS实现滑动两端的点选择时间的效果 效果图 自定义的view代码 public class Ring_Slide2 extends View { private static final double RADIAN = 180 / Math.PI; private int max_progress; // 设置最大进度 private int cur_progress; //设置锚点1当前进度 private int cur_progress2; //设置锚点2进度 p

随机推荐