刚出来工作,就负责一个APP的某块功能的编写,该功能就是类似微博那样的界面。微博界面的编写实际上是非常复杂的,虽然它只是一个ListView,但要想让这个ListView滑得动,是的,在一些配置低的手机,随便一个负载内容多的Item,都有可能导致OOM。。。如果只是简单的为了实现了效果,可以选择将所有内容都写在xml文件,如果布局不好的话,就会出现嵌套过多的情况,同样也会出现OOM的情况。。。就算不会出现OOM的情况,也能滑得动,也会面临是否能够滑得快。。。

要想能滑得动,也能滑得快,就要动点脑筋了。

一开始非常简单的代码就是这样:

  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {
  3. ViewHolder holder;
  4. if (convertView == null) {
  5. convertView = inflater.inflate(R.layout.list_item, null);
  6. holder = new ViewHolder();
  7. ……
  8. convertView.setTag(holder);
  9. } else {
  10. holder = (ViewHolder)convertView.getTag();
  11. }
  12. }
  13. /**
  14. * ViewHolder
  15. */
  16. private static class ViewHolder {
  17. ImageView appIcon;
  18. TextView appName;
  19. TextView appInfo;
  20. }

这是谷歌推荐的方式,实际上也解决了很多性能上的问题。

Android原生的ListView原本就做了相应的缓存机制,Recycler。

Recycler的工作原理大致如下:

假设屏幕最多能看到11个item,那么当第1个item滚出屏幕,这个item的View进入RecycleBin中,第12个要出现前,通过 getView从回收站(RecycleBin)中重用这个View,然后设置数据,而不必重新创建一个View。

这样即使有上万个Item,inflate的次数最多就是n,也就是一个屏幕能够容纳的个数。

大部分的情况都可以用这样的代码解决,但我觉得对于每个Adapter都要写一个ViewHolder实在是太麻烦了,于是进一步将代码改写为这样:

  1. @Override
  2. public View getView(int position, View convertView, ViewGroup parent) {
  3. if (convertView == null) {
  4. convertView = inflater.inflate(R.layout.list_item, null);
  5. }
  6.  
  7. ImageView icon = (ImageView)CommonViewHolder.get(convertView, R.id.image_view);
  8. }

其中CommonViewHolder的代码如下:

  1. public class CommonViewHolder {
  2.  
  3. /**
  4. * 用于获取ItemView中的控件
  5. *
  6. * @param view ItemView
  7. * @param id 要获取的控件的id
  8. * @param <T> 返回的控件的类型
  9. * @return 返回的控件
  10. */
  11. public static <T extends View> T get(View view, int id) {
  12. SparseArrayCompat<View> viewHolder = (SparseArrayCompat<View>) view.getTag();
  13. if (viewHolder == null) {
  14. viewHolder = new SparseArrayCompat<>();
  15. view.setTag(viewHolder);
  16. }
  17. View childView = viewHolder.get(id);
  18. if (childView == null) {
  19. childView = view.findViewById(id);
  20. viewHolder.put(id, childView);
  21. }
  22. return (T) childView;
  23. }
  24. }

实际上,ViewHolder就是通过setTag方法将相应的控件作为View的属性保存起来,然后下次使用的时候就可以直接复用。

既然明白了它的原理,就可以对它进行改造,CommonViewHolder是利用SparseArrayCompact存储控件。SparseArrayCompact是SparseArrayCompact是SparseArray的兼容类,本质上就是类似Map的键值对容器,谷歌宣称它的性能比Map要好,因为内在的算法已经优化了。

这里的工作很简单,同样是利用setTag方法保存View中的控件,但是却把findViewById这样的工作放在了CommonViewHolder中,这样就不用每次都要调用findViewById方法了。

为了考虑通用,还使用泛型。

代码量瞬间就减少了很多,但这时就面临了一个问题:Item项错乱了。。。

在快速滑动的时候,图片加载错了,仔细调试,就发现是Recycler机制出现了问题。当快速滑动的时候,原本应该开始加载图片的控件已经滑出屏幕,然后我的图片加载是异步的,所以图片就会加载在后面的Item上。

解决这个问题的方法同样得利用setTag方法:

  1. icon.setTag(imageUrl)
  2.  
  3. ....
  4. if(imageUrl.equals(icon.getTag()){
  5. ....
  6. }

通过setTag保存ImageView要加载的url信息,然后在下次加载的时候判断是否相同。

问题解决了,但看到getView方法中为了实现微博这样承载大量信息的界面,不得不编写大量的业务代码,而且这样复用性很差,因为微博详情的界面和列表项的界面基本一样,只是有一些不同而已。如果再写一个,就有点傻逼了,但如果不写,getView方法中肯定又要写更多的判断。

为了解决复用的问题,就开始组件化。

将微博界面拆成两个部分:HeaderView和BodyView。HeaderView负责微博作者的基本信息,而BodyView就是微博内容。

这样,我只要在getView方法中这样写:

  1. add(new HeaderView());
  2. ...
  3. add(new BodyView());
  4. ...

也就是说,我从一个静态布局改成动态布局,这样我在详情那里也可以复用。

到了这里原本也应该结束了,但我又想要为微博业务编写测试,但Android中View和业务的代码是各种纠缠,很难完全脱离View来测试业务。

经过思考和查找资料,我找到了一种方式:ViewModel。

编写相应的ViewModel作为Controller,就可以将View和业务的代码解绑:

  1. public class ViewModel{
  2. private String text;
  3. ...
  4. public void setText(TextView view){
  5. view.setText(text);
  6. }
  7. }

这样组件里面的代码就更少了,它只要声明好控件然后传进来就行了,数据的获取和绑定都在ViewModel这里。

然后我们来写一个简单的测试:

  1. ViewModel model = new ViewModel();
  2. Button button = new Button(context);
  3. button.setText("你好");
  4. model.changeButtonText(button);
  5. assertEquals("我好", button.getText());
  6.  
  7. public void changeButtonText(Button button){
  8. button.setOnClickListener(new OnClickListener(){
  9. button.setText("我好");
  10. });
  11. }

利用ViewModel,我们可以方便的测试Android种的业务。

这是到目前为止的思考和尝试,实际上,我认为代码还会不断演化下去,现在已经开始出现MVVM的一些思想的应用,到了最后,没准就会完全演化成MVVM模式。

简单的代码,只要不断思考,慢慢的,所能学到的东西就会变得越来越多,最后甚至超出我们的想象。

一个ListView布局的不断演化的更多相关文章

  1. android 开发 实现一个ListView套嵌GirdView的滚动布局

    效果图 实现思维: 首先要处理管理好需要导入的数据,我们这里创建class来处理这些数据并且便于管理它们. 创建一个主activity的布局,里面需要一个ListView控件. 创建一个class继承 ...

  2. Android 关于在ScrollView中加上一个ListView,ListView内容显示不完全(总是显示第一项)的问题的两种简单的解决方案

    是这样的哈: 有这样一个需求: 1.显示一个界面,界面上有一个列表(ListView),列表上面有一个可以滚动的海报. 2.要求在ListView滚动的过程中,ListView上面的海报也可以跟着Li ...

  3. 自定义一个ListView实现聊天界面

    摘要 ListView可以称得上Android中最常用也最难用的控件了,几乎所有的应用程序都会用到它.由于手机屏幕空间都比较有限,能够一次性在屏幕上显示的内容并不多,当我们的程序中有大量的数据需要展示 ...

  4. 心情日记app总结 数据存储+服务+广播+listview+布局+fragment+intent+imagebutton+tabactivity+美工

    ---恢复内容开始--- 结果截图如下: 第一张图是程序主界面,主要是显示记事列表的一些个事件.旁边的侧拉框是自己登陆用的.可以设置密码.可以查看反馈与关于等信息. 点击第一张图片下方的图标,会显示不 ...

  5. Android一个ListView列表之中插入两种不同的数据

    http://www.cnblogs.com/roucheng/ Android一个ListView列表之中插入两种不同的数据 代码如下: public class ViewHolder{ Butto ...

  6. Android 手机卫士--实现设置界面的一个条目布局结构

    本文地址:http://www.cnblogs.com/wuyudong/p/5908986.html,转载请注明源地址. 本文以及后续文章,将一步步完善功能列表: 要点击九宫格中的条目,需要注册点击 ...

  7. 意外地解决了一个WPF布局问题

    原文:意外地解决了一个WPF布局问题 今天做了一个小测试,意外地将之前的一个困扰解决了,原问题见<WPF疑难杂症会诊>中的“怎么才能禁止内容撑大容器?” 以前我是在外侧嵌套Canvas容器 ...

  8. 安卓activity之间值共享解决办法,tabhost之间共享父类值,字符串类型的转换,获取每一个listview的item

    1.tabhost父类值共享的解决办法 dianzhanliebiao.java是传值页面,zhuyemian.java放的是tabhost,dianzhangaikuang.java是tabhost ...

  9. android 开发 写一个RecyclerView布局的聊天室,并且添加RecyclerView的点击事件

    实现思维顺序: 1.首先我们需要准备2张.9的png图片(一张图片为左边聊天泡泡,一个图片为右边的聊天泡泡),可以使用draw9patch.bat工具制作,任何图片导入到drawable中. 2.需要 ...

随机推荐

  1. 使用Webpack和Babel来搭建React应用程序

    用Webpack(npm install -g webpack)代码打包,Webpack大致需要知道三件事: 1)让Webpack知道应用程序或js文件的根目录 2)让Webpack知道做何种转换 3 ...

  2. C#根据日期范围过滤IQueryable<T>集合

    需要扩展IQueryable<T>,参数包括一个DateTime类型的属性.开始日期.截止日期. public static class MyExtension { public stat ...

  3. asp.net 的page 基类页面 做一些判断 可以定义一个基类页面 继承Page类 然后重写OnPreLoad事件

    public class BasePage:Page protected override void OnPreLoad(EventArgs e){     base.OnPreLoad(e);    ...

  4. C# WinForm 技巧十: 开发工具

    一.摘要   为了开发效率就应该为这个框架开发一个配套工具.来生成固定格式的代码.工具界面如下:   二.数据库整理篇   添加表主键 修改表说明 修改表字段说明 生成数据库文档 导出数据库里相同的字 ...

  5. GitLab 的 Developer 角色没有权限提交问题

    "C:\Program Files\Git\bin\git.exe" push --recurse-submodules=check --progress "origin ...

  6. webpack 打包时到底如何组织js

    问题一:引入前端库,方法也是不一样的 比如 lodash.js ,作为一个 chunk 用 html-webpack-plugin 打包到页面里,会生成一个全局变量 window._ ,在其它 js ...

  7. vim中多标签和多窗口的使用

    用vim进行编辑的时候常常因为要编辑多个文件或者是编辑一个文件要参考其他文件而烦恼,这里介绍两种方法: 1.多标签 直接在编辑的时候输入: vim -p 要编辑的文件名 如vim -p * 就是编辑当 ...

  8. js 判断微信浏览器

    上周接到个需求,需求是这样的:用户扫一扫二维码会产生一个链接,该链接会向后端发送个请求,返回一个 apk 的下载地址,用户点击下载按钮可以下载此 apk.然后就发生了问题,经过测试,发现用微信扫一扫打 ...

  9. 【特别推荐】8个富有创意的jQuery/CSS3插件

    现在的互联网上什么都有,但是真正好的创意却非常稀缺,包括WEB界面也是如此,今天我们要特别推荐8个富有创意的jQuery/CSS3插件,也许这几个插件能让你的WEB界面更加富有创意和人性化. 1.jQ ...

  10. saiku执行速度优化二

    上一篇文章介绍了添加filter可以加快查询速度.下面继续分析: 下面这个MDX语句: WITH SET [~FILTER] AS {[create_date].[create_date].[--]} ...