Android自定义View

最近做项目碰到一个需求,需要实现如下图的一个自定义的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的使用。

  1. setShape() 设置形状,可以设置如上述七种参数
  2. setColor() 设置GradientDrawable的颜色
  3. setBound() 设置GradientDrawable的边界,其实可以理解为我们这个Drawable的大小。
  4. 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。

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

推荐阅读更多精彩内容