=======================ListView原理==============================
Android 的 ListView 的原理打个简单的比喻就是:
演员演小品(假设演员都长一样,每个角色任何演员都可以演)
小品剧不需要为每个角色都招募一个演员。ListView 也没必要为每一个 Item 创建 View 对象。
小品剧的演员在一个角色表演完成后,会在后台换下一个角色的服装,等待需要表演的时候再出场。
ListView 会让未显示的 View 填充数据后缓存在后台,等待滑动时再将它显示出来。
小品演员换个服装就成了另一个角色,所以不能以角色来判断是哪个演员。
ListView 中的 Item 的样式是随填充的数据动态变化的,所以不能以某个样式作为Item的标识。
如果你是导演,你要警察这个角色在白领抬手时双手举起,你会怎们做?如果你找上次演过警察的那个演员,告诉他你在白领抬手时将双手举起。
那么有三个结果:一、他仍然演警察、他出色的完成了表演。二、他演厨师,结果白领抬手时厨师举起了手。三、他没上台,结果警察没举手。
ListView 中如果你根据某个 Item 的状态来获取它的 View 对象,通过线程改变它的状态,就会发生这三种情况。
那么要如何做呢?当然是告诉所有演员,谁扮演警察谁就在白领抬手时双手举起。那表演时如何判断谁演的是警察呢?警察帽子就是标志,谁戴着谁是警察。
ListView 中你要获取所有的 View 对象的集合,并为每一个 View 设置标识,传递需要更新状态的视图标识。更新前,在集合中找到标识匹配的 View 对象,让它做出相应的更新操作。
=====================ListView 中更新 ProgressBar=========================
知道这个 ListView 这个特性之后,就可以动手开始写了:
布局文件
activity_main
list_item
Java文件
public class MainActivity extendsAppCompatActivity {private ListView listView; //列表控件
private List data; //数据源(模拟)
private MyAdapter adapter; //自定义适配器
@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);/*初始化控件*/listView=(ListView) findViewById(R.id.list_view);/*初始化数据*/data= new ArrayList<>();for (int i = 0; i < 30; i++) {//组装数据
MyObject myObject = newMyObject();
myObject.text= "按钮" +i;
myObject.progress= -1;//添加到数据源
data.add(myObject);
}/*填充适配器*/adapter= new MyAdapter(this, data);
listView.setAdapter(adapter);
}/*** 实体对象,用于保存数据*/
classMyObject {
Integer progress;//下载进度
String text; //按钮文字
}
}
MainActivity
public class MyAdapter extendsBaseAdapter {private Context context; //上下文对象用于视图填充
private List data; //需要适配的数据源
private List viewList; //View对象集合
public MyAdapter(Context context, Listdata) {this.viewList = new ArrayList<>();this.context =context;this.data =data;
}
@Overridepublic intgetCount() {returndata.size();
}
@Overridepublic Object getItem(intposition) {returndata.get(position);
}
@Overridepublic long getItemId(intposition) {returnposition;
}
@Overridepublic View getView(final intposition, View convertView, ViewGroup parent) {finalViewHolder viewHolder;/*初始化控件*/
if (convertView == null) {
convertView= LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
viewHolder= newViewHolder();
viewHolder.progressBar=(ProgressBar) convertView.findViewById(R.id.progress_bar);
viewHolder.button=(Button) convertView.findViewById(R.id.btn);
convertView.setTag(viewHolder);
}else{
viewHolder=(ViewHolder) convertView.getTag();
}/*添加控件样式*/
final MainActivity.MyObject myObject =data.get(position);
viewHolder.button.setText(myObject.text);
viewHolder.progressBar.setProgress(myObject.progress);/*设置按钮点击事件*/viewHolder.button.setOnClickListener(newView.OnClickListener() {
@Overridepublic voidonClick(View v) {if (myObject.progress == -1) {
myObject.progress= 0;//如果未开始下载,启动异步下载任务
MyAsyncTask asyncTask = newMyAsyncTask(viewList, position);//添加THREAD_POOL_EXECUTOR可启动多个异步任务
asyncTask.executeOnExecutor(MyAsyncTask.THREAD_POOL_EXECUTOR, myObject);
}
}
});/*标识View对象*/
//将list_view的ID作为Tag的Key值
convertView.setTag(R.id.list_view, position);//此处将位置信息作为标识传递
viewList.add(convertView);returnconvertView;
}/*** 用于缓存控件ID*/
classViewHolder {
ProgressBar progressBar;
Button button;
}
}
MyAdapter
public class MyAsyncTask extends AsyncTask{private MainActivity.MyObject myObject; //单个数据,用于完成后的处理
private List viewList; //视图对象集合,用于设置样式
private Integer viewId; //视图标识,用于匹配视图对象
public MyAsyncTask(ListviewList, Integer viewId) {this.viewList =viewList;this.viewId =viewId;
}
@OverrideprotectedVoid doInBackground(MainActivity.MyObject... params) {
myObject= params[0];/*模拟下载任务*/
for (int i = 0; i < 100; i++) {//发布进度
publishProgress(i);try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}return null;
}
@Overrideprotected voidonProgressUpdate(Integer... values) {
View view= null;/*匹配视图对象*/
for (int i = 0; i < viewList.size(); i++) {if (viewList.get(i).getTag(R.id.list_view) ==viewId) {//检查所有视图ID,如果ID匹配则取出该对象
view =viewList.get(i);break;
}
}if (view != null) {//将视图对象中缓存的ViewHolder对象取出,并使用该对象对控件进行更新
MyAdapter.ViewHolder viewHolder =(MyAdapter.ViewHolder) view.getTag();
viewHolder.progressBar.setProgress(values[0]);
}
myObject.progress= values[0];
}
@Overrideprotected voidonPostExecute(Void aVoid) {//更新数据源信息
myObject.progress = 100;
}
}
MyAsyncTask
实现效果
这里主要强调一下如何为View设置标识,以及从View集合中匹配标识:
首先是为View设置标识:
@Overridepublic View getView(final intposition, View convertView, ViewGroup parent) {finalViewHolder viewHolder;/*初始化控件*/
if (convertView == null) {
convertView= LayoutInflater.from(context).inflate(R.layout.list_item, parent, false);
viewHolder= newViewHolder();
viewHolder.progressBar=(ProgressBar) convertView.findViewById(R.id.progress_bar);
viewHolder.button=(Button) convertView.findViewById(R.id.btn);
convertView.setTag(viewHolder);//记录ViewHolder对象,缓存控件实例
} else{
viewHolder=(ViewHolder) convertView.getTag();
}/*添加控件样式*/
//略去……
/*设置按钮点击事件*/
//略去……
/*标识View对象*/convertView.setTag(R.id.list_view, position);//此处将位置信息作为标识传递
viewList.add(convertView); //将每个View添加到视图集合中
/*** View.setTag(int Key,Object object)中的Key值必须唯一
* 传入任何常量都是无效的,必须传入R.id中生成的值
*
* 标识并非用于识别View对象,而是识别View的状态
* 就像警帽并非用于识别演员,而是识别演员当前扮演的角色
*
* View集合就像演员名单一样重要,如果没有它表演无从开展
*
* notifyDataSetChanged()虽然能更新列表,但是它是更新所有控件数据
* 相比于选择某个控件进行更新,这种方法性能开销大,体验差*/
returnconvertView;
}
然后是在更新时匹配标识:
@Overrideprotected voidonProgressUpdate(Integer... values) {
View view= null;/*匹配视图对象*/
for (int i = 0; i < viewList.size(); i++) { //上场名单清点
if (viewList.get(i).getTag(R.id.list_view) == viewId) { //服装确认匹配//检查所有视图ID,如果ID匹配则取出该对象
view =viewList.get(i);break;
}
}if (view != null) { //上场进行表演//将视图对象中缓存的ViewHolder对象取出,并使用该对象对控件进行更新
MyAdapter.ViewHolder viewHolder =(MyAdapter.ViewHolder) view.getTag();
viewHolder.progressBar.setProgress(values[0]);
}/*** 在更新时ViewList的重要性就体现出来了
* 遍历整个ViewList直到找到标识相同的视图
*
* 因为每次填充View时,View都会添加一个标识,而标识记录了当前的位置
* 所以标识代表某个视图在特定的位置,如果标识固定那么位置也就固定了
*
* 就像演员每次表演前,虽然角色谁都可以演,但是只要服装确定
* 那么只有穿着这服装且在上场名单内的演员才可以进行表演
**/}
其实更新 ListView 中的某个控件的状态真是是很麻烦的事,因为适配器会为视图填充新的数据,这就要求使用对象记录状态,比如在实体对象中添加完成与否的判断,还有完成进度的记录,并且在更新视图中也同步更新这些数据。