MaterialProgressDrawable

平方X翻译说明:
仅供参考,对正确性概不负责,翻译不对的欢迎指正,勿喷。


参考:Android 圆形进度条MaterialProgressDrawable

译于20160718,原文android.support.v4.widget.MaterialProgressDrawable.java

翻译

MaterialProgressDrawable

class MaterialProgressDrawable(译注:注意不是public的哦)
extends Drawable
implements Animatable


Java.lang.Object
↳android.graphics.drawable.Drawable
↳android.support.v4.widget.MaterialProgressDrawable

类概述

为Material主题设置的Fancy(译注:花式的、花样的、奇特的)的进度条

嵌套类

类型名称说明
public @interfaceProgressDrawableSize用于限制updateSizes(@ProgressDrawableSize int size)方法的参数
private static classRing

常量

类型常量说明
static final intLARGE值:0
大样式
static final intDEFAULT值:1
默认样式

公共构造方法

MaterialProgressDrawable(Context context, View parent)

重写的公共方法

返回值方法
voiddraw(Canvas c)
intgetAlpha()
intgetIntrinsicHeight()
intgetIntrinsicWidth()
intgetOpacity()
booleanisRunning()
voidsetAlpha(int alpha)
voidsetColorFilter(ColorFilter colorFilter)
voidstart()
voidstop()

公共方法

返回值方法
voidsetArrowScale(float scale)
设置箭头的比例
(译注:这里没写,但这个比例应该是0-1的,因为代码中有*mArrowScale)
voidsetBackgroundColor(int color)
设置背景色
voidsetColorSchemeColors(int… colors)
设置进度条的颜色,可以传多个,依次显示。
同时第一个颜色也会被用于响应用户的swipe手势。
voidsetProgressRotation(float rotation)
设置进度条的旋旋转,[0..1](译注:0-1代表顺时针旋转0-360度)
voidsetStartEndTrim(float startAngle, float endAngle)
设置进度条弧度的起始角。
(译注:指圆环的起始范围。顺时针,0为左边。如0-0.5表示上半圆,0.5-1表示下半圆。1-0.5表示下半圆。0-1就是整个圆了,一般要留有缺口。该方法可配合setProgressRotation(float rotation)使用)
voidshowArrow(boolean show)
设置为true以显示进度条
voidupdateSizes(@ProgressDrawableSize int size)
设置进度条的整体大小。这将更新圆环的半径和宽度。
参数只能为LARGE=0,或default=1

使用说明

        //①创建drawable并设给view
        ImageView imageView=...;
        progress = new MaterialProgressDrawable(getContext(),imageView);
        imageView.setImageDrawable(progress);

        //②设置相关属性
        //背景
        progress.setBackgroundColor(0xFF000000);
        //颜色
        int[] colors = {0xFFFF0000,0xFF00FF00,0xFF0000FF};
        progress.setColorSchemeColors(colors);
        //尺寸
        progress.updateSizes(MaterialProgressDrawable.DEFAULT);
        //旋转角度,0-1
        progress.setProgressRotation(0f);
        //圆环范围,0-1
        progress.setStartEndTrim(0f,  1f);
        //箭头大小,0-1
        progress.setArrowScale(0f);
        //透明度,0-255
        progress.setAlpha(100);

        //③start
        progress.start();

源码分析

构造方法

调用构造方法后,要记得把这个drawable设给view,如imageView.setImageDrawable(drawable);

    public static final int LARGE = 0;
    public static final int DEFAULT = 1;

    private final int[] COLORS = new int[] {
            Color.BLACK
    };
    public MaterialProgressDrawable(Context context, View parent) {
        mParent = parent;
        mResources = context.getResources();

        mRing = new Ring(mCallback);
        mRing.setColors(COLORS);

        updateSizes(DEFAULT);
        setupAnimators();
    }

初始化动画


    private void setupAnimators() {
        final Ring ring = mRing;
        final Animation animation = new Animation() {
            @Override
            public void applyTransformation(float interpolatedTime, Transformation t) {
                if (mFinishing) {
                    applyFinishTranslation(interpolatedTime, ring);
                } else {
                    //初始化,最小弧度等于宽度
                    // The minProgressArc is calculated from 0 to create an
                    // angle that matches the stroke width.
                    final float minProgressArc = getMinProgressArc(ring);
                    final float startingEndTrim = ring.getStartingEndTrim();
                    final float startingTrim = ring.getStartingStartTrim();
                    final float startingRotation = ring.getStartingRotation();
                    //更新颜色,后面有补充1
                    updateRingColor(interpolatedTime, ring);
                    //前50%,移动start trim
                    // Moving the start trim only occurs in the first 50% of a
                    // single ring animation
                    if (interpolatedTime <= START_TRIM_DURATION_OFFSET) {
                        //这个计算使得在这50%的时间段内可以完成0-1的动画展
                        // scale the interpolatedTime so that the full
                        // transformation from 0 - 1 takes place in the
                        // remaining time
                        final float scaledTime = (interpolatedTime)
                                / (1.0f - START_TRIM_DURATION_OFFSET);
                        //对MATERIAL_INTERPOLATOR的补充2
                        final float startTrim = startingTrim
                                + ((MAX_PROGRESS_ARC - minProgressArc) * MATERIAL_INTERPOLATOR
                                .getInterpolation(scaledTime));
                        ring.setStartTrim(startTrim);
                    }

                    //后50%移动end trim
                    // Moving the end trim starts after 50% of a single ring
                    // animation completes
                    if (interpolatedTime > END_TRIM_START_DELAY_OFFSET) {
                        // scale the interpolatedTime so that the full
                        // transformation from 0 - 1 takes place in the
                        // remaining time
                        final float minArc = MAX_PROGRESS_ARC - minProgressArc;
                        float scaledTime = (interpolatedTime - START_TRIM_DURATION_OFFSET)
                                / (1.0f - START_TRIM_DURATION_OFFSET);
                        final float endTrim = startingEndTrim
                                + (minArc * MATERIAL_INTERPOLATOR.getInterpolation(scaledTime));
                        ring.setEndTrim(endTrim);
                    }
                    //补充3
                    final float rotation = startingRotation + (0.25f * interpolatedTime);
                    ring.setRotation(rotation);
                    float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)
                            + (FULL_ROTATION * (mRotationCount / NUM_POINTS));
                    setRotation(groupRotation);
                }
            }
        };
        animation.setRepeatCount(Animation.INFINITE);
        animation.setRepeatMode(Animation.RESTART);
        animation.setInterpolator(LINEAR_INTERPOLATOR);
        animation.setAnimationListener(new Animation.AnimationListener() {

            @Override
            public void onAnimationStart(Animation animation) {
                mRotationCount = 0;
            }

            @Override
            public void onAnimationEnd(Animation animation) {
                // do nothing
            }

            @Override
            public void onAnimationRepeat(Animation animation) {
                ring.storeOriginals();
                //设置下一个颜色
                ring.goToNextColor();
                //结束角设为开始角
                ring.setStartTrim(ring.getEndTrim());
                if (mFinishing) {
                    // finished closing the last ring from the swipe gesture; go
                    // into progress mode
                    mFinishing = false;
                    animation.setDuration(ANIMATION_DURATION);
                    ring.setShowArrow(false);
                } else {
                    //+1求余
                    mRotationCount = (mRotationCount + 1) % (NUM_POINTS);
                }
            }
        });
        mAnimation = animation;
    }

补充1更新环的颜色。
这里要看一下这个方法

    /**
     *在动画的最后25%的时间内,更新环的颜色
     *环的新颜色是当前环的颜色到下一个颜色的渐变
     */
    private void updateRingColor(float interpolatedTime, Ring ring) {
        if (interpolatedTime > COLOR_START_DELAY_OFFSET) {
            // scale the interpolatedTime so that the full
            // transformation from 0 - 1 takes place in the
            // remaining time
            ring.setColor(evaluateColorChange((interpolatedTime - COLOR_START_DELAY_OFFSET)
                            / (1.0f - COLOR_START_DELAY_OFFSET), ring.getStartingColor(),
                    ring.getNextColor()));
        }
    }

    //看一个渐变方法值得学习
    // Adapted from ArgbEvaluator.java
    private int evaluateColorChange(float fraction, int startValue, int endValue) {
        int startInt = (Integer) startValue;
        int startA = (startInt >> 24) & 0xff;
        int startR = (startInt >> 16) & 0xff;
        int startG = (startInt >> 8) & 0xff;
        int startB = startInt & 0xff;

        int endInt = (Integer) endValue;
        int endA = (endInt >> 24) & 0xff;
        int endR = (endInt >> 16) & 0xff;
        int endG = (endInt >> 8) & 0xff;
        int endB = endInt & 0xff;

        return (int)((startA + (int)(fraction * (endA - startA))) << 24) |
                (int)((startR + (int)(fraction * (endR - startR))) << 16) |
                (int)((startG + (int)(fraction * (endG - startG))) << 8) |
                (int)((startB + (int)(fraction * (endB - startB))));
    }

补充2
这个interpolator


    private static final Interpolator MATERIAL_INTERPOLATOR = new FastOutSlowInInterpolator();
    /**
     *和android.R.interpolator#fast_out_slow_in一致的interpolator
     *用查找表格来表示贝塞尔曲线在(0.0)到(1,1)的控制点
     */
    public class FastOutSlowInInterpolator extends LookupTableInterpolator {}

abstract class LookupTableInterpolator implements Interpolator {

    private final float[] mValues;
    private final float mStepSize;

    public LookupTableInterpolator(float[] values) {
        mValues = values;
        mStepSize = 1f / (mValues.length - 1);
    }

    @Override
    public float getInterpolation(float input) {
        if (input >= 1.0f) {
            return 1.0f;
        }
        if (input <= 0f) {
            return 0f;
        }
        //计算索引,用length-2,以防后面的越界,后面+1了
        // Calculate index - We use min with length - 2 to avoid IndexOutOfBoundsException when
        // we lerp (linearly interpolate) in the return statement
        int position = Math.min((int) (input * (mValues.length - 1)), mValues.length - 2);
        //因为表离散,计算小的偏移
        // Calculate values to account for small offsets as the lookup table has discrete values
        float quantized = position * mStepSize;
        float diff = input - quantized;
        float weight = diff / mStepSize;

        // Linearly interpolate between the table values
        return mValues[position] + weight * (mValues[position + 1] - mValues[position]);

        //(译注:不会翻译,意思就是说,因为计算所求的input比例的位置,和查询表中的位置有差值,要根据这个差值求得相差的值。具体做法是位置*步长,求得位置所占比例,(input比例-位置所占比例)/步长,得到权重。(下一位置的值-当前位置的值)*权重,得到差值)
        //示例如下[0,2,4,6,8]
        //要求0.8位的值
        //position=min(0.8*4,3)=3
        //quantized=3*1/4=0.75,
        //diff=0.8-0.75=0.05
        //weight=0.05/0.25=0.2
        //也就是说,和0.8相近的是0.75位置的数6,它和6相差0.05个步长,这个步长的值为
        //(8-6)*0.2=0.4
        //所以0.8的值应为6.4
        //并不是很难,主要是数学的知识,我的数学似乎也不是很好……
    }

}

补充3
这里设置了2个旋转,一个是环自己,一个是动画的旋转。
如果注释掉动画的旋转,环在慢慢变短、变长的同时,还会慢慢地旋转。
后面这个动画的旋转在draw方法中调用

    c.rotate(mRotation, bounds.exactCenterX(), bounds.exactCenterY());

    private static final float FULL_ROTATION = 1080.0f;
    /** The number of points in the progress "star". */
    private static final float NUM_POINTS = 5f;
float groupRotation = ((FULL_ROTATION / NUM_POINTS) * interpolatedTime)+ (FULL_ROTATION * (mRotationCount / NUM_POINTS));
    //不是很明白,可能是这样的意思,在5次内转1080度,当前要转的度数就等于转次的度数乘以时间,加上已经转了多少圈对应的度数,即1080/5*时间+1080*(次数/5)

原理分析

看了上面的源码,我们整理一下,原理也是很清晰呢。
用动画控制环,
在动画中,让环先变短,再变长。
同时,让环和drawable都旋转。

draw


        /**
         * Draw the progress spinner
         */
        public void draw(Canvas c, Rect bounds) {
            final RectF arcBounds = mTempBounds;
            arcBounds.set(bounds);
            arcBounds.inset(mStrokeInset, mStrokeInset);

            final float startAngle = (mStartTrim + mRotation) * 360;
            final float endAngle = (mEndTrim + mRotation) * 360;
            float sweepAngle = endAngle - startAngle;

            mPaint.setColor(mCurrentColor);
            //画弧,主要实现
            c.drawArc(arcBounds, startAngle, sweepAngle, false, mPaint);
            //画三角
            drawTriangle(c, startAngle, sweepAngle, bounds);

            if (mAlpha < 255) {
                mCirclePaint.setColor(mBackgroundColor);
                mCirclePaint.setAlpha(255 - mAlpha);
                //画圆,就是背景的
                c.drawCircle(bounds.exactCenterX(), bounds.exactCenterY(), bounds.width() / 2,
                        mCirclePaint);
            }
        }

drawTriangle

        //看不懂,哈哈。看了好几遍还是看不懂,等我以后学成再来看。
        private void drawTriangle(Canvas c, float startAngle, float sweepAngle, Rect bounds) {
            if (mShowArrow) {
                if (mArrow == null) {
                    mArrow = new Path();
                    mArrow.setFillType(Path.FillType.EVEN_ODD);
                } else {
                    mArrow.reset();
                }

                // Adjust the position of the triangle so that it is inset as
                // much as the arc, but also centered on the arc.
                float inset = (int) mStrokeInset / 2 * mArrowScale;
                float x = (float) (mRingCenterRadius * Math.cos(0) + bounds.exactCenterX());
                float y = (float) (mRingCenterRadius * Math.sin(0) + bounds.exactCenterY());

                // Update the path each time. This works around an issue in SKIA
                // where concatenating a rotation matrix to a scale matrix
                // ignored a starting negative rotation. This appears to have
                // been fixed as of API 21.
                mArrow.moveTo(0, 0);
                mArrow.lineTo(mArrowWidth * mArrowScale, 0);
                mArrow.lineTo((mArrowWidth * mArrowScale / 2), (mArrowHeight
                        * mArrowScale));
                mArrow.offset(x - inset, y);
                mArrow.close();
                // draw a triangle
                mArrowPaint.setColor(mCurrentColor);
                c.rotate(startAngle + sweepAngle - ARROW_OFFSET_ANGLE, bounds.exactCenterX(),
                        bounds.exactCenterY());
                c.drawPath(mArrow, mArrowPaint);
            }
        }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值