Android 你可能需要用到的资产分布图

现在公司的项目大多数都是和钱有关,这就牵扯到,必须要给用户展示他当前的收益、积分等。如果我们单纯的文字展示,其实也是可以的。但那样会看起来更死板,不生动,不能让用户一目了然,所以这就需要我们的UI了,但是UI设计出来的View,大多数都不是系统自带的。所以这就需要我们自定义了。代码链接在文末

大家先看看,自定义出来的效果:


资产分布图

这就是UI要求的效果,里面所有的你看的东西都是一个View喔→_→,好啦。撸起袖子,搞起~

自定义View嘛,都是老套路了。

第一步 我们在res目录的values目录下新建资源文件attrs


定义的attrs


第二步 : 在代码里面初始化这些属性

//总资产颜色
private int mMoneyTextColor = 0xFF0D0C0C;
//总资产字体大小
private float mMoneyTextSize = 17;
//总资产提示文本颜色
private int mMoneyTextHintColor = 0xFFADA8A9;
//总资产提示文本字体大小
private float mMoneyTextHintSize = 15;
//总资产文本
private String mMoneyText = "";
//总资产提示文本
private String mMoneyTextHint = "";
//百分比 0~355
private int mDegree = 0;
//未覆盖inside颜色
private int mUnreachInsideColor = 0xFFF5AF77;
//未覆盖outside颜色
private int mUnreachOutsideColor = 0xFFFC8424;
//已覆盖 inside 颜色
private int mReachInsideColor = 0xFFC0F7DF;
//已覆盖 outside 颜色
private int mReachOutsideColor = 0xFF20C77D;
//半径
private float mRadius = 62;
//inside radius;
private float mInsideRadius = 13;
//outside radius
private float mOutsideRadius = 17;
//已覆盖inside paint
private Paint mReachInsidePaint;
//已覆盖outside paint
private Paint mReachOutsidePaint;
//未覆盖 inside Paint
private Paint mUnreachInsidePaint;
//未覆盖 outside Paint
private Paint mUnreachOutsidePaint;
//已覆盖 inside RectF
private RectF mReachInsideRectF = new RectF(0, 0, 0, 0);
//已覆盖 outside RectF;
private RectF mReachOutsideRectF = new RectF(0, 0, 0, 0);
//未覆盖 inside RectF
private RectF mUnreachInsideRectF = new RectF(0, 0, 0, 0);
//未覆盖 outside RectF;
private RectF mUnreachOutsideRectF = new RectF(0, 0, 0, 0);
//总金额 Paint
private Paint mTextPaint;
//总金额提示 Paint
private Paint mTextHintPaint;
//最大角度
private int mMaxDegree = 360;
//最小角度
private int mMinDegree = 0;
//是否绘制文本
private boolean ifDrawText = true;
//文本是否显示
private static final int TEXT_VISIBLE = 0;

//文本之间的间距
private float mTextSpace = 2;

/**
* 进度条文本和文本外边框是否显示的枚举
*/
public enum AssetTextVisibility {
Visible , Invisible
}


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

public CircleAssetView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}

public CircleAssetView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CircleAssetView, defStyleAttr, 0);
mMoneyTextColor = array.getColor(R.styleable.CircleAssetView_moneyTextColor, mMoneyTextColor);
mMoneyTextHintColor = array.getColor(R.styleable.CircleAssetView_moneyTextHintColor, mMoneyTextHintColor);
mMoneyTextSize = array.getDimension(R.styleable.CircleAssetView_moneyTextSize, mMoneyTextSize);
mMoneyTextHintSize = array.getDimension(R.styleable.CircleAssetView_moneyTextHintSize, mMoneyTextHintSize);
mDegree = array.getInteger(R.styleable.CircleAssetView_degree, mDegree);
mMoneyText = array.getString(R.styleable.CircleAssetView_moneyText);
mMoneyTextHint = array.getString(R.styleable.CircleAssetView_moneyTextHint);
mRadius = array.getDimension(R.styleable.CircleAssetView_radius, mRadius);
mInsideRadius = array.getDimension(R.styleable.CircleAssetView_insideRadius, mInsideRadius);
mOutsideRadius = array.getDimension(R.styleable.CircleAssetView_outsideRadius, mOutsideRadius);
mTextSpace = array.getDimension(R.styleable.CircleAssetView_textSpace,mTextSpace);
int textVisible = array.getInt(R.styleable.CircleAssetView_text_visibility, TEXT_VISIBLE);

if (textVisible != TEXT_VISIBLE) {
ifDrawText = false;
}
array.recycle();
//初始化画笔
initPaint();


}

第三步 : 我们要重写View的onMeasure方法,因为该方法指定该控件在屏幕上的大小

onMeasure传入的widthMeasureSpec和heightMeasureSpec不是一般的尺寸数值,而是将模式和尺寸组合在一起的数值。我们需要通过int mode = MeasureSpec.getMode(widthMeasureSpec)得到模式,用int size = MeasureSpec.getSize(widthMeasureSpec)得到尺寸。

mode共有三种情况,取值分别为MeasureSpec.UNSPECIFIED, MeasureSpec.EXACTLY, MeasureSpec.AT_MOST。

MeasureSpec.EXACTLY是精确尺寸,当我们将控件的layout_width或layout_height指定为具体数值时如andorid:layout_width="50dip",或者为FILL_PARENT是,都是控件大小已经确定的情况,都是精确尺寸。

MeasureSpec.AT_MOST是最大尺寸,当控件的layout_width或layout_height指定为WRAP_CONTENT时,控件大小一般随着控件的子空间或内容进行变化,此时控件尺寸只要不超过父控件允许的最大尺寸即可。因此,此时的mode是AT_MOST,size给出了父控件允许的最大尺寸。

MeasureSpec.UNSPECIFIED是未指定尺寸,这种情况不多,一般都是父控件是AdapterView,通过measure方法传入的模式。

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measure(widthMeasureSpec), measure(heightMeasureSpec));
}

private int measure(int sizeMeasureSpec) {
int result;
int mode = MeasureSpec.getMode(sizeMeasureSpec);
int size = MeasureSpec.getSize(sizeMeasureSpec);
if (mode == MeasureSpec.EXACTLY)
result = size;
else
result = (int) Math.min(size, (mRadius + mInsideRadius + mOutsideRadius) * 2);
return result;
}

从UI的要求来看我们的width、height是相同的。所以我们没有必要分别处理。如果mode是MeasureSpec.EXACTLY;我们就返回他绝对的大小就好。如果是其他两者呢,我们取得是他取得是它的result = (int)Math.min(size,(mRadius+mInsideRadius+mOutsideRadius)*2);

第四步: 我们重写onDraw方法,开始绘制资产分布图

@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int center = getWidth();
if (mDegree < mMinDegree || mDegree > mMaxDegree) {
throw new IllegalArgumentException("degree must be 0 ~ 360");
}
if (mDegree != 0)
//测量已覆盖
calculateReachRect(center, canvas, mDegree);
if (mDegree != 360)
//测量未覆盖
calculateUnreachRect(center, canvas, mDegree);
if (ifDrawText) {
//绘制总金额文本
int moneyLength = (int) mTextPaint.measureText(mMoneyText == null ? "" : mMoneyText);
canvas.drawText(mMoneyText, center / 2 - moneyLength / 2, center / 2 - mTextSpace / 2 - mMoneyTextSize / 2, mTextPaint);
//绘制总金额提示文本
int moneyHintLength = (int) mTextHintPaint.measureText(mMoneyTextHint == null ? "" : mMoneyTextHint);
canvas.drawText(mMoneyTextHint, center / 2 - moneyHintLength / 2, center / 2 + mTextSpace / 2 + mMoneyTextSize / 2, mTextHintPaint);
}
}

我们在绘制之前先判断一下,他设置的旋转角度是不是在minDegreemaxDegree之间,如果不是我们直接抛异常 throw new IllegalArgumentException( " degree must be 0~ 360 " )

(英语献丑了 -.-||)当设置的角度等于0的时候我们绘制未覆盖的区域,当设置的角度等于360的时候我们绘制已覆盖的区域

/**
* 测量已覆盖
*
* @param center
*/
private void calculateReachRect(int center, Canvas canvas, int mDegree) {
if (mDegree != 360) {
mDegree -= 1;
}
//已覆盖外侧
mReachOutsidePaint.setStrokeWidth(mOutsideRadius);
mReachOutsidePaint.setColor(mReachOutsideColor);
//绘制方块的左侧
mReachOutsideRectF.left = mOutsideRadius / 2;
//绘制方块的顶部
mReachOutsideRectF.top = mReachOutsideRectF.left;
//绘制方块的右侧
mReachOutsideRectF.right = center - mOutsideRadius / 2;
//绘制方块的底部
mReachOutsideRectF.bottom = mReachOutsideRectF.right;
canvas.drawArc(mReachOutsideRectF, -90, mDegree, false, mReachOutsidePaint);
//已覆盖内侧
mReachInsidePaint.setStrokeWidth(mInsideRadius);
mReachInsidePaint.setColor(mReachInsideColor);
mReachInsideRectF.left = mOutsideRadius + mInsideRadius / 2;
mReachInsideRectF.top = mReachInsideRectF.left;
mReachInsideRectF.right = center - mOutsideRadius - mInsideRadius / 2;
mReachInsideRectF.bottom = mReachInsideRectF.right;
canvas.drawArc(mReachInsideRectF, -90, mDegree, false, mReachInsidePaint);
}

在这里我需要说一下,一般我们想到的是绘制方块的左侧应该是从0开始的,其实不是哦,如果你设置0的时候会出现你看到的View的大小并不是你的实际大小哦,不信的话,可以自己试试!!!

未覆盖的绘制和已覆盖的绘制基本差不多,这里就不贴代码了,完整的代码,会在文末,附上链接。

其实到这里我们的资产分布View已经基本完成了。但是这时候UI要求,数据加载完成的时候要求他是从0度旋转到我们算出来的角度的一个过渡动画。Easy ~~~

 这时候我们就用到了强大的ValueAnimator所以我们只需要在数据加载出来的时候设置度数的时候设置动画就行了。

/**
* 设置角度
*
* @param degree
*/
public void setDegree(int degree) {
ValueAnimator anim = ValueAnimator.ofInt(degree);
//设置动画的过渡时间
anim.setDuration(500);
//设置动画的更新监听
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mDegree = (int) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.start();
}

至此大功告成。

代码传送门

QQ交流群:271127803(欢迎一起交流,共同成长~)

这是我和朋友一起运营的公众号,我们会定期的推送优质的Android文章

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,711评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,079评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,194评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,089评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,197评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,306评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,338评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,119评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,541评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,846评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,014评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,694评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,322评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,026评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,257评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,863评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,895评论 2 351

推荐阅读更多精彩内容