Android 模仿android5上的Reveal实现

模仿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一样的用就可以了,小达自己的理解都写在注释里面了,理解起来应该不是很困难把...~~~
实现页面间波纹动画效果切换。可配合Activity或Fragment间切换。如果是Activity间切换,你需要将Activity背景设为透明,否则影响切换效果。而Fragment间就不存在这样的问题,可以实现流畅、无缝切换。项目地址:https://github.com/kyze8439690/RevealLayout 效果图:单击上面的"下载源码"按钮,可下载完整demo。一个是官方的demo,比较复杂, 一个是我简化的。如何使用将被跳转页的根布局改为<me.yugy.github.reveallayout.RevealLayout><me.yugy.github.reveallayout.RevealLayout xmlns:android="http://schemas.android.com/apk/res/android"                 android:layout_width="match_parent"                 android:layout_height="match_parent"                 android:id="@ id/reveal_layout">     ...你的页面的View      </me.yugy.github.reveallayout.RevealLayout>2. 得到RevealLayoutmRevealLayout = (RevealLayout)this.findViewById(R.id.reveal_layout);3. 为RevealLayout的ViewTreeObserver添加监听ViewTreeObserver.OnGlobalLayoutListener, 在监听中调用show()来启动进入页面波动动画。mRevealLayout.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {             @Override             public void onGlobalLayout() { //                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { //                    mRevealLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this); //                } else { //                    mRevealLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this); //                }                 mRevealLayout.postDelayed(new Runnable() {//设置了50毫秒的延时                     @Override                     public void run() {                         mRevealLayout.show();                     }                 }, 50);             }         });你可以调用mRevealLayout.hide()来启动关闭页面波动动画。如果是Activity间跳转,你还需要调用finish()才能退出页面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值