Android酷炫动画效果之进度加载动画

       前一段时间,跟一个项目,忙的焦头烂额,所以博客断更了。最近处于学习阶段,细细总结了上个项目的不足之处,最突出的一点就是UI做的很中规中矩,Android版本已经来到6.0了,在UI style上面已经可以与IOS界面相比较了,我们的界面怎么只能满足于用基本的控件和基本的动画类来渲染呢?老版本的补间动画(Tween Animation)和逐帧动画(Frame Animation or Drawable Animation )已经满足不了日益增长的用户体验了,3.0以后引进了属性动画,通过它,我们可以演绎出很多炫目的动态效果,不过也仅仅局限在API层,其实我们可以自己通过一定的算法、高等数学配合图形学,做出一些更加赏心悦目的动画效果。实际上,Android还可以使用SVG矢量图打造酷炫动效

  SVG 意为可缩放矢量图形(Scalable Vector Graphics),是使用 XML 来描述二维图形和绘图程序的语言;使用 SVG 的优势在于:

1.SVG 可被非常多的工具读取和修改(比如记事本),由于使用xml格式定义,所以可以直接被当作文本文件打开,看里面的数据;

2.SVG 与 JPEG 和 GIF 图像比起来,尺寸更小,且可压缩性更强,SVG 图就相当于保存了关键的数据点,比如要显示一个圆,需要知道圆心和半径,那么SVG 就只保存圆心坐标和半径数据,而平常我们用的位图都是以像素点的形式根据图片大小保存对应个数的像素点,因而SVG尺寸更小;

3.SVG 是可伸缩的,平常使用的位图拉伸会发虚,压缩会变形,而SVG格式图片保存数据进行运算展示,不管多大多少,可以不失真显示;

4.SVG 图像可在任何的分辨率下被高质量地打印;

5.SVG 可在图像质量不下降的情况下被放大;

6.SVG 图像中的文本是可选的,同时也是可搜索的(很适合制作地图);

7.SVG 可以与 Java 技术一起运行;

8.SVG 是开放的标准;

9.SVG 文件是纯粹的 XML;

  既然SVG是公认的xml文件格式定义的,那么我们则可以通过解析xml文件拿到对应SVG图的所有数据;拿到数据之后,我们需要用"path"元素产生SVG一个路径,5.0以前,api并未提供矢量图资源使用支持,我们需要自定义一个工具类,对数据进行解析,封装成我们要的Path

package com.eftimoff.androipathview;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.DrawFilter;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;

import com.caverock.androidsvg.PreserveAspectRatio;
import com.caverock.androidsvg.SVG;
import com.caverock.androidsvg.SVGParseException;

import java.util.ArrayList;
import java.util.List;

/**
 * Util class to init and get paths from svg.
 */
public class SvgUtils {
    /**
     * It is for logging purposes.
     */
    private static final String LOG_TAG = "SVGUtils";
    /**
     * All the paths with their attributes from the svg.
     */
    private final List<SvgPath> mPaths = new ArrayList<>();
    /**
     * The paint provided from the view.
     */
    private final Paint mSourcePaint;
    /**
     * The init svg.
     */
    private SVG mSvg;

    /**
     * Init the SVGUtils with a paint for coloring.
     *
     * @param sourcePaint - the paint for the coloring.
     */
    public SvgUtils(final Paint sourcePaint) {
        mSourcePaint = sourcePaint;
    }

    /**
     * Loading the svg from the resources.
     *
     * @param context     Context object to get the resources.
     * @param svgResource int resource id of the svg.
     */
    public void load(Context context, int svgResource) {
        if (mSvg != null) 
            return;
        try {
            mSvg = SVG.getFromResource(context, svgResource);
            mSvg.setDocumentPreserveAspectRatio(PreserveAspectRatio.UNSCALED);
        } catch (SVGParseException e) {
            Log.e(LOG_TAG, "Could not load specified SVG resource", e);
        }
    }

    /**
     * Draw the svg to the canvas.
     *
     * @param canvas The canvas to be drawn.
     * @param width  The width of the canvas.
     * @param height The height of the canvas.
     */
    public void drawSvgAfter(final Canvas canvas, final int width, final int height) {
        final float strokeWidth = mSourcePaint.getStrokeWidth();
        rescaleCanvas(width, height, strokeWidth, canvas);
    }

    /**
     * Render the svg to canvas and catch all the paths while rendering.
     *
     * @param width  - the width to scale down the view to,
     * @param height - the height to scale down the view to,
     * @return All the paths from the svg.
     */
    public List<SvgPath> getPathsForViewport(final int width, final int height) {
        final float strokeWidth = mSourcePaint.getStrokeWidth();
        Canvas canvas = new Canvas() {
            private final Matrix mMatrix = new Matrix();

            @Override
            public int getWidth() {
                return width;
            }

            @Override
            public int getHeight() {
                return height;
            }

            @Override
            public void drawPath(Path path, Paint paint) {
                Path dst = new Path();

                //noinspection deprecation
                getMatrix(mMatrix);
                path.transform(mMatrix, dst);
                paint.setAntiAlias(true);
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(strokeWidth);
                mPaths.add(new SvgPath(dst, paint));
            }
        };

        rescaleCanvas(width, height, strokeWidth, canvas);

        return mPaths;
    }

    /**
     * Rescale the canvas with specific width and height.
     *
     * @param width       The width of the canvas.
     * @param height      The height of the canvas.
     * @param strokeWidth Width of the path to add to scaling.
     * @param canvas      The canvas to be drawn.
     */
    private void rescaleCanvas(int width, int height, float strokeWidth, Canvas canvas) {
        if (mSvg == null) 
            return;
        final RectF viewBox = mSvg.getDocumentViewBox();

        final float scale = Math.min(width
                        / (viewBox.width() + strokeWidth),
                height / (viewBox.height() + strokeWidth));

        canvas.translate((width - viewBox.width() * scale) / 2.0f,
                (height - viewBox.height() * scale) / 2.0f);
        canvas.scale(scale, scale);

        mSvg.renderToCanvas(canvas);
    }

    /**
     * Path with bounds for scalling , length and paint.
     */
    public static class SvgPath {

        /**
         * Region of the path.
         */
        private static final Region REGION = new Region();
        /**
         * This is done for clipping the bounds of the path.
         */
        private static final Region MAX_CLIP =
                new Region(Integer.MIN_VALUE, Integer.MIN_VALUE,
                        Integer.MAX_VALUE, Integer.MAX_VALUE);
        /**
         * The path itself.
         */
        final Path path;
        /**
         * The paint to be drawn later.
         */
        final Paint paint;
        /**
         * The length of the path.
         */
        float length;
        /**
         * Listener to notify that an animation step has happened.
         */
        AnimationStepListener animationStepListener;
        /**
         * The bounds of the path.
         */
        final Rect bounds;
        /**
         * The measure of the path, we can use it later to get segment of it.
         */
        final PathMeasure measure;

        /**
         * Constructor to add the path and the paint.
         *
         * @param path  The path that comes from the rendered svg.
         * @param paint The result paint.
         */
        SvgPath(Path path, Paint paint) {
            this.path = path;
            this.paint = paint;

            measure = new PathMeasure(path, false);
            this.length = measure.getLength();

            REGION.setPath(path, MAX_CLIP);
            bounds = REGION.getBounds();
        }

        /**
         * Sets the animation step listener.
         *
         * @param animationStepListener AnimationStepListener.
         */
        public void setAnimationStepListener(AnimationStepListener animationStepListener) {
            this.animationStepListener = animationStepListener;
        }

        /**
         * Sets the length of the path.
         *
         * @param length The length to be set.
         */
        public void setLength(float length) {
            path.reset();
            measure.getSegment(0.0f, length, path, true);
            path.rLineTo(0.0f, 0.0f);

            if (animationStepListener != null) {
                animationStepListener.onAnimationStep();
            }
        }

        /**
         * @return The length of the path.
         */
        public float getLength() {
            return length;
        }
    }

    public interface AnimationStepListener {

        /**
         * Called when an animation step happens.
         */
        void onAnimationStep();
    }
}


    有了图形对应的path,我们只需要按照我们想要的效果进行绘制即可,我们可以以此自定义一个动画控件,以后可以作为一个类工程或者打包成jar,被其他项目引用:

package com.eftimoff.androipathview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.Interpolator;

import com.eftimoff.mylibrary.R;
import com.nineoldandroids.animation.Animator;
import com.nineoldandroids.animation.ObjectAnimator;
import com.nineoldandroids.animation.AnimatorSet;

import java.util.ArrayList;
import java.util.List;

/**
 * PathView is a View that animates paths.
 */
@SuppressWarnings("unused")
public class PathView extends View implements SvgUtils.AnimationStepListener {
    /**
     * Logging tag.
     */
    public static final String LOG_TAG = "PathView";
    /**
     * The paint for the path.
     */
    private Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * Utils to catch the paths from the svg.
     */
    private final SvgUtils svgUtils = new SvgUtils(paint);
    /**
     * All the paths provided to the view. Both from Path and Svg.
     */
    private List<SvgUtils.SvgPath> paths = new ArrayList<>();
    /**
     * This is a lock before the view is redrawn
     * or resided it must be synchronized with this object.
     */
    private final Object mSvgLock = new Object();
    /**
     * Thread for working with the object above.
     */
    private Thread mLoader;

    /**
     * The svg image from the raw directory.
     */
    private int svgResourceId;
    /**
     * Object that builds the animation for the path.
     */
    private AnimatorBuilder animatorBuilder;
    /**
     * Object that builds the animation set for the path.
     */
    private AnimatorSetBuilder animatorSetBuilder;
    /**
     * The progress of the drawing.
     */
    private float progress = 0f;

    /**
     * If the used colors are from the svg or from the set color.
     */
    private boolean naturalColors;
    /**
     * If the view is filled with its natural colors after path drawing.
     */
    private boolean fillAfter;
    /**
     * The view will be filled and showed as default without any animation.
     */
    private boolean fill;
    /**
     * The solid color used for filling svg when fill is true
     */
    private int fillColor;
    /**
     * The width of the view.
     */
    private int width;
    /**
     * The height of the view.
     */
    private int height;
    /**
     * Will be used as a temporary surface in each onDraw call for more control over content are
     * drawing.
     */
    private Bitmap mTempBitmap;
    /**
     * Will be used as a temporary Canvas for mTempBitmap for drawing content on it.
     */
    private Canvas mTempCanvas;


    /**
     * Default constructor.
     *
     * @param context The Context of the application.
     */
    public PathView(Context context) {
        this(context, null);
    }

    /**
     * Default constructor.
     *
     * @param context The Context of the application.
     * @param attrs   attributes provided from the resources.
     */
    public PathView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    /**
     * Default constructor.
     *
     * @param context  The Context of the application.
     * @param attrs    attributes provided from the resources.
     * @param defStyle Default style.
     */
    public PathView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        paint.setStyle(Paint.Style.STROKE);
        getFromAttributes(context, attrs);
    }

    /**
     * Get all the fields from the attributes .
     *
     * @param context The Context of the application.
     * @param attrs   attributes provided from the resources.
     */
    private void getFromAttributes(Context context, AttributeSet attrs) {
        final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.PathView);
        try {
            if (a != null) {
                paint.setColor(a.getColor(R.styleable.PathView_pathColor, 0xff00ff00));
                paint.setStrokeWidth(a.getDimensionPixelSize(R.styleable.PathView_pathWidth, 8));
                svgResourceId = a.getResourceId(R.styleable.PathView_svg, 0);
                naturalColors = a.getBoolean(R.styleable.PathView_naturalColors, false);
                fill = a.getBoolean(R.styleable.PathView_fill,false);
                fillColor = a.getColor(R.styleable.PathView_fillColor,Color.argb(0,0,0,0));
            }
        } finally {
            if (a != null) {
                a.recycle();
            }
            //to draw the svg in first show , if we set fill to true
            invalidate();
        }
    }

    /**
     * Set paths to be drawn and animated.
     *
     * @param paths - Paths that can be drawn.
     */
    public void setPaths(final List<Path> paths) {
        for (Path path : paths) {
            this.paths.add(new SvgUtils.SvgPath(path, paint));
        }
        synchronized (mSvgLock) {
            updatePathsPhaseLocked();
        }
    }

    /**
     * Set path to be drawn and animated.
     *
     * @param path - Paths that can be drawn.
     */
    public void setPath(final Path path) {
        paths.add(new SvgUtils.SvgPath(path, paint));
        synchronized (mSvgLock) {
            updatePathsPhaseLocked();
        }
    }

    /**
     * Animate this property. It is the percentage of the path that is drawn.
     * It must be [0,1].
     *
     * @param percentage float the percentage of the path.
     */
    public void setPercentage(float percentage) {
        if (percentage < 0.0f || percentage > 1.0f) {
            throw new IllegalArgumentException("setPercentage not between 0.0f and 1.0f");
        }
        progress = percentage;
        synchronized (mSvgLock) {
            updatePathsPhaseLocked();
        }
        invalidate();
    }

    /**
     * This refreshes the paths before draw and resize.
     */
    private void updatePathsPhaseLocked() {
        final int count = paths.size();
        for (int i = 0; i < count; i++) {
            SvgUtils.SvgPath svgPath = paths.get(i);
            svgPath.path.reset();
            svgPath.measure.getSegment(0.0f, svgPath.length * progress, svgPath.path, true);
            // Required only for Android 4.4 and earlier
            svgPath.path.rLineTo(0.0f, 0.0f);
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        if(mTempBitmap==null || (mTempBitmap.getWidth()!=canvas.getWidth()||mTempBitmap.getHeight()!=canvas.getHeight()) )
        {
            mTempBitmap = Bitmap.createBitmap(canvas.getWidth(), canvas.getHeight(), Bitmap.Config.ARGB_8888);
            mTempCanvas = new Canvas(mTempBitmap);
        }

        mTempBitmap.eraseColor(0);
        synchronized (mSvgLock) {
            mTempCanvas.save();
            mTempCanvas.translate(getPaddingLeft(), getPaddingTop());
            fill(mTempCanvas);
            final int count = paths.size();
            for (int i = 0; i < count; i++) {
                final SvgUtils.SvgPath svgPath = paths.get(i);
                final Path path = svgPath.path;
                final Paint paint1 = naturalColors ? svgPath.paint : paint;
                mTempCanvas.drawPath(path, paint1);
            }

            fillAfter(mTempCanvas);

            mTempCanvas.restore();

            applySolidColor(mTempBitmap);

            canvas.drawBitmap(mTempBitmap,0,0,null);
        }
    }
    /**
     * If there is svg , the user called setFillAfter(true) and the progress is finished.
     *
     * @param canvas Draw to this canvas.
     */
    private void fillAfter(final Canvas canvas) {
        if (svgResourceId != 0 && fillAfter && Math.abs(progress - 1f) < 0.00000001) {
            svgUtils.drawSvgAfter(canvas, width, height);
        }
    }

    /**
     * If there is svg , the user called setFill(true).
     *
     * @param canvas Draw to this canvas.
     */
    private void fill(final Canvas canvas) {
        if (svgResourceId != 0 && fill) {
            svgUtils.drawSvgAfter(canvas, width, height);
        }
    }

    /**
     * If fillColor had value before then we replace untransparent pixels of bitmap by solid color
     *
     * @param bitmap Draw to this canvas.
     */
    private void applySolidColor(final Bitmap bitmap) {
        if(fill && fillColor!=Color.argb(0,0,0,0) )
            if (bitmap != null) {
                for(int x=0;x<bitmap.getWidth();x++)
                {
                    for(int y=0;y<bitmap.getHeight();y++)
                    {
                        int argb = bitmap.getPixel(x,y);
                        int alpha = Color.alpha(argb);
                        if(alpha!=0)
                        {
                            int red = Color.red(fillColor);
                            int green = Color.green(fillColor);
                            int blue =  Color.blue(fillColor);
                            argb = Color.argb(alpha,red,green,blue);
                            bitmap.setPixel(x,y,argb);
                        }
                    }
                }
            }
    }

    @Override
    protected void onSizeChanged(final int w, final int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);

        if (mLoader != null) {
            try {
                mLoader.join();
            } catch (InterruptedException e) {
                Log.e(LOG_TAG, "Unexpected error", e);
            }
        }
        if (svgResourceId != 0) {
            mLoader = new Thread(new Runnable() {
                @Override
                public void run() {

                    svgUtils.load(getContext(), svgResourceId);

                    synchronized (mSvgLock) {
                        width = w - getPaddingLeft() - getPaddingRight();
                        height = h - getPaddingTop() - getPaddingBottom();
                        paths = svgUtils.getPathsForViewport(width, height);
                        updatePathsPhaseLocked();
                    }
                }
            }, "SVG Loader");
            mLoader.start();
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        if (svgResourceId != 0) {
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            setMeasuredDimension(widthSize, heightSize);
            return;
        }

        int desiredWidth = 0;
        int desiredHeight = 0;
        final float strokeWidth = paint.getStrokeWidth() / 2;
        for (SvgUtils.SvgPath path : paths) {
            desiredWidth += path.bounds.left + path.bounds.width() + strokeWidth;
            desiredHeight += path.bounds.top + path.bounds.height() + strokeWidth;
        }
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(widthMeasureSpec);

        int measuredWidth, measuredHeight;

        if (widthMode == MeasureSpec.AT_MOST) {
            measuredWidth = desiredWidth;
        } else {
            measuredWidth = widthSize;
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            measuredHeight = desiredHeight;
        } else {
            measuredHeight = heightSize;
        }

        setMeasuredDimension(measuredWidth, measuredHeight);
    }

    /**
     * If the real svg need to be drawn after the path animation.
     *
     * @param fillAfter - boolean if the view needs to be filled after path animation.
     */
    public void setFillAfter(final boolean fillAfter) {
        this.fillAfter = fillAfter;
    }
    /**
     * If the real svg need to be drawn without the path animation.
     *
     * @param fill - boolean if the view needs to be filled after path animation.
     */
    public void setFill(final boolean fill) {
        this.fill = fill;
    }
    /**
     * The color for drawing svg in that color if the color be not transparent
     *
     * @param color - the color for filling in that
     */
    public void setFillColor(final int color){
        this.fillColor=color;
    }
    /**
     * If you want to use the colors from the svg.
     */
    public void useNaturalColors() {
        naturalColors = true;
    }

    /**
     * Animator for the paths of the view.
     *
     * @return The AnimatorBuilder to build the animation.
     */
    public AnimatorBuilder getPathAnimator() {
        if (animatorBuilder == null) {
            animatorBuilder = new AnimatorBuilder(this);
        }
        return animatorBuilder;
    }

    /**
     * AnimatorSet for the paths of the view to be animated one after the other.
     *
     * @return The AnimatorBuilder to build the animation.
     */
    public AnimatorSetBuilder getSequentialPathAnimator() {
        if (animatorSetBuilder == null) {
            animatorSetBuilder = new AnimatorSetBuilder(this);
        }
        return animatorSetBuilder;
    }

    /**
     * Get the path color.
     *
     * @return The color of the paint.
     */
    public int getPathColor() {
        return paint.getColor();
    }

    /**
     * Set the path color.
     *
     * @param color -The color to set to the paint.
     */
    public void setPathColor(final int color) {
        paint.setColor(color);
    }

    /**
     * Get the path width.
     *
     * @return The width of the paint.
     */
    public float getPathWidth() {
        return paint.getStrokeWidth();
    }

    /**
     * Set the path width.
     *
     * @param width - The width of the path.
     */
    public void setPathWidth(final float width) {
        paint.setStrokeWidth(width);
    }

    /**
     * Get the svg resource id.
     *
     * @return The svg raw resource id.
     */
    public int getSvgResource() {
        return svgResourceId;
    }

    /**
     * Set the svg resource id.
     *
     * @param svgResource - The resource id of the raw svg.
     */
    public void setSvgResource(int svgResource) {
        svgResourceId = svgResource;
    }

    /**
     * Object for building the animation of the path of this view.
     */
    public static class AnimatorBuilder {
        /**
         * Duration of the animation.
         */
        private int duration = 350;
        /**
         * Interpolator for the time of the animation.
         */
        private Interpolator interpolator;
        /**
         * The delay before the animation.
         */
        private int delay = 0;
        /**
         * ObjectAnimator that constructs the animation.
         */
        private final ObjectAnimator anim;
        /**
         * Listener called before the animation.
         */
        private ListenerStart listenerStart;
        /**
         * Listener after the animation.
         */
        private ListenerEnd animationEnd;
        /**
         * Animation listener.
         */
        private PathViewAnimatorListener pathViewAnimatorListener;

        /**
         * Default constructor.
         *
         * @param pathView The view that must be animated.
         */
        public AnimatorBuilder(final PathView pathView) {
            anim = ObjectAnimator.ofFloat(pathView, "percentage", 0.0f, 1.0f);
        }

        /**
         * Set the duration of the animation.
         *
         * @param duration - The duration of the animation.
         * @return AnimatorBuilder.
         */
        public AnimatorBuilder duration(final int duration) {
            this.duration = duration;
            return this;
        }

        /**
         * Set the Interpolator.
         *
         * @param interpolator - Interpolator.
         * @return AnimatorBuilder.
         */
        public AnimatorBuilder interpolator(final Interpolator interpolator) {
            this.interpolator = interpolator;
            return this;
        }

        /**
         * The delay before the animation.
         *
         * @param delay - int the delay
         * @return AnimatorBuilder.
         */
        public AnimatorBuilder delay(final int delay) {
            this.delay = delay;
            return this;
        }

        /**
         * Set a listener before the start of the animation.
         *
         * @param listenerStart an interface called before the animation
         * @return AnimatorBuilder.
         */
        public AnimatorBuilder listenerStart(final ListenerStart listenerStart) {
            this.listenerStart = listenerStart;
            if (pathViewAnimatorListener == null) {
                pathViewAnimatorListener = new PathViewAnimatorListener();
                anim.addListener(pathViewAnimatorListener);
            }
            return this;
        }

        /**
         * Set a listener after of the animation.
         *
         * @param animationEnd an interface called after the animation
         * @return AnimatorBuilder.
         */
        public AnimatorBuilder listenerEnd(final ListenerEnd animationEnd) {
            this.animationEnd = animationEnd;
            if (pathViewAnimatorListener == null) {
                pathViewAnimatorListener = new PathViewAnimatorListener();
                anim.addListener(pathViewAnimatorListener);
            }
            return this;
        }

        /**
         * Starts the animation.
         */
        public void start() {
            anim.setDuration(duration);
            anim.setInterpolator(interpolator);
            anim.setStartDelay(delay);
            anim.start();
        }

        /**
         * Animation listener to be able to provide callbacks for the caller.
         */
        private class PathViewAnimatorListener implements Animator.AnimatorListener {

            @Override
            public void onAnimationStart(Animator animation) {
                if (listenerStart != null) 
                    listenerStart.onAnimationStart();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (animationEnd != null) 
                    animationEnd.onAnimationEnd();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        }

        /**
         * Called when the animation start.
         */
        public interface ListenerStart {
            /**
             * Called when the path animation start.
             */
            void onAnimationStart();
        }

        /**
         * Called when the animation end.
         */
        public interface ListenerEnd {
            /**
             * Called when the path animation end.
             */
            void onAnimationEnd();
        }
    }

    @Override
    public void onAnimationStep() {
        invalidate();
    }

    /**
     * Object for building the sequential animation of the paths of this view.
     */
    public static class AnimatorSetBuilder {
        /**
         * Duration of the animation.
         */
        private int duration = 1000;
        /**
         * Interpolator for the time of the animation.
         */
        private Interpolator interpolator;
        /**
         * The delay before the animation.
         */
        private int delay = 0;
        /**
         * List of ObjectAnimator that constructs the animations of each path.
         */
        private final List<Animator> animators = new ArrayList<>();
        /**
         * Listener called before the animation.
         */
        private AnimatorBuilder.ListenerStart listenerStart;
        /**
         * Listener after the animation.
         */
        private AnimatorBuilder.ListenerEnd animationEnd;
        /**
         * Animation listener.
         */
        private AnimatorSetBuilder.PathViewAnimatorListener pathViewAnimatorListener;
        /**
         * The animator that can animate paths sequentially
         */
        private AnimatorSet animatorSet = new AnimatorSet();
        /**
         * The list of paths to be animated.
         */
        private List<SvgUtils.SvgPath> paths;

        /**
         * Default constructor.
         *
         * @param pathView The view that must be animated.
         */
        public AnimatorSetBuilder(final PathView pathView) {
            paths = pathView.paths;
            for (SvgUtils.SvgPath path : paths) {
                path.setAnimationStepListener(pathView);
                ObjectAnimator animation = ObjectAnimator.ofFloat(path, "length", 0.0f, path.getLength());
                animators.add(animation);
            }
            animatorSet.playSequentially(animators);
        }

        /**
         * Sets the duration of the animation. Since the AnimatorSet sets the duration for each
         * Animator, we have to divide it by the number of paths.
         *
         * @param duration - The duration of the animation.
         * @return AnimatorSetBuilder.
         */
        public AnimatorSetBuilder duration(final int duration) {
            this.duration = duration / paths.size();
            return this;
        }

        /**
         * Set the Interpolator.
         *
         * @param interpolator - Interpolator.
         * @return AnimatorSetBuilder.
         */
        public AnimatorSetBuilder interpolator(final Interpolator interpolator) {
            this.interpolator = interpolator;
            return this;
        }

        /**
         * The delay before the animation.
         *
         * @param delay - int the delay
         * @return AnimatorSetBuilder.
         */
        public AnimatorSetBuilder delay(final int delay) {
            this.delay = delay;
            return this;
        }

        /**
         * Set a listener before the start of the animation.
         *
         * @param listenerStart an interface called before the animation
         * @return AnimatorSetBuilder.
         */
        public AnimatorSetBuilder listenerStart(final AnimatorBuilder.ListenerStart listenerStart) {
            this.listenerStart = listenerStart;
            if (pathViewAnimatorListener == null) {
                pathViewAnimatorListener = new PathViewAnimatorListener();
                animatorSet.addListener(pathViewAnimatorListener);
            }
            return this;
        }

        /**
         * Set a listener after of the animation.
         *
         * @param animationEnd an interface called after the animation
         * @return AnimatorSetBuilder.
         */
        public AnimatorSetBuilder listenerEnd(final AnimatorBuilder.ListenerEnd animationEnd) {
            this.animationEnd = animationEnd;
            if (pathViewAnimatorListener == null) {
                pathViewAnimatorListener = new PathViewAnimatorListener();
                animatorSet.addListener(pathViewAnimatorListener);
            }
            return this;
        }

        /**
         * Starts the animation.
         */
        public void start() {
            resetAllPaths();
            animatorSet.cancel();
            animatorSet.setDuration(duration);
            animatorSet.setInterpolator(interpolator);
            animatorSet.setStartDelay(delay);
            animatorSet.start();
        }

        /**
         * Sets the length of all the paths to 0.
         */
        private void resetAllPaths() {
            for (SvgUtils.SvgPath path : paths) {
                path.setLength(0);
            }
        }

        /**
         * Animation listener to be able to provide callbacks for the caller.
         */
        private class PathViewAnimatorListener implements Animator.AnimatorListener {

            @Override
            public void onAnimationStart(Animator animation) {
                if (listenerStart != null) 
                    listenerStart.onAnimationStart();
            }

            @Override
            public void onAnimationEnd(Animator animation) {
                if (animationEnd != null) 
                    animationEnd.onAnimationEnd();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        }
    }
}


   当然5.0以后,API已经封装了SVG数据解析获取path过程,开始提供了新的API VectorDrawable,直接可以使用SVG类型的资源。似乎有点跑题了,下面正式进入本篇要呈现给读者的东西,关于Android进度加载动画。

参考http://blog.csdn.net/tianjian4592/article/details/44538605http://blog.csdn.net/xu_fu/article/details/44725921


1、圆形进度条

       
       1.1 使用animated-rotate

在drawable中创建一个RotateDrawable,

<?xml version="1.0" encoding="utf-8"?>
<animated-rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/loading"
    android:duration="150"
    android:pivotX="50%"
    android:pivotY="50%" />

然后在xml定义ProgressBar,引用此drawable就可以了

<ProgressBar
        android:id="@+id/seekBar"
        style="?android:attr/progressBarStyleLarge"
        android:layout_width="258dip"
        android:layout_height="258dip"
        android:layout_centerInParent="true"
        android:gravity="center"
        android:indeterminate="false"
        android:indeterminateDrawable="@drawable/start_progressbar_style"
        android:max="10000"
        android:visibility="gone" />

  1.2 自定义控件实现:

CircleProgress.java

package com.john.circleprogress;

import android.animation.TimeInterpolator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.view.animation.AnimationUtils;

public class CircleProgress extends View
{

	private static final int RED = 0xFFE5282C;
	private static final int YELLOW = 0xFF1F909A;
	private static final int BLUE = 0xFFFC9E12;
	private static final int COLOR_NUM = 3;
	private int[] COLORS;
	private TimeInterpolator mInterpolator = new EaseInOutCubicInterpolator();

	private final double DEGREE = Math.PI / 180;
	private Paint mPaint;
	private int mViewSize;
	private int mPointRadius;
	private long mStartTime;
	private long mPlayTime;
	private boolean mStartAnim = false;
	private Point mCenter = new Point();

	private ArcPoint[] mArcPoint;
	private static final int POINT_NUM = 15;
	private static final int DELTA_ANGLE = 360 / POINT_NUM;
	private long mDuration = 3600;

	public CircleProgress(Context context)
	{
		super(context);
		init(null, 0);
	}

	public CircleProgress(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		init(attrs, 0);
	}

	public CircleProgress(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		init(attrs, defStyle);
	}

	private void init(AttributeSet attrs, int defStyle)
	{
		mArcPoint = new ArcPoint[POINT_NUM];

		mPaint = new Paint();
		mPaint.setAntiAlias(true);
		mPaint.setStyle(Paint.Style.FILL);

		TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.CircleProgress, defStyle, 0);
		int color1 = a.getColor(R.styleable.CircleProgress_color1, RED);
		int color2 = a.getColor(R.styleable.CircleProgress_color2, YELLOW);
		int color3 = a.getColor(R.styleable.CircleProgress_color3, BLUE);
		a.recycle();

		COLORS = new int[] { color1, color2, color3 };
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		int defaultSize = getResources().getDimensionPixelSize(R.dimen.default_circle_view_size);
		int width = getDefaultSize(defaultSize, widthMeasureSpec);
		int height = getDefaultSize(defaultSize, heightMeasureSpec);
		mViewSize = Math.min(width, height);
		setMeasuredDimension(mViewSize, mViewSize);
		mCenter.set(mViewSize / 2, mViewSize / 2);

		calPoints(1.0f);
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		canvas.save();
		canvas.translate(mCenter.x, mCenter.y);

		float factor = getFactor();
		canvas.rotate(36 * factor);
		float x, y;
		for (int i = 0; i < POINT_NUM; ++i)
		{
			mPaint.setColor(mArcPoint[i].color);
			float itemFactor = getItemFactor(i, factor);
			x = mArcPoint[i].x - 2 * mArcPoint[i].x * itemFactor;
			y = mArcPoint[i].y - 2 * mArcPoint[i].y * itemFactor;
			canvas.drawCircle(x, y, mPointRadius, mPaint);
		}

		canvas.restore();

		if (mStartAnim)
		{
			postInvalidate();
		}
	}

	private void calPoints(float factor)
	{
		int radius = (int) (mViewSize / 3 * factor);
		mPointRadius = radius / 12;

		for (int i = 0; i < POINT_NUM; ++i)
		{
			float x = radius * -(float) Math.sin(DEGREE * DELTA_ANGLE * i);
			float y = radius * -(float) Math.cos(DEGREE * DELTA_ANGLE * i);

			ArcPoint point = new ArcPoint(x, y, COLORS[i % COLOR_NUM]);
			mArcPoint[i] = point;
		}
	}

	private float getFactor()
	{
		if (mStartAnim)
		{
			mPlayTime = AnimationUtils.currentAnimationTimeMillis() - mStartTime;
		}
		float factor = mPlayTime / (float) mDuration;
		return factor % 1f;
	}

	private float getItemFactor(int index, float factor)
	{
		float itemFactor = (factor - 0.66f / POINT_NUM * index) * 3;
		if (itemFactor < 0f)
		{
			itemFactor = 0f;
		}
		else if (itemFactor > 1f)
		{
			itemFactor = 1f;
		}
		return mInterpolator.getInterpolation(itemFactor);
	}

	public void startAnim()
	{
		mPlayTime = mPlayTime % mDuration;
		mStartTime = AnimationUtils.currentAnimationTimeMillis() - mPlayTime;
		mStartAnim = true;
		postInvalidate();
	}

	public void reset()
	{
		stopAnim();
		mPlayTime = 0;
		postInvalidate();

	}

	public void stopAnim()
	{
		mStartAnim = false;
	}

	public void setInterpolator(TimeInterpolator interpolator)
	{
		mInterpolator = interpolator;
	}

	public void setDuration(long duration)
	{
		mDuration = duration;
	}

	public void setRadius(float factor)
	{
		stopAnim();
		calPoints(factor);
		startAnim();
	}

	static class ArcPoint
	{
		float x;
		float y;
		int color;

		ArcPoint(float x, float y, int color)
		{
			this.x = x;
			this.y = y;
			this.color = color;
		}
	}

}

动画插值器实现类EaseInOutCubicInterpolator.java

package com.john.circleprogress;

import android.animation.TimeInterpolator;

/**
 * The MIT License (MIT)
 * 
 * Copyright (c) 2015 fichardu
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
public class EaseInOutCubicInterpolator implements TimeInterpolator
{

	@Override
	public float getInterpolation(float input)
	{
		if ((input *= 2) < 1.0f)
		{
			return 0.5f * input * input * input;
		}
		input -= 2;
		return 0.5f * input * input * input + 1;
	}

}

主类测试两种圆形进度条效果:MainActivity.java

package com.john.circleprogress;

import android.app.Activity;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.widget.LinearLayout;
import android.widget.ProgressBar;

/**
 * test
 * 
 * @author john
 * @created 2016-6-30
 */
public class MainActivity extends Activity implements View.OnClickListener
{

	private CircleProgress mProgressView;
	private ProgressBar progressBar;
	private View mBtn0;
	private View mBtn1;
	private LinearLayout mLinearLayout;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.activity_main);

		mProgressView = (CircleProgress) findViewById(R.id.progress);

		progressBar = (ProgressBar) findViewById(R.id.seekBar);

		mLinearLayout = (LinearLayout) findViewById(R.id.btn_layout);

		mBtn0 = findViewById(R.id.circle_progress);
		mBtn0.setOnClickListener(this);
		mBtn1 = findViewById(R.id.rotate_progress);
		mBtn1.setOnClickListener(this);

	}

	@Override
	public void onClick(View v)
	{
		// TODO Auto-generated method stub
		mLinearLayout.setVisibility(View.GONE);
		if (v == mBtn0)
		{
			mProgressView.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			mProgressView.startAnim();
		}
		else if (v == mBtn1)
		{
			mProgressView.setVisibility(View.GONE);
			progressBar.setVisibility(View.VISIBLE);
		}
	}

	@Override
	public boolean onKeyDown(int keyCode, KeyEvent event)
	{
		// TODO Auto-generated method stub
		if (event.getAction() == KeyEvent.ACTION_DOWN)
		{
			switch (keyCode)
			{
			case KeyEvent.KEYCODE_BACK:
				if (mProgressView.isShown() || progressBar.isShown())
				{
					mLinearLayout.setVisibility(View.VISIBLE);
					progressBar.setVisibility(View.GONE);
					mProgressView.setVisibility(View.GONE);
					return true;
				}
				break;

			default:
				break;
			}
		}

		return super.onKeyDown(keyCode, event);
	}
}

圆形进度条测试效果:

http://v.youku.com/v_show/id_XMTYzMjY4NTkwNA==.html

2、一个柔和饱满的Loading UI

http://v.youku.com/v_show/id_XMTYzMjY5MjA0OA==.html

实现这样一个效果主要有三点:
1、叶子随机产生并作无规律移动旋转;
2、随着进度往前绘制的进度条;
3、叶子与进度条有一个融合交互过程

代码结构:

一个动画工具类AnimationUtils.java,这里主要是用来实现风扇的旋转效果。

package com.john.loading.animation;

import android.content.Context;
import android.graphics.drawable.AnimationDrawable;
import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;

public class AnimationUtils
{

	public static RotateAnimation initRotateAnimation(long duration, int fromAngle, int toAngle, boolean isFillAfter, int repeatCount)
	{
		RotateAnimation mLoadingRotateAnimation = new RotateAnimation(fromAngle, toAngle, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
		LinearInterpolator lirInterpolator = new LinearInterpolator();
		mLoadingRotateAnimation.setInterpolator(lirInterpolator);
		mLoadingRotateAnimation.setDuration(duration);
		mLoadingRotateAnimation.setFillAfter(isFillAfter);
		mLoadingRotateAnimation.setRepeatCount(repeatCount);
		mLoadingRotateAnimation.setRepeatMode(Animation.RESTART);
		return mLoadingRotateAnimation;
	}

	public static RotateAnimation initRotateAnimation(boolean isClockWise, long duration, boolean isFillAfter, int repeatCount)
	{
		int endAngle;
		if (isClockWise)
		{
			endAngle = 360;
		}
		else
		{
			endAngle = -360;
		}
		RotateAnimation mLoadingRotateAnimation = new RotateAnimation(0, endAngle, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
		LinearInterpolator lirInterpolator = new LinearInterpolator();
		mLoadingRotateAnimation.setInterpolator(lirInterpolator);
		mLoadingRotateAnimation.setDuration(duration);
		mLoadingRotateAnimation.setFillAfter(isFillAfter);
		mLoadingRotateAnimation.setRepeatCount(repeatCount);
		mLoadingRotateAnimation.setRepeatMode(Animation.RESTART);
		return mLoadingRotateAnimation;
	}

	public static AnimationDrawable initAnimationDrawable(Context context, int[] drawableIds, int durationTime, boolean isOneShot)
	{
		AnimationDrawable mAnimationDrawable = new AnimationDrawable();
		for (int i = 0; i < drawableIds.length; i++)
		{
			int id = drawableIds[i];
			mAnimationDrawable.addFrame(context.getResources().getDrawable(id), durationTime);
		}
		mAnimationDrawable.setOneShot(isOneShot);
		return mAnimationDrawable;
	}

	public static Animation initAlphaAnimtion(Context context, float fromAlpha, float toAlpha, long duration)
	{
		Animation alphaAnimation = new AlphaAnimation(fromAlpha, toAlpha);
		alphaAnimation.setDuration(duration);
		return alphaAnimation;
	}

}


一个屏幕分辨率适配工具类UiUtils.java

package com.john.loading.animation;

import android.content.Context;
import android.util.DisplayMetrics;
import android.view.WindowManager;

public class UiUtils
{

	static public int getScreenWidthPixels(Context context)
	{
		DisplayMetrics dm = new DisplayMetrics();
		((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);
		return dm.widthPixels;
	}

	static public int dipToPx(Context context, int dip)
	{
		return (int) (dip * getScreenDensity(context) + 0.5f);
	}

	static public float getScreenDensity(Context context)
	{
		try
		{
			DisplayMetrics dm = new DisplayMetrics();
			((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(dm);
			return dm.density;
		}
		catch (Exception e)
		{
			return DisplayMetrics.DENSITY_DEFAULT;
		}
	}

}

绘制叶子和进度条,将其封装成一个自定义控件,LeafLoadingView.java

package com.john.loading.animation;

import java.util.LinkedList;
import java.util.List;
import java.util.Random;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class LeafLoadingView extends View
{

	private static final String TAG = "LeafLoadingView";
	// 淡白色
	private static final int WHITE_COLOR = 0xfffde399;
	// 橙色
	private static final int ORANGE_COLOR = 0xffffa800;
	// 中等振幅大小
	private static final int MIDDLE_AMPLITUDE = 13;
	// 不同类型之间的振幅差距
	private static final int AMPLITUDE_DISPARITY = 5;

	// 总进度
	private static final int TOTAL_PROGRESS = 100;
	// 叶子飘动一个周期所花的时间
	private static final long LEAF_FLOAT_TIME = 3000;
	// 叶子旋转一周需要的时间
	private static final long LEAF_ROTATE_TIME = 2000;

	// 用于控制绘制的进度条距离左/上/下的距离
	private static final int LEFT_MARGIN = 9;
	// 用于控制绘制的进度条距离右的距离
	private static final int RIGHT_MARGIN = 25;
	private int mLeftMargin, mRightMargin;
	// 中等振幅大小
	private int mMiddleAmplitude = MIDDLE_AMPLITUDE;
	// 振幅差
	private int mAmplitudeDisparity = AMPLITUDE_DISPARITY;

	// 叶子飘动一个周期所花的时间
	private long mLeafFloatTime = LEAF_FLOAT_TIME;
	// 叶子旋转一周需要的时间
	private long mLeafRotateTime = LEAF_ROTATE_TIME;
	private Resources mResources;
	private Bitmap mLeafBitmap;
	private int mLeafWidth, mLeafHeight;

	private Bitmap mOuterBitmap;
	private Rect mOuterSrcRect, mOuterDestRect;
	private int mOuterWidth, mOuterHeight;

	private int mTotalWidth, mTotalHeight;

	private Paint mBitmapPaint, mWhitePaint, mOrangePaint;
	private RectF mWhiteRectF, mOrangeRectF, mArcRectF;
	// 当前进度
	private int mProgress;
	//最大进度
	private int mMaxProgress=TOTAL_PROGRESS;
	// 所绘制的进度条部分的宽度
	private int mProgressWidth;
	// 当前所在的绘制的进度条的位置
	private int mCurrentProgressPosition;
	// 弧形的半径
	private int mArcRadius;

	// arc的右上角的x坐标,也是矩形x坐标的起始点
	private int mArcRightLocation;
	// 用于产生叶子信息
	private LeafFactory mLeafFactory;
	// 产生出的叶子信息
	private List<Leaf> mLeafInfos;
	// 用于控制随机增加的时间不抱团
	private int mAddTime;

	public LeafLoadingView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		mResources = getResources();
		mLeftMargin = UiUtils.dipToPx(context, LEFT_MARGIN);
		mRightMargin = UiUtils.dipToPx(context, RIGHT_MARGIN);

		mLeafFloatTime = LEAF_FLOAT_TIME;
		mLeafRotateTime = LEAF_ROTATE_TIME;

		initBitmap();
		initPaint();
		mLeafFactory = new LeafFactory();
		mLeafInfos = mLeafFactory.generateLeafs();

	}

	private void initPaint()
	{
		mBitmapPaint = new Paint();
		mBitmapPaint.setAntiAlias(true);
		mBitmapPaint.setDither(true);
		mBitmapPaint.setFilterBitmap(true);

		mWhitePaint = new Paint();
		mWhitePaint.setAntiAlias(true);
		mWhitePaint.setColor(WHITE_COLOR);

		mOrangePaint = new Paint();
		mOrangePaint.setAntiAlias(true);
		mOrangePaint.setColor(ORANGE_COLOR);
	}

	@Override
	protected void onDraw(Canvas canvas)
	{
		super.onDraw(canvas);

		// 绘制进度条和叶子
		// 之所以把叶子放在进度条里绘制,主要是层级原因
		drawProgressAndLeafs(canvas);

		canvas.drawBitmap(mOuterBitmap, mOuterSrcRect, mOuterDestRect, mBitmapPaint);

		postInvalidate();
	}

	private void drawProgressAndLeafs(Canvas canvas)
	{

		if (mProgress >= TOTAL_PROGRESS)
		{
			mProgress = 0;
		}
		// mProgressWidth为进度条的宽度,根据当前进度算出进度条的位置
		mCurrentProgressPosition = mProgressWidth * mProgress / TOTAL_PROGRESS;
		// 即当前位置在图中所示1范围内
		if (mCurrentProgressPosition < mArcRadius)
		{
			Log.i(TAG, "mProgress = " + mProgress + "---mCurrentProgressPosition = " + mCurrentProgressPosition + "--mArcProgressWidth" + mArcRadius);
			// 1.绘制白色ARC,绘制orange ARC
			// 2.绘制白色矩形

			// 1.绘制白色ARC
			canvas.drawArc(mArcRectF, 90, 180, false, mWhitePaint);

			// 2.绘制白色矩形
			mWhiteRectF.left = mArcRightLocation;
			canvas.drawRect(mWhiteRectF, mWhitePaint);

			// 绘制叶子
			drawLeafs(canvas);

			// 3.绘制棕色 ARC
			// 单边角度
			int angle = (int) Math.toDegrees(Math.acos((mArcRadius - mCurrentProgressPosition) / (float) mArcRadius));
			// 起始的位置
			int startAngle = 180 - angle;
			// 扫过的角度
			int sweepAngle = 2 * angle;
			Log.i(TAG, "startAngle = " + startAngle);
			canvas.drawArc(mArcRectF, startAngle, sweepAngle, false, mOrangePaint);
		}
		else
		{
			Log.i(TAG, "mProgress = " + mProgress + "---transfer-----mCurrentProgressPosition = " + mCurrentProgressPosition + "--mArcProgressWidth" + mArcRadius);
			// 1.绘制white RECT
			// 2.绘制Orange ARC
			// 3.绘制orange RECT
			// 这个层级进行绘制能让叶子感觉是融入棕色进度条中

			// 1.绘制white RECT
			mWhiteRectF.left = mCurrentProgressPosition;
			canvas.drawRect(mWhiteRectF, mWhitePaint);
			// 绘制叶子
			drawLeafs(canvas);
			// 2.绘制Orange ARC
			canvas.drawArc(mArcRectF, 90, 180, false, mOrangePaint);
			// 3.绘制orange RECT
			mOrangeRectF.left = mArcRightLocation;
			mOrangeRectF.right = mCurrentProgressPosition;
			canvas.drawRect(mOrangeRectF, mOrangePaint);

		}

	}

	/**
	 * 绘制叶子
	 * 
	 * @param canvas
	 */
	private void drawLeafs(Canvas canvas)
	{
		mLeafRotateTime = mLeafRotateTime <= 0 ? LEAF_ROTATE_TIME : mLeafRotateTime;
		long currentTime = System.currentTimeMillis();
		for (int i = 0; i < mLeafInfos.size(); i++)
		{
			Leaf leaf = mLeafInfos.get(i);
			if (currentTime > leaf.startTime && leaf.startTime != 0)
			{
				// 绘制叶子--根据叶子的类型和当前时间得出叶子的(x,y)
				getLeafLocation(leaf, currentTime);
				// 根据时间计算旋转角度
				canvas.save();
				// 通过Matrix控制叶子旋转
				Matrix matrix = new Matrix();
				float transX = mLeftMargin + leaf.x;
				float transY = mLeftMargin + leaf.y;
				Log.i(TAG, "left.x = " + leaf.x + "--leaf.y=" + leaf.y);
				matrix.postTranslate(transX, transY);
				// 通过时间关联旋转角度,则可以直接通过修改LEAF_ROTATE_TIME调节叶子旋转快慢
				float rotateFraction = ((currentTime - leaf.startTime) % mLeafRotateTime) / (float) mLeafRotateTime;
				int angle = (int) (rotateFraction * 360);
				// 根据叶子旋转方向确定叶子旋转角度
				int rotate = leaf.rotateDirection == 0 ? angle + leaf.rotateAngle : -angle + leaf.rotateAngle;
				matrix.postRotate(rotate, transX + mLeafWidth / 2, transY + mLeafHeight / 2);
				canvas.drawBitmap(mLeafBitmap, matrix, mBitmapPaint);
				canvas.restore();
			}
			else
			{
				continue;
			}
		}
	}

	private void getLeafLocation(Leaf leaf, long currentTime)
	{
		long intervalTime = currentTime - leaf.startTime;
		mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;
		if (intervalTime < 0)
		{
			return;
		}
		else if (intervalTime > mLeafFloatTime)
		{
			leaf.startTime = System.currentTimeMillis() + new Random().nextInt((int) mLeafFloatTime);
		}

		float fraction = (float) intervalTime / mLeafFloatTime;
		leaf.x = (int) (mProgressWidth - mProgressWidth * fraction);
		leaf.y = getLocationY(leaf);
	}

	// 通过叶子信息获取当前叶子的Y值
	private int getLocationY(Leaf leaf)
	{
		// y = A(wx+Q)+h
		float w = (float) ((float) 2 * Math.PI / mProgressWidth);
		float a = mMiddleAmplitude;
		switch (leaf.type)
		{
		case LITTLE:
			// 小振幅 = 中等振幅 - 振幅差
			a = mMiddleAmplitude - mAmplitudeDisparity;
			break;
		case MIDDLE:
			a = mMiddleAmplitude;
			break;
		case BIG:
			// 小振幅 = 中等振幅 + 振幅差
			a = mMiddleAmplitude + mAmplitudeDisparity;
			break;
		default:
			break;
		}
		Log.i(TAG, "---a = " + a + "---w = " + w + "--leaf.x = " + leaf.x);
		return (int) (a * Math.sin(w * leaf.x)) + mArcRadius * 2 / 3;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
	{
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	private void initBitmap()
	{
		mLeafBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.leaf)).getBitmap();
		mLeafWidth = mLeafBitmap.getWidth();
		mLeafHeight = mLeafBitmap.getHeight();

		mOuterBitmap = ((BitmapDrawable) mResources.getDrawable(R.drawable.leaf_kuang)).getBitmap();
		mOuterWidth = mOuterBitmap.getWidth();
		mOuterHeight = mOuterBitmap.getHeight();
	}

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh)
	{
		super.onSizeChanged(w, h, oldw, oldh);
		mTotalWidth = w;
		mTotalHeight = h;
		mProgressWidth = mTotalWidth - mLeftMargin - mRightMargin;
		mArcRadius = (mTotalHeight - 2 * mLeftMargin) / 2;

		mOuterSrcRect = new Rect(0, 0, mOuterWidth, mOuterHeight);
		mOuterDestRect = new Rect(0, 0, mTotalWidth, mTotalHeight);

		mWhiteRectF = new RectF(mLeftMargin + mCurrentProgressPosition, mLeftMargin, mTotalWidth - mRightMargin, mTotalHeight - mLeftMargin);
		mOrangeRectF = new RectF(mLeftMargin + mArcRadius, mLeftMargin, mCurrentProgressPosition, mTotalHeight - mLeftMargin);

		mArcRectF = new RectF(mLeftMargin, mLeftMargin, mLeftMargin + 2 * mArcRadius, mTotalHeight - mLeftMargin);
		mArcRightLocation = mLeftMargin + mArcRadius;
	}

	private enum StartType
	{
		LITTLE, MIDDLE, BIG
	}

	/**
	 * 叶子对象,用来记录叶子主要数据
	 * 
	 * @author Ajian_Studio
	 */
	private class Leaf
	{

		// 在绘制部分的位置
		float x, y;
		// 控制叶子飘动的幅度
		StartType type;
		// 旋转角度
		int rotateAngle;
		// 旋转方向--0代表顺时针,1代表逆时针
		int rotateDirection;
		// 起始时间(ms)
		long startTime;
	}

	private class LeafFactory
	{
		private static final int MAX_LEAFS = 8;
		Random random = new Random();

		// 生成一个叶子信息
		public Leaf generateLeaf()
		{
			Leaf leaf = new Leaf();
			int randomType = random.nextInt(3);
			// 随时类型- 随机振幅
			StartType type = StartType.MIDDLE;
			switch (randomType)
			{
			case 0:
				break;
			case 1:
				type = StartType.LITTLE;
				break;
			case 2:
				type = StartType.BIG;
				break;
			default:
				break;
			}
			leaf.type = type;
			// 随机起始的旋转角度
			leaf.rotateAngle = random.nextInt(360);
			// 随机旋转方向(顺时针或逆时针)
			leaf.rotateDirection = random.nextInt(2);
			// 为了产生交错的感觉,让开始的时间有一定的随机性
			mLeafFloatTime = mLeafFloatTime <= 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;
			mAddTime += random.nextInt((int) (mLeafFloatTime * 2));
			leaf.startTime = System.currentTimeMillis() + mAddTime;
			return leaf;
		}

		// 根据最大叶子数产生叶子信息
		public List<Leaf> generateLeafs()
		{
			return generateLeafs(MAX_LEAFS);
		}

		// 根据传入的叶子数量产生叶子信息
		public List<Leaf> generateLeafs(int leafSize)
		{
			List<Leaf> leafs = new LinkedList<Leaf>();
			for (int i = 0; i < leafSize; i++)
			{
				leafs.add(generateLeaf());
			}
			return leafs;
		}
	}

	/**
	 * 设置中等振幅
	 * 
	 * @param amplitude
	 */
	public void setMiddleAmplitude(int amplitude)
	{
		this.mMiddleAmplitude = amplitude;
	}

	/**
	 * 设置振幅差
	 * 
	 * @param disparity
	 */
	public void setMplitudeDisparity(int disparity)
	{
		this.mAmplitudeDisparity = disparity;
	}

	/**
	 * 获取中等振幅
	 * 
	 * @param amplitude
	 */
	public int getMiddleAmplitude()
	{
		return mMiddleAmplitude;
	}

	/**
	 * 获取振幅差
	 * 
	 * @param disparity
	 */
	public int getMplitudeDisparity()
	{
		return mAmplitudeDisparity;
	}

	/**
	 * 设置进度
	 * 
	 * @param progress
	 */
	public void setProgress(int progress)
	{
		this.mProgress = progress;
		postInvalidate();
	}

	/**
	 * 设置叶子飘完一个周期所花的时间
	 * 
	 * @param time
	 */
	public void setLeafFloatTime(long time)
	{
		this.mLeafFloatTime = time;
	}

	/**
	 * 设置叶子旋转一周所花的时间
	 * 
	 * @param time
	 */
	public void setLeafRotateTime(long time)
	{
		this.mLeafRotateTime = time;
	}

	/**
	 * 获取叶子飘完一个周期所花的时间
	 */
	public long getLeafFloatTime()
	{
		mLeafFloatTime = mLeafFloatTime == 0 ? LEAF_FLOAT_TIME : mLeafFloatTime;
		return mLeafFloatTime;
	}

	/**
	 * 获取叶子旋转一周所花的时间
	 */
	public long getLeafRotateTime()
	{
		mLeafRotateTime = mLeafRotateTime == 0 ? LEAF_ROTATE_TIME : mLeafRotateTime;
		return mLeafRotateTime;
	}
	
	public int getMaxProgress()
	{
		return mMaxProgress;
	}

	public void setMaxProgress(int maxProgress)
	{
		mMaxProgress = maxProgress;
	}

}

创建一个Activity,专门用于显示进度加载效果。

package com.john.loading.animation;

import java.util.Random;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.View;
import android.view.Window;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;

public class LeafLoadingActivity extends Activity
{

	Handler mHandler = new Handler()
	{
		public void handleMessage(Message msg)
		{
			switch (msg.what)
			{
			case REFRESH_PROGRESS:
				if (mProgress < 40)
				{
					mProgress += 1;
					// 随机800ms以内刷新一次
					mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, new Random().nextInt(800));
					mLeafLoadingView.setProgress(mProgress);
				}
				else
				{
					mProgress += 1;
					// 随机1200ms以内刷新一次
					mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, new Random().nextInt(1200));
					mLeafLoadingView.setProgress(mProgress);

				}

				if (mProgress == mLeafLoadingView.getMaxProgress())
				{
					mHandler.sendEmptyMessage(LOADING_FINISHED);
				}
				break;

			case LOADING_FINISHED:
			{
				finish();
				// go to other activity
			}
				break;

			default:
				break;
			}
		};
	};

	private static final int REFRESH_PROGRESS = 0x10;
	private static final int LOADING_FINISHED = 1;
	private LeafLoadingView mLeafLoadingView;
	private View mFanView;
	private int mProgress = 0;

	@Override
	protected void onCreate(Bundle savedInstanceState)
	{
		super.onCreate(savedInstanceState);
		requestWindowFeature(Window.FEATURE_NO_TITLE);

		setContentView(R.layout.leaf_loading_layout);
		initViews();
		mHandler.sendEmptyMessageDelayed(REFRESH_PROGRESS, 3000);
	}

	private void initViews()
	{
		mFanView = findViewById(R.id.fan_pic);
		RotateAnimation rotateAnimation = AnimationUtils.initRotateAnimation(false, 1500, true, Animation.INFINITE);
		mFanView.startAnimation(rotateAnimation);
		mLeafLoadingView = (LeafLoadingView) findViewById(R.id.leaf_loading);

	}

}

下一篇:Android酷炫动画效果之3D星体旋转效果





















你可能感兴趣的:(Android酷炫动画效果之进度加载动画)