Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> MaterialProgressDrawable

MaterialProgressDrawable

編輯:關於Android編程

譯於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 @interface ProgressDrawableSize 用於限制updateSizes(@ProgressDrawableSize int size)方法的參數 private static class Ring 環

常量

類型 常量 說明 static final int LARGE 值:0
大樣式 static final int DEFAULT 值:1
默認樣式

公共構造方法

MaterialProgressDrawable(Context context, View parent)

重寫的公共方法

返回值 方法 void draw(Canvas c) int getAlpha() int getIntrinsicHeight() int getIntrinsicWidth() int getOpacity() boolean isRunning() void setAlpha(int alpha) void setColorFilter(ColorFilter colorFilter) void start() void stop()

公共方法

返回值 方法 void setArrowScale(float scale)
設置箭頭的比例
(譯注:這裡沒寫,但這個比例應該是0-1的,因為代碼中有*mArrowScale) void setBackgroundColor(int color)
設置背景色 void setColorSchemeColors(int… colors)
設置進度條的顏色,可以傳多個,依次顯示。
同時第一個顏色也會被用於響應用戶的swipe手勢。 void setProgressRotation(float rotation)
設置進度條的旋旋轉,[0..1](譯注:0-1代表順時針旋轉0-360度) void setStartEndTrim(float startAngle, float endAngle)
設置進度條弧度的起始角。
(譯注:指圓環的起始范圍。順時針,0為左邊。如0-0.5表示上半圓,0.5-1表示下半圓。1-0.5表示下半圓。0-1就是整個圓了,一般要留有缺口。該方法可配合setProgressRotation(float rotation)使用) void showArrow(boolean show)
設置為true以顯示進度條 void updateSizes(@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);
            }
        }
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved