1. 程式人生 > >Android中按鈕的水波紋點選效果的實現

Android中按鈕的水波紋點選效果的實現

關於按鈕水波紋的點選效果,這個是我在http://blog.csdn.net/singwhatiwanna/article/details/42614953這篇文章讀到的。寫得真心不錯,我只是站在巨人的肩上而已。

我加了一些註釋,以至於我們更好的理解這篇不錯的文章

下面是主要原始碼:

public class RevealLayout 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;

	// mMaxRadius為繪製的水波紋圓圈最大的半徑
	private int mMaxRevealRadius;
	// mRevealRadiusGap為每次重新繪製半徑增加的值
	private int mRevealRadiusGap;
	// mRevealRadius為初始的數值
	private int mRevealRadius = 0;
	// 使用者點選處的座標
	private float mCenterX;
	private float mCenterY;
	// 獲取自定義控制元件MyRevealLayout 在螢幕上的位置
	private int[] mLocationInScreen = new int[2];
	// 是否執行動畫
	private boolean mShouldDoAnimation = false;
	// 是否被按下
	private boolean mIsPressed = false;
	// 重新繪製的時間 單位毫秒
	private int INVALIDATE_DURATION = 50;
	// mTouchTarget指的是使用者點選的那個view
	private View mTouchTarget;
	// 鬆手的事件分發執行緒
	private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();

	public RevealLayout(Context context)
	{
		super(context);
		init();
	}

	public RevealLayout(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init();
	}

	@TargetApi(Build.VERSION_CODES.HONEYCOMB)
	public RevealLayout(Context context, AttributeSet attrs, int defStyleAttr)
	{
		super(context, attrs, defStyleAttr);
		init();
	}

	private void init()
	{
		setWillNotDraw(false);
		mPaint.setColor(getResources().getColor(R.color.reveal_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);
	}

	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);
		// 獲得要繪製View的left, top, right, bottom值
		int left = location[0] - mLocationInScreen[0];
		int top = location[1] - mLocationInScreen[1];
		int right = left + mTouchTarget.getMeasuredWidth();
		int bottom = top + mTouchTarget.getMeasuredHeight();

		canvas.save();
		canvas.clipRect(left, top, right, bottom);
		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);
	}

	/**
	 * 遍歷view樹找到使用者所點選的那個view
	 * 
	 * @param view
	 * @param x
	 * @param y
	 * @return
	 */
	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;
	}

	/**
	 * 判斷事件的xy是否落在view的上下左右四個角之內
	 * 
	 * @param view
	 * @param x
	 * @param y
	 * @return
	 */
	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;
	}

	// 使用程式碼主動去呼叫控制元件的點選事件(模擬人手去觸控控制元件)
	@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();
			}
		}
	};

}
注:

在Android中實現view的更新有中方法,一種是invalidate,另一種是postInvalidate,

其中:前者是在UI執行緒自身中使用,而後者在非UI執行緒中使用。 

當invalidate方法被呼叫的時候,會告訴系統,當前的view發生改變,應該儘可能快的來進行重繪。並且執行在UI執行緒

postInvalidate方法被呼叫的時候,將會發送訊息到主執行緒,當主執行緒的訊息佇列輪詢到當前訊息的時候,這個方法才會被呼叫,

重新整理介面並不能保證馬上重新整理。只是儘可能快的進行重新整理。尤其是在postInvalidate方法中,這種情況會出現機率更大。

當我們想要使用這種效果時候,只需要將我的控制元件包裹在這個佈局下面即可,如下:

    <包名.RevealLayout
        android:id="@+id/layout1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <Button
            android:id="@+id/button1"
            android:layout_width="200dp"
             android:layout_height="40dp"
            android:enabled="true"
            android:text="Button" />
    </包名.RevealLayout>


這樣我們就可以實現按鈕的水波紋效果了