2.水波纹控件RippleLayout

作者: android_赵乐玮 | 来源:发表于2017-01-22 17:55 被阅读750次

    ---------------------更新2017/2/24--------
    发现一个更好的类库,https://github.com/traex/RippleEffect
    这轮子果然重造了.... ,不过还是学到好多。
    本控件的特点:可以对所有的子View添加此特效

    ----------------------------------以下是历史内容,并非该类库讲解-----------------

    该水波纹控件 可以对所有子控件添加水波纹效果支持 api14(最新代码在最下方)

    先看效果:
    RippleLayout.gif
    说明:

    未修复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设置失效问题

    使用:
    1. 使用xml进行引用添加
    2. 使用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);
            }
        }
    }
    

    相关文章

      网友评论

        本文标题:2.水波纹控件RippleLayout

        本文链接:https://www.haomeiwen.com/subject/dtoibttx.html