模仿android5上的Reveal实现
大家应该都接触过安卓5吧,上面的按钮点击的时候会出现波纹的动画,那个是新出来的一个reveal,但是在低版本的系统上不支持,只能想办法自己实现了,同样也是看到了任老师的实现方法,自己分析了一会儿后,加上了点自己的见解,分享给大家,demo的源码我已经上传了.
实现的基本原理就是,自定义一个继承了LinearLayout的MyRevealLayout控件,重写这个控件里面的一些函数,只要是在这个Layout中的控件被点击了,遍历MyRevealLayout中的所有子控件,和点击的位置作比对,确定被点击的子控件后初始化各种参数,随后开始绘制水波.
package com.example.dada.testapplication;
import java.util.ArrayList;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.LinearLayout;
public class MyRevealLayout extends LinearLayout implements Runnable {
/**
* 画笔工具
*/
private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
/**
* 用户点击处的坐标
*/
private float mCenterX, mCenterY;
/**
* 获取自定义控件MyRevealLayout
* 在屏幕上的位置
*/
private int[] mLocation = new int[2];
/**
* 重新绘制的时间
*/
private int INVALIDATE_DURATION = 20;
/**
* 被点击的控件的参数
*/
private int mTargetHeight, mTargetWidth;
/**
* mRevealRadius为初始的数值
* mRevealRadiusGap为每次重新绘制半径增加的值
* mMaxRadius为绘制的水波纹圆圈最大的半径
*/
private int mRevealRadius = 0, mRevealRadiusGap, mMaxRadius;
/**
* 通过名字就可以看出来了把
* 在被选中的控件长宽中的最大值和最小值
*/
private int mMinBetweenWidthAndHeight, mMaxBetweenWidthAndHeight;
/**
* 是否被按下
* 是否需要执行动画
*/
private boolean mIsPressed;
private boolean mShouldDoAnimation;
/**
* 这个就是在布局文件中被点击的控件了
*/
private View mTargetView;
/**
* 松手的事件分发线程
*/
private DispatchUpTouchEventRunnable mDispatchUpTouchEventRunnable = new DispatchUpTouchEventRunnable();
public MyRevealLayout(Context context) {
super(context);
init();
}
public MyRevealLayout(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public MyRevealLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public 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(mLocation);
}
/**
* 这个就是主要的绘制函数
* 在确定了被点击的控件之后
* 这个方法就会不断的被调用
* 直到水波纹的绘制完成
*
* 这个的原理就是不断的改变绘制的水波纹圆圈半径的大小
* 同时也在判断是否需要继续绘制
* @param canvas
*/
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
if (mTargetView == null || !mShouldDoAnimation || mTargetWidth <= 0)
return;
if (mRevealRadius > mMinBetweenWidthAndHeight / 2)
mRevealRadius += mRevealRadiusGap * 4;
else
mRevealRadius += mRevealRadiusGap;
int[] location = new int[2];
this.getLocationOnScreen(mLocation);
mTargetView.getLocationOnScreen(location);
int top = location[1] - mLocation[1];
int left = location[0] - mLocation[0];
int right = left + mTargetView.getMeasuredWidth();
int bottom = top + mTargetView.getMeasuredHeight();
canvas.save();
canvas.clipRect(left, top, right, bottom);
canvas.drawCircle(mCenterX, mCenterY, mRevealRadius, mPaint);
canvas.restore();
if (mRevealRadius <= mMaxRadius)
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
else if (!mIsPressed) {
mShouldDoAnimation = false;
postInvalidateDelayed(INVALIDATE_DURATION, left, top, right, bottom);
}
}
/**
* 重写事件分发
* 分别对按下等动作做出不同的响应
*
*
* @param event
* @return
*/
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
int x = (int) event.getRawX();
int y = (int) event.getRawY();
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
View targetView = getTargetView(this, x, y);
if (targetView != null && targetView.isEnabled()) {
mTargetView = targetView;
initParametersForChild(event, targetView);
postInvalidateDelayed(INVALIDATE_DURATION);
}
break;
case MotionEvent.ACTION_UP:
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
mDispatchUpTouchEventRunnable.event = event;
postDelayed(mDispatchUpTouchEventRunnable, 100);
break;
case MotionEvent.ACTION_CANCEL:
mIsPressed = false;
postInvalidateDelayed(INVALIDATE_DURATION);
break;
}
return super.dispatchTouchEvent(event);
}
/**
* 这个函数的作用正如它的名字一样
* 用于返回被点击的控件是哪一个
*
* 其中调用了另外一个函数isTouchPointInView
* 在后面会注释到
* @param view
* @param x
* @param y
* @return
*/
public View getTargetView(View view, int x, int y) {
View target = null;
ArrayList<View> views = view.getTouchables();
for (View child : views)
if (isTouchPointInView(child, x, y)) {
target = child;
break;
}
return target;
}
/**
* 对比用户点击的坐标和传入的控件坐标
* 如果在控件内就返回true
* 否则返回false
*
* @param child
* @param x
* @param y
* @return
*/
public boolean isTouchPointInView(View child, int x, int y) {
int[] location = new int[2];
child.getLocationOnScreen(location);
int top = location[1];
int left = location[0];
int right = left + child.getMeasuredWidth();
int bottom = top + child.getMeasuredHeight();
if (child.isClickable() && y >= top && y <= bottom && x >= left && x <= right)
return true;
else
return false;
}
/**
* 初始化被点击控件的参数
* 确定开始绘制水波纹的各项参数
* 比如圆的半径,最大半径,半径增长数值等
*
* 在dispatchTouchEvent()对DOWN事件响应的时候调用
* @param event
* @param view
*/
public 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;
mRevealRadiusGap = mMinBetweenWidthAndHeight / 20;
mIsPressed = true;
mShouldDoAnimation = true;
int[] location = new int[2];
view.getLocationOnScreen(location);
int left = location[0] - mLocation[0];
int mTransformedCenterX = (int) mCenterX - left;
mMaxRadius = Math.max(mTransformedCenterX, mTargetWidth - mTransformedCenterX);
int top = location[1]-mLocation[1];
int transformedCenterY = (int)mCenterY-top;
mMaxRadius = Math.max(mMaxRadius,Math.max(transformedCenterY,mTargetHeight-transformedCenterY));
}
@Override
public void run() {
super.performClick();
}
@Override
public boolean performClick() {
postDelayed(this, 40);
return true;
}
private class DispatchUpTouchEventRunnable implements Runnable {
public MotionEvent event;
@Override
public void run() {
if (mTargetView.isEnabled() && mTargetView.isClickable())
return;
if (isTouchPointInView(mTargetView, (int) event.getRawX(), (int) event.getRawX()))
mTargetView.performClick();
}
}
}
上面的代码就是自定义的控件MyRevealLayout.java,使用的时候只需要将它当作是一个LInearLayout一样的用就可以了,小达自己的理解都写在注释里面了,理解起来应该不是很困难把...~~~