我们要实现的是如下的效果,

1.该view在设置属性之后时候会有数字和圆圈不断增长的效果

2.该view在按下和放开状态下显示不同的样式。

这种效果逻辑上并不复杂,底层灰色圆圈和蓝色扇形圆圈都是用canvas.drawArc()绘制出来的,中间的数字用drawtext绘制,数字不断增长的效果用了继承Animation的动画类;在按下和放开状态下显示不同的样式是重写了View 的setPressed()方法。

先贴出所有代码,再一一解释import com.jcodecraeer.util.MyUtils;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Path;

import android.graphics.Rect;

import android.graphics.RectF;

import android.graphics.Typeface;

import android.graphics.Paint.Align;

import android.graphics.Paint.Style;

import android.graphics.drawable.Drawable;

import android.util.AttributeSet;

import android.util.Log;

import android.view.View;

import android.view.animation.Animation;

import android.view.animation.Transformation;

public class CircleBar extends View {

private RectF mColorWheelRectangle = new RectF();

private Paint mDefaultWheelPaint;

private Paint mColorWheelPaint;

private Paint textPaint;

private float mColorWheelRadius;

private float circleStrokeWidth;

private float pressExtraStrokeWidth;

private String mText;

private int mCount;

private float mSweepAnglePer;

private float mSweepAngle;

private int mTextSize;

BarAnimation anim;

public CircleBar(Context context) {

super(context);

init(null, 0);

}

public CircleBar(Context context, AttributeSet attrs) {

super(context, attrs);

init(attrs, 0);

}

public CircleBar(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

init(attrs, defStyle);

}

private void init(AttributeSet attrs, int defStyle) {

circleStrokeWidth = MyUtils.dip2px(getContext(), 10);

pressExtraStrokeWidth = MyUtils.dip2px(getContext(), 2);

mTextSize = MyUtils.dip2px(getContext(), 40);

mColorWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mColorWheelPaint.setColor(0xFF29a6f6);

mColorWheelPaint.setStyle(Paint.Style.STROKE);

mColorWheelPaint.setStrokeWidth(circleStrokeWidth);

mDefaultWheelPaint = new Paint(Paint.ANTI_ALIAS_FLAG);

mDefaultWheelPaint.setColor(0xFFeeefef);

mDefaultWheelPaint.setStyle(Paint.Style.STROKE);

mDefaultWheelPaint.setStrokeWidth(circleStrokeWidth);

textPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.LINEAR_TEXT_FLAG);

textPaint.setColor(0xFF333333);

textPaint.setStyle(Style.FILL_AND_STROKE);

textPaint.setTextAlign(Align.LEFT);

textPaint.setTextSize(mTextSize);

mText = "0";

mSweepAngle = 0;

anim = new BarAnimation();

anim.setDuration(2000);

}

@Override

protected void onDraw(Canvas canvas) {

canvas.drawArc(mColorWheelRectangle, -90, 360, false, mDefaultWheelPaint);

canvas.drawArc(mColorWheelRectangle, -90, mSweepAnglePer, false, mColorWheelPaint);

Rect bounds = new Rect();

String textstr=mCount+"";

textPaint.getTextBounds(textstr, 0, textstr.length(), bounds);

canvas.drawText(

textstr+"",

(mColorWheelRectangle.centerX())

- (textPaint.measureText(textstr) / 2),

mColorWheelRectangle.centerY() + bounds.height() / 2,

textPaint);

}

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int height = getDefaultSize(getSuggestedMinimumHeight(),

heightMeasureSpec);

int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

int min = Math.min(width, height);

setMeasuredDimension(min, min);

mColorWheelRadius = min - circleStrokeWidth -pressExtraStrokeWidth ;

mColorWheelRectangle.set(circleStrokeWidth+pressExtraStrokeWidth, circleStrokeWidth+pressExtraStrokeWidth,

mColorWheelRadius, mColorWheelRadius);

}

@Override

public void setPressed(boolean pressed) {

Log.i(TAG,"call setPressed ");

if (pressed) {

mColorWheelPaint.setColor(0xFF165da6);

textPaint.setColor(0xFF070707);

mColorWheelPaint.setStrokeWidth(circleStrokeWidth+pressExtraStrokeWidth);

mDefaultWheelPaint.setStrokeWidth(circleStrokeWidth+pressExtraStrokeWidth);

textPaint.setTextSize(mTextSize-pressExtraStrokeWidth);

} else {

mColorWheelPaint.setColor(0xFF29a6f6);

textPaint.setColor(0xFF333333);

mColorWheelPaint.setStrokeWidth(circleStrokeWidth);

mDefaultWheelPaint.setStrokeWidth(circleStrokeWidth);

textPaint.setTextSize(mTextSize);

}

super.setPressed(pressed);

this.invalidate();

}

public void startCustomAnimation(){

this.startAnimation(anim);

}

public void setText(String text){

mText = text;

this.startAnimation(anim);

}

public void setSweepAngle(float sweepAngle){

mSweepAngle = sweepAngle;

}

public class BarAnimation extends Animation {

/**

* Initializes expand collapse animation, has two types, collapse (1) and expand (0).

* @param view The view to animate

* @param type The type of animation: 0 will expand from gone and 0 size to visible and layout size defined in xml.

* 1 will collapse view and set to gone

*/

public BarAnimation() {

}

@Override

protected void applyTransformation(float interpolatedTime, Transformation t) {

super.applyTransformation(interpolatedTime, t);

if (interpolatedTime < 1.0f) {

mSweepAnglePer = interpolatedTime * mSweepAngle;

mCount = (int)(interpolatedTime * Float.parseFloat(mText));

} else {

mSweepAnglePer = mSweepAngle;

mCount = Integer.parseInt(mText);

}

postInvalidate();

}

}

}

属性变量及其说明

private RectF mColorWheelRectangle = new RectF();圆圈的矩形范围

private Paint mDefaultWheelPaint;  绘制底部灰色圆圈的画笔

private Paint mColorWheelPaint; 绘制蓝色扇形的画笔

private Paint textPaint; 中间文字的画笔

private float mColorWheelRadius; 圆圈普通状态下的半径

private float circleStrokeWidth; 圆圈的线条粗细

private float pressExtraStrokeWidth;按下状态下增加的圆圈线条增加的粗细

private String mText;中间文字内容

private int mCount; 为了达到数字增加效果而添加的变量,他和mText其实代表一个意思

private float mSweepAnglePer;  为了达到蓝色扇形增加效果而添加的变量,他和mSweepAngle其实代表一个意思

private float mSweepAngle; 扇形弧度

private int mTextSize;文字颜色

BarAnimation anim;动画类

构造方法调用之后,第一个调用的是init方法,在该方法中初始化了各种画笔的颜色,风格等,字体大小和线条粗细则使用了我自己定义的工具函数dip2px(),这样做的目的是在不同分辨率的手机上,相同数值的最终显示效果差别不大,比如字体大小mTextSize的初始化:mTextSize = MyUtils.dip2px(getContext(), 40);

还定义了动画对象以及动画持续时间:anim = new BarAnimation();

anim.setDuration(2000);

其中BarAnimation为自定义的动画类:public class BarAnimation extends Animation {

/**

* Initializes expand collapse animation, has two types, collapse (1) and expand (0).

* @param view The view to animate

* @param type The type of animation: 0 will expand from gone and 0 size to visible and layout size defined in xml.

* 1 will collapse view and set to gone

*/

public BarAnimation() {

}

@Override

protected void applyTransformation(float interpolatedTime, Transformation t) {

super.applyTransformation(interpolatedTime, t);

if (interpolatedTime < 1.0f) {

mSweepAnglePer = interpolatedTime * mSweepAngle;

mCount = (int)(interpolatedTime * Float.parseFloat(mText));

} else {

mSweepAnglePer = mSweepAngle;

mCount = Integer.parseInt(mText);

}

postInvalidate();

}

}

这个动画类利用了applyTransformation参数中的interpolatedTime参数(从0到1)的变化特点,实现了该View的某个属性随时间改变而改变。原理是在每次系统调用animation的applyTransformation()方法时,改变mSweepAnglePer,mCount的值,然后调用postInvalidate()不停的绘制view。if (interpolatedTime < 1.0f) {

mSweepAnglePer = interpolatedTime * mSweepAngle;

mCount = (int)(interpolatedTime * Float.parseFloat(mText));

}

mSweepAnglePer,mCount这两个属性只是动画过程中要用到的临时属性,mText和mSweepAngle才是动画结束之后表示扇形弧度和中间数值的真实值。

绘制方法

在onDraw方法中我们绘制了圆圈、扇形以及文字,但是绘制需要用到的一些坐标值是经过计算得出的,比如绘制扇形:canvas.drawArc(mColorWheelRectangle, -90, mSweepAnglePer, false, mColorWheelPaint);

mColorWheelRectangle是一个矩形,这个矩形的上下左右边界都是在onMeasure方法中根据控件所分配的大小得出来的。

具体计算方式在onMeasure的实现中:@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int height = getDefaultSize(getSuggestedMinimumHeight(),

heightMeasureSpec);

int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);

int min = Math.min(width, height);

setMeasuredDimension(min, min);

mColorWheelRadius = min - circleStrokeWidth -pressExtraStrokeWidth ;

mColorWheelRectangle.set(circleStrokeWidth+pressExtraStrokeWidth, circleStrokeWidth+pressExtraStrokeWidth,

mColorWheelRadius, mColorWheelRadius);

}

从setMeasuredDimension(min, min)可以看出我们强制该View为正方形。上面说到的mColorWheelRectangle矩形区域比控件的实际边界要小,这样做的目的是在按下状态下状态下让圆圈的线条变大之后也并不会超出矩形区域。

按下松开view样式改变的实现

改变样式很简单,只需改变画笔的样式就可以了,关键是在什么地方改变。我们都知道设置背景成selector就能是按下松开状态下背景改变,但是直接设背景不满足这里的要求,因为这是个圆圈,如果设置背景那肯定不会紧贴着圆圈边缘,但是我们可以在不同状态下更改画笔然后重绘达到相同的效果。如何检测到按下与松开呢?

看了view的源码知道setPressed()方法可以满足我们的要求:

@Override

public void setPressed(boolean pressed) {

Log.i(TAG,"call setPressed ");

if (pressed) {

mColorWheelPaint.setColor(0xFF165da6);

textPaint.setColor(0xFF070707);

mColorWheelPaint.setStrokeWidth(circleStrokeWidth+pressExtraStrokeWidth);

mDefaultWheelPaint.setStrokeWidth(circleStrokeWidth+pressExtraStrokeWidth);

textPaint.setTextSize(mTextSize-pressExtraStrokeWidth);

} else {

mColorWheelPaint.setColor(0xFF29a6f6);

textPaint.setColor(0xFF333333);

mColorWheelPaint.setStrokeWidth(circleStrokeWidth);

mDefaultWheelPaint.setStrokeWidth(circleStrokeWidth);

textPaint.setTextSize(mTextSize);

}

super.setPressed(pressed);

this.invalidate();

}

每次按下或者松开setPressed都会被调用,我们重写该方法,但要注意调用super.setPress()不然长按放开之后boolean pressed参数仍然为true,这样松开之后样式就保持按下的状态。具体原因还需要多阅读view的源码。

总结

其实这里最主要的是要有耐心了解canvas的一些方法,还有就是要根据自己的需求有针对性的分析view的源码。

android自定义圆圈动画,自定义view实现动画数字圆圈相关推荐

  1. android view.gone 动画,Android 模仿iPhone列表数据View刷新动画详解

    因为我本人很喜欢在不同的页面之间跳转时加点好玩的动画,今天无意间看到一个动画效果感觉不错,几种效果图如下:既然好玩就写在博客中,直接说就是:该效果类似于iPhone中View的切换动画效果,今天就只介 ...

  2. android伸缩动画自定义,Android干货:自定义带动画的View

    对于一个自定义View来说,onMeasure只是用来计算View尺寸,onDraw()才是真正执行View的绘制,所以一般我们都需要重写onDraw()函数来绘制我们期望的UI界面,下面我以一个具体 ...

  3. android view绘制中调用的函数,Android开发实践:自定义带动画的View

    前面两篇文章介绍了自定义View的onMeasure和onLayout原理,本文准备介绍自定义View的第三个关键部分,即onDraw()函数的重载. 对于一个自定义View来说,onMeasure只 ...

  4. android 动画之漂移,[超棒]自定义View居然还能这样?用 Android 实现一条小金鱼游动动画...

    原标题:[超棒]自定义View居然还能这样?用 Android 实现一条小金鱼游动动画 前言 此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝 ...

  5. android 自定义view如何控制view的高度_Android自定义View属性动画

    属性动画 DEMO地址:https://github.com/chaozhouzhang/CustomProgressView 1.值动画 ValueAnimator 值动画具体实现步骤: /** * ...

  6. android覆盖扩散动画,[Android]多层波纹扩散动画——自定义View绘制

    之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...

  7. Android模仿淘宝语音输入条形动画,录音动画自定义View

    Android模仿淘宝语音输入条形动画自定义View,类似柱状音频,折线音频,音乐跳动,音频跳动,录音动画,语音输入效果 地址: https://github.com/xfans/VoiceWaveV ...

  8. android 水波纹扩散动画,[Android]多层波纹扩散动画——自定义View绘制

    之前整理过一些属性动画的基本操作,这一段时间的动画相关需求都安然度过了.直到这次-- 一.另一种动画需求 多数交互中的动画都是让单个页面元素动起来,这种就很适合用属性动画实现.但是对于 多个元素.非页 ...

  9. android开发 鱼动画,自定义Drawable实现灵动的红鲤鱼动画(上篇)

    此篇中的小鱼动画是模仿国外一个大牛做的flash动画,第一眼就爱上它了,简约灵动又不失美学,于是抽空试着尝试了一下,如下是我用Android实现的效果图: 小鱼儿 由于整个绘制分析过程比较繁琐所以灵动 ...

  10. android 布局收缩动画,自定义View-带过渡动画的折叠收缩布局

    简介: 由于界面View.VISIBLE和View.GONE的动画太生硬,所以写了ExpandLayout类来平滑过渡. 基本思路,动态的设置布局的高度. 先上效果图: expand.gif 核心动画 ...

最新文章

  1. 如何教计算机认识手写数字(上)
  2. box_sizing
  3. *args and **kwargs in Python 变长参数
  4. Hadoop集群完全分布式模式环境部署
  5. sql server 数据脚本生成工具
  6. Spring Data JPA 从入门到精通~方法的查询策略设置
  7. 程序员,别再无脑刷题了,这样学 Python,编程能力暴增!
  8. 如果P = NP 则 NP = co-NP.
  9. 网络安全:个人网站防黑安全技巧
  10. 进程状态-Linux ps命令详细参数
  11. 超越LLMNR /NBNS欺骗 - 利用Active Directory集成的DNS
  12. linux usb转串口驱动报错,USB转串口驱动编译出错
  13. 学习-Java循环while之求非负数之和
  14. 新手小白安装Ubuntu18.04后的操作指南
  15. 使用 paddlehub的人物识别 对游戏人物识别 绘制方框
  16. 文件或目录损坏且无法读取的解决办法
  17. 【蓝桥杯2022】- 数的拆分
  18. 智能经济时节已至,百度智能云扬起风帆
  19. 设计模式之中介者模式---Mediator Pattern
  20. 关于和||的优先级问题

热门文章

  1. 人工智能与大数据的完美结合 1
  2. 英雄帖!移动云首批最有价值专家(MVP)招募开始了!
  3. 中科院战略咨询院与戴尔发布《产业数字化转型:战略与实践》研究报告
  4. QingStor NeonSAN跻身四强 新风口下的青云QingCloud正在厚积薄发
  5. 面试还搞不懂Redis,快看看这40道面试题!| 博文精选
  6. 哈工大人工智能研究院院长刘劼:AIoT 核心在“智”不在“联”,需云边端协同...
  7. 互联网大佬马老师于昨日教师节正式卸任,让位现任CEO张勇;华为发布新一代CloudLink视讯解决方案,普惠4K+AI;联通……...
  8. 国内首款全国产固态硬盘控制芯片发布
  9. 什么是5G,我们能从中得到什么?
  10. com+ system application 启动_dubbo启动引导过程(基于2.7.9)