實現一個最簡單的自訂視圖(不包含處理使用者的觸摸事件),往往只需要三步
一、繼承自一個View(可以是一個更具體的Android已實現好的View),並增加必須的構造方法(這個根據自訂View怎麼使用來判斷)
二、覆寫onDraw(Canvas canvas)方法,並且自訂繪製 三、重繪,分為即時重繪和屬性更新重繪
自訂視圖如果是繼承自原始View的話,public View(android.content.Context context)這個構造方法是必須的。而public View(android.content.Context context, android.util.AttributeSet attrs)這個構造方法是可選,但如果你想在xml布局檔案中使用自訂視圖的話,帶屬性的建構函式也是必須的,因為Android系統是根據這個建構函式去執行個體化視圖的,這樣也可以讓我們的視圖可視化,配置的屬性值也能夠根據它來獲得 關於自訂繪製,需要注意的是Android架構已經為我們提供了很多的方法來繪製各種圖元,直接使用就行了,要記住的是自訂視圖並不會自己主動的去重繪自己,首次顯示會繪製一次,往往需要我們去通知它進行重繪,可以在視圖上調用invalidate()和postInvalidate()來通知重繪,兩者的區別稍後會進行解釋 重繪,有些視圖往往需要即時的進行重繪,像一些即時性很強的遊戲,但這個一般需要使用到SurfaceView,有興趣的話可以去學習下,我們今天的例子也需要進行即時重繪,但沒有使用到SurfaceView,而是繼承於View。有的時候可能並不需要即時進行重繪,像只有在改變了視圖的一個屬性值的情況下才需要重新繪製,這個時候我們可以在setXX()方法中調用invalidate()方法就行
下面是一個等待進度條的例子
package tu.bingbing.customdialogview.view;import tu.bingbing.customdialogview.R;import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.Canvas;import android.graphics.Paint;import android.util.AttributeSet;import android.view.View;public class CustomDialogView extends View { // 載入圖片資源id,存入緩衝數組 private final int[] ids = new int[] { R.drawable. loading01, R.drawable. loading02, R.drawable.loading03, R.drawable.loading04 , R.drawable. loading05, R.drawable.loading06, R.drawable.loading07 , R.drawable. loading08, R.drawable.loading09, R.drawable.loading10 , R.drawable. loading11 }; private Bitmap[] loadingImgs ; private Paint loadingImagePaint ; private int currentIdsIndex = 0; public CustomDialogView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public CustomDialogView(Context context) { super(context); init(); } private void init() { // 執行個體化畫筆 loadingImagePaint = new Paint(); // 設定消除鋸齒 loadingImagePaint.setAntiAlias(true); // 一次性放進緩衝數組中 loadingImgs = new Bitmap[] { BitmapFactory. decodeResource(getResources(), ids[0]), BitmapFactory. decodeResource(getResources(), ids[1]), BitmapFactory. decodeResource(getResources(), ids[2]), BitmapFactory. decodeResource(getResources(), ids[3]), BitmapFactory. decodeResource(getResources(), ids[4]), BitmapFactory. decodeResource(getResources(), ids[5]), BitmapFactory. decodeResource(getResources(), ids[6]), BitmapFactory. decodeResource(getResources(), ids[7]), BitmapFactory. decodeResource(getResources(), ids[8]), BitmapFactory. decodeResource(getResources(), ids[9]), BitmapFactory. decodeResource(getResources(), ids[10]) }; } @Override protected void onDraw(Canvas canvas) { // 迴圈控制每一張圖片的繪製順序,讓看起來像是播放動畫 if (currentIdsIndex >= (ids .length - 1)) { currentIdsIndex = 0; } Bitmap currentLoadingBitmap = loadingImgs[currentIdsIndex ]; // 繪製圖片,顯示在螢幕正中 canvas.drawBitmap(currentLoadingBitmap, (getWidth() - currentLoadingBitmap.getWidth())/2, (getHeight() - currentLoadingBitmap.getHeight())/2, loadingImagePaint ); currentIdsIndex++; super.onDraw(canvas); }}
在View上getWidth()和getHeight()方法取到的是螢幕的高和寬,BitmapFactory.decodeResource(Resources res,
int id)方法根據資源id得到一張位元影像用於顯示,關於是否應該把所有的圖片一次性的載入入記憶體,這個可以根據需要來定,你可以一次性全部載入,可以得到更流暢的UI,但更耗損記憶體;你也可以到在要用到的時候再載入,這樣可能會有一定的延遲,但可以有效節省記憶體;也可以使用軟引用來儲存圖片,這樣可以使得在程式記憶體不足時,圖片記憶體得以回收,兩全其美。
在Activity中即時重繪視圖
package tu.bingbing.customdialogview;import tu.bingbing.customdialogview.view.CustomDialogView;import android.os.Bundle;import android.app.Activity;import android.view.Menu;public class MainActivity extends Activity { private CustomDialogView customDialogView ; private RedrawCustomDialogViewThread redrawCdvRunnable ; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout. activity_main); customDialogView = (CustomDialogView) findViewById(R.id.view_customdialog ); redrawCdvRunnable = new RedrawCustomDialogViewThread(); new Thread(redrawCdvRunnable ).start(); } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu; this adds items to the action bar if it is present. getMenuInflater().inflate(R.menu. main, menu); return true ; } final class RedrawCustomDialogViewThread implements Runnable{ private boolean isRun = true; @Override public void run() { while(isRun ){ try { Thread. sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // 通知重繪 customDialogView.postInvalidate(); } } public boolean isRun() { return isRun ; } public void setRun(boolean isRun) { this.isRun = isRun; } } @Override protected void onDestroy() { redrawCdvRunnable.setRun(false ); super.onDestroy(); }}
布局檔案
<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" 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=".MainActivity" > <tu.bingbing.customdialogview.view.CustomDialogView android:id="@+id/view_customdialog" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </RelativeLayout>
除了使用postInvalidate()方法外,還可以使用invalidate()方法,前者讓程式可以在UI主線程外的線程中去通知視圖進行重繪,後者必須在UI主線程中進行調用通知,一般可以和Handler一起使用
使用到的圖片資源
程式運行效果