今天我们来看2个列表的布局,一个是ListView,一个是RecyclerView.
xml布局
activity代码
class AActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
acMainAListView.adapter = MyListViewAdapter(getDataList(), this)
}
private fun getDataList(): List {
val dataList = mutableListOf()
for (i in 0 until 50) {
dataList.add("我是第:${i + 1}项")
}
return dataList
}
}
Adapter代码
class MyListViewAdapter extends BaseAdapter {
private List dataList;
private LayoutInflater inflater;
public MyListViewAdapter(List dataList, Context context) {
this.dataList = dataList;
inflater = LayoutInflater.from(context);
}
@Override
public int getCount() {
return dataList == null || dataList.isEmpty() ? 0 : dataList.size();
}
@Override
public Object getItem(int position) {
return dataList.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
MyViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
holder = new MyViewHolder();
holder.tvMsg = convertView.findViewById(R.id.layoutListItemTvMsg);
convertView.setTag(holder);
} else {
holder = (MyViewHolder) convertView.getTag();
}
holder.tvMsg.setText(dataList.get(position));
return convertView;
}
private class MyViewHolder {
TextView tvMsg;
}
}
代码讲解
首先我们需要在xml中布局listView,代码很简单,直接放置listView就可以了。
然后我们就需要最重要的一步,在代码中给listView设置适配器adapter。
什么是适配器?简单说就是我们想要让列表的每一项怎么显示?大家应该能想到了,其实适配器就是一个布局,布局的是列表的每一项的样式。
这里我们需要自定义一个adapter,只需要继承BaseAdapter即可,其中 有几个需要重写的方法:
前面3个方法都比较好理解,我们重点来看看第4个getView方法。可以看到这个方法的参数,除了position之外还有2个,一个View,一个ViewGroup。
根据参数名称我们可以大概知道其代表的意思,convertView表示的当前position的视图,如果当前position是反复滑动的时候再次出现的,那么这个view是有值的,第二个是代表的当前项View的父视图,即listView。
大家想象一种情况,如果我们的列表有几百上千个,那么一屏肯定是显示不完的,那么就要上下拉来查看更多和返回查看之前的。如果我们在getView里面都是新建一个View,重新创建对象那么肯定是会很耗内存的。这里怎么办呢?就需要我们的convertView了。
看看代码里面是怎么写的:
MyViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
holder = new MyViewHolder();
holder.tvMsg = convertView.findViewById(R.id.layoutListItemTvMsg);
convertView.setTag(holder);
} else {
holder = (MyViewHolder) convertView.getTag();
}
holder.tvMsg.setText(dataList.get(position));
return convertView;
首先我们定义了一个ViewHolder,但是并没有new对象,这个Viewholder里面放的就是一个TextView。
然后我们就判断了convertView是不是为空,如果为空那么就表示当前的position是第一屏第一次出现,为什么说第一屏第一次出现待会我们再说,然后我们就通过layoutInflate来加载一个布局,注意,这里加载布局的时候我们把parent传给了加载方法。加载了当前需要的view之后我们再new的ViewHolder,并且通过findViewById获取的textview,第二个重要的点就在后面了,我们使用convertView的setTag方法把当前ViewHolder设置进去了。
如果convertView不为空,那么说明当前的view是复用的之前已经加载到内存里面的视图,还记得刚刚设置的tag吗? 这里我们只需要取出tag就能拿到ViewHolder了,而不用在通过new来创建对象了。
然后我们在重新设置TextView的文本。
这样我们就能通过view的复用来消除内存的异常了,从而大大的节省内存。
这里再介绍另外一个非常有用的方法,int getItemViewType(int position),这个方法的作用是通过position来返回当前View的类型。试想一下,我们刚刚这样的处理列表每一项的样式都是一样的,那如果我想要在一些特殊的位置有不同的布局呢?就可以使用这个方法了,只需要在getView中调用此方法来判断是什么类型再加载不同的view即可。
@Override
public int getItemViewType(int position) {
return position % 2;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
Log.d(getClass().getName(), "ZLog getView :" + convertView + " " + parent);
MyViewHolder holder;
if (convertView == null) {
convertView = inflater.inflate(R.layout.layout_list_item, parent, false);
holder = new MyViewHolder();
holder.tvMsg = convertView.findViewById(R.id.layoutListItemTvMsg);
holder.rootView = convertView;
convertView.setTag(holder);
} else {
holder = (MyViewHolder) convertView.getTag();
}
if (getItemViewType(position) == 0) {
holder.rootView.setBackgroundColor(Color.parseColor("#CCCCCC"));
} else {
holder.rootView.setBackgroundColor(Color.WHITE);
}
holder.tvMsg.setText(dataList.get(position));
return convertView;
}
这里简单实现了一个效果,通过getItemViewType来设备view的背景
下面一个问题,怎么进行交互?怎么知道用户点击了哪一项?
这个很简单,跟我们设置view的点击事件一样,listView有一个setOnItemClickListener方法来处理点击操作。
acMainAListView.setOnItemClickListener { parent, view, position, id ->
Toast.makeText(this, "点击了第${position}项", Toast.LENGTH_SHORT).show()
}
不过使用这里有一个需要注意的地方,如果我们需要使用setOnItemClickListener方法,那么在每一项的布局中不能使用button。因为如果在子项里面使用了button之后,setOnItemClickListener就无效了。
如果要使用它,我们首先要做一个导包的操作:
或者通过IDE来添加:
搜索出来后直接点击OK就可以了。
xml布局
activity代码:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
acMainAListView.adapter = MyRecyclerViewAdapter(getDataList(), this)
acMainAListView.layoutManager = LinearLayoutManager(this)
}
private fun getDataList(): List {
val dataList = mutableListOf()
for (i in 0 until 50) {
dataList.add("我是第:${i + 1}项")
}
return dataList
}
MyRecyclerViewAdapter代码:
class MyRecyclerViewAdapter(val dataList: List, context: Context) : RecyclerView.Adapter() {
private val layoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(layoutInflater.inflate(R.layout.layout_list_item, parent, false))
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = dataList[position]
}
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val textView = view.findViewById(R.id.layoutListItemTvMsg)
}
布局xml
看到adapter的代码有没有感觉代码少了好多……好幸福,得益于recyclerview的优秀设计,我们不需要想在listview中去自己判断是不是要复用view这些东西了,我们只需要关注自己的业务逻辑。
recyclerview的adapter需要继承RecyclerView.Adapter。它需要泛型参数,即我们的ViewHolder,我们在下面定义了一个ViewHolder类,继承自RecyclerView.ViewHolder,它的构造方法需要一个View,即每个item的视图View。
adapter本身也只需要重新onCreateViewHolder/getItemCount/onBindViewHolder3个方法。
onCreateViewHolder即获取我们自定义的ViewHolder,我们在这里加载需要的布局。
getItemCount返回列表数量
onBindViewHolder为处理视图数据的方法,这里系统给我们带回了ViewHolder和position。
大家应该也注意到在activity中设置adapter后,我们还设置了一个acMainAListView.layoutManager = LinearLayoutManager(this)。这里开始体现了RecyclerView的强大了。我们这里设置的layoutManager是LinearLayout,默认是垂直方向的,也可以设置水平方向,
acMainAListView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)
这样就是水平方向的滑动。如果是网格呢?简单
acMainAListView.layoutManager = GridLayoutManager(this, 3)
网格同样也是可以设置方向的
acMainAListView.layoutManager = GridLayoutManager(this, 2, GridLayoutManager.HORIZONTAL, false)
是不是很方便?
还有一种更厉害的布局,大家是不是在一些展示图片类的应用上看到过所谓瀑布流的布局?即每个item的宽高都不同。
我们稍微改一下代码,让每项的内容不同,达到每个布局大小不一样的效果
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
acMainAListView.adapter = MyRecyclerViewAdapter(getDataList(), this)
acMainAListView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
}
private fun getDataList(): List {
val dataList = mutableListOf()
for (i in 0 until 50) {
val sb = StringBuilder()
val size = Random().nextInt(50)
for (j in 1 until size) {
sb.append(j)
}
dataList.add("我是第:${i + 1}项,${sb.toString()}")
}
return dataList
}
修改的地方有2点,第一个是getDataList,显示的文本随机增加了内容。第二个是layoutManager,改成了StaggeredGridLayoutManager。我们来看看效果:
因为我们随机的对显示内容进行了修改,所以每个item要显示的都不一样,这样看到的效果就是每个item的高度都不同。
下面是比较麻烦的一点,你会发现recyclerview没有类似setItemClickListener这样的方法。确实,recyclerview没有这样的方法,并不是故意不支持,而且Google特意这样做的,其实像listView的setOnItemClickListener方法在实际应用的时候用的并不多。一般的布局都是很复杂的,所以用到这个方法的机会也不多。那recyclerview怎么来监听点击事件呢?
这里介绍1种,自定义一个接口,然后在adapter的onBindViewHolder中对要响应点击的控件设置onClickListener,再在onclick方法中回调接口。下面看代码
首先定义我们需要的接口
interface IRecyclerViewClick {
fun onItemClick(position: Int)
}
adapter中传递接口并且在onClick中调用
class MyRecyclerViewAdapter(val dataList: List, context: Context, val itemClick: IRecyclerViewClick) :
RecyclerView.Adapter() {
private val layoutInflater = LayoutInflater.from(context)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(layoutInflater.inflate(R.layout.layout_list_item, parent, false))
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.textView.text = dataList[position]
holder.textView.setOnClickListener {
itemClick.onItemClick(position)
}
}
}
activity
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
acMainAListView.adapter = MyRecyclerViewAdapter(getDataList(), this, object : IRecyclerViewClick {
override fun onItemClick(position: Int) {
Toast.makeText(this@AActivity, "点击了第$position 项", Toast.LENGTH_SHORT).show()
}
})
acMainAListView.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
}
这里就差不多讲完了这种2列表的处理,当然目前主推的还是第二种Recyclerview。还有很多没有讲到的地方后续会在以后应用的地方在慢慢讲解。大家也都可以动手试试。