Android自定义View实现圆弧进度效果

前言:Android开发中,自定义View实现自己想要的效果已成为一项必备的技能,当然自定义View也是Android开发中比较难的部分,涉及到的知识有Canvas(画布),Paint(画笔)等,自定义控件分为三种:一是直接继承自View,完全的自定义;二是在原有控件的基础上进行改造,达到自己想要的效果;还有一种就是自定义组合控件,将已有的控件根据自己的需要进行组合实现的效果。本人对自定义View也是一知半解,简单记录下自己学习自定义View(继承自View)的过程,方便日后翻阅。

技术实现

1.ArcView继承自View

2.Canvas(画布)

3.Paint(画笔)

效果图:类似于QQ的计步效果

1.继承自View

(1)重写3个构造方法(新的API中的构造方法是4个)

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

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

public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
  super(context, attrs, defStyleAttr);
  //init();
}

(2)重写View的OnDraw方法

@SuppressLint("DrawAllocation")
@Override
protected void onDraw(Canvas canvas) {
  super.onDraw(canvas);
  centerX=getWidth()/2;
  centerY=getHeight()/2;
  //初始化paint
  initPaint();
  //绘制弧度
  drawArc(canvas);
  //绘制文本
  drawText(canvas);
}

注:这里的paint初始化我放在了onDraw方法中进行的,当然你也可以放在有三个参数的构造方法中初始化。

2.Paint初始化

(1)圆弧的画笔mArcPaint

//圆弧的paint
mArcPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
//抗锯齿
mArcPaint.setAntiAlias(true);
mArcPaint.setColor(Color.parseColor("#666666"));
//设置透明度(数值为0-255)
mArcPaint.setAlpha(100);
//设置画笔的画出的形状
mArcPaint.setStrokeJoin(Paint.Join.ROUND);
mArcPaint.setStrokeCap(Paint.Cap.ROUND);
//设置画笔类型
mArcPaint.setStyle(Paint.Style.STROKE);
mArcPaint.setStrokeWidth(dp2px(mStrokeWith));

(2)文字的画笔mTextPaint

//中心文字的paint
mTextPaint=new Paint();
mTextPaint.setAntiAlias(true);
mTextPaint.setColor(Color.parseColor("#FF4A40"));
//设置文本的对齐方式
mTextPaint.setTextAlign(Paint.Align.CENTER);
//mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12));
mTextPaint.setTextSize(dp2px(25));

3.Canvas绘制

(1)圆弧的绘制

/**
 * 绘制圆弧
 * @param canvas
 */
private void drawArc(Canvas canvas) {
  //绘制圆弧背景
  RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
  canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
  //绘制当前数值对应的圆弧
  mArcPaint.setColor(Color.parseColor("#FF4A40"));
  //根据当前数据绘制对应的圆弧
  canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint);
}

(2)文本的绘制

/**
 * 绘制文本
 * @param canvas
 */
private void drawText(Canvas canvas) {
  Rect mRect=new Rect();
  String mValue=String.valueOf(mAnimatorValue);
  //绘制中心的数值
  mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
  canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);

  //绘制中心文字描述
  mTextPaint.setColor(Color.parseColor("#999999"));
  mTextPaint.setTextSize(dp2px(12));
  mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
  canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);

  //绘制最小值
  String minValue=String.valueOf(mMinValue);
  String maxValue=String.valueOf(mMaxValue);
  mTextPaint.setTextSize(dp2px(18));
  mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
  canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
  //绘制最大值
  mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
  canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
}

4.添加动画效果及数据

(1)动画效果

/**
 * 为绘制弧度及数据设置动画
 *
 * @param startAngle 开始的弧度
 * @param currentAngle 需要绘制的弧度
 * @param currentValue 需要绘制的数据
 * @param time 动画执行的时长
 */
private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
  //绘制当前数据对应的圆弧的动画效果
  ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
  progressAnimator.setDuration(time);
  progressAnimator.setTarget(mIncludedAngle);
  progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
      mIncludedAngle = (float) animation.getAnimatedValue();
      //重新绘制,不然不会出现效果
      postInvalidate();
    }
  });
  //开始执行动画
  progressAnimator.start();

  //中心数据的动画效果
  ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
  valueAnimator.setDuration(2500);
  valueAnimator.setInterpolator(new LinearInterpolator());
  valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

    @Override
    public void onAnimationUpdate(ValueAnimator valueAnimator) {

      mAnimatorValue = (int) valueAnimator.getAnimatedValue();
      postInvalidate();
    }
  });
  valueAnimator.start();
}

(2)数据添加

/**
 * 设置数据
 * @param minValue 最小值
 * @param maxValue 最大值
 * @param currentValue 当前绘制的值
 * @param des 描述信息
 */
public void setValues(int minValue,int maxValue, int currentValue,String des) {
  mDes=des;
  mMaxValue=maxValue;
  mMinValue=minValue;
  //完全覆盖背景弧度
  if (currentValue > maxValue) {
    currentValue = maxValue;
  }
  //计算弧度比重
  float scale = (float) currentValue / maxValue;
  //计算弧度
  float currentAngle = scale * mAngle;
  //开始执行动画
  setAnimation(0, currentAngle, currentValue,2500);

完整代码:

/**
 * Created by ruancw on 2018/6/13.
 * 自定义的圆弧形view
 */

public class ArcView extends View {

  //根据数据显示的圆弧Paint
  private Paint mArcPaint;
  //文字描述的paint
  private Paint mTextPaint;
  //圆弧开始的角度
  private float startAngle=135;
  //圆弧结束的角度
  private float endAngle=45;
  //圆弧背景的开始和结束间的夹角大小
  private float mAngle=270;
  //当前进度夹角大小
  private float mIncludedAngle=0;
  //圆弧的画笔的宽度
  private float mStrokeWith=10;
  //中心的文字描述
  private String mDes="";
  //动画效果的数据及最大/小值
  private int mAnimatorValue,mMinValue,mMaxValue;
  //中心点的XY坐标
  private float centerX,centerY;

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

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

  public ArcView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //init();
  }

  private void initPaint() {
    //圆弧的paint
    mArcPaint=new Paint(Paint.ANTI_ALIAS_FLAG);
    //抗锯齿
    mArcPaint.setAntiAlias(true);
    mArcPaint.setColor(Color.parseColor("#666666"));
    //设置透明度(数值为0-255)
    mArcPaint.setAlpha(100);
    //设置画笔的画出的形状
    mArcPaint.setStrokeJoin(Paint.Join.ROUND);
    mArcPaint.setStrokeCap(Paint.Cap.ROUND);
    //设置画笔类型
    mArcPaint.setStyle(Paint.Style.STROKE);
    mArcPaint.setStrokeWidth(dp2px(mStrokeWith));

    //中心文字的paint
    mTextPaint=new Paint();
    mTextPaint.setAntiAlias(true);
    mTextPaint.setColor(Color.parseColor("#FF4A40"));
    //设置文本的对齐方式
    mTextPaint.setTextAlign(Paint.Align.CENTER);
    //mTextPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.dp_12));
    mTextPaint.setTextSize(dp2px(25));

  }

  @SuppressLint("DrawAllocation")
  @Override
  protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    centerX=getWidth()/2;
    centerY=getHeight()/2;
    //初始化paint
    initPaint();
    //绘制弧度
    drawArc(canvas);
    //绘制文本
    drawText(canvas);
  }

  /**
   * 绘制文本
   * @param canvas
   */
  private void drawText(Canvas canvas) {
    Rect mRect=new Rect();
    String mValue=String.valueOf(mAnimatorValue);
    //绘制中心的数值
    mTextPaint.getTextBounds(mValue,0,mValue.length(),mRect);
    canvas.drawText(String.valueOf(mAnimatorValue),centerX,centerY+mRect.height(),mTextPaint);

    //绘制中心文字描述
    mTextPaint.setColor(Color.parseColor("#999999"));
    mTextPaint.setTextSize(dp2px(12));
    mTextPaint.getTextBounds(mDes,0,mDes.length(),mRect);
    canvas.drawText(mDes,centerX,centerY+2*mRect.height()+dp2px(10),mTextPaint);

    //绘制最小值
    String minValue=String.valueOf(mMinValue);
    String maxValue=String.valueOf(mMaxValue);
    mTextPaint.setTextSize(dp2px(18));
    mTextPaint.getTextBounds(minValue,0,minValue.length(),mRect);
    canvas.drawText(minValue, (float) (centerX-0.6*centerX-dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
    //绘制最大指
    mTextPaint.getTextBounds(maxValue,0,maxValue.length(),mRect);
    canvas.drawText(maxValue, (float) (centerX+0.6*centerX+dp2px(5)), (float) (centerY+0.75*centerY+mRect.height()+dp2px(5)),mTextPaint);
  }

  /**
   * 绘制当前的圆弧
   * @param canvas
   */
  private void drawArc(Canvas canvas) {
    //绘制圆弧背景
    RectF mRectF=new RectF(mStrokeWith+dp2px(5),mStrokeWith+dp2px(5),getWidth()-mStrokeWith-dp2px(5),getHeight()-mStrokeWith);
    canvas.drawArc(mRectF,startAngle,mAngle,false,mArcPaint);
    //绘制当前数值对应的圆弧
    mArcPaint.setColor(Color.parseColor("#FF4A40"));
    //根据当前数据绘制对应的圆弧
    canvas.drawArc(mRectF,startAngle,mIncludedAngle,false,mArcPaint);
  }

  /**
   * 为绘制弧度及数据设置动画
   *
   * @param startAngle 开始的弧度
   * @param currentAngle 需要绘制的弧度
   * @param currentValue 需要绘制的数据
   * @param time 动画执行的时长
   */
  private void setAnimation(float startAngle, float currentAngle,int currentValue, int time) {
    //绘制当前数据对应的圆弧的动画效果
    ValueAnimator progressAnimator = ValueAnimator.ofFloat(startAngle, currentAngle);
    progressAnimator.setDuration(time);
    progressAnimator.setTarget(mIncludedAngle);
    progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
      @Override
      public void onAnimationUpdate(ValueAnimator animation) {
        mIncludedAngle = (float) animation.getAnimatedValue();
        //重新绘制,不然不会出现效果
        postInvalidate();
      }
    });
    //开始执行动画
    progressAnimator.start();

    //中心数据的动画效果
    ValueAnimator valueAnimator = ValueAnimator.ofInt(mAnimatorValue, currentValue);
    valueAnimator.setDuration(2500);
    valueAnimator.setInterpolator(new LinearInterpolator());
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

      @Override
      public void onAnimationUpdate(ValueAnimator valueAnimator) {

        mAnimatorValue = (int) valueAnimator.getAnimatedValue();
        postInvalidate();
      }
    });
    valueAnimator.start();
  }

  /**
   * 设置数据
   * @param minValue 最小值
   * @param maxValue 最大值
   * @param currentValue 当前绘制的值
   * @param des 描述信息
   */
  public void setValues(int minValue,int maxValue, int currentValue,String des) {
    mDes=des;
    mMaxValue=maxValue;
    mMinValue=minValue;
    //完全覆盖
    if (currentValue > maxValue) {
      currentValue = maxValue;
    }
    //计算弧度比重
    float scale = (float) currentValue / maxValue;
    //计算弧度
    float currentAngle = scale * mAngle;
    //开始执行动画
    setAnimation(0, currentAngle, currentValue,2500);
  }

  public float dp2px(float dp) {
    DisplayMetrics metrics = Resources.getSystem().getDisplayMetrics();
    return dp * metrics.density;
  }
}

总结:设置Paint的画笔形状(Cap和Join设置为弧形);使用Canvas的drawArc方法绘制圆弧及drawText绘制文本信息等;ValueAnimator设置数据及当前圆弧进度的动画效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • Android自定义WaveView实现波浪进度效果

    实现原理 首先就是自定义个WaveView 继承View,然后再WaveView 内部实现代码逻辑: ① 水波就波嘛? sin函数? 贝塞尔曲线? 都行,这里就用二阶贝塞 尔曲线去画吧 ② 波要动嘛,怎么动呢?线程? 好吧 这里用了个Handler. ③绘制波首先要找点,那么在onMeasure()里找出需要的点咯,这里就暂时展示一个波段吧,一个波长移动左边不就没了?OK 那就两个波吧,吼吼,两个波(猥琐男潜质表露无遗啊).接下来就是Handler 结合 onDraw()绘制.OK,那就先看我W

  • Android Webview添加网页加载进度条实例详解

    推荐阅读:Android WebView线性进度条实例详解 最近在android项目中使用webview嵌套了一个抽奖活动网页,活动上线,运行良好(改了N次需求和突发bug),还好这种模式的活动,只需要修改网页,不需要重新打包发布市场,这也是这种模式开发的优势之一.后来据产品哥反馈说加载网页无进度提示,好吧,这个当时真没考虑这么多,这个要加加..想当然以为轻松搞定之....其实还是比轻松要复杂点... 1.首先自定义一个WebView控件 /** * 带进度条的Webivew * @author

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

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

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

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

  • Android自定义view实现水波纹进度球效果

    今天我们要实现的这个view没有太多交互性的view,所以就继承view. 自定义view的套路,套路很深 1.获取我们自定义属性attrs(可省略) 2.重写onMeasure方法,计算控件的宽和高 3.重写onDraw方法,绘制我们的控件 这么看来,自定义view的套路很清晰嘛. 我们看下今天的效果图,其中一个是放慢的效果(时间调的长) 我们按照套路来. 一.自定义属性 <declare-styleable name="WaveProgressView"> <at

  • android ListView和ProgressBar(进度条控件)的使用方法

    ListView控件的使用:ListView控件里面装的是一行一行的数据,一行中可能有多列,选中一行,则该行的几列都被选中,同时可以触发一个事件,这种控件在平时还是用得很多的.使用ListView时主要是要设置一个适配器,适配器主要是用来放置一些数据.使用起来稍微有些复杂,这里用的是android自带的SimpleAdapter,形式如下:android.widget.SimpleAdapter.SimpleAdapter(Context context, List<? extends Map<

  • Android自定义View基础开发之图片加载进度条

    学会了Paint,Canvas的基本用法之后,我们就可以动手开始实践了,先写个简单的图片加载进度条看看. 按照惯例,先看效果图,再决定要不要往下看: 既然看到这里了,应该是想了解这个图片加载进度条了,我们先看具体用法,再看自定义View的实现: <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:custom="http://schemas.android.co

  • android自定义进度条渐变色View的实例代码

    最近在公司,项目不是很忙了,偶尔看见一个兄台在CSDN求助,帮忙要一个自定义的渐变色进度条,我当时看了一下进度条,感觉挺漂亮的,就尝试的去自定义view实现了一个,废话不说,先上图吧! 这个自定义的view,完全脱离了android自带的ProgressView,并且没使用一张图片,这样就能更好的降低程序代码上的耦合性! 下面我贴出代码  ,大概讲解一下实现思路吧! 复制代码 代码如下: package com.spring.progressview; import android.conten

  • Android中实现Webview顶部带进度条的方法

    写这篇文章,做份备忘,简单滴展示一个带进度条的Webview示例,进度条位于Webview上面. 示例图如下: 主Activity代码: 复制代码 代码如下: package com.droidyue.demo.webviewprogressbar; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.View; import android.vi

  • Android自定义View实现带数字的进度条实例代码

    第一步.效果展示 图1.蓝色的进度条 图2.红色的进度条 图3.多条颜色不同的进度条 图4.多条颜色不同的进度条 第二步.自定义ProgressBar实现带数字的进度条 0.项目结构 如上图所示:library项目为自定义的带数字的进度条NumberProgressBar的具体实现,demo项目为示例项目以工程依赖的方式引用library项目,然后使用自定义的带数字的进度条NumberProgressBar来做展示 如上图所示:自定义的带数字的进度条的library项目的结构图 如上图所示:de

随机推荐