本帖最后由 就业部_安卓组 于 2017-1-11 14:56 编辑
接 上篇。首先我们要先做数据,这里我创建了一个 CustomBean.java [Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
/**
* Created by mesmerize on 2017/1/5.
*
* 数据
*/
public class CustomBean {
// 昵称
public String nick;
// 最后一条聊天信息
public String chatMessage;
// 头像
public int headImage;
// 最后聊天的时间
public String time;
public CustomBean(String nick, String chatMessage, int headImage, String time) {
this.nick = nick;
this.chatMessage = chatMessage;
this.headImage = headImage;
this.time = time;
}
}
让我们看看 activity_main.xml [XML] 纯文本查看 复制代码 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.mesmerize.listviewapp.MainActivity">
<ListView
android:id="@+id/lv_listview"
android:layout_width="match_parent"
android:layout_height="match_parent"></ListView>
</RelativeLayout>
很简单,就是一个单纯的 ListView,其他没什么控件了,然后呢?没有然后了,就这么多就可以了,接下来再创建一个 CustomActivity.java [Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by mesmerize on 2017/1/5.
*
* 自定义 ListView
*/
public class CustomActivity extends Activity {
private List<CustomBean> customBeanList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customBeanList = new ArrayList<>();
ListView mListView = (ListView) findViewById(R.id.lv_listview);
}
}
写到这里你可能会有疑问了,我们找到了 ListView 这个控件,也有了 List 集合,接下来怎么办呢?如何将数据与视图做关联呢? 不知道你还是否记得前面咱们说过 ListView 是通过 Adapter 来将视图与数据进行关联的,在 Adapter 对象构造的时候将数据传递过去,并且在 Adapter 中定义 ListView 每一个 item 条目的视图,然后通过 ListView 的 setAdapter() 方法进行关联。那接下来咱们来创建一个 Adapter,看看这个 Adapter 是如何编写的。 [Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by mesmerize on 2017/1/5.
*/
public class CustomAdapter extends BaseAdapter {
private final CustomActivity context;
private final List<CustomBean> list;
public CustomAdapter(CustomActivity customActivity, List<CustomBean> customBeanList) {
this.context = customActivity;
this.list = customBeanList;
}
@Override
public int getCount() {
return list.size();
}
@Override
public Object getItem(int position) {
return null;
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = View.inflate(context, R.layout.adapter_custom, null);
ImageView head = (ImageView) view.findViewById(R.id.head);
TextView nick = (TextView) view.findViewById(R.id.nick);
TextView message = (TextView) view.findViewById(R.id.message);
TextView time = (TextView) view.findViewById(R.id.time);
head.setImageResource(list.get(position).headImage);
nick.setText(list.get(position).nick);
message.setText(list.get(position).chatMessage);
time.setText(list.get(position).time);
return view;
}
}
在这里,我们的 Adapter 继承的是 BaseAdapter,之后重写四个方法,在这里我们重点关注两个方法就够了,分别是 getCount() 以及 getView() 这两个方法。可以看到,在这里,我们的 CustomAdapter 构造方法里面接收了两个参数,分别是 Context 上下文以及 List这两个参数。上下文是需要的,在填充一个 view 视图的时候需要用到,在这里我们传进来的上下文是 CustomActivity,而第二个参数的意思是 view 需要显示的数据是哪些。这两个缺一不可。接下来看 getCount() 和 getView()。 首先 getCount() 很简单,就是指 ListView 一共有多少条,返回值是一个 int,我们这里只需要看传进来的 List 的 size 是多少就可以了,所以直接 return list.size() 就可以了。这样,系统就知道要绘制多少次了。 其次,getView(),顾名思义,就是视图的意思,他的返回值是一个 View。里面的参数暂时先不用管,待会会讲到他们的意思。
在这里,我们首先要告诉系统我们的 item 的视图长什么样子,所以第一行代码的意义在此: [Java] 纯文本查看 复制代码 View view = View.inflate(context, R.layout.adapter_custom, null);
这个可以理解为,将 R.layout.adapter_custom 这个布局填充成了一个 view 对象,我们可以通过这个对象查找其中的 view 控件。这个就相当于咱们在 activity 中 findViewById 一样,只不过在这之前要加上 view,我们需要通过 view.findViewById 的形式来查找。其实在 Activity 中,是通过 Activity.this.findViewById 的形式来查找的,只不过一般咱们都省略了 this 罢了。 我们看一看 adapter_custom.xml 布局文件: [Java] 纯文本查看 复制代码 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/head"
android:layout_margin="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@mipmap/head_jinx"/>
<TextView
android:id="@+id/nick"
android:layout_toRightOf="@id/head"
android:textSize="18sp"
android:layout_marginLeft="9dp"
android:layout_marginTop="12dp"
android:textColor="#000000"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="金克斯"/>
<TextView
android:id="@+id/message"
android:layout_marginLeft="7dp"
android:layout_marginTop="15dp"
android:layout_below="@id/nick"
android:layout_toRightOf="@id/head"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="啊哈哈哈哈啊哈哈哈啊哈哈哈"/>
<TextView
android:id="@+id/time"
android:text="15:33"
android:layout_alignParentRight="true"
android:layout_marginTop="15dp"
android:layout_marginRight="15dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
布局显示如下:
也就是说,咱们的每一个 item 都是长这个样子。在这里,为了显示方便,我写了一些死数据,真实开发中不建议写死数据,这一点切记。 然后我们将这个 xml 布局填充成一个 view 对象之后,要将此 view 返回出去,告知系统咱们的 view 视图。接下来,咱们就要做些事情了,先是通过 findViewById 将 xml 布局中的控件一个个的查找出来:
[Java] 纯文本查看 复制代码 ImageView head = (ImageView) view.findViewById(R.id.head);
TextView nick = (TextView) view.findViewById(R.id.nick);
TextView message = (TextView) view.findViewById(R.id.message);
TextView time = (TextView) view.findViewById(R.id.time);
查找出来之后就好办了,接下来就开始操作这些控件了,咱们只需要将对应的数据一个个的给相应的控件 set 上就可以了,代码如下: [Java] 纯文本查看 复制代码
head.setImageResource(list.get(position).headImage);
nick.setText(list.get(position).nick);
message.setText(list.get(position).chatMessage);
time.setText(list.get(position).time);
给头像设置上图片,昵称、聊天内容、最近聊天时间一个个的都填充上。这样,咱们的 adapter 就写好了。接下来只差最后一步了,我们需要将 adapter 与 ListView 做关联,打开咱们的 CustomActivity,加上这句代码: [Java] 纯文本查看 复制代码 CustomAdapter mAdapter = new CustomAdapter(this, customBeanList);
mListView.setAdapter(mAdapter);
意思是,首先我们创建了 Adapter 对象,并且将上下文以及 List 传递进去,接下来调用 ListView 的 setAdapter 方法关联,这样就可以了。但是这个时候我们运行会发现程序一片空白,什么也没有。这是为什么呢?因为我们没有数据啊! 我们创建了 List 的确不假,但是我们没有给它添加数据啊,那这个 List 的size 不就是 0吗?如果是 0 的话,系统在绘制的时候肯定绘制 0 次啊!所以显示一片空白是很正常的。那现在我们要造一点数据了。我这里是这么写的: [Java] 纯文本查看 复制代码 /**
* 初始化数据
*/
private void initCustomList(){
CustomBean vn = new CustomBean("薇恩", "走,开黑去,我带你秀起来", R.mipmap.head_vn, "20:23");
customBeanList.add(vn);
CustomBean lks = new CustomBean("拉克丝", "我刚买的大天使元素,来来来,带你上分", R.mipmap.head_lakesi, "17:33");
customBeanList.add(lks);
CustomBean ey = new CustomBean("好运", "啊哈哈,啊哈哈哈哈,哈啊啊哈哈啊", R.mipmap.head_eyun, "16:53");
customBeanList.add(ey);
CustomBean xz = new CustomBean("瞎子", "我用双手成就你的梦想,一库", R.mipmap.head_liqing, "16:15");
customBeanList.add(xz);
CustomBean ylst = new CustomBean("伊利斯坦", "我的滑板鞋时尚时尚最时尚", R.mipmap.head_ylst, "15:25");
customBeanList.add(ylst);
CustomBean irly = new CustomBean("艾瑞莉娅", "不要再削我了,呜呜~~~~(>_<)~~~~", R.mipmap.head_irly, "14:22");
customBeanList.add(irly);
CustomBean ali = new CustomBean("阿狸", "你渴望什么呢?", R.mipmap.head_ali, "13:55");
customBeanList.add(ali);
CustomBean jinx = new CustomBean("金克斯", "我数到三...... 3", R.mipmap.head_jinx, "13:53");
customBeanList.add(jinx);
}
创建了一个 initCustomList 方法,然后里面造了一大堆数据,并且将他们都添加到 customBeanList 集合中去。接下来,咱们只需要调用一下这个方法执行就可以了,因此,最终 CustomActivity . java 的代码如下: [Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
import android.app.Activity;
import android.os.Bundle;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
/**
* Created by mesmerize on 2017/1/5.
*
* 自定义 ListView
*/
public class CustomActivity extends Activity {
private List<CustomBean> customBeanList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customBeanList = new ArrayList<>();
initCustomList();
ListView mListView = (ListView) findViewById(R.id.lv_listview);
CustomAdapter mAdapter = new CustomAdapter(this, customBeanList);
mListView.setAdapter(mAdapter);
}
/**
* 初始化数据
*/
private void initCustomList(){
CustomBean vn = new CustomBean("薇恩", "走,开黑去,我带你秀起来", R.mipmap.head_vn, "20:23");
customBeanList.add(vn);
CustomBean lks = new CustomBean("拉克丝", "我刚买的大天使元素,来来来,带你上分", R.mipmap.head_lakesi, "17:33");
customBeanList.add(lks);
CustomBean ey = new CustomBean("好运", "啊哈哈,啊哈哈哈哈,哈啊啊哈哈啊", R.mipmap.head_eyun, "16:53");
customBeanList.add(ey);
CustomBean xz = new CustomBean("瞎子", "我用双手成就你的梦想,一库", R.mipmap.head_liqing, "16:15");
customBeanList.add(xz);
CustomBean ylst = new CustomBean("伊利斯坦", "我的滑板鞋时尚时尚最时尚", R.mipmap.head_ylst, "15:25");
customBeanList.add(ylst);
CustomBean irly = new CustomBean("艾瑞莉娅", "不要再削我了,呜呜~~~~(>_<)~~~~", R.mipmap.head_irly, "14:22");
customBeanList.add(irly);
CustomBean ali = new CustomBean("阿狸", "你渴望什么呢?", R.mipmap.head_ali, "13:55");
customBeanList.add(ali);
CustomBean jinx = new CustomBean("金克斯", "我数到三...... 3", R.mipmap.head_jinx, "13:53");
customBeanList.add(jinx);
}
}
数据还是有点少,这个时候我们可以多添加几次,for 循环一下就可以了,然后运行一下,看看效果。
滑动一下看看:
ok 了,以上就是一个简单的用法。我们可以自定义修改 xml 的布局,就可以定制出各种复杂的界面了,简单吧?快去试试吧!
ListView 的效率
前面说到 ListView 很难用,这么说是有原因的,不知道各位在实现了上述效果的时候是否有疑惑,“就是这样吗?”,“就这么简单吗”,如果你这么想了,那就对了。因为我们还有很多的细节可以优化,而且咱们的代码也有个隐患,这也关乎到运行效率。这样的代码,如果加载的数据量过多的话,程序直接就蹦了。当前我们的这个程序的运行效率也是很低的,问题就出在 CustomAdapter 这个类中的 getView() 方法中。在这里,由于篇幅的原因,我就不演示了,直接告诉大家测试的方法,大家可以自行去测一下。大家可以在 getView() 方法中打印一下,会发现每次滑动出一个新条目的时候都会创建一个新的对象,每次 getView() 这个方法都会执行一次的。 这个现象是很严重的,这相当于每次都将布局重新加载了一遍,可以想象,当我们快速滑动 ListView 的话会有什么样的后果,这很有可能会成为性能的瓶颈,那该怎么办? 别担心,我走这条路已经很多年了,稳的狠,上车,我带你。 让我们先把焦点放在 getView() 这个方法上面,我们发现getView() 方法中有一个 convertView 的参数,这个参数用于将之前加载好的布局进行缓存,方便我们之后进行重用。这就很好的解决了我们上面所提到的重复构建对象、每次重复绘制布局的问题了,该怎么使用呢?看代码: [Java] 纯文本查看 复制代码 public class CustomAdapter extends BaseAdapter {
private final CustomActivity context;
private final List<CustomBean> list;
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CustomBean item = list.get(position);
View view;
if (convertView == null) {
view = View.inflate(context, R.layout.adapter_custom, null);
} else {
view = convertView;
}
ImageView head = (ImageView) view.findViewById(R.id.head);
TextView nick = (TextView) view.findViewById(R.id.nick);
TextView message = (TextView) view.findViewById(R.id.message);
TextView time = (TextView) view.findViewById(R.id.time);
head.setImageResource(item.headImage);
nick.setText(item.nick);
message.setText(item.chatMessage);
time.setText(item.time);
return view;
}
}
在这里,我们修改了一些代码,在 getView 方法中对 convertView 做了判断,如果为 null,则去填充一个布局,否则直接重用 convertView。这样就大大提高了 ListView 的运行效率,哪怕我们快速滑动也不会崩溃了。同时还对 CustomBean 做一层抽取,方便后续代码的简化,做到这一步,就完成了咱们的优化。但是仅仅如此吗? 肯定不止,我们还可以继续做优化的,为什么还要做优化呢?因为我们现在的代码虽然不会重复去加载布局,但是每次调用 getView() 方法还是会调用 View 的findViewById() 方法来获取控件的实例,那么我们下一步的优化就是对它下手,在这里我们需要使用 ViewHolder 的方法来对这部分做优化了,代码如下:
[Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
import java.util.List;
/**
* Created by mesmerize on 2017/1/5.
*/
public class CustomAdapter extends BaseAdapter {
private final CustomActivity context;
private final List<CustomBean> list;
...
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CustomBean item = list.get(position);
ViewHolder holder;
if (convertView == null) {
convertView = View.inflate(context, R.layout.adapter_custom, null);
holder = new ViewHolder();
holder.head = (ImageView) convertView.findViewById(R.id.head);
holder.nick = (TextView) convertView.findViewById(R.id.nick);
holder.message = (TextView) convertView.findViewById(R.id.message);
holder.time = (TextView) convertView.findViewById(R.id.time);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.head.setImageResource(item.headImage);
holder.nick.setText(item.nick);
holder.message.setText(item.chatMessage);
holder.time.setText(item.time);
return convertView;
}
class ViewHolder {
ImageView head;
TextView nick,message,time;
}
}
在这里修改的代码比较多,我们新创建了一个内部类 ViewHolder,这个是为了对每个控件的实例进行缓存。然后判断 convertView 为 null 的时候,我们创建一个 ViewHolder 对象,并且每次 findViewById 查找出来的控件实例统一由 ViewHolder 进行管理,最后调用了 View 的 setTag() 方法。可以将之理解为打上一个标记,将 ViewHolder 对象存储在了 View 中。当 convertView 不为 null 的时候通过 getTag() 的方法将 ViewHolder 取出。这样所有控件的实例都缓存在了 ViewHolder 中,这样就节省了 findViewById 的次数。 简而言之,我们所做的事就是把控件的实例存放在 ViewHolder 中,又通过 setTag() 将其放入了 view 中。 做了以上两步优化之后,运行效率就可以了。
ListView 的 Item 点击事件
我们仔细观察 QQ ,甚至压根不用仔细观察就知道,每个 Item 都是可以点击的,而不是单纯的展示。现在我们所写的这个 ListView 还不能点击,如果加上点击事件,该怎么做呢? 其实仔细翻阅 ListView 的 API 会发现,ListView 有一个 API 叫 setOnItemClickListener(),从名字上来看好像是给条目设置一个点击事件,那我们试试,修改 Activity 的代码,如下: [Java] 纯文本查看 复制代码 package com.mesmerize.listviewapp;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
/**
* Created by mesmerize on 2017/1/5.
*
* 自定义 ListView
*/
public class CustomActivity extends Activity {
private List<CustomBean> customBeanList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
customBeanList = new ArrayList<>();
initCustomList();
ListView mListView = (ListView) findViewById(R.id.lv_listview);
CustomAdapter mAdapter = new CustomAdapter(this, customBeanList);
mListView.setAdapter(mAdapter);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(CustomActivity.this, "哈哈哈 我是 " + customBeanList.get(position).nick, Toast.LENGTH_SHORT).show();
}
});
}
/**
* 初始化数据
*/
private void initCustomList(){
for (int i = 0; i < 3; i++) {
CustomBean vn = new CustomBean("薇恩", "走,开黑去,我带你秀起来", R.mipmap.head_vn, "20:23");
customBeanList.add(vn);
CustomBean lks = new CustomBean("拉克丝", "我刚买的大天使元素,来来来,带你上分", R.mipmap.head_lakesi, "17:33");
customBeanList.add(lks);
CustomBean ey = new CustomBean("好运", "啊哈哈,啊哈哈哈哈,哈啊啊哈哈啊", R.mipmap.head_eyun, "16:53");
customBeanList.add(ey);
CustomBean xz = new CustomBean("瞎子", "我用双手成就你的梦想,一库", R.mipmap.head_liqing, "16:15");
customBeanList.add(xz);
CustomBean ylst = new CustomBean("伊利斯坦", "我的滑板鞋时尚时尚最时尚", R.mipmap.head_ylst, "15:25");
customBeanList.add(ylst);
CustomBean irly = new CustomBean("艾瑞莉娅", "不要再削我了,呜呜~~~~(>_<)~~~~", R.mipmap.head_irly, "14:22");
customBeanList.add(irly);
CustomBean ali = new CustomBean("阿狸", "你渴望什么呢?", R.mipmap.head_ali, "13:55");
customBeanList.add(ali);
CustomBean jinx = new CustomBean("金克斯", "我数到三...... 3", R.mipmap.head_jinx, "13:53");
customBeanList.add(jinx);
}
}
}
我们为其 set 了一个监听器,当点击其中任何一个 item 时都会回调 onItemClick() 方法,我们在这个方法里面弹了一个 Toast,点击的时候弹出该项 item 的 nick,通过 position 参数判断点击的是哪一个子项,然后获取相应的 nick 并提示。 以上就是 ListView 的一些基本用法,收获了这么多,赶紧去试试吧!
源码在底部,敬请下载查阅。
Android 人事+技术总贴
Android 基础篇总贴
Android 进阶篇总贴
Android 讨论区
以上言论,如有错误或者表达不准确或者有纰漏的地方还请指正,同时欢迎大家在评论区留言给予一定的建议,我会根据大家的意见进行相应的改正,谢谢各位!
|
|