Android_UI控件:ListView 属性+使用+优化+Header+Footer+单选+多选+全选+setEmptyView (View emptyView)

这里写图片描述

属性(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);
    }
}
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知多少(下)ChoiceMode详解

要实现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普通模式-

ListViewchoiceMode有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==truekey

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();

也就是adaptergetItemId(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中的设置

emptyViewListView在一个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)
  • 如果想要显示其他控件,如TextViewImageView,那么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个类XListViewXListViewHeaderXListViewFooter
- xlistview_header.xmlxlistview_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);
    }
});
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值