这次借助github上的开源项目ShapeLoadingView来学习下ObjectAnimator和animatorSet.
代码结构目录:
- ShapeLoadingView.java
- LoadingView.java
LoadingView是绘制三个基本图形的类。
ShapeLoadingView初始化图形并操作图形进行动画。
下面上加了注释的代码:
package com.mingle.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.DecelerateInterpolator;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.TextView;
import com.mingle.shapeloading.R;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.AnimatorSet;
import com.nineoldandroids.animation.ObjectAnimator;
/**
* Created by zzz40500 on 15/4/6.
*/
public class LoadingView extends FrameLayout {
private static final int ANIMATION_DURATION = 500;
private static float mDistance = 200;
private ShapeLoadingView mShapeLoadingView;
private ImageView mIndicationIm;
private TextView mLoadTextView;
private int mTextAppearance;
private String mLoadText;
public LoadingView(Context context) {
super(context);
}
public LoadingView(Context context, AttributeSet attrs) {
//构造函数
super(context, attrs, 0);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
//这里是通过自定义属性来显示字符串
TypedArray typedArray = context
.obtainStyledAttributes(attrs, R.styleable.LoadingView);
mLoadText = typedArray.getString(R.styleable.LoadingView_loadingText);
mTextAppearance = typedArray.getResourceId(R.styleable.LoadingView_loadingTextAppearance, -1);
typedArray.recycle();
}
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
//构造函数
super(context, attrs, defStyleAttr);
init(context, attrs);
}
//这里定义了一个针对LL版本的构造函数,我这可能因为sdk版本这里会报错,如果报错注释掉就行了
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public LoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init(context, attrs);
}
//dp和像素的转换
public int dip2px(float dipValue) {
final float scale = getContext().getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
//引入布局
@Override
protected void onFinishInflate() {
super.onFinishInflate();
View view = LayoutInflater.from(getContext()).inflate(R.layout.load_view, null);
mDistance = dip2px(54f);
LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
layoutParams.gravity = Gravity.CENTER;
mShapeLoadingView = (ShapeLoadingView) view.findViewById(R.id.shapeLoadingView);
mIndicationIm = (ImageView) view.findViewById(R.id.indication);
mLoadTextView = (TextView) view.findViewById(R.id.promptTV);
if (mTextAppearance != -1) {
mLoadTextView.setTextAppearance(getContext(), mTextAppearance);
}
setLoadingText(mLoadText);
//显示绘画布局
addView(view, layoutParams);
//这里是设计一个延时 每隔900调用一次跌落,相当于900ms是一次动画的周期
this.postDelayed(new Runnable() {
@Override
public void run() {
freeFall();
}
}, 900);
}
public void setLoadingText(CharSequence loadingText) {
if (TextUtils.isEmpty(loadingText)) {
mLoadTextView.setVisibility(GONE);
} else {
mLoadTextView.setVisibility(VISIBLE);
}
mLoadTextView.setText(loadingText);
}
/**
* 上抛,上抛是动画的核心,上抛是两个组合动作:1,图形进行旋转;2,图形向上平移,同时还有下面阴影部分随着图形位置变化
* 进行的跟随变化。这里使用了ObjectAnimator来控制每个动画的动作,最后使用AnimatorSet将三个部分组合在一起。
* 看一下具体的动作
*/
public void upThrow() {
//mShapeLoadingView就是LoadingView里面绘制的图形买第一个objectAnimator控制它进行平移
//使用objectAnimator.ofFloat及参数translationY来进行纵向的平移
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mShapeLoadingView, "translationY", mDistance, 0);
//动画下部的阴影这里使用ofFloat及参数scaleX来进行X轴的缩放,02f-1是缩放比例 阴影在20%到100%之间变化
ObjectAnimator scaleIndication = ObjectAnimator.ofFloat(mIndicationIm, "scaleX", 0.2f, 1);
//这段是对图形做一个旋转的动作
ObjectAnimator objectAnimator1 = null;
switch (mShapeLoadingView.getShape()) {
case SHAPE_RECT:
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, -120);
break;
case SHAPE_CIRCLE:
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, 180);
break;
case SHAPE_TRIANGLE:
objectAnimator1 = ObjectAnimator.ofFloat(mShapeLoadingView, "rotation", 0, 180);
break;
}
//设置animation的持续时间,通过setDuration.
objectAnimator.setDuration(ANIMATION_DURATION);
objectAnimator1.setDuration(ANIMATION_DURATION);
//设置一个减速插值器
objectAnimator.setInterpolator(new DecelerateInterpolator(factor));
objectAnimator1.setInterpolator(new DecelerateInterpolator(factor));
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATION_DURATION);
//animatorSet的方法playtogther让三个动画同时运行
animatorSet.playTogether(objectAnimator, objectAnimator1, scaleIndication);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
freeFall();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet.start();
}
public float factor = 1.2f;
/**
* 下落
*/
public void freeFall() {
//主要的点和上抛一致不讲了
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(mShapeLoadingView, "translationY", 0, mDistance);
ObjectAnimator scaleIndication = ObjectAnimator.ofFloat(mIndicationIm, "scaleX", 1, 0.2f);
objectAnimator.setDuration(ANIMATION_DURATION);
objectAnimator.setInterpolator(new AccelerateInterpolator(factor));
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(ANIMATION_DURATION);
animatorSet.playTogether(objectAnimator, scaleIndication);
animatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
//下落到底端改变图形
mShapeLoadingView.changeShape();
upThrow();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animatorSet.start();
}
}
package com.mingle.widget;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Build;
import android.util.AttributeSet;
import android.view.View;
import com.mingle.shapeloading.R;
/**
* Created by zzz40500 on 15/4/4.
*/
public class ShapeLoadingView extends View {
private static final float genhao3 = 1.7320508075689f;
private static final float mTriangle2Circle =0.25555555f;
private Shape mShape = Shape.SHAPE_CIRCLE;
/**
* 用贝赛尔曲线画圆
*/
private float mMagicNumber = 0.55228475f;
public ShapeLoadingView(Context context) {
super(context);
init();
}
public ShapeLoadingView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ShapeLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
public ShapeLoadingView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setColor(getResources().getColor(R.color.triangle));
mPaint.setAntiAlias(true);
//看到网上说这个FILL_AND_STROKE有去锯齿的作用
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
setBackgroundColor(getResources().getColor(R.color.view_bg));
}
public boolean mIsLoading = false;
private Paint mPaint;
private float mControlX = 0;
private float mControlY = 0;
private float mAnimPercent;
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//绘制三个图形的三角形方框圆形的位置,作者在这里标记动画可以优化,估计会有后续修改
if(getVisibility()==GONE){
return;
}
// FIXME: 15/6/15 动画待优化
switch (mShape) {
case SHAPE_TRIANGLE:
if (mIsLoading) {
mAnimPercent += 0.1611113;
// triangle to circle
Path path = new Path();
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f));
if (mAnimPercent >= 1) {
mShape = Shape.SHAPE_CIRCLE;
mIsLoading = false;
mAnimPercent=1;
}
float controlX = mControlX - relativeXFromView(mAnimPercent* mTriangle2Circle)
* genhao3;
float controlY = mControlY - relativeYFromView(mAnimPercent* mTriangle2Circle);
path.quadTo(relativeXFromView(1) - controlX, controlY, relativeXFromView(0.5f + genhao3 / 4), relativeYFromView(0.75f));
path.quadTo(relativeXFromView(0.5f), relativeYFromView(0.75f + 2 * mAnimPercent* mTriangle2Circle), relativeXFromView(0.5f - genhao3 / 4), relativeYFromView(0.75f));
path.quadTo(controlX, controlY, relativeXFromView(0.5f), relativeYFromView(0f));
path.close();
canvas.drawPath(path, mPaint);
invalidate();
} else {
Path path = new Path();
mPaint.setColor(getResources().getColor(R.color.triangle));
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f));
path.lineTo(relativeXFromView(1), relativeYFromView(genhao3 / 2f));
path.lineTo(relativeXFromView(0), relativeYFromView(genhao3/2f));
mControlX = relativeXFromView(0.5f - genhao3 / 8.0f);
mControlY = relativeYFromView(3 / 8.0f);
mAnimPercent = 0;
path.close();
canvas.drawPath(path, mPaint);
}
break;
case SHAPE_CIRCLE:
if (mIsLoading) {
float magicNumber = mMagicNumber + mAnimPercent;
mAnimPercent += 0.12;
if (magicNumber + mAnimPercent >= 1.9f) {
mShape = Shape.SHAPE_RECT;
mIsLoading = false;
}
Path path = new Path();
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f));
path.cubicTo(relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(0f),
relativeXFromView(1), relativeYFromView(0.5f - magicNumber / 2),
relativeXFromView(1f), relativeYFromView(0.5f));
path.cubicTo(
relativeXFromView(1), relativeXFromView(0.5f + magicNumber / 2),
relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(1f),
relativeXFromView(0.5f), relativeYFromView(1f));
path.cubicTo(relativeXFromView(0.5f - magicNumber / 2), relativeXFromView(1f),
relativeXFromView(0), relativeYFromView(0.5f + magicNumber / 2),
relativeXFromView(0f), relativeYFromView(0.5f));
path.cubicTo(relativeXFromView(0f), relativeXFromView(0.5f - magicNumber / 2),
relativeXFromView(0.5f - magicNumber / 2), relativeYFromView(0),
relativeXFromView(0.5f), relativeYFromView(0f));
path.close();
canvas.drawPath(path, mPaint);
invalidate();
} else { mPaint.setColor(getResources().getColor(R.color.circle));
Path path = new Path();
float magicNumber = mMagicNumber;
path.moveTo(relativeXFromView(0.5f), relativeYFromView(0f));
path.cubicTo(relativeXFromView(0.5f + magicNumber / 2), 0,
relativeXFromView(1), relativeYFromView(magicNumber / 2),
relativeXFromView(1f), relativeYFromView(0.5f));
path.cubicTo(
relativeXFromView(1), relativeXFromView(0.5f + magicNumber / 2),
relativeXFromView(0.5f + magicNumber / 2), relativeYFromView(1f),
relativeXFromView(0.5f), relativeYFromView(1f));
path.cubicTo(relativeXFromView(0.5f - magicNumber / 2), relativeXFromView(1f),
relativeXFromView(0), relativeYFromView(0.5f + magicNumber / 2),
relativeXFromView(0f), relativeYFromView(0.5f));
path.cubicTo(relativeXFromView(0f), relativeXFromView(0.5f - magicNumber / 2),
relativeXFromView(0.5f - magicNumber / 2), relativeYFromView(0),
relativeXFromView(0.5f), relativeYFromView(0f));
mAnimPercent = 0;
path.close();
canvas.drawPath(path, mPaint);
}
break;
case SHAPE_RECT:
if (mIsLoading) {
mAnimPercent += 0.15;
if (mAnimPercent >= 1) {
mShape = Shape.SHAPE_TRIANGLE;
mIsLoading = false;
mAnimPercent = 1;
}
Path path = new Path();
path.moveTo(relativeXFromView(0.5f * mAnimPercent), 0);
path.lineTo(relativeYFromView(1 - 0.5f * mAnimPercent), 0);
float distanceX = (mControlX) * mAnimPercent;
float distanceY = (relativeYFromView(1f) - mControlY) * mAnimPercent;
path.lineTo(relativeXFromView(1f) - distanceX, relativeYFromView(1f) - distanceY);
path.lineTo(relativeXFromView(0f) + distanceX, relativeYFromView(1f) - distanceY);
path.close();
canvas.drawPath(path, mPaint);
invalidate();
} else {
mPaint.setColor(getResources().getColor(R.color.rect));
mControlX = relativeXFromView(0.5f - genhao3 / 4);
mControlY = relativeYFromView(0.75f);
Path path = new Path();
path.moveTo(relativeXFromView(0f), relativeYFromView(0f));
path.lineTo(relativeXFromView(1f), relativeYFromView(0f));
path.lineTo(relativeXFromView(1f), relativeYFromView(1f));
path.lineTo(relativeXFromView(0f), relativeYFromView(1f));
path.close();
mAnimPercent = 0;
canvas.drawPath(path, mPaint);
}
break;
}
}
private float relativeXFromView(float percent) {
return getWidth() * percent;
}
private float relativeYFromView(float percent) {
return getHeight() * percent;
}
public void changeShape() {
mIsLoading = true;
invalidate();
public enum Shape {
SHAPE_TRIANGLE, SHAPE_RECT, SHAPE_CIRCLE
}
@Override
public void setVisibility(int visibility) {
super.setVisibility(visibility);
if(visibility==VISIBLE){
invalidate();
}
}
public Shape getShape() {
return mShape;
}
}
这个开源项目我们学习的两个主要知识
1.使用path绘制图形
2.ObjectAnimation&AnimatorSet
看了这个项目是不是可以用这两个知识点做一个自己喜欢的动画?
just do it.