ListView 是Android中最常用的一种视图组件。以垂直的方式列表需要显示的列表项。在Android中我们通过两种方式来实现ListView,一种是直接使用ListView组件,一种是让Activity继承ListActivity。
ListView中的一些常用属性
- android:divider 设置列表的分隔条,可以是颜色 也可以是Drawable资源
- android:dividerHeight 设置分隔条的高度
- android:entries 用于通过数组资源为ListView 指定内容
- android:HeaderDividersEnabled 设置是否在header View 之后绘制分隔条,默认为true。
- android:footerDividersEnabled 设置是否在footerView 之前绘制分隔条,默认为true。
实现ListView组件
通过指定数组资源来实现ListView
我们先在XML中定义我们的ListView
<ListView
android:layout_width="match_parent"
android:id="@+id/lv_my_test"
android:entries="@array/myData"
android:layout_height="match_parent"/>
我们引用的我们写的数组里面的数据,我们在values文件夹下创建一个arrays.xml 里面的代码为
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string-array name="myData">
<item>这是第一个</item>
<item>这是第二个</item>
<item>这是第三个</item>
<item>这是第四个</item>
<item>这是第五个</item>
<item>这是第六个</item>
<item>这是第七个</item>
<item>这是第八个</item>
<item>这是第九个</item>
</string-array>
</resources>
这样一个最简单的ListView 就已经写好了,在Activiity中完全不用写什么其他代码,但是这样的ListView并没什么卵用!!
当然可能有时候就需要一个特别简单的来展示一点数组这样也可以。更多的时候我们会通过Adapter去适配数据。
通过ArrayAdapter来展示ListView
删掉ListView的xml代码中的 android:entries="@array/myData",我们看看java里面的代码和效果图。
public class TestActivity extends Activity {
private ListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initViews();
}
private void initViews() {
String[] data = new String[]{"刘亦菲", "李宇春", "王尼玛", "金星", "其他"};//创建一个数组
mListView = (ListView) findViewById(R.id.lv_my_test);//初始化ListView
//初始化ArrayAdapter 适配器
ArrayAdapter<String> mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, data);//【注1】
mListView.setAdapter(mAdapter);//将适配器设置给ListView
}
}
注1: 我们为ArrayAdapter设置的三个参数分别为 上下文、item的布局,我们的数据。
我们用了系统给我们提供好的简单的布局,那么系统给我们都提供了些什么简单的item布局呢?
常用的有以下几种:
1. simple_list_item_1 每个列表项都是一个普通文本
2. simple_list_item_2 每个列表项都是双行显示
3. simple_list_item_checked 每个列表项全部都是选中的状态 并且不可取消选中
4. simple_list_item_multiple_choice 每个列表项都是带checkbox的文本
5. simple_list_item_single_choice 每个列表项都是带单选按钮的文本
simple_list_item_2 的实现
用上面ArrayAdapter不能实现simple_list_item_2 的布局。
这时候我们要用SimpleAdapter来实现。
只需要替换上班的initViews()方法里面的代码
List<Map<String, String>> data = new ArrayList<>();
Map<String, String> map1 = new HashMap<>();
map1.put("name", "刘亦菲");
map1.put("sex", "女");
Map<String, String> map2 = new HashMap<>();
map2.put("name", "李宇春");
map2.put("sex", "男");
Map<String, String> map3 = new HashMap<>();
map3.put("name", "王尼玛");
map3.put("sex", "基佬");
data.add(map1);
data.add(map2);
data.add(map3);
mListView = (ListView) findViewById(R.id.lv_my_test);//初始化ListView
//初始化ArrayAdapter 适配器
SimpleAdapter mAdapter = new SimpleAdapter(this, data, android.R.layout.simple_list_item_2, new String[]{"name", "sex"}, //每行显示一组姓名和性别
new int[]{android.R.id.text1, android.R.id.text2});
mListView.setAdapter(mAdapter);//将适配器设置给ListView
可选中的选项,获取选中的项
这三个的处理
simple_list_item_checked 每个列表项全部都是选中的状态 并且不可取消选中
simple_list_item_multiple_choice
simple_list_item_single_choice 每个列表项都是带单选按钮的文本
String[] data = new String[]{"刘亦菲", "李宇春", "王尼玛", "金星", "其他"};//创建一个数组
mListView = (ListView) findViewById(R.id.lv_my_test);//初始化ListView
//初始化ArrayAdapter 适配器
ArrayAdapter<String> mAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_checked, data);//
mListView.setAdapter(mAdapter);//将适配器设置给ListView
//一定有设置这句,不然不能选择 多选为 CHOICE_MODE_MULTIPLE,单选为CHOICE_MODE_SINGLE
mListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
Button button1 = (Button) findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SparseBooleanArray sbArray = mListView.getCheckedItemPositions();//在这个里面选中项是一个SparseBooleanArray
for (int i = 0; i < sbArray.size(); i++) {
if (sbArray.valueAt(i)) {//判断选中是否为true
//输入选中的item的position
System.out.println(sbArray.keyAt(i));
}
}
}
});
自定义小布局,继承BaseAdapter的实现
这也是我们开发中用到的最多的一种实现ListView的方式。
我们TestActiviy的布局为: activity_test.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/lv_my_test"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
我们的主界面TestActivity.java
public class TestActivity extends Activity {
private ListView mListView;
List<UserBean> mList = new ArrayList<>();//存放列表数据
private TestAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
initData();
initViews();
}
/**模拟20条数据*/
private void initData() {
for (int i = 0; i < 20; i++) {
UserBean user = new UserBean();
user.setName("name" + i);
user.setSex(i % 2 == 0 ? "男" : "女");
user.setImage("url=" + i);
mList.add(user);
}
}
private void initViews() {
mListView = (ListView) findViewById(R.id.lv_my_test);
mAdapter = new TestAdapter(this, mList);
mListView.setAdapter(mAdapter);
//列表项的点击事件
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Toast.makeText(TestActivity.this, "点击了第"+i+"项", Toast.LENGTH_SHORT).show();
}
});
}
}
我们放数据的UserBean
public class UserBean {
private String name;
private String sex;
private String image;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getImage() {
return image;
}
public void setImage(String image) {
this.image = image;
}
}
我们自己的Adapter :TestAdapter.java
public class TestAdapter extends BaseAdapter {
private Context context;
private List<UserBean> mList;
public TestAdapter(Context context, List<UserBean> mList) {
this.mList = mList;
this.context = context;
}
@Override
public int getCount() {
return mList.size();
}
@Override
public Object getItem(int i) {
return mList.get(i);
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
view = LayoutInflater.from(context).inflate(R.layout.my_item_layout, null);//加载我们自己的小布局
ImageView image = (ImageView) view.findViewById(R.id.iv_notify_image);
TextView mName = (TextView) view.findViewById(R.id.tv_name);
TextView mSex = (TextView) view.findViewById(R.id.tv_sex);
mName.setText(mList.get(i).getName());//设置name
mSex.setText(mList.get(i).getSex());//设置布局
return view;
}
}
我们自己写的小布局 :my_item_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/notice_view_type_0"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="@+id/iv_notify_image"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_gravity="center_vertical"
android:layout_marginLeft="15dp"
android:src="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="80dp"
android:gravity="center_vertical"
android:paddingLeft="16dp"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_sex"
android:layout_marginTop="10dp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
通过继承ListActivity来实现ListView
在何种情况下,我们该用这种方式来实现ListView呢?
当我们的程序窗口仅仅需要显示一个列表,则可以用此种方式来实现ListView,继承了ListActivity的类中不用调用setContentView()方法去加界面,直接去给其设置适配器就行。
当然此种情况在实际开发中用到的几率非常小,我们了解一下就行
我们只需要将上面的TestActivity 代码改成下面的即可:
public class TestActivity extends ListActivity {
List<UserBean> mList = new ArrayList<>();//存放列表数据
private TestAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setContentView(R.layout.activity_test);
initData();
initViews();
}
/**模拟20条数据*/
private void initData() {
for (int i = 0; i < 20; i++) {
UserBean user = new UserBean();
user.setName("name" + i);
user.setSex(i % 2 == 0 ? "男" : "女");
user.setImage("url=" + i);
mList.add(user);
}
}
private void initViews() {
mAdapter = new TestAdapter(this, mList);
setListAdapter(mAdapter);
}
/**
* 重写父类中的onListItemClick()方法,实现我们自己的点击事件
* */
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
Toast.makeText(TestActivity.this, "点击了第"+position+"项", Toast.LENGTH_SHORT).show();
}
}
最后贴一张效果图:
通过hashmap来实现ListView的单选、多选
我们通过点击item去改变一个checkbox或者radiobutton的状态来控制是单选还是多选,至于为什么用checkbox或者radiobutton,是因为这两个本是有选中和非选中两种状态,当然你要用其他控件也完全可以,无非就是用它的selector去区分是否显示的是选中状态。。
我们来看看代码:
首先我们的小布局应该有一个显示是否选中的控件,一个显示内容的控件,当然你用RadioButton或者CheckBox既可以显示是否选中,也可以同时显示内容也可以,但是考虑到我们实际中可能单选或者多选可能不仅仅是只要一个可以显示内容的控件,有可能会比较复杂,所以我这里还是分开写了。
item_list.xml
这里我们一定要将Radiobutton的clickable和focusable设置为false,不然它本身的特性会和我们写的冲突,就不能实现单选了,当然这样需要我们在OnItemClick的时候去处理也能实现单选,不过就麻烦了。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp">
<RadioButton
android:layout_width="match_parent"
android:id="@+id/rbtn_item"
android:text=""
android:clickable="false"
android:focusable="false"
android:layout_height="50dp" />
<TextView
android:layout_width="wrap_content"
android:id="@+id/text_item"
android:layout_centerInParent="true"
android:layout_height="wrap_content" />
</RelativeLayout>
然后看看我们的Activity中
MainActivity.java
public class MainActivity extends AppCompatActivity {
private String[] data = new String[20];
private MyAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
TextView mTextView = (TextView) findViewById(R.id.tv_test);
mTextView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(MainActivity.this, "选中了第"+getCheckedItem()+"项", Toast.LENGTH_SHORT).show();
}
});
/**模拟20条数据*/
for (int i = 0; i < data.length; i++) {
data[i] = "item" + i;
}
ListView mList = (ListView) findViewById(R.id.lv_test);
mAdapter= new MyAdapter(this,data);
mList.setAdapter(mAdapter);
mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
//单选则先将除了点击项之外的选项全部重置为false
//去掉这个for循环则就是多选
for (int i = 0; i < data.length; i++) {
//每次跳出点击的这一项的状态重置,在下面去取反保存可以取消选中的,否则一旦选中一个,就取消不了了
if (i==position){
continue;
}
if (MyAdapter.getIsSelected().get(i)) {//判断所有的项的选中状态,如果是选中,则改为false
MyAdapter.getIsSelected().put(i, false);
}
}
MyAdapter.getIsSelected().put(position, !MyAdapter.getIsSelected().get(position));//将改变后的状态保存到hashmap中
mAdapter.notifyDataSetChanged();//刷新适配器
}
});
}
/**
* 单选我们可以在拿到选中项后直接返回选中项的id,如果都没选中则返回-1
*/
private Integer getCheckedItem() {
for (int i = 0; i < MyAdapter.getIsSelected().size(); i++) {
if (MyAdapter.getIsSelected().get(i)) {
return i;
}
}
return -1;
}
/**
* 多选用此方法来获取最后选中的项的position,返回值为一个数组
* */
private Integer[] getMoreThanOneItem(){
Integer [] array = new Integer[MyAdapter.getIsSelected().size()];
for (int i = 0; i < MyAdapter.getIsSelected().size(); i++) {
if (MyAdapter.getIsSelected().get(i)) {
//我们遍历hashmap,遇到为true的项则往我们的新的数组里面去存这一项的position
//这里我们循环遍历我们保存选中项的数组,找到数组中的第一个非空的位置,插入我们的选中的position
for (int j =0;j<array.length;j++){
if (null ==array[j]){
array[j] = i;
break;
}
}
}
}
return array;
}
}
然后看看我们的Adapter
MyAdapter.java
public class MyAdapter extends BaseAdapter {
private Context mContext;
private String [] data;
// 用来保存选中状况
private static HashMap<Integer, Boolean> isSelected;
public MyAdapter(Context mContext, String [] data){
this.mContext = mContext;
this.data = data;
isSelected = new HashMap<>();
initList();
}
//首先将所有的item项置为未选中状态
private void initList() {
for (int i =0 ;i<data.length;i++){
getIsSelected().put(i,false);
}
}
@Override
public int getCount() {
return data.length;
}
@Override
public Object getItem(int i) {
return data[i];
}
@Override
public long getItemId(int i) {
return i;
}
@Override
public View getView(int i, View view, ViewGroup viewGroup) {
ViewHolder holder;
if (view ==null){
holder = new ViewHolder();
view = LayoutInflater.from(mContext).inflate(R.layout.list_item,null);
view.setTag(holder);
}else {
holder = (ViewHolder) view.getTag();
}
holder.mItemText = (TextView) view.findViewById(R.id.text_item);
holder.mChooseRadio = (RadioButton) view.findViewById(R.id.rbtn_item);
holder.mItemText.setText(data[i]);
holder.mChooseRadio.setChecked(getIsSelected().get(i));
return view;
}
public static HashMap<Integer, Boolean> getIsSelected() {
return isSelected;
}
public class ViewHolder {
TextView mItemText;
RadioButton mChooseRadio;
}
}
最后放上这个demo的代码,需要的同学可以去下载>http://download.csdn.net/detail/qq_27561483/9617329
关于ListView的addHeaderView()和addFooterView()
我们在用ListView展示东西的时候,有时候可能还需要给listviw添加头部或者底部的view,例如下拉刷新,上拉加载等。关于使用方法很简单 ,就是listView.addHeaderView(view);
不过这个addHeaderView()方法可以传一个view ,也可以传三个参数如:addHeaderView(view,null,false); 最后一个参数是用来标识我们的view是否可以被selected,也就是我们监听ListView的OnItemClickListener方法时候,点击headerView 会不会执行这个方法。如果设置了false,就不会执行onItemClickListener方法。
addHeaderView 和addFooterView 都必须要放到 setAdapter()方法之前调用。因为我们在调用setAdapter()方法的时候,系统会判断我们是否添加了头部或者底部,如果添加了,则会新生成一个tempadapter,我们可以用listview.getAdapter().getCount()方法去看我们listview的adpter的数量 和我们传进去的adapter的数量是否相同,如果 没有添加头部和底部,则肯定是相同的;如果添加了。则比我们传进去的数值大。
这就导致了,我们自定义的adapter 中的 getItem方法里面返回的position是不包括headerView和footerView的,position是从0开始的,和我们传递进去的List是一一对应的。
但是在Activity的OnItemClickListener中:
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {
Toast.makeText(MainActivity.this, "点击了第" + position + "项", Toast.LENGTH_SHORT).show();
}
});
这里的position是包含了我们的footerView 和HeaderView 之后的position
就是说系统生成的tempadapter 的position 为0的项指的是我们的HeaderView,而不是我们之前的传到自定义adapter中的第一项数据。所以,当我们要在onItemClick方法中处理的事件,点击的item要获取对应的我们显示的数据,则要在这方法里面用 position减去1,这样才能对的是我们的真实位置。
最后附上关于HeadView和FooterView 的一个小demo>http://download.csdn.net/detail/qq_27561483/9621086