不使用第三方框架編寫的多線程斷線續傳功能 原 薦

一、背景

最近需要個斷線續傳功能,但是覺得一些框架不太適合,所以基於原理編寫了一個多線程斷線續傳功能

支持技術分享,但是複製和轉發我的博客時候請標明出處,謝謝 https://my.oschina.net/grkj/blog/2907188

二、斷線續傳的個人理解:

1、斷線續傳在個人理解,其實就是在出現正常下載流程之外的事情的時候,保存好當前文件下載的進度,然後點擊繼續下載的時候,從上次的下載進度繼續進行下載。

2、如何從上次下載進度繼續進行下載呢? 主要就是設置頭部信息進行告知實現的

setRequestProperty("Range", "bytes=" + progress + "-" + total);//設置下載範圍

三、主要功能有

1、支持多線程斷線續傳

2、支持回調事件拓展,使用泛型定義對象,支持更加靈活的去拓展對象

3、如果要下載的資源在要保存的文件夾中存在,那麼會自動進行下載位置校準和下載

4、支持自定義資源請求的方式(GET和POST方式)和請求超時時間

5、我編不下了,如果你發現了就幫我寫上去,謝謝....... 效果圖如下

下載3只是裝飾,你可以換個地址和修改一下MainActivity的按鈕監控那塊的代碼,抄下載下載1和下載2的代碼即可 下載中 下載完畢

後面我會完善個功能,只要連上網絡就進行檢查,然後自動進行資源下載,如果有需要可以給我留言

四、直接上源碼講解

篇幅太長,貼不了那麼多,只貼8點 代碼下載地址爲:點擊下載 源碼裏面DownLoadTask構造函數裏面有個地方寫錯了,寫死成了GET方式,如果下載源碼的要使用,可以複製下面的DownLoadTask源碼進去覆蓋掉就好了

1、多線程實例,主要的內容都在這裏了

//執行下載的線程
public class DownLoadTask implements Runnable {
    private static final String TAG = "DownLoadTask";
    public static final int CACHE_SIZE = 4 * 1024;//緩衝區間,4應該足夠了
    public static final int DEFAULT_TIME_OUT = 5000;//單位是毫秒,默認是5秒,支持自定義
    //線程安全的資源列表,key是文件名稱,value是下載實例
    private static ConcurrentHashMap<String, DownLoadEntity> mResourceMap = new ConcurrentHashMap<String, DownLoadEntity>();

    /**
     * @Description 停止下載
     * [@author](https://my.oschina.net/arthor) 姚旭民
     * [@date](https://my.oschina.net/u/2504391) 2018/11/20 16:37
     */
    public static void stop(String key) throws NullPointerException {
        try {
            if (key == null)
                throw new NullPointerException();
            mResourceMap.get(key).setStop(true);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    /**
     * [@param](https://my.oschina.net/u/2303379) key 文件憑證
     * @Description 資源刪除
     * @author 姚旭民
     * @date 2018/11/20 17:22
     */
    public static void remove(String key) throws NullPointerException {
        if (key == null || mResourceMap.get(key) == null)
            throw new NullPointerException("參數爲null或者下載數據不存在");
        mResourceMap.get(key).setDelete(true);
    }

    //下載實體
    DownLoadEntity mDownLoadEntity;
    //回調對象,只要進行實現,就可以獲得各種事件的觀察回調,IDownLoadCallBack源碼在 第2點 有貼出來
    IDownLoadCallBack mCallBack;
    //傳輸方式,是一個枚舉類型,支持自定義傳輸
    TransmissionType mType;
    //下載的超時時間
    int mTimeout;

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack) {
        this(downLoadEntity, mCallBack, TransmissionType.TYPE_GET);
    }

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type) {
        this(downLoadEntity, mCallBack, type, DEFAULT_TIME_OUT);
    }

    public DownLoadTask(DownLoadEntity downLoadEntity, IDownLoadCallBack mCallBack, TransmissionType type, int timeout) {
        this.mDownLoadEntity = downLoadEntity;
        this.mCallBack = mCallBack;
        this.mType = type;
        this.mTimeout = timeout;
        //數據存儲
        mResourceMap.put(downLoadEntity.getKey(), downLoadEntity);
        Log.v(TAG, "存放數據進入鍵值對,key:" + downLoadEntity.getKey() + ",downLoadEntity:" + downLoadEntity);
    }

    @Override
    public void run() {
        //下載路徑
        String downUrl = mDownLoadEntity.getDownUrl();
        //保存路徑
        String savePath = mDownLoadEntity.getSavePath();
        //已經下載的進度
        long progress = mDownLoadEntity.getProgress();//已經下載好的長度
        long total = mDownLoadEntity.getTotal();//文件的總長度
        String key = mDownLoadEntity.getKey();
        HttpURLConnection connection = null;
        //有人可能覺得NIO 的 FileChannel 也可以的話,那麼你也可以替換掉
        RandomAccessFile randomAccessFile = null;
        try {
            //設置文件寫入位置
            File file = new File(savePath);
            //父類文件夾是否存在
            File fileParent = file.getParentFile();
            if (!fileParent.exists()) {//如果父類文件夾不存在,即創建文件夾
                Log.v(TAG, "父類文件夾:" + fileParent.getPath() + ",不存在,開始創建");
                fileParent.mkdirs();
            }

            if (file != null) {//這一步是針對於斷線續傳的文件,用於比對數據庫和真實的數據,避免出現誤差
                long fileSize = file.length();
                if (progress != fileSize) {//如果文件有問題,以實際下載的文件大小爲準
                    Log.v(TAG, "文件傳輸節點不一致,開始修復傳數據節點");
                    progress = fileSize;
                    mDownLoadEntity.setProgress(progress);
                }
            }

            int precent = (int) ((float) progress / (float) total * 100);
            //開始下載之前先回調開始下載的進度
            mCallBack.onNext(key, precent);

            URL url = new URL(downUrl);
            connection = (HttpURLConnection) url.openConnection();
            //請求方式默認爲GET
            connection.setRequestMethod(mType.getType());
            //超時時間
            connection.setConnectTimeout(mTimeout);
            //從上次下載完成的地方下載
            //設置下載位置(從服務器上取要下載文件的某一段)
            connection.setRequestProperty("Range", "bytes=" + progress + "-" + total);//設置下載範圍

            randomAccessFile = new RandomAccessFile(file, "rwd");
            //從文件的某一位置開始寫入
            randomAccessFile.seek(progress);
            if (connection.getResponseCode() == 206) {//文件部分下載,返回碼爲206
                InputStream is = connection.getInputStream();
                byte[] buffer = new byte[CACHE_SIZE];
                //接收到的資源大小
                int len;
                while ((len = is.read(buffer)) != -1) {
                    //寫入文件
                    randomAccessFile.write(buffer, 0, len);
                    progress += len;
                    precent = (int) ((float) progress / (float) total * 100);
                    //更新進度回調
                    mCallBack.onNext(key, precent);
                    //停止下載
                    if (mDownLoadEntity.isStop()) {
                        mDownLoadEntity.setProgress(progress);
                        mCallBack.onPause(mDownLoadEntity, key, precent, progress, total);
                        return;
                    }
                    //取消下載
                    if (mDownLoadEntity.isDelete()) {
                        mResourceMap.remove(key);
                        //文件刪除
                        file.delete();
                        mCallBack.onDelete(mDownLoadEntity, key);
                        return;
                    }
                }
            }

            //資源刪除
            mResourceMap.remove(mDownLoadEntity.getFileName());
            //下載完成
            mCallBack.onSuccess(mDownLoadEntity, key);
        } catch (Exception e) {
            //資源刪除
            mResourceMap.remove(mDownLoadEntity.getFileName());
            mDownLoadEntity.setProgress(progress);
            //防止意外
            mDownLoadEntity.setStop(false);
            //失敗原因回調
            mCallBack.onFail(mDownLoadEntity, key, e.toString());

            StringBuffer sb = new StringBuffer();
            Writer writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            e.printStackTrace(printWriter);
            Throwable cause = e.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.close();
            //異常的詳細內容
            String result = writer.toString();
            Log.e(TAG, result);
        } finally {
            if (connection != null) {
                connection.disconnect();
            }
            try {
                if (randomAccessFile != null) {
                    randomAccessFile.close();
                }
            } catch (IOException e) {
                StringBuffer sb = new StringBuffer();
                Writer writer = new StringWriter();
                PrintWriter printWriter = new PrintWriter(writer);
                e.printStackTrace(printWriter);
                Throwable cause = e.getCause();
                while (cause != null) {
                    cause.printStackTrace(printWriter);
                    cause = cause.getCause();
                }
                printWriter.close();
                //異常的詳細內容
                String result = writer.toString();
                Log.e(TAG, result);
            }
        }
    }
}

2、IDownLoadCallBack源碼,這裏的泛型主要是因爲和公司一些業務有關,這裏沒有列出來,這裏的泛型其實可以去掉的,因爲基本這裏沒什麼用的,T 都改成 DownLoadEntity實例即可

public interface IDownLoadCallBack<T> {
    /**
     * @param key     下載的文件的標識,主要用於顯示的時候辨別是哪個文件在操作,由使用的人去定義
     * @param precent 已經下載的百分比 取值區間爲 [0,100]
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onNext(String key, int precent);

    /**
     * @param t            下載的文件的實體封裝類
     * @param key          下載的文件的標識,主要用於顯示的時候辨別是哪個文件在操作,由使用的人去定義
     * @param precent      已經下載的百分比
     * @param downLoadSize 已經下載的長度
     * @param total        資源的總長度
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 10:48
     */
    public abstract void onPause(T t, String key, int precent, long downLoadSize, long total);

    /**
     * @Description 刪除文件回調
     * @author 姚旭民
     * @date 2018/11/22 10:47
     *
     * @param t 操作的下載對象
     * @param key 文件憑證
     */
    public abstract void onDelete(T t, String key);

    /**
     * @param t   自定義的值
     * @param key 下載的文件的標識,主要用於顯示的時候辨別是哪個文件在操作,由使用的人去定義
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onSuccess(T t, String key);

    /**
     * @param t      自定義的值
     * @param key    下載的文件的標識,主要用於顯示的時候辨別是哪個文件在操作,由使用的人去定義
     * @param reason 失敗原因
     * @Description
     * @author 姚旭民
     * @date 2018/11/20 9:46
     */
    public abstract void onFail(T t, String key, String reason);

3、IDownLoadCallBack包裝類繼承,包裝類用於包裝泛型對象,其實這一步可以不要的,只是有點別的考慮,所以這樣寫

/**
 * @Description 包裝類
 * @author 姚旭民
 * @date 2018/11/20 13:57
 */
public interface IResumeCallBack extends IDownLoadCallBack<ResumeEntity> {
}

4、ResumeEntity對象源碼主要繼承了DownLoadEntity(第5點),其他沒什麼的

public class ResumeEntity extends DownLoadEntity {
    public static enum STATUS {
        FAIL(-1),//下載失敗
        DOWNLOAD(0),//下載中
        SUCCESS(1);//下載成功,可以使用
        private int value;

        private STATUS(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    }

    ResumeEntity(builder builder) {
        this.fileName = builder.fileName;
        this.downUrl = builder.downUrl;
        this.savePath = builder.savePath;
        this.total = builder.total;
        this.progress = builder.progress;
        this.status = builder.status;
        this.key = builder.key;
    }

    //鏈式編程,防止對象不一致,用static修飾,避免被保留強引用
    public static class builder {
        private String fileName;
        private String downUrl;
        private String savePath;
        private long total;
        private long progress;
        private int status;
        private boolean stop;
        private String key;

        public builder fileName(String fileName) {
            this.fileName = fileName;
            return this;
        }

        public builder downUrl(String downUrl) {
            this.downUrl = downUrl;
            return this;
        }

        public builder savePath(String savePath) {
            this.savePath = savePath;
            return this;
        }

        public builder total(long total) {
            this.total = total;
            return this;
        }

        public builder progress(long progress) {
            this.progress = progress;
            return this;
        }

        public builder status(int status) {
            this.status = status;
            return this;
        }

        public builder stop(boolean stop) {
            this.stop = stop;
            return this;
        }

        public builder key(String key) {
            this.key = key;
            return this;
        }

        public ResumeEntity builder() {
            return new ResumeEntity(this);
        }
    }

    @Override
    public String toString() {
        return "{" +
                "fileName='" + fileName + '\'' +
                ", downUrl='" + downUrl + '\'' +
                ", savePath='" + savePath + '\'' +
                ", total=" + total +
                ", progress=" + progress +
                ", status=" + status +
                ", stop=" + stop +
                ", key='" + key + '\'' +
                '}';
    }
}

5、DownLoadEntity源碼區域

public class DownLoadEntity {
    //資源文件的名稱
    protected String fileName;
    //資源文件的下載路徑
    protected String downUrl;
    //資源文件的保存完整路徑
    protected String savePath;
    //下載的資源的總長度
    protected long total;
    //已經下載的進度
    protected long progress;
    //資源的狀態 //下載的狀況 1爲下載成功,0爲可下載, -1爲下載失敗 默認爲0
    protected int status;
    //是否暫停下載 true爲暫停下載, false代表可以下載, 默認爲false
    protected boolean stop;
    //下載的文件的標識,讓使用者更加靈活的去定義如何識別正在下載的文件
    protected String key;
    //是否刪除下載的文件
    protected boolean delete;

    //這裏是各種set和get,不花費篇幅粘貼了,直接用工具生成就好了
}

6、IDownLoadCallBack的實現類,我是不想每次都創建一個匿名類了,太長了也繁瑣,我直接用activity去實現IDownLoadCallBack,感覺也挺好的,這裏是隨便寫的activity,主要用來測試的,UI界面源碼在第7點

public class MainActivity extends AppCompatActivity implements View.OnClickListener, IResumeCallBack, INetCallBack {
    private static final String TAG = "MainActivity";
    //數據庫操作輔助類
    private ResumeDbHelper mHelper = ResumeDbHelper.getInstance();
    private ResumeService mResumeService = ResumeService.getInstance();
    private MainActivity mInstance = this;

    private Button downloadBtn1, downloadBtn2, downloadBtn3;
    private Button pauseBtn1, pauseBtn2, pauseBtn3;
    private Button cancelBtn1, cancelBtn2, cancelBtn3;
    private ProgressBar mProgress1, mProgress2, mProgress3;
    private String url1 = "http://192.168.1.103/2.bmp";
    private String url2 = "http://192.168.1.103/testzip.zip";
    private String url3 = "http://192.168.1.103/photo.png";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        try {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);

            NetReceiver.setCallBack(this);
            downloadBtn1 = bindView(R.id.main_btn_down1);
            downloadBtn2 = bindView(R.id.main_btn_down2);
            downloadBtn3 = bindView(R.id.main_btn_down3);

            pauseBtn1 = bindView(R.id.main_btn_pause1);
            pauseBtn2 = bindView(R.id.main_btn_pause2);
            pauseBtn3 = bindView(R.id.main_btn_pause3);

            cancelBtn1 = bindView(R.id.main_btn_cancel1);
            cancelBtn2 = bindView(R.id.main_btn_cancel2);
            cancelBtn3 = bindView(R.id.main_btn_cancel3);

            mProgress1 = bindView(R.id.main_progress1);
            mProgress2 = bindView(R.id.main_progress2);
            mProgress3 = bindView(R.id.main_progress3);

            downloadBtn1.setOnClickListener(this);
            downloadBtn2.setOnClickListener(this);
            downloadBtn3.setOnClickListener(this);

            pauseBtn1.setOnClickListener(this);
            pauseBtn2.setOnClickListener(this);
            pauseBtn3.setOnClickListener(this);

            cancelBtn1.setOnClickListener(this);
            cancelBtn2.setOnClickListener(this);
            cancelBtn3.setOnClickListener(this);
        } catch (Exception e) {
            Log.e(TAG, e.toString());
        }
    }

    @Override
    public void onClick(View v) {
        try {
            switch (v.getId()) {
                case R.id.main_btn_down1:
                    Log.d(TAG, "點擊了下載1,url1:" + url1);
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            mResumeService.download("1", url1, FileConts.IMG_PATH, mInstance);
                        }
                    });
                    break;
                case R.id.main_btn_down2:
                    Log.d(TAG, "點擊了下載2");
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            mResumeService.download("2", url2, FileConts.IMG_PATH, mInstance);
                        }
                    });
                    break;
                case R.id.main_btn_down3:
                    Log.d(TAG, "點擊了下載3");

                    break;

                case R.id.main_btn_pause1:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "點擊暫停1");
                            ResumeService.getInstance().stop("1");
                        }
                    });
                    break;
                case R.id.main_btn_pause2:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            Log.v(TAG, "點擊暫停2");
                            ResumeService.getInstance().stop("2");
                        }
                    });
                    break;
                case R.id.main_btn_pause3:
//                ResumeService.getInstance().cancel(url3);
                    break;

                case R.id.main_btn_cancel1:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            ResumeService.getInstance().remove(url1, "1");
                        }
                    });
                    break;
                case R.id.main_btn_cancel2:
                    ThreadUtils.exeMgThread3(new Runnable() {
                        @Override
                        public void run() {
                            ResumeService.getInstance().remove(url2, "2");
                        }
                    });
                    break;
                case R.id.main_btn_cancel3:
//                ResumeService.getInstance().cancel(url3);
                    break;
            }
        } catch (Exception e) {
            StringBuffer sb = new StringBuffer();
            Writer writer = new StringWriter();
            PrintWriter printWriter = new PrintWriter(writer);
            e.printStackTrace(printWriter);
            Throwable cause = e.getCause();
            while (cause != null) {
                cause.printStackTrace(printWriter);
                cause = cause.getCause();
            }
            printWriter.close();
            //異常的詳細內容
            String result = writer.toString();
            Log.e(TAG, result);
        }
    }

    private <T extends View> T bindView(@IdRes int id) {
        View viewById = findViewById(id);
        return (T) viewById;
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        Log.v(TAG, "onDestroy");
    }

//IDownLoadCallBack 接口 的各種回調 事件 開始
    //下載的進度回調
    @Override
    public void onNext(String key, int precent) {
        if ("1".equals(key)) {
            mProgress1.setMax(100);
            mProgress1.setProgress(precent);
        } else if ("2".equals(key)) {
            mProgress2.setMax(100);
            mProgress2.setProgress(precent);
        }
    }

    //下載的停止回調,同時會將暫停狀態保存進入數據庫
    @Override
    public void onPause(ResumeEntity resumeEntity, String key, int precent, long downLoadSize, long total) {
        Log.v(TAG, "onNext| 下載 暫停 回調方法,resumeEntity:" + resumeEntity);
        mHelper.update(resumeEntity);
    }

    //刪除文件回調
    @Override
    public void onDelete(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onDelete| 下載 刪除 回調方法,resumeEntity:" + resumeEntity);
        mHelper.delete(resumeEntity.getFileName());
    }

    //下載成功的回調
    @Override
    public void onSuccess(ResumeEntity resumeEntity, String key) {
        Log.v(TAG, "onNext| 下載 成功 回調方法,resumeEntity:" + resumeEntity);
        resumeEntity.setStatus(ResumeEntity.STATUS.SUCCESS.getValue());
        mHelper.update(resumeEntity);
    }

    //下載失敗的回調
    @Override
    public void onFail(ResumeEntity resumeEntity, String key, String reason) {
        Log.v(TAG, "onFail| 下載 失敗 回調方法,resumeEntity:" + resumeEntity + ",reason:" + reason);
        resumeEntity.setStatus(ResumeEntity.STATUS.FAIL.getValue());
        mHelper.update(resumeEntity);
    }
    //IDownLoadCallBack 接口 的各種回調 事件 結束

    //網絡狀態回調區域,這裏是我用來接着編寫網絡重連之後繼續下載的東西的
    public void onStatusChange(String msg) {
        Log.v(TAG, "onStatusChange| 網絡狀態回調,內容爲:" + msg);
    }
}

7、UI界面

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    tools:context="com.yxm.resume.activity.MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress1"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:progressDrawable="@drawable/progressbar" /> 進度條樣式在第8點

        <Button
            android:id="@+id/main_btn_down1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下載1" />

        <Button
            android:id="@+id/main_btn_pause1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暫停1" />

        <Button
            android:id="@+id/main_btn_cancel1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消1" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress2"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <Button
            android:id="@+id/main_btn_down2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下載2" />

        <Button
            android:id="@+id/main_btn_pause2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暫停2" />

        <Button
            android:id="@+id/main_btn_cancel2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消2" />
    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <ProgressBar
            android:id="@+id/main_progress3"
            style="@style/Widget.AppCompat.ProgressBar.Horizontal"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1" />

        <Button
            android:id="@+id/main_btn_down3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下載3" />

        <Button
            android:id="@+id/main_btn_pause3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="暫停3" />

        <Button
            android:id="@+id/main_btn_cancel3"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="取消3" />
    </LinearLayout>
</LinearLayout>

8、進度條樣式

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" >

    <item android:id="@android:id/background">

        <shape>

            <corners android:radius="5dip" />

            <gradient
                android:angle="0"
                android:centerColor="#ff5a5d5a"
                android:centerY="0.75"
                android:endColor="#ff747674"
                android:startColor="#ff9d9e9d" />
        </shape>
    </item>

    <item android:id="@android:id/secondaryProgress">

        <clip>

            <shape>

                <corners android:radius="5dip" />

                <gradient
                    android:angle="0"
                    android:centerColor="#80ffb600"
                    android:centerY="0.75"
                    android:endColor="#a0ffcb00"
                    android:startColor="#80ffd300" />
            </shape>
        </clip>
    </item>

    <item android:id="@android:id/progress">

        <clip>

            <shape>

                <corners android:radius="5dip" />

                <gradient
                    android:angle="0"
                    android:endColor="#8000ff00"
                    android:startColor="#80ff0000" />
            </shape>
        </clip>
    </item>

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