您的位置:首页 > 移动开发 > Android开发

Android 5.0 Material Design的实现点击任意View的水波效果

2016-11-10 22:57 351 查看
文章目的:点击任意的View,实现水波的点击效果。

本文来自【 Mr.Simple的博客


前言:自从Android 5.0问世以后,它的UI风格受到了大家普遍的赞美,简单、动感十足,但是由于工作比较忙,本人对于Android
5.0并没有太多的关注。前几天在知名博主任玉刚 ( 博客地址 )
帅哥的群中有同学问到实现Android 5.0 Material Design中的点击任意View时产生水波的效果,刚哥表示已经实现水波效果,但是需要过段时间才能开源出来。刚好本人在昨天写了声波支付的波纹效果,于是今天按照刚哥给出的实现思路弄了一下,于是也就有了今天的文章。可能效果不是很好,分享出来一是自我学习,二也是希望分享一下思路。



从目前的一些实现来看,主要有那么两个实现思路,第一种就是自定义View,比如继承Button,在Button的onDraw里面再动态绘制一层背景,然后改变背景的大小以及颜色,达到动态效果,这种实现使用比较局限,自定义一种类型的View,那么就只有这种View能够产生波纹效果;另一种是自定义布局,然后该布局中只有一个视图,也是同样的方法绘制背景,然后动画,但是也有局限性,就是一个布局中只能放一个视图,只有这个视图能够产生水波效果!

现实的情况是我们需要所有的视图在点击时都产生波纹效果,那么问题就来了,如何实现呢?

代码实现

其实大家的实现思路都是类似的,这是适用性、复杂度的问题。

我的实现思路是自定义一个布局,然后在用户触摸该布局时,通过该触摸点的坐标找到对应的子视图,找到该视图后我们在布局的dispatchDraw函数中裁剪一块区域,并且在这块区域中绘制波纹效果,使得背景图层的半径逐渐增大、透明度逐渐减小。这样点击某个视图时它的上面就产生了一个逐渐变大、颜色变浅的背景图层,不管是任何视图都会有这个动态效果!效果完成之后清除掉背景图层即可。

直接上代码吧。

[java] view
plain copy

/*

* The MIT License (MIT)

*

* Copyright (c) 2015 bboyfeiyu@gmail.com ( mr.simple )

*

* Permission is hereby granted, free of charge, to any person obtaining a copy

* of this software and associated documentation files (the "Software"), to deal

* in the Software without restriction, including without limitation the rights

* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

* copies of the Software, and to permit persons to whom the Software is

* furnished to do so, subject to the following conditions:

*

* The above copyright notice and this permission notice shall be included in

* all copies or substantial portions of the Software.

*

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

* THE SOFTWARE.

*/

package org.simple;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Point;

import android.graphics.RectF;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.RelativeLayout;

import org.simple.materiallayout.R;

/**

* MaterialLayout是模拟Android 5.0中View被点击的波纹效果的布局,与其他的模拟Material

* Desigin效果的View不同,所有在MaterialLayout布局下的子视图被点击时都会产生波纹效果,而不是某个特定的View才会有这样的效果.

*

* @author mrsimple

*/

public class MaterialLayout extends RelativeLayout {

private static final int DEFAULT_RADIUS = 10;

private static final int DEFAULT_FRAME_RATE = 10;

private static final int DEFAULT_DURATION = 200;

private static final int DEFAULT_ALPHA = 255;

private static final float DEFAULT_SCALE = 0.8f;

private static final int DEFAULT_ALPHA_STEP = 5;

/**

* 动画帧率

*/

private int mFrameRate = DEFAULT_FRAME_RATE;

/**

* 渐变动画持续时间

*/

private int mDuration = DEFAULT_DURATION;

/**

*

*/

private Paint mPaint = new Paint();

/**

* 被点击的视图的中心点

*/

private Point mCenterPoint = null;

/**

* 视图的Rect

*/

private RectF mTargetRectf;

/**

* 起始的圆形背景半径

*/

private int mRadius = DEFAULT_RADIUS;

/**

* 最大的半径

*/

private int mMaxRadius = DEFAULT_RADIUS;

/**

* 渐变的背景色

*/

private int mCirclelColor = Color.LTGRAY;

/**

* 每次重绘时半径的增幅

*/

private int mRadiusStep = 1;

/**

* 保存用户设置的alpha值

*/

private int mBackupAlpha;

/**

* 圆形半径针对于被点击视图的缩放比例,默认为0.8

*/

private float mCircleScale = DEFAULT_SCALE;

/**

* 颜色的alpha值, (0, 255)

*/

private int mColorAlpha = DEFAULT_ALPHA;

/**

* 每次动画Alpha的渐变递减值

*/

private int mAlphaStep = DEFAULT_ALPHA_STEP;

private View mTargetView;

/**

* @param context

*/

public MaterialLayout(Context context) {

this(context, null);

}

public MaterialLayout(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

public MaterialLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

init(context, attrs);

}

private void init(Context context, AttributeSet attrs) {

if (isInEditMode()) {

return;

}

if (attrs != null) {

initTypedArray(context, attrs);

}

initPaint();

this.setWillNotDraw(false);

this.setDrawingCacheEnabled(true);

}

private void initTypedArray(Context context, AttributeSet attrs) {

final TypedArray typedArray = context.obtainStyledAttributes(attrs,

R.styleable.MaterialLayout);

mCirclelColor = typedArray.getColor(R.styleable.MaterialLayout_color, Color.LTGRAY);

mDuration = typedArray.getInteger(R.styleable.MaterialLayout_duration,

DEFAULT_DURATION);

mFrameRate = typedArray

.getInteger(R.styleable.MaterialLayout_framerate, DEFAULT_FRAME_RATE);

mColorAlpha = typedArray.getInteger(R.styleable.MaterialLayout_alpha, DEFAULT_ALPHA);

mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE);

typedArray.recycle();

}

private void initPaint() {

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(mCirclelColor);

mPaint.setAlpha(mColorAlpha);

// 备份alpha属性用于动画完成时重置

mBackupAlpha = mColorAlpha;

}

/**

* 点击的某个坐标点是否在View的内部

*

* @param touchView

* @param x 被点击的x坐标

* @param y 被点击的y坐标

* @return 如果点击的坐标在该view内则返回true,否则返回false

*/

private boolean isInFrame(View touchView, float x, float y) {

initViewRect(touchView);

return mTargetRectf.contains(x, y);

}

/**

* 获取点中的区域,屏幕绝对坐标值,这个高度值也包含了状态栏和标题栏高度

*

* @param touchView

*/

private void initViewRect(View touchView) {

int[] location = new int[2];

touchView.getLocationOnScreen(location);

// 视图的区域

mTargetRectf = new RectF(location[0], location[1], location[0]

+ touchView.getWidth(), location[1] + touchView.getHeight());

}

/**

* 减去状态栏和标题栏的高度

*/

private void removeExtraHeight() {

int[] location = new int[2];

this.getLocationOnScreen(location);

// 减去两个该布局的top,这个top值就是状态栏的高度

mTargetRectf.top -= location[1];

mTargetRectf.bottom -= location[1];

// 计算中心点坐标

int centerHorizontal = (int) (mTargetRectf.left + mTargetRectf.right) / 2;

int centerVertical = (int) ((mTargetRectf.top + mTargetRectf.bottom) / 2);

// 获取中心点

mCenterPoint = new Point(centerHorizontal, centerVertical);

}

private View findTargetView(ViewGroup viewGroup, float x, float y) {

int childCount = viewGroup.getChildCount();

// 迭代查找被点击的目标视图

for (int i = 0; i < childCount; i++) {

View childView = viewGroup.getChildAt(i);

if (childView instanceof ViewGroup) {

return findTargetView((ViewGroup) childView, x, y);

} else if (isInFrame(childView, x, y)) { // 否则判断该点是否在该View的frame内

return childView;

}

}

return null;

}

private boolean isAnimEnd() {

return mRadius >= mMaxRadius;

}

private void calculateMaxRadius(View view) {

// 取视图的最长边

int maxLength = Math.max(view.getWidth(), view.getHeight());

// 计算Ripple圆形的半径

mMaxRadius = (int) ((maxLength / 2) * mCircleScale);

int redrawCount = mDuration / mFrameRate;

// 计算每次动画半径的增值

mRadiusStep = (mMaxRadius - DEFAULT_RADIUS) / redrawCount;

// 计算每次alpha递减的值

mAlphaStep = (mColorAlpha - 100) / redrawCount;

}

/**

* 处理ACTION_DOWN触摸事件, 注意这里获取的是Raw x, y,

* 即屏幕的绝对坐标,但是这个当屏幕中有状态栏和标题栏时就需要去掉这些高度,因此得到mTargetRectf后其高度需要减去该布局的top起点

* ,也就是标题栏和状态栏的总高度.

*

* @param event

*/

private void deliveryTouchDownEvent(MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_DOWN) {

mTargetView = findTargetView(this, event.getRawX(), event.getRawY());

if (mTargetView != null) {

removeExtraHeight();

// 计算相关数据

calculateMaxRadius(mTargetView);

// 重绘视图

invalidate();

}

}

}

@Override

public boolean onInterceptTouchEvent(MotionEvent event) {

deliveryTouchDownEvent(event);

return super.onInterceptTouchEvent(event);

}

@Override

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

// 绘制Circle

drawRippleIfNecessary(canvas);

}

private void drawRippleIfNecessary(Canvas canvas) {

if (isFoundTouchedSubView()) {

// 计算新的半径和alpha值

mRadius += mRadiusStep;

mColorAlpha -= mAlphaStep;

// 裁剪一块区域,这块区域就是被点击的View的区域.通过clipRect来获取这块区域,使得绘制操作只能在这个区域范围内的进行,

// 即使绘制的内容大于这块区域,那么大于这块区域的绘制内容将不可见. 这样保证了背景层只能绘制在被点击的视图的区域

canvas.clipRect(mTargetRectf);

mPaint.setAlpha(mColorAlpha);

// 绘制背景圆形,也就是

canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint);

}

if (isAnimEnd()) {

reset();

} else {

invalidateDelayed();

}

}

/**

* 发送重绘消息

*/

private void invalidateDelayed() {

this.postDelayed(new Runnable() {

@Override

public void run() {

invalidate();

}

}, mFrameRate);

}

/**

* 判断是否找到被点击的子视图

*

* @return

*/

private boolean isFoundTouchedSubView() {

return mCenterPoint != null && mTargetView != null;

}

private void reset() {

mCenterPoint = null;

mTargetRectf = null;

mRadius = DEFAULT_RADIUS;

mColorAlpha = mBackupAlpha;

mTargetView = null;

invalidate();

}

}

自定义的属性, attrs.xml

[html] view
plain copy

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

<resources>

<declare-styleable name="MaterialLayout">

<attr name="alpha" format="integer" />

<attr name="alpha_step" format="integer" />

<attr name="framerate" format="integer" />

<attr name="duration" format="integer" />

<attr name="color" format="color" />

<attr name="scale" format="float" />

</declare-styleable>

</resources>

使用示例

引用MaterialLayout工程或者将代码和attrs.xml拷贝到你的工程中,然后在你的布局xml中添加MaterialLayout布局,注意,不要忘了引用MaterialLayout自定义属性的命名空间,即下面的xmlns:ml这句。把com.example.materialdemo替换成你的包名就OK了。

[html] view
plain copy

<org.simple.MaterialLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:ml="http://schemas.android.com/apk/res/com.example.materialdemo"

android:id="@+id/layout"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_margin="5dp"

android:background="#f0f0f0"

android:gravity="center"

ml:duration="200"

ml:alpha="200"

ml:scale="1.2"

ml:color="#FFD306" >

<Button

android:id="@+id/my_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#33CC99"

android:padding="10dp"

android:text="@string/click"

android:textSize="20sp" />

<ImageView

android:id="@+id/my_imageview1"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_below="@id/my_button"

android:layout_marginTop="30dp"

android:background="#33CC99"

android:contentDescription="@string/app_name"

android:padding="10dp"

android:src="@drawable/ic_launcher" />

</org.simple.MaterialLayout>

效果图



这个gif录得有点卡,真机上看起来还是不错的。大家可以到github上clone一份运行看看效果,如果觉得不行也别喷,给出你的github地址,本人也愿意学习您的优秀实现。在这里也期待刚哥早日开源出更好的实现。

GitHub下载地址 :https://github.com/bboyfeiyu/materiallayout



从目前的一些实现来看,主要有那么两个实现思路,第一种就是自定义View,比如继承Button,在Button的onDraw里面再动态绘制一层背景,然后改变背景的大小以及颜色,达到动态效果,这种实现使用比较局限,自定义一种类型的View,那么就只有这种View能够产生波纹效果;另一种是自定义布局,然后该布局中只有一个视图,也是同样的方法绘制背景,然后动画,但是也有局限性,就是一个布局中只能放一个视图,只有这个视图能够产生水波效果!

现实的情况是我们需要所有的视图在点击时都产生波纹效果,那么问题就来了,如何实现呢?

代码实现

其实大家的实现思路都是类似的,这是适用性、复杂度的问题。

我的实现思路是自定义一个布局,然后在用户触摸该布局时,通过该触摸点的坐标找到对应的子视图,找到该视图后我们在布局的dispatchDraw函数中裁剪一块区域,并且在这块区域中绘制波纹效果,使得背景图层的半径逐渐增大、透明度逐渐减小。这样点击某个视图时它的上面就产生了一个逐渐变大、颜色变浅的背景图层,不管是任何视图都会有这个动态效果!效果完成之后清除掉背景图层即可。

直接上代码吧。

[java] view
plain copy

/*

* The MIT License (MIT)

*

* Copyright (c) 2015 bboyfeiyu@gmail.com ( mr.simple )

*

* Permission is hereby granted, free of charge, to any person obtaining a copy

* of this software and associated documentation files (the "Software"), to deal

* in the Software without restriction, including without limitation the rights

* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell

* copies of the Software, and to permit persons to whom the Software is

* furnished to do so, subject to the following conditions:

*

* The above copyright notice and this permission notice shall be included in

* all copies or substantial portions of the Software.

*

* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR

* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,

* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE

* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER

* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,

* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN

* THE SOFTWARE.

*/

package org.simple;

import android.content.Context;

import android.content.res.TypedArray;

import android.graphics.Canvas;

import android.graphics.Color;

import android.graphics.Paint;

import android.graphics.Point;

import android.graphics.RectF;

import android.util.AttributeSet;

import android.view.MotionEvent;

import android.view.View;

import android.view.ViewGroup;

import android.widget.RelativeLayout;

import org.simple.materiallayout.R;

/**

* MaterialLayout是模拟Android 5.0中View被点击的波纹效果的布局,与其他的模拟Material

* Desigin效果的View不同,所有在MaterialLayout布局下的子视图被点击时都会产生波纹效果,而不是某个特定的View才会有这样的效果.

*

* @author mrsimple

*/

public class MaterialLayout extends RelativeLayout {

private static final int DEFAULT_RADIUS = 10;

private static final int DEFAULT_FRAME_RATE = 10;

private static final int DEFAULT_DURATION = 200;

private static final int DEFAULT_ALPHA = 255;

private static final float DEFAULT_SCALE = 0.8f;

private static final int DEFAULT_ALPHA_STEP = 5;

/**

* 动画帧率

*/

private int mFrameRate = DEFAULT_FRAME_RATE;

/**

* 渐变动画持续时间

*/

private int mDuration = DEFAULT_DURATION;

/**

*

*/

private Paint mPaint = new Paint();

/**

* 被点击的视图的中心点

*/

private Point mCenterPoint = null;

/**

* 视图的Rect

*/

private RectF mTargetRectf;

/**

* 起始的圆形背景半径

*/

private int mRadius = DEFAULT_RADIUS;

/**

* 最大的半径

*/

private int mMaxRadius = DEFAULT_RADIUS;

/**

* 渐变的背景色

*/

private int mCirclelColor = Color.LTGRAY;

/**

* 每次重绘时半径的增幅

*/

private int mRadiusStep = 1;

/**

* 保存用户设置的alpha值

*/

private int mBackupAlpha;

/**

* 圆形半径针对于被点击视图的缩放比例,默认为0.8

*/

private float mCircleScale = DEFAULT_SCALE;

/**

* 颜色的alpha值, (0, 255)

*/

private int mColorAlpha = DEFAULT_ALPHA;

/**

* 每次动画Alpha的渐变递减值

*/

private int mAlphaStep = DEFAULT_ALPHA_STEP;

private View mTargetView;

/**

* @param context

*/

public MaterialLayout(Context context) {

this(context, null);

}

public MaterialLayout(Context context, AttributeSet attrs) {

super(context, attrs);

init(context, attrs);

}

public MaterialLayout(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

init(context, attrs);

}

private void init(Context context, AttributeSet attrs) {

if (isInEditMode()) {

return;

}

if (attrs != null) {

initTypedArray(context, attrs);

}

initPaint();

this.setWillNotDraw(false);

this.setDrawingCacheEnabled(true);

}

private void initTypedArray(Context context, AttributeSet attrs) {

final TypedArray typedArray = context.obtainStyledAttributes(attrs,

R.styleable.MaterialLayout);

mCirclelColor = typedArray.getColor(R.styleable.MaterialLayout_color, Color.LTGRAY);

mDuration = typedArray.getInteger(R.styleable.MaterialLayout_duration,

DEFAULT_DURATION);

mFrameRate = typedArray

.getInteger(R.styleable.MaterialLayout_framerate, DEFAULT_FRAME_RATE);

mColorAlpha = typedArray.getInteger(R.styleable.MaterialLayout_alpha, DEFAULT_ALPHA);

mCircleScale = typedArray.getFloat(R.styleable.MaterialLayout_scale, DEFAULT_SCALE);

typedArray.recycle();

}

private void initPaint() {

mPaint.setAntiAlias(true);

mPaint.setStyle(Paint.Style.FILL);

mPaint.setColor(mCirclelColor);

mPaint.setAlpha(mColorAlpha);

// 备份alpha属性用于动画完成时重置

mBackupAlpha = mColorAlpha;

}

/**

* 点击的某个坐标点是否在View的内部

*

* @param touchView

* @param x 被点击的x坐标

* @param y 被点击的y坐标

* @return 如果点击的坐标在该view内则返回true,否则返回false

*/

private boolean isInFrame(View touchView, float x, float y) {

initViewRect(touchView);

return mTargetRectf.contains(x, y);

}

/**

* 获取点中的区域,屏幕绝对坐标值,这个高度值也包含了状态栏和标题栏高度

*

* @param touchView

*/

private void initViewRect(View touchView) {

int[] location = new int[2];

touchView.getLocationOnScreen(location);

// 视图的区域

mTargetRectf = new RectF(location[0], location[1], location[0]

+ touchView.getWidth(), location[1] + touchView.getHeight());

}

/**

* 减去状态栏和标题栏的高度

*/

private void removeExtraHeight() {

int[] location = new int[2];

this.getLocationOnScreen(location);

// 减去两个该布局的top,这个top值就是状态栏的高度

mTargetRectf.top -= location[1];

mTargetRectf.bottom -= location[1];

// 计算中心点坐标

int centerHorizontal = (int) (mTargetRectf.left + mTargetRectf.right) / 2;

int centerVertical = (int) ((mTargetRectf.top + mTargetRectf.bottom) / 2);

// 获取中心点

mCenterPoint = new Point(centerHorizontal, centerVertical);

}

private View findTargetView(ViewGroup viewGroup, float x, float y) {

int childCount = viewGroup.getChildCount();

// 迭代查找被点击的目标视图

for (int i = 0; i < childCount; i++) {

View childView = viewGroup.getChildAt(i);

if (childView instanceof ViewGroup) {

return findTargetView((ViewGroup) childView, x, y);

} else if (isInFrame(childView, x, y)) { // 否则判断该点是否在该View的frame内

return childView;

}

}

return null;

}

private boolean isAnimEnd() {

return mRadius >= mMaxRadius;

}

private void calculateMaxRadius(View view) {

// 取视图的最长边

int maxLength = Math.max(view.getWidth(), view.getHeight());

// 计算Ripple圆形的半径

mMaxRadius = (int) ((maxLength / 2) * mCircleScale);

int redrawCount = mDuration / mFrameRate;

// 计算每次动画半径的增值

mRadiusStep = (mMaxRadius - DEFAULT_RADIUS) / redrawCount;

// 计算每次alpha递减的值

mAlphaStep = (mColorAlpha - 100) / redrawCount;

}

/**

* 处理ACTION_DOWN触摸事件, 注意这里获取的是Raw x, y,

* 即屏幕的绝对坐标,但是这个当屏幕中有状态栏和标题栏时就需要去掉这些高度,因此得到mTargetRectf后其高度需要减去该布局的top起点

* ,也就是标题栏和状态栏的总高度.

*

* @param event

*/

private void deliveryTouchDownEvent(MotionEvent event) {

if (event.getAction() == MotionEvent.ACTION_DOWN) {

mTargetView = findTargetView(this, event.getRawX(), event.getRawY());

if (mTargetView != null) {

removeExtraHeight();

// 计算相关数据

calculateMaxRadius(mTargetView);

// 重绘视图

invalidate();

}

}

}

@Override

public boolean onInterceptTouchEvent(MotionEvent event) {

deliveryTouchDownEvent(event);

return super.onInterceptTouchEvent(event);

}

@Override

protected void dispatchDraw(Canvas canvas) {

super.dispatchDraw(canvas);

// 绘制Circle

drawRippleIfNecessary(canvas);

}

private void drawRippleIfNecessary(Canvas canvas) {

if (isFoundTouchedSubView()) {

// 计算新的半径和alpha值

mRadius += mRadiusStep;

mColorAlpha -= mAlphaStep;

// 裁剪一块区域,这块区域就是被点击的View的区域.通过clipRect来获取这块区域,使得绘制操作只能在这个区域范围内的进行,

// 即使绘制的内容大于这块区域,那么大于这块区域的绘制内容将不可见. 这样保证了背景层只能绘制在被点击的视图的区域

canvas.clipRect(mTargetRectf);

mPaint.setAlpha(mColorAlpha);

// 绘制背景圆形,也就是

canvas.drawCircle(mCenterPoint.x, mCenterPoint.y, mRadius, mPaint);

}

if (isAnimEnd()) {

reset();

} else {

invalidateDelayed();

}

}

/**

* 发送重绘消息

*/

private void invalidateDelayed() {

this.postDelayed(new Runnable() {

@Override

public void run() {

invalidate();

}

}, mFrameRate);

}

/**

* 判断是否找到被点击的子视图

*

* @return

*/

private boolean isFoundTouchedSubView() {

return mCenterPoint != null && mTargetView != null;

}

private void reset() {

mCenterPoint = null;

mTargetRectf = null;

mRadius = DEFAULT_RADIUS;

mColorAlpha = mBackupAlpha;

mTargetView = null;

invalidate();

}

}

自定义的属性, attrs.xml

[html] view
plain copy

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

<resources>

<declare-styleable name="MaterialLayout">

<attr name="alpha" format="integer" />

<attr name="alpha_step" format="integer" />

<attr name="framerate" format="integer" />

<attr name="duration" format="integer" />

<attr name="color" format="color" />

<attr name="scale" format="float" />

</declare-styleable>

</resources>

使用示例

引用MaterialLayout工程或者将代码和attrs.xml拷贝到你的工程中,然后在你的布局xml中添加MaterialLayout布局,注意,不要忘了引用MaterialLayout自定义属性的命名空间,即下面的xmlns:ml这句。把com.example.materialdemo替换成你的包名就OK了。

[html] view
plain copy

<org.simple.MaterialLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:ml="http://schemas.android.com/apk/res/com.example.materialdemo"

android:id="@+id/layout"

android:layout_width="match_parent"

android:layout_height="match_parent"

android:layout_margin="5dp"

android:background="#f0f0f0"

android:gravity="center"

ml:duration="200"

ml:alpha="200"

ml:scale="1.2"

ml:color="#FFD306" >

<Button

android:id="@+id/my_button"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:background="#33CC99"

android:padding="10dp"

android:text="@string/click"

android:textSize="20sp" />

<ImageView

android:id="@+id/my_imageview1"

android:layout_width="100dp"

android:layout_height="100dp"

android:layout_below="@id/my_button"

android:layout_marginTop="30dp"

android:background="#33CC99"

android:contentDescription="@string/app_name"

android:padding="10dp"

android:src="@drawable/ic_launcher" />

</org.simple.MaterialLayout>

效果图



这个gif录得有点卡,真机上看起来还是不错的。大家可以到github上clone一份运行看看效果,如果觉得不行也别喷,给出你的github地址,本人也愿意学习您的优秀实现。在这里也期待刚哥早日开源出更好的实现。

github仓库

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息