自己定义进度条PictureProgressBar——从开发到开源公布全过程

出处

炎之铠邮箱:yanzhikai_yjk@qq.com

本文原创。转载请注明本出处!

本项目JCenter地址:https://bintray.com/yanzhikaijky/CustomViewRepository/PictureProgressbar/

本项目GitHub地址:https://github.com/totond/PictureProgressBar

欢迎 Star or Fork和在Issue里提出意见建议。

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家公布

前言

  上一篇文章掌握了ProgressBar的自己定义样式和它的扩展ProgressDialog。可是没有进行封装,这一次就继承View从零開始做了一个自己定义进度条——PictureProgressBar,并公布到Github和JCenter上,以下就開始一步一步介绍这个过程。

PS:JCenter是一个Android的代码库,把代码放上去,就能够在AS项目里的Gradle文件中compile ‘xxx’这样来引入你的代码了。

本文涉及到:

  • 一个继承自View的自己定义ProgressBar实现全过程
  • 一个项目开源的全过程:使用AndroidStudio上传代码到GitHub、JCenter的过程,加入开源协议的过程等。

实现

  PictureProgressBar是一个能够带图片和动画效果的进度条,能够先看看它的效果,例如以下图:

  实现的逻辑并不复杂,看看流程图:



  基本的逻辑是在onDraw()方法实现,里面大量利用到Canvas。Canvas的使用能够參考下我曾经这篇笔记。

1.初始化属性

  由于前面的属性定义太多了,所以这里不列出来,后面要用到的属性会有介绍,想详细了解的能够看GitHub的介绍文档,那里有个表详细介绍。

这里定义初始化方法,用来配置画笔和设置Gradient渐变器,由于Gradient须要进度条的宽高。所以要在Measure过程之后才配置:

  1. //初始化
  2. private void init() {
  3. //初始化画笔
  4. paintPicture = new Paint();
  5. paintBackGround = new Paint();
  6. paintBackGround.setColor(backGroundColor);
  7. paintBar = new Paint();
  8. paintBar.setColor(barColor);
  9. if (isGradient) {
  10. //在PreDraw时获取View属性,由于在初始化的时候View还没进行Measure
  11. getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
  12. @Override
  13. public boolean onPreDraw() {
  14. getViewTreeObserver().removeOnPreDrawListener(this);
  15. linearGradient = new LinearGradient(0, progressHeight / 2, progressWidth, progressHeight / 2, gradientStartColor, gradientEndColor, Shader.TileMode.CLAMP);
  16. paintBar.setShader(linearGradient);
  17. return false;
  18. }
  19. });
  20. }
  21. }

2.画进度条

  首先就是要画好进度条,Android源代码自带的ProgressBar是基于事件机制来刷新View的,也就是每当有进度改变才会调用刷新View的方法,可是由于我这里要实现动画并且对怎么实现事件机制不是非常熟,所以採用了定时刷新的方法,先把进度条画出来:

  1. //画进度条
  2. private void drawBar(Canvas canvas){
  3. if (isRound) {
  4. //画圆角矩形
  5. rectFBG.set(0, y - progressHeight / 2 + progressHeightOffset,
  6. progressWidth, y + progressHeight / 2 + progressHeightOffset);
  7. canvas.drawRoundRect(rectFBG, roundX, roundY, paintBackGround);
  8. rectFPB.set(0, y - progressHeight / 2 + progressHeightOffset,
  9. x, y + progressHeight / 2 + progressHeightOffset);
  10. canvas.drawRoundRect(rectFPB, roundX, roundY, paintBar);
  11. } else {
  12. //画矩形
  13. rectFBG.set(0, 0, getWidth(), getHeight());
  14. canvas.drawRect(rectFBG, paintBackGround);
  15. canvas.drawRect(0, 0, x, getHeight(), paintBar);
  16. }
  17. }

  简单说一下上面的一些属性:

- isRound是决定进度条是否圆角的boolean变量,由于认为不是圆角的进度条有点难看,所以就终于公布时默认初始设置是true。

- progressWidth和progressHeight是进度条的宽高。而不是整个View的宽高,由于View是包含进度条和图片,要为图片的显示预留空间。所以进度条宽高会在onMeasure()依据属性设置来定义大小(详细怎么定义后面说)。

- x和y是当前进度的中心点坐标位置。

- progressHeightOffset是进度条的所处高度偏移量。负数为向上偏移,正数为向下偏移。

前面就说过progressHeight是进度条的宽高不一定是整个View的宽高,所以进度条能够处于一个自己定义的位置(眼下仅仅是高度。由于一般都不用设置宽度)。详细的效果能够看前面demo效果的第一个,进度条就是向下偏移而实现了被可爱的丘比龙踩在脚下的效果。

3.绘图片Drawable

  接下来是绘图片Drawable的方法,这个Drawable能够是图片或者是Shape,依据当前进度的中心点坐标x、y和图片的半宽高属性halfDrawableWidth、halfDrawableHeight来实现,当中drawableHeightOffset是图片的高度偏移量:

  1. //绘图
  2. private void drawPicture(Canvas canvas) {
  3. if (drawable == null && animMode != ANIM_NULL){
  4. Log.e(TAG,"drawable is null");
  5. return;
  6. }
  7. drawable.setBounds(x - halfDrawableWidth,
  8. getHeight() / 2 - halfDrawableHeight + drawableHeightOffset,
  9. x + halfDrawableWidth,
  10. getHeight() / 2 + halfDrawableHeight + drawableHeightOffset);
  11. drawable.draw(canvas);
  12. }

4.画动画:

  先对是否开启动画,当前动画模式做出推断,实现5个动画模式:

animMode模式 意义
ANIM_NULL 无动画模式
ANIM_ROTATE 旋转动画模式
ANIM_SCALE 缩放动画模式
ANIM_ROTATE_SCALE 旋转加缩放动画模式
ANIM_FRAME 帧动画模式
  1. //画动画
  2. private void drawAnimPicture(Canvas canvas) {
  3. if (isAnimRun) {
  4. switch (animMode) {
  5. case ANIM_NULL:
  6. drawPicture(canvas);
  7. break;
  8. case ANIM_ROTATE:
  9. rotateCanvas(canvas);
  10. drawPicture(canvas);
  11. break;
  12. case ANIM_SCALE:
  13. scaleCanvas(canvas);
  14. drawPicture(canvas);
  15. break;
  16. case ANIM_ROTATE_SCALE:
  17. rotateCanvas(canvas);
  18. scaleCanvas(canvas);
  19. drawPicture(canvas);
  20. break;
  21. case ANIM_FRAME:
  22. drawable = getResources().getDrawable(drawableIds[frameIndex]);
  23. drawPicture(canvas);
  24. if (frameIndex >= drawableIds.length - 1){
  25. frameIndex = 0;
  26. }else {
  27. frameIndex++;
  28. }
  29. break;
  30. }
  31. } else {
  32. drawPicture(canvas);
  33. }
  34. }

  实现帧动画是通过轮播图片的方法实现。

  实现旋转,缩放的效果。是採用操纵画布Canvas的方法来实现:

  1. //旋转画布
  2. private void rotateCanvas(Canvas canvas) {
  3. canvas.rotate(rotateDegree % 360, x, y + drawableHeightOffset);
  4. rotateDegree += rotateRate;
  5. }
  6. //伸缩画布
  7. private void scaleCanvas(Canvas canvas) {
  8. if (scaleLevel >= scaleMax) {
  9. isScaleIncrease = false;
  10. } else if (scaleLevel <= scaleMin) {
  11. isScaleIncrease = true;
  12. }
  13. if (isScaleIncrease) {
  14. scaleLevel += scaleRate;
  15. } else {
  16. scaleLevel -= scaleRate;
  17. }
  18. canvas.scale(scaleLevel, scaleLevel, x, y + drawableHeightOffset);
  19. }

  由于drawAnimPicture()方法之后并没有其它使用Canvas的方法了。所以这里不用Canvas.save()和Canvas.restore()来使Canvas恢复到初始状态了,这里说明一下。免得后面有功能拓展的须要加代码时候忘了。

5.重写onMeasure()

  重写onMeasure()的意义:让View支持wrap_content。还有设置了进度条的宽高(前面说过,进度条的宽高不一定等于整个View的宽高):

  1. //重写onMeasure,以自己定义获取进度条的宽高
  2. @Override
  3. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
  4. int height = MeasureSpec.getSize(heightMeasureSpec);
  5. int width = MeasureSpec.getSize(widthMeasureSpec);
  6. if (getLayoutParams().width == ViewGroup.LayoutParams.WRAP_CONTENT) {
  7. //在这里实现计算须要wrap_content时须要的宽
  8. width = halfDrawableWidth * 2;
  9. }
  10. if (getLayoutParams().height == ViewGroup.LayoutParams.WRAP_CONTENT) {
  11. //在这里实现计算须要wrap_content时须要的高
  12. height = halfDrawableHeight * 2;
  13. }
  14. progressWidth = width;
  15. //假设不是自己定义设置进度条高度。就直接把高度当作进度条高度
  16. if (!isSetBar) {
  17. progressHeight = height;
  18. }
  19. //假设有图片。就为图片预留空间
  20. if (drawable != null) {
  21. progressWidth = width - halfDrawableWidth;
  22. }
  23. //传入处理后的宽高
  24. setMeasuredDimension(width, height);
  25. }

6.封装

自己定义属性

  为了让自己定义View的属性能直接通过XML设置,须要用到自己定义属性,在res/value文件夹里新建一个attrs.xml(名字随便,建立位置对即可),定义自己所需的属性和对应类型:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3. <declare-styleable name="PictureProgressBar">
  4. <attr name="backGroundColor" format="color"/>
  5. <attr name="barColor" format="color"/>
  6. <attr name="drawable" format="reference"/>
  7. <attr name="halfDrawableWidth" format="dimension"/>
  8. <attr name="halfDrawableHeight" format="dimension"/>
  9. <attr name="drawableHeightOffset" format="dimension"/>
  10. <attr name="isRound" format="boolean"/>
  11. <attr name="roundX" format="dimension"/>
  12. <attr name="roundY" format="dimension"/>
  13. <attr name="progress" format="integer"/>
  14. <attr name="max" format="integer"/>
  15. <attr name="isSetBar" format="boolean"/>
  16. <attr name="progressHeight" format="dimension"/>
  17. <attr name="progressHeightOffset" format="dimension"/>
  18. <attr name="refreshTime" format="integer"/>
  19. <attr name="animMode" format="enum">
  20. <enum name="ANIM_NULL" value="0"/>
  21. <enum name="ANIM_ROTATE" value="1"/>
  22. <enum name="ANIM_SCALE" value="2"/>
  23. <enum name="ANIM_ROTATE_SCALE" value="3"/>
  24. <enum name="ANIM_FRAME" value="4"/>
  25. </attr>
  26. <attr name="rotateRate" format="integer"/>
  27. <attr name="rotateDegree" format="integer"/>
  28. <attr name="scaleMax" format="float"/>
  29. <attr name="scaleMin" format="float"/>
  30. <attr name="scaleRate" format="float"/>
  31. <attr name="isGradient" format="boolean"/>
  32. <attr name="gradientStartColor" format="color"/>
  33. <attr name="gradientEndColor" format="color"/>
  34. </declare-styleable>
  35. </resources>

一些set、get方法和其它

  有了自己定义属性,仅仅是能在XML上使用。想要在Java代码上设置属性,还须要弄一些set、get方法,另一些特殊的属性,在xml设置不了,如帧动画的图片id数组、线性渐变器、进度监听器等,也须要set方法,由于有太多。以下仅仅列举一些特殊的出来:


  1. //设置进度
  2. public void setProgress(int progress) {
  3. if (progress <= max) {
  4. this.progress = progress;
  5. } else if (progress < 0){
  6. this.progress = 0;
  7. }
  8. else {
  9. this.progress = max;
  10. }
  11. doProgressRefresh();
  12. }
  13. //进行进度改变之后的操作
  14. private synchronized void doProgressRefresh() {
  15. if (onProgressChangeListener != null) {
  16. onProgressChangeListener.onOnProgressChange(progress);
  17. if (progress >= max) {
  18. onProgressChangeListener.onOnProgressFinish();
  19. }
  20. }
  21. }
  22. //设置动画开关
  23. public void setAnimRun(boolean isAnimRun) {
  24. this.isAnimRun = isAnimRun;
  25. }
  26. //设置帧动画时要传入的图片ID数组
  27. public void setDrawableIds(int[] drawableIds) {
  28. this.drawableIds = drawableIds;
  29. }
  30. //设置图片
  31. public void setPicture(int id) {
  32. drawable = getResources().getDrawable(id);
  33. }
  34. //设置颜色渐变器
  35. public void setLinearGradient(LinearGradient linearGradient) {
  36. this.linearGradient = linearGradient;
  37. }
  38. //设置进度监听器
  39. public void setOnProgressChangeListener(OnProgressChangeListener onProgressChangeListener) {
  40. this.onProgressChangeListener = onProgressChangeListener;
  41. }
  42. //进度监听器
  43. public interface OnProgressChangeListener {
  44. //进度改变时的回调
  45. public void onOnProgressChange(int progress);
  46. //进度完毕时的回答
  47. public void onOnProgressFinish();
  48. }

7.公布

公布到GitHub

  这个我相信大家都非常熟悉,我这里就简单的列出一下我把这个项目上传到GitHub(我是使用AndroidStudio上自带的功能。简单方便)的步骤吧:

1. 首先是本地要安装git。没有的能够下载

2. 在AndroidStudio的File的Setting选项配置Git信息



3. 在Setting配置你的GitHub账号信息,没有的能够注冊,那里有button(我这个是AndroidStudio2.2版本号。不同的版本号可能里面的内容有一点不同),输入完账号password按Test连接一下,失败的确认好账号password多试几次。把timeout设置长点



4. 在VCS里点击例如以下(一个项目第一次提交时用这个),然后按提示操作commit即可了(由于我这个不是第一次提交就不演示下去了):



5. 后面更新项目就是点击Commitbutton了,然后push了:





  事实上到了第4步,在GitHub上就能看到我们的项目了(免得太多图就不贴了):https://github.com/totond/PictureProgressBar

公布到JCenter

  我们开发有时候会用到一些第三方库,有一些库非常方便,在AS的Gradle里面compile ‘xxx’就能够引入了,我们把项目提交到JCenter,也能够让别人非常方便地引入了。

公布到JCenter有非常多方法,本方法是參考鸿洋_大神的bintray-release插件方法,之后亲自试了非常多坑之后才成功的(鸿洋大神这篇方法有非常多细节没说,详细看它以下的评论)。能够说依据我这个步骤是肯定能成功的,以下我们来看看步骤:

1. 首先是在 https://bintray.com/signup/oss 进行注冊,不要直接在官网注冊,那是注冊企业试用版的。到时候你还要自己点击右上角的倒计时取消企业版切换回个人版。

注意注冊的邮箱不能是qq邮箱。163邮箱(不知道是不是全部中国的都不行)。

2. 注冊完之后首先就是查看自己的API,复制下来备用。后面的上传都要用到:



3. 建立一个仓库,用来装代码(填好Name,里面的Type选择Maven,其它的都不用管了,点Create即可):





4. 新建一个Module。把想要上传的代码放进去。上传就是上传这个Module(不这样做的话会在上传的时候报Error:Could not get unknown property ‘main’ for SourceSet container.)在这个Module的Gradle文件中面加入:

  1. apply plugin: 'com.novoda.bintray-release'//最上方加入
  2. publish {
  3. userOrg = 'yanzhikaijky' //Binaryusername
  4. repoName = 'CustomViewRepository' //Repository的名字
  5. groupId = 'com.yanzhikaijky' //包名
  6. artifactId = 'PictureProgressbar' //项目名
  7. publishVersion = '1.1.0' //版本号号
  8. desc = 'a picture progressbar' //description说明,随便写
  9. website = 'https://github.com/totond/PictureProgressBar'//VCS地址,这里最好写GitHub的,我试过不写然后上传不了
  10. }

* 5. 在项目的Gradle文件中加入(弄完就build一下project):*

  1. //在dependencies里面加
  2. classpath 'com.novoda:bintray-release:0.3.4'//加入bintray-release插件
  3. //以下这个整个加进去
  4. allprojects {
  5. repositories {
  6. jcenter()
  7. }
  8. //防止中文凝视出错
  9. tasks.withType(Javadoc) {
  10. options {
  11. encoding "UTF-8"
  12. charSet 'UTF-8'
  13. links "http://docs.oracle.com/javase/7/docs/api"
  14. }
  15. }
  16. }

** 6. 在AndroidStudio的Terminal里输入这些命令(**Key里面的星号的内容实际上应该是我们的API KEY,我这里屏蔽掉而已。注意每行命令之间用一个空格隔开。这个非常重要),最后回车提交:

  1. gradlew.bat clean build bintrayUpload
  2. -PbintrayUser=yanzhikaijky
  3. -PbintrayKey=*****************************
  4. -PdryRun=false



  第一次提交会比較慢(翻墙可能会提快速度?),给点耐心,假设报错里面会显示出来。

7. 成功了之后,能够在网页上看到你的项目,左下能够看到怎样加入依赖,右下角会有一个Add To jcenter(由于这个项目早已经add了,所以借用一下鸿洋大神的图片),本项目JCenter地址:https://bintray.com/yanzhikaijky/CustomViewRepository/PictureProgressbar

8.加入开源协议和说明文档README

加入开源协议

  开源项目能够选择一个开源协议来表示自己这个项目的许可声明。关于开源协议的选择,我这里也引用阮一峰老师的一篇文章里面的一幅图来大概说明一下:



  最后我选择了Apache v2 License。

至于怎样加入,一个方法是在GitHub创建Repository的时候选择加入(这个我一開始就忘了)。第二个是使用官方推荐的 www.addalicense.com 来给当前存在的Repository加入,然而



  这东西我用不了。仅仅能採用第三种方法手动加入了:能够查看GitHub关于这个的帮助文档,看不懂英文没关系,隔壁有图说明:



  成功之后主页会多了一个表示开源协议的栏目:

加入说明文档Readme

  说明文档就是告诉别人你的开源项目怎样使用的文档,能够在Android Project的根文件夹下加入README.md文件然后Push上去或者直接在项目GitHub上Add a README。

这个就是PictureProgressBar的README.md

9.測试

  在APP的Gradle里加入依赖项之后。就能够使用了:

  1. compile 'com.yanzhikaijky:PictureProgressbar:1.1.0'

  这里的測试就是demo用了属性动画来控制进度变化(想了解属性动画的能够看下我这篇笔记),想详细看demo代码的,这里再次给出地址和效果:

后话

  欢迎大家Star or Fork,使用Gradle依赖非常方便,也能够clone来试着按自己想法改动一下,改一下gradlewrapper文件就能够直接在你们的AS上执行了,欢迎提出意见和建议。

  这个PictureProgressBar的兴许可能会加入一些其它的动画和文字。眼下没有什么好的创意。假设大家有什么创意和意见,欢迎互相交流进步。

更新

  • version 1.1.1:2017/07/07修复bug:

      加入了progressPercentage属性来表示进度条进度比例,改动了setProgress()内容,在里面加入
  1. progressPercentage = progress/max;

  防止在输入较大int数值的时候。计算进度操作导致int类型溢出的情况,例如以下载场景下的进度数值。

- version 1.1.2:2017/09/07修复bug:

  progress/max得到0的结果,醉了过了两个月才发现。。。

  • version 1.2.0:2017/09/11更新:

    • 1.修复非圆角进度条宽度设置失效问题。
    • 2.进度条左方预留空间给图片,不会出现进度0%可是还是显示有载入了部分进度的情况。
    • 3.新增进度条的图片设置,相似官方ProgressBar的图片平铺设置功能。详细效果可看README。

最新版本号

  1. compile 'com.yanzhikaijky:PictureProgressbar:1.2.0'

自己定义进度条PictureProgressBar——从开发到开源公布全过程的更多相关文章

  1. 自定义进度条PictureProgressBar——从开发到开源发布全过程

    自定义进度条PictureProgressBar——从开发到开源发布全过程 出处: 炎之铠邮箱:yanzhikai_yjk@qq.com 本文原创,转载请注明本出处! 本项目JCenter地址:htt ...

  2. Android-自定义进度条

    圆形进度条,不确定进度条: <!-- 原生圆形进度条 不确定进度条 --> <ProgressBar android:layout_width="wrap_content& ...

  3. Android开发 ---构建对话框Builder对象,消息提示框、列表对话框、单选提示框、多选提示框、日期/时间对话框、进度条对话框、自定义对话框、投影

    效果图: 1.activity_main.xml 描述: a.定义了一个消息提示框按钮 点击按钮弹出消息 b.定义了一个选择城市的输入框 点击按钮选择城市 c.定义了一个单选提示框按钮 点击按钮选择某 ...

  4. 超酷jQuery进度条加载动画集合

    在丰富多彩的网页世界中,进度条加载动画的形式非常多样,有利用gif图片实现的loading动画,也有利用jQuery和CSS3实现的进度加载动画,本文主要向大家介绍很多jQuery和CSS3实现的进度 ...

  5. Android-做个性化的进度条

    1.案例效果图 2.准备素材                                progress1.png(78*78)              progress2.png(78*78) ...

  6. 阅读《Android 从入门到精通》(17)——进度条

    进度条(ProgressBar) java.lang.Object; android.view.View; android.widget.ProgressBar; ProgressBar 类方法 Pr ...

  7. JavaScript入门学习(2)--进度条

    <html> <style type="text/css"> #bar{width:0px; height:20px; background:#ee00ff ...

  8. Tkinter 之ProgressBar进度条标签

    一.参数说明 参数 作用 cursor 鼠标位于进度条内时的形状 length 进度条长度 maximum 进度条最大刻度值 mode  进度条的模式.有两种:‘determinate’和’indet ...

  9. 简单的jquery进度条插件LineProgressbar.js,myProgress.js

    参考   http://www.lanrenzhijia.com/jquery/4121.html demo下载 <script src="js/jquery.lineProgress ...

随机推荐

  1. mysql 创建存储过程及测试sql

    --存储过程 CREATE PROCEDURE proc_batch_id( out batch_id bigint ) begin insert into generate_sync_batch ( ...

  2. selector简介

    最近在学习java NIO,发现java nio selector 相对 channel ,buffer 这两个概念是比较难理解的 ,把学习理解的东西以文字的东西记录下来,就像从内存落地到硬盘,把内存 ...

  3. Android ANR优化 1

    1, 你碰到ANR了吗 在App使用过程中, 你可能遇到过这样的情况: ANR 恭喜你, 这就是传说中的ANR. 1.1 何为ANR ANR全名Application Not Responding, ...

  4. Openshift 用户,角色和RBAC

    OCP中的权限管理沿用的Kubernetes RBAC机制,授权模式主要取决于下面几个因数 Rules 针对主要对象的操作权限,比如建立Pod Sets of permitted verbs on a ...

  5. war后缀的文件

    其实war文件就是Java中web应用程序的打包.借用一个老兄的话,"当你一个web应用程序很多的时候,如果你想把它部署到别的机器上,来回拷这些文件是件挺郁闷的事情,如果要是一个文件就好了. ...

  6. ylbtech-LanguageSamples-Porperties(属性)

    ylbtech-Microsoft-CSharpSamples:ylbtech-LanguageSamples-Porperties(属性) 1.A,示例(Sample) 返回顶部 “属性”示例 本示 ...

  7. [转]在Sql Server中将字符串分割成表格数据示例

    本文转自:http://www.lmwlove.com/ac/ID718 比如我们有一个字符串 ) select @appName ='UserID=admin,Account=ABC' 然后我们要以 ...

  8. 教你u盘中毒后如何恢复被隐藏文件

    方法1: 1)在桌面空白处单击鼠标右键,新建一个文本文档,如下图所示: 2) 然后将下列一段代码拷贝到文档中: for /f "delims=?" %%a in ('dir /a ...

  9. http://blog.csdn.net/zxl315/article/details/10830105

    http://blog.csdn.net/zxl315/article/details/10830105

  10. 安装red5 1.0.1版本Java_home不能用Java7

    安装red5     1.0.1一直出现问题,安装顺利可以过,但是一访问老是报错. 用1.0之前的版本则没有问题.好一顿折腾,查看log发现问题出在tomcat 的nio上,查询这个问题有回复说是jr ...