一个酷炫的button变化动画开源库源码分析—Android morph Button(一)
来源:互联网 发布:做照片的软件 编辑:程序博客网 时间:2024/05/19 11:48
最近很是喜爱一些酷炫的动画效果,特意在github上找了一些,看看他们是怎么做到的,做个分析,顺便可以对自定义控件和动画有进一步的认识。
先来看下这个库中button的变化效果是什么样的:
是不是很酷炫,而且中间的变化过程很舒服,没有僵硬的感觉,应用的场景也比较广:只要点击按钮,执行一个操作之后,返回结果,这个结果以对错表示,如果是一个耗时的操作还可以显示执行的进度,有很好的用户体验。比如点击按钮后,在后台进行下载、用户点击按钮进行登录等。
先分析第一个动画效果:
稍微复杂的动画一般是用属性动画来做了,对多个属性进行同时变化,仔细观察这个动画效果可以看到,有width的变化,由长方形变成了圆形,必然有CornerRadius的变化,变化的过程中背景颜色也有变化,最后显示通过和没通过的ICON,来看下ObjectAnimator使用的方法,target就是要变化的对象,propertyName就是要变化的属性,现在要变化的属性已经有了,就是上面说的:width、cornerRadius、color等,那么target应该是什么?
直接是button本身吗?我们知道某个属性变化(如color)是依据target中的setColor()方法来动态设置color的值,也就是button中药提供setColor()、setCornerRadius()这样的方法,来更新对应的值到界面上,一般最后还有调用invalidate()方法来刷新界面展示变化的效果。但是这样实现比较麻烦,这些方法都要我们自己提供。那么Android Morphing Button这个库是怎么做的呢?
其实我们想象这个button动画真的变化的其实就是它的background,这个库就是将backgroud设置为一个GradientDrawable,然后对这个GradientDrawable进行变化,也就事target就是这个GradientDrawable,GradientDrawable本身就有setColor、setCornerRadius、setStroke这些方法,并且会自动刷新UI,这样就不不用我们自己去写这些方法来重绘,大体的思路就是这样的,接下来分析具体的代码。
public static ObjectAnimator ofInt(Object target, String propertyName, int... values)
1. 具体使用:
// sample demonstrate how to morph button to green circle with iconMorphingButton btnMorph = (MorphingButton) findViewById(R.id.btnMorph);// inside on click eventMorphingButton.Params circle = MorphingButton.Params.create() .duration(500) .cornerRadius(dimen(R.dimen.mb_height_56)) // 56 dp .width(dimen(R.dimen.mb_height_56)) // 56 dp .height(dimen(R.dimen.mb_height_56)) // 56 dp .color(color(R.color.green)) // normal state color .colorPressed(color(R.color.green_dark)) // pressed state color .icon(R.drawable.ic_done); // iconbtnMorph.morph(circle);
MorphingButton就是自定义的这个button,里面有个Params的静态内部类,设置一些参数如:cornerRadius、width,color等,表示变化到什么参数,Icon为结束的显示的图标。设置好参数后,就调用
public void morph(@NonNull Params params)
这个方法来执行动画,使用起来很是简单。
接下来看下这个库代码的构成,有下面几个类:
StrokeGradientDrawable.class 这个类就是GradientDrawable就是属性动画要变化的对象,在GradientDrawable的基础上加入了stroke,radius,color的设置,提供了对应set和get方法。
MorphingAnimation.class 这个类就是具体的动画变化类了。
MorphingButton.class 这个类继承自button,在代码中设置background为StrokeGradientDrawable,这样对StrokeGradientDrawable做了属性变化后,动画效果就显示在button上了。
就按照这个顺序来分析具体的代码,先看StrokeGradientDrawable.class:
public class StrokeGradientDrawable { private int mStrokeWidth; private int mStrokeColor; private GradientDrawable mGradientDrawable; private float mRadius; private int mColor; public StrokeGradientDrawable(GradientDrawable drawable) { mGradientDrawable = drawable; } public int getStrokeWidth() { return mStrokeWidth; } public void setStrokeWidth(int strokeWidth) { mStrokeWidth = strokeWidth; mGradientDrawable.setStroke(strokeWidth, getStrokeColor()); } public int getStrokeColor() { return mStrokeColor; } public void setStrokeColor(int strokeColor) { mStrokeColor = strokeColor; mGradientDrawable.setStroke(getStrokeWidth(), strokeColor); } public void setCornerRadius(float radius) { mRadius = radius; mGradientDrawable.setCornerRadius(radius); } public void setColor(int color) { mColor = color; mGradientDrawable.setColor(color); } public int getColor() { return mColor; } public float getRadius() { return mRadius; } public GradientDrawable getGradientDrawable() { return mGradientDrawable; }}
这个类就比较简单,有mStrokeWidth、mStrokeWidth、mRadius、mColor,这几个属性值,还有一个GradientDrawable对象,在构造函数中传入,然后上面几个属性对用的set方法,就是调用的GradientDrawable的对应属性的set方法,剩下的就是get方法。这里要注意的是:属性动画是在变化对象中寻找setXXXX(XXXX即为要变化的属性)方法来进行变化,所以一定要有对应的set方法
接下来看MorphingAnimation.class,这个类
public class MorphingAnimation { //动画结束的回调接口 public interface Listener { void onAnimationEnd(); } //内部参数类:变化的button和回调接口,变化前的属性和变化后的,属性有:圆角、高度、宽度、颜色、描边宽度和颜色 public static class Params { private float fromCornerRadius; private float toCornerRadius; private int fromHeight; private int toHeight; private int fromWidth; private int toWidth; private int fromColor; private int toColor; private int duration; private int fromStrokeWidth; private int toStrokeWidth; private int fromStrokeColor; private int toStrokeColor; private MorphingButton button; private MorphingAnimation.Listener animationListener; private Params(@NonNull MorphingButton button) { this.button = button; } public static Params create(@NonNull MorphingButton button) { return new Params(button); } public Params duration(int duration) { this.duration = duration; return this; } public Params listener(@NonNull MorphingAnimation.Listener animationListener) { this.animationListener = animationListener; return this; } public Params color(int fromColor, int toColor) { this.fromColor = fromColor; this.toColor = toColor; return this; } public Params cornerRadius(int fromCornerRadius, int toCornerRadius) { this.fromCornerRadius = fromCornerRadius; this.toCornerRadius = toCornerRadius; return this; } public Params height(int fromHeight, int toHeight) { this.fromHeight = fromHeight; this.toHeight = toHeight; return this; } public Params width(int fromWidth, int toWidth) { this.fromWidth = fromWidth; this.toWidth = toWidth; return this; } public Params strokeWidth(int fromStrokeWidth, int toStrokeWidth) { this.fromStrokeWidth = fromStrokeWidth; this.toStrokeWidth = toStrokeWidth; return this; } public Params strokeColor(int fromStrokeColor, int toStrokeColor) { this.fromStrokeColor = fromStrokeColor; this.toStrokeColor = toStrokeColor; return this; } } private Params mParams; public MorphingAnimation(@NonNull Params params) { mParams = params; } public void start() { StrokeGradientDrawable background = mParams.button.getDrawableNormal(); ObjectAnimator cornerAnimation = ObjectAnimator.ofFloat(background, "cornerRadius", mParams.fromCornerRadius, mParams.toCornerRadius); ObjectAnimator strokeWidthAnimation = ObjectAnimator.ofInt(background, "strokeWidth", mParams.fromStrokeWidth, mParams.toStrokeWidth); ObjectAnimator strokeColorAnimation = ObjectAnimator.ofInt(background, "strokeColor", mParams.fromStrokeColor, mParams.toStrokeColor); strokeColorAnimation.setEvaluator(new ArgbEvaluator()); ObjectAnimator bgColorAnimation = ObjectAnimator.ofInt(background, "color", mParams.fromColor, mParams.toColor); bgColorAnimation.setEvaluator(new ArgbEvaluator()); ValueAnimator heightAnimation = ValueAnimator.ofInt(mParams.fromHeight, mParams.toHeight); heightAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int val = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = mParams.button.getLayoutParams(); layoutParams.height = val; mParams.button.setLayoutParams(layoutParams); } }); ValueAnimator widthAnimation = ValueAnimator.ofInt(mParams.fromWidth, mParams.toWidth); widthAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator valueAnimator) { int val = (Integer) valueAnimator.getAnimatedValue(); ViewGroup.LayoutParams layoutParams = mParams.button.getLayoutParams(); layoutParams.width = val; mParams.button.setLayoutParams(layoutParams); } }); AnimatorSet animatorSet = new AnimatorSet(); animatorSet.setDuration(mParams.duration); animatorSet.playTogether(strokeWidthAnimation, strokeColorAnimation, cornerAnimation, bgColorAnimation, heightAnimation, widthAnimation); animatorSet.addListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { if (mParams.animationListener != null) { mParams.animationListener.onAnimationEnd(); } } }); animatorSet.start(); }}
里面有一个动画结束的回调接口和一个动画参数的设置内部类,主要看start()方法:
先利用
StrokeGradientDrawable background = mParams.button.getDrawableNormal();
获取到button的normal(还有按下状态)状态下的background,然后就是利用ObjectAnimator来对这个对象的属性进行变化,corner、strokeWidth、strokeColor、color这几个属性都是类似的,以Corner为例,都是利用:
ObjectAnimator cornerAnimation = ObjectAnimator.ofFloat(background, "cornerRadius", mParams.fromCornerRadius, mParams.toCornerRadius);
变化前后的参数就是Params中设置好的参数,然后这里的width和height变化是利用ValueAnimator,在AnimatorUpdateListener中利用 ViewGroup.LayoutParams,根据产生的变化值动态的设置width和height。最后将这几个动画加入到一个AnimatorSet中,来同时显示,并在最后设置动画结束后回调传入的接口。那么这个变化前后的参数值是哪里得到的呢,是在这个类的构造方法中:
public MorphingAnimation(@NonNull Params params) { mParams = params; }
最后我们分析MorphingButton.class这个类
public class MorphingButton extends Button { private Padding mPadding; private int mHeight; private int mWidth; private int mColor; private int mCornerRadius; private int mStrokeWidth; private int mStrokeColor; protected boolean mAnimationInProgress; private StrokeGradientDrawable mDrawableNormal; private StrokeGradientDrawable mDrawablePressed; public MorphingButton(Context context) { super(context); initView(); } public MorphingButton(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public MorphingButton(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (mHeight == 0 && mWidth == 0 && w != 0 && h != 0) { mHeight = getHeight(); mWidth = getWidth(); } } public StrokeGradientDrawable getDrawableNormal() { return mDrawableNormal; } public void morph(@NonNull Params params) { if (!mAnimationInProgress) { mDrawablePressed.setColor(params.colorPressed); mDrawablePressed.setCornerRadius(params.cornerRadius); mDrawablePressed.setStrokeColor(params.strokeColor); mDrawablePressed.setStrokeWidth(params.strokeWidth); if (params.duration == 0) { morphWithoutAnimation(params); } else { morphWithAnimation(params); } mColor = params.color; mCornerRadius = params.cornerRadius; mStrokeWidth = params.strokeWidth; mStrokeColor = params.strokeColor; } } private void morphWithAnimation(@NonNull final Params params) { mAnimationInProgress = true; setText(null); setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); setPadding(mPadding.left, mPadding.top, mPadding.right, mPadding.bottom); MorphingAnimation.Params animationParams = MorphingAnimation.Params.create(this) .color(mColor, params.color) .cornerRadius(mCornerRadius, params.cornerRadius) .strokeWidth(mStrokeWidth, params.strokeWidth) .strokeColor(mStrokeColor, params.strokeColor) .height(getHeight(), params.height) .width(getWidth(), params.width) .duration(params.duration) .listener(new MorphingAnimation.Listener() { @Override public void onAnimationEnd() { finalizeMorphing(params); } }); MorphingAnimation animation = new MorphingAnimation(animationParams); animation.start(); } private void morphWithoutAnimation(@NonNull Params params) { mDrawableNormal.setColor(params.color); mDrawableNormal.setCornerRadius(params.cornerRadius); mDrawableNormal.setStrokeColor(params.strokeColor); mDrawableNormal.setStrokeWidth(params.strokeWidth); if(params.width != 0 && params.height !=0) { ViewGroup.LayoutParams layoutParams = getLayoutParams(); layoutParams.width = params.width; layoutParams.height = params.height; setLayoutParams(layoutParams); } finalizeMorphing(params); } private void finalizeMorphing(@NonNull Params params) { mAnimationInProgress = false; if (params.icon != 0 && params.text != null) { setIconLeft(params.icon); setText(params.text); } else if (params.icon != 0) { setIcon(params.icon); } else if(params.text != null) { setText(params.text); } if (params.animationListener != null) { params.animationListener.onAnimationEnd(); } } public void blockTouch() { setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return true; } }); } public void unblockTouch() { setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { return false; } }); invalidate(); } private void initView() { mPadding = new Padding(); mPadding.left = getPaddingLeft(); mPadding.right = getPaddingRight(); mPadding.top = getPaddingTop(); mPadding.bottom = getPaddingBottom(); Resources resources = getResources(); int cornerRadius = (int) resources.getDimension(R.dimen.mb_corner_radius_2); int blue = resources.getColor(R.color.mb_blue); int blueDark = resources.getColor(R.color.mb_blue_dark); StateListDrawable background = new StateListDrawable(); mDrawableNormal = createDrawable(blue, cornerRadius, 0); mDrawablePressed = createDrawable(blueDark, cornerRadius, 0); mColor = blue; mStrokeColor = blue; mCornerRadius = cornerRadius; background.addState(new int[]{android.R.attr.state_pressed}, mDrawablePressed.getGradientDrawable()); background.addState(StateSet.WILD_CARD, mDrawableNormal.getGradientDrawable()); setBackgroundCompat(background); } private StrokeGradientDrawable createDrawable(int color, int cornerRadius, int strokeWidth) { StrokeGradientDrawable drawable = new StrokeGradientDrawable(new GradientDrawable()); drawable.getGradientDrawable().setShape(GradientDrawable.RECTANGLE); drawable.setColor(color); drawable.setCornerRadius(cornerRadius); drawable.setStrokeColor(color); drawable.setStrokeWidth(strokeWidth); return drawable; } @SuppressWarnings("deprecation") private void setBackgroundCompat(@Nullable Drawable drawable) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { setBackgroundDrawable(drawable); } else { setBackground(drawable); } } public void setIcon(@DrawableRes final int icon) { // post is necessary, to make sure getWidth() doesn't return 0 post(new Runnable() { @Override public void run() { Drawable drawable = getResources().getDrawable(icon); int padding = (getWidth() / 2) - (drawable.getIntrinsicWidth() / 2); setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); setPadding(padding, 0, 0, 0); } }); } public void setIconLeft(@DrawableRes int icon) { setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); }
首先在构造方法中调用了initView()方法,创建一个StateListDrawable对象,然后利用
private StrokeGradientDrawable createDrawable(int color, int cornerRadius, int strokeWidth)
产生一个StrokeGradientDrawable 对象,在利用
background.addState(new int[]{android.R.attr.state_pressed}, mDrawablePressed.getGradientDrawable()); background.addState(StateSet.WILD_CARD, mDrawableNormal.getGradientDrawable()); setBackgroundCompat(background);
分别设置了按下装填和普通状态的背景,当前的mColor,mStrokeColor,mCornerRadius,就是对用Params中变化前的参数,然后看下morph()这个方法,这个方法就是最开始使用的方法:开始进行变换,在其中调用了morphWithAnimation(@NonNull final Params params) 方法,来执行具体的动画,这个方法中传入的params就是使用这个库时,创建的param,代表变化后的参数,是MorphingButton类中的一个内部类,上面代码中没有贴出来,然后根据变化前的参数和传入的变化的param构造 MorphingAnimation.Params这个参数,就是变化前和变化后的参数animationParams,最后利用
MorphingAnimation animation = new MorphingAnimation(animationParams); animation.start();
在创建MorphingAnimation 时,将这个animationParams参数传入,调用start()方法开始动画。可以看到在动画结束后的回调接口中调用了
finalizeMorphing(params);
这个方法里面有个 setIconLeft(params.icon),setText()来设置动画结束后的显示的图标和文字,需要注意的是在setIconLeft()方法中,是利用:
// post is necessary, to make sure getWidth() doesn't return 0 post(new Runnable() { @Override public void run() { Drawable drawable = getResources().getDrawable(icon); int padding = (getWidth() / 2) - (drawable.getIntrinsicWidth() / 2); setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); setPadding(padding, 0, 0, 0); } });
setCompoundDrawablesWithIntrinsicBounds()方法来设置ICON,并且设置一个padding来留出ICON的位置,这里使用的Post(Runnable runbable)方法是为了避免获取获取的getWidth()为0,。
相信说到这里,应该已经明白第一个动画效果是怎么实现的,其实关于button的动画大多数是应该对background的drawable做变换,这个库代码我觉得写得还是不错的,看着比较清晰,对于设置参数这块的代码还是写得挺好的,,在外部调用很直观。
但这只是一个最简单的动画效果,还有一个更加酷炫的动画效果:
这个动画效果和第二个动画效果放到下一篇博客中进行解析。
- 一个酷炫的button变化动画开源库源码分析—Android morph Button(一)
- android Button源码分析
- android Button源码分析
- Morph动画的转移
- 3个按钮选一个 颜色产生变化 android button
- android button light 流程分析(一) — driver
- android button light 流程分析(一) — driver
- android button light 流程分析(一) — driver
- android源码分析——从button的点击事件看回调机制
- Android偏移动画-转圈的Button
- antd源码解析(一)button控件的解析
- android超炫button按钮动画效果
- android Button 源码
- Android自定义View系列之动态变化的Button
- android中的button动画效果
- android自定义一个带进度条的button
- 【Android】如何设置一个自定义的Button
- Android学习笔记:(一)设置Button的点击事件
- HBase client API 实例
- 添加tomcat到eclipse
- 通信协议—HTTP、TCP、UDP
- Python 获得命令行参数的方法
- android textview 走马灯设计
- 一个酷炫的button变化动画开源库源码分析—Android morph Button(一)
- sp<> 强指针类的用法
- 妖股特力A“妖风”重现
- 每日一问之图片压缩并上传问题(12.4)
- 所感所悟
- 意外II(数论)
- Maven+Spring+Spring MVC+MyBatis+MySQL,搭建SSM框架环境
- Android四大组件详解
- 文章标题