Android custom control implementation
Recently, I wrote a custom countdown control in the project. The result is that after the countdown starts, the red heart gradually fills up. The effect is as follows:
There are two parts: timer and Bitmap.
The Timer uses Timer and TimerTask to execute the run function of TimerTask every second to re-paint the control. The Code is as follows:
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);
The idea of drawing is probably:
1. parse the Bitmap from the image resource to obtain its Width and Height;
2. Reload the onMeasure and onSizeChanged functions to set and obtain the width and height of the control;
3. Use porterduxfermode to obtain the Bitmap;
4. Reload the onDraw function. In the function, scale the Bitmap obtained in the previous step to the control size to display it.
Next let's take a look at several important parts, and the rest of the code will be attached at the end.
1. Reload the onMeasure function to measure the widget size.
@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);}
If I set the following in the xml file:
We set the width and height of the control to a specific value: 100dp, so the getMode of widthMeasureSpec/heightMeasureSpec obtained by the onMeasure function when the width of the measurement control is high is
MeasureSpec. EXACTLY, the width and height of the control is getSize, which is the configured 100dp.
If we configure the following in xml:
In this case, the getMode of widthMeasureSpec/heightMeasureSpec is MeasureSpec. AT_MOST. In this case, the width and height of the control are smaller than those of the image resource width (or height) and the remaining width (or height) in the parent container.
.
2. Obtain the width and height of the control in the onSizeChanged function.
@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 and int h are the width and height of the control, respectively.
3. In onDraw, the expected Bitmap is obtained through the combination of images.
@ Overridepublic void onDraw (Canvas canvas) {canvas. drawBitmap (Bitmap. createScaledBitmap (makeBitmap (), width, height, true), 0, 0, mPaint);} // draw Bitmappublic Bitmap makeBitmap () {// draw the underlying image mCanvas first. 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 ;}
The border chart and solid chart are as follows:
First, draw the border image to Bitmap, then create a Canvas layer, and draw a red rectangle on the layer. The height of the rectangle changes each time. Then set the Piant image mixed mode, mPaint. setXfermode (new
Porterduxfermode (PorterDuff. Mode. DST_IN). Then, draw the solid graph into the layer to obtain the overlapping area. Then, store the restore drawn on the layer. Finally, use createScaledBitmap
The Bitmap is scaled to the control size and displayed.
Source code
Public class GrownHeart extends View {public Timer mTimer; public TimerTask mTimerTask; public int bWidth; // Bitmap width public int bHeight; // Bitmap height public int width; // control width public int height; // control height public Bitmap charm; // resource Bitmap public Bitmap charm_on; // resource Bitmap public Bitmap bm; public Canvas mCanvas; public Paint mPaint; public float H; private static int index; public static final porterduxfermode modeIn; public static final porterduxfermode modeOut; static {modeIn = new porterduxfermode (PorterDuff. mode. DST_IN); modeOut = new porterduxfermode (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,100 0) ;}@ 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);} // draw Bitmappublic Bitmap makeBitmap () {// draw the underlying image mCanvas first. 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 );}}
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();}}