一、前言
转眼就到了十一国庆佳节了,看了看上次的动态,已经过去快一个月了。并不是因为自己没有坚持而中断了写文章,真的是因为这一个月公司太忙了。我参与两个项目的编写,一个是之前的直播业务需要做后台的数据打通。一个是新开发的短视频项目。真是忙的不亦乐乎,996的生活就这样持续了一个月,周天真是太累所以选择了休息。现在两个项目都相继发版了,也可以轻松几天了。就在这个时间点上,写一篇关于ProgressBar的文章吧。主要也是因为前段时间开发遇到了关于ProgressBar的一些UI问题,发现有好多细的点之前并没有掌握好,所以仔细浏览了一些文章和官方文档,决定把我掌握的一些细节在这里写出来。
二、ProgressBar的使用场景
在日常开发中,我们经常会遇到有些界面的跳转需要等待访问一个接口,等待接口响应结果回来之后才能跳转,比如说登录界面。我们通常需要在等待接口响应的过程中给用户一个提示之类的东西,这时就用到了ProgressBar。再例如我们下载一个文件,需要显示现在的进度,这个时候也可以用到ProgressBar。ProgressBar的使用场景很多,而且其样式也是五花八门。除了系统提供的几种默认样式外,我们还可以自定义ProgressBar的样式。接下来分别介绍一下系统提供的几种ProgressBar的样式以及一些自定义样式的拓展。
三、ProgressBar的几种系统样式
一、系统样式的种类:
- 1.Widget.ProgressBar.Horizontal------水平进度条样式(有精确模式和模糊模式两种类型)
- 2.Widget.ProgressBar------正常的圆形进度条(模糊模式,还有小型的和大型的)
- 3.Widget.ProgressBar.Inverse------反色的正常圆形进度条(模糊模式, 还有小型的和大型的)
- 4.Widget.DeviceDefault.ProgressBar------默认的圆形进度条(模糊模式)
- 5.Widget.Holo.ProgressBar------Holo主题风格
- 6.Widget.Material.ProgressBar------Material主题风格
(注意:这里的精确模式和模糊模式的区别在于精确模式能够准确的看到当前的进度,而模糊模式回循环的播放一个动画来标识正在等待中。另外反色模式使用与一些浅色的主题背景)
在这里,附上一张UI展示图,分别按照上述的样式的顺序来安放的ProgressBar,代码中只是单纯的引用了style的属性,并未进行其他属性的设置,其效果图如下所示:
这里需要注意的是,我是运行在Nexus5机型上。在API小于21的机型上,你会发现,Widget.DeviceDefault.ProgressBar风格的ProgressBar的颜色是浅灰色,并且无法修改这个颜色。而Widget.Material.ProgressBar风格的ProgressBar当你在XML布局中引用的时候便会提示你需要在API大于21的机型上才能生效。刚才提到了Holo和Material主题,这里可能很多朋友不太了解Holo主题,因此这里附上一篇文章链接,希望能让你了解这个主题。(Android Design 与 Holo Theme)
二、水平样式的ProgressBar的讲解
上面介绍了几种系统提供的ProgressBar风格,就当是抛砖引玉了,接下来详细讲述本篇文章题目相关的东西。而为什么要着重讲.Widget.ProgressBar.Horizontal样式的呢,因为其他几种系统提供的样式都是模糊模式,即用户只是能看到Loading的状态,但却无法知道任务的进度到底是多少,这一缺点导致现在这样的样式的使用场景越来越少,而现如今更多的产品要么采用水平样式的ProgreessBar,要么选择自定义View来实现类似圆形ProgressBar但是能显示具体进度的效果。我们前段时间的产品在发版的头两天,设计人员提出了一堆的UI上的细节问题,让我们来改。其中有一条就是在我们的等级页面,有一个水平样式的ProgressBar,要修改成颜色渐变,而且两端带有圆角效果的。接下来,我们一步一步分析,这个效果的实现方式!
1.创建一个水平样式的ProgressBar
<ProgressBar
android:id="@+id/pb_horizontal"
android:layout_width="240dp"
android:layout_height="10dp"
style="@android:style/Widget.ProgressBar.Horizontal"
android:indeterminate="false" // 为true代表为模糊模式,false代表精确模式
android:max="100" // 进度的最大值,这里我设置成100
android:progress="30"/> // 当前的进度
我们先来看一下效果图:
这就是最简单的一个精确样式的水平进度条样式。可是,这UI未免也太丑了吧,估计没有几个产品会直接采用这样的原生效果,都会在一定程度上修改一下它的UI样式。接下来,我们首先做进度条颜色上的修改。
2.修改进度条的颜色
对于ProgressBar颜色的设置,并不像其他的一些控件,直接设置color属性即可,因为一个精确的水平进度条,是有很多颜色需要设置的。首先,就是进度条的背景色,其次就是进度条的颜色,然后还有可能需要显示缓冲进度条,那么这个时候就还要设置缓冲进度条的颜色。那这么多的颜色该怎么设置呢,这时其实我们应该联想到日常开发我们对一些按钮按压状态以及选择状态时不同颜色的设置方式。没错,就是引用drawable文件进行设置。然后将创建好的drawable引用到progressDrawable属性下。
(1) 背景、进度条都不带圆角的实现方式:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置ProgressBar背景色-->
<item android:id="@android:id/background">
<shape>
<solid android:color="#f0f0f0"/>
</shape>
</item>
<!--设置ProgressBar进度条颜色-->
<item android:id="@android:id/progress">
<clip android:clipOrientation="horizontal">
<shape>
<solid android:color="#FF577B"/>
</shape>
</clip>
</item>
</layer-list>
(2)上面是不带圆角的,那我们想一下带圆角的怎么实现啊!估计很多人第一反应就是,直接在两个item的shape里面加上corners-radius就行了啊!我们来试一下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置ProgressBar背景色-->
<item android:id="@android:id/background">
<shape>
<corners android:radius="8dp"/> // 圆角
<solid android:color="#f0f0f0"/>
</shape>
</item>
<!--设置ProgressBar进度条颜色-->
<item android:id="@android:id/progress">
<clip android:clipOrientation="horizontal">
<shape>
<corners android:radius="8dp"/> // 圆角
<solid android:color="#FF577B"/>
</shape>
</clip>
</item>
</layer-list>
运行后,效果图如下:
奇怪,为什么背景都成了圆角,进度条的左侧也成了圆角,但是进度条右侧为什么不带圆角呢?其实,这就要说道clip这个属性了,它的字面意思就是剪切,我们可以理解它在剪切的过程中是按照水平或者竖直的方向直线剪切的,并不会按照圆角的方向来剪切,因此,在剪切的操作中,就把圆角剪切没了。那么到底如何实现进度条的两端都是圆角呢?
(3)进度条实现两端圆角:
为了实现进度条也是圆角的样式,我们就不能使用clip属性了,因为它只会在某一方向裁剪出直线的边界。这时,我们就需要用到scale这一根属性了。代码如下:
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置ProgressBar背景色-->
<item android:id="@android:id/background">
<shape>
<corners android:radius="8dp"/>
<solid android:color="#f0f0f0"/>
</shape>
</item>
<!--设置ProgressBar进度条颜色-->
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%"
android:drawable="@drawable/progress_color"/>
</item>
</layer-list>
这里我们将clip根属性,换成了scale根属性,并且再引用一个drawable文件,来实现进度条的颜色样式。此drawable文件的代码如下:
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="8dp"/>
<gradient android:angle="180" // 这里我设置成了渐变色,如果颜色不想渐变就将gradient属性换成solid属性。
android:startColor="#FF577B"
android:endColor="#FFC400"/>
</shape>
运行后,样式如下:
看到了么,是不是实现了进度条和背景都带圆角。哈哈哈!还有的产品要求,某一端带圆角的。我想说,现在两端带圆角和不带圆角的你都会了,还怕设置单一方向圆角的吗???直接选择设置某个角的radius就OK了!
这就是在XML设置水平进度条样式的一些方式,然后可以在代码中动态的控制progress,只需调用setProgress()方法即可。(如果产品需要加上缓冲进度条颜色,那么只需在背景和进度条颜色之间设置一个item,id为secondaryProgress即可)
四、自定义进度条
自定义进度条的方法和样式有太多种,网上的案例更是一搜一大堆。其实这个的实现很简单,当然,具体的样式还是你们的产品设计决定,到最后说白了就是实现一个自定义View。这个View中有一个显示具体进度的文本,一个显示Loading的图标或者动画就OK了。下面,我举出一个我们项目中创建的一个自定义的进度条样式。
在我们的项目中,有一个缓存视频到本地的功能,需要我们先从网络中将视频下载下来,这个过程肯定是需要时间的,因此我们在下载的过程中展示一个类似于ProgressBar的自定义View来显示下载的进度。样式如下:
有一个显示进度的文本数字,一个转圈的Loading图标,一个显示状态的文本,还有一个黑色透明的圆角背景。下面我直接上代码,看一下这个的实现方式。
(1) 控件采用继承DialogFragment的方式来实现。
/**
* 使用DialogFragment更方便的控制View的显示与隐藏,以及资源的释放,
* 使其的使用更加有规律和方便
*/
public class ProgressDialog extends DialogFragment {
TextView mProgressTv; // 显示进度的数字
ImageView mLoadingIv; // Loading图标
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setStyle(DialogFragment.STYLE_NO_TITLE, R.style.DialogStyle);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
getDialog().getWindow().requestFeature(Window.FEATURE_NO_TITLE);
getDialog().setCanceledOnTouchOutside(true);
View mRootView = inflater.inflate(R.layout.layout_progress_dialog, container, false);
mProgressTv = mRootView.findViewById(R.id.tv_progress_num);
mLoadingIv = mRootView.findViewById(R.id.iv_progress);
return mRootView;
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// 设置Loading旋转动画
RotateAnimation rotateAnimation = new RotateAnimation(0f, 359f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
rotateAnimation.setInterpolator(new LinearInterpolator());
rotateAnimation.setDuration(400L);
rotateAnimation.setRepeatMode(Animation.RESTART);
rotateAnimation.setRepeatCount(Animation.INFINITE);
mLoadingIv.startAnimation(rotateAnimation);
}
@Override
public void onStart() {
super.onStart();
Window window = getDialog().getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.dimAmount = 0.5f;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.gravity = Gravity.CENTER;
window.setAttributes(params);
}
//IllegalStateException : Can not perform this action after onSaveInstanceSate
@Override
public void show(FragmentManager manager, String tag) {
try {
super.show(manager, tag);
} catch (IllegalStateException ignore) {
}
}
/**
* 初始化ProgressDialog实例
* @return
*/
public static ProgressDialog create() {
ProgressDialog dialog = new ProgressDialog();
return dialog;
}
/**
* 显示
* @param manger
* @return
*/
public ProgressDialog show(FragmentManager manger) {
show(manger, this.getTag());
return this;
}
/**
* 设置当前的进度
* @param progress
*/
public void setProgress(int progress) {
mProgressTv.setText(String.valueOf(progress));
}
}
(2) XML的布局文件
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="120dp"
android:layout_height="100dp"
android:layout_gravity="center"
android:background="@drawable/bg_progress_dialog" // 在drawable文件夹中定义
android:gravity="center"
android:orientation="vertical">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center">
<ImageView
android:id="@+id/iv_progress"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_loading" />
<TextView
android:id="@+id/tv_progress_num"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:textColor="#FFFFFF"
android:textSize="8sp"
android:text="66" />
</FrameLayout>
<TextView
android:id="@+id/tv_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="8dp"
android:textColor="#FFFFFF"
android:textSize="12sp"
android:text="Loading"/>
</LinearLayout>
</FrameLayout>
(3) DialogFragment的Style样式在res下的value文件夹下面创建styles_dialog文件, 在里面创建其style。(这个style直接在style文件内创建是无法被DialogFragment引用的)。好人做到底,上图更直接了当,省着有的朋友到时候不知所云。
代码如下:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!--android:windowBackground 后面的背景。background前置的背景-->
<style name="DialogStyle" parent="@android:style/Theme.Dialog">
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:background">@android:color/transparent</item>
<item name="android:windowFullscreen">true</item>
<item name="android:backgroundDimEnabled">true</item>
</style>
</resources>
以上就是一个简单的自定义进度条的实现方式,很简单的吧。其实,这个东西100个产品中可能会出现99个样式,所以,开发时只需要根据自身的产品需求创建相应的样式就行了,实现的方式都是差不多,并不是很难的。