android 自定义控件---圆形方向盘

在做Android平台开发的时候,经常会遇到安卓原生控件无法满足需求的情况,安卓允许开发者去继承已经存在的控件或者实现你自己的控件。

先来看一下效果图

采用直接集成View类,重写onDrow方法绘制。

下面附上主要代码。


1 新建一个类CircleView 继承自View

  1 package com.lennon.view;
2
3 import android.content.Context;
4 import android.graphics.Canvas;
5 import android.graphics.Color;
6 import android.graphics.Paint;
7 import android.graphics.Path;
8 import android.graphics.RectF;
9 import android.util.AttributeSet;
10 import android.view.MotionEvent;
11 import android.view.View;
12 /**
13 * 自定义圆形的方向布局
14 *
15 * @author 樊列龙
16 * @since 2014-06-07
17 */
18 public class CircleView extends View {
19
20 private int circleWidth = 100; // 圆环直径
21 private int circleColor = Color.argb(150, 255, 0, 0);
22 private int innerCircleColor = Color.rgb(0, 150, 0);
23 private int backgroundColor = Color.rgb(255, 255, 255);
24 private Paint paint = new Paint();
25 int center = 0;
26 int innerRadius = 0;
27 private float innerCircleRadius = 0;
28 private float smallCircle = 10;
29 public Dir dir = Dir.UP;
30
31 public CircleView(Context context, AttributeSet attrs) {
32 super(context, attrs);
33 }
34
35 public CircleView(Context context) {
36 super(context);
37
38 // paint = new Paint();
39 }
40
41 public CircleView(Context context, AttributeSet attrs, int defStyle) {
42 super(context, attrs, defStyle);
43
44 }
45
46 @Override
47 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
48 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
49
50 int measuredHeight = measureHeight(heightMeasureSpec);
51 int measuredWidth = measureWidth(widthMeasureSpec);
52
53 setMeasuredDimension(measuredWidth, measuredHeight);
54
55 center = getWidth() / 2;
56 innerRadius = (center - circleWidth / 2 - 10);// 圆环
57 innerCircleRadius = center / 3;
58 this.setOnTouchListener(onTouchListener);
59 }
60
61 /**
62 * 测量宽度
63 *
64 * @param measureSpec
65 * @return
66 */
67 private int measureWidth(int measureSpec) {
68 int specMode = MeasureSpec.getMode(measureSpec);
69 int specSize = MeasureSpec.getSize(measureSpec);
70
71 int result = 0;
72
73 if (specMode == MeasureSpec.AT_MOST) {
74 result = getWidth();
75 } else if (specMode == MeasureSpec.EXACTLY) {
76 result = specSize;
77 }
78 return result;
79 }
80
81 /**
82 * 测量高度
83 *
84 * @param measureSpec
85 * @return
86 */
87 private int measureHeight(int measureSpec) {
88
89 int specMode = MeasureSpec.getMode(measureSpec);
90 int specSize = MeasureSpec.getSize(measureSpec);
91
92 int result = 0;
93
94 if (specMode == MeasureSpec.AT_MOST) {
95
96 result = specSize;
97 } else if (specMode == MeasureSpec.EXACTLY) {
98 result = specSize;
99 }
100 return result;
101 }
102
103 /**
104 * 开始绘制
105 */
106 @Override
107 protected void onDraw(Canvas canvas) {
108 super.onDraw(canvas);
109
110 initBackGround(canvas);
111 drawDirTriangle(canvas, dir);
112
113 }
114
115 /**
116 * 绘制方向小箭头
117 *
118 * @param canvas
119 */
120 private void drawDirTriangle(Canvas canvas, Dir dir) {
121 paint.setColor(innerCircleColor);
122 paint.setStrokeWidth(1);
123 paint.setStyle(Paint.Style.FILL);
124
125 switch (dir) {
126 case UP:
127 drawUpTriangle(canvas);
128 break;
129 case DOWN:
130 drawDownTriangle(canvas);
131 break;
132 case LEFT:
133 drawLeftTriangle(canvas);
134 break;
135 case RIGHT:
136 drawRightTriangle(canvas);
137 break;
138 case CENTER:
139 invalidate();
140 break;
141 default:
142 break;
143 }
144
145 paint.setColor(backgroundColor);
146
147 canvas.drawCircle(center, center, smallCircle, paint);
148 // canvas.drawText(text, center, center+40, paint);
149
150 }
151
152 /**
153 * 绘制向右的小箭头
154 *
155 * @param canvas
156 */
157 private void drawRightTriangle(Canvas canvas) {
158 Path path = new Path();
159 path.moveTo(center, center);
160 double sqrt2 = innerCircleRadius / Math.sqrt(2);
161 double pow05 = innerCircleRadius * Math.sqrt(2);
162 path.lineTo((float) (center + sqrt2), (float) (center - sqrt2));
163 path.lineTo((float) (center + pow05), center);
164 path.lineTo((float) (center + sqrt2), (float) (center + sqrt2));
165 canvas.drawPath(path, paint);
166 paint.setColor(backgroundColor);
167 canvas.drawLine(center, center, center + innerCircleRadius, center, paint);
168
169 drawOnclikColor(canvas, Dir.RIGHT);
170 }
171
172 /**
173 * 绘制想左的小箭头
174 *
175 * @param canvas
176 */
177 private void drawLeftTriangle(Canvas canvas) {
178 Path path = new Path();
179 path.moveTo(center, center);
180 double sqrt2 = innerCircleRadius / Math.sqrt(2);
181 double pow05 = innerCircleRadius * Math.sqrt(2);
182 path.lineTo((float) (center - sqrt2), (float) (center - sqrt2));
183 path.lineTo((float) (center - pow05), center);
184 path.lineTo((float) (center - sqrt2), (float) (center + sqrt2));
185 canvas.drawPath(path, paint);
186
187 paint.setColor(backgroundColor);
188 canvas.drawLine(center, center, center - innerCircleRadius, center, paint);
189
190 drawOnclikColor(canvas, Dir.LEFT);
191
192 }
193
194 /**
195 * 绘制向下的小箭头
196 *
197 * @param canvas
198 */
199 private void drawDownTriangle(Canvas canvas) {
200 Path path = new Path();
201 path.moveTo(center, center);
202 double sqrt2 = innerCircleRadius / Math.sqrt(2);
203 double pow05 = innerCircleRadius * Math.sqrt(2);
204 path.lineTo((float) (center - sqrt2), (float) (center + sqrt2));
205 path.lineTo(center, (float) (center + pow05));
206 path.lineTo((float) (center + sqrt2), (float) (center + sqrt2));
207 canvas.drawPath(path, paint);
208
209 paint.setColor(backgroundColor);
210 canvas.drawLine(center, center, center, center + innerCircleRadius, paint);
211
212 drawOnclikColor(canvas, Dir.DOWN);
213 }
214
215 /**
216 * 点击的时候绘制黑色的扇形
217 *
218 * @param canvas
219 * @param dir
220 */
221 private void drawOnclikColor(Canvas canvas, Dir dir) {
222 paint.setColor(Color.BLACK);
223 paint.setStyle(Paint.Style.STROKE);
224 paint.setStrokeWidth(100);
225 switch (dir) {
226 case UP:
227 canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
228 + innerRadius), 225, 90, false, paint);
229 break;
230 case DOWN:
231 canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
232 + innerRadius), 45, 90, false, paint);
233 break;
234 case LEFT:
235 canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
236 + innerRadius), 135, 90, false, paint);
237 break;
238 case RIGHT:
239 canvas.drawArc(new RectF(center - innerRadius, center - innerRadius, center + innerRadius, center
240 + innerRadius), -45, 90, false, paint);
241 break;
242
243 default:
244 break;
245 }
246
247 paint.setStyle(Paint.Style.FILL);
248 }
249
250 /**
251 * 绘制像向上的箭头
252 *
253 * @param canvas
254 */
255 private void drawUpTriangle(Canvas canvas) {
256 Path path = new Path();
257 path.moveTo(center, center);
258 double sqrt2 = innerCircleRadius / Math.sqrt(2);
259 double pow05 = innerCircleRadius * Math.sqrt(2);
260
261 path.lineTo((float) (center - sqrt2), (float) (center - sqrt2));
262 path.lineTo(center, (float) (center - pow05));
263 path.lineTo((float) (center + sqrt2), (float) (center - sqrt2));
264 canvas.drawPath(path, paint);
265
266 paint.setColor(backgroundColor);
267 canvas.drawLine(center, center, center, center - innerCircleRadius, paint);
268
269 drawOnclikColor(canvas, Dir.UP);
270 }
271
272 /**
273 * 绘制基本的背景, 这包括了三个步骤:1.清空画布 2.绘制外圈的圆 3.绘制内圈的圆
274 *
275 * @param canvas
276 */
277 private void initBackGround(Canvas canvas) {
278 clearCanvas(canvas);
279 drawBackCircle(canvas);
280 drawInnerCircle(canvas);
281
282 }
283
284 /**
285 * 绘制中心白色小圆
286 *
287 * @param canvas
288 */
289 private void drawInnerCircle(Canvas canvas) {
290 paint.setColor(innerCircleColor);
291 paint.setStyle(Paint.Style.FILL);
292 paint.setStrokeWidth(1);
293 canvas.drawCircle(center, center, innerCircleRadius, paint);
294 }
295
296 /**
297 * 绘制背景的圆圈和隔线
298 *
299 * @param canvas
300 */
301 private void drawBackCircle(Canvas canvas) {
302 paint.setColor(circleColor);
303 paint.setStrokeWidth(circleWidth);
304 paint.setAntiAlias(true);
305 paint.setStyle(Paint.Style.STROKE);
306 canvas.drawCircle(center, center, innerRadius, paint); // 绘制圆圈
307
308 paint.setColor(backgroundColor);
309 paint.setStyle(Paint.Style.FILL);
310 paint.setStrokeWidth(4);
311 canvas.drawLine(center, center, 0, 0, paint);
312 canvas.drawLine(center, center, center * 2, 0, paint);
313 canvas.drawLine(center, center, 0, center * 2, paint);
314 canvas.drawLine(center, center, center * 2, center * 2, paint);
315
316 }
317
318 /**
319 * 清空画布
320 *
321 * @param canvas
322 */
323 private void clearCanvas(Canvas canvas) {
324 canvas.drawColor(backgroundColor);
325 }
326
327 OnTouchListener onTouchListener = new OnTouchListener() {
328
329 @Override
330 public boolean onTouch(View view, MotionEvent event) {
331 Dir tmp = Dir.UNDEFINE;
332 if ((tmp = checkDir(event.getX(), event.getY())) != Dir.UNDEFINE) {
333 dir = tmp;
334 invalidate();
335 }
336 return true;
337 }
338
339 /**
340 * 检测方向
341 *
342 * @param x
343 * @param y
344 * @return
345 */
346 private Dir checkDir(float x, float y) {
347 Dir dir = Dir.UNDEFINE;
348
349 if (Math.sqrt(Math.pow(y - center, 2) + Math.pow(x - center, 2)) < innerCircleRadius) {// 判断在中心圆圈内
350 dir = Dir.CENTER;
351 System.out.println("----中央");
352 } else if (y < x && y + x < 2 * center) {
353 dir = Dir.UP;
354 System.out.println("----向上");
355 } else if (y < x && y + x > 2 * center) {
356 dir = Dir.RIGHT;
357 System.out.println("----向右");
358 } else if (y > x && y + x < 2 * center) {
359 dir = Dir.LEFT;
360 System.out.println("----向左");
361 } else if (y > x && y + x > 2 * center) {
362 dir = Dir.DOWN;
363 System.out.println("----向下");
364 }
365
366 return dir;
367 }
368
369 };
370
371 /**
372 * 关于方向的枚举
373 *
374 * @author Administrator
375 *
376 */
377 public enum Dir {
378 UP, DOWN, LEFT, RIGHT, CENTER, UNDEFINE
379 }
380
381 }

2 在activity_main.xml中引用CircleView类

 1 <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" >
2
3 <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical">
4
5 <com.lennon.view.CircleView android:layout_width="fill_parent" android:layout_height="wrap_content" android:id="@+id/cv" />
6
7 </LinearLayout>
8
9 </RelativeLayout>
10
11

好了 可以直接运行处结果了!

下面对上述代码做一些说明:

主要的自定义控件的方法有:

1.有些基本功能原生控件都能提供,所以这个时候你只需要继承并对控件进行扩展。通过重写它的事件,onDraw,但是始终都保持都父类方法的调用。

2.组合控件 就是通过合并几个控件的功能来生成一个控件。

3.完完整整创建一个新的控件。

我们这里实现的是一个完全自定义的控件,通常是继承View或者SurfaceView
,View类提供一个Canvas(画布)和一系列的画的方法,还有Paint(画笔)。使用它们去创建一个自定义的UI。你可以重写事件,包括屏幕接触或者按键按下等等,用来提供与用户交互。

1.如果你不需要快速重画和3D图像的效果,那么让View作为父类提供一个轻量级的解决方案。

2.如若不然,就需要使用SurfaceView作为父类,这样你就可以提供一个后台线程去画和使用OPENGL去实现你的图像。这个就相对重量级了,如果你的视图需要经常更新,然后由需要显示比较复杂的图像信息(尤其是在游戏和3D可视化),SurfaceView将是更好的选择。

使用这这方式一般你需要重写2个方法:

1.onMeasure

什么是onMeasure?

下面转载一段文章:


View在屏幕上显示出来要先经过measure(计算)和layout(布局).

1、什么时候调用onMeasure方法? 

当控件的父元素正要放置该控件时调用.父元素会问子控件一个问题,“你想要用多大地方啊?”,然后传入两个参数——widthMeasureSpec和heightMeasureSpec.

这两个参数指明控件可获得的空间以及关于这个空间描述的元数据.

更好的方法是你传递View的高度和宽度到setMeasuredDimension方法里,这样可以直接告诉父控件,需要多大地方放置子控件.

widthMeasureSpec和heightMeasureSpec这2个参数都是整形是出于效率的考虑,所以经常要做的就是对其解码=>

  1. int specMode =
    MeasureSpec.getMode(measureSpec);

  2. int specSize =
    MeasureSpec.getSize(measureSpec);

  1. 依据specMode的值,(MeasureSpec有3种模式分别是UNSPECIFIED,
    EXACTLY和AT_MOST)

  2. 如果是AT_MOST,specSize
    代表的是最大可获得的空间;
    如果是EXACTLY,specSize 代表的是精确的尺寸;

    如果是UNSPECIFIED,对于控件尺寸来说,没有任何参考意义。

    2、那么这些模式和我们平时设置的layout参数fill_parent, wrap_content有什么关系呢?

    经过代码测试就知道,当我们设置width或height为fill_parent时,容器在布局时调用子
    view的measure方法传入的模式是EXACTLY,因为子view会占据剩余容器的空间,所以它大小是确定的。
    而当设置为
    wrap_content时,容器传进去的是AT_MOST,
    表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸。当子view的大小设置为精确值时,容器传入的是EXACTLY,
    而MeasureSpec的UNSPECIFIED模式表示你没有指定大小。

  3. View的onMeasure方法默认行为是当模式为UNSPECIFIED时,设置尺寸为mMinWidth(通常为0)或者背景drawable的最小尺寸,当模式为EXACTLY或者AT_MOST时,尺寸设置为传入的MeasureSpec的大小。 

    有个观念需要纠正的是,fill_parent应该是子view会占据剩下容器的空间,而不会覆盖前面已布局好的其他view空间,当然后面布局子
    view就没有空间给分配了,所以fill_parent属性对布局顺序很重要。以前所想的是把所有容器的空间都占满了,难怪google在2.2版本里
    把fill_parent的名字改为match_parent.

    在两种情况下,你必须绝对的处理这些限制。在一些情况下,它可能会返回超出这些限制的尺寸,在这种情况下,你可以让父元素选择如何对待超出的View,使用裁剪还是滚动等技术。

  4. @Overrideprotected
    void onMeasure(int
    widthMeasureSpec, int
    heightMeasureSpec) {int
    measuredHeight = measureHeight(heightMeasureSpec);int measuredWidth =
    measureWidth(widthMeasureSpec);

    setMeasuredDimension(measuredHeight, measuredWidth); //
    记住这句可不能省。 }
     private
    int measureHeight(int
    measureSpec) {
    int specMode =
    MeasureSpec.getMode(measureSpec);
    int specSize =
    MeasureSpec.getSize(measureSpec);
     //
    Default size if no limits are specified.
    int result = 500;
     if
    (specMode == MeasureSpec.AT_MOST) {
    // Calculate the ideal size of
    your // control within this maximum size.
    //
    If your control fills the available
    // space return the outer bound.
    result = specSize;
    } else
    if (specMode ==
    MeasureSpec.EXACTLY) {
    // If your control can fit within these
    bounds return that value.
    result = specSize;
    } return
    result; }
     private
    int measureWidth(int
    measureSpec) {
    // 代码基本类似measureHeight
    }

2
onDraw

使用Canvas进行图形的绘制

本文参考了:

1.http://my.oschina.net/wangjunhe/blog/99764

2.http://blog.csdn.net/ethan_xue/article/details/7313575

代码下载地址


http://download.csdn.net/detail/csulennon/7462169

android 自定义控件---圆形方向盘,布布扣,bubuko.com

时间: 2024-10-27 09:47:13

android 自定义控件---圆形方向盘的相关文章

Android自定义控件系列之应用篇——圆形进度条

一.概述 在上一篇博文中,我们给大家介绍了Android自定义控件系列的基础篇.链接:http://www.cnblogs.com/jerehedu/p/4360066.html 这一篇博文中,我们将在基础篇的基础上,再通过重写ondraw()方法和自定义属性实现圆形进度条,效果如图所示: 二.实现步骤   1.  编写自定义组件MyCircleProgress扩展View public class MyCircleProgress extends View { - } 2.  在MyCircl

[转]Android自定义控件系列五:自定义绚丽水波纹效果

出处:http://www.2cto.com/kf/201411/353169.html 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二:自定义开关按钮(一)).观察这个效果,发现应该

Android自定义控件View(三)组合控件

不少人应该见过小米手机系统音量控制UI,一个圆形带动画效果的音量加减UI,效果很好看.它是怎么实现的呢?这篇博客来揭开它的神秘面纱.先上效果图 相信很多人都知道Android自定义控件的三种方式,Android自定义控件View(一)自绘控件,Android自定义控件View(二)继承控件,还有就是这一节即将学习到的组合控件.我们通过实现圆形音量UI来讲解组合控件的定义和使用. 组合控件 所谓组合控件就是有多个已有的控件组合而成一个复杂的控件.比如上图的音量控件就是一个完美的组合控件.我们来分析

Android自定义控件---继承ProgressBar功能扩展

一.前言 前一段时间在做视频开发,由于本人刚接触视频开发这块,所以 领导没有对我提很高的要求,仅仅要求能够播放本地视频即可. 我想怎么简单怎么做.于是选择用Android VideoView控件来播放视频 (后面发现VideoView的灵活性实在太差,我不想吐槽). 最终的效果图: 视频全屏效果 这次的任务主要难度在于进度条这个控件.各位可以从上面的两张图中看到,进度条被分 为三段.每段表示一个视频,并且每个视频的长度不一,也就意味着每段视频进度条的前进速度是不相同的. 难点总结: 1.自定义控

Android自定义控件系列二:如何自定义属性

上一篇Android自定义控件系列一:如何测量控件尺寸 我们讲了如何确定控件的属性,这篇接着也是讲个必要的知识-如何自定义属性.对于一个完整的或者说真正有实用价值的控件,自定义属性是必不可少的. 如何为控件定义属性 在res/values/attrs.xml(attrs.xml如果不存在,可以创建个)中使用<declare-styleable>标签定义属性,比如我想定义个显示头像的圆形的图片控件(AvatarImageView): 01.<?xml version="1.0&q

Android 自定义控件 优雅实现元素间的分割线 (支持3.0以下)

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/42407923 ,本文出自:[张鸿洋的博客] 1.概述 话说,随着Android SDK版本的升级,很多控件增加了新的属性方便我们的使用,比如LinearLayout中多了:divider.showDividers等,用于为其内部元素添加分隔:但是呢,这样的属性在较低版本的SDK中不能被支持,那么,我们在开发过程中,可能会出现这样的需求:将这个新的特性想办法做到尽可能的向下兼容.

【android自定义控件】ProgressBar自定义

ProgressBar分为垂直和水平 经常在数据加载过程中,为了让用户感觉友好,弹出一个提示圆形的加载框 水平的经常在下载应用的时候用到,还伴随着下载进度. ProgressBar的样式有四种: android:progressBarStyle:默认进度条样式,不确定模式 android:progressBarStyleHorizontal:水平进度条样式 android:progressBarStyleLarge :大号进度条样式,也是不确定进度模式 android:progressBarSt

Android自定义控件系列五:自定义绚丽水波纹效果

尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551 今天我们来利用Android自定义控件实现一个比较有趣的效果:滑动水波纹.先来看看最终效果图: 图一 效果还是很炫的:饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现 一.实现单击出现水波纹单圈效果: 图二 照例来说,还是一个自定义控件,这里我们直接让这个控件撑满整个屏幕(对自定义控件不熟悉的可以参看我之前的一篇文章:Android自定义控件系列二

Android自定义控件View(二)

在前一篇博客中学习了Android自定义控件View的流程步骤和注意点,不了解的童鞋可以参考Android自定义控件View(一).这一节开始学习自定义控件View(二)之继承系统已有的控件.我们来自定义一个圆形ImageView. RoundImageView 随着Android UI效果越来越炫,很多系统自带的控件已经无法满足日常开发需求,比如很多应用的头像是圆形的,QQ头像就是圆形的图片.但是Android系统提供的控件当中没有一个是圆形的.那么怎么才能实现圆形头像效果呢?两种方法: 图片