按钮点击水波纹效果

水波纹的出现给我们的错觉是直接将波纹绘制在button上面的,但是这样能做到吗?首先button自己有background和src,如果把半透明的水波纹当作background或者src绘制到button上面,肯定是会损失button原有的样式的。可能有朋友猜想那就把水波纹绘制在屏幕上呗,恭喜这位朋友答对了,至少我是这么干的,具体思路就是,我们自己实现一个layout,在layout中捕捉事件,并对事件进行相应的处理,在down事件中寻找当前用户点击的是哪个view,找出view所在的矩形区域,将一个透明的圆环绘制到这个矩形区域。

实现思路

1、自己实现一个layout:
2、重写layout的dispatchTouchEvent方法,在down事件中找出被点击的view;
3、接着找出view所在的矩形区域,因为要将波纹绘制到该区域;
4、矩形区域找到之后,这个区域就是我们要绘制的博波纹所在地,上面也说过了,波纹其实就是圆环,绘制圆的画是需要知道圆心坐标和圆的半径,圆心坐标肯定就是down时候的x和y了,然后计算半径
5、半径算出来了,虽说圆心就是down时的x和y,但是有个地方还是需要注意的,在绘制圆环的时候提供的圆心坐标的x和y是在本文中是相对于layout的,所以在计算y的时候是需要进行一定处理的:
6、圆心坐标和半径都计算好了,记下来就可以绘制圆形波纹了,那么在哪里绘制这个波纹比较合适呢?有朋友立马就说肯定是在onDraw方法里面绘制了,那么恭喜你在我看来你是答错了,我们的layout中是很有很多childview的,而layout是个viewGroup,viewGroup在绘制的时候,是先绘制自身的背景,再绘制自身,再绘制childview,如果在onDraw中绘制波纹,也就意味者后面绘制出来的childView会将我们的波纹遮盖,所以我们就应该等到childview绘制完毕后再来绘制波纹,这样可以保证childview在最顶层。

自定义控件代码:

package com.example.viewresult;

import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Button;
import android.widget.LinearLayout;

/**
 * 
 * 1. 如何得知用户点击了哪个元素 2. 如何取得被点击元素的信息 3. 如何通过layout进行重绘绘制水波纹 4. 如果延迟up事件的分发
 * 
 */
@SuppressLint("NewApi")
public class CustomViewResult extends LinearLayout {
    private View mTargetTouchView;
    private Paint mHalfTransPaint;
    private Paint mTransPaint;
    private float[] mDownPositon;// 手指点击的坐标,也就是圆环的中心点
    private float rawRadius;// 原始的圆环半径
    private float drawedRadius;// 正在慢慢绘制的圆环半径
    private float drawingRadiusDegrees = 10;// 慢慢绘制圆环的时候,半径的递增百分比
    private static final long INVALID_DURATION = 30;
    private static postUpEventDelayed delayedRunnable;

    public void init() {
        setOrientation(VERTICAL);// 设置方向
        mHalfTransPaint = new Paint();
        mHalfTransPaint.setColor(Color.parseColor("#55ffffff"));
        mHalfTransPaint.setAntiAlias(true);
        mTransPaint = new Paint();
        mTransPaint.setColor(Color.parseColor("#00ffffff"));
        mTransPaint.setAntiAlias(true);// 抗锯齿
        mDownPositon = new float[2];
        delayedRunnable = new postUpEventDelayed();
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            mTargetTouchView = null;
            drawedRadius = 0;
            float x = ev.getRawX();
            float y = ev.getRawY();
            mTargetTouchView = findTargetView(x, y, this);
            if (mTargetTouchView != null) {
                Button msg = (Button) mTargetTouchView;
                RectF targetTouchRectF = getViewRectF(mTargetTouchView);
                mDownPositon = getCircleCenterPostion(x, y);
                // 所要绘制的圆环的中心点
                float circleCenterX = mDownPositon[0];
                float circleCenterY = mDownPositon[1];
                /**
                 * 圆环的半径: 圆环的中心点圆心当然是点击的那个点,但是半径是变化的
                 * 圆心可能落在mTargetTouchView区域的任意个方位之内,所以要想圆环绘制覆盖整个mTargetTouchView
                 * 则radius的取值为圆心的横坐标到mTargetTouchView的四个点的距离中的最大值
                 */
                float left = circleCenterX - targetTouchRectF.left;
                float right = targetTouchRectF.right - circleCenterX;
                float top = circleCenterY - targetTouchRectF.top;
                float bottom = targetTouchRectF.bottom - circleCenterY;
                // 计算出最大的值则为半径
                rawRadius = Math.max(bottom,
                        Math.max(Math.max(left, right), top));
                // Android中实现view的更新有两组方法,一组是invalidate,另一组是postInvalidate,
                // 其中前者是在UI线程自身中使用,而后者在非UI线程中使用。
                // postInvalidate还支持延迟刷新
                postInvalidateDelayed(INVALID_DURATION);
            }
        } else if (ev.getAction() == MotionEvent.ACTION_UP) {
            // 需要让波纹绘制完毕后再执行在up中执行的方法
            // if(drawedRadius==0){
            // return false;
            // }
            // long totalTime = (long) (INVALID_DURATION *
            // (drawingRadiusDegrees+5));
            // // 离波纹结束的时间
            // long time = (long) (totalTime - drawedRadius*totalTime /
            // rawRadius);
            delayedRunnable.event = ev;
            return true;
        }
        return super.dispatchTouchEvent(ev);
    }

    class postUpEventDelayed implements Runnable {
        private MotionEvent event;

        @Override
        public void run() {
            if (mTargetTouchView != null && mTargetTouchView.isClickable()
                    && event != null) {
                mTargetTouchView.dispatchTouchEvent(event);
            }
        }
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        /**
         * 绘制完子元素后开始绘制波纹
         */
        if (mTargetTouchView != null) {
            RectF clipRectF = clipRectF(mTargetTouchView);
            canvas.save();
            // 为了不让绘制的圆环超出所要绘制的范围
            canvas.clipRect(clipRectF);
            if (drawedRadius < rawRadius) {
                drawedRadius += rawRadius / drawingRadiusDegrees;
                canvas.drawCircle(mDownPositon[0], mDownPositon[1],
                        drawedRadius, mHalfTransPaint);
                postInvalidateDelayed(INVALID_DURATION);
            } else {
                canvas.drawCircle(mDownPositon[0], mDownPositon[1], rawRadius,
                        mTransPaint);
                post(delayedRunnable);
            }
            canvas.restore();
        }
    }

    /**
     * 获取圆环的中心坐标
     */
    public float[] getCircleCenterPostion(float x, float y) {
        int[] location = new int[2];
        float[] mDownPositon = new float[2];
        getLocationOnScreen(location);// 获取当前控件在屏幕中的绝对位置
        mDownPositon[0] = x;
        mDownPositon[1] = y - location[1];
        return mDownPositon;
    }

    /**
     * 获取要剪切的区域
     * 
     * @param mTargetTouchView
     * @return
     */
    public RectF clipRectF(View mTargetTouchView) {
        RectF clipRectF = getViewRectF(mTargetTouchView);
        int[] location = new int[2];
        getLocationOnScreen(location);
        clipRectF.top -= location[1];
        clipRectF.bottom -= location[1];
        return clipRectF;
    }

    /**
     * 寻找目标view
     * 
     * @param x
     * @param y
     * @param anchorView
     *            从哪个view开始往下寻找
     * @return
     */
    public View findTargetView(float x, float y, View anchorView) {
        ArrayList<View> touchablesView = anchorView.getTouchables();
        View targetView = null;
        for (View child : touchablesView) {
            // 1、精度不一样,Rect是使用int类型作为数值,RectF是使用float类型作为数值
            // 2、两个类型提供的方法也不是完全一致
            RectF rectF = getViewRectF(child);
            if (rectF.contains(x, y) && child.isClickable()) {
                // 这说明被点击的view找到了
                targetView = child;
                break;
            }
        }
        return targetView;
    }

    public RectF getViewRectF(View view) {
        int[] location = new int[2];
        // View.getLocationInWindow(int[] location)
        // 一个控件在其父窗口中的坐标位置
        // View.getLocationOnScreen(int[] location)
        // 一个控件在其整个屏幕上的坐标位置
        view.getLocationOnScreen(location);
        int childLeft = location[0];
        int childTop = location[1];
        int childRight = childLeft + view.getMeasuredWidth();
        int childBottom = childTop + view.getMeasuredHeight();
        return new RectF(childLeft, childTop, childRight, childBottom);
    }

    public CustomViewResult(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public CustomViewResult(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CustomViewResult(Context context) {
        this(context, null, 0);
    }

}

自定义控件的使用:

1.布局

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >
<com.example.viewresult.CustomViewResult 
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    >
    <Button 
        android:id="@+id/button1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#ff0000"
        />
     <Button 
        android:id="@+id/button2"
        android:layout_width="180dp"
        android:layout_height="wrap_content"
        android:background="#ff00ff"
        android:layout_marginLeft="50dp"
        android:layout_marginTop="10dp"
        />
      <Button 
        android:id="@+id/button3"
        android:layout_width="220dp"
        android:layout_height="wrap_content"
        android:background="#000000"
        android:layout_marginLeft="20dp"
        android:layout_marginTop="10dp"
        />
</com.example.viewresult.CustomViewResult>
</RelativeLayout>

2.活动类

package com.example.viewresult;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        findViewById(R.id.button1).setOnClickListener(new OnClickListener() {

            @Override
            public void onClick(View arg0) {
                // TODO Auto-generated method stub
                Toast.makeText(getApplicationContext(), "点击", 0).show();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }

}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值