《Android自定义View之——带有阴影扩散的Button》

一、效果预览

闲话少叙,先看看效果:


1

从上图可以看到,当我们长按时,“按钮”上的阴影扩散得比较慢;当我们纯粹只是点击一下时,“按钮”上的阴影扩散速度加快。



二、实现原理

其实,上图中显示的“按钮”是基于Android的ImageButton实现的。具体过程如下:

  • 通过Android的自定义View机制,继承Android原生的ImageButton并重写其中的按钮监听方法、界面重绘方法等;
  • 准备所需的按钮背景图片;
  • 在xml布局文件中使用该自定义的ImageButton,并引用准备好的背景图片;
  • 在MainActivity中获取该按钮的实例,为其绑定相应的点击事件。


因此从上面不难看出,我们可以根据自己的需求设置“按钮”底部的背景图,并在该“按钮”上呈现出我们期望的阴影扩散效果。



三、具体实现代码

考虑到代码注释有详细的注释,故在此不叙述实现过程。

1、自定义的ImageButton

Code:

RippleButton.java

package com.example.wuchangi.customview;


import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.SystemClock;
import android.support.v7.widget.AppCompatImageButton;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.ViewConfiguration;


/**
 * Created by WuchangI on 2018/6/10.
 */

//一款自定义的阴影扩散效果按钮
public class RippleButton extends AppCompatImageButton
{
    //按钮宽度、 高度
    private int buttonViewWidth, buttonViewHeight;

    //刷新周期
    private static final int INVALIDATE_PERIOD = 15;

    //扩散增量
    private int diffuseIncrement;

    //按钮的左上角原点坐标
    private int buttonViewX, buttonViewY;

    //扩散效果的最大半径
    private int maxDiffuseRadius;

    //扩散效果的半径
    private int diffuseRadius;

    //绘制按钮底部背景的画笔
    private Paint backgroundPaint;

    //绘制扩散效果(波纹)颜色的画笔
    private Paint diffusePaint;

    //记录按钮是否被按下
    private boolean isPushButton;

    //用户触摸位置
    private int touchEventX, touchEventY;

    //用户按下按钮的时间
    private long downTime = 0;

    //获取按钮的长按时间,超过此时间就认为是长按事件
    int longPressTimeout;


    public RippleButton(Context context, AttributeSet attrs)
    {
        super(context, attrs);

        //初始化界面数据
        initData();

        //初始化画笔
        initPaints();
    }


    //监听用户按钮事件
    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        switch (event.getAction())
        {
            case MotionEvent.ACTION_DOWN:

                if (downTime == 0)
                {
                    //记录按钮按下时的时间点
                    downTime = SystemClock.elapsedRealtime();
                }

                isPushButton = true;

                touchEventX = (int) event.getX();
                touchEventY = (int) event.getY();

                //计算扩散效果的最大半径
                countMaxDiffuseRadius();

                //进行扩散(刷新界面)
                postInvalidateDelayed(INVALIDATE_PERIOD);

                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //如果用户只是轻触按下,没有长按
                if (SystemClock.elapsedRealtime() - downTime < longPressTimeout)
                {
                    //增大扩散增量,加快扩散速度
                    diffuseIncrement = 35;

                    //刷新界面
                    postInvalidate();
                }
                else
                {
                    resetData();
                }

                break;
        }

        return super.onTouchEvent(event);
    }


    @Override
    protected void dispatchDraw(Canvas canvas)
    {
        super.dispatchDraw(canvas);

        //如果按钮没有被按下,则返回
        if (!isPushButton)
        {
            return;
        }

        //绘制按下按钮后的背景(这里设置为透明背景,以显示底部的背景图片)
        canvas.drawRect(buttonViewX, buttonViewY, buttonViewX + buttonViewWidth, buttonViewY + buttonViewHeight, backgroundPaint);

        //保存当前画布的状态
        canvas.save();

        //从画布中裁剪出待绘制的区域(也就是按钮所覆盖的画布区域)
        canvas.clipRect(buttonViewX, buttonViewY, buttonViewX + buttonViewWidth, buttonViewY + buttonViewHeight);

        //绘制扩散的圆形
        canvas.drawCircle(touchEventX, touchEventY, diffuseRadius, diffusePaint);

        //恢复画布之前的状态
        canvas.restore();

        if (diffuseRadius < maxDiffuseRadius)
        {
            postInvalidateDelayed(INVALIDATE_PERIOD, buttonViewX, buttonViewY, buttonViewX + buttonViewWidth, buttonViewY + buttonViewHeight);

            diffuseRadius += diffuseIncrement;
        }
        else
        {
            resetData();
        }

    }


    //获取按钮的宽度和高度
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh)
    {
        super.onSizeChanged(w, h, oldw, oldh);

        this.buttonViewWidth = w;
        this.buttonViewHeight = h;
    }


    //初始化界面数据
    private void initData()
    {
        downTime = 0;
        diffuseIncrement = 10;
        diffuseRadius = 0;
        buttonViewX = buttonViewY = 0;
        isPushButton = false;
        longPressTimeout = ViewConfiguration.getLongPressTimeout();
    }


    //初始化必要的画笔
    private void initPaints()
    {
        backgroundPaint = new Paint();
        //设置透明背景,以显示底部的背景图片
        backgroundPaint.setColor(Color.parseColor("#00000000"));

        diffusePaint = new Paint();
        //设置画笔为灰色,制造阴影波纹扩散效果
        diffusePaint.setColor(Color.parseColor("#50000000"));
    }


    //计算扩散效果的最大半径
    private void countMaxDiffuseRadius()
    {
        if (buttonViewWidth > buttonViewHeight)
        {
            if (touchEventX < buttonViewWidth / 2)
            {
                maxDiffuseRadius = buttonViewWidth - touchEventX;
            }
            else
            {
                maxDiffuseRadius = touchEventX;
            }
        }
        else
        {
            if (touchEventY < buttonViewHeight / 2)
            {
                maxDiffuseRadius = buttonViewHeight - touchEventY;
            }
            else
            {
                maxDiffuseRadius = touchEventY;
            }
        }
    }


    //重置数据并刷新界面
    private void resetData()
    {
        downTime = 0;
        diffuseIncrement = 10;
        diffuseRadius = 0;

        //刷新界面(取消波纹效果)
        isPushButton = false;
        postInvalidate();
    }

}


2、按钮背景图

可以根据自己的需要替换成带有文字的任何图片。


3、xml布局文件

Code:

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:orientation="vertical"
              >

    <com.example.wuchangi.customview.RippleButton
            android:id="@+id/custom_button"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="20dp"
            android:src="@mipmap/button_view"/>

</LinearLayout>


4、MainActivity

Code:

MainActivity.java

package com.example.wuchangi.customview;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast;


/**
 * Created by WuchangI on 2018/6/10.
 */

public class MainActivity extends AppCompatActivity
{
    private RippleButton rippleButton;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        rippleButton = (RippleButton)findViewById(R.id.custom_button);
        rippleButton.setOnClickListener(new View.OnClickListener()
        {
            @Override
            public void onClick(View view)
            {
                Toast.makeText(MainActivity.this, "我是一款自定义的button~~", Toast.LENGTH_SHORT).show();
            }
        });

    }
}



四、其他想说的

其实,以上的绘制机制不仅可以用来绘制按钮的阴影扩散,我们也可以根据自己的需要将扩散的阴影颜色替换成其他颜色,实现水波扩散的效果。


3

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
TypeArray 是 Android 中的一个特殊的资源类型,用于在 XML 中声明自定义 View 的属性。使用 TypeArray 可以方便地在 XML 布局中指定 View 的属性,而不需要在 Java 代码中进行硬编码。 使用 TypeArray 的步骤如下: 1. 在 res/values/attrs.xml 文件中定义自定义 View 的属性。 ```xml <resources> <declare-styleable name="MyCustomView"> <attr name="customAttr1" format="integer" /> <attr name="customAttr2" format="string" /> <attr name="customAttr3" format="boolean" /> </declare-styleable> </resources> ``` 2. 在自定义 View 的构造函数中获取 TypedArray 对象,并从中获取属性值。 ```java public class MyCustomView extends View { private int customAttr1; private String customAttr2; private boolean customAttr3; public MyCustomView(Context context, AttributeSet attrs) { super(context, attrs); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyCustomView); customAttr1 = a.getInt(R.styleable.MyCustomView_customAttr1, 0); customAttr2 = a.getString(R.styleable.MyCustomView_customAttr2); customAttr3 = a.getBoolean(R.styleable.MyCustomView_customAttr3, false); a.recycle(); } } ``` 在上面的代码中,`context.obtainStyledAttributes(attrs, R.styleable.MyCustomView)` 用于获取 TypedArray 对象,`R.styleable.MyCustomView` 是在 attrs.xml 文件中定义的自定义属性集合,`a.getInt()`、`a.getString()`、`a.getBoolean()` 用于从 TypedArray 对象中获取属性值,最后需要调用 `a.recycle()` 来回收 TypedArray 对象。 3. 在 XML 布局中使用自定义 View,并设置属性值。 ```xml <com.example.MyCustomView android:layout_width="match_parent" android:layout_height="wrap_content" app:customAttr1="123" app:customAttr2="hello" app:customAttr3="true" /> ``` 在上面的代码中,`app:customAttr1`、`app:customAttr2`、`app:customAttr3` 是在 attrs.xml 文件中定义的自定义属性名,可以在 XML 布局中使用。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值