---------------------更新2017/2/24--------
发现一个更好的类库,https://github.com/traex/RippleEffect
这轮子果然重造了.... ,不过还是学到好多。
本控件的特点:可以对所有的子View添加此特效
----------------------------------以下是历史内容,并非该类库讲解-----------------
该水波纹控件 可以对所有子控件添加水波纹效果支持 api14(最新代码在最下方)
先看效果:
说明:
未修复bug说明 :
- RippleManager动态添加效果不佳,部分机型不能实现(本人测试机型:vivo xplay510w, android 4.4-api19,ROM:Funtouch OS_2.0),原因未知,其他手机可以适配,样本数4。
动态添加原理: 在parentView中remove先前ViewGroup,再直接动态添加该控件,再添加remove的ViewGroup,实现动态添加效果
如嫌弃此bug,请忽略RippleManager内部类,使用xml添加效果就好。详见使用方法
如果有大神能解决此bug望发一份到739043667@qq.com,在此先谢过
欢迎交流学习,qq:73904`3667
更新日志:
----------2016/12-------------
1.修复Touch事件cancle无法执行的问题(点击后滑动到控件之外,事件仍触发的问题)
2.添加isDrawParent属性 false:最外层控件不会绘制水波纹;
TODO:
1.RippleManager动态添加bug
2.自定义控件属性实现
3、clickable设置失效问题
使用:
- 使用xml进行引用添加
- 使用RippleManager进行动态添加
代码:
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RelativeLayout;
import java.util.ArrayList;
/**
* 水波纹控件布局:
* 子控件点击后会绘制一个水波纹动画
* 配置:
* isDrawParent:是否给最外层控件绘制水波纹
* <p>
* <p>
* Bug统计:
* 1.当出现控件重叠时水波纹会出现在底层(xml中先绘制)的布局中;需求往往不是; //已解决
* 2、onTouch中的Cancle事件判断有误:当触摸点滑动到触发范围外后点击事件依然触发 //已解决
* <p>
* 注意事项:
* 1.在绘制中如果不想触发事件可设置enable|clickable=false
* 2.使用RippleManager动态添加效果
*
* @author zlw 73904`3667@qq.com
*/
public class RippleLayout extends RelativeLayout {
public boolean isDrawParent = true; //是否绘制最外层控件
private static final String TAG = RippleLayout.class.getSimpleName();
private Paint mPaint;
private int clickedViewWidth, clickedViewHeight;
private int mMaxRippleRadius;
private int mRippleRadiusGap;
private int mRippleRadius = 0;
private float mCenterX;
private float mCenterY;
private int[] mLocationInScreen = new int[2];
private boolean shouldDrawRipple = false;
private boolean isPressed = false;
private int INVALIDATE_DURATION = 20;
private View clickedView;
private boolean isClickedParent = false;
public RippleLayout(Context context) {
super(context);
init();
}
public RippleLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public RippleLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
setWillNotDraw(false);
mPaint.setColor(Color.GRAY); //水波纹颜色
mPaint.setAlpha(50); //水波纹透明度
}
public void setRippleColor(int color) {
mPaint.setColor(color);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
this.getLocationOnScreen(mLocationInScreen);
}
@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 clickedView = getTouchTarget(this, x, y);
if (clickedView != null && clickedView.isClickable() && clickedView.isEnabled()) {
this.clickedView = clickedView;
isClickedParent = false;
initParamForRipple(event, clickedView, false);
postInvalidateDelayed(INVALIDATE_DURATION);
} else {
this.clickedView = this;
isClickedParent = true;
RippleLayout.this.setClickable(true);
initParamForRipple(event, clickedView, true);
postInvalidateDelayed(INVALIDATE_DURATION);
}
} else if (action == MotionEvent.ACTION_UP) {
isPressed = false;
RippleLayout.this.setClickable(false);
postInvalidateDelayed(INVALIDATE_DURATION);
// clickedView.performClick();
// return true;
} else if (action == MotionEvent.ACTION_CANCEL) {
isPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
}
return super.dispatchTouchEvent(event);
}
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (!shouldDrawRipple) {
return;
}
if (clickedView == null) {
return;
}
if (!isDrawParent && clickedView instanceof ViewGroup) {
View parentView = (View) clickedView.getParent();
if (parentView != null) {
if (clickedView.getHeight() >= parentView.getHeight() - parentView.getPaddingBottom() - parentView.getPaddingTop()) {
return;
}
}
}
if (mRippleRadius > mMaxRippleRadius / 2) {
mRippleRadius += mRippleRadiusGap * 4;
} else {
mRippleRadius += mRippleRadiusGap;
}
this.getLocationOnScreen(mLocationInScreen);
int[] location = new int[2];
clickedView.getLocationOnScreen(location);
int left = location[0] - mLocationInScreen[0];
int top = location[1] - mLocationInScreen[1];
int right = left + clickedView.getMeasuredWidth();
int bottom = top + clickedView.getMeasuredHeight();
canvas.save();
if (!isClickedParent) {
canvas.clipRect(left, top, right, bottom);
}
canvas.drawCircle(mCenterX, mCenterY, mRippleRadius, mPaint);
canvas.restore();
if (mRippleRadius <= mMaxRippleRadius) {
if (isClickedParent) {
postInvalidateDelayed(INVALIDATE_DURATION);
} else {
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
}
} else if (!isPressed) {
shouldDrawRipple = false;
if (isClickedParent) {
postInvalidateDelayed(INVALIDATE_DURATION);
} else {
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
}
}
}
private void initParamForRipple(MotionEvent event, View view, boolean isClickedParent) {
mCenterX = event.getX();
mCenterY = event.getY();
if (isClickedParent) {
clickedViewWidth = this.getMeasuredWidth();
clickedViewHeight = this.getMeasuredHeight();
} else {
clickedViewWidth = view.getMeasuredWidth();
clickedViewHeight = view.getMeasuredHeight();
}
mMaxRippleRadius = (int) Math
.sqrt((double) (clickedViewWidth * clickedViewWidth + clickedViewHeight * clickedViewHeight));
mRippleRadius = 0;
shouldDrawRipple = true;
isPressed = true;
mRippleRadiusGap = mMaxRippleRadius / 20;
}
private View getTouchTarget(View view, int x, int y) {
View target = null;
ArrayList<View> touchableViews = view.getTouchables();
for (int i = touchableViews.size() - 1; i >= 0; i--) {
View child = touchableViews.get(i);
if (isTouchPointInView(child, x, y) && child != RippleLayout.this) {
return child;
}
}
return target;
}
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;
}
public static class RippleManager {
public static void addRipple(View child) {
addRipple(child, true);
}
public static void addRipple(View child, boolean isParentRip) {
View oldScreen = child;
ViewGroup parentView = (ViewGroup) oldScreen.getParent();
parentView.removeView(oldScreen);
RippleLayout rippleLayout = new RippleLayout(child.getContext());
rippleLayout.isDrawParent = isParentRip;
rippleLayout.addView(oldScreen);
parentView.addView(rippleLayout);
}
public static void addRipple(Activity activity, int res) {
View oldScreen = activity.findViewById(res);
ViewGroup parentView = (ViewGroup) oldScreen.getParent();
parentView.removeView(oldScreen);
RippleLayout rippleLayout = new RippleLayout(activity);
rippleLayout.addView(oldScreen);
parentView.addView(rippleLayout);
}
public static void addRipple(Activity activity) {
addRipple(activity, true);
}
/**
* 对整个activity的布局添加水波纹
*
* @param activity
* @param isParentRip 对全局是否设置水波纹
*/
public static void addRipple(Activity activity, boolean isParentRip) {
ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
View oldScreen = decorView.getChildAt(0);
decorView.removeViewAt(0);
RippleLayout rippleLayout = new RippleLayout(activity);
rippleLayout.isDrawParent = isParentRip;
rippleLayout.addView(oldScreen);
decorView.addView(rippleLayout);
}
}
}