博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
17、ListView & GridView
阅读量:6649 次
发布时间:2019-06-25

本文共 15508 字,大约阅读时间需要 51 分钟。

一、ListView

listView是用来解决大量的相似数据显示问题,同时大量的数据会导致不断在内存中创建对象,可能导致OOM。

1.1、listView的属性

该控件采用MVC的设计模式,而且还有大量适配器,一般情况是:BaseXXX、BasicXXX、SimpleXXX、DefaultXXX。

除此之外,来看看ListView的常用事件:

(1)setOnclickListener()

(2)setOnItemLongClickListener()

(3)setOnItemClickListener()

(4)setOnScrollListener()

(5)setOnItemSelectedListener()

(6)setOnTouchListener()

1.2、MVC模式的简单理解

  • Model:通常可以理解为数据
  • View:用户的操作接口,说白了就是GUI,应该使用哪种接口组件,组件间的排列位置与顺序都需要设计
  • Controller:控制器,作为model与view之间的枢纽,负责控制程序的执行流程以及对象之间的一个互动

而这个Adapter则是中间的这个Controller的部分: Model(数据) ---> Controller(以什么方式显示到)---> View(用户界面)

1.3、Adapter概念解析

让我们来看看常用的Adapter:

  • ArrayAdapter:支持泛型操作,最简单的一个Adapter,只能展现一行文字。
  • SimpleAdapter:同样具有良好扩展性的一个Adapter,可以自定义多种效果。
  • SimpleCursorAdapter:用于显示简单文本类型的listView,一般在数据库那里会用到。
  • BaseAdapter:抽象类,实际开发中我们会继承这个类并且重写相关方法,用得最多的一个Adapter。

1、ArrayAdapter:它是BaseAdapter的子类,主要用于存放字符串。

  ArrayAdapter<T>(context:这个参数表示上下文,一般都是this, 
                  textViewResourceId:这个参数是指定自定义的布局文件, 
                  objects):接收的参数,添加到ListView视图中的,类型根据泛型而变化。

public class MainActivity extends Activity {    private ListView mListView;    private static final String[] mDatas = {"功能1","功能2","功能3"};    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mListView = (ListView) findViewById(R.id.listview);        // 设置适配器,第三个数组是根据泛型而变化的        mListView.setAdapter(new ArrayAdapter
(this, R.layout.list_item, mDatas)); }}

需要注意的是,上面的list_item布局中,TextView必须作为根节点,否则报错:

如果需要使用系统自定义的样式,由于ArrayAdapter是单行显示,所以只能用simple_list_item_1

mListView.setAdapter(new ArrayAdapter
(this,android.R.layout.simple_list_item_1, new String[]{"功能1","功能2","功能3"}));

如果ArrayAdapter想要实现更多的效果则需要自定义ArrayAdapter,不过目前这种方式已经过时。

2、SimpleAdapter:它也是BaseAdapter的子类,用来实现一些图片、文字并排的效果。

  SimpleAdapter(context:上下文
                data:代表整个ListView的List集合,
                resource:自定义的布局文件或系统布局文件, 
                from:对应的key的数组。
                to):对应的value的数组。

public class MainActivity extends Activity {    private ListView mListView;    private String[] mDatas = {"声音","显示","存储","电池","应用"};    private int[] resources = {R.drawable.akb,R.drawable.akc,R.drawable.akd,R.drawable.ake,R.drawable.akf};    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mListView = (ListView) findViewById(R.id.listview);        // 构建数据模型        List
> lists = new ArrayList
>(); for (int i = 0; i < resources.length; i++) { Map
maps = new HashMap
(); maps.put("name", mDatas[i]); maps.put("drawable", resources[i]); lists.add(maps); } mListView.setAdapter(new SimpleAdapter(this, lists, R.layout.item, new String[]{"name","drawable"}, new int[]{R.id.tv_content,R.id.iv_img})); }}

如果需要用到系统的样式,有如下系统样式:

  • simple_list_item_1:单行文本组成
  • simple_list_item_2:两行文本组成
  • simple_list_item_checked:每项都是由一个已选中的列表框。
  • simple_list_item_single_choice:都带有一个单选按纽。
  • simple_list_item_multiple:全部带有一个复选框。

3.CursorAdapter:它同样是BaseAdapter的子类,它为Cursor和ListView提供连接的桥梁。

  (1) newView():并不是每次都被调用,它只在实例化和数据增加时调用,而修改条目的内容时不会调用。
  (2) bindView():在绘制item之前或重绘时一定会调用。
  (3) changeCursor():类似于notifyDataSetChange()方法。

详细请参考附件中的实例

1.4、ListView优化

BaseAdapter: 是经常用到的基础数据适配器,它的主要用途是将一组数据传到像ListView、Spinner、Gallery及GrideView等组件。

  (1) getCount():是listView的长度。
  (2) getView(): 根据这个长度逐一绘制它的每一行。
  (3) getItem()和getItemId()则需要处理和取得Adapter中的数据时调用。

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {    View view = View.inflate(MainActivity.this, R.layout.item, null);    TextView tvContent = (TextView) findViewById(R.id.tv_content);    tvContent.setText(mDatas.get(position));    return view;}

第一种优化:复用对象

由于上方的代码每次需要一个View对象都会重新inflate一个view出来,没有实现对象的复用。

而系统给我们提供convertView,代表的是可复用的对象,当它为空则创建一个对象,否则直接复用。

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {    View view;    if(convertView == null){        view = View.inflate(MainActivity.this, R.layout.item, null);    }else{        view = convertView;    }    TextView tvContent = (TextView) view.findViewById(R.id.tv_content);    tvContent.setText(mDatas.get(position));    return view;}

第二种优化:减少查找次数

当converView为空时,会重新inflate一个View对象,除此之外还会findViewById进行查找工作,我们可以通过一个ViewHolder类来存储对应的

成员变量,然后通过getTag和setTag来操作,这时,当convertView为空时,只需要取出ViewHolder中存储的成员变量进行复用即可。

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {    ViewHolder holder;    if(convertView == null){        convertView = View.inflate(MainActivity.this, R.layout.item, null);        holder = new ViewHolder();        holder.mTvContent = (TextView) convertView.findViewById(R.id.tv_content);        convertView.setTag(holder);    }else{        holder = (ViewHolder) convertView.getTag();    }    holder.mTvContent.setText(mDatas.get(position));    return convertView;}class ViewHolder{    TextView mTvContent;}

第三种写法:分批分页加载(待整理)

1.5、ListView多样式

  • 重写getViewTypeCount()   -- 该方法返回多个不同的布局总数。
  • 重写getItemViewType(int) -- 根据position返回响应的Item。
  • 根据view item的类型,在getView中创建正确的convertView。

a)创建MyAdapter继承BaseAdapter,首先在适配的getItemViewType()中通过计算得出不同的状态,用常量进行标记

public static final int TYPE_1 = 0;public static final int TYPE_2 = 1;public static final int TYPE_3 = 2;  @Overridepublic int getItemViewType(int position) {    int p = position % 6;    if(p == 0){        return TYPE_1;    }else if (p < 3) {        return TYPE_2;    }else if (p < 6) {        return TYPE_3;    }else{        return TYPE_1;    }}

注意:type必须从0开始,否则会报数组角标越界异常。

b)在getViewTypeCount()中获取不同布局的种类数

@Overridepublic int getViewTypeCount() {    return 3;}

c)此时我们需要给定义三个不同的布局,并创建三个不同的ViewHolder来针对不同的布局进行缓存复用

其他三个布局都是如此,只是指定的背景颜色不一样,同时定义三个ViewHolder

class ViewHolder1{    TextView textView;}class ViewHolder2{    TextView textView;}class ViewHolder3{    TextView textView;}

d)此时我们在getView中来判断常量,进行填充不同的布局以及设置资源等操作

@Overridepublic View getView(int position, View convertView, ViewGroup parent) {        ViewHolder1 holder1 = null;    ViewHolder2 holder2 = null;    ViewHolder3 holder3 = null;        int type = getItemViewType(position);    if(convertView == null){        // 按当前所需样式,确定new出的布局        switch (type) {        case TYPE_1:            convertView = View.inflate(MainActivity.this, R.layout.item1, null);            holder1 = new ViewHolder1();            holder1.textView = (TextView) convertView.findViewById(R.id.textview1);            convertView.setTag(holder1);            break;        case TYPE_2:            convertView = View.inflate(MainActivity.this, R.layout.item2, null);            holder2 = new ViewHolder2();            holder2.textView = (TextView) convertView.findViewById(R.id.textview2);            convertView.setTag(holder2);            break;        case TYPE_3:            convertView = View.inflate(MainActivity.this, R.layout.item3, null);            holder3 = new ViewHolder3();            holder3.textView = (TextView) convertView.findViewById(R.id.textview3);            convertView.setTag(holder3);            break;        }    }else{        switch (type) {        case TYPE_1:            holder1 = (ViewHolder1) convertView.getTag();            break;        case TYPE_2:            holder2 = (ViewHolder2) convertView.getTag();            break;        case TYPE_3:            holder3 = (ViewHolder3) convertView.getTag();            break;        }    }        // 根据不同样式设置资源    switch (type) {    case TYPE_1:        holder1.textView.setText("我是绿色"+mDatas.get(position));        break;    case TYPE_2:        holder2.textView.setText("我是蓝色"+mDatas.get(position));        break;    case TYPE_3:        holder3.textView.setText("我是红色"+mDatas.get(position));        break;    }    return convertView;}

1.6、ListView的Item动画

a) 在ListView布局使用layoutAnimation属性引入一个动画文件。

b) 在anin文件下创建该动画文件list_item_animation

android:delay的单位是s,每个Item出现的时间间隔

android:animation:表示每个Item对应的动画

android:animationOrder:动画执行顺序,normal从上到下;reverse从下到上;random随机

c) 动画文件又引入item_animation文件,该文件描述动画效果。

d) 我们也可以在代码中来设置Item的加载动画

Animation animation = AnimationUtils.loadAnimation(this, R.anim.item_animation);LayoutAnimationController animationController = new LayoutAnimationController(animation);animationController.setDelay(0.4f);// 设置间隔时间animationController.setOrder(LayoutAnimationController.ORDER_NORMAL);// 设置列表显示顺序mListView.setLayoutAnimation(animationController);

1.7、ListView的焦点

android:descendantFocusability="blocksDescendants"

如题,在Item布局的根节点添加上述属性,android:descendantFocusability="blocksDescendants" 即可,另外该属性有三个可供选择的值:

  • beforeDescendants:viewgroup会优先其子类控件而获取到焦点
  • afterDescendants:viewgroup只有当其子类控件不需要获取焦点时才获取焦点
  • blocksDescendants:viewgroup会覆盖子类控件而直接获得焦点

例如:ListView的Item条目中的Button事件冲突解决。

  • 在ItemView配置的xml文件中的根节点添加属性android:descendantFocusability="blocksDescendants"
  • 在要添加事件的控件上添加android:focusable="false"

 2.1、ListView中使用CheckBox错位问题

思路:

  首先ListView在使用到CheckBox的时候会存在焦点问题,我们可以使用上方的方式处理该问题,其次是Item会被复用的问题,

我们可以通过在Bean中创建一个变量isChecked标记当前Item是否被选中的变量即可,当选中条目时就将isChecked设置为true,则可以解决错位问题。

a) 在MainActivity中创建ListView并创建适配器

public class MainActivity extends AppCompatActivity {    private ListView mListView;    private List
mDatas; private MyAdapter mAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (ListView) findViewById(R.id.lv_listview); // 模拟数据 mDatas = new ArrayList<>(); for (int i = 0; i< 100; i++) { ItemBean item = new ItemBean(); item.setName("名字" + i); item.setAge("年龄" + i); mDatas.add(item); } mAdapter = new MyAdapter(); mListView.setAdapter(mAdapter); // 条目点击事件 mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView
parent, View view, int position, long id) { MyAdapter.ViewHold hold = (MyAdapter.ViewHold) view.getTag(); ItemBean itemBean = mDatas.get(position); if(hold.cbClick.isChecked()) { hold.cbClick.setChecked(false); itemBean.setChecked(false); }else { hold.cbClick.setChecked(true); itemBean.setChecked(true); } } }); } private class MyAdapter extends BaseAdapter { @Override public int getCount() { return mDatas.size(); } @Override public Object getItem(int position) { return mDatas.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHold hold; if (convertView == null) { convertView = View.inflate(MainActivity.this,R.layout.item_name_list, null); hold = new ViewHold(); hold.cbClick = (CheckBox) convertView.findViewById(R.id.cb_check); hold.tvName = (TextView) convertView.findViewById(R.id.tv_name); hold.tvAge = (TextView) convertView.findViewById(R.id.tv_age); convertView.setTag(hold); }else { hold = (ViewHold) convertView.getTag(); } ItemBean itemBean = mDatas.get(position); hold.cbClick.setChecked(itemBean.isChecked()); hold.tvName.setText(itemBean.getName()); hold.tvAge.setText(itemBean.getAge()); return convertView; } class ViewHold { CheckBox cbClick; TextView tvName; TextView tvAge; } }}

b) 然后是MainActivity的布局,Bean的字段为Name、Age和isCheckd。

c) 最后是item的布局,这里需要处理焦点问题

 

二、GridView

ListView是列表,GridView就是显示网格,他和ListView一样是AbsListView的子类,使用非常类似。

1.0、GridView网格线

前面我们知道ListView设置分割线是非常容易的,设置ListView的分割线颜色和宽度,只需要在布局中定义

android:divider和android:divideHeight属性即可。但是GridView并没有这样的方法。

a)其实实现这种效果并不难,原理是让每个item都设置成带有分割线的背景。

b) 考虑到有时候Item会比较多的情况,一般用ScrollView嵌套起来。但是这样会导致只显示第一行。

产生问题的原因是因为GridView和ListView都是根据子item的宽高来显示大小的,而嵌套ScrollView中上下滑动就导致

系统无法正确的识别item的大小。下面是解决方案:

public class MyGridView extends GridView {    public MyGridView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public MyGridView(Context context) {        super(context);    }    public MyGridView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2,                MeasureSpec.AT_MOST);        super.onMeasure(widthMeasureSpec, expandSpec);    }}

c) 定义一个selector,在里面设置形状外矩形rectangle,设置这个矩形的stroke描边属性的颜色为分割线的颜色,然后

在不同的state的item中设置不同的gradient渐变属性,从而实现单个item被点击选中时的效果。

d) 给 GridView的item布局使用上面定义的selector

e) 之后是适配器的编写,比较简单则不详细叙述

public class MyGridAdapter extends BaseAdapter {    private Context mContext;    public String[] strs = { "转账", "余额宝", "手机充值", "信用卡还款", "淘宝电影", "彩票",            "当面付", "亲密付", "机票", };    public int[] imgs = { R.drawable.app_transfer, R.drawable.app_fund,            R.drawable.app_phonecharge, R.drawable.app_creditcard,            R.drawable.app_movie, R.drawable.app_lottery,            R.drawable.app_facepay, R.drawable.app_close, R.drawable.app_plane };    public MyGridAdapter(Context mContext) {        super();        this.mContext = mContext;    }    @Override    public int getCount() {        return strs.length;    }    @Override    public Object getItem(int position) {        return position;    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        if (convertView == null) {            convertView = View.inflate(mContext, R.layout.grid_item, null);        }        TextView tv = BaseViewHolder.get(convertView, R.id.tv_item);        ImageView iv = BaseViewHolder.get(convertView, R.id.iv_item);        iv.setBackgroundResource(imgs[position]);        tv.setText(strs[position]);        return convertView;    }}

f) 比较重要的是下面的这个万能ViewHolder的写法,详细demo参考附件GridView网格

public class BaseViewHolder {    @SuppressWarnings("unchecked")    public static 
T get(View view, int id) { SparseArray
sparseArray = (SparseArray
) view.getTag(); if (sparseArray == null) { sparseArray = new SparseArray
(); view.setTag(sparseArray); } View childView = sparseArray.get(id); if (childView == null) { childView = view.findViewById(id); sparseArray.put(id, childView); } return (T) childView; }}

 

开源控件下载地址:

链接: 密码:hnyv

转载于:https://www.cnblogs.com/pengjingya/p/5508998.html

你可能感兴趣的文章
hdu 1879 继续通畅工程(最小生成树)
查看>>
Storm Topology的并发度
查看>>
hibernate回滚事务
查看>>
我的友情链接
查看>>
禁用USB
查看>>
Linux文本处理---文件属性查找
查看>>
umount 时出现的 "Device is busy"问题
查看>>
mac装windows遇到的问题总结
查看>>
OPENDNS的地址不能用,劫持DNS
查看>>
云舒网络:容器系列二:容器的视角-设计交付和架构
查看>>
Ubuntu中安装mysql-python失败的解决方法
查看>>
Linux文件系统和目录的作用
查看>>
springmvc+mybatis+spring 整合 bootstrap
查看>>
aix 文件大小相关查询
查看>>
cgroup
查看>>
hadoop
查看>>
keepalived
查看>>
《Linux菜鸟入门2》系统定时延时任务
查看>>
局域网共享故障的分析与排除
查看>>
使用 sitemesh/decorator装饰器装饰jsp页面(原理及详细配置)
查看>>