android项目开发过程中和ListView打交道几乎是大家日常工作的内容。 除了典型的行风格UI,还有嵌入种各样控件的UI。比如嵌入GridView、ListView、HListView、ViewPager等等。表格嵌入表格实现二维数据结构,丰富了UI的表现能力。但随着界面增加的复杂度,也影响界面的加载和交互响应速度。比如像下面的界面:
根据个人实际项目开发经验,面对复杂的界面介绍几种优化方法: 1,只更新可见区域 这种方式利用getFirstVisiblePosition、getLastVisiblePosition获取到ListView当前显示的item范围,从而有条件的对这些item更新。如果要展示一个下载界面,里面的item实时显示下载进度条。如果通过adapter不停的notifyDataSetChanged实现刷新,那么则需要监听下载进度不停的刷新整张列表。而ListView局部刷新,通过匹配Item是否要更新视图,避免刷新整个列表也节省了更多无畏的动作。下面是实现局部更新的伪码 void updateProgress(AbsListView lv,Data data) { final int fvp = lv.getFirstVisiblePosition(); final int lvp = lv.getLastVisiblePosition(); fianl ListAdapter la = lv.getAdapter(); for (int i = fvp; i <= lvp; i++) { Object item = la.getItem(i); if(checkItemNeedUpdate(item,data)){//是否需要更新 View view = lv.getChildAt(i - fvp); updateItemView(view,data);//更新item视图 } } }
注册OnScrollListener配合手势FLING、SCROLL、IDLE加载显示视图。在图片加载中典型的做法是在FLING的时候不加载图片,在IDLE时加载图片。 上图的ListView每行内嵌入三页的列表,可意料的是构造UI将非常耗时或者导致卡死的情况。通过配合手势,我们就可以首次加载数据时只加载可见行里的第一页,当手势在IDLE或者行内拨动页面时再加载其它页。这里IDLE时去加载其他行内页是为了优化体验,从第一页拨动到第二页会出现第二页突然出现一堆UI的感觉。 3,预加载视图 由于ListView加载时复用视图、只加载要显示视图,所以如果getView加载很复杂的视图就会导致卡顿的情况。像下面的界面,行内有各种不同风格类型的卡片。那么在这种情形下预先加载要显示的item是很好的解决方法。 所谓预加载即是在adapter构造完在调用getView之前先把view加载解析好,在getView时直接返回view即可了。比如显示一段loading界面执行预加载,执行完后直接显示。下面是伪码: class PreLoader{ Map<Integer,View> mViews=new HashMap<Integer,View>(); private void realLoadView(int position, ViewGroup parent) { if(mViews.containsKey(position)){ View view=....//实际加载view动作,相当于getView mViews.put(position,view); } } public View get(int position){ return mViews.get(position); } public void preLoadView(int startPos,int endPos,ViewGroup parent){ for(int i=startPos;i<=endPos;i++){ realLoadView(i,parent); } } } class XAdapter extends BaseAdapter{ PreLoader mPreLoader; @Override public View getView(int position, View convertView, ViewGroup parent) { if(convertView==null){ convertView=mPreLoader.get(position); } if(convertView==null){ //实际加载view动作,相当于原getView } return convertView; } } PreLoader preLoader=new PreLoader(); XAdapter adapter= new XAdapter(data,preLoader);//构造data数据的适配 displayLoading();//显示loading界面 preLoader.preLoadView(0,adapter.getCount()-1,listView);//预加载 hideLoading();//取消loading界面 //下面显示listView
LayoutInflater.inflate解析xml加载视图是同步执行的。如果在ui线程里调用该api可能会出现阻塞ui的情况,遇到复杂的ui构造动作卡死是必须滴。 翻阅LayoutInflater源码,inflate函数主要包括两个动作:解析xml、通过xml反射实例化视图。那么照葫芦画瓢实现一个异步的LayoutInflater类不就是了吗?没错,这是可以的。但是请注意:android各种设备满天飞,怎么保证兼容! LayoutInflater是个抽象类,实际的实现在Service里(通过getSystemService获取LayoutInflater实例),所以不同的厂商可能会有不同的实现。 那么是否就没有解决方法了呢?当然不是,没有办法我写出这个方式干嘛。考虑到兼容性问题,那么就用getSystemService(Context.LAYOUTINFLATERSERVICE)返回的LayoutInflater实例,包装一个异步处理的实现。不过这个实现方法有点局限, 必须保证inflate函数的attachToRoot参数为false,因为如果attachToRoot=true的话,inflate会将解析实例化的视图add到root里,由于是异步操作导致add方法不在ui线程里执行可能会抛异常,不过通常情况下attachToRoot=false的场景比较多的。下面是实现异步的伪码 new Thread(){ public void run(){ final View view=layoutInflater.inflate(resource,root,false); postUIDosomething(view,root);//在ui线程里干活 } }.start()
自定义控件一方面可以减少组合控件之间的层级,另外也可避免控件组合时迎合某种控件而花空心思构造各种数据结构。不过这个方法应用的场景非常小。对于界面经常变化的项目来说无疑增加开发和维护成本,还不如用原生控件组合而成。还受限于项目开发的进度和项目成员的具体水平。通常的优化也只能是对复杂UI中的容器独立做一个控件,减少控件的层级嵌套。完全对具体的UI展示开发一个数据展示控件成本是非常高的,时间、稳定性都需时间来考验。 上面介绍了本人在实际项目开发者中遇到各种奇葩的界面时用到的优化方法。各种方法可能在实际使用中会交叉的派上用场。实际项目开发中遇到复杂到奇葩的UI,让工程师发费大量的时间来优化,还不如让设计师重新考虑设计的UI是否合理。用户真的能接受到如此之多的界面元素和繁杂的操作吗? 最后去下广告时间,介绍个人开源项目easyadapter
|
|