1. 程式人生 > >點選Button實現水波紋和點選

點選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

    static final float DEFAULT_SCALE = 0.8f;

    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();

    }

    }