Android自己定义控件系列五:自己定义绚丽水波纹效果

尊重原创!转载请注明出处:http://blog.csdn.net/cyp331203/article/details/41114551


今天我们来利用Android自己定义控件实现一个比較有趣的效果:滑动水波纹。先来看看终于效果图:


图一


效果还是非常炫的;饭要一口口吃,路要一步步走,这里我们将整个过程分成几步来实现


一、实现单击出现水波纹单圈效果:



图二


照例来说,还是一个自己定义控件,这里我们直接让这个控件撑满整个屏幕(对自己定义控件不熟悉的能够參看我之前的一篇文章:Android自己定义控件系列二:自己定义开关button(一))。观察这个效果,发现应该须要重写onTouchEvent和onDraw方法,通过在onTouchEvent中获取触摸的坐标,然后以这个坐标值为圆心来绘制我们须要的图形,这个绘制过程就是调用的onDraw方法。


1、新建一个project,定义一个WaterWave的类,继承自View,作为一个自己定义控件;在清单文件里将这个自己定义控件写出来,直接填满父窗口。


2、在WaterWave类中,实现它的两參构造函数:

package com.example.waterwavedemo.ui;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;


public class WaterWave extends View {
	...
	/*
	 * 1、两參构造函数
	 */
	public WaterWave(Context context, AttributeSet attrs) {
		super(context, attrs);
		alpha = 0;
		radius = 0;
		initPaint();
	}
	...
}



3、要使用自己定义控件,那么一般都须要指定它的大小,这里我们因为仅仅须要其填满窗口,所以使用默认的onMeasure方法就可以:


/**
	 * onMeasure方法,确定控件大小,这里使用默认的
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// TODO Auto-generated method stub
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}



4、将这个自己定义图形画出来,重写onDraw方法,在这里因为我们须要画一个圈,所以这样写:


	@Override
	/**
	 * 画出须要的图形的方法,这种方法比較关键
	 */
	protected void onDraw(Canvas canvas) {
		canvas.drawCircle(xDown, yDown, radius, paint);

	}



当中的參数xDown和yDown是成员变量,代表按下时的x和y坐标,这个坐标所相应的点就是要绘制的圆环的圆心;radius參数也是成员变量,代表要绘制的圆环的半径;


看到这里还须要一个paint,是Paint类型的画笔对象,这里先将其定义成一个成员变量,因为onDraw方法在第一次自己定义控件显示的时候就会被调用,所以这个paint须要我们在两參的构造函数中就进行初始化,否则会报出空指针异常;那么我们这里另外写一个initPaint()方法来初始化我们的paint:

	/**
	 * 初始化paint
	 */
	private void initPaint() {
		/*
		 * 新建一个画笔
		 */
		paint = new Paint();

		paint.setAntiAlias(true);
		paint.setStrokeWidth(width);

		// 设置是环形方式绘制
		paint.setStyle(Paint.Style.STROKE);

		System.out.println("alpha=" + alpha);
		paint.setAlpha(alpha);
		System.out.println("得到的透明度:" + paint.getAlpha());

		paint.setColor(Color.RED);
	}


5、触摸定时刷新

在onDraw方法之后,我们已经能够画出这个圆环了,可是实际问题是,我们想要实现点击的时候才在点击的位置来画一个圆环,那么我们肯定须要获得点击的时候的坐标xDown和yDown,所以肯定须要重写onTouchEvent方法,另外我们须要在按下的时候,让透明度是最不透明(alpha=255),在绘制的过程中,让圆环的半径(radius)不断扩大,同一时候让透明度不断减小,直至全然透明(alpha=0),这个不断变化的过程又须要每隔一段时间又一次刷新状态和又一次绘制图形,所以我们这里使用handler来处理:


@Override
	/**
	 * 触摸事件的方法
	 */
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			radius = 0;
			alpha = MAX_ALPHA;
			width = radius / 4;
			xDown = (int) event.getX();
			yDown = (int) event.getY();

			handler.sendEmptyMessage(0);

			break;
		case MotionEvent.ACTION_MOVE:

			break;
		case MotionEvent.ACTION_UP:

			break;

		default:
			break;
		}

		return true;
	}



能够看到,我们这里先仅仅实现了ACTION_DOWN里面的逻辑,在每个按下的时候将半径radius设置为0,透明度alpha设置为全然不透明,而宽度也为0,而且获取按下的x和y坐标,之后就使用handler发送了一个空消息,让handler去实现定时刷新状态和绘制图形的工作,我们想让圆环的透明度alpha捡到0的时候就不再继续定时自己主动刷新了,否则在每一次handleMessage的时候都先刷新状态值,然后绘制图形:


	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			switch (msg.what) {
			case 0:
				flushState();

				invalidate();

				if (alpha != 0) {
					// 假设透明度没有到0,则继续刷新,否則停止刷新
					handler.sendEmptyMessageDelayed(0, 50);
				}

				break;

			default:
				break;
			}
		}

		/**
		 * 刷新状态
		 */
		private void flushState() {
			radius += 5;
			alpha -= 10;
			if (alpha < 0) {
				alpha = 0;
			}
			// System.out.println("alpha=" + alpha);
			width = radius / 4;

			paint.setAlpha(alpha);
			paint.setStrokeWidth(width);
		}

	};




我们能够看到,在handler中,我们重写了handleMessage方法,在msg.what=0的时候,我们调用flushState()方法来刷新状态,和invalidate()方法来绘制图形,,然后使用handler.sendEmptyMessageDelayed(0, 50);来每隔50毫秒反复一次上面的工作;当中invalidate()是Android提供的,而flushState()则须要我们自己来实现;

依照我们的需求,每一次状态的刷新工作flushState(),我们须要做例如以下几件事:

(1)让半径添加

(2)让透明度降低,并设置给paint;

(3)环形的宽度添加,并设置给paint

(4)对于透明度而言,最大值是255,可是这里假设让透明度降低到0下面,比方说-1,那么实际上alpha的值不会是-1,而是255+(-1)=254,所以我们还须要加一个推断条件,防止alpha<0


/**
		 * 刷新状态
		 */
		private void flushState() {
			radius += 5;
			alpha -= 10;
			if (alpha < 0) {
				alpha = 0;
			}
			// System.out.println("alpha=" + alpha);
			width = radius / 4;

			paint.setAlpha(alpha);
			paint.setStrokeWidth(width);
		}



6、在两參的构造函数中加入一些初始化工作:


public WaterWave(Context context, AttributeSet attrs) {
		super(context, attrs);
		alpha = 0;
		radius = 0;
		initPaint();
	}



至此,我们的第一步就基本完毕了


二、实现多次点击圆环同一时候存在,同一时候刷新效果:


从面图二中,我们不难发现,不论怎样点击,屏幕上都仅仅会同一时候存在一个圆圈的效果,这是由于我们每次点击的时候,都又一次设置了圆心,并且全部圆形的參数都是成员变量,都是共享的;不仅如此,假设在上一个圆圈没有消失的时候,就再次点击,会让新出现的圆圈变大的速度大大添加,这是由于使用handler.sendEmptyMessageDelayed(0,50)方法的原因,第二次点击时会反复触发这种方法,使得前后两次点击的handler.sendEmptyMessageDelayed()重叠生效,让实际间隔远远小于50毫秒,所以刷新速度快了非常多

那么我们如今就要解决上面两个小问题,实现例如以下图的效果:


解决这两个小问题的思路:

1、针对全部水波纹圆圈共享參数的问题:

方法就是新建一个内部类Wave,用于存放每个圆圈的參数,每个圆圈都相应一个Wave对象,然后在onDraw方法里面,同一时候重绘全部的圆圈视图;那么这里就还须要一个List集合waveList,用于存放全部的wave对象,方便遍历。


2、针对handler.sendEmptyMessageDelayed方法在兴许点击的时候不断被调用,导致刷新越来越快的问题。

这里能够设置一个成员变量 boolean isStart;来标志是不是第一次按下;由于我们在第一次按下的时候,肯定是希望開始定时刷新,调用handler.sendEmptyMessageDelayed,让圆环的状态不断变化。可是对于之后的点击,我们事实上仅仅希望它立马被刷新一次,并被增加到waveList集合中,而并不须要发送一个handler的信息来调用handler.sendEmptyMessageDelayed。所以在一開始的时候我们将其设置为true,而在第一次点击时候将其设置为false,那么在什么时候将其设置为false呢,这里牵涉到第三个问题:


3、对于waveList集合而言,假设一直点击往集合里面加入Wave对象,那么无疑会让这个集合越来越大,这个是我们不希望看到的。

我们希望在圆环的透明度值alpha变为0,也就是全然透明的时候,让其从waveList中remove掉,让其能被垃圾回收回收掉,这样假设点击几个点之后停止,点都会自己主动消失(alpha值减到0),那么相应的Wave对象也会从waveList被移除,waveList的大小也会变成0,这个时候我们就能够停止handler.sendEmptyMessageDelayed方法继续被调用,同一时候能够将isStart又一次设为true。那么isStart何时设为false呢?我们能够在flushState刷新状态的时候将其设为false,由于刷新状态的时候表明第一次点击已经按下了。然后在onTouchEvent方法的ACTION_DWON条件下,假设isStart为true才发送handler的消息,这代表第一次点击,之后再点击也不会发送而仅仅是将wave对象加入到waveList中,由于第一次的时候调用flushState已经将isStart置为false了。

因为修改较大,代码例如以下:

package com.example.waterwavedemo.ui;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class WaterWave extends View {

	/**
	 * 波形的List
	 */
	private List<Wave> waveList;

	/**
	 * 最大的不透明度,全然不透明
	 */
	private static final int MAX_ALPHA = 255;

	protected static final int FLUSH_ALL = -1;

	private boolean isStart = true;

	// /**
	// * 按下的时候x坐标
	// */
	// private int xDown;
	// /**
	// * 按下的时候y的坐标
	// */
	// private int yDown;
	// /**
	// * 用来表示圆环的半径
	// */
	// private float radius;
	// private int alpha;

	/*
	 * 1、两參构造函数
	 */
	public WaterWave(Context context, AttributeSet attrs) {
		super(context, attrs);
		waveList = Collections.synchronizedList(new ArrayList<Wave>());
	}

	/**
	 * onMeasure方法,确定控件大小,这里使用默认的
	 */
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	/**
	 * 画出须要的图形的方法,这种方法比較关键
	 */
	protected void onDraw(Canvas canvas) {
		// 重绘全部圆环
		for (int i = 0; i < waveList.size(); i++) {
			Wave wave = waveList.get(i);
			canvas.drawCircle(wave.xDown, wave.yDown, wave.radius, wave.paint);
		}

	}

	/**
	 * 初始化paint
	 */
	private Paint initPaint(int alpha, float width) {
		/*
		 * 新建一个画笔
		 */
		Paint paint = new Paint();

		paint.setAntiAlias(true);
		paint.setStrokeWidth(width);

		// 设置是环形方式绘制
		paint.setStyle(Paint.Style.STROKE);

		// System.out.println("alpha=" + alpha);
		paint.setAlpha(alpha);
		// System.out.println("得到的透明度:" + paint.getAlpha());

		paint.setColor(Color.RED);
		return paint;
	}

	private Handler handler = new Handler() {

		@Override
		public void handleMessage(Message msg) {
			super.handleMessage(msg);
			switch (msg.what) {
			case 0:
				flushState();

				invalidate();

				if (waveList != null && waveList.size() > 0) {
					handler.sendEmptyMessageDelayed(0, 50);
				}

				break;

			default:
				break;
			}
		}

	};

	/**
	 * 刷新状态
	 */
	private void flushState() {
		for (int i = 0; i < waveList.size(); i++) {
			Wave wave = waveList.get(i);
			if (isStart == false && wave.alpha == 0) {
				waveList.remove(i);
				wave.paint = null;
				wave = null;
				continue;
			} else if (isStart == true) {
				isStart = false;
			}
			wave.radius += 5;
			wave.alpha -= 10;
			if (wave.alpha < 0) {
				wave.alpha = 0;
			}
			wave.width = wave.radius / 4;
			wave.paint.setAlpha(wave.alpha);
			wave.paint.setStrokeWidth(wave.width);
		}

	}

	// private Paint paint;
	// private float width;

	@Override
	/**
	 * 触摸事件的方法
	 */
	public boolean onTouchEvent(MotionEvent event) {
		super.onTouchEvent(event);

		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			Wave wave = new Wave();
			wave.radius = 0;
			wave.alpha = MAX_ALPHA;
			wave.width = wave.radius / 4;
			wave.xDown = (int) event.getX();
			wave.yDown = (int) event.getY();
			wave.paint = initPaint(wave.alpha, wave.width);
			if (waveList.size() == 0) {
				isStart = true;
			}
			System.out.println("isStart=" + isStart);
			waveList.add(wave);
			// 点击之后刷洗一次图形
			invalidate();
			if (isStart) {
				handler.sendEmptyMessage(0);
			}
			break;
		case MotionEvent.ACTION_MOVE:

			break;
		case MotionEvent.ACTION_UP:

			break;

		default:
			break;
		}

		return true;
	}

	private class Wave {
		int waveX;
		int waveY;
		/**
		 * 用来表示圆环的半径
		 */
		float radius;
		Paint paint;
		/**
		 * 按下的时候x坐标
		 */
		int xDown;
		/**
		 * 按下的时候y的坐标
		 */
		int yDown;
		float width;
		int alpha;
	}

}


三、实现全然效果(点击和移动,颜色随机,圆圈大小变化速度)

效果图就是跟图一的一样了,主要做几个小地方:

1、让onTouchEvent里面的ACTION_DOWN和ACTION_MOVE响应相同的事件,实际上就是去掉ACTION_DOWN的break;然后将处理代码写到随后的ACTION_MOVE中去就可以


2、新建一个成员变量数组colors,里面放自己想要的颜色,然后在initPaint方法的设置color的时候,使用paint.setColor(colors[(int) (Math.random() * (colors.length - 1))]);


3、控制波形的变化趋势,这个看个人爱好,我是这样做的:在flushState中:


wave.radius += waveList.size() - i;
wave.width = (wave.radius / 3);
wave.paint.setStrokeWidth(wave.width);

// wave.alpha -= 10;
if (wave.alpha < 0) {
	wave.alpha = 0;
}
// wave.width = wave.radius / 4;
wave.paint.setAlpha(wave.alpha);

至此,就完毕了自己定义的水波纹效果了。存在的问题就是,假设在模拟器上,高速滑动,会有卡顿,在我的手机Nexus5上,还算流畅,应该跟内存无关,兴许可能还会做一些优化。

posted @ 2015-02-20 20:41  mfrbuaa  阅读(217)  评论(0编辑  收藏  举报