AndroidGUI06:ProgressBar的常用技巧
ProgressBar 有两种主要的形态:一个是圆形的,一个是长条形的,形如:
圆形的 ProgressBar ,通常用于未确定的何时结束的进度显示,它会一直显示动画。
长条形的 ProgressBar ,通常用于进度明确的进度显示。
1. 在布局文件 (main.xml) 中,增加界面元素声明如下:
<? xml version = "1.0" encoding = "utf-8" ?>
< LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android"
android:orientation = "vertical"
android:layout_width = "fill_parent"
android:layout_height = "fill_parent"
>
< ProgressBar
android:id = "@+id/progress_bar1"
style = "?android:attr/progressBarStyleSmall" <!-- 小圆 ProgressBar -->
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
/>
< ProgressBar
android:id = "@+id/progress_bar2" <!-- 缺省地,是中圆 ProgressBar -->
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
/>
< ProgressBar
android:id = "@+id/progress_bar3"
style = "?android:attr/progressBarStyleLarge" <!-- 大圆 ProgressBar -->
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
/>
< ProgressBar
android:id = "@+id/progress_bar4"
style = "?android:attr/progressBarStyleHorizontal" <!-- 长条形 ProgressBar -->
android:layout_width = "fill_parent"
android:layout_height = "8sp" <!-- 指明高度为 8sp-->
android:max = "100" <!-- 最大值为 100 -->
/>
< TextView <!-- 此 TextView 做分隔符用 -->
android:layout_width = "fill_parent"
android:layout_height = "6sp"
/>
< ProgressBar
android:id = "@+id/progress_bar5"
style = "?android:attr/progressBarStyleHorizontal"
android:layout_width = "fill_parent"
android:layout_height = "wrap_content"
android:max = "100"
/>
< Button <!-- 用于触发进度条相关操作 –>
android:id = "@+id/button"
android:layout_width = "wrap_content"
android:layout_height = "wrap_content"
android:text = " 进度条测试 "
/>
</ LinearLayout >
对应的界面结果显示如下:
2. 通过程序的方式,我们还可以把进度条放到应用的标题栏 (Title bar) 中。这个可以用程序来实现:
requestWindowFeature(Window. FEATURE_INDETERMINATE_PROGRESS ); // 在 Titlebar 中,放置圆形进度条
requestWindowFeature(Window. FEATURE_PROGRESS ); // 在 Titlebar 中,放置长条形进度条
setContentView(R.layout. main );
setProgressBarIndeterminateVisibility( true ); // 在 Titlebar 中,显示圆形进度条
setProgressBarVisibility( true ); // 在 Titlebar 中,显示长条形进度条
注意: requestWindowFeature 方法的调用,必须在 setContentView 之前!
3. Activity 所对应的代码:
public class ControlProgressBar extends Activity
implements
OnClickListener
{
// 声明 5 个 ProgressBar 对象
private ProgressBar progress_bar1 ;
private ProgressBar progress_bar2 ;
private ProgressBar progress_bar3 ;
private ProgressBar progress_bar4 ;
private ProgressBar progress_bar5 ;
// 声明一个 Button 对象
private Button button ;
// 声明一个 Handler 对象
private Handler progressHandler ;
@Override
public void onCreate(Bundle savedInstanceState)
{
super .onCreate(savedInstanceState);
requestWindowFeature(Window. FEATURE_INDETERMINATE_PROGRESS );
// 在应用的 Title bar 上放置圆形 ProgressBar
requestWindowFeature(Window. FEATURE_PROGRESS );
// 在应用的 Title bar 上放置条形 ProgressBar
setContentView(R.layout. main );
// requestWindowFeature 方法的调用必须在 setContentView 之前
setProgressBarIndeterminateVisibility( true ); // 在应用的 Title bar 上显示圆形 ProgressBar
setProgressBarVisibility( true ); // 在应用的 Title bar 上显示条形 ProgressBar
// 获取 ProgressBar 对象
progress_bar1 = (ProgressBar)findViewById(R.id. progress_bar1 );
progress_bar2 = (ProgressBar)findViewById(R.id. progress_bar2 );
progress_bar3 = (ProgressBar)findViewById(R.id. progress_bar3 );
progress_bar4 = (ProgressBar)findViewById(R.id. progress_bar4 );
progress_bar5 = (ProgressBar)findViewById(R.id. progress_bar5 );
// 获取 Handler 对象
progressHandler = new Handler()
{
@Override
public void handleMessage(Message msg) // 重写 handleMessage 方法
{
setProgress(msg. what * 100); // 设定 Title bar 上的条形和圆形 ProgressBar 的进度 , 缺省地 ,
// 他们的最大值为 10000 ,为此,由于我们准备的 msg.what 的取值
// 范围为 0 ~ 100 ,因此,在这里需要乘以 100 。
// 另外需要注意的是,当 Title bar 上的 ProgressBar 达到最大值
// 后,他们会自行消失
progress_bar4 .setProgress(msg. what ); // 根据 msg.what 的取值 , 设定 progress_bar4 的进度
progress_bar5 .setProgress(msg. what );
// 根据 msg.what 的取值 , 设定 progress_bar5 的进度
progress_bar5 .setSecondaryProgress(( int )(Math.sqrt (msg. what )) * 10);
// 根据 msg.what 的取值,设定 progress_bar5 的第二个进度
if (msg. what == 100) // 达到最大值后 , 让 progress_bar4 不可见
{
progress_bar4 .setVisibility(ProgressBar. INVISIBLE );
}
}
};
// 获取 Button 对象 , 并为其设置 OnClickListener
button = (Button)findViewById(R.id. button );
button .setOnClickListener( this );
}
public void onClick(View v)
{
progress_bar1 .setVisibility(ProgressBar. VISIBLE ); // progress_bar1 继续可见
progress_bar2 .setVisibility(ProgressBar. GONE ); // progress_bar2 不可见 , 且离开界面
progress_bar3 .setVisibility(ProgressBar. INVISIBLE ); // progress_bar3 不可见 , 但依然在界面上
ProgressThread pt = new ProgressThread( progressHandler ); // 创建线程 pt
pt.start(); // 启动线程 pt
}
}
class ProgressThread extends Thread
{
private Handler handler ;
public ProgressThread(Handler handler) // 传入一个 Handler 对象
{
this . handler = handler;
}
@Override
public void run()
{
int i = 0;
while (i <= 100)
{
handler .sendEmptyMessage(i); // i 就是 Message 中 what 的值 ,handler.sendEmptyMessage
// 会触发 handler 的 handleMessage 执行
++i;
try
{
sleep (100); // 休息 100ms
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
运行结果:
在点击按钮之前:
点击按钮后:
达到最大值后:
4. 关于 Only the original thread that created a view hierarchy can touch its views 错误
在 Android 编程中,使用 Thread 更新 ProgressBar 的进度的时候,很多人都碰到过类似的问题。最好的解决办法就是类似 3中所给出的代码。
5. 在上面的代码中,我们知道,所有的 ProgressBar 对象,都是在 Activity ControlProgressBar 中创建的,因此,对于这些ProgressBar 的更新工作,就必须要在 ControlProgressBar 这个 Activity 中完成 ( 在这个例子中,是该应用的主线程 ) ,而不能在另外一个线程中来完成对那些 ProgressBar 的状态更新。否则,就会出现“ Only the original thread that created a view hierarchy can touch its views ”这样的错误,从而使整个应用被 Forced Close 。
在上面的代码中,在 onClick 方法里面,我们新创建了一个 ProgressThread 的线程,在这个线程中,并没有直接去更新各个ProgressBar 的状态,只是通过一个循环,不断产生 Message.what ,并通过 sendMessage 方法,向 progressHandler 发送消息,并触发 progressHandler 中的 handleMessage 方法的执行。很显然 progressHandler 是在 Activity ControlProgressBar 中创建的,它本身也并没有创建新的线程,而在 handleMessage 方法中,我们对各个 ProgressBar 对象的状态,进行了更新。
类似下面的代码,试图在另外一个线程中改变主线程中界面元素的状态,就会出现“ Only the original thread that created a view hierarchy can touch its views ”这样的错误:
public class ControlProgressBar extends Activity
implements
OnClickListener
{
// 声明 5 个 ProgressBar 对象
// … …
// 声明一个 Button 对象
private Button button ;
@Override
public void onCreate(Bundle savedInstanceState)
{
super .onCreate(savedInstanceState);
requestWindowFeature(Window. FEATURE_INDETERMINATE_PROGRESS );
requestWindowFeature(Window. FEATURE_PROGRESS );
setContentView(R.layout. main );
setProgressBarIndeterminateVisibility( true );
setProgressBarVisibility( true );
// 获取 ProgressBar 对象
// … …
// 获取 Button 对象 , 并为其设置 OnClickListener
button = (Button)findViewById(R.id. button );
button .setOnClickListener( this );
}
public void onClick(View v)
{
ProgressThread pt = new ProgressThread( this ); // 创建线程 pt
pt.start(); // 启动线程 pt
}
}
class ProgressThread extends Thread
{
private Context ctx ;
public ProgressThread(Context ctx) // 传入一个 Context 对象 ( 实参将是 ControlProgressBar )
{
this . ctx = ctx;
}
@Override
public void run()
{
int i = 0;
while (i <= 100)
{
setProgress(i* 100);
// 在本线程中 , 更新主线程中界面元素 Title bar 上的 ProgressBar 的状态 ,就会出现:
// “ Only the original thread that created a view hierarchy can touch its views ”这样的错误
++i;
try
{
sleep (100); // 休息 100ms
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
6. 形如下面的代码,不会报错,但会阻塞 ProgressBar 对象的状态更新,因此也不是好办法。可惜网上很多人似乎在推荐这种方式,实有误人之嫌。
final Handler handler= new Handler();
final Runnable runnable = new Runnable()
{
public void run()
{
// 更新界面元素
}
};
private OnClickListener btnLisener = new OnClickListener()
{
public void onClick(View v)
{
requestData();
}
};
protected void requestData()
{
Thread t = new Thread()
{
public void run()
{
handler.post(runnable);
// 加入到消息队列这样没有启动新的线程 , 虽然没有报异常 , 但仍然阻塞 ProgressBar 的进度显示
}
};
t.start();
}