toperfect 2013-01-21
做过Android开发的同学应该都不会对这个控件陌生。主要是用来实现处理或加载进度的显示或者提示用户正在处理或加载数据。
基本来说就两种情况,一种是转圈的小菊花,一种是水平的进度条。
默认情况下ProgressBar是圆形的那种,如果你要设置成水平状的,需要加入style
style="?android:attr/progressBarStyleHorizontal"
这两种形式的默认效果都不是很理想,个人认为Google的UI真的是丑到家了,不知道3.0之前是不是没有请UI呢?开个玩笑...
下面我们来看看ProgressBar中的style。通过style来简单分析下ProgressBar
首先在你的工程的res/values/styles.xml文件中加入下面的代码:
<style name="test" parent="@android:style/Widget.ProgressBar"> </style>
这只是为了方便你查看源码而已(我使用的是Eclipse,鼠标移到Widget.ProgressBar上,按下ctrl键即可),如果你的IDE看不到源码,也没有关系,你可以直接到源码的目录下找到这个style(比如我的sdk目录):
E:\androidSDK\sdk\platforms\android-7\data\res\values\styles.xml
然后找到“Widget.ProgressBar”即可:
<style name="Widget.ProgressBar"> <item name="android:indeterminateOnly">true</item> <item name="android:indeterminateDrawable">@android:drawable/progress_medium_white</item> <item name="android:indeterminateBehavior">repeat</item> <item name="android:indeterminateDuration">3500</item> <item name="android:minWidth">48dip</item> <item name="android:maxWidth">48dip</item> <item name="android:minHeight">48dip</item> <item name="android:maxHeight">48dip</item> </style> <style name="Widget.ProgressBar.Horizontal"> <item name="android:indeterminateOnly">false</item> <item name="android:progressDrawable">@android:drawable/progress_horizontal</item> <item name="android:indeterminateDrawable">@android:drawable/progress_indeterminate_horizontal</item> <item name="android:minHeight">20dip</item> <item name="android:maxHeight">20dip</item> </style>
我把其中的Widget.ProgressBar和Widget.ProgressBar.Horizontal拿出来给大家看看。
indeterminate意思是“模糊的,不明确的”,而 android:indeterminateOnly这个属性如果设置为true,表示的是这个ProgressBar是模糊的,不明确的,也就是说,当前它并没有体现出具体的进度,只是一个小菊花在转(Widget.ProgressBar默认这个属性设置为true),对于水平ProgressBar的话,如果设置为true,则出现一个默认的加载的动画,即android:indeterminateDrawable中设置的
progress_indeterminate_horizonta.xml:
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/progressbar_indeterminate1" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate2" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate3" android:duration="200" /> </animation-list>
这里面就是 3张这样的图片,在循环播放:
在布局文件中设置:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" tools:context=".MainActivity" android:background="#ffffff" > <!-- 圆形的progressBar --> <ProgressBar android:id="@+id/pb_circle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dip" android:layout_centerHorizontal="true" /> <!-- 水平的progressBar android:indeterminateOnly="true"--> <ProgressBar android:id="@+id/pb_horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_below="@+id/pb_circle" android:layout_marginTop="30dip" style="?android:attr/progressBarStyleHorizontal" android:progress="50" android:secondaryProgress="60" android:minWidth="200dip" android:indeterminateOnly="true" /> </RelativeLayout>
效果如下:
上面的水平ProgressBar中还设置了android:progress="50"和android:secondaryProgress="60"这两个属性,但是不起作用。这是因为我们设置了android:indeterminateOnly="true"
这个时候我们通常会想到在java代码中去将这个值设置为false,然后再设置进度progress,可是我并没有发现有这样的方法,查阅了sdk文档也没有发现,后来发现了有人利用了反射可已将源码中的mOnlyIndeterminate字段设置为false,这就达到了我们的目的了:
BeanUtils.java:
public class BeanUtils { private BeanUtils() { } /** * 直接设置对象属性值,无视private/protected修饰符,不经过setter函数. */ public static void setFieldValue(final Object object, final String fieldName, final Object value) { Field field = getDeclaredField(object, fieldName); if (field == null) throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + object + "]"); makeAccessible(field); try { field.set(object, value); } catch (IllegalAccessException e) { Log.e("got exception:", "", e); } } /** * 循环向上转型,获取对象的DeclaredField. */ protected static Field getDeclaredField(final Object object, final String fieldName) { return getDeclaredField(object.getClass(), fieldName); } /** * 循环向上转型,获取类的DeclaredField. */ @SuppressWarnings("unchecked") protected static Field getDeclaredField(final Class clazz, final String fieldName) { for (Class superClass = clazz; superClass != Object.class; superClass = superClass.getSuperclass()) { try { return superClass.getDeclaredField(fieldName); } catch (NoSuchFieldException e) { // Field不在当前类定义,继续向上转型 } } return null; } /** * 强制转换fileld可访问. */ protected static void makeAccessible(Field field) { if (!Modifier.isPublic(field.getModifiers()) || !Modifier.isPublic(field.getDeclaringClass().getModifiers())) { field.setAccessible(true); } } }
MainActivity.java:
Timer timer = new Timer(); timer.schedule(new TimerTask() { @Override public void run() { //执行的内容 BeanUtils.setFieldValue((Object)pbHorizontal, "mOnlyIndeterminate", (Object)Boolean.valueOf(false)); pbHorizontal.setIndeterminate(false); pbHorizontal.setProgress(50); pbHorizontal.setSecondaryProgress(60); } }, 3000);//表示3秒后执行
效果如下:
这样我们就可以实现:在正在加载数据之前显示默认的动画,而在加载数据的时候显示具体的进度。
还是存在这样一个大问题,那就是默认的ProgressBar的样式实在不敢恭维,当然通过上面的分析,我们可以很容易地对默认的style进行覆盖。
1.自定义小菊花进度条:
通过Widget.ProgressBar中的style的<item name="android:indeterminateDrawable">@android:drawable/progress_medium_white</item>我们去源码中查找:
progress_medium_white.xml:
<?xml version="1.0" encoding="utf-8"?> <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/spinner_white_48" android:pivotX="50%" android:pivotY="50%" android:framesCount="12" android:frameDuration="100" />
发现spinner_white_48是一张图片:
这就很简单了,我们可以替换掉style中默认的android:indeterminateDrawable为自己的drawable就可以了:
1.在res/drawable/indicate.xml:
<?xml version="1.0" encoding="utf-8"?> <animated-rotate xmlns:android="http://schemas.android.com/apk/res/android" android:drawable="@drawable/indeterminate_progress_1" android:pivotX="50%" android:pivotY="50%" />
indeterminate_progress_1就是我们自定义的小菊花图片了
2.然后在布局文件中:
<!-- 圆形的progressBar --> <ProgressBar android:id="@+id/pb_circle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="30dip" android:layout_centerHorizontal="true" android:indeterminateDrawable="@drawable/indicate" />
就这么简单,看了很多人的博客,有的人采用的是10几张图片通过Progressbar进行播放,有的采用一个ImageView对10几张图片进行重复播放或者对一张图片进行旋转,我认为我的这种方式比较好,比较方便,而且只要一张图就够了。
2.自定义水平的ProgressBar:
我们要改变的是Widget.ProgressBar.Horizontal中的drawable/progress_horizontal和drawable/progress_indeterminate_horizontal
drawable/progress_horizontal.xml:
<?xml version="1.0" encoding="utf-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:startColor="#ff9d9e9d" android:centerColor="#ff5a5d5a" android:centerY="0.75" android:endColor="#ff747674" android:angle="270" /> </shape> </item> <item android:id="@android:id/secondaryProgress"> <clip> <shape> <corners android:radius="5dip" /> <gradient android:startColor="#80ffd300" android:centerColor="#80ffb600" android:centerY="0.75" android:endColor="#a0ffcb00" android:angle="270" /> </shape> </clip> </item> <item android:id="@android:id/progress"> <clip> <shape> <corners android:radius="5dip" /> <gradient android:startColor="#ffffd300" android:centerColor="#ffffb600" android:centerY="0.75" android:endColor="#ffffcb00" android:angle="270" /> </shape> </clip> </item> </layer-list>
这实际上是通过xml来绘制图片,当然你可以以类似的方式来实现自己的效果。
progress_indeterminate_horizontal.xml:(这个在前面已经介绍过了)
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/progressbar_indeterminate1" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate2" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate3" android:duration="200" /> </animation-list>
为了图方便,我直接到4.0的源码中,找到这个文件直接拿出来用,这样你就可以在4.0及以下的系统中看到4.0ProgressBar的效果了:
1.布局文件中:
<ProgressBar android:id="@+id/pb_horizontal" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginLeft="50dip" android:layout_marginRight="50dip" android:layout_centerHorizontal="true" android:layout_below="@+id/pb_circle" android:layout_marginTop="30dip" style="?android:attr/progressBarStyleHorizontal" android:progressDrawable="@drawable/pb_layer_list" android:indeterminateDrawable="@drawable/bg_progressbar" android:indeterminateOnly="true" />
2.res/drawable/pb_layout_list.xml:
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@android:id/background" android:drawable="@drawable/progress_bg_holo_dark" /> <item android:id="@android:id/secondaryProgress"> <scale android:scaleWidth="100%" android:drawable="@drawable/progress_secondary_holo_dark" /> </item> <item android:id="@android:id/progress"> <scale android:scaleWidth="100%" android:drawable="@drawable/progress_primary_holo_dark" /> </item> </layer-list>
3.res/drawable/bg_progressbar.xml:
<?xml version="1.0" encoding="utf-8"?> <animation-list xmlns:android="http://schemas.android.com/apk/res/android" android:oneshot="false"> <item android:drawable="@drawable/progressbar_indeterminate_holo1" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo2" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo3" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo4" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo5" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo6" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo7" android:duration="200" /> <item android:drawable="@drawable/progressbar_indeterminate_holo8" android:duration="200" /> </animation-list>
相应的图片文件你都可以在源码中找到。下面我们看看自定义后的运行效果:
信息量有点大,希望大家能够看的懂,如果自己能够去尝试一下,应该能够更好地了解。
主要的实现步骤在上面都以红颜色表示出来了。