Android RippleEffect波纹效果,重写View
在前一篇中介绍了通过重写ViewGroup实现RippleEffect波纹效果,这里再介绍介绍一种通过重写View的方式实现RippleEffect波纹效果的方式,当然,顺便还介绍 通过重写Button或者ImagView,实现点击变色效果的方式,不再写 selector了,直接在XMl的属性中就可以设置。这是fork的Git上的一个项目,有几个小Bug,自己修复了一下。Git地址:https://github.com/myjoybar/AndroidUIView,Git上的是Android studio版本的,这里展示的效果是Eclipse版的。
在CardView中,使用 android:foreground="?android:attr/selectableItemBackground",可以使CardView点击产生触摸点向外扩散波纹的效果,网易新闻客户端item的点击效果就是用CardView的这个属性实现的。关于android Recyclerview 和 CardView,以后介绍。
自定义属性:
<declare-styleable name="UIButton">
<attr name="color_pressed" format="color"/>
<attr name="color_unpressed" format="color"/>
<attr name="ripple_color" format="color"/>
<attr name="ripple_alpha" format="integer"/>
<attr name="ripple_duration" format="integer"/>
<attr name="alpha_pressed" format="integer"/>
<attr name="radius" format="dimension"/>
<attr name="shape_type" format="enum">
<enum name="round" value="0"/>
<enum name="rectangle" value="1"/>
</attr>
</declare-styleable>
- color_unpressed: 未按下填充的颜色。
- color_pressed:按下填充的颜色。
- alpha_pressed:给color_pressed设置透明度,按下的效果就是color_pressed和color_unpressed重叠产生的。
- radius:绘制圆角矩形时x方向上的圆角半径和y方向上的圆角半径。
- shape_type:绘制圆drawCircle或者绘制圆角矩形drawRoundRect。
- ripple_color:RippleEffect波纹颜色。
- ripple_alpha:Ripple透明度。
- radius:Ripple的半径。
在UIBaseButton中,主要是绘制以color_unpressed颜色绘制圆或者绘制圆角矩形:
public class UIBaseButton extends Button {
protected int WIDTH;
protected int HEIGHT;
protected Paint mBackgroundPaint;
protected int mShapeType;
protected int mRadius;
public UIBaseButton(Context context) {
this(context, null);
}
public UIBaseButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public UIBaseButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
protected void init(final Context context, final AttributeSet attrs) {
if (isInEditMode()) return;
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UIButton);
mShapeType = typedArray.getInt(R.styleable.UIButton_shape_type, 1);
mRadius = typedArray.getDimensionPixelSize(R.styleable.UIButton_radius, getResources().getDimensionPixelSize(R.dimen.ui_radius));
int unpressedColor = typedArray.getColor(R.styleable.UIButton_color_unpressed, Color.TRANSPARENT);
typedArray.recycle();
mBackgroundPaint = new Paint();
mBackgroundPaint.setStyle(Paint.Style.FILL);
mBackgroundPaint.setAlpha(Color.alpha(unpressedColor));
mBackgroundPaint.setColor(unpressedColor);
mBackgroundPaint.setAntiAlias(true);
this.setWillNotDraw(false);
this.setDrawingCacheEnabled(true);
this.setClickable(true);
if (unpressedColor != Color.TRANSPARENT)
this.setBackgroundColor(Color.TRANSPARENT);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
WIDTH = w;
HEIGHT = h;
}
@Override
protected void onDraw(Canvas canvas) {
if (mBackgroundPaint == null) {
super.onDraw(canvas);
return;
}
if (mShapeType == 0) {
canvas.drawCircle(WIDTH / 2, HEIGHT / 2, WIDTH / 2, mBackgroundPaint);
} else {
RectF rectF = new RectF();
rectF.set(0, 0, WIDTH, HEIGHT);
canvas.drawRoundRect(rectF, mRadius, mRadius, mBackgroundPaint);
}
super.onDraw(canvas);
}
}
简单介绍一下,不熟悉
Paint,Canvas,path的童鞋去补一下。
Paint.Style.FILL:填充内部,
setAntiAlias(true):抗锯齿功能
setWillNotDraw(false):保证View的onDraw函数被调用
setDrawingCacheEnabled(true):提高绘图速度
如果 drakeet:shape_type="round",那么就以View的中心为圆心,以View的宽度的 1/ 2为半径绘制一个圆。如果drakeet:shape_type="rectangle",就根据设定的四个点绘制矩形。
UIRippleButton:
<pre name="code" class="java">public class UIRippleButton extends UIBaseButton {
private int mRoundRadius;
private int mRippleColor;
private int mRippleDuration;
private int mRippleRadius;
private float pointX, pointY;
private Paint mRipplePaint;
private RectF mRectF;
private Path mPath;
private Timer mTimer;
private TimerTask mTask;
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == MSG_DRAW_COMPLETE) {
invalidate();
}
}
};
private int mRippleAlpha;
// private final static int HALF_ALPHA = 127;
private final static int RIPPLR_ALPHE = 47;
private final static int MSG_DRAW_COMPLETE = 101;
public UIRippleButton(Context context) {
super(context);
}
public UIRippleButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public UIRippleButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
protected void init(final Context context, final AttributeSet attrs) {
super.init(context, attrs);
if (isInEditMode()) {
return;
}
final TypedArray typedArray = context.obtainStyledAttributes(attrs,
R.styleable.UIButton);
mRippleColor = typedArray.getColor(R.styleable.UIButton_ripple_color,
getResources().getColor(R.color.ripple_color));
mRippleAlpha = typedArray.getInteger(R.styleable.UIButton_ripple_alpha,
RIPPLR_ALPHE);
mRippleDuration = typedArray.getInteger(
R.styleable.UIButton_ripple_duration, 1000);
mShapeType = typedArray.getInt(R.styleable.UIButton_shape_type, 1);
mRoundRadius = typedArray.getDimensionPixelSize(
R.styleable.UIButton_radius, getResources()
.getDimensionPixelSize(R.dimen.ui_radius));
typedArray.recycle();
mRipplePaint = new Paint();
mRipplePaint.setColor(mRippleColor);
mRipplePaint.setAlpha(mRippleAlpha);
mRipplePaint.setStyle(Paint.Style.FILL);
mRipplePaint.setAntiAlias(true);
mPath = new Path();
mRectF = new RectF();
pointY = pointX = -1;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mRipplePaint == null) {
return;
}
drawFillCircle(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
pointX = event.getX();
pointY = event.getY();
onStartDrawRipple();
}
return super.onTouchEvent(event);
}
/** Draw ripple effect */
private void drawFillCircle(Canvas canvas) {
if (canvas != null && pointX >= 0 && pointY >= 0) {
int rbX = canvas.getWidth();
int rbY = canvas.getHeight();
float x_max = Math.max(pointX, Math.abs(rbX - pointX));
float y_max = Math.max(pointY, Math.abs(rbY - pointY));
float longDis = (float) Math.sqrt(x_max * x_max + y_max * y_max);
if (mRippleRadius > longDis) {
onCompleteDrawRipple();
return;
}
final float drawSpeed = longDis / mRippleDuration * 35;
mRippleRadius += drawSpeed;
canvas.save();
// canvas.translate(0, 0);//保持原点
mPath.reset();
canvas.clipPath(mPath);
if (mShapeType == 0) {
mPath.addCircle(rbX / 2, rbY / 2, WIDTH / 2, Path.Direction.CCW);
} else {
mRectF.set(0, 0, WIDTH, HEIGHT);
mPath.addRoundRect(mRectF, mRoundRadius, mRoundRadius,
Path.Direction.CCW);
}
canvas.clipPath(mPath, Region.Op.REPLACE);
canvas.drawCircle(pointX, pointY, mRippleRadius, mRipplePaint);
canvas.restore();
}
}
/** Start draw ripple effect */
private void onStartDrawRipple() {
onCompleteDrawRipple();
mTask = new TimerTask() {
@Override
public void run() {
mHandler.sendEmptyMessage(MSG_DRAW_COMPLETE);
}
};
mTimer = new Timer();
mTimer.schedule(mTask, 0, 30);
}
/** Stop draw ripple effect */
private void onCompleteDrawRipple() {
mHandler.removeMessages(MSG_DRAW_COMPLETE);
if (mTimer != null) {
if (mTask != null) {
mTask.cancel();
}
mTimer.cancel();
}
mRippleRadius = 0;
}
}
在onTouchEvent中,当event.getAction() == MotionEvent.ACTION_DOWN按下时,开启一个TimerTask定时器,每个30ms去invalidate()重绘一次View,在onStartDrawRipple()方法中首先执行了onCompleteDrawRipple(),是为了保证在RippleEffect波纹效果还未消失时,再次点击让上一次的效果消失,再执行此次的RippleEffect波纹效果。
在drawFillCircle中,为了保证RippleEffect波纹能够到达距离点击位置最远的Button边缘再消失,通过
int rbX = canvas.getWidth();
int rbY = canvas.getHeight();
float x_max = Math.max(pointX, Math.abs(rbX - pointX));
float y_max = Math.max(pointY, Math.abs(rbY - pointY));
float longDis = (float) Math.sqrt(x_max * x_max + y_max * y_max);
计算出距离点击位置最远的边界点的距离,如果RippleEffect的半径大于这个值了,就调用onCompleteDrawRipple()让效果消失,逻辑和上一篇中的类似。
canvas.clipPath(mPath): makes the clip empty
mPath.addCircle :添加一个圆形路径
mPath.addRoundRect:添加一个圆角矩形路径
Path.Direction.CCW :不闭合,顺时针
canvas.clipPath(mPath, Region.Op.REPLACE):裁剪出圆形或者矩形,
Region.Op.REPLACE指裁剪出的区域为我们的圆或者矩形
对这块不熟悉的话,去看一看ApiDemo中的那个Clipping 例子就明白了。
canvas.drawCircle(pointX, pointY, mRippleRadius, mRipplePaint):绘制出RippleEffect效果的圆。就这么简单的实现了RippleEffect波纹效果了,比上一篇的重写RelativeLayout简单一些。
再来看看Button或者ImageView点击自带的变色效果。
public class UIButton extends UIBaseButton {
private int COVER_ALPHA = 48;
private Paint mPressedPaint;
private int mPressedColor;
public UIButton(Context context) {
super(context);
}
public UIButton(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public UIButton(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
@Override
protected void init(Context context, AttributeSet attrs) {
super.init(context, attrs);
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.UIButton);
COVER_ALPHA = typedArray.getInteger(R.styleable.UIButton_alpha_pressed, COVER_ALPHA);
mPressedColor = typedArray.getColor(R.styleable.UIButton_color_pressed, getResources().getColor(R.color.color_pressed));
typedArray.recycle();
mPressedPaint = new Paint();
mPressedPaint.setStyle(Paint.Style.FILL);
mPressedPaint.setColor(mPressedColor);
mPressedPaint.setAlpha(0);
mPressedPaint.setAntiAlias(true);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mShapeType == 0) {
canvas.drawCircle(WIDTH/2, HEIGHT/2, WIDTH/2.1038f, mPressedPaint);
} else {
RectF rectF = new RectF();
rectF.set(0, 0, WIDTH, HEIGHT);
canvas.drawRoundRect(rectF, mRadius, mRadius, mPressedPaint);
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPressedPaint.setAlpha(COVER_ALPHA);
invalidate();
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
mPressedPaint.setAlpha(0);
invalidate();
break;
}
return super.onTouchEvent(event);
}
这里主要是绘制点击时的改变效果的颜色了,当MotionEvent.ACTION_DOWN按下时, 给color_pressed设置透明度,然后在通过 invalidate()刷新一下View就产生了点击变色的效果了。当MotionEvent.ACTION_UP抬起时:setAlpha(0),再invalidate()一下,效果消失。
在UIImageView中,因为已经有一张图片作为背景了,所以只需绘制点击后的效果,代码和UIButton类似,就不贴出来了。
DEMO下载地址:http://download.youkuaiyun.com/detail/yalinfendou/8808663