水波图形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开发笔记的完整目录