Android自定义控件实现

最近在项目中写了一个自定义的倒计时控件,效果是倒计时开始后,红心逐渐被填充满。效果如下图:

                                                  技术分享


分为两部分:计时器和绘制Bitmap。

计时器使用Timer和TimerTask,每个一秒执行一次TimerTask的run函数,使控件重绘。代码如下:

mTimer = new Timer();
		mTimerTask = new TimerTask() {
			@Override
			public 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函数完成控件大小的测量

@Override
	public 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函数中获得控件的宽和高

@Override
	public 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。

@Override
	public void onDraw(Canvas canvas) {
		canvas.drawBitmap(
				Bitmap.createScaledBitmap(makeBitmap(), width, height, true),
				0, 0, mPaint);
	}
	
	// 绘制Bitmap
	public 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() {
			@Override
			public 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);
	}

	@Override
	public 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);
	}

	@Override
	public 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);
	}

	// 绘制Bitmap
	public 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;
	}

	@Override
	public 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 {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		GrownHeart grownHeart=(GrownHeart)findViewById(R.id.grownHeart);
		grownHeart.startTimer();
	}

}



郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。