程序员人生 网站导航

ListView的用法

栏目:综合技术时间:2016-11-24 09:04:27

1、ListView的简单介绍

1. ListView 概念:

  ListView是Android中最重要的组件之1,几近每一个Android利用中都会使用ListView。它以垂直列表的方式列出所需的列表项。

2. ListView的两个职责:

(1)将数据填充到布局;
(2)处理用户的选择点击等操作。

3. 列表的显示需要3个元素

(1)ListVeiw:用来展现列表的View;
(2)适配器: 用来把数据映照到ListView上的中介;
(3)数据源: 具体的将被映照的字符串,图片,或基本组件。

4. 甚么是适配器?

  适配器是1个连接数据和AdapterView的桥梁,通过它能有效地实现数据与AdapterView的分离设置,使AdapterView与数据的绑定更加简便,修改更加方便。

  Android开发中的适配器1共可分为:
  ArrayAdapter,
  BaseAdapter,
  CursorAdapter,
  HeaderViewListAdapter,
  ResourceCursorAdapter,
  SimpleAdapter,
  SimpleCursorAdapter,
  WrapperListAdapter
  其中,ArrayAdapter和SimpleAdapter最为常见。

ArrayAdapter最为简单,只能展现1行字;
SimpleAdapter有最好的扩充性,可以自定义各种各样的布局,除文本外,还可以放ImageView(图片)、Button(按钮)、CheckBox(复选框)等等;
但是实际工作中,经常使用自定义适配器。即继承于BaseAdapter的自定义适配器类。

5. ListView的经常使用UI属性:

  android:divider
  android:dividerHeight

2、ListView的创建与使用

1. ArrayAdapter适配器

先看下面代码:

package com.danny_jiang.day08_listview_introduce; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemLongClickListener; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 1 初始化ListView ListView listView = (ListView) findViewById(R.id.list_Main); // 2 初始化数据源 String[] data = getResources().getStringArray(R.array.arr); // 3 初始化适配器 ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data); // 4 将适配器设置到ListView控件中 listView.setAdapter(adapter); // 设置ListView的单击事件 listView.setOnItemClickListener(new OnItemClickListener() { /** * @param parent * ListView * @param view * 所点击的item视图,也就是TextView * @param position * 所点击item的位置 * @param id * 所点击item的id */ @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { if (view instanceof TextView) { TextView textView = (TextView) view; String content = textView.getText().toString(); Toast.makeText(MainActivity.this, "点击了 " + content, Toast.LENGTH_SHORT).show(); } } }); // 设置ListView的长按事件 listView.setOnItemLongClickListener(new OnItemLongClickListener() { /** * @param parent * ListView * @param view * 所点击的item视图,也就是TextView * @param position * 所点击item的位置 * @param id * 所点击item的id */ @Override public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) { if (view instanceof TextView) { TextView textView = (TextView) view; String content = textView.getText().toString(); Toast.makeText(MainActivity.this, "长按了 " + content, Toast.LENGTH_SHORT).show(); } // 返回true,表示将单击事件进行拦截 return true; } }); } }

布局文件:

<RelativeLayout 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" tools:context="${relativePackage}.${activityClass}" > <!-- 通过ListView标签可以声明列表视图 android:divider="#FF0000" 设置ListView间隙之间的色彩,必须指定dividerHeight后才会生效 android:dividerHeight="2dp" 设置ListView间隙之间的间隔 --> <ListView android:id="@+id/list_Main" android:layout_width="match_parent" android:layout_height="match_parent" android:divider="#FF0000" android:dividerHeight="2dp" > </ListView> </RelativeLayout>

strings.xml文件:

<?xml version="1.0" encoding="utf⑻"?> <resources> <string name="app_name">Day08_ListView_Introduce</string> <string name="hello_world">Hello world!</string> <string-array name="arr"> <item>西游记</item> <item>红楼梦</item> <item>李尔王</item> <item>麦克白</item> <item>西游记</item> <item>红楼梦</item> <item>李尔王</item> <item>麦克白</item> <item>西游记</item> <item>红楼梦</item> <item>李尔王</item> <item>麦克白</item> <item>西游记</item> <item>红楼梦</item> <item>李尔王</item> <item>麦克白</item> </string-array> </resources>

效果图以下:
这里写图片描述

2. SimpleAdapter适配器

2.1 使用系统自带item布局
先看下面代码:

package com.danny_jiang.day08_listview_simpleadapter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { private ListView listView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化ListView控件 listView = (ListView) findViewById(R.id.list_Main); // 初始化数据源 List<Map<String, String>> list = new ArrayList<Map<String, String>>(); for (int i = 0; i < 10; i++) { Map<String, String> map = new HashMap<String, String>(); map.put("name", "Android-" + i); map.put("age", "age-" + i); map.put("gender", "male"); list.add(map); } // 初始化item的布局文件(使用系统自带布局) int itemLayout = android.R.layout.simple_list_item_2; /** * 初始化SimpleAdapter适配器 * * @param this * 上下文 * @param list * 数据源 List<Map> * @param itemLayout * item的布局文件ID,使用此item来显示ListVirw中的每个item * @param new String[] { "gender", "age" } * 是1系列String,key的数组,key是Map中put时所使用的key值 * @param new int[] { android.R.id.text1, android.R.id.text2 } * 1些列id的int数组,id的顺序决定了数据源中item的摆放顺序 * */ SimpleAdapter adapter = new SimpleAdapter(this, list, itemLayout, new String[] { "gender", "age" }, new int[] { android.R.id.text1, android.R.id.text2 }); // ListView设置适配器 listView.setAdapter(adapter); } }

布局文件:

<RelativeLayout 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" tools:context="${relativePackage}.${activityClass}" > <ListView android:id="@+id/list_Main" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </RelativeLayout>

效果图以下:
这里写图片描述

2.2 自定义item布局文件

代码以下:

package com.danny_jiang.day08_listview_iconitem; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.widget.ListView; import android.widget.SimpleAdapter; public class MainActivity extends Activity { private ListView listView; // 声明需要显示到ListView上的图片资源ID private int[] iconId = new int[] { R.drawable.tv01, R.drawable.tv02, R.drawable.tv03, R.drawable.tv04, R.drawable.tv05, R.drawable.tv06, R.drawable.tv07, R.drawable.tv08, R.drawable.tv09, R.drawable.tv10, R.drawable.tv01,}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化ListView控件 listView = (ListView) findViewById(R.id.list_main); // 初始化数据源 List<Map<String, Object>> list = new ArrayList<Map<String, Object>>(); for (int i = 0; i < iconId.length; i++) { Map<String, Object> map = new HashMap<String, Object>(); map.put("name", "Android-" + i); map.put("icon", iconId[i]); list.add(map); } // 初始化item布局 int itemLayout = R.layout.list_item; // 初始化适配器SimpleAdapter SimpleAdapter adapter = new SimpleAdapter(this, list, itemLayout, new String[] { "icon", "name" }, new int[] { R.id.image, R.id.name }); // ListView设置适配器 listView.setAdapter(adapter); } }

布局文件:

<RelativeLayout 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" tools:context="${relativePackage}.${activityClass}" > <ListView android:id="@+id/list_main" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

item布局文件:

<?xml version="1.0" encoding="utf⑻"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal" > <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="TEXT" /> <ImageView android:id="@+id/image" android:layout_width="200dp" android:layout_height="120dp" android:layout_marginLeft="50dp" android:scaleType="fitXY" android:src="@drawable/ic_launcher" /> </LinearLayout>

效果图以下:
这里写图片描述

3. BaseAdapter适配器

代码以下:

package com.danny_jiang.day08_listview_baseadapter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.app.Activity; import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; import android.widget.ArrayAdapter; import android.widget.ListView; public class MainActivity extends Activity { private ListView listView; private List<Map<String, String>> list = new ArrayList<Map<String,String>>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.list_Main); for(int i = 0; i < 50; i++) { Map<String, String> map = new HashMap<String, String>(); map.put("key", "value" + i); map.put("asd", "asd" + i); list.add(map); } MyBaseAdapter adapter = new MyBaseAdapter(this, list); listView.setAdapter(adapter); } }

自定义适配器 MyBaseAdapter:

package com.danny_jiang.day08_listview_baseadapter; import java.util.List; import java.util.Map; import android.content.Context; import android.graphics.Color; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class MyBaseAdapter extends BaseAdapter{ // 在自定义Adapter中声明全局的数据源对象 private List<Map<String, String>> list; private Context context; // 声明布局填充器,通过它可以填充xml布局文件并返回View对象 private LayoutInflater inflater; public MyBaseAdapter(Context context, List<Map<String, String>> list) { this.context = context; inflater = LayoutInflater.from(context); this.list = list; } /** * 返回ListView需要显示条数 * 1般情况下需要返回数据源的长度 */ @Override public int getCount() { // 如果数据源是null,则返回0,否则返回数据源的长度 return list == null ? 0 : list.size(); } /** * 返回某位置上的item对象 * 当调用ListView.getItemAtPosition(0/1) * 应当返回数据源中position对应的对象 */ @Override public Object getItem(int position) { return list.get(position); } /** * 返回position对应item的id,1般返回position */ @Override public long getItemId(int position) { return position; } /** * 返回ListView中position所对应的item需要显示的视图对象 * position从0开始 * * @param position 需要填充视图的视图位置,从0开始 * @param convertView ListView的优化机制,Android系统对getView返回视图的1个复用机制 * @param parent 将item视图填充到的AdapterView,也就是ListView本身 */ @Override public View getView(int position, View convertView, ViewGroup parent) { ViewHolder holder = null; if (convertView == null) { // 通过LayoutInflater填充xml布局文件,将获得到的convertView对象返回 convertView = inflater.inflate(R.layout.list_item, null); // 初始化holder,并为其属性赋值 holder = new ViewHolder(); holder.text1 = (TextView) convertView.findViewById(R.id.text1); holder.text2 = (TextView) convertView.findViewById(R.id.text2); // 给convertView添加标签holder convertView.setTag(holder); } else { holder = (ViewHolder) convertView.getTag(); } // 获得当前位置中数据源的元素 Map<String, String> map = list.get(position); String value = map.get("key"); String asd = map.get("asd"); // 为每一个item中的所有UI控件设置属性值 holder.text1.setText(value); holder.text2.setText(asd); return convertView; } class ViewHolder{ TextView text1; TextView text2; } }

布局文件:

<RelativeLayout 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" tools:context="${relativePackage}.${activityClass}" > <ListView android:id="@+id/list_main" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout>

自定义item布局文件:

<?xml version="1.0" encoding="utf⑻"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center_vertical" android:orientation="horizontal" > <TextView android:id="@+id/name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:text="TEXT" /> <ImageView android:id="@+id/image" android:layout_width="200dp" android:layout_height="120dp" android:layout_marginLeft="50dp" android:scaleType="fitXY" android:src="@drawable/ic_launcher" /> </LinearLayout>

效果图以下:
这里写图片描述

3、convertView原理:

  Adapter的作用就是ListView界面与数据之间的桥梁,当列表里的每项显示到页面时,都会调用Adapter的getView方法返回1个View。

  如果在我们的列表有上千项时会是甚么样的?是否是会占用极大的系统资源?

  Android中有个叫做Recycler的构件,如果你有100个item,其中只有可见的项目存在内存中,其他的在Recycler中。ListView先要求1个type1视图(getView),然后要求其他可见的item,convertView在getView中是空(null)的。
当item1滚出屏幕,并且1个新的item从屏幕底端上来时,ListView再要求1个type1视图,convertView此时不是空值了,它的值是item1。你只需设定新的数据,然后返回convertView,没必要重新创建1个视图。

4、甚么是listview点击的灵异事件?

  项目中的ListView不单单是简单的文字,常常需要自己定义ListView,如果自己定义的Item中存在诸如ImageButton,Button,CheckBox等子控件,此时这些子控件会将焦点获得到,所以当点击item中的子控件时有变化,而item本身的点击没有响应。
  解决方案的关键是:android:descendantFocusability

  当1个view获得焦点时,定义ViewGroup及其子控件之间的关系。
  属性的值有3种:
beforeDescendants:viewgroup会优先其子类控件而获得到焦点
afterDescendants:viewgroup只有当其子类控件不需要获得焦点时才获得焦点
blocksDescendants:viewgroup会覆盖子类控件而直接取得焦点

  通常我们用到的是第3种,即在Item布局的根布局加android:descendantFocusability=”blocksDescendants”的属性(阻塞子控件抢夺焦点,让Item具有焦点。这样ListView的onItemClick就可以被正确触发,同时item上的button等控件在被点击时照样可以触发本身的点击事件)就行了,至此ListView点击的灵异事件告1段落。

5、ListView优化中的细节问题:

1、android:layout_height属性:
  必须将ListView的布局高度属性设置为非“wrap_content“,(可以是match_parent/fill parent/400dp等绝对数值),如果ListView的布局高度为“wrap_content”,那末getView()就会重复调用。1般来讲,1个item会被调用3次左右。
2、ViewHolder:
  利用ViewHolder内部类,将item布局文件中需要展现的控件定义为属性(其实ViewHolder就是1个自定义的模型类)。这样就把item中散在的多个控件合成1个整体,这样可以有效地避免图片错位。
3、convertView:
  ListView的加载是1个item1个item的加载,这样就会每次都inflate1个item布局,然后findViewById1遍该布局上的所有控件。当数据量大的时候,是不可想象的。而利用Recycle回收利用就能够解决问题。所以要善于重复利用convertView,这样可以减少填充布局的进程,减少ViewHolder对象实例化的次数。减少内存开消,提高性能。
4、convertView的setTag():
  利用setTag()方法将ViewHolder对象作为标签附加到convertView上,当convertView被重复利用的时候,由于上面有ViewHolder对象,所以convertView就具有了ViewHolder中的几个属性,这样就节省了findViewById()这个进程。如果1个item有3个控件,如果有100条item,那末在加载数据进程中,就就相当于节省了几百次findViewById(),节俭了履行findViewById()的时间,提升了加载速度,节省了性能的开消。
5、LayoutInflater对象的inflate()方法:
  inflate()方法1般接收两个参数,第1个参数就是要加载的布局id,第2个参数是指给该布局的外部再嵌套1层父布局,如果不需要就直接传null。

  inflate()方法还有个接收3个参数的方法重载
  1.如果root为null,attachToRoot将失去作用,设置任何值都没成心义。
  2.如果root不为null,attachToRoot设为true,则会在加载的布局文件的最外层再嵌套1层root布局。
  3.如果root不为null,attachToRoot设为false,则root参数失去作用。
  4.在不设置attachToRoot参数的情况下,如果root不为null,attachToRoot参数默许为true。
  所以在使用LayoutInflater填充布局的时候,要注意inflate()方法的参数。如果是两个参数,则第2个参数可以采取null;如果使用3个参数的方法,则要注意参数之间的搭配。

6、ListView分页的实现

1. 目的:
  Android 利用开发中,采取ListView组件来展现数据是很经常使用的功能,当1个利用要展现很多的数据时,1般情况下都不会把所有的数据1次就展现出来,而是通过 分页的情势来展现数据,这样会有更好的用户体验。因此,很多利用都是采取分批次加载的情势来获得用户所需的数据。例如:微博客户端可能会在用户滑 动至列表底端时自动加载下1页数据,也可能在底部放置1个”查看更多”按钮,用户点击后,加载下1页数据。
2. 核心技术点:
  a. 借助 ListView组件的OnScrollListener监听事件,去判断什么时候该加载新数据;
  b.往服务器get传递表示页码的参数:page。而该page会每加载1屏数据后自动加1;
  c.利用addAll()方法不断往list集合末端添加新数据,使得适配器的数据源每新加载1屏数据就产生变化;
   d.利用适配器对象的notifyDataSetChanged()方法。该方法的作用是通知适配器自己及与该数据相关的view,数据已产生变动,要刷新自己、更新数据。

案例演示:

package com.danny_jiang.day09_listview_scroll; import java.util.ArrayList; import java.util.List; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.widget.AbsListView.LayoutParams; import android.widget.AbsListView.OnScrollListener; import android.widget.AbsListView; import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.ImageView.ScaleType; import android.widget.ListView; public class MainActivity extends Activity { private ListView listView; private boolean isLastShown; private List<String> data = new ArrayList<String>(); private ArrayAdapter<String> adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); listView = (ListView) findViewById(R.id.list_Main); for(int i = 0; i < 20; i++) { data.add("Android-" + i); } adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, data); listView.setAdapter(adapter); /** * 通过ListView.addHeaderView方法可以给ListView添加1个头视图View */ ImageView header = new ImageView(this); /** * 当需要动态修改UI控件的宽高,需要使用LayoutParams参数对象, * 指定宽高,并将此LayoutParams设置到相应的UI控件上 */ //初始化LayoutParams时,需要指定宽高 LayoutParams param = new LayoutParams(LayoutParams.MATCH_PARENT, 200); //将LayoutParams设置到UI控件上 header.setLayoutParams(param); //设置ImageView宽高都填充父视图 header.setScaleType(ScaleType.FIT_XY); header.setImageResource(R.drawable.food); listView.addHeaderView(header); /** * 通过ListView.addFooterView方法可以给ListView添加1个底部视图View */ View footer = getLayoutInflater().inflate(R.layout.footer_loading, null); listView.addFooterView(footer); /** * ListView的分页实现 * 通过setOnScrollListener可以给ListView设置滑动监听 * 1 滑动状态的改变 * 2 滑动时,item的位置信息 * 通过此OnScrollListener可以判断出ListView是不是已滑动到屏幕底部,并且滑动停止 */ listView.setOnScrollListener(new OnScrollListener() { /** * 当ListView的滑动状态发送改变时,此方法被调用 * @param view 指ListView本身 * @param scrollState 表示当前ListView所处于的状态 * SCROLL_STATE_FLING-⑵ 抛掷状态--手指离开屏幕前,用力滑了1下,屏幕产生惯性滑动 * SCROLL_STATE_IDLE--0 停止状态 * SCROLL_STATE_TOUCH_SCROLL-⑴ 手指触摸屏幕触发ListView转动状态 */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { android.util.Log.e("TAG", "onScrollStateChanged"); // 说明ListView已滑动到底部,并且停止状态 if (isLastShown && scrollState == SCROLL_STATE_IDLE) { int dataSize = data.size(); for(int i = dataSize; i < dataSize + 20; i++) { data.add("Andorid-" + i); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } // 通知适配器数据源已产生改变 adapter.notifyDataSetChanged(); } } /** * 只要ListView处于滑动状态,此方法会被1直调用 * @param view 指ListView本身 * @param firstVisibleItem 屏幕当前第1个可见Item的位置,从0开始 * @param visibleItemCount 屏幕当前可见item的个数 * @param totalItemCount ListView中总的item的个数 */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { android.util.Log.e("TAG", "onScroll"); // 通过方法参数可以判断出最后1条是不是已显示到屏幕上 isLastShown = firstVisibleItem + visibleItemCount == totalItemCount; } }); } }

布局文件:

<RelativeLayout 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" tools:context="${relativePackage}.${activityClass}" > <ListView android:id="@+id/list_Main" android:layout_width="match_parent" android:layout_height="match_parent" > </ListView> </RelativeLayout>

底部视图布局文件:footer_loading.xml

<?xml version="1.0" encoding="utf⑻"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:orientation="horizontal" > <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:text="Loading..." /> </LinearLayout>
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐