Android 自定義動畫進度條 帶漸變和動畫效果 《一》(從入門到巔峯)

uploading.4e448015.gif轉存失敗重新上傳取消
 
分析原理;
1.所以我們不得不把它拆分爲2個形狀:圓環.
2.如何實現漸變
3.如何實現動畫的效果
4.測量及自適應圓形進度條View的寬高
5.下載進度不會勻速到100%,相冊下載進度
6.如何添加手動拖動進度
7.onSizeChanged
 

概述:

自定義帶進度圓環思路主要可以分爲以下幾步:

1.自定義View屬性
2.View 的測量
3.計算繪製 View 所需參數
4.圓弧的繪製及漸變的實現
5.文字的繪製
6.動畫效果的實現

 
第一步:構造方法自定義styel中初始化變量
public FitnessVideoCircleProgressBar(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);

    paint = new Paint();

    TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
            R.styleable.FitnessVideoCircleProgressBar);

    boardColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_boardCircleColor, Color.parseColor("#ffffff"));

    circleProgressBgColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_circleProgressBgColor, Color.parseColor("#597C7C7C"));
    circleProgressColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_circleProgressColor, Color.parseColor("#6AC5DC"));
    innerCircleColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_innerCircleColor, Color.parseColor("#4E6483"));
    textColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_textColor, Color.parseColor("#ffffff"));
    textSize = mTypedArray.getDimension(R.styleable.FitnessVideoCircleProgressBar_textSize, dip2px(ShadowApp.context(), 21));
    circleWidth = mTypedArray.getDimension(R.styleable.FitnessVideoCircleProgressBar_circleWidth, dip2px(context, 6));
    max = mTypedArray.getInteger(R.styleable.FitnessVideoCircleProgressBar_max, 1000);
    textIsDisplayable = mTypedArray.getBoolean(R.styleable.FitnessVideoCircleProgressBar_textIsDisplayable, true);
    style = mTypedArray.getInt(R.styleable.FitnessVideoCircleProgressBar_style, 0);

    mTypedArray.recycle();

}
第二步:重寫ondraw方法
 
1.一個背景圖片
2.裏面畫一個文字
3.外面畫一個進度條,就是畫圓弧
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    /**
     * 畫進度條的灰色背景,最外層的。畫最外層的大圓環,
     */
    centre = getWidth() / 2;
    radius = (int) (centre - circleWidth / 2);
    paint.setAntiAlias(true);
    paint.setColor(circleProgressBgColor);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(circleWidth);
    canvas.drawCircle(centre, centre, radius, paint);


    /**
     * 最裏面的,畫中間內層圓
     * //半徑減去空白的空隙
     */
    paint.setColor(innerCircleColor);
    paint.setStyle(Paint.Style.FILL);
    canvas.drawCircle(centre, centre, (float) (radius - 16), paint);

    /**
     * 畫中間外層圓,邊線
     */
    paint.setColor(boardColor);
    paint.setStyle(Paint.Style.STROKE);
    paint.setStrokeWidth(1);
    canvas.drawCircle(centre, centre, (float) (radius - 30), paint);

    /**
     * 畫圓弧 ,畫圓環的進度
     */
    paint.setStrokeWidth(circleWidth);
    paint.setColor(circleProgressColor);
    oval.set(centre - radius, centre - radius, centre + radius, centre + radius);
    switch (style) {
        case STROKE: {
            paint.setStyle(Paint.Style.STROKE);
            canvas.drawArc(oval, -90, 360 * progress / max, false, paint);
            break;
        }
        case FILL: {
            paint.setStyle(Paint.Style.FILL_AND_STROKE);
            if (progress != 0)
                canvas.drawArc(oval, -90, 360 * progress / max, true, paint);
            break;
        }
    }

    /**
     * 畫進度百分比文本
     */
    paint.setStrokeWidth(0);
    paint.setColor(textColor);
    paint.setTextSize(textSize);
    paint.setTypeface(Typeface.DEFAULT);
    paint.setStyle(Paint.Style.FILL);

    float textWidth = paint.measureText(remainSec);


    if (textIsDisplayable && style == STROKE) {
        canvas.drawText(remainSec, centre - textWidth / 2, centre + textSize / 2, paint);

    }
}
第三步:進度有動畫效果
    set方法設置 或者更新的方法
   用到補間動畫,自定義RotateAnimation
public synchronized void setProgress(int progress, View view) {
    if (progress < 0) {
        throw new IllegalArgumentException("progress not less than 0");
    }
    if (progress * 10 < this.destProgress) {
        oldProgress = 0;
        destProgress = 0;
        view.clearAnimation();
        this.progress = 0;
        mBeginPoint = 0;
    }
    if (progress > max) {
        progress = max;
    }
    progress = progress * 10;
    oldProgress = this.progress;
    destProgress = progress;
    int endPoint = (int) (((float) 360 / max) * progress);
    view.startAnimation(pointRotationAnim(mBeginPoint, endPoint));
    mBeginPoint = endPoint;
}

private Animation pointRotationAnim(float fromDegrees, float toDegrees) {
    int initDegree = 54;// 進度點起始位置(圖片偏移約54度)
    RotateAnimation theRotateAnimation = new MyRotationAni(fromDegrees - initDegree,
            toDegrees - initDegree, Animation.RELATIVE_TO_SELF, 0.5f,
            Animation.RELATIVE_TO_SELF, 0.5f);
    theRotateAnimation.setDuration(1000);
    theRotateAnimation.setRepeatCount(0);
    theRotateAnimation.setFillAfter(true);// 設置動畫結束後是否停留在結束位置
    return theRotateAnimation;
}

private class MyRotationAni extends RotateAnimation {
    int offset;

    public MyRotationAni(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
        super(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
        offset = destProgress - oldProgress;
    }

    @Override
    protected void applyTransformation(float interpolatedTime, Transformation t) {
        progress = (int) (oldProgress + offset * interpolatedTime);
        super.applyTransformation(interpolatedTime, t);
        invalidate();
    }
}

4.漸變來啦

使用Android提供的掃描渲染器SweepGradient實現需要的漸變
LinearGradient
 //先創建一個渲染器
 SweepGradient mSweepGradient = new SweepGradient(canvas.getWidth() / 2,
         canvas.getHeight() / 2, //以圓弧中心作爲掃描渲染的中心以便實現需要的效果
         mMinColors, //這是我定義好的顏色數組,包含2個顏色:#35C3D7、#2894DD
         null);
//把漸變設置到筆刷
mMinPaint.setShader(mSweepGradient);
5.在上面的代碼中,View的寬高是由繪製圓弧的矩形區域大小決定的,直接寫死在了init()方法中,
而我們的實際需求是View的寬高可以由我們在外部進行設置,且無論怎麼設置寬高View都應該是一個正方形(寬高相等),因此我們需要重寫View的onMeasure()方法
 
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#cc99cd">private</span> <span style="color:#f8c555">RectF</span> mRectF;<span style="color:#999999">//繪製圓弧的矩形區域</span>

<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">float</span> barWidth;<span style="color:#999999">//圓弧進度條寬度</span>
<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> defaultSize;<span style="color:#999999">//自定義View默認的寬高</span>

<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">init</span>(<span style="color:#f8c555">Context</span> context,<span style="color:#f8c555">AttributeSet</span> attrs){
    <span style="color:#999999">//省略部分代碼...</span>
    defaultSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">DpOrPxUtils</span>.<span style="color:#f08d49">dip2px</span>(context,<span style="color:#f08d49">100</span>);
    barWidth <span style="color:#67cdcc">=</span> <span style="color:#f8c555">DpOrPxUtils</span>.<span style="color:#f08d49">dip2px</span>(context,<span style="color:#f08d49">10</span>);
    mRectF <span style="color:#67cdcc">=</span> <span style="color:#cc99cd">new</span> <span style="color:#f8c555">RectF</span>();
    
    progressPaint.<span style="color:#f08d49">setStrokeWidth</span>(barWidth);
    
    bgPaint.<span style="color:#f08d49">setStrokeWidth</span>(barWidth);
}

@Override
<span style="color:#cc99cd">protected</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">onMeasure</span>(<span style="color:#cc99cd">int</span> widthMeasureSpec, <span style="color:#cc99cd">int</span> heightMeasureSpec) {
    <span style="color:#cc99cd">super</span>.<span style="color:#f08d49">onMeasure</span>(widthMeasureSpec, heightMeasureSpec);

    <span style="color:#cc99cd">int</span> height <span style="color:#67cdcc">=</span> <span style="color:#f08d49">measureSize</span>(defaultSize, heightMeasureSpec);
    <span style="color:#cc99cd">int</span> width <span style="color:#67cdcc">=</span> <span style="color:#f08d49">measureSize</span>(defaultSize, widthMeasureSpec);
    <span style="color:#cc99cd">int</span> min <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(width, height);<span style="color:#999999">// 獲取View最短邊的長度</span>
    <span style="color:#f08d49">setMeasuredDimension</span>(min, min);<span style="color:#999999">// 強制改View爲以最短邊爲長度的正方形</span>

    <span style="color:#cc99cd">if</span>(min <span style="color:#67cdcc">>=</span> barWidth<span style="color:#67cdcc">*</span><span style="color:#f08d49">2</span>){<span style="color:#999999">//這裏簡單限制了圓弧的最大寬度</span>
        mRectF.<span style="color:#f08d49">set</span>(barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,min<span style="color:#67cdcc">-</span>barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,min<span style="color:#67cdcc">-</span>barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>);
    }

}

<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> <span style="color:#f08d49">measureSize</span>(<span style="color:#cc99cd">int</span> defaultSize,<span style="color:#cc99cd">int</span> measureSpec) {
    <span style="color:#cc99cd">int</span> result <span style="color:#67cdcc">=</span> defaultSize;
    <span style="color:#cc99cd">int</span> specMode <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getMode</span>(measureSpec);
    <span style="color:#cc99cd">int</span> specSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getSize</span>(measureSpec);

    <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.EXACTLY) {
        result <span style="color:#67cdcc">=</span> specSize;
    } <span style="color:#cc99cd">else</span> <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.AT_MOST) {
        result <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(result, specSize);
    }
    <span style="color:#cc99cd">return</span> result;
}</code></span></span>
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> <span style="color:#f08d49">measureSize</span>(<span style="color:#cc99cd">int</span> defaultSize,<span style="color:#cc99cd">int</span> measureSpec) {
    <span style="color:#cc99cd">int</span> result <span style="color:#67cdcc">=</span> defaultSize;
    <span style="color:#cc99cd">int</span> specMode <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getMode</span>(measureSpec);
    <span style="color:#cc99cd">int</span> specSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getSize</span>(measureSpec);

    <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.EXACTLY) {
        result <span style="color:#67cdcc">=</span> specSize;
    } <span style="color:#cc99cd">else</span> <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.AT_MOST) {
        result <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(result, specSize);
    }
    <span style="color:#cc99cd">return</span> result;
}</code></span></span>
得到SpecMode和SpecSize
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#f08d49">setMeasuredDimension()方法</span></code></span></span>
 
以上是:
1)自定義組件時什麼情況下需要實現onMeasure()方法,2)怎麼實現onMeasure()方法
當MyView的寬和高設置爲match_parent時,MyView會填充整個界面是毋庸置疑的,當將其寬和高設置爲某個確定值時,
MyView的大小也會按這個確定的值顯示。但是上面的例子中,我們把MyView的寬和高設置爲wrap_content,它依然填充整個界面。問題就出在這裏,所以我們需要重寫onMeasure()方法控制其大小
 
2)。怎麼重寫onMeasure()方法
每個View的大小,不僅取決於自身,而且也受父視圖的影響,普通的列子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    // TODO Auto-generated method stub
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int width = measureDimension(200, widthMeasureSpec);
    int height = measureDimension(200, heightMeasureSpec);
    setMeasuredDimension(width, height);
}

public int measureDimension(int defaultSize, int measureSpec){
    int result;

    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    if(specMode == MeasureSpec.EXACTLY){
        result = specSize;
    }else{
        result = defaultSize;   //UNSPECIFIED
        if(specMode == MeasureSpec.AT_MOST){
            result = Math.min(result, specSize);
        }
    }
    return result
uploading.4e448015.gif轉存失敗重新上傳取消
查看View內的onMeasure源碼:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}
可以看到AT_MOST和EXACTLY返回的是相同的specSiz,
這也是使用wrap_content和match_parent會出現相同的結果的原因。
 
7.onSizeChanged:
在控件發生變化的時候 重新賦值寬高,以及重新爲一些需要在控件大小發生變化時需要充值賦值的屬性賦值。
<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-java"> <span style="color:#999999">/**
     * 第四部分,在size發生變化時,重新賦值寬高
     *
     * @param w
     * @param h
     * @param oldw
     * @param oldh
     */</span>
    @Override
    <span style="color:#cc99cd">protected</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">onSizeChanged</span>(<span style="color:#cc99cd">int</span> w, <span style="color:#cc99cd">int</span> h, <span style="color:#cc99cd">int</span> oldw, <span style="color:#cc99cd">int</span> oldh) {
        <span style="color:#cc99cd">super</span>.<span style="color:#f08d49">onSizeChanged</span>(w, h, oldw, oldh);
        mWidth <span style="color:#67cdcc">=</span> w;
        mHeight <span style="color:#67cdcc">=</span> h;
        <span style="color:#f8c555">LayerDrawable</span> background <span style="color:#67cdcc">=</span> (<span style="color:#f8c555">LayerDrawable</span>) <span style="color:#f08d49">getBackground</span>();
        <span style="color:#f8c555">Drawable</span> drawable <span style="color:#67cdcc">=</span> background.<span style="color:#f08d49">getDrawable</span>(<span style="color:#f08d49">0</span>);
        drawable.<span style="color:#f08d49">getPadding</span>(rectOfBackGround);
    }</code></span></span></span>
進度條
漸變進度條
 
圓形進度條
 
自定義屬性的時候,不要和系統的名字一樣,否則會重複

Error:(139) Attribute "background" already defined with incompatible format.

=============================================
 
 
 
 
 
 
 
 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章