第三章 优化小游戏
系列教程导航3.3 优化信息显示
3.3.2 下篇
文章目录
适配器
在上篇的最后,我提到了适配器这个概念。这里,我来简单讲解一下。
还记得吗?我们为了实现上下滑动的效果,选择了ListView,它可以用来存放我们的消息框控件(其实也就是一些TextView)。由于ListView这个容器里面存储的数据比较复杂,Java提供了适配器来帮助将各种数据显示在ListView里面。简单来说,适配器起到了一个桥梁的作用,将各种数据和ListView联系在一起。
数组适配器
讲完了适配器是什么,我们接下来就来实际操作它。这里我们选择比较简单的ArrayAdapter,也就是数组适配器。顾名思义,ArrayAdapter像数组一样,里面可以存放一些数据,再把这些数据显示到ListView里面。
不过,ArrayAdapter一些局限性。首先,ArrayAdapter里面的每个列表项只能是TextView(也就是说只能显示文本),好在我们暂时也是只需要显示文本。所以我们还是会选择ArrayAdapter
接下来,我们开始使用ArrayApater。
首先,我们新建一个Java文件:
MsgAdapter这个类就是我们的适配器。为了使这个类具有ArrayAdapter的功能,我们用extends关键字,使它继承ArrayAdapter类。
由于ArrayAdapter里面存储的是消息框,所以我们需要消息框的布局文件(这个我们在上文已经写好)。为了找到消息框的布局文件,我们需要知道其id。于是,我们定义一个变量用来存储消息框的布局文件的id。
然后写构造函数:
这里用到了一个新的关键字:super。那么super是什么呢?为了保持进度,这个知识点我会放到本文的末尾。此处大家只需要简单的理解即可:super函数其实指的是子类对应的父类的构造函数。
接下来我们就要进入最关键的部分了:写getView函数。ListView可以上下滑动,里面有若干个子项,每个子项都有对应的一个View,而getView函数就是用来获取这些view对象,然后显示在屏幕上的。下图中,msg变量里面存储的自然就是listview的对应位置上的消息对象了,而view变量则是存储着我们需要显示在屏幕上面的view视图,事实上,getView方法的返回值也就是这个view。注意这里我们只是先声明了一下view变量,并没有给它初始化。这是因为我们不知道是否需要创建一个新的view对象。具体的原因大家可以在后文找到答案。
下面我来解释一下getView函数的三个参数:
- position,顾名思义,就是代表该view对象位于listview的位置(或者说是它告诉我们这个view位于listview的第几项)
- flagview,简单来说,它可以缓存滑到屏幕之外的项目,这样在我们下一次滑到这个项目的时候,就不需要重新加载了。正因如此,我们在加载view的时候,首先应该判断一下flagview是不是null,如果不是null的话就不需要重新加载view了
- parent,其实就是我们的listview啦
判断flagview是否为null
为了更好的讲解接下来的内容,我们简单复习一下实现聊天消息显示效果的基本思路
在定义消息布局的时候,将左右的消息框都定义出来,然后在发送消息的时候进行判断,如果是我们自己发的消息,就把左边的消息框隐藏;反之,如果是程序发送的消息,就把右边的消息框隐藏
事实上,listview的每一项里面都有很多控件。如果按照一般思路,我们会依次创建这些对象,然后把这些对象“装”到列表项里面。但是,仔细一想,这样会有很多坏处。创建大量的控件对象会在一定程度上影响程序的运行速度,还会占用大量内存。
究其原因,就在于我们创建了很多变量,用来存储控件对象,而这些变量只是加载当前的view的时候有用,之后就没有用了。所以说我们可以引入一个“容器”来帮忙存储这些变量,这样就不用每次都创建新的变量来存储控件对象,只需要把这些变量的值变化即可。一般来讲,我们会使用一个类来存储这些变量,这个类就是ViewHolder
ViewHolder类和我们平常见到的类其实差不多,只不过,它里面一般只有属性,没有方法。所以说,它只是充当一个容器而已。这里我把它直接写在了MsgAdapter.java文件中,代码如下:
然后,我们回到MsgAdapter类的getView函数里面,声明一个ViewHolder变量。注意,这里只需要声明即可,不需要创建ViewHolder对象,因为有可能我们需要加载的view对象正好和前一个一样。所以我们应该先判断一下flagView,也就是先前加载的那个view对象,是不是null。如果是null,那就新建一个viewHolder对象,然后赋给viewHolder变量;如果不是null,那就把flagView的viewHolder对象直接赋给viewHolder变量。代码如下:
在获取view的viewHolder对象时,我们使用到了getTag函数。这个函数可以获取到对应view对象所绑定的viewHolder对象。不过,这个函数的返回值类型并不是viewHolder,所以我们需要进行强制类型转换。(我们可以大胆猜一猜,既然获取viewHolder对象是用getTag函数,那么绑定viewHolder对象应该就是用setTag之类的函数了)
接下来,我们思考flagview为null的情况。(事实上,flagview不为null的情况我们已经思考完了。因为那里的view对象和viewHolder对象都已经创建完成)当flagview为空时,我们需要得到view对象和viewHolder对象。首先获取view 对象。之前我们获取一个控件对象的话一般用的都是findViewById方法,不过,这里我们不能用这个方法。因为它的使用前提是,控件所在的界面已经被加载出来了。这里,很明显,getView方法需要返回的控件对象原本不在界面里面,换句话说,getView方法返回的对象是被动态地添加到了界面里面。
不过,办法总比困难多。获取控件对象 的方法除了findViewById,还有另外一个:使用LayoutInflater类的inflate函数。
大概的原理就讲到这里,如果有同学雪妖了解的更深一点的话,可以自行查阅有关资料。下面我给出有关的代码:
接下来,我们把消息框里面的各种控件全都对应着放到容器——ViewHolder类里面去。注意这里我们可以使用findViewById方法,这是因为这些控件已经随着view对象加载到了界面里面。
然后,将viewHolder与这个view对象绑定,使用setTag方法(跟之前猜测的一样,哈哈)
到这里,我们完成了view对象的创建工作。不过,现在的view对象并不是最终返回的view对象。简单来讲,我们还需要完成以下任务:
- 根据msg对象的name属性,判断该消息是由谁发出
- 如果消息由我们发出,则把左边的线性布局设置为“不可见”,右边的线性布局设为“可见”;反之,如果消息由电脑发出,则把右边的线性布局设置为“不可见”,左边的线性布局设为“可见”
- 向可见的线性布局里面的控件填充有关信息。比如需要发送的文本等等
一个一个来解决。首先是msg对象,这个我们在才开始写getView方法的时候就已经搞定了。接下来,如何得到msg的name属性呢?仔细一想,我们之前在Msg里面写过一个方法,叫做getName,就是用来获取msg对象的name属性的。最后,方便起见,我把“我”的名字设为“玩家”。于是,第一点完成。
第二点,设置某个线性布局不可见的方法,自然是它自己最清楚了。这里要注意,这个线性布局是被存在viewHolder里面的,所以说我们需要从viewHolder里面取到左边或者右边的线性布局对象。取到线性布局对象之后,使用setVisibility方法,就可以设置该对象是可见还是不可见了。具体代码如下:
第三点,我们这里需要填充的信息有两个:名字和消息内容。这两个信息都可以通过msg对象的对应方法来获得。为此,我们只需要分别获取到两个文本框对象,使用setText方法即可。具体代码如下:
MsgAdapter类至此编写完成!回头看看自己写的代码,会不会很有成就感呢!
完成
做完了之前的步骤,本节的任务也终于接近尾声了。下面的内容,全部在MainActivity类中完成。在开始写代码之前,我依然先来分析一下接下来的思路:
- 获取到listview控件对象
- 我们知道,每一条消息都会被封装为Msg对象,于是我们自然需要一个容器来存储这些Msg对象。用一个数组可以吗?当然不行。因为我们事先并不知道Msg对象的个数,自然也就无法知道这个数组的长度了。不过,如果你想的话,当然也可以把这个数组的长度设置为几千或者几万,不过这样很浪费内存。于是我们需要一个新的容器来存储这些对象。
- 创建msgAdapter对象,并把msgAdapter绑定在listview上
- 按下“发送”按钮之后,首先应该判断编辑框里面是否为空字符串。然后,根据我们的消息和名字来创建一个Msg对象。接下来,把电脑的消息和名字也封装到一个Msg对象里面。最后把这两个Msg对象添加到存储Msg对象的容器里面
- 事情到这里还没完。因为系统其实并不知道消息列表已经改变,所以我们需要“提醒”它对msglist进行刷新。为了更好的阅读体验,我们还需要在发送一条消息之后,把列表自动拉到最底部
接下来,我们依次完成上面的思路:
第1点,我们依然是先在onCreate函数的外面声明变量,然后到onCreate函数里面给变量赋值。由于变量show已经没有用了,所以我们把相关的这两句代码删掉
第2点,这个新的容器,叫做集合,它可以存储各种类型的对象,而且,它的长度可以动态的改变。由于集合是很复杂的一个知识点,所以此处我们简单的了解即可,不需要深究。
声明集合变量
大家可能会感到疑惑,那个‹Msg›是什么意思呢?这个东西,叫做泛型。简单来讲,就是一个标识。这里的‹Msg›就标志着msgList这个集合里面存储的是Msg类型的对象。
接下来,我们在onCreate函数里面创建集合对象,并赋给msgList。
和一般的对象一样,我们使用new关键字即可创建这个集合的对象。
第3点,我们依然是在onCreate函数的外面声明适配器变量(声明变量已经在第1点里面完成),然后回到onCreate函数里面创建适配器对象,赋给适配器变量
然后,我们使用listview对象的setAdapter函数,将这个适配器对象与listview绑定。
第4点,我们首先看一下按钮绑定的监听器对象。
这里,因为我们之前添加消息都是在show这个TextView中进行的,所以我们需要把之前写的有关show的语句都进行一定的改动。简单分析可知,其中涉及show变量的语句有两种:show.setText("...")和show.append("..."),前者是把之前的消息全部删除,然后写下新的消息,后者则是保留原来的消息,并在原来的消息后面追加一条消息。于是我们只需要分别完成这两种功能即可。
对于show的setText方法的改动:我们首先是调用集合msgList对象的clear函数,清空其中的所有消息;然后再向其中加入一条电脑发送的消息(Msg对象)
对于show的append方法的改动:直接使用msgList对象的add方法,将对应的Msg对象存到集合里面即可。注意要删除字符串里面,我们之前写的\n。最后的样子如下图所示
下面的需要修改的语句按照相似的方法进行处理即可。下图是完成之后的代码
第5点,“提醒”系统刷新界面和自动把页面滑到底部。它们分别对应着两个函数:适配器的notifyDataSetChanged函数和列表视图的setSelection函数。这里同样不需要深究,直接使用即可。见下图:
到这里,程序的编写终于可以告一段落了!!
测试
我们运行一下这个新的软件。下面是我自己手机上面的运行结果:
结果发现消息倒是有了,但是每一条消息都没有名字!大家可以先想一想可能有哪些原因。
根据我细致的查找,我最后找到了原因:原来我们在写message.xml文件的时候,把显示名字的文本框中的文字颜色设置成了白色!于是我们回去修改一下颜色:
修改的地方有两处,图中把其中一个划上了黑线,另外一处请大家自行找到,并把#FFFFFF改为#000000
![在这里插入图片描述](https://www.icode9.com/i/ll/?i=a223c33b1cb147158c2d62d5c8c23847.png?,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBAT3dlbl9UZWQ=,size_20,color_FFFFFF,t_70,g_se,x_16#pic_center)重试一次:
成功!至此,本文所要讲述的主要内容已经结束。
super关键字
为了更好的理解super关键字,我们需要举一个例子。这里我新建了一个Java程序,并写下了一个父类和一个子类,其中父类的空参数构造函数里面写了一个打印语句,子类的构造函数里面也写了另外的一个打印语句。接着我们在主函数里面通过new关键字,创建一个子类对象。
上面的程序运行结果如下:
由此可见,在调用子类的构造函数的时候,父类的空参数构造函数也跟着被调用了!
这是为什么呢?因为在子类构造函数的第一行,其实有一个默认的隐式语句super();,我们可以尝试着&自己把它加上:
运行结果同上:
现在我们把父类的构造函数进行修改,添加一个参数:
然后我们就会发现子类构造函数里面的super();语句出现了错误。这是因为我们在父类中写了一个含有参数的构造函数,原来的空参数构造函数就没有了。于是,我们也应该按照对应的样子,在super语句中传入对应的参数:
运行结果如下:
由此,我们发现:在子类的实例化过程中,其构造函数会通过super关键字,访问父类的构造函数。为什么会有这种设定呢?这是因为子类继承了父类的属性,所以在使用父类内容之前,要先看一下父类是怎么初始化自己的内容的。这个内容是必须完成的,于是子类的构造函数中会加入一个默认的隐式的super()语句。如果说父类中没有定义空参数的构造函数,那么子类的构造函数里面就必须用super明确需要调用父类中的哪一个构造函数。
后记
本文,我带领大家完整的体验了一次开发一个简单的消息发送功能的过程。回顾一下这整个过程:我们首先是确定了要使用新的容器,listview;然后编写了消息类Msg,适配器类MsgAdapter;最后在MainActivity文件里面,把前面写的东西组装起来。简单来讲,我们这种是从“部分到整体”,也就是说我们首先完成各个部分,最后组装起来。而有的时候,问题很复杂,我们不知道各个步骤的时候,就要学会“从整体到部分”,也就是说我们首先大致完成整体,看需要哪些部分,然后再去写。当然,我真正开发的时候,很多时候都是两个过程同时进行。
最后,我在这里祝大家新年快乐!
感谢你的阅读!本教程会长期不定时更新。本人不是大神,也会犯错,如果有建议或者提问的话,欢迎评论留言!