一、效果预览
闲话少叙,先看看效果:
从上图可以看到,当我们长按时,“按钮”上的阴影扩散得比较慢;当我们纯粹只是点击一下时,“按钮”上的阴影扩散速度加快。
二、实现原理
其实,上图中显示的“按钮”是基于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();
}
});
}
}
四、其他想说的
其实,以上的绘制机制不仅可以用来绘制按钮的阴影扩散,我们也可以根据自己的需要将扩散的阴影颜色替换成其他颜色,实现水波扩散的效果。