在做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個參數都是整形是出於效率的考慮,所以經常要做的就是對其解碼=>
- int specMode = MeasureSpec.getMode(measureSpec);
- int specSize = MeasureSpec.getSize(measureSpec);
- 依據specMode的值,(MeasureSpec有3種模式分別是UNSPECIFIED, EXACTLY和AT_MOST)
- 如果是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模式表示你沒有指定大小。- 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,使用裁剪還是滾動等技術。- @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