最近做项目碰到一个需求,需要实现如下图的一个自定义的view。
icon.jpg
如上图,还需实现一个动画效果,中间的圆形image保持不动,外面的两个圆弧与圆点沿圆心转动,当触摸view时,整个view等比例放大,转动速度变快,触摸取消时恢复原样。
自己本身对Android自定义View的实现并不是很熟练,在这个View上卡住了。在后面师傅实现了这个View的所有功能后,自己将代码仔细学习了一遍,有种豁然开朗的感觉。
首先可以看到,我们这个View应该是直接继承View,看上去貌似是一个组合的view,由中间的ImageView加上周边旋转的圆弧组成,然而仔细考虑后发现,如果采用组合View的写法,可能会有缩放比例方面的问题,所以我们选择了直接继承View来写。
首先我们分析一下这个View,它由中间的圆形View,两个圆环,两个圆点组成,我们可以记为mAvatar,mGrayRingInner,mGrayRingOuter,mGreenCircle,mRedCircle。其实思路很简单,我们先在自定义View的构造函数里面确定各个部分的长宽参数、转动速度以及触摸事件,然后在重写onDraw()函数进行各个部分的绘制。
那么我们的实现过程有下列两个部分:
- 构造函数中确定各数值
- onDraw()中进行绘制
GradientDrawable
在进行这两部分工作之前,我们先介绍一个GradientDrawable这个类。
GradientDrawable是Drawable类的直接子类,而Drawable类又是个什么类呢?
官方文档是这样形容的
A Drawable is a general abstraction for "something that can be drawn." Most often you will deal with Drawable as the type of resource retrieved for drawing things to the screen; the Drawable class provides a generic API for dealing with an underlying visual resource that may take a variety of forms. Unlike a View, a Drawable does not have any facility to receive events or otherwise interact with the user.
意思是,Drawable首先是一个abstract类,它表示“可以被绘制的一些事情”。经常我们会认为Drawable是能够绘制到屏幕上的资源。但是Drawable跟View不一样,它无法获取事件,同时也无法与用户发生任何的交互。
那么,既然GradientDrawable是Drawable的直接子类,那么GradientDrawable也能够被绘制。我们同样看一下它的官方介绍。
A Drawable with a color gradient for buttons, backgrounds, etc.
可以看出来GradientDrawable是一般被用来绘制按钮、背景等。GradientDrawable可以绘制如下形状。
int LINE Shape is a line
int LINEAR_GRADIENT Gradient is linear (default.)
int OVAL Shape is an ellipse
int RADIAL_GRADIENT Gradient is circular.
int RECTANGLE Shape is a rectangle, possibly with rounded corners
int RING Shape is a ring.
int SWEEP_GRADIENT Gradient is a sweep.
那么,我们如何使用GradientDrawable呢?一般来讲,我们通过四个函数实现对GradientDrawable的使用。
- setShape() 设置形状,可以设置如上述七种参数
- setColor() 设置GradientDrawable的颜色
- setBound() 设置GradientDrawable的边界,其实可以理解为我们这个Drawable的大小。
- GradientDrawable.draw(canvas) 最后是将其绘制到画布canvas上去。
构造函数中的数值确定
在设计中,我们整个View的大小为95dp*95dp,mOuterRing的直径大小为85p,mInnerRing的直径为75dp,mAvatar直径为65dp。这部分直接看代码吧。
mViewSize = 95dp;
mGrayRingInner = new GradientDrawable();
mGrayRingInner.setShape(GradientDrawable.OVAL);
mGrayRingInner.setStroke(2, getResources().getColor(R.color.divider));
mGrayRingOuter = new GradientDrawable();
mGrayRingOuter.setShape(GradientDrawable.OVAL);
mGrayRingOuter.setStroke(2, getResources().getColor(R.color.divider));
mGreenCircle = new GradientDrawable();
mGreenCircle.setShape(GradientDrawable.OVAL);
mGreenCircle.setColor(getResources().getColor(R.color.green);
mRedCircle = new GradientDrawable();
mRedCircle.setShape(GradientDrawable.OVAL);
mRedCircle.setColor(getResources().getColor(R.color.red));
initDrawableBounds(); // 设置各个部分的大小
setOnTouchListener(new OnTouchListener() {
@Override
public boolean OnTouch(View v, MotionEvent event) {
}
});
onDraw()中的绘制
首先我们将不转动的部分绘制上去,包括mAvatar, mGrayRingInner和mGrayRingOuter。调用GradientDrawable的draw(canvas)。
mAvatar.draw(canvas);
mGrayRingInner.draw(canvas);
mGrayRingOuter.draw(canvas);
剩下的两部分要实现旋转的动画,我们可以通过不断绘制画布,每一次绘制mGreenCircle与mRedCircle进行相应的坐标变换,这样就可以实现旋转的动画效果。但是,同时我们需要保证mAvatar、mGrayRingInner和mGrayRingOuter不旋转,则需要调用canvas的save()和restore()函数。我们先看看这两个函数的官方说明。
save(). Saves the current matrix and clip onto a private stack.
保存当前的matrix放置到一个私有的栈中去。
restore() .This call balances a previous call to save(), and is used to remove all modifications to the matrix/clip state since the last save call.
save()和restore()成对出现时,可以有这种效果:保存当前的画布状态,然后你可以进行其他的绘制操作,当调用restore()的时候,移除掉这些修改(新的绘制还存在)。
所以,我们在绘制mGreenCircle和mRedCircle时,先调用save(),再绘制圆点,再restore()。
canvas.save();
canvas.translate(x, y);
mGreenCircle.draw(canvas);
canvas.restore();
// x y 为计算出来的坐标,上面的与下面的并不相同,省去了相关代码
canvas.save();
canvas.translate(x, y);
mRedCircle.draw(canvas);
canvas.restore();
因为要实现不断的绘制,那么在onDraw()中加上invalidate()。为了保证画布不会多次绘制出现重叠的图案,需要在上述draw操作的外层再增加一层save()和restore()代码。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.save();
...
...
...
canvas.restore();
invalidate();
}
这样便实现了我们需求的自定义View。
网友评论