點選Button實現水波紋和點選
新增一個自定義的佈局類 MaterialLayout.class
public class MaterialLayout extends RelativeLayout {
private static final int DEFAULT_RADIUS = 10;
private static final int DEFAULT_FRAME_RATE = 10;
private static final int DEFAULT_DURATION = 200;
private static final int DEFAULT_ALPHA = 255;
private
private static final int DEFAULT_ALPHA_STEP = 5;
/**
* 動畫幀率
*/
private int mFrameRate = DEFAULT_FRAME_RATE;
/**
* 漸變動畫持續時間
*/
private int mDuration = DEFAULT_DURATION;
/**
*
*/
private Paint mPaint = new Paint();
/**
* 被點選的檢視的中心點
*/
private Point mCenterPoint = null;
/**
* 檢視的Rect
*/
private RectF mTargetRectf;
/**
* 起始的圓形背景半徑
*/
private int mRadius = DEFAULT_RADIUS;
/**
* 最大的半徑
*/
private int mMaxRadius = DEFAULT_RADIUS;
/**
* 漸變的背景色
*/
private int mCirclelColor = Color.LTGRAY;
/**
* 每次重繪時半徑的增幅
*/
private int mRadiusStep = 1;
/**
* 儲存使用者設定的alpha值
*/
private int mBackupAlpha;
/**
* 圓形半徑針對於被點選檢視的縮放比例,預設為0.8
*/
private float mCircleScale = DEFAULT_SCALE;
/**
* 顏色的alpha值, (0, 255)
*/
private int mColorAlpha = DEFAULT_ALPHA;
/**
* 每次動畫Alpha的漸變遞減值
*/
private int mAlphaStep = DEFAULT_ALPHA_STEP;
private View mTargetView;
/**
* @param context
*/
public MaterialLayout(Context context) {
this(context, null);
}
public MaterialLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}public MaterialLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}private void init(Context context, AttributeSet attrs) {
if (isInEditMode()) {
return;
}
if (attrs != null) {
initTypedArray(context, attrs);
} initPaint();this.setWillNotDraw(false);
this.setDrawingCacheEnabled(true);
}
private void initTypedArray(Context context, AttributeSet attrs) {
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.MaterialLayout);
mCirclelColor = typedArray.getColor(R.styleable.MaterialLayout_mycolor, Color.LTGRAY);
mDuration = typedArray.getInteger(R.styleable.MaterialLayout_duration, DEFAULT_DURATION);
mFrameRate = typedArray.getInteger(R.styleable.MaterialLayout_framerate, DEFAULT_FRAME_RATE);
mColorAlpha = typedArray.getInteger(R.styleable.MaterialLayout_alpha, DEFAULT_ALPHA);typedArray.recycle();
mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE); }private void initPaint() {
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mCirclelColor);// 備份alpha屬性用於動畫完成時重置
mPaint.setAlpha(mColorAlpha);mBackupAlpha = mColorAlpha;
}/**
* 點選的某個座標點是否在View的內部
** @param touchView
* @param x 被點選的x座標
* @param y 被點選的y座標
* @return 如果點選的座標在該view內則返回true,否則返回false
*/
private boolean isInFrame(View touchView, float x, float y) {
initViewRect(touchView);
return mTargetRectf.contains(x, y);
}
/**
* 獲取點中的區域,螢幕絕對座標值,這個高度值也包含了狀態列和標題欄高度
** @param touchView
*/
private void initViewRect(View touchView) {
int[] location = new int[2];
touchView.getLocationOnScreen(location);
// 檢視的區域
mTargetRectf = new RectF(location[0], location[1], location[0] + touchView.getWidth(),
location[1] + touchView.getHeight());
}/**
* 減去狀態列和標題欄的高度
*/
private void removeExtraHeight() {
int[] location = new int[2];
this.getLocationOnScreen(location);
// 減去兩個該佈局的top,這個top值就是狀態列的高度
mTargetRectf.top -= location[1];
mTargetRectf.bottom -= location[1];
// 計算中心點座標
int centerHorizontal = (int) (mTargetRectf.left + mTargetRectf.right) / 2;
int centerVertical = (int) ((mTargetRectf.top + mTargetRectf.bottom) / 2);
// 獲取中心點
mCenterPoint = new Point(centerHorizontal, centerVertical);
}private View findTargetView(ViewGroup viewGroup, float x, float y) {
int childCount = viewGroup.getChildCount();
// 迭代查詢被點選的目標檢視
for (int i = 0; i < childCount; i++) {
View childView = viewGroup.getChildAt(i);
if (childView instanceof ViewGroup) {
return findTargetView((ViewGroup) childView, x, y);
} else if (isInFrame(childView, x, y)) { // 否則判斷該點是否在該View的frame內
return childView;
}
}return null;
}
private boolean isAnimEnd() {
return mRadius >= mMaxRadius;
}private void calculateMaxRadius(View view) {
// 取檢視的最長邊
int maxLength = Math.max(view.getWidth(), view.getHeight());
// 計算Ripple圓形的半徑
mMaxRadius = (int) ((maxLength / 2) * mCircleScale);
int redrawCount = mDuration / mFrameRate;
// 計算每次動畫半徑的增值
mRadiusStep = (mMaxRadius - DEFAULT_RADIUS) / redrawCount;
// 計算每次alpha遞減的值
mAlphaStep = (mColorAlpha - 100) / redrawCount;
}/**
* 處理ACTION_DOWN觸控事件, 注意這裡獲取的是Raw x, y,
* 即螢幕的絕對座標,但是這個當螢幕中有狀態列和標題欄時就需要去掉這些高度,因此得到mTargetRectf後其高度需要減去該佈局的top起點
* ,也就是標題欄和狀態列的總高度. ** @param event
*/
private void deliveryTouchDownEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mTargetView = findTargetView(this, event.getRawX(), event.getRawY());
if (mTargetView != null) {
removeExtraHeight();
// 計算相關資料
calculateMaxRadius(mTargetView);
// 重繪檢視
invalidate();
} } }@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
deliveryTouchDownEvent(event);
return super.onInterceptTouchEvent(event);
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
// 繪製Circle
drawRippleIfNecessary(canvas);
}private void drawRippleIfNecessary(Canvas canvas) {
if (isFoundTouchedSubView()) {
// 計算新的半徑和alpha值
mRadius += mRadiusStep;
mColorAlpha -= mAlphaStep;
// 裁剪一塊區域,這塊區域就是被點選的View的區域.通過clipRect來獲取這塊區域,使得繪製操作只能在這個區域範圍內的進行,
// 即使繪製的內容大於這塊區域,那麼大於這塊區域的繪製內容將不可見. 這樣保證了背景層只能繪製在被點選的檢視的區域
canvas.clipRect(mTargetRectf);
mPaint.setAlpha(mColorAlpha);// 繪製背景圓形,也就是
canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint);
}if (isAnimEnd()) {
reset();
} else {
invalidateDelayed();
} }/**
* 傳送重繪訊息
*/
private void invalidateDelayed() {
this.postDelayed(new Runnable() {
@Override
public void run() {
invalidate();
} }, mFrameRate); }/**
* 判斷是否找到被點選的子檢視
** @return
*/
privateboolean isFoundTouchedSubView() {
return mCenterPoint != null && mTargetView != null;
}
privatevoid reset() {
mCenterPoint = null;
mTargetRectf = null;
mRadius = DEFAULT_RADIUS;
mColorAlpha = mBackupAlpha;
mTargetView = null;
invalidate();
}}