1. 程式人生 > >自定義View實現炫酷進度條

自定義View實現炫酷進度條

本篇部落格主要介紹利用自定義View來實現自己想要的進度載入效果,其中涉及到的內容不會說的很多,但是我會稍微解釋一下,其實只要有了思路都好處理。而且個人感覺自己寫一個自定義View要比看別人寫的自定義View要簡單一點,因為你很明白裡面屬性的意義,看別人的自定義View還需要不斷的判斷每個屬性的作用,然後是各種計算什麼的,感覺不是很好!!(這個炫酷的進度條不是我的想法,是我在網上看到的,不過很不好意思,我當時只是從他的git倉庫中把程式碼clone下來,沒有儲存原文連結,如果作者看到了,可以告訴我一下,我會將原文連結重新新增上去)。不過這篇的自定義View的實現,是我自己寫的,沒有完全拷貝作者的程式碼!!!詳細情況可以看一下我的程式碼!!

好了,廢話不多說了,先看一下效果圖:
這裡寫圖片描述這裡寫圖片描述這裡寫圖片描述

具體的程式碼的執行效果就是如上圖所示,效果不是很好,還有非常大的優化空間,有興趣的朋友可以自己更改我的程式碼,或者直接推倒重寫,謝謝!!!

主佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.example.administrator.customview.MainActivity">
<com.example.administrator.customview.CustomView android:id="@+id/pb" android:layout_width="wrap_content" android:layout_height="wrap_content"> </com.example.administrator.customview.CustomView> <Button android:layout_marginTop="20dp" android:layout_width="match_parent" android:layout_height="wrap_content" android:onClick="startProgressAnimation" android:layout_below="@id/pb" android:text="@string/start_animation"/> </RelativeLayout>

在這裡主要是將我自定義的View引入到佈局中,就不做過多的介紹了!!!

Activity檔案:

package com.example.administrator.customview;

import android.os.CountDownTimer;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends AppCompatActivity {

    private CustomView cv;
    private int progressValue = 0;
    private boolean mProgressIsRunning = false;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cv = (CustomView) findViewById(R.id.pb);
    }

    public void startProgressAnimation(View view){
        if (mProgressIsRunning){
            timer.cancel();
            timer.onFinish();
        }else{
            mProgressIsRunning = true;
            if (progressValue>=100){
                progressValue = 0;
            }
            timer.start();
        }
    }

    private CountDownTimer timer = new CountDownTimer(Integer.MAX_VALUE,100) {
        @Override
        public void onTick(long millisUntilFinished) {
            cv.setProgressValue(progressValue);
            progressValue ++;
        }

        @Override
        public void onFinish() {
            mProgressIsRunning = false;
        }
    };
}

使用一個定時器來模擬載入時value變化,根據時間不斷的調整Value值,給人進度不斷變化的感覺。這裡的cv就是我們自定義的View。

CustomView檔案(這個很關鍵,主要就是這個檔案):

package com.example.administrator.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;

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

/**
 * Created by zhuyuqiang on 2017/3/13.
 */

public class CustomView extends View {

    private final int WHITE_COLOR = Color.WHITE;
    private final int ORANGE_COLOR = 0xffffa800;
    //確定葉子旋轉方向
    private final int ROTATE_RIGHT = 0;
    private final int ROTATE_LEFT = 1;
    private final int ROTATE_DEGREE = 5;
    //確定葉子垂直方向的振幅大小
    private final int SMALL_AMPLITUDE = 0;
    private final int MIDDLE_AMPLITUDE = 1;
    private final int BIG_AMPLITUDE = 2;
    private final int SMALL_Y = 1;
    private final int MIDDLE_Y = 2;
    private final int BIG_Y = 3;
    //確定葉子水平方向的移動大小
    private final int SMALL_TRANSITION = 0;
    private final int MIDDLE_TRANSITION = 1;
    private final int BIG_TRANSITION = 2;
    private final int SMALL_X = 5;
    private final int MIDDLE_X = 10;
    private final int BIG_X = 15;
    private Paint mWhitePaint,mOrangePaint,mBitmapPaint;
    private int mPadding_left,mPadding_top,mPadding_right,mPadding_bottom;
    private Point mLeftCircleCenter,mRightCircleCenter,mFengshanPoint;
    private BitmapContainer mLeafContainer,mFengShanContainer,mBg_Container;
    private Matrix mFengShanRotate,mLeafMatrix,mTextMatrix;
    private int startDegree = 0;
    private int mProgressValue = 0;
    private int radius;
    private final float MAX_PROGRESS_VALUE = 100;
    private RectF mLeftArcRect;
    private Rect mTextRect;
    private final int max = 10;
    private int y_step = 2;
    private final String mFinish= "100%";
    private float mTextScale = 0.1f;
    private int mTextSize = 42;
    private List<Leaf> mLeaves = new ArrayList<>();
    private onProgressChangedListener listener;
    public interface onProgressChangedListener{
        void onProgressValueChanged(int progressValue);
    }

    public CustomView(Context context) {
        this(context,null);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr,0);
    }

    public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        initAttrs();
        initBitmaps();
        initPaints();
    }

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

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        initKeyPoints();
        initMatrix();
        generateLeaves();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawLeafs(canvas);
        drawProgress(canvas);
        canvas.drawBitmap(mBg_Container.src,mPadding_left,mPadding_top,mBitmapPaint);
        drawFengShan(canvas);
        postInvalidateDelayed(50);
    }

    private void drawLeafs(Canvas canvas) {
        int mProgressWidth = (int) ((mProgressValue/MAX_PROGRESS_VALUE)*(getWidth()-mPadding_left-mPadding_right-radius));
        for (Leaf mLeaf:mLeaves){
            canvas.save();
            mLeafMatrix.reset();
            mLeafMatrix.preRotate(mLeaf.degree,mLeafContainer.width/2,mLeafContainer.height/2);
            switch (mLeaf.rotateDirection){
                case ROTATE_LEFT:
                    mLeaf.degree = mLeaf.degree-ROTATE_DEGREE;
                    break;
                case ROTATE_RIGHT:
                    mLeaf.degree = mLeaf.degree+ROTATE_DEGREE;
                    break;
            }
            mLeafMatrix.postTranslate(mLeaf.position.x-50,mLeaf.position.y-mLeafContainer.height/2);
            switch (mLeaf.transition){
                case SMALL_TRANSITION:
                    mLeaf.position.x = mLeaf.position.x - SMALL_X;
                    break;
                case MIDDLE_TRANSITION:
                    mLeaf.position.x = mLeaf.position.x - MIDDLE_X;
                    break;
                case BIG_TRANSITION:
                    mLeaf.position.x = mLeaf.position.x - BIG_X;
                    break;
            }
            switch (mLeaf.transition){
                case SMALL_AMPLITUDE:
                    if(mLeaf.position.y<(mRightCircleCenter.y-max)){
                        y_step = -SMALL_Y;
                    }else if (mLeaf.position.y>(mRightCircleCenter.y+max)){
                        y_step = SMALL_Y;
                    }
                    break;
                case MIDDLE_AMPLITUDE:
                    if(mLeaf.position.y<(mRightCircleCenter.y-max)){
                        y_step = -MIDDLE_Y;
                    }else if (mLeaf.position.y>(mRightCircleCenter.y+max)){
                        y_step = MIDDLE_Y;
                    }
                    break;
                case BIG_AMPLITUDE:
                    if(mLeaf.position.y<(mRightCircleCenter.y-max)){
                        y_step = -BIG_Y;
                    }else if (mLeaf.position.y>(mRightCircleCenter.y+max)){
                        y_step = BIG_Y;
                    }
                    break;
            }

            mLeaf.position.y = mLeaf.position.y - y_step;
            if(mLeaf.position.x > mProgressWidth+mPadding_left+10){
                canvas.drawBitmap(mLeafContainer.src,mLeafMatrix,mBitmapPaint);
            }else{
                mLeaf.position.set(mRightCircleCenter.x-radius,mRightCircleCenter.y-mLeafContainer.height/2);
                mLeaf.transition = new Random().nextInt(3);
                mLeaf.degree = new Random().nextInt(360);
                mLeaf.rotateDirection = new Random().nextInt(2);
                mLeaf.amplitude = new Random().nextInt(3);
            }
            canvas.restore();
        }
    }

    public void setProgressValue(int value){
        this.mProgressValue = value;
        invalidate();
    }

    public int getProgressValue(){
        return mProgressValue;
    }

    public void setProgressChangeListener(onProgressChangedListener listener){
        this.listener = listener;
    }

    private void drawProgress(Canvas canvas) {

        int shouldDrawWidth = (int) ((mProgressValue/MAX_PROGRESS_VALUE)*(getWidth()-mPadding_left-mPadding_right-radius));
        Log.d("zyq_progress","shouldDrawWidth = "+shouldDrawWidth);
        if(shouldDrawWidth<(mLeftCircleCenter.x-mPadding_left)){
            float degree = (float) Math.toDegrees(Math.acos((radius - shouldDrawWidth)
                    / (float) radius));
            canvas.save();
            canvas.drawArc(mLeftArcRect,180-degree,2*degree,false,mOrangePaint);
        }else{
            if (shouldDrawWidth>mRightCircleCenter.x){
                shouldDrawWidth = mRightCircleCenter.x;
            }
            canvas.save();
            canvas.drawArc(mLeftArcRect,90,180,true,mOrangePaint);
            canvas.drawRect(radius+mPadding_left,mPadding_top,shouldDrawWidth,2*radius+mPadding_top,mOrangePaint);
            canvas.restore();
        }
        if(listener != null){
            listener.onProgressValueChanged(mProgressValue);
        }
    }

    private void drawFengShan(Canvas canvas){
        if(mProgressValue<100){
            startDegree = startDegree+15;
            canvas.save();
            mFengShanRotate.reset();
            mFengShanRotate.preRotate(startDegree,mFengShanContainer.width/2,mFengShanContainer.height/2);
            mFengShanRotate.postTranslate(mFengshanPoint.x,mFengshanPoint.y);
            Log.i("zyq","mFengshanPoint.x ="+mFengshanPoint.x+" mFengshanPoint.y="+mFengshanPoint.y);
            canvas.drawBitmap(mFengShanContainer.src,mFengShanRotate,mBitmapPaint);
            canvas.restore();
        }else{
            canvas.save();
            mTextMatrix.reset();
            if(mTextScale<1){
                mWhitePaint.setTextSize(mTextSize*mTextScale);
                mWhitePaint.getTextBounds(mFinish,0,mFinish.length(),mTextRect);
            }else{
                mWhitePaint.setTextSize(mTextSize);
                mWhitePaint.getTextBounds(mFinish,0,mFinish.length(),mTextRect);
            }
            canvas.drawText(mFinish,0,mFinish.length(),mRightCircleCenter.x-((mTextRect.width())/2),
                    mRightCircleCenter.y+(mTextRect.height()/2),mWhitePaint);
            mTextScale = mTextScale+0.1f;
            canvas.restore();
        }

    }

    private void initKeyPoints(){
        mLeftCircleCenter = new Point();
        radius = getHeight()/2-mPadding_bottom/2-mPadding_top/2;
        mLeftCircleCenter.y = radius+mPadding_top;
        mLeftCircleCenter.x = radius+mPadding_right;
        Log.d("zyq_point","View,height="+getHeight()+" mLeftCircleCenter.x = "+mLeftCircleCenter.x+"mLeftCircleCenter.y="+mLeftCircleCenter.y);
        mRightCircleCenter = new Point();
        mRightCircleCenter.y = radius+mPadding_top;
        mRightCircleCenter.x = getWidth()-radius-mPadding_right;
        Log.d("zyq_point","mRightCircleCenter.x = "+mRightCircleCenter.x+"mRightCircleCenter.y="+mRightCircleCenter.y);
        mFengshanPoint = new Point();
        mFengshanPoint.x = mRightCircleCenter.x-mFengShanContainer.width/2;
        mFengshanPoint.y = mRightCircleCenter.y-mFengShanContainer.height/2;
        Log.d("zyq_point","mFengshanPoint.x = "+mFengshanPoint.x+"mFengshanPoint.y="+mFengshanPoint.y);
    }

    private void initPaints(){
        mWhitePaint = new Paint();
        mWhitePaint.setAntiAlias(true);
        mWhitePaint.setDither(true);
        mWhitePaint.setTextSize(32);
        mWhitePaint.setTextAlign(Paint.Align.LEFT);
        mWhitePaint.setStrokeWidth(6f);
        mWhitePaint.setColor(WHITE_COLOR);


        mOrangePaint = new Paint();
        mOrangePaint.setAntiAlias(true);
        mOrangePaint.setDither(true);
        mOrangePaint.setColor(ORANGE_COLOR);
        mOrangePaint.setStyle(Paint.Style.FILL);

        mBitmapPaint = new Paint();
        mBitmapPaint.setAntiAlias(true);
        mBitmapPaint.setDither(true);
        mBitmapPaint.setFilterBitmap(true);
    }

    private void initBitmaps(){
        Bitmap mLeaf = ((BitmapDrawable)getResources().getDrawable(R.drawable.leaf_drawable,null)).getBitmap();
        Bitmap mFengShan = ((BitmapDrawable)getResources().getDrawable(R.drawable.fengshan_drawable,null)).getBitmap();
        Bitmap mbg_kuang = ((BitmapDrawable)getResources().getDrawable(R.drawable.leaf_kuang_drawable,null)).getBitmap();
        mLeafContainer = new BitmapContainer();
        mLeafContainer.src = mLeaf;
        mLeafContainer.width = mLeaf.getWidth();
        mLeafContainer.height = mLeaf.getHeight();

        mFengShanContainer = new BitmapContainer();
        mFengShanContainer.src = mFengShan;
        mFengShanContainer.width = mFengShan.getWidth();
        mFengShanContainer.height = mFengShan.getHeight();
        Log.i("zyq","fen:width="+mFengShanContainer.width+" height="+mFengShanContainer.height);

        mBg_Container = new BitmapContainer();
        mBg_Container.src = mbg_kuang;
        mBg_Container.width = mbg_kuang.getWidth();
        mBg_Container.height = mbg_kuang.getHeight();
        Log.i("zyq_Bg_Container","mBg_Container:width="+mBg_Container.width+" height="+mBg_Container.height);
    }

    private void initMatrix(){
        mFengShanRotate = new Matrix();
        mLeafMatrix = new Matrix();
        mTextMatrix = new Matrix();
        calculatePosition();
    }

    private void calculatePosition(){
        mLeftArcRect = new RectF();
        mLeftArcRect.left = mPadding_left;
        mLeftArcRect.top = mPadding_top;
        mLeftArcRect.right = 2*radius+mPadding_left;
        mLeftArcRect.bottom = 2*radius+mPadding_top;
        mTextRect = new Rect();
        mWhitePaint.getTextBounds(mFinish,0,mFinish.length(), mTextRect);
    }

    private class BitmapContainer{
        Bitmap src;
        int width;
        int height;
    }

    private class Leaf{
        Point position;
        int degree;
        long startTime;
        int rotateDirection;
        int amplitude;
        int transition;
        public Leaf(){
            position = new Point();
            position.set(mRightCircleCenter.x-radius,mRightCircleCenter.y);
            degree = new Random().nextInt(360);
            startTime = System.currentTimeMillis();
            rotateDirection = new Random().nextInt(2);
            amplitude = new Random().nextInt(3);
            transition = new Random().nextInt(3);
        }
        public Leaf(int transition){
            position = new Point();
            position.set(mRightCircleCenter.x-radius,mRightCircleCenter.y);
            degree = new Random().nextInt(360);
            startTime = System.currentTimeMillis();
            rotateDirection = new Random().nextInt(2);
            amplitude = new Random().nextInt(3);
            this.transition = transition;
        }
    }

    private void generateLeaves() {
        if(mLeaves.isEmpty()){
            for(int i = 0;i<3;i++){
                Leaf l = new Leaf(i);
                mLeaves.add(l);
            }
        }
    }

    private int getCustomMeasureWidth(int widthMeasureSpec){
        int mode = MeasureSpec.getMode(widthMeasureSpec);
        int size = MeasureSpec.getSize(widthMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            Log.i("zyq_view","view.size = "+size);
            return size;
        }else{
            Log.i("zyq_view","view.width = "+(mBg_Container.width+getPaddingLeft()+getPaddingRight()));
            return mBg_Container.width+getPaddingLeft()+getPaddingRight();
        }
    }

    private int getCustomMeasureHeight(int heightMeasureSpec){
        int mode = MeasureSpec.getMode(heightMeasureSpec);
        int size = MeasureSpec.getSize(heightMeasureSpec);
        if (mode == MeasureSpec.EXACTLY){
            return size;
        }else{
            return mBg_Container.height+getPaddingTop()+getPaddingBottom();
        }
    }

    private void initAttrs() {
        mPadding_left = getPaddingLeft();
        mPadding_top = getPaddingTop();
        mPadding_right = getPaddingRight();
        mPadding_bottom = getPaddingBottom();
        Log.i("zyq_pad","left="+mPadding_left+" top="+mPadding_top+" right="+mPadding_right+" bottom="+mPadding_bottom);
    }
}

在檔案剛開始的時候,定義了各種寬高,比如葉子每次水平移動的距離、垂直移動的距離,旋轉的角度、旋轉的方向等。基本上來說就是你想控制什麼樣的動作,就需要你定義什麼樣的屬性,通過屬性控制物件的動作。這裡的三個圖片都是提前準備好的。然後在view中獲取這個單個圖片對應的bitmap,並獲取每個bitmap的寬高,這裡的寬高用於以後確定這些bitmap的繪製位置。

在實際的繪製過程中,還需要考慮圖層的關係,因為需要有葉子融入的感覺,所以在這裡把繪製葉子的步驟放在最前面,這樣實現的了葉子融入的感覺。

其次,自定義View中的動畫都是通過控制矩陣來實現,比如旋轉、移動等。但是字型放大的動畫,我試過矩陣,但是不起作用,最後不得已通過改變字型尺寸來實現,希望有知道的朋友可以告訴我一下,不勝感激!!!

其實,其他的動畫還好,就是葉子的動畫不太好弄,因為需要一種,葉子隨機出現的效果,我這裡做的不好,希望大神們可以幫忙更改一下!!原作者的葉子動畫是與時間關聯的,我沒有采用,只是限定了葉子出現的位置。具體的情況,還請麻煩各位看程式碼吧!!!

好了,關於自定義View,暫時就說到這裡,以後還將繼續介紹有關於自定義View方面的內容,希望大家可以關一下!!!

這是我的微信公眾號,如果可以的話,希望您可以幫忙關注一下,這將是對我最大的鼓勵了,謝謝!!

公眾號