摸鱼学Android 十四(列表视图+适配器)

  • UI控件之四
  • ListView(列表视图)
  • 1 常用属性
  • 2 方法
  • 3 说明
  • 4 实例
  • 5 ListView问题
  • 5.1 焦点问题
  • 5.2 checkbox错位问题
  • 附录
  • Adapter(适配器)
  • ArrayAdapter(数组适配器)
  • 1 说明
  • 2 参数
  • 3 系统提供的样式
  • SimpleAdapter(简单适配器)
  • 1 说明
  • 2 参数
  • BaseAdapter(通用适配器)
  • 1 说明
  • 2 自定义Adapter
  • 3 对BaseAdapter优化
  • 4 可复用的自定义Adapter


UI控件之四

ListView(列表视图)

1 常用属性

  • cacheColorHint:拖动背景色,若为图片,设置透明(#00000000)
  • divider:设置分隔条,可以用颜色分割,也可以用drawable资源分割
  • dividerHeight:设置分隔条的高度
  • fadingEdge:上边和下边的阴影
  • scrollbars:滚动条
  • fastScrollEnabled:快速滚动效果
  • fadeScrollbars:自动隐藏/显示滚动条
  • stackFromBottom:显示列表最下面
  • transciptMode:自动滑到最底部
  • footerDividersEnabled:是否在表尾前绘制一个分隔条,默认为true
  • headerDividersEnabled:是否在表头前绘制一个分隔条,默认为true

2 方法

  • addHeaderView(View v):添加表头,括号中的参数是一个View对象
  • addFooterView(View v):添加表尾,括号中的参数是一个View对象
  • addHeaderView(headView, null, false):设置表头是否可以被选中
  • addFooterView(View,view,false):设置表尾是否可以被选中
  • setAdapter(Adapter adapter):映射数据

PS:表头表尾设置方法必须在setAdapter之前。

3 说明

列表的显示需要3要素:

  • ListVeiw:用来展示列表的View
  • Adapter: 适配器,用来把数据映射到ListView上的中介
  • 数据源: 具体的将被映射的字符串,图片,或者基本组件

4 实例

  1. 加入布局
<ListView
            android:id="@+id/listView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
  1. 显示数据
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //要显示的数据
        String[] strs = {"A","B","C","D","E"};
        //创建ArrayAdapter
        ArrayAdapter<String> adapter = new ArrayAdapter<String>
                (this,android.R.layout.simple_expandable_list_item_1,strs);
        //获取ListView对象,通过调用setAdapter方法为ListView设置Adapter设置适配器
        ListView list_test = (ListView) findViewById(R.id.listView);
        list_test.setAdapter(adapter);
    }
}

显示:

Android 叠层 viewpager_android


PS:也可在res\value下创建一个数组资源的xml文件:arrays.xml,然后在listview中使用entries

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string-array name="myArray">
        <item>A</item>
        <item>B</item>
        <item>C</item>
        <item>D</item>
        <item>E</item>
    </string-array>
</resources>
<ListView
            android:id="@+id/listView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" 
            android:entries="@arrays/myArray"/>

5 ListView问题

5.1 焦点问题

如果ListView中添加了Button等控件时,点击item无法触发onItemClick方法,这是因为ListView的焦点被其他控件抢了。
解决方案:

  1. 为抢占了控件的组件设置:android:focusable=“false”,但EditView无法解决
  2. item根节点设置android:descendantFocusability=“blocksDescendants”。该属性有三个可供选择的值:
  • beforeDescendants:viewgroup会优先其子类控件而获取到焦点
  • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点
5.2 checkbox错位问题

如果ListView的item带有checkbox,当item超过一页时,就会出现checkbox错位的问题,这是因为可见的item处于内存中,而其他的item存放在Recycler中,第一次加载时,getView的convertView为空,再次调用时,复用了convertView。
解决方案:

  1. 将checkbox的状态放到一个HashMap<Integer, Boolean>中, 每次初始化的时候根据postion取出对应的boolean值,然后再进行checkbox的状态设置
  2. 在entity类中加入状态变量,getView直接取出对应的item对象类中的状态,初始化checkbox选中为flase,然后通过监听设置状态

附录

Adapter(适配器)

Adapter是用来帮助填出数据的中间桥梁。

ArrayAdapter(数组适配器)

1 说明

ArrayAdapter有一定的局限性,只能显示一行文本数据。

2 参数

ArrayAdapter(context,layout,data)

  • context:context上下文对象
  • layout:每一个item的样式,可以使用系统提供,也可以自定义就是一个TextView
  • data:数据源,要显示的数据
3 系统提供的样式
  • simple_list_item1:单独的一行文本框
  • simple_list_item2:有两个文本框组成
  • simple_list_item_checked:每项都是由一个已选中的列表项
  • simple_list_item_multiple_choice:都带有一个复选框
  • simple_list_item_single_choice:都带有一个单选框

SimpleAdapter(简单适配器)

1 说明

SimpleAdapter功能强大,比较常用

2 参数

SimpleAdapter(context,itemList,layout,keyArray,idArray)

  • context:context上下文对象
  • itemList:数据源是含有Map的一个集合
  • layout:每一个item的布局文件
  • keyArray:new String[]{}数组,数组的里面的每一项要与第二个参数中的存入map集合的的key值一样,一一对应
  • idArray:new int[]{}数组,数组里面的第三个参数中的item里面的控件id。

BaseAdapter(通用适配器)

1 说明

BaseAdapter是使用最多的适配器。使用时,自定义Adapter继承BaseAdapter,并实现其中方法。

2 自定义Adapter
public class MyAdapter extends BaseAdapter {
    private List<Message> Datas;
    private Context mContext;

    public MyAdapter(List<Message> datas, Context mContext) {
        Datas = datas;
        this.mContext = mContext;
    }

    /**
     * 返回item的个数
     * @return
     */
    @Override
    public int getCount() {
        return Datas.size();
    }

    /**
     * 返回每一个item对象
     * @param i
     * @return
     */
    @Override
    public Object getItem(int i) {
        return Datas.get(i);
    }

    /**
     * 返回每一个item的id
     * @param i
     * @return
     */
    @Override
    public long getItemId(int i) {
        return i;
    }

    /**
     * @param i
     * @param view
     * @param viewGroup
     * @return
     */
    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        view = LayoutInflater.from(mContext).inflate(R.layout.list_item,viewGroup,false);
       	//设置内容
        //...
		//此处需要返回view 不能是view中某一个
        return view;
    }
}
3 对BaseAdapter优化

无优化代码:

@Override
    public View getView(int i, View view, ViewGroup viewGroup) {
        view = LayoutInflater.from(mContext).inflate(R.layout.list_item,viewGroup,false);
        ImageView imageView = (ImageView) view.findViewById(R.id.image1);
        TextView textView1 = (TextView) view.findViewById(R.id.text1);
        TextView textView2 = (TextView) view.findViewById(R.id.text2);
        imageView.setImageResource(Datas.get(i).getImageId());
        textView1.setText(Datas.get(i).getTheme());
        textView2.setText(Datas.get(i).getContent());
        return view;
    }

优化1-充分利用listview的缓存机制:

@Override
    public View getView(int i, View view, ViewGroup viewGroup) {
    	if(view == null) {
    	    view = LayoutInflater.from(mContext).inflate(R.layout.list_item,viewGroup,false);
    	}
        ImageView imageView = (ImageView) view.findViewById(R.id.image1);
        TextView textView1 = (TextView) view.findViewById(R.id.text1);
        TextView textView2 = (TextView) view.findViewById(R.id.text2);
        imageView.setImageResource(Datas.get(i).getImageId());
        textView1.setText(Datas.get(i).getTheme());
        textView2.setText(Datas.get(i).getContent());
        return view;
    }

优化2-避免重复的findViewById:

static class ViewHolder {
	private ImageView imageView;
	private TextView textView1;
	private TextView textView2;
}

PS:static修饰是为了多次使用此Holder时类只需加载一次,只用一次不加也行。

@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,viewGroup,false);
    	    holder.imageView = (ImageView) view.findViewById(R.id.image1);
            holder.textView1 = (TextView) view.findViewById(R.id.text1);
            holder.textView2 = (TextView) view.findViewById(R.id.text2);
            view.setTag(holder);
    	}else {
    		holder = (ViewHolder) view.getTag();
    	}
        holder.imageView.setImageResource(Datas.get(i).getImageId());
        holder.textView1.setText(Datas.get(i).getTheme());
        holder.textView2.setText(Datas.get(i).getContent());
        return view;
    }
4 可复用的自定义Adapter
  1. 初始的自定义Adapter
public class MyAdapter extends BaseAdapter {

    private Context mContext;
    private LinkedList<Data> mData;

    public MyAdapter() {
    }

    public MyAdapter(LinkedList<Data> mData, Context mContext) {
        this.mData = mData;
        this.mContext = mContext;
    }

    //重写getCount、getItem、getItemId
    //...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
            holder = new ViewHolder();
            holder.img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
            holder.txt_content = (TextView) convertView.findViewById(R.id.txt_content);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.img_icon.setImageResource(mData.get(position).getImgId());
        holder.txt_content.setText(mData.get(position).getContent());
        return convertView;
    }
    //添加一个元素
    public void add(Data data) {
        if (mData == null) {
            mData = new LinkedList<>();
        }
        mData.add(data);
        notifyDataSetChanged();
    }
    //移除一个元素
    public void remove(Data data) {
        if(mData != null) {
            mData.remove(data);
        }
        notifyDataSetChanged();
    }

    private class ViewHolder {
        ImageView img_icon;
        TextView txt_content;
    }
}
  1. 改造1-将Entity设置成泛型:
public class MyAdapter<T> extends BaseAdapter {

    private Context mContext;
    private LinkedList<T> mData;

    public MyAdapter() {
    }

    public MyAdapter(LinkedList<T> mData, Context mContext) {
        this.mData = mData;
        this.mContext = mContext;
    }

    //重写getCount、getItem、getItemId
    //...

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list, parent, false);
            holder = new ViewHolder();
            holder.img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
            holder.txt_content = (TextView) convertView.findViewById(R.id.txt_content);
            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }
        holder.img_icon.setImageResource(mData.get(position).getImgId());
        holder.txt_content.setText(mData.get(position).getContent());
        return convertView;
    }
    //添加一个元素
    public void add(T data) {
        if (mData == null) {
            mData = new LinkedList<>();
        }
        mData.add(data);
        notifyDataSetChanged();
    }
    //移除一个元素
    public void remove(T data) {
        if(mData != null) {
            mData.remove(data);
        }
        notifyDataSetChanged();
    }

    private class ViewHolder {
        ImageView img_icon;
        TextView txt_content;
    }
}
  1. 改造2-getView()方法大部分的逻辑写到ViewHolder类
    1)相关参数和构造方法
public static class ViewHolder {

    private SparseArray<View> mViews;   //存储ListView 的 item中的View
    private View item;                  //存放convertView
    private int position;               //游标
    private Context context;            //Context上下文

    //构造方法,完成相关初始化
    private ViewHolder(Context context, ViewGroup parent, int layoutRes) {
        mViews = new SparseArray<>();
        this.context = context;
        View convertView = LayoutInflater.from(context).inflate(layoutRes, parent,false);
        convertView.setTag(this);
        item = convertView;
    }

    ImageView img_icon;
    TextView txt_content;
}

2) 绑定ViewHolder与Item

public static ViewHolder bind(Context context, View convertView, ViewGroup parent,
                              int layoutRes, int position) {
    ViewHolder holder;
    if(convertView == null) {
        holder = new ViewHolder(context, parent, layoutRes);
    } else {
        holder = (ViewHolder) convertView.getTag();
        holder.item = convertView;
    }
    holder.position = position;
    return holder;
}

3)根据id获取集合中保存的控件

public <T extends View> T getView(int id) {
    T t = (T) mViews.get(id);
    if(t == null) {
        t = (T) item.findViewById(id);
        mViews.put(id, t);
    }
    return t;
}
  1. 改造3-定义抽象方法,完成ViewHolder与Data数据集的绑定:

1)创建新的BaseAdapter的时候,实现这个方法就好

public abstract void bindView(ViewHolder holder, T obj);

2)自定义BaseAdapter改成抽象的

public abstact class MyAdapter<T> extends BaseAdapter{
}
  1. 改造4-修改getView():
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder = ViewHolder.bind(parent.getContext(), convertView, parent, mLayoutRes, position);
    bindView(holder,getItem(position));
    return holder.getItemView();
}
  1. 示例:
private void init() {

        list_book = (ListView) findViewById(R.id.list_book);
        list_app = (ListView) findViewById(R.id.list_app);

        //数据初始化
        mData1 = new ArrayList<App>();
        mData1.add(new App(R.mipmap.iv_icon_baidu,"百度"));
        mData1.add(new App(R.mipmap.iv_icon_douban,"豆瓣"));
        mData1.add(new App(R.mipmap.iv_icon_zhifubao,"支付宝"));

        mData2 = new ArrayList<Book>();
        mData2.add(new Book("《第一行代码Android》","郭霖"));
        mData2.add(new Book("《Android群英传》","徐宜生"));
        mData2.add(new Book("《Android开发艺术探索》","任玉刚"));

        //Adapter初始化
        myAdapter1 = new MyAdapter<App>((ArrayList)mData1,R.layout.item_one) {
            @Override
            public void bindView(ViewHolder holder, App obj) {
                holder.setImageResource(R.id.img_icon,obj.getaIcon());
                holder.setText(R.id.txt_aname,obj.getaName());
            }
        };
        myAdapter2 = new MyAdapter<Book>((ArrayList)mData2,R.layout.item_two) {
            @Override
            public void bindView(ViewHolder holder, Book obj) {
                holder.setText(R.id.txt_bname,obj.getbName());
                holder.setText(R.id.txt_bauthor,obj.getbAuthor());
            }
        };

        //ListView设置下Adapter:
        list_book.setAdapter(myAdapter2);
        list_app.setAdapter(myAdapter1);

    }