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();
	}

}

时间: 2024-11-06 15:00:45

Android自定义控件实现的相关文章

Android自定义控件之自定义组合控件(三)

前言: 前两篇介绍了自定义控件的基础原理Android自定义控件之基本原理(一).自定义属性Android自定义控件之自定义属性(二).今天重点介绍一下如何通过自定义组合控件来提高布局的复用,降低开发成本,以及维护成本. 使用自定义组合控件的好处? 我们在项目开发中经常会遇见很多相似或者相同的布局,比如APP的标题栏,我们从三种方式实现标题栏来对比自定义组件带来的好处,毕竟好的东西还是以提高开发效率,降低开发成本为导向的. 1.)第一种方式:直接在每个xml布局中写相同的标题栏布局代码 <?xm

android自定义控件实现TextView按下后字体颜色改变

今天跟大家分享一下Android自定义控件入门,先介绍一个简单的效果TextView,按下改变字体颜色,后期慢慢扩展更强大的功能 直接看图片             第一张是按下后截的图,功能很简单,也很容易实现,下面来看一下如何通过重写TextView来实现 一共三个文件  TextViewM.java,MainActivity.java,activity_textview.xml TextViewM.java 1 package landptf.control; 2 3 import and

android 自定义控件---圆形方向盘

在做Android平台开发的时候,经常会遇到安卓原生控件无法满足需求的情况,安卓允许开发者去继承已经存在的控件或者实现你自己的控件. 先来看一下效果图 采用直接集成View类,重写onDrow方法绘制. 下面附上主要代码. 1 新建一个类CircleView 继承自View 1 package com.lennon.view; 2 3 import android.content.Context; 4 import android.graphics.Canvas; 5 import androi

Android自定义控件系列 十:利用添加自定义布局来搞定触摸事件的分发,解决组合界面中特定控件响应特定方向的事件

这个例子是比较有用的,基本上可以说,写完这一次,以后很多情况下,直接拿过来addView一下,然后再addInterceptorView一下,就可以轻轻松松的达到组合界面中特定控件来响应特定方向的触摸事件了. 请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45198549,非允许请勿用于商业或盈利用途,违者必究. 在写Android应用的过程之中,经常会遇到这样的情况:界面包含了多个控件,我们希望触摸在界面上的不

Android自定义控件,轻松实现360软件详情页

Android自定义控件,轻松实现360软件详情页在海军陆战队服役超过 10 年后,我于去年 7 月份退役了.随后在 8 月份找到了一份赌场的工作做公关,到今年 2 月中旬的时候又被辞退了.到 5 月中旬的时候我在 DE 协会找到了一份临时的"初级用户体验工程师"工作,而到了 8 月底我则成了正式的"用户体验工程师". 当我丢掉赌场的那份工作时,我就在想公关这行可能真的不适合我.我想做一名程序员.于是我开始节衣缩食学习编程.家人对我的情况非常担心.从 2 月份到 5

Android自定义控件_View的绘制流程

每一个View/ViewGroup的显示都会经过三个过程:1.measure过程(测量View显示的大小,位置):2.layout过程(布局view的位置):3.draw过程(上一篇文章说到的通过canvas绘制到界面上显示,形成了各色的View) 下面分析一下各个过程:measure过程: 因为DecorView实际上是派生自FrameLayout的类,也即一个ViewGroup实例,该ViewGroup内部的ContentViews又是一个ViewGroup实例,依次内嵌View或ViewG

Android自定义控件之滑动开关

自定义开关控件 Android自定义控件一般有三种方式 1.继承Android固有的控件,在Android原生控件的基础上,进行添加功能和逻辑. 2.继承ViewGroup,这类自定义控件是可以往自己的布局里面添加其他的子控件的. 3.继承View,这类自定义控件没有跟原生的控件有太多的相似的地方,也不需要在自己的肚子里添加其他的子控件. ToggleView自定义开关控件表征上没有跟Android原生的控件有什么相似的地方,而且在滑动的效果上也没有沿袭Android原生的地方,所以我们的自定义

一起来学习Android自定义控件1

概述 Android已经为我们提供了大量的View供我们使用,但是可能有时候这些组件不能满足我们的需求,这时候就需要自定义控件了.自定义控件对于初学者总是感觉是一种复杂的技术.因为里面涉及到的知识点会比较多.但是任何复杂的技术后面都是一点点简单知识的积累.通过对自定义控件的学习去可以更深入的掌握android的相关知识点,所以学习android自定义控件是很有必要的.记得以前学习总是想着去先理解很多知识点,然后再来学着自定义控件,但是每次写自定义控件的时候总是不知道从哪里下手啊.后来在学习的过程

Android 自定义控件之第三讲:obtainStyledAttributes 系列函数详解

在项目中开发自定义控件时,或多或少都会用到 obtainStyledAttributes(AttributeSet, int[], int, int) 或者 obtainAttributes(AttributeSet, int[]) 函数,它们的主要作用是:根据传入的参数,返回一个对应的 TypedArray ,如果小伙伴还没有看过 LZ 的第二讲,那么请自行移步 Android 自定义控件之第二讲:TypedArray 详解,好了,就先扯到这里,下面开始今天内容讲解: 获取 TypedArra

Android自定义控件 -Canvas绘制折线图(实现动态报表效果)

有时候我们在项目中会遇到使用折线图等图形,Android的开源项目中为我们提供了很多插件,但是很多时候我们需要根据具体项目自定义这些图表,这一篇文章我们一起来看看如何在Android中使用Canvas绘制折线图.先看看绘制的效果: 代码: public class MyView extends View { //坐标轴原点的位置 private int xPoint=60; private int yPoint=260; //刻度长度 private int xScale=8;  //8个单位构