必要条件是这个控件需要实现点击事件
package com.rong.activity; import java.util.ArrayList; import android.R; import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Path.Direction; import android.graphics.RectF; import android.os.Build; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; /** * 一个特殊的LinearLayout,任何放入内部的clickable元素都具有波纹效果,当它被点击的时候, 为了性能,尽量不要在内部放入复杂的元素 * note: long click listener is not supported current for fix compatible bug. */ @SuppressWarnings("unused") public class RectangleWaterWave extends LinearLayout implements Runnable { private Paint mPaint = 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 float mCenterX; private float mCenterY; private int[] mLocationInScreen = new int[2]; private boolean mShouldDoAnimation = false; private boolean mIsPressed = false; private int INVALIDATE_DURATION = 40; private View mTouchTarget; private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable(); public RectangleWaterWave(Context context) { super(context); init(); } public RectangleWaterWave(Context context, AttributeSet attrs) { super(context, attrs); init(); } @TargetApi(Build.VERSION_CODES.HONEYCOMB) public RectangleWaterWave(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { setWillNotDraw(false); mPaint.setColor(Color.RED); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); this.getLocationOnScreen(mLocationInScreen); } private void initParametersForChild(MotionEvent event, View view) { mCenterX = event.getX(); mCenterY = event.getY(); 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); int left = location[0] - mLocationInScreen[0]; int transformedCenterX = (int) mCenterX - left; mMaxRevealRadius = Math.max(transformedCenterX, mTargetWidth - transformedCenterX); } protected void dispatchDraw(Canvas canvas) { super.dispatchDraw(canvas); if (!mShouldDoAnimation || mTargetWidth <= 0 || mTouchTarget == null) { return; } if (mRevealRadius > mMinBetweenWidthAndHeight / 2) { mRevealRadius += mRevealRadiusGap * 4; } 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(); Path path = new Path(); // path.addCircle(180,180, 165, Direction.CCW); path.addRoundRect(new RectF(left, top, right, bottom), 7, 7, Direction.CCW); canvas.clipPath(path); canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint); canvas.restore(); if (mRevealRadius <= mMaxRevealRadius) { postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom); } else if (!mIsPressed) { mShouldDoAnimation = false; postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom); } } @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; initParametersForChild(event, touchTarget); postInvalidateDelayed(INVALIDATE_DURATION); } } else if (action == MotionEvent.ACTION_UP) { mIsPressed = false; postInvalidateDelayed(INVALIDATE_DURATION); mDispatchUpTouchEventRunnable.event = event; postDelayed(mDispatchUpTouchEventRunnable, 40); return true; } else if (action == MotionEvent.ACTION_CANCEL) { mIsPressed = false; postInvalidateDelayed(INVALIDATE_DURATION); } return super.dispatchTouchEvent(event); } 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; } 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; } @SuppressLint("ClickableViewAccessibility") @Override public boolean performClick() { postDelayed(this, 400); return true; } @Override public void run() { super.performClick(); } private class DispatchUpTouchEventRunnable implements Runnable { public MotionEvent event; @Override public void run() { if (mTouchTarget == null || !mTouchTarget.isEnabled()) { return; } if (isTouchPointInView(mTouchTarget, (int) event.getRawX(), (int) event.getRawY())) { mTouchTarget.performClick(); } } }; }