Android开发笔记(一百三十一)水波图形与水波动画

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/aqi00/article/details/54849513

水波图形RippleDrawable

RippleDrawable是Android在5.0之后新增的图形类,它的作用是在点击时展示水波动画,从而提示用户在这里按压了屏幕。这个提示效果类似于状态图形StateListDrawable,区别在于,StateListDrawable使用一张静止图片表示按下状态,而RippleDrawable使用荡起涟漪的水波动画表示按压动作。


水波图形的用法很简单,先在xml文件中定义水波图形的规格,然后把视图的android:background属性设置为该图形,然后点击视图就会产生动画效果了。具体的水波样式主要有三种,说明如下:

1、没有边界限制的水波,这意味着允许水波动画充满整个视图,xml定义如下:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"  
    android:color="#ffaaaa"> 
</ripple>

下面是没有边界限制的水波效果截图:


2、有边界限制的水波,只能在规定范围内显示水波动画,范围边界由mask遮罩对象指定,xml定义如下:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"  
    android:color="#ffaaaa">
    <item  
        android:id="@android:id/mask"  
        android:drawable="@drawable/btn_nine_selector" />
</ripple>

下面是有边界限制的水波效果截图(无其它背景):


3、有边界限制的水波,且水波动画必须在指定的背景图形上显示,xml定义如下:
<ripple xmlns:android="http://schemas.android.com/apk/res/android"  
    android:color="#ffaaaa">
    <item android:drawable="@drawable/btn_nine_selector" />
</ripple>

下面是有边界限制的水波效果截图(有其它背景):


方式二与方式三看起来很像,展示效果却不一样。方式二的遮罩图形,只起到指定边界的作用,本身并没有显示出来;而方式三的背景图形,不但指定了水波的边界,而且背景自身也会显示在屏幕上。


水波动画RippleView

RippleDrawable只支持Android5.0以后的系统,如果想在4.*系统上也能展示水波动画效果,就得自己编写水波动画的控件了。


水波动画的实现思路不难,主要是以触摸点为圆心,间隔很短时间不停地向外画圆圈,从而产生水波荡漾的动画效果。但在具体编码的时候,尚有几个功能需要特别注意:
1、水波图案不能被子控件遮挡,所以不能在onDraw方法中绘制水波,只能在dispatchDraw方法中绘制;
2、与RippleDrawable一样,自定义的水波也要有边界限制,因此要调用Canvas的clipRect方法进行范围限定;
3、为了区别是否按压,在按下状态时,应保持水波图案,只有松开手指后才会消失,故而需对手势的按下事件和放开事件区分判断;
4、随着水波扩散与消失,水波图案的颜色应当逐渐变淡,这样才符合现实生活中的情况;
5、对于按钮等控件,点击操作应延迟若干时长(如0.5秒)再处理具体事务,以便留出充裕时间播放水波动画;


下面是自定义水波动画的截图:



下面是自定义水波动画的关键代码片段:
	@Override
	protected void dispatchDraw(Canvas canvas) {
		super.dispatchDraw(canvas);
		if (mPaint.getColor()==Color.TRANSPARENT || mTargetWidth<=0 || mTouchTarget==null) {
			return;
		}

		if (mRadius > mMinSize / 2) {
			mRadius += mRadiusGap * 4;
		} else {
			mRadius += mRadiusGap;
		}
		getLocationOnScreen(mLocation);
		int[] location = new int[2];
		mTouchTarget.getLocationOnScreen(location);
		int left = location[0] - mLocation[0];
		int top = location[1] - mLocation[1];
		int right = left + mTouchTarget.getMeasuredWidth();
		int bottom = top + mTouchTarget.getMeasuredHeight();

		canvas.save();
		canvas.clipRect(left, top, right, bottom); // 裁剪水波的范围
		canvas.drawCircle(mCenterX, mCenterY, mRadius, mPaint); // 画水波
		canvas.restore();

		if (mRadius <= mMaxRadius) {
			postInvalidateDelayed(mDelay, left, top, right, bottom);
		} else if (!bPressed) {
			if (mPaint.getColor() == mPaintColor) {
				mPaint.setColor(mPaintHalfColor); // 最后一次画水波,颜色减淡
			} else {
				mPaint.setColor(Color.TRANSPARENT); // 结束水波动画
			}
			postInvalidateDelayed(mDelay, left, top, right, bottom);
		}
	}

	@Override
	public boolean dispatchTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
			// 获取水波动画的载体
			mTouchTarget = getTouchTarget(this, event.getRawX(), event.getRawY());
			if (mTouchTarget != null) {
				initChild(event, mTouchTarget);
				mPaint.setColor(mPaintColor);
				postInvalidateDelayed(mDelay);
			}
		} else if (event.getAction() == MotionEvent.ACTION_UP) {
			bPressed = false;
			postInvalidateDelayed(mDelay);
		}
		return super.dispatchTouchEvent(event);
	}

	private View getTouchTarget(View view, float x, float y) {
		View target = null;
		ArrayList<View> touchableViews = view.getTouchables();
		for (View child : touchableViews) {
			if (isTouchInView(child, (int) x, (int) y)) {
				target = child;
				break;
			}
		}
		return target;
	}

	private boolean isTouchInView(View view, int x, int y) {
		int[] location = new int[2];
		view.getLocationOnScreen(location);
		int left = location[0];
		int top = location[1];
		int right = left + view.getMeasuredWidth();
		int bottom = top + view.getMeasuredHeight();
		if (view.isEnabled() && x >= left && x <= right && y >= top && y <= bottom) {
			return true;
		} else {
			return false;
		}
	}


点击下载本文用到的水波图形与水波动画的工程代码


点此查看Android开发笔记的完整目录

猜你喜欢

转载自blog.csdn.net/aqi00/article/details/54849513