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

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

先来看一下效果图

circl

 

 

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

下面附上主要代码。


1 新建一个类CircleView 继承自View

 

image

 

 

  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 }
View Code

 

 

 

 

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  
View Code

 

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

 

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

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

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. @Override protected 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)