大部分应用的内容都是是纵向的列表,因为手机上下划屏成本很低,而且用户对下面的内容长度是有心理预期的,所以大部分应用都是使用这个设计的。对应这个需求,安卓里面提供了ListView这个控件,是做安卓开发打交道最多的,类似iOS的TableView。
ListView是一个多行的列表,每行是一个item,item本身对应一个Layout。ListView本身是AdapterView的派生类,需要绑定Adapter来负责管理生成对应每行的item。item的View是复用的,即使Adapter里面的item有成百上千,但是真正生成的View一般只有屏幕显示的那么多个。这和iOS的TableCell的复用是一致的。
//下面是一个ListView在layout里的样子 <ListView android:id="@id/list" android:layout_height="match_parent" android:layout_width="match_parent" android:fadingEdge="none" android:fastScrollEnabled="false" android:cacheColorHint="#00000000" /> //下面是具体的一个item的layout,item_notify.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:padding="10dp" android:gravity="center_vertical" > <RelativeLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_weight="1.0" > <TextView android:id="@id/title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="16sp" android:textColor="#000000" android:paddingBottom="2dp" android:layout_alignParentLeft="true" android:layout_alignParentTop="true" /> <TextView android:id="@id/time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="14sp" android:textColor="#999999" android:layout_below="@id/title" android:layout_alignParentLeft="true" android:layout_alignParentBottom="true" /> </RelativeLayout> <ImageView android:id="@id/icon" android:layout_width="50dp" android:layout_height="50dp" android:scaleType="centerInside" /> </LinearLayout>
下面是典型的调用过程:
ListView list = (ListView)findViewById(R.id.list); ListAdapter adapter = new ListAdapter(); list.setAdapter(adapter);
//假设数据是存在一个叫做data 的数组里的 class ListAdapter extends BaseAdapter{ //返回item的数量 public int getCount() { return data.length; } //返回对应位置item的数据对象 public Object getItem(int position) { return data[position]; } //返回对应位置item的id,一般不直接用这个函数,而是getItem得到对象后,用对象的id public long getItemId(int position) { return 0; } //使用ViewHolder机制,避免findViewById的操作,优化性能 public View getView(int position, View convertView, ViewGroup parent){ //注意这里使用了View的tag属性来把一个ViewHolder对象绑定在一个convertView上面 //convertView就是对应item的真正View ViewHolder holder = null; //如果没有可以复用的convertView,也就是真正要创建item的layout if (convertView == null) { holder = new ViewHolder(); convertView = getLayoutInflater().inflate(R.layout.item_notify, null); holder.icon = (ImageView)convertView.findViewById(R.id.icon); holder.title = (TextView)convertView.findViewById(R.id.title); holder.time = (TextView)convertView.findViewById(R.id.time); //把holder对象绑定到convertView上 convertView.setTag(holder); //如果convertView已经被创建过,也就是说该item被复用 } else { //从tag里取出来holder对象 holder = (ViewHolder) convertView.getTag(); } refreshView(position, holder); return convertView; } private void refreshView(int position, ViewHolder h) { //根据position对应的item数据对象,具体的更新UI } }; static class ViewHolder { ImageView icon; TextView title; TextView time; };
当adapter的数据有任何变化时,需要及时调用adapter.notifyDataSetChanged()来通知ListView来更新界面,否则会在滑动或UI重绘时,因为数据已经变了,导致fc。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" //这里的利用了Layout的descendantFocusability属性 //afterDescendants表示单条只有在里面的控件没有得到焦点时,最外面的item整个才获得焦点。 android:descendantFocusability="afterDescendants" > ... <Button android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout>
//给ListView设置AbsListView.OnScrollListener listView.setOnScrollListener(new AbsListView.OnScrollListener(){ public void onScrollStateChanged(AbsListView view, int scrollState){ if (lastScrollState == SCROLL_STATE_IDLE){ //加载图片 } } });