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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM