标签:android geniusbutton material design animation 动画
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/42471119
——学之开源,用于开源;初学者的心态,与君共勉!
========================================================
在我的文章中曾经有两篇关于Material Design风格的按钮实现。在第一章中只是简单的实现了动画的波纹效果,而在第二篇中对此进行了一定的扩充与优化,最后实现可以自动移动到中心位置的动画;虽然两者都可用,但是在我的使用中却发现了一定的问题,如有些位置点击会出现波纹速度的运算上的问题。
在这一章中将带你打造一个极致的Material Design动画风格Button;至少在我看来与官方的相当接近了。
可以看出其基本上差不多了。
首先我们来解析一下官方的:
在这里我截取了最后一个按钮相应的连续几张图片的情况,从图片我们可以看出以下情况:
我们第二张中的按钮之所以有很大的差距我总结出以下几点:
不知道你们在做的过程中是否想过,我们的动画是在用户点击 onTouch() 的基础上不断的刷新触发 onDraw() 然后绘制来的,与一个按钮的结合点也就是这么两个地方,最多为了方便我们结合的地方还有一个 onMeasure() .所以我们能得出这样一个类:
public class TouchEffectAnimator { public TouchEffectAnimator(View mView) { } public void onMeasure() { } public void onTouchEvent(final MotionEvent event) { } public void onDraw(final Canvas canvas) { } private void startAnimation() { } private void cancelAnimation() { } private void fadeOutEffect() { } }一个类,这个类作用于一个控件,所以我们需要传入一个 View.
然后我们提供一个 onMeasure() 方法用于初始化高度宽度等数据;onTouchEvent() 当然是用来在控件中触发点击事件所用的;onDraw() 这个无需说也是控件中调用,用来绘制所用;一个动画当然需要启动方法和取消方法,当然在波纹动画后我们还需要的是 "淡出" 的动画。
而后我们想想,其是我们需要的动画类型无非就是那么几种,我们何不合在一起呢?
public enum TouchEffect { Move, Ease, Ripple, None }在这个枚举中分别代表:一边扩散一边移动到中心,无波纹只有淡入淡出,纯扩散不移动的类型,没有动画的类型。
下面我们来看看主类中的变量情况。
private static final Interpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator(2.8f); private static final Interpolator ACCELERATE_INTERPOLATOR = new AccelerateInterpolator(); private static final int EASE_ANIM_DURATION = 200; private static final int RIPPLE_ANIM_DURATION = 300; private static final int MAX_RIPPLE_ALPHA = (int) (255 * 0.8);分别是:动画减速、加速效果;淡入淡出默认时间200毫秒,扩散时间默认300毫秒,最大的透明度为255的80%用于淡入淡出。主色为255 100%。
在这里,减速效果中之所有一个2.8,其主要作用是使扩散效果在初期尽量的快 (起到隐藏小圆圈),而后期尽量的慢(增强触摸感觉)
private View mView; private int mClipRadius; private int mAnimDuration = RIPPLE_ANIM_DURATION; private TouchEffect mTouchEffect = TouchEffect.Move; private Animation mAnimation = null;一个View,一个圆角弧度,一个动画时间,一个动画类型,最后一个动画类(在这里没有使用属性动画,而是准备采用最基本的动画,采用回调来直接设置参数)
private float mMaxRadius; private float mRadius;一个最大半径,一个当前半径;之所以有最大半径,在我看来有多种情况:如果是移动模式那么其最大半径扫过地区域能达到最长边的75%就行了;如果是纯扩散,如果用户点击的是最右下角,那么其扫过区域最好能达到其对角的长度;更具勾股定理可以得出其为最长边的1.25倍。
private float mDownX, mDownY; private float mCenterX, mCenterY; private float mPaintX, mPaintY;点击坐标,中心坐标,当前圆心坐标
private Paint mPaint = new Paint(); private RectF mRectRectR = new RectF(); private Path mRectPath = new Path(); private int mRectAlpha = 0;一只画笔,一个区域,一个区域所生成的Path路径,一个区域透明度
private boolean isTouchReleased = false; private boolean isAnimatingFadeIn = false;这两个变量主要用于控制淡出动画触发的时机,我们可以这么想:
在用户一直按着控件的时候就算扩散动画完成了也不进行淡出动画,该动画在用户释放时触发;如果用户点击后立刻抬起那么在抬起时肯定不能触发淡出动画,要等到扩散动画完成后才触发;所以一个变量是是否释放按钮,另外一个是是否动画结束。
private Animation.AnimationListener mAnimationListener = new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { isAnimatingFadeIn = true; } @Override public void onAnimationEnd(Animation animation) { isAnimatingFadeIn = false; // Is un touch auto fadeOutEffect() if (isTouchReleased) fadeOutEffect(); } @Override public void onAnimationRepeat(Animation animation) { } };上面刚刚说了,控制其释放触发淡出动画,那么这里这个监听器就是用来监听其开始动画状态的,结束后调整值,如果此时用户释放了按钮则触发淡出效果。OK,继续!
public TouchEffectAnimator(View mView) { this.mView = mView; onMeasure(); } public void onMeasure() { mCenterX = mView.getWidth() / 2; mCenterY = mView.getHeight() / 2; mRectRectR.set(0, 0, mView.getWidth(), mView.getHeight()); mRectPath.reset(); mRectPath.addRoundRect(mRectRectR, mClipRadius, mClipRadius, Path.Direction.CW); }在控件触发 onMeasure() 方法的时候回调该类的 onMeasure() 方法,在该方法中我们得出其中心坐标,初始化一个长方形区域,然后根据区域与圆角半径初始化一个Path路径。
public void setAnimDuration(int animDuration) { this.mAnimDuration = animDuration; } public TouchEffect getTouchEffect() { return mTouchEffect; } public void setTouchEffect(TouchEffect touchEffect) { mTouchEffect = touchEffect; if (mTouchEffect == TouchEffect.Ease) mAnimDuration = EASE_ANIM_DURATION; } public void setEffectColor(int effectColor) { mPaint.setColor(effectColor); } public void setClipRadius(int mClipRadius) { this.mClipRadius = mClipRadius; }既然上面有那么多的变量,那么这里提供了一些方法用于初始化使用,分别是:
动画时间,获取动画类型,设置动画类型,设置颜色,设置控件的圆角弧度。
private void startAnimation() { Animation animation = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { if (mTouchEffect == TouchEffect.Move) { mRadius = mMaxRadius * interpolatedTime; mPaintX = mDownX + (mCenterX - mDownX) * interpolatedTime; mPaintY = mDownY + (mCenterY - mDownY) * interpolatedTime; } else if (mTouchEffect == TouchEffect.Ripple) { mRadius = mMaxRadius * interpolatedTime; } mRectAlpha = (int) (interpolatedTime * MAX_RIPPLE_ALPHA); mView.invalidate(); } }; animation.setInterpolator(DECELERATE_INTERPOLATOR); animation.setDuration(mAnimDuration); animation.setAnimationListener(mAnimationListener); mView.startAnimation(animation); } private void cancelAnimation() { if (mAnimation != null) { mAnimation.cancel(); mAnimation.setAnimationListener(null); } } private void fadeOutEffect() { Animation animation = new Animation() { @Override protected void applyTransformation(float interpolatedTime, Transformation t) { mRectAlpha = (int) (MAX_RIPPLE_ALPHA - (MAX_RIPPLE_ALPHA * interpolatedTime)); mView.invalidate(); } }; animation.setInterpolator(ACCELERATE_INTERPOLATOR); animation.setDuration(EASE_ANIM_DURATION); mView.startAnimation(animation); }
public void onTouchEvent(final MotionEvent event) { if (event.getActionMasked() == MotionEvent.ACTION_CANCEL) { isTouchReleased = true; if (!isAnimatingFadeIn) { fadeOutEffect(); } } if (event.getActionMasked() == MotionEvent.ACTION_UP) { isTouchReleased = true; if (!isAnimatingFadeIn) { fadeOutEffect(); } } else if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { // Gets the bigger value (width or height) to fit the circle mMaxRadius = mCenterX > mCenterY ? mCenterX : mCenterY; // This circle radius is 75% or fill all if (mTouchEffect == TouchEffect.Move) mMaxRadius *= 0.75; else mMaxRadius *= 2.5; // Set default operation to fadeOutEffect() isTouchReleased = false; isAnimatingFadeIn = true; // Set this start point mPaintX = mDownX = event.getX(); mPaintY = mDownY = event.getY(); // This color alpha mRectAlpha = 0; // Cancel and Start new animation cancelAnimation(); startAnimation(); } }在触发方法中,我们分别需要判断是:取消/抬起/按下 操作。
public void onDraw(final Canvas canvas) { // Draw Area mPaint.setAlpha(mRectAlpha); canvas.drawPath(mRectPath, mPaint); // Draw Ripple if (isAnimatingFadeIn && (mTouchEffect == TouchEffect.Move || mTouchEffect == TouchEffect.Ripple)) { // Canvas Clip canvas.clipPath(mRectPath); mPaint.setAlpha(MAX_RIPPLE_ALPHA); canvas.drawCircle(mPaintX, mPaintY, mRadius, mPaint); } }这个方法是最后一个方法,也是较核心的一个地方,我们的成果就靠这个方法了。
首先当然是画出背景部分,在画之前当然就是设置背景色;该背景色是一个随动画时间变化的量,具体详见上面动画部分。
然后判断是否是启动动画,因为淡出时也会触发该方法但是却不绘制圆形区域部分,所以需要判断;之后判断是否是属于需要绘制圆形的动画类型;再然后就是绘制具体的圆形区域了,分别就是坐标和半径;但是这里需要注意的是,在绘制前我们调用了 canvas.clipPath(mRectPath); 。
canvas.clipPath(mRectPath):这个的作用就是剪切,意思是剪切画布部分,然后在剪切后的画布上绘制;这样就解决了圆角时溢出的问题,因为剪切后的画布就那么大你就算画到外部也是无法显示的。
public class GeniusButton extends Button implements Attributes.AttributeChangeListener { private TouchEffectAnimator touchEffectAnimator = null; public void setTouchEffect(TouchEffect touchEffect) { if (touchEffect == TouchEffect.None) touchEffectAnimator = null; else { if (touchEffectAnimator == null) { touchEffectAnimator = new TouchEffectAnimator(this); touchEffectAnimator.setTouchEffect(touchEffect); touchEffectAnimator.setEffectColor("this color"); touchEffectAnimator.setClipRadius(20); } } } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (touchEffectAnimator != null) touchEffectAnimator.onMeasure(); } @Override protected void onDraw(Canvas canvas) { if (touchEffectAnimator != null) touchEffectAnimator.onDraw(canvas); super.onDraw(canvas); } @Override public boolean onTouchEvent(MotionEvent event) { if (touchEffectAnimator != null) touchEffectAnimator.onTouchEvent(event); return super.onTouchEvent(event); } }在你自定义的控件中按着上面的方式进行实例化调用就OK。
算是分析完了,下面附上源码和我分析时画的一些图,辅助解释。
========================================================
作者:qiujuer
博客:blog.csdn.net/qiujuer
网站:www.qiujuer.net
开源库:Genius-Android
转载请注明出处:http://blog.csdn.net/qiujuer/article/details/42471119
——学之开源,用于开源;初学者的心态,与君共勉!
========================================================
标签:android geniusbutton material design animation 动画
原文地址:http://blog.csdn.net/qiujuer/article/details/42471119