標籤:自訂view onsizechanged onmeasure porterduffxfermode
最近在項目中寫了一個自訂的倒計時控制項,效果是倒計時開始後,紅心逐漸被填充滿。效果如:
分為兩部分:計時器和繪製Bitmap。
計時器使用Timer和TimerTask,每個一秒執行一次TimerTask的run函數,使控制項重繪。代碼如下:
mTimer = new Timer();mTimerTask = new TimerTask() {@Overridepublic void run() {postInvalidate();synchronized (this) {if (index > 59) {index = 1;mTimer.cancel();}index++;}}};mTimer.schedule(mTimerTask, 1000, 1000);
繪製的思路大概是:
1.從圖片資源中解析得到Bitmap,獲得其Width和Height;
2.重載onMeasure和onSizeChanged函數,設定並得到控制項的寬和高;
3.使用PorterDuffXfermode圖形混合模式來得到所需的Bitmap;
4.重載onDraw函數,在函數中,將上一步所得到的Bitmap縮放至控制項大小以顯示出來。
下面我們來看一下幾個重要部分,其餘代碼最後會附上。
1.重載onMeasure函數完成控制項大小的測量
@Overridepublic void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int resultW = 0;if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {resultW = MeasureSpec.getSize(widthMeasureSpec);} else {if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {resultW = Math.min(bWidth,MeasureSpec.getSize(widthMeasureSpec));}}int resultH = 0;if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {resultH = MeasureSpec.getSize(heightMeasureSpec);} else {if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {resultH = Math.min(bHeight,MeasureSpec.getSize(heightMeasureSpec));}}setMeasuredDimension(resultW, resultH);}
如果我在xml檔案中設定如下:
<com.example.grownheart.GrownHeart android:id="@+id/grownHeart" android:layout_width="100dp" android:layout_height="100dp" />
我們對控制項的寬和高設定的是具體的值:100dp,那麼onMeasure函數在測量控制項的寬高時所得的 widthMeasureSpec/heightMeasureSpec的getMode就是
MeasureSpec.EXACTLY,則控制項的寬高就是getSize,就是我們設定的100dp。
如果我們在xml中配置如下:
<com.example.grownheart.GrownHeart android:id="@+id/grownHeart2" android:layout_width="wrap_content" android:layout_height="wrap_content"/>
那麼widthMeasureSpec/heightMeasureSpec的getMode就是MeasureSpec.AT_MOST,這時候控制項的寬高就是圖片資源寬(或高)與父容器中剩餘寬(或高)兩者中比較小
的那個。
2.在onSizeChanged函數中獲得控制項的寬和高
@Overridepublic void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);width = w;height = h;bm = Bitmap.createBitmap(bWidth, bHeight, Bitmap.Config.ARGB_8888);mCanvas = new Canvas(bm);}
參數中的int w和int h,分別是控制項的寬,高。
3.在onDraw中通過圖形的混合得到期望的Bitmap。
@Overridepublic void onDraw(Canvas canvas) {canvas.drawBitmap(Bitmap.createScaledBitmap(makeBitmap(), width, height, true),0, 0, mPaint);}// 繪製Bitmappublic Bitmap makeBitmap() {// 先繪製底層圖片mCanvas.drawBitmap(charm, 0, 0, mPaint);int i = mCanvas.saveLayer(0, 0, bm.getWidth(), bm.getHeight(), null,Canvas.ALL_SAVE_FLAG);mPaint.setColor(Color.RED);Log.i("GrownHeart", "onDraw:index=" + index);mCanvas.drawRect(new RectF(0f, (60 - index) * H, bm.getWidth(), bm.getHeight()),mPaint);mPaint.setXfermode(modeIn);mCanvas.drawBitmap(charm_on, 0, 0, mPaint);mPaint.setXfermode(null);mCanvas.restoreToCount(i);return bm;}
邊框圖和實心圖如下:
首先先將邊框圖繪製到Bitmap中,然後建立Canvas圖層,在該圖層上繪製紅色矩形,該矩形的高度每次是改變的。然後設定Piant的圖形混合模式,mPaint.setXfermode(new
PorterDuffXfermode(PorterDuff.Mode.DST_IN));其次將實心圖繪製到圖層中,這樣就能得到重疊地區。然後將圖層上所繪製的restore。最後通過createScaledBitmap將
Bitmap縮放至控制項大小並顯示。
原始碼
public class GrownHeart extends View {public Timer mTimer;public TimerTask mTimerTask;public int bWidth;// Bitmap寬度public int bHeight;// Bitmap高度public int width;// 控制項寬度public int height;// 控制項高度public Bitmap charm;// 資源位元影像public Bitmap charm_on;// 資源位元影像public Bitmap bm;public Canvas mCanvas;public Paint mPaint;public float H;private static int index;public static final PorterDuffXfermode modeIn;public static final PorterDuffXfermode modeOut;static {modeIn = new PorterDuffXfermode(PorterDuff.Mode.DST_IN);modeOut = new PorterDuffXfermode(PorterDuff.Mode.SRC_IN);}public GrownHeart(Context context) {super(context);init();}public GrownHeart(Context context, AttributeSet attrs) {super(context, attrs);init();}public void init() {mTimer = new Timer();mTimerTask = new TimerTask() {@Overridepublic void run() {postInvalidate();synchronized (this) {if (index > 59) {index = 1;mTimer.cancel();}Log.i("GrownHeart", "TimerTask1:index=" + index);index++;Log.i("GrownHeart", "TimerTask2:index=" + index);}}};charm = BitmapFactory.decodeResource(getResources(),R.drawable.chatroom_charm).copy(Bitmap.Config.ARGB_8888, true);charm_on = BitmapFactory.decodeResource(getResources(),R.drawable.chatroom_charm_on).copy(Bitmap.Config.ARGB_8888,true);bWidth = charm_on.getWidth();bHeight = charm_on.getHeight();H = bHeight / 60F;index = 1;mPaint = new Paint();mPaint.setAntiAlias(true);mPaint.setFilterBitmap(false);}public void startTimer() {mTimer.schedule(mTimerTask, 1000, 1000);}@Overridepublic void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int resultW = 0;if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) {resultW = MeasureSpec.getSize(widthMeasureSpec);} else {if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST) {resultW = Math.min(bWidth,MeasureSpec.getSize(widthMeasureSpec));}}int resultH = 0;if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {resultH = MeasureSpec.getSize(heightMeasureSpec);} else {if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST) {resultH = Math.min(bHeight,MeasureSpec.getSize(heightMeasureSpec));}}setMeasuredDimension(resultW, resultH);}@Overridepublic void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);width = w;height = h;bm = Bitmap.createBitmap(bWidth, bHeight, Bitmap.Config.ARGB_8888);mCanvas = new Canvas(bm);}// 繪製Bitmappublic Bitmap makeBitmap() {// 先繪製底層圖片mCanvas.drawBitmap(charm, 0, 0, mPaint);int i = mCanvas.saveLayer(0, 0, bm.getWidth(), bm.getHeight(), null,Canvas.ALL_SAVE_FLAG);mPaint.setColor(Color.RED);Log.i("GrownHeart", "onDraw:index=" + index);mCanvas.drawRect(new RectF(0f, (60 - index) * H, bm.getWidth(), bm.getHeight()),mPaint);mPaint.setXfermode(modeIn);mCanvas.drawBitmap(charm_on, 0, 0, mPaint);mPaint.setXfermode(null);mCanvas.restoreToCount(i);return bm;}@Overridepublic void onDraw(Canvas canvas) {canvas.drawBitmap(Bitmap.createScaledBitmap(makeBitmap(), width, height, true),0, 0, mPaint);}}
<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:gravity="center" tools:context="com.example.grownheart.MainActivity" > <com.example.grownheart.GrownHeart android:id="@+id/grownHeart" android:layout_width="wrap_content" android:layout_height="wrap_content"/> </RelativeLayout>
public class MainActivity extends Activity {@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);GrownHeart grownHeart=(GrownHeart)findViewById(R.id.grownHeart);grownHeart.startTimer();}}
Android自訂控制項實現