• 故宫文创产品在韩国展出 2019-05-26
  • 明清家具风格差异(知闻) 2019-05-26
  • 【每日最陕西】NO.1449 西安幼升小反映问题是去年近3倍 上学难层出不穷 2019-05-26
  • 美国发起贸易战,我们要让世界知道美元、美债并不可靠 2019-05-25
  • 新一轮中美北京经贸谈判释放了什么信号 2019-05-25
  • 您访问的页面找不回来了 2019-05-25
  • 小区麻将馆 何时不扰民 2019-05-24
  • 端午节期间济南增开20.5对热门方向列车 2019-05-24
  • 借“谈心”暗示罪犯行贿!“80后”监狱指导员沦为阶下囚 2019-05-23
  • 你读过的教科书没有给你讲过共产主义,所以你也不知道共产主义社会的核心内容是什么。[微笑][微笑] 2019-05-23
  • 电子商务法草案三审:微商纳入经营者范围,个人二手转让不算 2019-05-23
  • 2018年东南大学将继续推进“金钥匙计划”与国际化办学 2019-05-23
  • 前所未见的“苏州雪景”大片 怎一个美字了得 2019-05-22
  • 淮北市:“智慧城管+”综合治理新模式 2019-05-22
  • 新科欧洲状元 五盾军团能否延续法兰西之夏荣耀 2019-05-22
  • 六合图库财神 >Android相关

    不使用第三方框架编写的多线程断线续传功能

    2018-12-05 15:04 编辑: 米米狗 分类:Android相关 来源:开源中国

    一、背景

    最近需要个断线续传功能,但是觉得一些框架不太适合,所以基于原理编写了一个多线程断线续传功能

    支持技术分享,但是复制和转发我的博客时候请标明出处,谢谢 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.OnClickListenerIResumeCallBackINetCallBack {
        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 = "//192.168.1.103/2.bmp";
        private String url2 = "//192.168.1.103/testzip.zip";
        private String url3 = "//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  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界面


    <LinearLayout xmlns:android="//schemas.android.com/apk/res/android"
        xmlns:tools="//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="//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>
    搜索CocoaChina微信公众号:CocoaChina
    微信扫一扫
    订阅每日移动开发及APP推广热点资讯
    公众号:
    CocoaChina
    我要投稿   收藏文章
    上一篇:Android里应用程序,应用程序窗口和视图对象之间的关系
    下一篇:安卓后台?;詈诳萍?播放无声音乐

    相关资讯

    我来说两句
    发表评论
    您还没有登录!请登录注册
    所有评论(0

    综合评论

    相关帖子

    sina weixin mail 回到顶部
  • 故宫文创产品在韩国展出 2019-05-26
  • 明清家具风格差异(知闻) 2019-05-26
  • 【每日最陕西】NO.1449 西安幼升小反映问题是去年近3倍 上学难层出不穷 2019-05-26
  • 美国发起贸易战,我们要让世界知道美元、美债并不可靠 2019-05-25
  • 新一轮中美北京经贸谈判释放了什么信号 2019-05-25
  • 您访问的页面找不回来了 2019-05-25
  • 小区麻将馆 何时不扰民 2019-05-24
  • 端午节期间济南增开20.5对热门方向列车 2019-05-24
  • 借“谈心”暗示罪犯行贿!“80后”监狱指导员沦为阶下囚 2019-05-23
  • 你读过的教科书没有给你讲过共产主义,所以你也不知道共产主义社会的核心内容是什么。[微笑][微笑] 2019-05-23
  • 电子商务法草案三审:微商纳入经营者范围,个人二手转让不算 2019-05-23
  • 2018年东南大学将继续推进“金钥匙计划”与国际化办学 2019-05-23
  • 前所未见的“苏州雪景”大片 怎一个美字了得 2019-05-22
  • 淮北市:“智慧城管+”综合治理新模式 2019-05-22
  • 新科欧洲状元 五盾军团能否延续法兰西之夏荣耀 2019-05-22