前言
上篇博客已经是很久以前了,这段时间的确是忙了很多,技术相关的也做的少了。近期一个项目中,需要在4.4上实现一些5.0上的特效,例如Button之类的控件点击时的水波纹效果。在blog里找到了任教主的一篇博文,受到了启发,将他的Demo改动了一些就实现了。具体在应用到自己项目中时遇到了些需求变化和其他问题,这里就相当于做一个记录。
效果演示
AS还是不怎么会用,gif的图就没有了,这里给个静态的
这里说明一下,这个截图是一个拨号盘,在拨号的时候会有水波纹效果。
对比Demo中的效果

从效果图中可以看出与其他水波纹Demo的差异之处在于这个水波纹可以突破控件自身的大小。
思路
重载dispatchTouchEvent获取相应的坐标以及绘制对象,
重载dispatchDraw,根据点击事件来绘制一个逐渐变大的Circle。
代码及说明
Java代码
public class CustomTableLayout extends TableLayout{
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private Paint mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**目标控件宽度*/
private int mTargetWidth;
/**目标控件高度*/
private int mTargetHeight;
/**最小高宽*/
private int mMinBetweenWidthAndHeight;
private int mMaxBetweenWidthAndHeight;
/**水波纹最大半径*/
private int mMaxRevealRadius;
/**水波纹半径增量*/
private int mRevealRadiusGap;
/**水波纹半径*/
private int mRevealRadius = 0;
/**布局处于屏幕的坐标*/
private int[] mLocationInScreen = new int[2];
/**是否绘制水波纹*/
private boolean mShouldDoAnimation = false;
/**点击状态*/
private boolean mIsPressed = false;
/**绘制间隔*/
private int INVALIDATE_DURATION = 40;
/**点击的View*/
private View mTouchTarget;
public CustomTableLayout(Context context) {
super(context);
init();
}
public CustomTableLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
setWillNotDraw(false);
mPaint.setColor(getResources().getColor(R.color.reveal_color_2));
mBgPaint.setColor(getResources().getColor(R.color.reveal_color_2));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.getLocationOnScreen(mLocationInScreen);
}
/**
* @Title: initParametersForChildNew
* @Description: 初始化各个参数
* @param: @param view
* @return: void
* @throws
*/
private void initParametersForChildNew(View view){
mTargetWidth = view.getMeasuredWidth();
mTargetHeight = view.getMeasuredHeight();
mMinBetweenWidthAndHeight = Math.min(mTargetWidth, mTargetHeight);
mMaxBetweenWidthAndHeight = Math.max(mTargetWidth, mTargetHeight);
mRevealRadius = 0;
mShouldDoAnimation = true;
mIsPressed = true;
mRevealRadiusGap = mMinBetweenWidthAndHeight / 8;
int[] location = new int[2];
view.getLocationOnScreen(location);
//突破控件大小的关键之一
mMaxRevealRadius = mMaxBetweenWidthAndHeight/4 +2*mRevealRadiusGap;
}
/**
* @Title: getTouchTarget
* @Description: 获取点击的View
* @param: @param view
* @param: @param x
* @param: @param y
* @param: @return
* @return: View
* @throws
*/
private View getTouchTarget(View view, int x, int y) {
View target = null;
ArrayList<View> TouchableViews = view.getTouchables();
for (View child : TouchableViews) {
if (isTouchPointInView(child, x, y)) {
target = child;
break;
}
}
return target;
}
/**
* @Title: isTouchPointInView
* @Description: 判断point是否是处于View中
* @param: @param view
* @param: @param x
* @param: @param y
* @param: @return
* @return: boolean
* @throws
*/
private boolean isTouchPointInView(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.isClickable() && y >= top && y <= bottom
&& x >= left && x <= right) {
return true;
}
return false;
}
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
int action = event.getAction();
if (action == MotionEvent.ACTION_DOWN) {
View touchTarget = getTouchTarget(this, x, y);
if (touchTarget != null && touchTarget.isClickable() && touchTarget.isEnabled()) {
mTouchTarget = touchTarget;
initParametersForChildNew(touchTarget);
postInvalidateDelayed(INVALIDATE_DURATION);
}
} else if (action == MotionEvent.ACTION_UP) {
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION*2);
} else if (action == MotionEvent.ACTION_CANCEL) {
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
}
return super.dispatchTouchEvent(event);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) {
return;
}
if (mRevealRadius > mMaxRevealRadius) {
} else {
mRevealRadius += mRevealRadiusGap;
}
this.getLocationOnScreen(mLocationInScreen);
int[] location = new int[2];
mTouchTarget.getLocationOnScreen(location);
int left = location[0] - mLocationInScreen[0];
int top = location[1] - mLocationInScreen[1];
int right = left + mTouchTarget.getMeasuredWidth();
int bottom = top + mTouchTarget.getMeasuredHeight();
canvas.save();
canvas.clipRect(left, top-mRevealRadius, right, bottom+mRevealRadius);
canvas.drawCircle(left+mTargetWidth/2, top+mTargetHeight/2, mMaxRevealRadius, mBgPaint);
canvas.drawCircle(left+mTargetWidth/2, top+mTargetHeight/2, mRevealRadius, mPaint);
canvas.restore();
if (mRevealRadius <= mMaxRevealRadius) {
postInvalidateDelayed(INVALIDATE_DURATION, left, top-mMaxRevealRadius, right, bottom+mMaxRevealRadius);
} else if (!mIsPressed) {
mShouldDoAnimation = false;
postInvalidateDelayed(INVALIDATE_DURATION, left, top-mMaxRevealRadius, right, bottom+mMaxRevealRadius);
}
}
}
我这继承的是一个TabLayout,当然也可以继承其他的Layout实现相同效果。
由于是要做一个扩散出来的水波纹,这里的circle就的圆心就取的是死的,没有根据event来获取,需求不一致就需要重新计算canvas.drawCircle(left+mTargetWidth/2, top+mTargetHeight/2, mRevealRadius, mPaint);和postInvalidateDelayed(INVALIDATE_DURATION, left, top-mMaxRevealRadius, right, bottom+mMaxRevealRadius);
最后有几个需要提醒的地方是,dispatchTouchEvent中最好只return super.dispatchTouchEvent(event);
否则可能会出现长按事件混淆的问题。