Android自訂view通過繼承系統的View並重寫部分方法來滿足自己的特定需要。首先我們來看一下都有哪些方法可能需要被重寫:
onMeasure() 檢測View組件及其子組件的大小
onLayout() 當該組件需要分配其子組件的位置、大小時
onTouchEvent 當發生觸屏事件時
onDraw() 當組件將要繪製它的內容時
onKeyDown 當按下某個鍵盤時
onKeyUp 當鬆開某個鍵盤時
onTrackballEvent 當發生軌跡球事件時
onSizeChange() 當該組件的大小被改變時
onFinishInflate() 回調方法,當應用從XML載入該組件並用它構建介面之後調用的方法
onWindowFocusChanged(boolean) 當該組件得到、失去焦點時
onAtrrachedToWindow() 當把該組件放入到某個視窗時
onDetachedFromWindow() 當把該組件從某個視窗上分離時觸發的方法
onWindowVisibilityChanged(int): 當包含該組件的視窗的可見度發生改變時觸發的方法
紅色標註的部分是我們經常需要重寫的函數。具體的實現我們舉一個簡單的例子來說明,首先上效果圖:
圓形和文字跟隨觸摸事件移動的一個簡單的自訂view
實現上面的效果我們大致需要分成這幾步
在res/values/ 下建立一個attrs.xml 來聲明自訂view的屬性
一個繼承View並複寫部分函數的自訂view的類
一個展示自訂view 的容器介面
我們的view 叫做myView,一定要和我們的class檔案名稱相同。它有一個屬性值,格式為color
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="myView">
<attr name="TextColor" format="color"/>
</declare-styleable>
</resources>
2.在自訂view類中實現其建構函式(用於初始獲得view的屬性配置)和複寫onDraw和onTouchEvent。
public class myView extends View{
//定義畫筆和初始位置
Paint p = new Paint();
public float currentX = 50;
public float currentY = 50;
public int textColor;
public myView(Context context, AttributeSet attrs) {
super(context, attrs);
//擷取資源檔裡面的屬性,由於這裡只有一個屬性值,不用遍曆數組,直接通過R檔案拿出color值
//把屬性放在資源檔裡,方便設定和複用
TypedArray array = context.obtainStyledAttributes(attrs,R.styleable.myView);
textColor = array.getColor(R.styleable.myView_TextColor,Color.BLACK);
array.recycle();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//畫一個藍色的圓形
p.setColor(Color.BLUE);
canvas.drawCircle(currentX,currentY,30,p);
//設定文字和顏色,這裡的顏色是資源檔values裡面的值
p.setColor(textColor);
canvas.drawText("BY finch",currentX-30,currentY+50,p);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
currentX = event.getX();
currentY = event.getY();
invalidate();//重新繪製圖形
return true;
}
}
這裡思路很簡單,通過不斷的更新當前位置座標和重新繪製圖形實現效果,要注意的是使用TypedArray後一定要記得recycle(),否則會對下次調用產生影響。
除非你不會再用TypedArray.
3.我們把myView放在activity_main.xml裡面,當然也可以在代碼中通過addview函數加到布局中。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:myview="http://schemas.android.com/apk/res-auto"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="finch.scu.cn.myview.MainActivity">
<finch.scu.cn.myview.myView
android:layout_width="match_parent"
android:layout_height="match_parent"
myview:TextColor="#ff0000"
/>
</RelativeLayout>
這裡 xmlns:自訂控制項的首碼="http://schemas.android.com/apk/res/包名(或res-auto)" , 首碼:TextColor="#ff0000"。如果不申明命名空間屬性就會
最後是MainActivity
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
具體的view要根據具體的需求來,比如我們要側滑刪除的listview我們可以繼承listview,監聽側滑事件,顯示刪除按鈕實現功能。
Android自訂View的實現
自訂View首先要實現一個繼承自View的類。添加類的構造方法,override父類的方法,如onDraw,(onMeasure)等。如果自訂的View有自己的屬性,需要在values下建立attrs.xml檔案,在其中定義屬性,同時代碼也要做修改。
一個簡單的例子:
·建立一個MyView類,繼承自TextView,並添加構造方法:
package com.example.xhelloworld;
import android.content.Context;
import android.widget.TextView;
public class MyView extends TextView{
public MyView(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
}
·再在主activity中調用。方法是setContentView(new MyView(this));這句
package com.example.xhelloworld;
import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;
public class NewView extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_newview);
setContentView(new MyView(this));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_newview, menu);
return true;
}
}
運行後的結果為:
這樣一個簡單的自訂View就可以使用了。可以改變一下背景顏色,在MyView類中添加:
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
super.onDraw(canvas);
canvas.drawColor(Color.BLUE);
}
即可完成。運行結果
上面的例子很簡單,沒有涉及到屬性的添加。使用範圍很小,不能在布局檔案中使用。如果要在布局檔案中用到,還需要添加一個構造方法:
public MyView(Context context,AttributeSet attrs){
super(context, attrs);
}
當然,上面只是在code中做的修改,在xml檔案(main.xml)中也需要進行如下操作:
<com.example.xhelloworld.NewView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
至少在xml檔案中寫上上面的內容。其中com.example.xhelloworld.NewView這句是需要顯示的控制項所代表的類。Com.example.xhelloworld是類的包名,NewView是類名。這個類肯定是繼承自View的自訂類(其實就是,使我們自己寫的,這是廢話了。。。),可以是在工程中直接源碼添加xxxx.java的,也可以是在libs目錄下自己新添加的jar包裡面的。如果是jar包裡面的一個類,則路徑就是jar包裡面,這個類的路徑。
完成上面的兩步之後就可以在代碼中執行個體化這個布局檔案了
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
//setContentView(new MyView(this));
顯示的效果同上圖。
下面介紹如何?自訂View的屬性設定。實現自訂View的屬性設定,需要:
·在values目錄下建立attrs.xml檔案,添加屬性內容
·在布局檔案中添加新的命名空間xmlns,然後可以使用命名空間給自訂的空間設定屬性
·設定完屬性之後,當然還要對其進行處理。在自訂View類中的構造方法中進行處理
根據這三步給一個例子進行說明一下
首先添加attrs.xml檔案,在定義屬性
<resources>
<declare-styleable name="MyView">
<attr name="textColor" format="color"/>
<attr name="textSize" format="dimension"/>
declare-styleable>
resources>
然後在布局檔案中完成:
xmlns:my=http://schemas.android.com/apk/res/com.example.xhelloworld
<com.example.xhelloworld.MyView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
my:textColor="#FFFFFFFF"
my:textSize="22dp"
/>
註:這步我在實現的時候出錯,問題是顯示找不到屬性textColor和textSize,這奇怪的錯誤。解決方案是,在寫my:textColor="#FFFFFFFF" 時,寫到my之後,按alt+/,這是會自動添加一個xmlns,和my的路徑是一樣的,用產生的這個替換掉my就可以了。奇葩的問題就用奇葩的方法解決。起初我也不知道怎麼弄,瞎搞出來的。
最後在MyView.java中添加另一個構造方法,並添加代碼來處理從xml中獲得的屬性
public MyView(Context context,AttributeSet attrs){
super(context, attrs);
mPaint = new Paint();
//TypedArray是一個用來存放由context.obtainStyledAttributes獲得的屬性的數組
//在使用完成後,一定要調用recycle方法
//屬性的名稱是styleable中的名稱+“_”+屬性名稱
//TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.MyView);
int textColor = array.getColor(R.styleable.MyView_textColor, 0XFF00FF00); //提供預設值,放置未指定
float textSize = array.getDimension(R.styleable.MyView_textSize, 36);
mPaint.setColor(textColor);
mPaint.setTextSize(textSize);
array.recycle(); //一定要調用,否則這次的設定會對下次的使用造成影響
}
完成之後就已經實現了自定視圖的構造,自訂視圖屬性的添加很處理。現在完成的是一般的自訂視圖,繼承自TextView或者View等視圖,也就是通過程式主UI線程完成更新的視圖,如果是自訂SurfaceView,實現方法有所不同。
添加完之後肯定有很多疑問,自己去做可能還不能理解。這裡再對上面操作進行解釋說明。
背後的事
View類的構造方法:
·public view(Context context) 當在代碼中建立對象時會被調用
·public View (Context context, AttributeSet attrs)
官方的文檔是:
Constructor that is called when inflating a view from XML. This is called when a view is being constructed from an XML file, supplying attributes that were specified in the XML file. This version uses a default style of 0, so the only attribute values applied are those in the Context's Theme and the given AttributeSet
大致應該是這個方法是通過xml檔案來建立一個view對象的時候調用。很顯然xml檔案一般是布局檔案,就是現實控制項的時候調用,而布局檔案中免不了會有屬性的設定,如android:layout_width等,對這些屬性的設定對應的處理代碼也在這個方法中完成。
兩個參數
Context The Context the view is running in, through which it can access the current theme, resources, etc.
Attrs The attributes of the XML tag that is inflating the view
·public View (Context context, AttributeSet attrs,int defStyle)
Perform inflation from XML and apply a class-specific base style. This constructor of View allows subclasses to use their own base style when they are inflating. For example, a Button class's constructor would call this version of the super class constructor and supply R.attr.buttonStyle fordefStyle; this allows the theme's button style to modify all of the base view attributes (in particular its background) as well as the Button class's attributes.
看的不太懂,沒用到,下放一下吧額
這就是為什麼要添加上面的兩個構造方法的原因。