Android的ExpandableListView的动画展开效果和使用traceview的性能优化

解决方案Github pull request链接:https://github.com/idunnololz/AnimatedExpandableListView/pull/30


Android的原生提供和展开分组的ListView:ExpandableListView,然而相比于iOS上原生提供的UITableView,其UI能力不足,比如没有原生的动画展开和收起效果支持。


在开源代码社区我们可以找到几个为Android的ExpandableListView添加的动画解决方案。其中idunnololz的AnimatedExpandableListView是不错的方案之一。
(https://github.com/idunnololz/AnimatedExpandableListView)。它的优点:性能较好,提供源代码而不是library(这点很重要),注释清晰。


然而性能的优化是没有止境的,当分组内的子view(childView)变得复杂,或者ListView的parent结构复杂,例如内嵌于其它LinearLayout, FrameLayout或者ScrollView之中,并且parent使用重写的onMeasure()方法时,生成childView的效率就有可能大大影响应用的性能。


合理使用AnimatedExpandableListView的关键是在于AnimatedExpandableListView#getRealChildView()的实现,这是应用开发的责任。实际项目中,通过优化getRealChildView(),动画效果的启动时间从1340ms减少到了680ms (展开一个含有5个子项目的分组),优化比例超过50%。而发现的问题的定位和解决方案,基本是用过使用Android提供的method tracing方法(android.os.Debug.startMethodTraceing)进行分析。


优化前的getRealChildView()实现,需要大量的view初始化,因为没有可用的convertView,而事实上,在动画绘制阶段时生成的childView完全可以被重用,即使convertView并没有被Android Framework给出。如下面的traceview profile看到的,优化前,getChildView()消耗了超过1秒的时间。



优化后的性能:getChildView耗时从1340ms下降到678ms。



这是如何做到的呢?这需要我们再研究一下动画展开的原理,也就是分析getChildView()里面耗时最长的那些动作。首先排除其他因素的影响,专注于AnimatedExpandableList本身的逻辑,我们使用GitHub上原生提供的Example来做分析:这是展开5个子项目的分组的情况,注意5个子分组的view生成,LayoutInflater.inflate被执行了10次,是其两倍。而inflate是相当耗时的。有没有方法来减少这部分工作消耗呢?



方法是使用Android推荐的LRU cache来保存childView的。关于LruCache,请见Android的reference documents和training。这里特别要注意的是,childView在dataSet改变时需要重新生成,而不是在cache中获得,这里使用的方法是判断childView的type。在自己的项目中需要根据情况认真考虑dataSet改变如何更新cache的问题。效果如下所示:inflate的次数减少到5次,一次都不浪费。消耗时间从160ms降低到80ms。




代码还是最直观说明问题的:

public class MainActivity extends Activity {
    ...
	
    private class ExampleAdapter extends AnimatedExpandableListAdapter {
        private LruCache<Long, View> vcache;
        
        ...

        @Override
        public View getRealChildView(int groupPosition, int childPosition, boolean isLastChild, View convertView, ViewGroup parent) {
            ChildHolder holder;
            ChildItem item = getChild(groupPosition, childPosition);
            if (convertView == null) {
                // when convertView is not given, we try using view from cache.
                // Note: check the view type to ensure it's really resuable.
                Long key = ((long)groupPosition << 32) + childPosition;
                View view = vcache.get(key);
                if (view != null && view.getTag() != null) {
                    holder = (ChildHolder) view.getTag();
                    if (holder.type == getRealChildType(groupPosition, childPosition) ) {
                        convertView = view;
                        Log.d(TAG, "getRealChildView: " + groupPosition + " " + childPosition + " " + "Got Cache!");
                    }
                } else {
                    holder = new ChildHolder();
                    convertView = inflater.inflate(R.layout.list_item, parent, false);
                    holder.title = (TextView) convertView.findViewById(R.id.textTitle);
                    holder.hint = (TextView) convertView.findViewById(R.id.textHint);
                    convertView.setTag(holder);
                    vcache.put(key, convertView);
                    Log.d(TAG, "getRealChildView: " + groupPosition + " " + childPosition + " " + "No Cache!");
                }
            } else {
                holder = (ChildHolder) convertView.getTag();
            }

            holder.title.setText(item.title);
            holder.hint.setText(item.hint);

            return convertView;
        }

      ...
    
}


评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值