分享一个最近实现的一个效果,主要是用来显示分数。分数的范围是0~100,没有小数。


我一步一步的分解开,来说说我是怎么实现的。首先来一张动态效果。

设置分数后,分数会从0到目标分数增长,并伴随圆环的动画。

在你阅读此文之前最好先了解自定义View的步骤,比如onMeasure,onLayout,onDraw等等。这类的文章有很多,我这里不再一一赘述了。

准备阶段

一.首先建好Activity,和Activity的布局:

package com.greendami.gdmimport android.app.Activity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*class MainActivity : Activity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)bt_0.setOnClickListener { pp.setProgress("0",0f,true) }bt_100.setOnClickListener { pp.setProgress("100",100f,true) }}
}

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:gravity="center"android:orientation="vertical"android:background="@color/colorPrimary"><LinearLayout
        android:layout_marginBottom="100dp"android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"android:gravity="center"><Button
            android:id="@+id/bt_0"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="0" /><Button
            android:id="@+id/bt_100"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="100" /></LinearLayout><com.greendami.gdm.PPCircleProgressView
        android:id="@+id/pp"android:layout_width="200dp"android:layout_height="200dp" /></LinearLayout>

然后是一个工具类,主要是用于dp转px:

package com.greendami.gdm;import android.content.Context;/*** Created by hsy on 2016/4/8.*/
public class DPUnitUtil {/*** 将px值转换为dip或dp值,保证尺寸大小不变** @param pxValue (DisplayMetrics类中属性density)* @return*/public static int px2dip(Context context, float pxValue) {final float scale = context.getResources().getDisplayMetrics().density;return (int) (pxValue / scale + 0.5f);}}

二.建一个PPCircleProgressView类:

自定义的View就叫做PPCircleProgressView。
然后建一个类PPCircleProgressView,继承View类。

package com.greendami.gdm;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;/*** 圆形进度条* Created by GreendaMi on 2017/3/1.*/public class PPCircleProgressView extends View {private float progress = 0; //显示的进度private String strprogress = "100"; //显示的进度private int mLayoutSize = 100;//整个控件的尺寸(方形)public int mColor;//主要颜色public int mColorBackground;Context mContext;private float now = 0; //当前的进度public PPCircleProgressView(Context context) {super(context);mContext = context;}public PPCircleProgressView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;mColor = context.getResources().getColor(R.color.yellow);mColorBackground = context.getResources().getColor(R.color.colorPrimary);}public PPCircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mContext = context;}}

因为我只用在主xml中,所以要实现带2个参数的构造方法,在这个方法中取了2个颜色。一般的做法是取style文件,但是我偷懒一下,直接取的color文件中的颜色。

到此准备工作结束。

实现阶段

三.测量宽高

这是一个方形的View,我偷懒,就把方形定死了,直接在xml给定dp值,设定宽高。

    @Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);mLayoutSize = Math.min(widthSpecSize, heightSpecSize);if (mLayoutSize == 0) {mLayoutSize = Math.max(widthSpecSize, heightSpecSize);}setMeasuredDimension(mLayoutSize, mLayoutSize);}

三.绘画

在onDraw方法中绘画。

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(0x66d4b801);paint.setStyle(Paint.Style.FILL); //设置空心}

首先是初始化画笔,当然我知道这直接初始化不太好,一般都是在某处初始化一次,然后调用paint的reset()方法重置。

第一笔就是最外面的一个圆线,颜色是半透明的黄

@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(0x66d4b801);//透明的黄色paint.setStyle(Paint.Style.FILL); //设置填充//画半透明黄线int centre = getWidth() / 2; //获取圆心的x坐标float radius = centre * 0.96f; //圆环的半径canvas.drawCircle(centre, centre, radius, paint); //画出圆环....}

效果是这样的:

这个圆的半径是宽的一半,但是由于那个小水滴的底部会在这个圆的外侧,所以这个圆不可以占满整个View,所以 centre * 0.96f,缩小了这个半径。

接着再画一个红色的圆,把半径减小1,画在上面那个圆的中心,这样就是一个圆线了。

//接上面在onDraw中
paint.setColor(mColorBackground);
canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 1f), paint); //画出圆环

效果是这样的:

接着画中间一段一段的感觉的部分,这里有一种被‘点亮’的感觉。背景‘未点亮’的是半透明的黄色,点亮就是正常的黄色。‘未点亮’是一个360度的扇形,‘点亮’的是角度会变化的扇形。先画‘未点亮’的部分。

float gap = DPUnitUtil.dip2px(mContext, 14);
RectF rectF = new RectF(gap, gap, mLayoutSize - gap, mLayoutSize - gap);//找出扇形所在的矩形,距离View的边框上下左右各缩14dp。paint.setColor(0x44d4b801);
canvas.drawArc(rectF, 0, 360, true, paint);

效果如下:

其实上面的效果可以画圆而不是扇形。
下一步,画点亮的部分。这里的每一段是15°,所以有些数值需要四舍五入。

//15度一个格子,防止占半个格子
int endR = (int) (360 * (now / 100) / 15) * 15;
paint.setColor(mColor);
canvas.drawArc(rectF, -90, endR, true, paint);

endR是根据当前显示的分数计算的扇形的结束角度,这里会根据15°进行四舍五入。开始角度是-90°,扇形是从12点钟方向开始。这里的now就是当前的分数,因为是有动画的,所以now的值会变化,具体是如何变化的后面说,这里只是根据now值画扇形。

接着用一个实心的红色圆把这个扇形的内部‘盖住’。这个圆的半径再一次的缩小。这里乘了一个0.83

//画红圆
paint.setColor(mColorBackground);        paint.setStyle(Paint.Style.FILL); //设置空心
radius = radius * 0.83f; //圆环的半径
canvas.drawCircle(centre, centre, radius, paint); //画出圆环

然后就是把这个比较宽的圆环切成一段一段的。形成‘断开’的感觉。我用的就是比较宽的红线,每旋转15°画一条。

        paint.setStrokeWidth(DPUnitUtil.dip2px(mContext, 2));for (int r = 0; r < 360; r = r + 15) {canvas.drawLine(centre + (float) ((centre - gap) * Math.sin(Math.toRadians(r))),centre - (float) ((centre - gap) * Math.cos(Math.toRadians(r))),centre - (float) ((centre - gap) * Math.sin(Math.toRadians(r))),centre + (float) ((centre - gap) * Math.cos(Math.toRadians(r))), paint);}

为了方便看,我把线设置成的白色,是这样的:

实际设成红色,是这样的:

然后画内圈的一个浅浅的圆环,这里的方法和外圈的画法一样:

        paint.setColor(0x44d4b801);canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 2f), paint); //画出圆环paint.setColor(mColorBackground);canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 2.5f), paint); //画出圆环

效果如下,好像不太明显:

接下来是画上面的文字,如果文字是空白的,会画两条横线:

//到此,背景绘制完毕String per = (int) now + "";//写百分比if ("".equals(strprogress)) {paint.setColor(mColor);paint.setStrokeWidth(DPUnitUtil.dip2px(mContext, 2));canvas.drawLine(centre * 0.77f, centre, centre * 0.95f, centre, paint);canvas.drawLine(centre * 1.05f, centre, centre * 1.23f, centre, paint);} else {paint.setColor(mColor);paint.setTextSize(mLayoutSize / 4f);//控制文字大小Paint paint2 = new Paint();paint2.setAntiAlias(true);paint2.setTextSize(mLayoutSize / 12);//控制文字大小paint2.setColor(mColor);canvas.drawText(per,centre - 0.5f * (paint.measureText(per)),centre - 0.5f * (paint.ascent() + paint.descent()),paint);canvas.drawText("分",centre + 0.5f * (paint.measureText((int) now + "") + paint2.measureText("分")),centre - 0.05f * (paint.ascent() + paint.descent()),paint2);}

文字的大小会根据控件的尺寸进行计算。然后就是随便测量了一下文字的长度,计算文字的位置。上面这些数字后是目测随便写的。数字是根据now来变化的。

接下来画最外面的小水滴,小水滴的位置是和扇形的endR一致。
先画一个小球:

        centre = getWidth() / 2;canvas.drawCircle(centre + (float) ((centre * 0.95f) * Math.sin(Math.toRadians(endR))),centre - (float) ((centre * 0.95f) * Math.cos(Math.toRadians(endR))), centre * 0.04f, paint);

小球的圆心是根据endR和最外面的圆环的半径计算的,小球的半径就是在外面圆环到View边的距离(1-0.96)。

接下来时画尖尖的角。我们需要一个Path。这个角的顶点在小球的圆心和View圆心的连线上,角度是endR。另外两个点是角的顶点与小球的切点,这个就比较难了。因为这个水滴比较小,所以其实这两个点不用十分精确,我把endR分别向左右移动2.5°,然后半径从centre * 0.95f稍稍减小了一点到centre * 0.94f,差不多找到了‘切点’的位置。

        Path p = new Path();p.moveTo(centre + (float) ((centre * 0.86f) * Math.sin(Math.toRadians(endR))),centre - (float) ((centre * 0.86f) * Math.cos(Math.toRadians(endR))));//顶点p.lineTo(centre + (float) ((centre * 0.94f) * Math.sin(Math.toRadians(endR + 2.5))),centre - (float) ((centre * 0.94f) * Math.cos(Math.toRadians(endR + 2.5))));p.lineTo(centre + (float) ((centre * 0.94f) * Math.sin(Math.toRadians(endR - 2.5))),centre - (float) ((centre * 0.94f) * Math.cos(Math.toRadians(endR - 2.5))));p.close();canvas.drawPath(p, paint);

效果如下:

最后动起来:

if (now < progress - 1) {now = now + 1;postInvalidate();} else if (now < progress) {now = (int) progress;postInvalidate();}

now是当前动画的显示数值,progress是最终的显示数值,如果now < progress - 1则调用postInvalidate()重绘。带刺onDraw方法结束。

最后加上外部的调用设设值:

/*** 外部回调** @param strprogress 显示调进度文字,如果是"",或者null了,则显示两条横线* @param progress    进度条调进度* @param isAnim      进度条是否需要动画*/public void setProgress(String strprogress, float progress, boolean isAnim) {if (strprogress == null) {this.strprogress = "";} else {this.strprogress = strprogress;}this.now = 0;this.progress = progress;if (!isAnim) {now = progress;}postInvalidate();}

完整代码

package com.greendami.gdm;import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;/*** 圆形进度条* Created by GreendaMi on 2017/3/1.*/public class PPCircleProgressView extends View {private float progress = 0; //显示的进度private String strprogress = "100"; //显示的进度private int mLayoutSize = 100;//整个控件的尺寸(方形)public int mColor;//主要颜色public int mColorBackground;Context mContext;private float now = 0; //当前的进度public PPCircleProgressView(Context context) {super(context);mContext = context;}public PPCircleProgressView(Context context, AttributeSet attrs) {super(context, attrs);mContext = context;mColor = context.getResources().getColor(R.color.yellow);mColorBackground = context.getResources().getColor(R.color.colorPrimary);}public PPCircleProgressView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);mContext = context;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);mLayoutSize = Math.min(widthSpecSize, heightSpecSize);if (mLayoutSize == 0) {mLayoutSize = Math.max(widthSpecSize, heightSpecSize);}setMeasuredDimension(mLayoutSize, mLayoutSize);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Paint paint = new Paint();paint.setAntiAlias(true);paint.setColor(0x66d4b801);paint.setStyle(Paint.Style.FILL); //设置空心//画灰线int centre = getWidth() / 2; //获取圆心的x坐标float radius = centre * 0.96f; //圆环的半径canvas.drawCircle(centre, centre, radius, paint); //画出圆环paint.setColor(mColorBackground);canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 1f), paint); //画出圆环float gap = DPUnitUtil.dip2px(mContext, 14);RectF rectF = new RectF(gap, gap, mLayoutSize - gap, mLayoutSize - gap);//15度一个格子,防止占半个格子int endR = (int) (360 * (now / 100) / 15) * 15;paint.setColor(0x44d4b801);canvas.drawArc(rectF, 0, 360, true, paint);paint.setColor(mColor);canvas.drawArc(rectF, -90, endR, true, paint);//画红圆paint.setColor(mColorBackground);paint.setStyle(Paint.Style.FILL); //设置空心radius = radius * 0.83f; //圆环的半径canvas.drawCircle(centre, centre, radius, paint); //画出圆环//画线,许多的线,15度画一条,线的宽度是2dppaint.setStrokeWidth(DPUnitUtil.dip2px(mContext, 2));for (int r = 0; r < 360; r = r + 15) {canvas.drawLine(centre + (float) ((centre - gap) * Math.sin(Math.toRadians(r))),centre - (float) ((centre - gap) * Math.cos(Math.toRadians(r))),centre - (float) ((centre - gap) * Math.sin(Math.toRadians(r))),centre + (float) ((centre - gap) * Math.cos(Math.toRadians(r))), paint);}paint.setColor(0x44d4b801);canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 2f), paint); //画出圆环paint.setColor(mColorBackground);canvas.drawCircle(centre, centre, radius - DPUnitUtil.dip2px(mContext, 2.5f), paint); //画出圆环//到此,背景绘制完毕String per = (int) now + "";//写百分比if ("".equals(strprogress)) {paint.setColor(mColor);paint.setStrokeWidth(DPUnitUtil.dip2px(mContext, 2));canvas.drawLine(centre * 0.77f, centre, centre * 0.95f, centre, paint);canvas.drawLine(centre * 1.05f, centre, centre * 1.23f, centre, paint);} else {paint.setColor(mColor);paint.setTextSize(mLayoutSize / 4f);//控制文字大小Paint paint2 = new Paint();paint2.setAntiAlias(true);paint2.setTextSize(mLayoutSize / 12);//控制文字大小paint2.setColor(mColor);canvas.drawText(per,centre - 0.5f * (paint.measureText(per)),centre - 0.5f * (paint.ascent() + paint.descent()),paint);canvas.drawText("分",centre + 0.5f * (paint.measureText((int) now + "") + paint2.measureText("分")),centre - 0.05f * (paint.ascent() + paint.descent()),paint2);}//外部小球centre = getWidth() / 2;canvas.drawCircle(centre + (float) ((centre * 0.95f) * Math.sin(Math.toRadians(endR))),centre - (float) ((centre * 0.95f) * Math.cos(Math.toRadians(endR))), centre * 0.04f, paint);Path p = new Path();p.moveTo(centre + (float) ((centre * 0.86f) * Math.sin(Math.toRadians(endR))),centre - (float) ((centre * 0.86f) * Math.cos(Math.toRadians(endR))));p.lineTo(centre + (float) ((centre * 0.94f) * Math.sin(Math.toRadians(endR + 2.5))),centre - (float) ((centre * 0.94f) * Math.cos(Math.toRadians(endR + 2.5))));p.lineTo(centre + (float) ((centre * 0.94f) * Math.sin(Math.toRadians(endR - 2.5))),centre - (float) ((centre * 0.94f) * Math.cos(Math.toRadians(endR - 2.5))));p.close();canvas.drawPath(p, paint);if (now < progress - 1) {now = now + 1;postInvalidate();} else if (now < progress) {now = (int) progress;postInvalidate();}}/*** 外部回调** @param strprogress 显示调进度文字,如果是"",或者null了,则显示两条横线* @param progress    进度条调进度* @param isAnim      进度条是否需要动画*/public void setProgress(String strprogress, float progress, boolean isAnim) {if (strprogress == null) {this.strprogress = "";} else {this.strprogress = strprogress;}this.now = 0;this.progress = progress;if (!isAnim) {now = progress;}postInvalidate();}}

Android鬼点子-自定义View就像PS相关推荐

  1. Android 中自定义View 裁剪扇形图片

    Android 中自定义View 裁剪扇形图片 当需要裁剪图片为扇形区域时,使用Canvas.clipPath(path)方法可以裁剪为扇形区域 ps:此方法会导致绘制图片边缘有锯齿,暂无解决方法(知 ...

  2. android 动态画直线,Android使用自定义view在指定时间内匀速画一条直线的实例代码...

    本文讲述了Android使用自定义view在指定时间内匀速画一条直线的实例代码.分享给大家供大家参考,具体如下: 1.效果图: 2.自定义view实现 public class UniformLine ...

  3. Android 系统(264)---android进阶——自定义View

    android进阶--自定义View 软件架构 01.自定义View简介 - onMeasure,onDraw,自定义属性  https://www.jianshu.com/p/48944aad200 ...

  4. android 动态生成直线,Android使用自定义view在指定时间内匀速画一条直线的实例代码...

    本文讲述了Android使用自定义view在指定时间内匀速画一条直线的实例代码.分享给大家供大家参考,具体如下: 1.效果图: 2.自定义view实现 public class UniformLine ...

  5. android五子棋编程教程全集,android简单自定义View实现五子棋

    本文实例为大家分享了android自定义View实现五子棋的具体代码,供大家参考,具体内容如下 先说一下吧,android的自定义View就是自己实现一个类去继承View,实现其中的方法,这里面我最感 ...

  6. 【Android】自定义View、画家(画布)Canvas与画笔Paint的应用——画图、涂鸦板app的实现

    利用一个简单的画图app来说明安卓的图形处理类与自定义View的应用. 如下图,有一个供用户自己任意画图.涂鸦的app, 这里不做那么花俏了,仅提供黑白两色,但可以改变笔尖的粗细. 实质上这里的橡皮擦 ...

  7. android开发自定义View(三)仿芝麻信用积分

    此文参考了https://github.com/HotBitmapGG/CreditSesameRingView 感谢作者的分享!! 首先看一下支付宝上显示的样子 然后看一下模仿的效果 代码 基础部分 ...

  8. Android之自定义view引用xml,Android自定义View在XML中映射错误

    Android开发中我们经常会遇到自定义View地址映射错误的情况,现将遇到的情况做下总结: //Android Studio的异常信息 Error inflating class 1.直接像下面这样 ...

  9. android面试自定义view,资深面试官:自定义View的实现方式,你知道几种?

    前提 为什么要自定义View? 怎么自定义View? 当 Android SDK 中提供的系统 UI 控件无法满足业务需求时,我们就需要考虑自己实现 UI 控件.而且自定义View在面试时是很经典的一 ...

最新文章

  1. 逆向思维--魔兽世界封包分析(1)
  2. FreeOTP可以用作谷歌认证的替代
  3. 典型用户描述及进一步需求分析
  4. Python---编写一函数 Fabonacci(n),其中参数 n 代表第 n 次的迭代。
  5. cs架构用什么语言开发_我为什么建议Python开发者将ES6作为第二语言
  6. 了解 XML 架构(XML与OO)
  7. adb查看某个文件是否存在_linux实现检查文件夹是否存在不存在则创建
  8. 链接mysql_JavaScript学习笔记(二十四)-- MYSQL基础操作
  9. 路由算法之距离矢量算法和链路状态算法
  10. PR视频剪辑软件教程
  11. Playmaker节点工具使用(二)—Odin绘制支持
  12. React项目本地环境正常显示,打包部署服务器图片不显示问题
  13. matlab 模拟滤波器转换为数字滤波器,模拟低通滤波器转换为数字高通滤波器.doc...
  14. mysql创建制度账户_Mysql数据库用户管理
  15. 程序员外包工作3年,跳槽却没人要,网友:你也太老实了吧
  16. 写个厦门市健身徒步爬山线路的web静态页面
  17. nsfw什么颜色_“ NSFW”是什么意思,以及如何使用它?
  18. 基于MFC的桌面时钟应用程序
  19. [1159]adb判断手机屏幕状态并点亮屏幕
  20. 几种常见web 容器比较 (tomcat、 jboss 、resin、 weblogic、 websphere、 glassfish)

热门文章

  1. 代码编辑器语法着色功能实现-Java版
  2. vue 项目实现水印效果
  3. “史上最贵”卡塔尔世界杯,有哪些炫酷的“黑科技”?
  4. 竞价推广方案怎么写,这些点你get到了吗?
  5. 什么?内存不够了?进来教你malloc空间
  6. Ubuntu安装VMware Tools后不起作用
  7. postgresql 使用odbc_fdw连接 sqlserver
  8. 大数据、云计算、区块链、人工智能!你选择哪个?
  9. 使用html2canvas和jspdf把网页保存pdf并下载
  10. ora-07445 oracle 9,遇到ORA-07445错误