今回は、github 上のオープンソース プロジェクト ShapeLoadingView を使用して、ObjectAnimator と animatorSet を学習します。
コード構造ディレクトリ:
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; }}
このオープンソース プロジェクトで学んだ 2 つの主な知識
1. パスを使用してグラフィックスを描画する
2. ObjectAnimation と AnimatorSet
このプロジェクトを読んだ後、これら 2 つの知識ポイントを使用できますか?好きですか?
just do it.
著作権表示: この記事はブロガーによるオリジナルの記事であり、ブロガーの許可なく複製することはできません。