Android鬼点子-自定义View就像PS
来源:互联网 发布:php实例教程 编辑:程序博客网 时间:2024/05/22 10:52
分享一个最近实现的一个效果,主要是用来显示分数。分数的范围是0~100,没有小数。
我一步一步的分解开,来说说我是怎么实现的。首先来一张动态效果。
设置分数后,分数会从0到目标分数增长,并伴随圆环的动画。
在你阅读此文之前最好先了解自定义View的步骤,比如onMeasure,onLayout,onDraw等等。这类的文章有很多,我这里不再一一赘述了。
准备阶段
一.首先建好Activity,和Activity的布局:
package com.greendami.gdmimport android.app.Activityimport android.os.Bundleimport 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值,设定宽高。
@Override protected 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方法中绘画。
@Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); Paint paint = new Paint(); paint.setAntiAlias(true); paint.setColor(0x66d4b801); paint.setStyle(Paint.Style.FILL); //设置空心 }
首先是初始化画笔,当然我知道这直接初始化不太好,一般都是在某处初始化一次,然后调用paint的reset()方法重置。
第一笔就是最外面的一个圆线,颜色是半透明的黄
@Override protected 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; } @Override protected 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); } @Override protected 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度画一条,线的宽度是2dp 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); } //外部小球 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
- Android鬼点子-代码混淆
- 自定义View android 像支付宝支付界面的progress
- 一看就会Android之开发自定义的View
- 自定义View-仿微信群组头像
- Android View---自定义View
- Android View---自定义View
- android中使用自定义View让图片像画卷一样被展开显示
- Android 自定义View 之 自定义View属性
- 【自定义View系列】android自定义View概述
- Android自定义view自定义属性
- Android自定义控件 -- 自定义View
- android自定义view(自定义数字键盘)
- Android自定义View-自定义属性
- Android自定义View-自定义属性
- Android 自定义View
- Android 自定义 View
- android自定义View
- c++继承(子类调用父类函数、全局函数)
- Shiro学习参考
- dash激活
- *.问题记录集1
- 机器学习笔记(九)隐马尔可夫模型
- Android鬼点子-自定义View就像PS
- 全面的购物车
- TCP协议与UDP协议的区别
- Delphi整理四(程序控制结构)
- mybatis中注册映射sql的Mapper的三种方式详解
- Redis 入门
- Android开发引导页进入应用,第二次欢迎页进入应用
- python函数
- Dialog