Android 自定義控件開發入門(一)
作為一個有創意的開發者,或者軟件對UI設計的要求比較高,你經常會遇到安卓自帶的控件無法滿足你的需求的情況,這種時候,我們隻能去自己去實現適合項目的控件。同時,安卓也允許你去繼承已經存在的控件或者實現你自己的控件以便優化界麵和創造更加豐富的用戶體驗。
那麼怎樣來創建一個新的控件呢?
這得看需求是怎樣的了。
1.需要在原生控件的基本功能上進行擴展,這個時候你隻需要繼承並對控件進行擴展。通過重寫它的事件,onDraw ,但是始終都保持都父類方法的調用。如從已有的高級控件上繼承,例如繼承一個TextView。
2.需要幾個控件的功能的加和,這個時候要把控件組合起來,就是通過合並幾個控件來生成一個新控件。比如在ListView中用適配器來將多種控件有機的結合在一起,又如寫一個控件是多個控件的組合,一般是自定義布局,可以用一個類繼承一個布局。這個布局中包含多個控件。
3.白手起家自己創建一個新的控件。即直接從View,ViewGroup開始繪製控件
4.另外大家不要忘了,還有一個好用的東西<include>標簽。 在一個項目中我們可能會需要用到相同的布局設計,如果都寫在一個xml文件中,代碼顯得很冗餘,並且可讀性也很差,所以我們可以把相同布局的代碼單獨寫成一個模塊,然後用到的時候可以通過<include
/> 標簽來重用layout代碼。
作過Android 應用開發的朋友都知道,Android的UI界麵都是由View和ViewGroup及其派生類組合而成的。基於安卓UI設計原理,我們作為開發者,完全能夠按照自己的意願開發出項目定製的組件。其中,View是所有UI組件的基類,而ViewGroup是容納這些組件的容器,其本身也是從View派生出來的。AndroidUI界麵的一般結構可參見下麵的示意圖:
可見,作為容器的ViewGroup可以包含作為葉子節點的View,也可以包含作為更低層次的子ViewGroup,而子ViewGroup又可以包含下一層的葉子節點的View和ViewGroup。事實上,這種靈活的View層次結構可以形成非常複雜的UI布局,開發者可據此設計、開發非常精致的UI界麵。
ViewGroup可以通過重寫onMeasure,onLayout為加入其中的View進行布局和處理,功能十分強大,我們這次先學習View類派生自定義組件:
View組件的作用類似於JAVA中Swing裏的Panel,是一個矩形的空白區域,不帶有任何內容,對於Android應用的其他UI控件來說,都是繼承了View組件,然後繪製出來的。所以我們通過View子類並重寫View類的方法來派生我們自己的控件。
Android自定義View實現很簡單:
繼承View,重寫構造函數、onDraw,(onMeasure)等函數,下麵會逐一列舉。
如果自定義的View需要有自定義的屬性,需要在values下建立attrs.xml。在其中定義你的屬性。在使用到自定義View的xml布局文件中需要加入xmlns:前綴="https://schemas.android.com/apk/res/你的自定義View所在的包路徑".在使用自定義屬性的時候,使用前綴:屬性名,如my:textColor="……"。
讓我們先看一下View類的方法:
Category |
Methods |
Description |
Creation |
Constructors |
There is a form of the constructor that are called when the view is created from code and a form that is called when the view is inflated from a layout file. The second form should parse and apply any attributes defined in the layout file. |
Called after a view and all of its children has been inflated from XML. |
||
Layout |
Called to determine the size requirements for this view and all of its children. |
|
Called when this view should assign a size and position to all of its children. |
||
Called when the size of this view has changed. |
||
Drawing |
Called when the view should render its content. |
|
Event processing |
Called when a new hardware key event occurs. |
|
Called when a hardware key up event occurs. |
||
Called when a trackball motion event occurs. |
||
Called when a touch screen motion event occurs. |
||
Focus |
Called when the view gains or loses focus. |
|
Called when the window containing the view gains or loses focus. |
||
Attaching |
Called when the view is attached to a window. |
|
Called when the view is detached from its window. |
||
Called when the visibility of the window containing the view has changed. |
通常可能需要重寫以下方法:
1.構造器,至少用來獲取Context
2.onFinishlnflate()這是一個回調方法, 當應用從 XML 布局文件加載該組件並利用
它來構建界麵之後, 該方法就會被回調。
3.onMeasure(int,int):調用該方法來檢測View組件及它所包含的所有子組件的大小.
4.onlayout(boolean,int,int,int,int):當該組件需要分配其子組件的位置、大小時,
該方法就會被回調. View類中布局發生改變時會調用的方法,這個方法是所有View、ViewGroup及其派生類都具有的方法,重載該類可以在布局發生改變時作定製處理,這在實現一些特效時非常有用。
5.onSizeChanged(int,int, int, int):當該組件的大小被改變時回調該方法.
6.onDraw(canves): 當該組件將要繪製它的內容時回調該方法迸行繪製. View類中用於重繪的方法,這個方法是所有View、ViewGroup及其派生類都具有的方法,也是Android UI繪製最重要的方法。開發者可重載該方法,並在重載的方法內部基於參數canvas繪製自己的各種圖形、圖像效果。
7.onKeyDown(int,KeyEvent): 當某個鍵被按下時觸發該方法.
8.onKayUp(int,KeyEvent), 當鬆開某個鍵時觸發該方法.
9.onTrackballEvent (MotionEvent): 當發生軌跡球事件時觸發該方法.
10.onTouchEvent (MotionEvent): 當發生觸摸屏事件時觸發該方法.
11.onWindowFocuschanged(boolean): 當該組件得到、失去焦點時觸發該方法.
12.onAttachedToWindow():當把該組件放入某個窗口時觸發該方法.
13.onDetachedFromWindow(): 當把該組件從某個窗口上分離時觸發該方法.
14.onWindowVisibilityChanged(int):當包含該組件的窗口的可見性發生改變時觸發該
方法.
另外再補充兩個ViewGroup類經常重載的方法:
1.protected void dispatchDraw(Canvas canvas):ViewGroup類及其派生類具有的方法,這個方法主要用於控製子View的繪製分發,重載該方法可改變子View的繪製,進而實現一些複雜的視效。
2.protected boolean drawChild(Canvas canvas, View child, long drawingTime)):ViewGroup類及其派生類具有的方法,這個方法直接控製繪製某局具體的子view,重載該方法可控製具體某個具體子View。
在需要開發自定義View的時候,我們不需要列舉出上麵所有的方法,,而是可以根據業務需要來有選擇的使用·上麵的方法,下麵我們看一個簡單的示例程序,在這個示例程序裏麵我們隻需要重寫onDraw方法就可以了!
示例程序一:
我們要寫一個跟隨手指移動的小球,思路很簡單,隻要獲取到用戶點擊屏幕的位置,並且在該位置處重繪小球即可:
下麵我們看一下程序:
我注釋寫的比較清楚,我就說的簡略一點:
首先我們寫一個類DrawView,也就是我們自定義的控件,繼承自View
然後我們先寫出構造器,獲取到Context,這裏如果用隻含有Context的構造器會在xml裏調用控件的時候出錯,詳情請看我的另外一篇博文:
https://blog.csdn.net/sunmc1204953974/article/details/38101057
下麵我們開始寫:
// 構造方法 public DrawView(Context context,AttributeSet attrs){ super(context,attrs); } // 重寫ondraw方法 @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); // 創建畫筆 Paint paint = new Paint(); // 設置畫筆顏色 paint.setColor(Color.RED); // 畫出小球 canvas.drawCircle(circleX, circleY, circleR, paint); }
然後不要忘了設置這些數據的setter和getter,因為我們需要再使用這個View的時候加上監聽才可以:
// get set 方法 public float getCircleX() { return circleX; } public void setCircleX(float circleX) { this.circleX = circleX; } public float getCircleY() { return circleY; } public void setCircleY(float circleY) { this.circleY = circleY; } public float getCircleR() { return circleR; } public void setCircleR(float circleR) { this.circleR = circleR; }
這樣我們的一個簡單地自定控件就大功告成了,下麵是該類的完整代碼:
package com.example.moveball; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.AttributeSet; import android.view.View; public class DrawView extends View{ private float circleX = 40; private float circleY = 50; private float circleR = 15; // 構造方法 public DrawView(Context context,AttributeSet attrs){ super(context,attrs); } // 重寫ondraw方法 @Override public void onDraw(Canvas canvas){ super.onDraw(canvas); // 創建畫筆 Paint paint = new Paint(); // 設置畫筆顏色 paint.setColor(Color.RED); // 畫出小球 canvas.drawCircle(circleX, circleY, circleR, paint); } // get set 方法 public float getCircleX() { return circleX; } public void setCircleX(float circleX) { this.circleX = circleX; } public float getCircleY() { return circleY; } public void setCircleY(float circleY) { this.circleY = circleY; } public float getCircleR() { return circleR; } public void setCircleR(float circleR) { this.circleR = circleR; } }
之後我們就是像平時使用安卓原生控件那樣使用就可以了,我們看一下Activity的代碼:
package com.example.moveball; import android.os.Bundle; import android.app.Activity; import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; public class MainActivity extends Activity { // 定義DrawView組件 DrawView drawView = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 創建DrawView組件 drawView = (DrawView)this.findViewById(R.id.drawView); // 為DrawView組件綁定Touch事件 drawView.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View arg0, MotionEvent event) { // 獲取坐標並改變小球的坐標 drawView.setCircleX(event.getX()); drawView.setCircleY(event.getY()); // 通知draw組件重繪 drawView.invalidate(); // 返回true表明被執行 return true; } }); } }
以及xml格式的布局文件:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <com.example.moveball.DrawView android: android:layout_width="match_parent" android:layout_height="match_parent" > </com.example.moveball.DrawView> </RelativeLayout>
這樣一個簡單的例子就呈現在大家麵前了,無論是多麼複雜的自定義控件,思路總是這樣子的,大家是不是覺得怪怪的,對了,作為一個控件,我們居然還要為了他的實現為其增加麻煩的監聽,這就是因為我們重寫的方法太少的原因,下一講再給大家介紹一個經常重寫的方法:publicboolean onTouchEvent (MotionEvent event)。
源代碼上麵已經很詳細了,我在最後一篇的最後還會發一個工程上來,歡迎大家一起學習!
我也還是學生,寫的不好或者有問題的地方還請多多指教~
最後更新:2017-04-03 05:39:49