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>


这样我们就可以实现按钮的水波纹效果了


  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值