Viewpager 中子view 持有焦点诱发的卡顿问题

转载请标明出处:http://blog.csdn.net/EdisonChang/article/details/49816319
今天,博客终于开张了,在写第一篇博客之前,还是有些不相干的废话要说的。
不知不觉从事android开发也有少许年头,期间遇到了各种困惑,各种疑难杂症,自己的不断摸索和成长也和周边的高手和网络上的各路大神的帮助息息相关,在此至上十分的敬意!当然我也愈发觉得自己应该把平时遇到的一些疑难问题,一些解决思路拿出来和大家分享,共同学习,一方面是工作占据了大部分时间,但主要还是自己的惰性导致,今天终于迈出第一步,发扬下奉献精神吧。
希望我的博客可以给一些爱好android的朋友提供一些思路,帮到大家,当然有任何问题也请大家随时指正,多谢。
言归正传,开始今天的主题吧,今天同大家分享的是:如何解决在viewpager中切换fragment时存在的卡顿性能问题。
先来项目背景吧,我们的应用和各类安卓市场比较类似,采用的是ViewPager 结合Fragment实现的多个可滑动的标签页,每个标签页有不同的布局,但基本以列表ListView为主干。特殊的地方在于,其中一些fragment又有viewpager, 简单而言就是viewpager + viewpager + fragment结构。(eg:demo multitabfragment)。
项目初期一切都比较顺利,但过了一段时间,有qa 反映一级viewpager 页面的切换存在卡顿问题,感觉页面切换不流畅。当然感官是最不准确的,不同手机性能问题也大不一样,很难鉴定。所以遇到问题后,我们首先要做的是把这个问题量化,数据化,最好有一些指标能说明这些问题,因为第一需要验证问题是否存在,第二需要判定修改后的效果。
开门见山,说下问题和解决方案吧:
在几处非常重要的地方,加上日志 ,RelativeLayoutCustom 是MainActivity View的根节点,ListViewCustom 是fragment的列表。

    public class RelativeLayoutCustom extends RelativeLayout { 

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        Log.d("RelativeLayoutCustom", "onMeasure");
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);

        Log.d("RelativeLayoutCustom", "onLayout changed = " + changed);
    }

    @Override
    public void requestLayout() {
        super.requestLayout();

        Log.d("RelativeLayoutCustom", "requestLayout");
    }
}
``

切换fragment 时,从系统的log日志(图log)可以看出每次都触发根节点的 onMeasure onLayout 方法 ,说明每次切换页面都重新布局,但代码并没有地方主动调用 requestLayout 。由于我们的布局元素比较多,这么一来每次切换页面都得重新计算,布局,绘制一次,相关的ui操作都在主线程,卡顿现象也就不足为奇了。

log

ok, 看来问题确实存在,那么为什么每次切换tab会导致页面重新布局呢?为了更快的暴露问题,我把viewpager、listview都继承重写,终于发现在每次切换页面,listview 的 onFocusChanged 会被调用多次,顺藤摸瓜,在viewpager 源码中populate方法中有这么一段代码,

      if (hasFocus()) {
            View currentFocused = findFocus();

            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
            if (ii == null || ii.position != mCurItem) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    ii = infoForChild(child);
                    if (ii != null && ii.position == mCurItem) {
                        if (child.requestFocus(focusDirection)) {
                            break;
                        }
                    }
                }
            }

        }

viewpager 在切换页面的时候会检查是否有子view 持有焦点,如果持有焦点的view 不属于当前item,就会调用 child.requestFocus(focusDirection) ,带来的结果就是子view的 requestLayout,所以在日志中会发现会有多次的onMeasure 和onLayout。既然问题存在,如何解决呢?其实从populate的寻找焦点的方法中,我们可以看出viewpager调用了子view的requestFocus 的方法,而这个子view 其实就是我们fragment onCreateView方法中inflate 的view。

   public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle bundle) {
        if (mListNormal == null) {
            mListNormal = (ListView) inflater.inflate(R.layout.common_list_view, null, false);
        }
        return NoSaveStateFrameLayout.wrap(mListNormal);
    }

接下去就是重写NoSaveStateFrameLayout requestFocus 方法,修改后切换页面时果然没有相关Layout日志输出,感官上确实也流畅了许多。

  public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
        return true;
    }

到这里基本已经介绍完了,其实这个问题并不复杂,难的地方就是如何量化证明问题的存在,有些时候甚至只能用排除法不断的去精简代码发现问题,当然解决问题的手段有很多,希望大家多多交流。对于文章内容有兴趣的话,可以下载demo看看,demo只是个基本架子,没有实现所有的内容,源码下载,请点击这里

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值