属性(doc)
fadingEdge属性用来设置拉滚动条时 ,边框渐变的放向。none(边框颜色不变),horizontal(水平方向颜色变淡),vertical(垂直方向颜色变淡)。
fadingEdgeLength用来设置边框渐变的长度。
<!--divider:item的分割线 ,dividerHeight 分割线的高度-->
<!--cacheColorHint 缓存颜色 -->
<!--descendantFocusability:父控件item与子控件(button等)谁先获取焦点 ,Descendants:子孙,后代 -->
<!--ListView设置为 layout_height="wrap_content" listview的最后一条数据是不显示item的分割线的,必须是match_parent或者fill_parent -->
<ListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:dividerHeight="2dp"
android:divider="@android:drawable/divider_horizontal_bright"
android:cacheColorHint="@android:color/transparent"
android:descendantFocusability="beforeDescendants">
</ListView>
<View
android:layout_width="fill_parent"
android:layout_height="1dip"
android:background="?android:attr/listDivider" />
使用
准备数据
adapter的创建
listView.setAdapter(adapter);
adapter
常用BaseAdapter,其余还有ArrayAdapter/SimpleAdapter
ArrayAdapter
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,arr);
SimpleAdapter
SimpleAdapter adapter = new SimpleAdapter(this,list,android.R.layout.simple_list_item_2,
new String[]{"name","sex"},new int[]{android.R.id.text1,android.R.id.text2});
优化
复用convertview + 创建ViewHolder
复用convertview:减少了itemView的创建
ViewHolder:减少了方法findViewById(resId)的次数
分批、分页加载数据:提高了用户的体验
MyAdapter.java
public class MyAdapter extends BaseAdapter {
public Context context;
public List<StudentBean> list;
// 构造方法(alt+shift+s+c),一般都需要context+list
public MyAdapter(Context context, List<StudentBean> list) {
super();
this.context = context;
this.list = list;
}
// 共有多少条数据,必填
@Override
public int getCount() {
return list.size();
}
// 点击的item的数据
@Override
public Object getItem(int position) {
return null;
}
// 点击的item的position
@Override
public long getItemId(int position) {
return 0;
}
// 返回item的view
@Override
public View getView(int position, View convertView, ViewGroup parent) {
// listView优化一:复用convertView
ViewHolder holder ;
if (convertView == null) {
convertView = View.inflate(context, R.layout.item_main, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
StudentBean stu = list.get(position);
holder.tv_name.setText(stu.name);
holder.tv_number.setText(stu.number);
return convertView;
}
// listView优化二:ViewHolder:减少findViewById(resID)
public class ViewHolder {
public TextView tv_name;
public TextView tv_number;
public ViewHolder(View convertView) {
super();
tv_name = (TextView) convertView.findViewById(R.id.tv_name);
tv_number = (TextView) convertView.findViewById(R.id.tv_number);
}
}
}
ViewHolder
也可以这样写
static class ViewHolder {
TextView tv_name;
TextView tv_number;
}
findViewById(R.id.tv)
写在getView(...)
中
@Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_app, parent, false);
holder = new ViewHolder();
holder.tv= (TextView) convertView.findViewById(R.id.tv);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.tv.setText(list.get(position));
}
分批加载的思想
MainActivity.java
滚动监听: OnScrollListener():方法:onScrollStateChanged(…)+onScroll(…)
判断listview是不是滑动了底部: listView.getLastVisiblePosition() == list.size() -1
public class MainActivity extends Activity {
private ListView listView;
private List<StudentBean> list;
private ArrayList<StudentBean> list2;
private MyAdapter adapter;
public int i = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initData();
findView();
initView();
}
private void initData() {
list = new ArrayList<StudentBean>();
for (int i = 0; i < 20; i++) {
StudentBean stu = new StudentBean();
stu.name = "小王" + i;
stu.number = "学号:" + i;
list.add(stu);
}
}
private void initView() {
adapter = new MyAdapter(this, list);
listView.setAdapter(adapter);
/**
* ListView优化三:分批加载 需要:1 监听事件 2 获取数据
*/
listView.setOnScrollListener(new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case SCROLL_STATE_IDLE:// 手指离开屏幕,不接触
int lastVisiblePosition = listView.getLastVisiblePosition();
if (lastVisiblePosition == list.size() -1) {
loadData();
}
break;
case SCROLL_STATE_TOUCH_SCROLL:// 滑动,接触屏幕
break;
case SCROLL_STATE_FLING:// 猛的一滑,手指不接触屏幕
break;
default:
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
}
protected void loadData() {
if (i < 3) {
i++;
list2 = new ArrayList<StudentBean>();
for (int i = 0; i < 20; i++) {
StudentBean stu = new StudentBean();
stu.name = "小王" + i;
stu.number = "学号:" + i;
list2.add(stu);
}
list.addAll(list2);
// 显示数据
showData();
}
}
private void showData() {
if (list2.size() == 0) {
Toast.makeText(MainActivity.this, "没有数据了", 0);
} else {
adapter.notifyDataSetChanged();
}
}
private void findView() {
listView = (ListView) findViewById(R.id.listview);
}
}
问题
ListView的item的height无效(设置具体的值无效)
原因:
ListView的item的根布局的height设置为fill_parent / match_parent / wrap_content是一样的。它会以子view中最大的高为item的高。
所以:item 和 子view 设置高度值要合理
解决方法:
方法一:item的跟布局设置参数:android:minHeight=”50dp”
方法二:item的子view的高度设置具体的数值
点击item,怎么变色?
listview的item点击时可以通过设置seletor改变颜色,但是松开后就会还原成原色,
所以,要想实现,item点击后颜色改变,就需要在listview的adapter中的getView(..)中作比较:
listview.setOnItemClickListener()中的position,与listview的adapter中的getView(..)方法中的position,
若相等,则改变item背景和文字的颜色。
为什么已经给item设置了selector,但是按下的时候item不变色?
原因:
android:state_pressed="true" android:state_selected="true"
不能同时出现在一个item中。
解决方法:
去掉
android:state_selected="true"
,只保留android:state_pressed="true"
ScrollView嵌套ListView
最简单的方法是重写onMeasure()
方法
public class CustomListView extends ListView {
public CustomListView(Context context) {
super(context);
}
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomListView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int height = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, height);
}
}
添加Header + Footer
View headerView = View.inflate(this, R.layout.layout_header, null);
View footerView = View.inflate(this, R.layout.layout_footer, null);
listView.addHeaderView(headerView);
listView.addFooterView(footerView);
ListView的单选和多选
要实现ListView的单选和多选需要以下几步
1. 在xml中给ListView的添加属性: choiceMode
(必要)listSelector
(改变背景是需要)
2. 获取选中的item的id
listSelector + choiceMode
listView_selector
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/c2" android:state_checked="true" android:state_pressed="true"/>
<item android:drawable="@color/colorAccent"/>
</selector>
choiceMode
我们要实现单选和多选,先要在xml中给ListView
添加属性choiceMode
<ListView
...
android:choiceMode="singleChoice"/>
如果需要改变ListView
的背景色,还需要加入:
android:listSelector="@drawable/list_selector"
choiceMode
也可以java设置:
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
choiceMode
还有多种:
choiceMode | 属性 | item |
---|---|---|
singleChoice | 单选 | android.R.layout.simple_list_item_single_choice |
multipleChoice | 多选 | android.R.layout.simple_list_item_multiple_choice |
multipleChoiceModal | 长按进入多选模式 | android.R.layout.simple_list_item_multiple_choice |
none | 普通模式 | - |
ListView
的choiceMode
有3种:
android:choiceMode="singleChoice"
android:choiceMode="multipleChoice"
android:choiceMode="multipleChoiceModal"
android:choiceMode="none"
<ListView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@drawable/list_selector"
android:choiceMode="multipleChoiceModal"/>
<CheckedTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"android:drawableLeft="?android:attr/listChoiceIndicatorMultiple"/>
单选和多选中用到的方法
ListView的方法
方法 | 说明 |
---|---|
getCheckedItemCount() | 选中的item数量 |
getCheckItemIds() | 过时方法,选中的item的id,就是adapter中的getItemId(int position)的返回值,单选模式只有一个,checkItemIds[0] |
getCheckedItemIds() | 选中的item的id,需配合方法adapter中的方法hasStableIds()(返回true |
getCheckedItemPosition() | 选中item的position,适用于单选模式 |
getItemAtPosition(position) | 获取position对应的数据 |
SparseBooleanArray checkedItemPositions = listView.getCheckedItemPositions()
存储的是键值对,<Ingeter,Boolean>
如果我们先点击的pisition是:1 2--》1
那么值:
1 true
2 true
1 false
如何获取单选的值?
第一种:最简单(仅适用单选)
int checkedItemPosition = listView.getCheckedItemPosition();
第二种:只取第一个值
long[] checkItemIds = listView.getCheckItemIds();
long[] checkedItemIds = listView.getCheckedItemIds();
int position= checkItemIds[0];
int position= checkedItemIds[0];
如何多选的数据的position?
多选情况下,如何获取多数据?其实就是获取选中的position
的集合,有2种方法,上面的是第一种
第一种:`
long[] checkItemIds = listView.getCheckItemIds();
这个方法已经过时了,但是还可以用,id就是我们在adapter中设置的getItemId(...)
,必须返回position
。
@Override
public long getItemId(int position) {
return position;
}
第二种:
SparseBooleanArray array = listView.getCheckedItemPositions();
SparseBooleanArray
本质是hashMap,存放的是键值对,在listView的多选中,key就是position
valueAt(i)
获取的是value,keyAt(i)获取的是key。所以,我们获取到value==true
的key
for (int i = 0; i < array.size(); i++) {
if(array.valueAt(i)){
int key = array.keyAt(i);
}
}
第三种:
long[] checkedItemIds = listView.getCheckedItemIds();
单独用上面的方法是无效的,获取不到数据,如果想要获取到选中的数据,那么adapter中必须重写方法hasStableIds()
private class MyAdapter extends BaseAdapter {
...
// 要想使用getCheckedItemIds(),必须返回true
@Override
public boolean hasStableIds() {
return true;
}
}
单选
单选:item用系统的android.R.layout.simple_list_item_single_choice
item用的是android.R.layout.simple_list_item_single_choice
,不需要listSelector
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="singleChoice"/>
ArrayAdapter<String> adapter = new ArrayAdapter<>(OSSingleChoiceActivity.this, android.R.layout.simple_list_item_single_choice, dataList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String text = (String) listView.getItemAtPosition(position);
Log.d(TAG, "text=" + text);
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//单选获取选中item的position
int checkedItemPosition = listView.getCheckedItemPosition();
Log.d(TAG, "checkedItemPosition=" + checkedItemPosition);
}
});
单选:改变item的背景色
由于item是自定义的,改变背景色时需要添加android:listSelector="@drawable/list_selector"
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:listSelector="@drawable/list_selector"
android:choiceMode="singleChoice"/>
item_single_choice:
<?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">
<TextView
android:id="@+id/tv_item_single_choice"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="aaaa"
android:gravity="center"/>
</LinearLayout>
如果是单选,在给ListView
设置listSelector
时:
直接写即可:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/colorAccent"/>
</selector>
不需要全写:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/transparent" android:state_checked="true" android:state_pressed="true"/>
<item android:drawable="@color/colorAccent" android:state_checked="false"/>
</selector>
但是如果是多选,设置listSelector
是无效的,这是需要重写item的根布局的类。
listView.setAdapter(new MyAdapter());
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String text = (String) listView.getItemAtPosition(position);
Log.d(TAG, "text=" + text);
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//选中的item的id,注意:adapter中的getItemId(int position)返回position
long[] checkItemIds = listView.getCheckItemIds();
StringBuffer sb1 = new StringBuffer();
for (int i = 0; i < checkItemIds.length; i++) {
sb1.append(checkItemIds[i] + ",");
}
Log.d(TAG, "sb1=" + sb1.toString());
}
});
long[] checkedItemIds = listView.getCheckedItemIds();
是无效的,获取不到数据,如果想要获取到选中的数据,那么adapter中必须重写方法hasStableIds()
private class MyAdapter extends BaseAdapter {
...
// 要想使用getCheckedItemIds(),必须返回true
@Override
public boolean hasStableIds() {
return true;
}
}
或者:
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
listView.setSelector(R.color.color3);
}
});
复杂item的:包含CheckedTextView/CheckBox
其实就是采用标志位实现的,为了获取选中的item,在点击item的时候我把它放在了集合中(其实这样复杂了)。一般的做法是:获取选中的item,直接利用for找出选中的item即可。
效果图:
xml中的ListView
复杂的item,单选不需要添加
android:choiceMode="singleChoice"
,添加了页无效
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
item:
<?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="80dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<CheckedTextView
android:id="@+id/checkTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:drawableLeft="?android:attr/listChoiceIndicatorSingle"
android:gravity="center"/>
<!--android:clickable="false"-->
<!--android:focusable="false"-->
<!--android:focusableInTouchMode="false"-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_choice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="aaaa"/>
<TextView
android:id="@+id/tv_choice2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="bbb"/>
</LinearLayout>
</LinearLayout>
如果item中只有一个带有点击事件的view,那么CheckedTextView下面的3属性不去掉也不影响。
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
在给bean添加字段selected
,用来表示item是否被选中。
public class Person {
private String name;
private int age;
private boolean selected;//是否选中
setX();
getX();
}
由于是单选,我们在选中当前项的同时,还需要把其它的“不选中”
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
//单选需要把其他的改成“不选”
for (int i = 0; i < perList.size(); i++) {
if (i == position) {
// perList.get(i).setSelected(true); //必须选中一个
perList.get(i).setSelected(!perList.get(i).isSelected());//可以不选
Log.d(TAG, "position=" + position + "点击即选中:" + perList.get(i).isSelected());
} else {
perList.get(i).setSelected(false);
}
}
selectPosition = position;
adapter.notifyDataSetChanged();
}
});
看上面的代码,如果我们必须选中一个的话,那么点击第n个,就选中了第n个,再次点击不会取消
perList.get(i).setSelected(true); /
如果我们可以不选的话,那么点击第n个,就选中了第n个,再次点击会取消。
perList.get(i).setSelected(!perList.get(i).isSelected());
点击Button
获取选中的item
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//当必须选中一个时
// Log.d(TAG, "selectPosition=" + selectPosition);
//当可以不选时
if (perList.get(selectPosition).isSelected()) {
Log.d(TAG, "您选中的是=" + selectPosition);
} else {
Log.d(TAG, "您没有选择");
}
}
});
也可以:
int checkedItemPosition = listView.getCheckedItemPosition();
多选
多选:item用系统的android.R.layout.simple_list_item_multiple_choice
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="multipleChoice"/>
//listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
ArrayAdapter<String> adapter = new ArrayAdapter<>(OSMultipleChoiceActivity.this, android.R.layout.simple_list_item_multiple_choice, dataList);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
String text = (String) listView.getItemAtPosition(position);
Log.d(TAG, "text=" + text);
}
});
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
long[] checkItemIds = listView.getCheckItemIds();
StringBuffer sb1 = new StringBuffer();
for (int i = 0; i < checkItemIds.length; i++) {
sb1.append(checkItemIds[i]+",");
}
Log.d(TAG, "选中的position集合是:sb1 =" + sb1.toString());
}
});
多选:改变item的背景色
单选:给ListView
设置listSelector
即可。
多选:给ListView
设置listSelector
是无效的,需要重写item的根布局来改变item的背景色。
item_multiple_choice
:
<?xml version="1.0" encoding="utf-8"?>
<com.cqc.listviewchoicedemo.ChoiceLiearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--com.cqc.listviewchoicedemo.ChoiceLiearLayout-->
<TextView
android:id="@+id/tv_item_multiple_choice"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="aaaa"
android:gravity="center"/>
</com.cqc.listviewchoicedemo.ChoiceLiearLayout>
item的根布局:ChoiceLinearLayout
:
public class ChoiceLinearLayout extends LinearLayout implements Checkable {
private boolean mChecked;
public ChoiceLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setChecked(boolean checked) {
mChecked = checked;
//改变item的背景色
setBackgroundResource(checked? R.color.colorPrimary : android.R.color.transparent);
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void toggle() {
setChecked(!mChecked);
}
}
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="multipleChoice"/>
item:
<?xml version="1.0" encoding="utf-8"?>
<com.cqc.listviewchoicedemo.ChoiceLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--com.cqc.listviewchoicedemo.ChoiceLiearLayout-->
<TextView
android:id="@+id/tv_item_multiple_choice"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="aaaa"
android:gravity="center"/>
</com.cqc.listviewchoicedemo.ChoiceLinearLayout>
如何获取选中的数据的position?
有3种方法
第一种:
long[] checkedItemIds = listView.getCheckedItemIds();
单独用上面的方法是无效的,获取不到数据,如果想要获取到选中的数据,那么adapter中必须重写方法hasStableIds()
private class MyAdapter extends BaseAdapter {
...
// 要想使用getCheckedItemIds(),必须返回true
@Override
public boolean hasStableIds() {
return true;
}
}
第二种:
long[] checkItemIds = listView.getCheckItemIds();
第三种
StringBuffer sb3 = new StringBuffer();
SparseBooleanArray array = listView.getCheckedItemPositions();
for (int i = 0; i < array.size(); i++) {
if(array.valueAt(i)){
sb3.append(array.keyAt(i)+",");
}
}
复杂item的:包含CheckedTextView/CheckBox
多选:定义一个集合,我们只需要把选中的添加到集合中,把取消的从集合中删掉, 其实就是采用标志位实现的,为了获取选中的item,在点击item的时候我把它放在了集合中(其实这样复杂了)。获取选中的item,直接利用for找出选中的item即可。
在选中一个item后,不需要调用
adapter.notifyDataSetChanged();
,因为item中包含CheckedTextView/CheckBox
,直接调用holder.checkTV.toggle()
即可。但划出屏幕再回来的时候,原来选中的item变成未选中了,这就需要把改变person的状态perList.get(position).setSelected(holder.checkTV.isChecked());
或者定义private HashMap<Integer, Boolean> selectMap = new HashMap<>();
记录是否选中
ListView
不需要添加android:choiceMode="multipleChoiceModal"
,因为item是我们自定义的,不是android.R.layout.simple_list_item_multiple_choice
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
<!--android:choiceMode="multipleChoiceModal"-->
item包含CheckedTextView
<?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="80dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<CheckedTextView
android:id="@+id/checkTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="false"
android:drawableLeft="?android:attr/listChoiceIndicatorMultiple"
android:gravity="center"/>
<!--android:clickable="false"-->
<!--android:focusable="false"-->
<!--android:focusableInTouchMode="false"-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/tv_choice"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="aaaa"/>
<TextView
android:id="@+id/tv_choice2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="bbb"/>
</LinearLayout>
</LinearLayout>
在listView的点击事件中保存选中的position,当使用toggle()
方法是,就不需要adapter.notifyDataSetChanged();
,注意:positionList
是用来保存选中的item的position和删除未选中的item的position。
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
MyViewHolder holder = (MyViewHolder) view.getTag();//注意是: view.getTag() 不是adapterView.getTag()
holder.checkTV.toggle();//页面显示
//不加这句行代码的后果:上滑后下滑,原来选中的item变成没选中
perList.get(position).setSelected(holder.checkTV.isChecked());
}
});
获取选中的数据:根据 标志位遍历集合。
for (int i = 0; i < perList.size(); i++) {
if (perList.get(i).isSelected()) {
sb2.append(perList.get(i).getName() + ",");
}
}
当然,更好的方式是先改数据,再改根据数据显示CheckBox是否选中
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
Person info = perList.get(position);
info.setSelected(!info.isSelected());
MyViewHolder holder = (MyViewHolder) view.getTag();//注意是: view.getTag() 不是adapterView.getTag()
holder.checkTV.setChecked(info.isSelected());
}
});
如果没有给bean设置标志位private boolean selected;
,那么就必须设置一个hashMap
用来保存是否选中
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapterView, View view, int position, long id) {
MyViewHolder holder = (MyViewHolder) view.getTag();//注意是: view.getTag() 不是adapterView.getTag()
holder.checkTV.toggle();//页面显示
// 如果不加这一行代码:当划出屏幕在划回来的时候,原来选中的状态没了
adapter.selectMap.put(position, holder.checkTV.isChecked());
}
});
获取选中的数据:
for (int i = 0; i < dataList.size(); i++) {
if (adapter.selectMap.get(i)) {
sb2.append(dataList.get(i) + ",");
}
}
adapter中定义HashMap
private class MyAdapter extends BaseAdapter {
private HashMap<Integer, Boolean> selectMap = new HashMap<>();
public MyAdapter() {
for (int i = 0; i < dataList.size(); i++) {
selectMap.put(i, false);
}
}
...
@Override
public View getView(int position, View convertView, ViewGroup viewGroup) {
...
holder.checkTV.setChecked(selectMap.get(position));
return convertView;
}
}
属性duplicateParentState
说明
在上面的单选和多选中,为了让子view变化,我们使用了标志位,然后adapter.notifyDataSetChanged()
刷新数据改变子view的显示状态,这样比较麻烦。Android已经提供给我们一个非常重要的属性duplicateParentState
来实现这个功能,不在需要标志位和adapter.notifyDataSetChanged()
,但是需要重写item的布局;由于是单选或多选,还需要实现接口Checkable
duplicateParentState
:让子view和父view(item)的状态保持一致,即当父view(item)是checked时,子view也是checked,所以凡是(item)跟布局下的子view或子layout,都需要添加这个属性。
CheckableLinearLayout
首先是重写的CheckableLinearLayout
,并实现了Checkable
接口,该类作为item的根布局的类
public class CheckableLinearLayout extends LinearLayout implements Checkable {
private static final int[] CHECKED_STATE_SET = {android.R.attr.state_checked};//选中的集合
private boolean mChecked = false;
public CheckableLinearLayout(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
@Override
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
@Override
public boolean isChecked() {
return mChecked;
}
@Override
public void toggle() {
setChecked(!mChecked);
}
@Override
public int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
}
ListView
多选
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="multipleChoice"/>
如果是单选choiceMode
就改成
android:choiceMode="singleChoice"
item
item中子view或子layout中使用属性duplicateParentState
,同时根布局使用的而是我们自定义的CheckableLinearLayout
<?xml version="1.0" encoding="utf-8"?>
<com.cqc.listviewchoicedemo.view.CheckableLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="50dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:duplicateParentState="true"
android:orientation="vertical">
<TextView
android:id="@+id/tv_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:gravity="center"
android:text="aaaa"
android:textColor="@color/item_text_color_seletor"/>
<TextView
android:id="@+id/tv_age"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:gravity="center"
android:text="bbb"
android:textColor="@color/item_text_color_seletor"/>
</LinearLayout>
<ImageView
android:id="@+id/iv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:duplicateParentState="true"
android:src="@drawable/item_duplicate_multiple_img_seletor"/>
</com.cqc.listviewchoicedemo.view.CheckableLinearLayout>
获取选中的item的position
long[] checkItemIds = listView.getCheckItemIds();
也就是adapter
中getItemId(int position)
的返回值
@Override
public long getItemId(int position) {
return position;
}
长按item进入多选模式
第一步:给ListView
设置choiceMode
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:choiceMode="multipleChoiceModal"/>
item用的是android.R.layout.simple_list_item_multiple_choice
第二步:实现ListView.MultiChoiceModeListener
public class MyMultiChoiceModeListener implements ListView.MultiChoiceModeListener {
@Override
public void onItemCheckedStateChanged(ActionMode actionMode, int position, long id, boolean checked) {
//item 被点击后的操作,可以不做处理
}
@Override
public boolean onCreateActionMode(ActionMode actionMode, Menu menu) {
//返回true
return true;
}
@Override
public boolean onPrepareActionMode(ActionMode actionMode, Menu menu) {
//返回true
return true;
}
@Override
public boolean onActionItemClicked(ActionMode actionMode, MenuItem menuItem) {
//返回true
return true;
}
@Override
public void onDestroyActionMode(ActionMode actionMode) {
//不做处理
}
}
第三步:给ListView
设置监听
MyMultiChoiceModeListener listener = new MyMultiChoiceModeListener();
listView.setMultiChoiceModeListener(listener);
全选
这里ListView
在xml布局中使用了android:choiceMode="multipleChoice"
,item采用的是系统提供的android.R.layout.simple_list_item_multiple_choice
全不选,这样会把所有选中的清除掉
listView.clearChoices();
全选是利用for循环listView.setItemChecked(i, true)
:
for (int i = 0; i < dataList.size(); i++) {
listView.setItemChecked(i, true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Log.d(TAG,"onOptionsItemSelected");
switch (item.getItemId()) {
case R.id.select_all:
if (listView.getCheckedItemCount() == adapter.getCount()) {
listView.clearChoices();
item.setTitle("全选");
} else {
for (int i = 0; i < dataList.size(); i++) {
listView.setItemChecked(i, true);
}
item.setTitle("全不选");
}
adapter.notifyDataSetChanged();
break;
}
return super.onOptionsItemSelected(item);
}
demo
https://git.oschina.net/AndroidUI/ListViewChoiceDemo
setEmptyView (View emptyView)
效果图
emptyView在xml中的设置
emptyView
和ListView
在一个xml中,emptyView
在下面
<?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"
tools:context="com.cqc.listviewdemo.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="刷新后重新显示数据"/>
<Button
android:id="@+id/btn2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="添加EmptyView"/>
</LinearLayout>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/layout_empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawableTop="@mipmap/empty"
android:text="没有数据"/>
</LinearLayout>
</LinearLayout>
怎么才可以显示emptyView?
当ListView
的数据list中没有数据时,或者list.clear()
或者listView.setAdapter(null)
后,就会出现EmptyView
adapter = new ArrayAdapter<>(MainActivity.this, android.R.layout.simple_list_item_1, itemList);
listView.setAdapter(adapter);
listView.setEmptyView(layout_empty_view);
btn1.setOnClickListener(this);
btn2.setOnClickListener(this);
layout_empty_view.setOnClickListener(this);
点击emptyView后重新加载数据
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.btn1:
itemList.clear();
for (int i = 0; i < 100; i++) {
itemList.add("李" + i);
}
listView.setAdapter(adapter);//可以
// adapter.notifyDataSetChanged();//无效,因为listView.setAdapter(null);
break;
case R.id.btn2:
listView.setAdapter(null);
break;
case R.id.layout_empty_view:
itemList.clear();
for (int i = 0; i < 100; i++) {
itemList.add("李" + i);
}
listView.setAdapter(adapter);//可以
// adapter.notifyDataSetChanged();//无效,因为listView.setAdapter(null);
break;
}
}
源码
https://git.oschina.net/AndroidUI/ListViewDemo
ListActivity & ListFragment
ListActivity
其实就是ListView
& Activity
的结合,
LisFragment
其实就是ListView
& Fragment
的结合,
没有区别,只是使用起来比较方便。
注意事项
- 不需要方法
setContentView (int layoutResID)
- 如果想要显示其他控件,如
TextView
、ImageView
,那么ListView
的id必须是android:id="@android:id/list"
,如果使用emptyView
,那么他的id必须是android:id="@android:id/empty"
使用前提:
当前Activity
继承ListActivity
当前Fragmetn
继承Fragment
ListActivity:无setContentView(…)
setListAdapter(adapter)
:设置adapter
onListItemClick(...)
:ListView
的item的点击事件
public class MainActivity extends ListActivity {
private List<String> list = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//不需要setContentView
for (int i = 0; i < 100; i++) {
list.add("ListActivity:不需要setContentView" + i);
}
setListAdapter(new ArrayAdapter<String>(MainActivity.this, android.R.layout.simple_list_item_1, list));
}
/**
* ListView的点击事件
*/
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String text = (String) l.getItemAtPosition(position);
Log.d(TAG, "text=" + text);
startActivity(new Intent(MainActivity.this, ListActivity.class));
}
}
ListActivity:有setContentView(…)
setContentView(R.layout.activity_list);
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.cqc.listactivityfragment01.MainActivity">
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<TextView
android:id="@android:id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:drawable/ic_dialog_email"
android:text="没有数据"/>
</LinearLayout>
ListFragment
同ListActivity一样,可以不需要onCreateView()
,如果需要添加其它控件,可以重写onCreateView()
,但ListView
的id必须是android:id="@android:id/list"
,emptyView
的id必须是android:id="@android:id/empty"
public class MyFrag extends ListFragment {
private List<String> list = new ArrayList<>();
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
for (int i = 0; i < 100; i++) {
list.add("ListFragment" + i);
}
setListAdapter(new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, list));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
String text = (String) l.getItemAtPosition(position);
Log.d(TAG, "ListActivity--text=" + text);
}
}
Demo
http://git.oschina.net/AndroidUI/ListActivityFragment
Android Developer中关于ListActivity
& LisFragment
介绍:
ListActivity
:https://developer.android.google.cn/reference/android/app/ListActivity.html
LisFragment
:https://developer.android.google.cn/reference/android/app/ListFragment.html
XListView
github:XListView-Android
比较久的刷新ListView
的库。需要复制类+xml+图片。
- 3个类XListView
、XListViewHeader
、XListViewFooter
- xlistview_header.xml
、xlistview_footer.xml
- xlistview_arrow.png
- strings.xml
xml
把XListView当做ListView来使用即可。
<com.cqc.xlistview01.view.XListView
android:id="@+id/xListView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
setAdapter
xListView.setAdapter(adapter);
setOnItemClickListener
xListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d(TAG,"position="+position);
}
});
setXListViewListener
xListView.setXListViewListener(new XListView.IXListViewListener() {
@Override
public void onRefresh() {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
list.add(0,"refresh add item");
adapter.notifyDataSetChanged();
xListView.stopLoadMore();
xListView.stopRefresh();
xListView.setRefreshTime("刚刚");
}
}, 3000);
}
@Override
public void onLoadMore() {
xListView.stopLoadMore();
xListView.stopRefresh();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
list.add("load more add item");
adapter.notifyDataSetChanged();
xListView.stopLoadMore();
xListView.stopRefresh();
}
}, 3000);
}
});
停止刷新
xListView.stopLoadMore();
xListView.stopRefresh();
addHeaderView() + addFooterView()
xListView.addHeaderView(headView);
xListView.addFooterView(footerView);
setPullLoadEnable()+ setPullRefreshEnable()
xListView.setPullLoadEnable(false);//上拉加载
xListView.setPullRefreshEnable(false);//下拉刷新
OnScrollListener
通过滑动监听,我们可以处理上拉加载|下拉刷新+索引提示。
常见的就是滑动通讯录,手机屏幕中央会显示姓名的第一个字母,下面我们先来实现这个功能。
索引提示
Demo:http://git.oschina.net/AndroidUI/ListViewIndex01
需求:当滑动通讯录时,手机屏幕中央会显示姓名的第一个字母。
由于需要的是字母,而不是姓名,所以我们需要jpinyin-1.0.jar
,它可以把汉字转换成拼音字母。
显示首字母有2种方法:
- 用Toast
显示
- 用TextView
显示,通知控制它显示时长
用Toast
显示
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//获取首字母
String s = PinyinHelper.convertToPinyinString(names[firstVisibleItem], "", PinyinFormat.WITHOUT_TONE);
String substring = s.substring(0, 1).toUpperCase();
ToastUtil.showShortToast(context, substring);
}
});
用TextView
显示,通知控制它显示时长
用TextView
显示,当什么时候隐藏呢?
- 用Handler延迟1s隐藏,
- 根据判断滑动状态,在SCROLL_STATE_IDLE(手指离开屏幕)时隐藏
用Handler延迟1s隐藏:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
String s = PinyinHelper.convertToPinyinString(names[firstVisibleItem], "", PinyinFormat.WITHOUT_TONE);
String substring = s.substring(0, 1).toUpperCase();
if (tv_pin_yin.getVisibility() != View.VISIBLE) {
tv_pin_yin.setVisibility(View.VISIBLE);
}
tv_pin_yin.setText(substring);
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
if (tv_pin_yin.getVisibility() == View.VISIBLE) {
tv_pin_yin.setVisibility(View.GONE);
}
}
}, 1000);
}
});
在SCROLL_STATE_IDLE(手指离开屏幕)时隐藏:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
break;
case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
break;
case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
if (tv_pin_yin.getVisibility() == View.VISIBLE) {
tv_pin_yin.setVisibility(View.GONE);
}
break;
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
String s = PinyinHelper.convertToPinyinString(names[firstVisibleItem], "", PinyinFormat.WITHOUT_TONE);
String substring = s.substring(0, 1).toUpperCase();
if (tv_pin_yin.getVisibility() != View.VISIBLE) {
tv_pin_yin.setVisibility(View.VISIBLE);
}
tv_pin_yin.setText(substring);
}
});