ListView就是列表组件,一般通过继承ListActivity使用系统提供的ListView.
所有的AdapterView组件都需要有一个对应的Adapter作为适配器来显示列表中元素的布局方式
见思维导图
AbsListView的常用XML属性:
android:choiceMode
设置ListView的选择行为 none:不显示任何选中项 singleChoice:允许单选 multipleChoice:允许多选
android:drawSelectorOnTop
设置为true,则选中的列表项会显示在上面
android:fastScrollEnabled
是否允许快速滚动
android:listSelector
指定被选中的列表项上绘制的Drawable
android:scrollingCache
是否使用绘制缓存
android:smoothScrollBar
如果设置为false,则不在header View之后绘制分隔条
android:stackFromBottom
设置是否从底端开始排列列表项
android:textFilterEnabled
设置是否对列表项进行过滤.当对应的Adapter实现了Filter接口时该属性才会起作用
android:transcriptMode
设置该组件的滚动模式。disable:关闭滚动,这是默认值 normal:当listView收到数据改变通知,且最后一个列表项可见时,将会自动滚动到底端。 alwaysScoll:该listView总会自动滚动到底端
android:divider
设置List列表项的分隔条(既可用颜色分隔,也可用Drawable分隔)
android:dividerHeight
分隔条高度
android:entries
指定一个数组资源,生成一个ListView
android:footerDividersEnabled
设置为false,则不在footer View之前绘制分隔条
android:headerDividersEnabled
设置为false,则不在header View之后绘制分隔条
ArrayAdapter: 数组或集合的适配器。
例:
private final String[] mous = {
"郭嘉",
"荀攸",
"荀彧",
"程昱",
"戏志才",
"徐庶"
};
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,mous);
也可以在XML文件中用android:entries属性指定绑定的数组资源文件
资源文件存放在value文件夹下
如:
android:entries="@array/book"
<string-array name="book" >
<item >1</item>
<item >2</item>
<item >3</item>
<item >3</item>
</string-array>
例子
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="horizontal" > <ListView android:id="@+id/lv" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>
package com.light.android.study; import android.app.Activity; import android.os.Bundle; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private final String[] mous = { "郭嘉", "荀攸", "荀彧", "程昱", "戏志才", "徐庶" }; private ListView lv; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); init(); initAdapter(); } private void init(){ lv = (ListView) findViewById(R.id.lv); } private void initAdapter(){ ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,mous); lv.setAdapter(adapter); } }
SimpleAdapter(Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)
第一个参数是Context对象
第二个参数是保存有每一行数据的Map构成的List对象,也就是说,每一行数据里的每一个属性都由它的名字和它的值构成一个键值对,多个属性有多个键值对,每一行的多个键值对构成这一行的一个Map对象,这一行的Map对象对应到这个List中.
第三个参数布局文件Id
第四个参数Map对象中的多个键值对的key值
第五个参数布局文件中用来显示内容的组件ID
SimpleAdapter绑定数据到视图分两个阶段
1.首先,如果设置了SimpleAdapter.ViewBinder,那么这个设置的ViewBinder的setViewValue(android.view.View, Object, String)将被调用.如果setViewValue的返回值是true,则表示绑定已经完成,将不再调用系统默认的绑定实现.
2.如果返回值为false,视图将按以下顺序绑定数据:
•如果View实现了Checkable(例如CheckBox),期望绑定值是一个布尔类型.
•TextView.期望绑定值是一个字符串类型,通过调用setViewText(TextView, String)绑定.
•ImageView,期望绑定值是一个资源id或者一个字符串,通过调用setViewImage(ImageView, int) 或 setViewImage(ImageView, String)绑定数据.
如果没有一个合适的绑定发生将会抛出IllegalStateException.
例子:
布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="vertical" > <!-- 定义一个List --> <ListView android:id="@+id/mylist" android:layout_width="fill_parent" android:layout_height="wrap_content" /> </LinearLayout>
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <!-- 定义一个ImageView,用于作为列表项的一部分。 --> <ImageView android:id="@+id/header" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="10dp" tools:ignore="ContentDescription" /> <!-- 定义一个TextView,用于作为列表项的一部分。 --> <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center_vertical" android:paddingLeft="10dp" android:textSize="16sp" /> </LinearLayout>
package com.light.android.study; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { private String[] names = new String[] { "杜甫", "弄玉", "清照", "李白" }; private int[] imageIds = new int[] { R.drawable.tiger, R.drawable.nongyu, R.drawable.qingzhao, R.drawable.libai }; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); List<Map<String, Object>> listItems = new ArrayList<Map<String, Object>>(); for (int i = 0; i < names.length; i++) { Map<String, Object> map = new HashMap<String, Object>(); map.put("header", imageIds[i]); map.put("personName", names[i]); listItems.add(map); } //創建SimpleAdapter SimpleAdapter simpleAdapter = new SimpleAdapter(this, listItems, R.layout.list_item_layout, new String[] { "personName", "header" }, new int[] { R.id.name, R.id.header }); ListView list = (ListView) findViewById(R.id.mylist); list.setAdapter(simpleAdapter); } }
CursorAdapter:
Cursor型集合的适配器,与数据库连接的桥梁
我们一般使用其子类SimpleCursorAdapter来实现查询出来的数据List的Adapter
SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to,int flags)
context:当前Context对象
layout:布局文件Id
c:集合
from:需要绑定的集合中的列的名字
to:显示项目的View的id集合,在layout布局文件中定义
flags:用来判断适配器的行为标志
建议使用CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER
适配器会在游标上注册一个内容观测器,当通知到达时会调用 onContentChanged() 方法.使用该标志位时要注意:在注册观察器时需要先解除当前游标与适配器的关联,防止发生泄漏.
Android 3.0引入了CursorLoader实现异步加载数据,为了避免同步查询数据库时阻塞UI线程的问题。所以一般先将SimpleCursorAdapter中的Cursor对象放空,然后用CursorLoader对象加载数据,再放入适配器。
例子:
布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout 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" android:orientation="horizontal" > <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> </LinearLayout>Item布局文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="64dip" android:orientation="horizontal" android:gravity="center_vertical" android:paddingLeft="8dip"> <TextView android:id="@android:id/text1" android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="18sp" android:gravity="center_vertical" android:singleLine="true" android:fadingEdge="horizontal" android:fadingEdgeLength="3dip" android:ellipsize="marquee" /> </LinearLayout>
package com.light.android.study; import android.app.ListActivity; import android.app.LoaderManager.LoaderCallbacks; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; import android.database.Cursor; import android.os.Bundle; import android.provider.ContactsContract; import android.widget.SimpleCursorAdapter; //實現接口作為CursorLoader public class MainActivity extends ListActivity implements LoaderCallbacks<Cursor> { private SimpleCursorAdapter mAdapter; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Create an empty adapter we will use to display the loaded data. mAdapter = new SimpleCursorAdapter(MainActivity.this, R.layout.contacts_list_item, null, new String[] { ContactsContract.Contacts.DISPLAY_NAME }, new int[] { android.R.id.text1 }, 0); getListView().setAdapter(mAdapter); //初始化Loader //第一個參數 為這個Loader對象指定唯一的标识ID,第二个可选参数,用来支持Loader的构造方法, //第三个参数是LoaderCallbacks接口类型 getLoaderManager().initLoader(0, null, this); } public Loader<Cursor> onCreateLoader(int id, Bundle args) { // 去数据库读取数据等要消耗大量时间的操作放在 // 自定义 CursorLoader 的 onLoadInBackground return new MyCursorLoader(getApplicationContext()); } public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) { // Swap the new cursor in. (The framework will take care of closing the // old cursor once we return.) mAdapter.swapCursor(cursor); } public void onLoaderReset(Loader<Cursor> arg0) { // This is called when the last Cursor provided to onLoadFinished() // above is about to be closed. We need to make sure we are no // longer using it. mAdapter.swapCursor(null); } public static class MyCursorLoader extends CursorLoader { String[] mContactProjection = { ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME }; private Context mContext; public MyCursorLoader(Context context) { super(context); mContext = context; } /** * 4.自定义 CursorLoader 的 onLoadInBackground * 会返回一个Cursor,这里给SimpleCursorAdapter用 * 来填充数据。查询数据等操作放在这里执行 */ @Override protected Cursor onLoadInBackground() { Cursor cursor = mContext.getContentResolver().query( ContactsContract.Contacts.CONTENT_URI, mContactProjection, null, null, null); return cursor; } } }
最后看下BaseAdapter
经常需要覆写的方法
boolean isEnabled (int position):
如果列表的一项item是separator(充当分隔项目,跟其他item项一样,也可以不一样,但是无法进行点击),返回true,也就是可以点击,并接收响应事件。如果此时position处的item是separator的话,返回false,也就无法响应点击或触摸事件,此项目是不可以点击的,表现形式为点了没任何反应,可以充当一个列表中的分隔,当然可以自定义这个分隔项的布局。
abstract Object getItem(int position)
得到位于position位置的项
public int getViewTypeCount()
返回共有多少个不同的布局
abstract int getCount()
得到项目总数
abstract long getItemId(int position)
得到位于position位置的项的Id
public int getItemViewType(int position)
以int数值型返回itemView的类型。一般普通列表的item都是一样的布局,也就是说这个列表只有一种类型,但是很多时候我们需要列表显示不同的item,比如有的列表有普通item和separator两种类型,item用于响应用户点击事件,separator用于分隔item,不可以点击,这样这个列表就有了两种类型,重载这个方法,如果当前位置是item,我们可以返回1,如果是separator我们可以返回2,以此类推。
abstract View getView(int position,View convertVeiw,ViewGroup parent)
每一项显示的设置
在getView方法中需要缓存加载View优化ListView
官方建议使用ViewHolder,其实就是单例
例:
@Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder; if (convertView == null) { final LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.list_item_icon_text, null); holder = new ViewHolder(); holder.icon = (ImageView) convertView.findViewById(R.id.icon); holder.text = (TextView) convertView.findViewById(R.id.text); convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } holder.icon.setImageResource(R.drawable.icon); holder.text.setText(mData[position]); return convertView; } static class ViewHolder { ImageView icon; TextView text; }
知识点
①解决ListView滑动过程中背景颜色变黑的问题
android:cacheColorHint="#00000000" // setting transparent color
http://stackoverflow.com/questions/2833057/background-listview-becomes-black-when-scrolling/2833103#2833103
②ListView变圆角显示
定义一个shape:res/drawable/customshape.xml
<?xml version="1.0" encoding="UTF-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <gradient android:startColor="#SomeGradientBeginColor" android:endColor="#SomeGradientEndColor" android:angle="270"/> <corners android:bottomRightRadius="7dp" android:bottomLeftRadius="7dp" android:topLeftRadius="7dp" android:topRightRadius="7dp"/> </shape>
然后在XML中
android:background="@drawable/customshape"
http://stackoverflow.com/questions/1683185/how-do-i-create-a-listview-with-rounded-corners-in-android/1683195#1683195
③保留ListView上次滑动的位置
// save index and top position int index = mList.getFirstVisiblePosition(); View v = mList.getChildAt(0); int top = (v == null) ? 0 : v.getTop(); // ... // restore index and position mList.setSelectionFromTop(index, top);
说明:
ListView.getFirstVisiblePosition()
returns the top visible list item. But this item may be partially scrolled out of view, and if you want to restore the exact scroll position of the list you need to get this offset. So ListView.getChildAt(0)
returns the View
for the top list item, and then View.getTop()
returns its relative offset from the top of the ListView
. Then, to restore the ListView
's scroll position, we call ListView.setSelectionFromTop()
with the index of the item we want and an offset to position its top edge from the top of the ListView
.
http://stackoverflow.com/questions/3014089/maintain-save-restore-scroll-position-when-returning-to-a-listview