1.自定义BaseAdapter,然后绑定ListView的最简单例子
先看看我们要实现的效果图:
一个很简单的ListView,自己写下Item,然后加载点数据这样~ 下面贴下关键代码:
Animal.java:
代码语言:javascript复制/**
* Created by Jay on 2015/9/18 0018.
*/
public class Animal {
private String aName;
private String aSpeak;
private int aIcon;
public Animal() {
}
public Animal(String aName, String aSpeak, int aIcon) {
this.aName = aName;
this.aSpeak = aSpeak;
this.aIcon = aIcon;
}
public String getaName() {
return aName;
}
public String getaSpeak() {
return aSpeak;
}
public int getaIcon() {
return aIcon;
}
public void setaName(String aName) {
this.aName = aName;
}
public void setaSpeak(String aSpeak) {
this.aSpeak = aSpeak;
}
public void setaIcon(int aIcon) {
this.aIcon = aIcon;
}
}
AnimalAdapter.java:自定义的BaseAdapter:
代码语言:javascript复制/**
* Created by Jay on 2015/9/18 0018.
*/
public class AnimalAdapter extends BaseAdapter {
private LinkedList<Animal> mData;
private Context mContext;
public AnimalAdapter(LinkedList<Animal> mData, Context mContext) {
this.mData = mData;
this.mContext = mContext;
}
@Override
public int getCount() {
return mData.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal,parent,false);
ImageView img_icon = (ImageView) convertView.findViewById(R.id.img_icon);
TextView txt_aName = (TextView) convertView.findViewById(R.id.txt_aName);
TextView txt_aSpeak = (TextView) convertView.findViewById(R.id.txt_aSpeak);
img_icon.setBackgroundResource(mmData.get(position).getaIcon());
txt_aName.setText(mData.get(position).getaName());
txt_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;
}
}
最后是MainActivity.java:
代码语言:javascript复制public class MainActivity extends AppCompatActivity {
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_icon_dog));
mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_icon_cow));
mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_icon_duck));
mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_icon_fish));
mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_icon_horse));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
list_animal.setAdapter(mAdapter);
}
}
好的,自定义BaseAdapter以及完成数据绑定就是这么简单~ 别问我拿示例的代码,刚开始学就会写出这些代码,我只是演示下流程,让大家熟悉 熟悉而已。另外,也是为下面的属性验证做准备~
2.表头表尾分割线的设置:
listview作为一个列表控件,他和普通的列表一样,可以自己设置表头与表尾: 以及分割线,可供我们设置的属性如下:
- footerDividersEnabled:是否在footerView(表尾)前绘制一个分隔条,默认为true
- headerDividersEnabled:是否在headerView(表头)前绘制一个分隔条,默认为true
- divider:设置分隔条,可以用颜色分割,也可以用drawable资源分割
- dividerHeight:设置分隔条的高度
翻遍了了API发现并没有可以直接设置ListView表头或者表尾的属性,只能在Java中写代码 进行设置了,可供我们调用的方法如下:
- addHeaderView(View v):添加headView(表头),括号中的参数是一个View对象
- addFooterView(View v):添加footerView(表尾),括号中的参数是一个View对象
- addHeaderView(headView, null, false):和前面的区别:设置Header是否可以被选中
- addFooterView(View,view,false):同上
对了,使用这个addHeaderView方法必须放在listview.setAdapter前面,否则会报错。
使用示例:
运行效果图:
代码实现:
先编写下表头与表尾的布局:
view_header.xml(表头),表尾一样,就不贴了:
代码语言:javascript复制<?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"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:textSize="18sp"
android:text="表头"
android:gravity="center"
android:background="#43BBEB"
android:textColor="#FFFFFF"/>
</LinearLayout>
MainActivty.java:
代码语言:javascript复制public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener{
private List<Animal> mData = null;
private Context mContext;
private AnimalAdapter mAdapter = null;
private ListView list_animal;
private LinearLayout ly_content;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = MainActivity.this;
list_animal = (ListView) findViewById(R.id.list_animal);
//动态加载顶部View和底部View
final LayoutInflater inflater = LayoutInflater.from(this);
View headView = inflater.inflate(R.layout.view_header, null, false);
View footView = inflater.inflate(R.layout.view_footer, null, false);
mData = new LinkedList<Animal>();
mData.add(new Animal("狗说", "你是狗么?", R.mipmap.ic_icon_dog));
mData.add(new Animal("牛说", "你是牛么?", R.mipmap.ic_icon_cow));
mData.add(new Animal("鸭说", "你是鸭么?", R.mipmap.ic_icon_duck));
mData.add(new Animal("鱼说", "你是鱼么?", R.mipmap.ic_icon_fish));
mData.add(new Animal("马说", "你是马么?", R.mipmap.ic_icon_horse));
mAdapter = new AnimalAdapter((LinkedList<Animal>) mData, mContext);
//添加表头和表尾需要写在setAdapter方法调用之前!!!
list_animal.addHeaderView(headView);
list_animal.addFooterView(footView);
list_animal.setAdapter(mAdapter);
list_animal.setOnItemClickListener(this);
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(mContext,"你点击了第" position "项",Toast.LENGTH_SHORT).show();
}
}
好的,代码还是比较简单的,从上面我们看出来一个要注意的问题,就是:
添加表头表尾后,我们发现positon是从表头开始算的,就是你添加的第一个数据本来的 postion 是 0,但是此时却变成了 1,因为表头也算!!
3.列表从底部开始显示:stackFromBottom
如果你想让列表显示你列表的最下面的话,那么你可以使用这个属性,将stackFromBottom 属性设置为true即可,设置后的效果图如下:
4.设置点击颜色cacheColorHint
如果你为ListView设置了一个图片作为Background的话,当你拖动或者点击listView空白位置会发现 item都变成黑色了,这是时候我们可以通过这个cacheColorHint将颜色设置为透明:#00000000
5.隐藏滑动条
我们可以通过设置:android:scrollbars=“none” 或者 setVerticalScrollBarEnabled(true); 解决这个问题!
本节小结:
好的,关于ListView的基本用法大概就这些,当然除了上述的这些属性外还有其他的, 实际遇到再查查吧~这里知道如何去重写BaseAdapter和完成数据绑定就好,下节我们来 教大家如何来优化这个BaseAdapter的编写~
补充:如何重写BaseAdapter
首先学习Android还是秉持从先会使用SDK提供的代码框架开始,掌握了方法的使用,如果有需要再去针对于某一个框架实现要点学习源码方面的知识,对于一个系统级别的框架来说,广撒网全都学可能精力的确不够。
对于一个BaseAdapter的子类来说,我们需要重写BaseAdapter中的几个抽象方法,但是抽象方法是被如何调用的,我们在此不妨先不去深究,只需知道被重写的方法是用于何种目的,我们只需秉持:“调用逻辑是由组件负责,你只要按照规定实现方法即可”的观念即可。
引言:
Adapter用来把数据绑定到扩展了AdapterView类的视图组。系统自带了几个原生的Adapter。
由于原生的Adapter视图功能太少,有时需要有自己的视图格式。并且在开发中经常用到。
重写的四种方法:
方法名 | 方法用途 |
---|---|
public int getCount() | 适配器中数据集中的数据个数 |
public Object getItem(int arg0) | 获取数据集中与索引对应的数据项 |
public long getItemId(int arg0) | 获取指定行对应的ID |
View getView(int arg0, View arg1, ViewGroup arg2) | 获取每一个Item的显示内容 |
ListView绘制的过程如下:
- 首先,系统在绘制ListView之前,将会先调用getCount方法来获取Item的个数。
- 之后每绘制一个Item就会调用一次getView方法,在此方法内就可以引用事先定义好的xml来确定显示的效果并返回一个View对象作为一个Item显示出来。也正是在这个过程中完成了适配器的主要转换功能,把数据和资源以开发者想要的效果显示出来。也正是getView的重复调用,使得ListView的使用更为简单和灵活。
这两个方法是自定ListView显示效果中最为重要的,同时只要重写好了就两个方法,ListView就能完全按开发者的要求显示。
- 而getItem和getItemId方法将会在调用ListView的响应方法的时候被调用到。所以要保证ListView的各个方法有效的话,这两个方法也得重写。比如:没有完成getItemId方法的功能实现的话,当调用ListView的getItemIdAtPosition方法时将会得不到想要的结果,因为该方法就是调用了对应的适配器的getItemId方法。
以下给出此类四个方法重写的相关一个常见的例子,再做解释:
代码语言:javascript复制 /**
* @return 适配器中数据集中的数据个数
*/
@Override
public int getCount() {
return mData.size();
}
/**
* @param position
* @return 获取数据集中与索引对应的数据项
*/
@Override
public Object getItem(int position) {
return null;
}
/**
* @param position
* @return 获取指定行对应的ID
* 这里需要注意的一点是对于一个元素的ID实际上和position不能认为是一个东西,实际上可以是完全不同的值
*/
@Override
public long getItemId(int position) {
return position;
}
/**
* @param position
* @param convertView
* @param parent
* @return 获取每一个Item的显示内容
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal, parent, false);
ImageView img_icon = convertView.findViewById(R.id.img_icon);
TextView text_aName = convertView.findViewById(R.id.txt_aName);
TextView text_aSpeak = convertView.findViewById(R.id.txt_aSpeak);
img_icon.setBackgroundResource(mData.get(position).getaIcon());
text_aName.setText(mData.get(position).getaName());
text_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;
}
注意事项:
- 可能你会对getItemId()方法有所疑问:为什么可以直接返回position,那么这个方法的意义是什么?实际上这里只是一个特殊的例子,一个元素的ID号和position其不一定相同!此处特殊就特殊在将iD号和positon设为相同了,所以我们在2.中的MainActivity.java文件中完全可以将语句:
Toast.makeText(mContext, "你点击了第" position "项", Toast.LENGTH_SHORT).show();
修该为:
代码语言:javascript复制 Toast.makeText(mContext, "你点击了第" mAdapter.getItemId(position) "项", Toast.LENGTH_SHORT).show();
- 为什么方法
getItem()
可以返回null,难道BaseAdapter内部通过此方法去找对应的元素不会报出空指针异常吗?事实上没有报,这是因为BaseAdapter方法并没有内部调用此方法,此方法修饰为public就是为了方便程序员进行调用,而BaseAdapter内部早就集成了访问列表元素的方法。 证明方法:将上述Toast的方法改写: Toast.makeText(mContext, "你点击了第" mAdapter.getItem(position).getClass(), Toast.LENGTH_SHORT).show(); 如果你在app中点击某一行的item直接会造成程序闪退,并在Android Studio上抛出空指针异常:
如果将getItem
方法重写为以下版本:
@Override public Object getItem(int position) { return mData.get(position); }
就不会再报出空指针异常,而是正常打出类信息的相关提示,如下图所示:
我们从主动调用不同定义的getItem()
方法来说明此例,BaseAdapter真的没有调用此方法,否则一定会爆出空指针异常。
- BaseAdapter内部为何使用LinkedList而不是其他数据结构?实际上说明类型的数据结构并不重要,BaseAdapter对此并没有规定,你可以使用ArrayList代替,甚至可以选择使用数组代替。但是为何不选择数组呢,主要原因还是我们再MainActivity.java中加入每行的数据只需要调用
add
方法即可,而数组就麻烦的很多,后者需要确定个数,尾指针等等额外的理解开销;
补充2: 方法 getItem 以及 getItemId 的用途
对于这两个方法的理解对于Andoird中关于Adapter设计模式会有很大的帮助:
可以参考的网址:
What is the intent of the methods getItem and getItemId in the Android class BaseAdapter?
如果你嫌英语太麻烦,那么可以直接看我对此的理解:
首先我们要理解一点,为何使用Adapter适配器,Adapter就是给我们视图资源在调用数据的时候能够相当程度上方便,视图只需要直接和Adapter交互,而不需要和数据本身交互,总结一句话:Adapter对象将资源访问进行了封装。首先getItem()
方法就是出于这个目的。假设我们没有Adapter,那么我们读取数据就需要使用例如:myListData.get(position)
的方法,这里我们就直接调用了数据资源myListData
对象,封装程度不高。但是如果利用Adapter对象,那么我们将资源访问就简化为:adapter.get(position)
。
简单地说,Android允许将一个long类型的数据附加到任何ListView对象的元素上,对,这是附加的,实际上你可以选择忽略此值。当你选择一个ListView所存的元素时,适配器可以提供给我三个有关的特性值:
- 一个元素对象自身的引用
- 此元素在ArrayList所存的下标索引position
- 返回此元素上所附着的long类型值
实际上这三个特性值分别对应我们需要重写BaseAdapter的2个抽象get方法:
- getItem
- 第二个position本身就是自带的
- getItemId
而这些值的计算以及设定完全区别于我们打算对读取这些值后做什么操作,自然我们可以选择不做任何操作。比方说每个Adapter对象都提供了以getItemId()
方法,我们可以选择用或者不用,但是我们不用也无法避免重写此方法,因为BaseAdapter被设置为抽象类,而这些方法是抽象方法,所以我们直接就简单地写为:
@Override
public long getItemId(int position) {
return position;
}
我们本来就能通过position直接读出下标索引,所以用getItemId()
这个方法单纯来获取position值反而更加麻烦了,但是我们本身如此重写定义目的单纯只是为了重写规则而重写。这样的写法已经成为了Android世界中的一个通常的做法(惯例)。
补充3 :方法getView()的代码解释
代码语言:javascript复制 /**
* @param position
* @param convertView
* @param parent
* @return 获取每一行Item的显示内容,每有一行Adapter对象都需要通过此方法向ListView传递控件的属性以及资源的取值
*
*
*/
@Override
public View getView(int position, View convertView, ViewGroup parent) {
//通过infalte方法返回ListView对象每一行的布局item对象
convertView = LayoutInflater.from(mContext).inflate(R.layout.item_list_animal, parent, false);
/**
* 以下三个方法是通过方法convertView.findViewById()方法返回R文件中所导入的,构成每一行布局文件的控件
*
* 注意这里和Activity中所用的findViewById是有所区别的,这里需要主动地写出调用此方法的对象:convertView
*/
ImageView img_icon = convertView.findViewById(R.id.img_icon);
TextView text_aName = convertView.findViewById(R.id.txt_aName);
TextView text_aSpeak = convertView.findViewById(R.id.txt_aSpeak);
/***
* 通过不同控件的不同set方法,将从数据源中所存的资源写入到不同控件中去
*/
img_icon.setBackgroundResource(mData.get(position).getaIcon());
text_aName.setText(mData.get(position).getaName());
text_aSpeak.setText(mData.get(position).getaSpeak());
return convertView;//返回对应于ListView一行对应的布局View对象
}
看到这个方法,我们就要想我们在MainActivity.java中应当以何种方式来方便的处理Adapter对象呢?我们需要做以下3件事:
- 确定传递给Adapter对象数据源,数据源可以是在MainActivity中创建的,也可以是创建在values文件夹中的;
- 调用Adapter构造方法,需要输入数据源以及上下文
- 通过findViewById找到listView对象
- 调用listView对象的setAdapter方法将适配器对象传递给ListView控件
一个最简单的控件利用适配器来进行布局资源的调配就是如此,对于控件而言是相当轻松的,和数据源相关的甚至直接就简化为setAdapter()
方法,而且不是直接相关,只是有间接的关系。