15人收藏此文章, 我要收藏發表於8個月前(2013-01-03
22:14) , 已有
1176次閱讀 ,共
0個評論
創建新的控件:
作爲一個有創意的開發者,你經常會遇到安卓原生控件無法滿足你的需求。
爲了優化你的界面和工作流程,安卓允許你去繼承已經存在的控件或者實現你自己的控件。
那麼最好的方式去創建一個新的控件是什麼? 這主要取決你想要完成什麼。
1.有些基本功能原生控件都能提供,所以這個時候你只需要繼承並對控件進行擴展。通過重寫它的事件,onDraw,但是始終都保持都父類方法的調用。
2.組合控件 就是通過合併幾個控件的功能來生成一個控件。
3.完完整整創建一個新的控件。
1.修改存在的控件
例子:
01 |
public class MyTextView extends TextView
{ |
03 |
public MyTextView(Context
context, AttributeSet ats, int defStyle)
{ |
04 |
super (context,
ats, defStyle); |
07 |
public MyTextView(Context
context) { |
11 |
public MyTextView(Context
context, AttributeSet attrs) { |
12 |
super (context,
attrs); |
16 |
public void onDraw(Canvas
canvas) { |
27 |
public boolean onKeyDown( int keyCode,
KeyEvent keyEvent) { |
31 |
return super .onKeyDown(keyCode,
keyEvent); |
2.組合控件
1.最簡單的方式,是定義了XML佈局文件,然後用include實現重用。(。。。這也算啊。。。)
2.去合併一個控件 通常你自定義的控件需要繼承一個ViewGroup(通常就是Layout),就像:
1 |
public class MyCompoundView extends LinearLayout
{ |
2 |
public MyCompoundView(Context
context) { |
6 |
public MyCompoundView(Context
context, AttributeSet attrs) { |
就像activity,比較好的設計一個混合的控件UI佈局是使用一個外部的layout資源。
這裏我們模擬定義一個:
01 |
<? xml version=”1.0”
encoding=”utf-8”?> |
02 |
< LinearLayout xmlns:android=”http://schemas.android.com/apk/res/android” |
03 |
android:orientation=”vertical” |
04 |
android:layout_width=”match_parent” |
05 |
android:layout_height=”wrap_content”> |
07 |
android:id=”@+id/editText” |
08 |
android:layout_width=”match_parent” |
09 |
android:layout_height=”wrap_content” |
12 |
android:id=”@+id/clearButton” |
13 |
android:layout_width=”match_parent” |
14 |
android:layout_height=”wrap_content” |
然後在構造函數初始化的時候:
01 |
public class ClearableEditText extends LinearLayout
{ |
06 |
public ClearableEditText(Context
context) { |
10 |
String
infService = Context.LAYOUT_INFLATER_SERVICE; |
13 |
li
= (LayoutInflater) getContext().getSystemService(infService); |
18 |
editText
= (EditText) findViewById(R.id.editText); |
19 |
clearButton
= (Button) findViewById(R.id.clearButton); |
使用:在activity_main.xml
<com.example.customview.MyCompoundView android:layout_width="match_parent" android:layout_height="wrap_content" />
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,使用裁剪還是滾動等技術。
接下來的框架代碼給出了處理View測量的典型實現:
02 |
protected void onMeasure( int widthMeasureSpec, int heightMeasureSpec)
{ |
03 |
int measuredHeight
= measureHeight(heightMeasureSpec); |
04 |
int measuredWidth
= measureWidth(widthMeasureSpec); |
06 |
setMeasuredDimension(measuredHeight,
measuredWidth); |
09 |
private int measureHeight( int measureSpec)
{ |
10 |
int specMode
= MeasureSpec.getMode(measureSpec); |
11 |
int specSize
= MeasureSpec.getSize(measureSpec); |
16 |
if (specMode
== MeasureSpec.AT_MOST) { |
22 |
} else if (specMode
== MeasureSpec.EXACTLY) { |
29 |
private int measureWidth( int measureSpec)
{ |
總結:
通過
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
這2個值,然後計算自己想要佔有的寬和高。
2.onDraw
這個不解釋了。(後期會細說canvas和paint)