现在公司的项目大多数都是和钱有关,这就牵扯到,必须要给用户展示他当前的收益、积分等。如果我们单纯的文字展示,其实也是可以的。但那样会看起来更死板,不生动,不能让用户一目了然,所以这就需要我们的UI了,但是UI设计出来的View,大多数都不是系统自带的。所以这就需要我们自定义了。代码链接在文末
大家先看看,自定义出来的效果:
这就是UI要求的效果,里面所有的你看的东西都是一个View喔→_→,好啦。撸起袖子,搞起~
自定义View嘛,都是老套路了。
第一步 我们在res目录的values目录下新建资源文件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);
}
}
我们在绘制之前先判断一下,他设置的旋转角度是不是在minDegree和maxDegree之间,如果不是我们直接抛异常 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文章