写点什么

Fragment 可见性监听方案,多种 case 完美兼容,我凭什么拿到了阿里、腾讯、今日头条 3 家大厂 offer

作者:嘟嘟侠客
  • 2021 年 11 月 27 日
  • 本文字数:5487 字

    阅读完需:约 18 分钟

AndroidX 的适配(也是一个坑)


在 AndroidX 当中,FragmentAdapter?和 FragmentStatePagerAdapter?的构造方法,添加一个 behavior?参数实现的。


如果我们指定不同的?behavior,会有不同的表现。


  1. 当?behavior?为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 时,ViewPager?中切换?Fragment,setUserVisibleHint?方法将不再被调用,他会确保?onResume?的正确调用时机。

  2. 当?behavior?为 BEHAVIOR_SET_USER_VISIBLE_HINT,跟之前的方式是一致的,我们可以通过?setUserVisibleHint?结合?fragment?的生命周期来监听。


//FragmentStatePagerAdapter 构造方法


public?FragmentStatePagerAdapter(@NonNull?FragmentManager?fm,


@Behavior?int?behavior)?{


mFragmentManager?=?fm;


mBehavior?=?behavior;


}


//FragmentPagerAdapter 构造方法


public?FragmentPagerAdapter(@NonNull?FragmentManager?fm,


@Behavior?int?behavior)?{


mFragmentManager?=?fm;


mBehavior?=?behavior;


}


@IntDef({BEHAVIOR_SET_USER_VISIBLE_HINT,?BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT})


private?@interface?Behavior?{?}


既然是这样,我们就很好适配呢,直接在?onResume?中调用 checkVisibility?方法,判断当前?Fragment?是否可见。


回过头,Behavior?是如何实现的呢?


以?FragmentStatePagerAdapter?为例,我们一起来看看源码。


@SuppressWarnings({"ReferenceEquality",?"deprecation"})


@Override


public?void?setPrimaryItem(@NonNull?ViewGroup?container,?int?position,?@NonNull?Object?object)?{


Fragment?fragment?=?(Fragment)object;


if?(fragment?!=?mCurrentPrimaryItem)?{


if?(mCurrentPrimaryItem?!=?null)?{


//当前显示 Fragment


mCurrentPrimaryItem.setMenuVisibility(false);


if?(mBehavior?==?BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)?{


if?(mCurTransaction?==?null)?{


mCurTransaction?=?mFragmentManager.beginTransaction();


}


//最大生命周期设置为 STARTED,生命周期回退到 onPause


mCurTransaction.setMaxLifecycle(mCurrentPrimaryItem,?Lifecycle.State.STARTED);


}?else?{


//可见性设置为 false


mCurrentPrimaryItem.setUserVisibleHint(false);


}


}


//将要显示的 Fragment


fragment.setMenuVisibility(true);


if?(mBehavior?==?BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)?{


if?(mCurTransaction?==?null)?{


mCurTransaction?=?mFragmentManager.beginTransaction();


}


//最大?生命周期设置为 RESUMED


mCurTransaction.setMaxLifecycle(fragment,?Lifecycle.State.RESUMED);


}?else?{


//可见性设置为 true


fragment.se?tUserVisibleHint(true);


}


//赋值


mCurrentPrimaryItem?=?fragment;


}


}


代码比较简单很好理解。


  • 当?mBehavior?设置为 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 会通过?setMaxLifecycle?来修改当前 Fragment 和将要显示的 Fragment 的状态,使得只有正在显示的?Fragment 执行到 onResume()?方法,其他?Fragment?只会执行到 onStart()?方法,并且当?Fragment?切换到不显示状态时触发?onPause()方法。

  • 当?mBehavior?设置为 BEHAVIOR_SET_USER_VISIBLE_HINT?时,会当 frament?可见性发生变化时调用?setUserVisibleHint()?,也就是跟我们上面提到的第一种懒加载实现方式一样。


宿主 Fragment 再嵌套 Fragment


====================================================================================


这种 case 也是比较常见的,比如?ViewPager?嵌套 ViewPager,再嵌套?Fragment。


宿主 Fragment 在生命周期执行的时候会相应的分发到子 Fragment 中,但是 setUserVisibleHint 和 onHiddenChanged 却没有进行相应的回调。试想一下,一个 ViewPager 中有一个 FragmentA 的 tab,而 FragmentA 中有一个子 FragmentB,FragmentA 被滑走了,FragmentB 并不能接收到 setUserVisibleHint 事件,onHiddenChange 事件也是一样的。


那有没有办法监听到宿主的?setUserVisibleHint?和 ,onHiddenChange?事件呢?


方法肯定是有的。


  1. 第一种方法,宿主?Fragment?提供可见性的回调,子 Fragment?监听改回调,有点类似于观察者模式。难点在于子 Fragment?要怎么拿到宿主?Fragment。

  2. 第二种 case,宿主?Fragment?可见性变化的时候,主动去遍历所有的 子 Fragment,调用 子?Fragment?的相应方法。


第一种方法


总体思路是这样的,宿主?Fragment?提供可见性的回调,子 Fragment?监听改回调,有点类似于观察者模式。也有点类似于 Rxjava 中下游持有。


第一步,我们先定义一个接口。


interface?OnFragmentVisibilityChangedListener?{


fun?onFragmentVisibilityChanged(visible:?Boolean)


}


第二步,在?BaseVisibilityFragment?中提供 addOnVisibilityChangedListener 和 removeOnVisibilityChangedListener?方法,这里需要注意的是,我们需要用一个 ArrayList?来保存所有的?listener,因为一个宿主?Fragment?可能有多个子?Fragment。


当?Fragment?可见性变化的时候,会遍历?List?调用 OnFragmentVisibilityChangedListener?的 onFragmentVisibilityChanged?方法 **。


open?class?BaseVisibilityFragment?:?Fragment(),?View.OnAttachStateChangeListener,


OnFragmentVisibilityChangedListener?{


private?val?listeners?=?ArrayList<OnFragmentVisibilityChangedListener>()


fun?addOnVisibilityChangedListener(listener:?OnFragmentVisibilityChangedListener?)?{


listener?.apply?{


listeners.add(this)


}


}


fun?removeOnVisibilityChangedListener(listener:?OnFragmentVisibilityChangedListener?)?{


listener?.apply?{


listeners.remove(this)


}


}


private?fun?checkVisibility(expected:?Boolean)?{


if?(expected?==?visible)?return


val?parentVisible?=


if?(localParentFragment?==?null)?parentActivityVisible


else?localParentFragment?.isFragmentVisible()??:?false


val?superVisible?=?super.isVisible()


val?hintVisible?=?userVisibleHint


val?visible?=?parentVisible?&&?superVisible?&&?hintVisible


if?(visible?!=?this.visible)?{


this.visible?=?visible


listeners.forEach?{?it?->


it.onFragmentVisibilityChanged(visible)


}


onVisibilityChanged(this.visible)


}


}


第三步,在?Fragment attach?的时候,我们通过 getParentFragment?方法,拿到宿主?Fragment,进行监听。这样,当宿主?Fragment?可见性变化的时候,子?Fragment 能感应到。


override?fun?onAttach(context:?Context)?{


super.onAttach(context)


val?parentFragment?=?parentFragment


if?(parentFragment?!=?null?&&?parentFragment?is?BaseVisibilityFragment)?{


this.localParentFragment?=?parentFragment


info("onAttach,?localParentFragment?is?$localParentFragment")


localParentFragment?.addOnVisibilityChangedListener(this)


}


checkVisibility(true)


}


第二种方法


第二种方法,它的实现思路是这样的,宿主?Fragment?生命周期发生变化的时候,遍历子?Fragment,调用相应的方法,通知生命周期发生变化。


//当自己的显示隐藏状态改变时,调用这个方法通知子 Fragment


private?void?notifyChildHiddenChange(boolean?hidden)?{


if?(isDetached()?||?!isAdded())?{


return;


}


FragmentManager?fragmentManager?=?getChildFragmentManager();


List<Fragment>?fragments?=?fragmentManager.getFragments();


if?(fragments?==?null?||?fragments.isEmpty())?{


return;


}


for?(Fragment?fragment?:?fragments)?{


if?(!(fragment?instanceof?IPareVisibilityObserver))?{


continue;


}


((IPareVisibilityObserver)?fragment).onParentFragmentHiddenChanged(hidden);


}


}


完整代码


================================================================


/**


*?Created?by?jun?xu?on?2020/11/26.


*/


interface?OnFragmentVisibilityChangedListener?{


fun?onFragmentVisibilityChanged(visible:?Boolean)


}


/**


*?Created?by?jun?xu?on?2020/11/26.



    *?支持以下四种?case


    *?1.?支持?viewPager?嵌套?fragment,主要是通过?setUserVisibleHint?兼容,


    *??FragmentStatePagerAdapter?BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT?的?case,因为这时候不会调用?setUserVisibleHint?方法,在?onResume?check?可以兼容


    *?2.?直接?fragment?直接?add,?hide?主要是通过?onHiddenChanged


    *?3.?直接?fragment?直接?replace?,主要是在?onResume?做判断


    *?4.?Fragment?里面用?ViewPager,?ViewPager?里面有多个?Fragment?的,通过?setOnVisibilityChangedListener?兼容,前提是一级?Fragment?和?二级?Fragment?都必须继承??BaseVisibilityFragment,?且必须用?FragmentPagerAdapter?或者?FragmentStatePagerAdapter


    *?项目当中一级?ViewPager?adapter?比较特殊,不是?FragmentPagerAdapter,也不是?FragmentStatePagerAdapter,导致这种方式用不了


    */


    open?class?BaseVisibilityFragment?:?Fragment(),?View.OnAttachStateChangeListener,


    OnFragmentVisibilityChangedListener?{


    companion?object?{


    const?val?TAG?=?"BaseVisibilityFragment"


    }


    /**


    *?ParentActivity 是否可见


    */


    private?var?parentActivityVisible?=?false


    /**


    *?是否可见(Activity 处于前台、Tab 被选中、Fragment 被添加、Fragment 没有隐藏、Fragment.View 已经 Attach)


    */


    private?var?visible?=?false


    private?var?localParentFragment:?BaseVisibilityFragment??=


    null


    private?val?listeners?=?ArrayList<OnFragmentVisibilityChangedListener>()


    fun?addOnVisibilityChangedListener(listener:?OnFragmentVisibilityChangedListener?)?{


    listener?.apply?{


    listeners.add(this)


    }


    }


    fun?removeOnVisibilityChangedListener(listener:?OnFragmentVisibilityChangedListener?)?{


    listener?.apply?{


    listeners.remove(this)


    }


    }


    override?fun?onAttach(context:?Context)?{


    info("onAttach")


    super.onAttach(context)


    val?parentFragment?=?parentFragment


    if?(parentFragment?!=?null?&&?parentFragment?is?BaseVisibilityFragment)?{


    this.localParentFragment?=?parentFragment


    localParentFragment?.addOnVisibilityChangedListener(this)


    }


    checkVisibility(true)


    }


    override?fun?onDetach()?{


    info("onDetach")


    localParentFragment?.removeOnVisibilityChangedListener(this)


    super.onDetach()


    checkVisibility(false)


    localParentFragment?=?null


    }


    override?fun?onResume()?{


    info("onResume")


    super.onResume()


    onActivityVisibilityChanged(true)


    }


    override?fun?onPause()?{


    info("onPause")


    super.onPause()


    onActivityVisibilityChanged(false)


    }


    /**


    *?ParentActivity 可见性改变


    */


    protected?fun?onActivityVisibilityChanged(visible:?Boolean)?{


    parentActivityVisible?=?visible


    checkVisibility(visible)


    }


    /**


    *?ParentFragment 可见性改变


    */


    override?fun?onFragmentVisibilityChanged(visible:?Boolean)?{


    checkVisibility(visible)


    }


    override?fun?onCreate(savedInstanceState:?Bundle?)?{


    info("onCreate")


    super.onCreate(savedInstanceState)


    }


    override?fun?onViewCreated(


    view:?View,


    savedInstanceState:?Bundle?


    )?{


    super.onViewCreated(view,?savedInstanceState)


    //?处理直接?replace?的?case


    view.addOnAttachStateChangeListener(this)


    }


    /**


    *?调用?fragment?add?hide?的时候回调用这个方法


    */


    override?fun?onHiddenChanged(hidden:?Boolean)?{


    super.onHiddenChanged(hidden)


    checkVisibility(hidden)


    }


    /**


    • Tab 切换时会回调此方法。对于没有 Tab 的页面,[Fragment.getUserVisibleHint]默认为 true。


    */


    override?fun?setUserVisibleHint(isVisibleToUser:?Boolean)?{


    info("setUserVisibleHint?=?$isVisibleToUser")


    super.setUserVisibleHint(isVisibleToUser)


    checkVisibility(isVisibleToUser)


    }


    override?fun?onViewAttachedToWindow(v:?View?)?{


    info("onViewAttachedToWindow")


    checkVisibility(true)


    }


    override?fun?onViewDetachedFromWindow(v:?View)?{


    info("onViewDetachedFromWindow")


    v.removeOnAttachStateChangeListener(this)


    checkVisibility(false)


    }


    /**


    *?检查可见性是否变化



      *?@param?expected 可见性期望的值。只有当前值和 expected 不同,才需要做判断


      */


      private?fun?checkVisibility(expected:?Boolean)?{


      if?(expected?==?visible)?return


      val?parentVisible?=


      if?(localParentFragment?==?null)?parentActivityVisible


      else?localParentFragment?.isFragmentVisible()??:?false


      val?superVisible?=?super.isVisible()


      val?hintVisible?=?userVisibleHint


      val?visible?=?parentVisible?&&?superVisible?&&?hintVisible


      info(


      String.format(


      "==>?checkVisibility?=?%s??(?parent?=?%s,?super?=?%s,?hint?=?%s?)",


      visible,?parentVisible,?superVisible,?hintVisible


      )


      )


      if?(visible?!=?this.visible)?{

      总结

      算法知识点繁多,企业考察的题目千变万化,面对越来越近的“金九银十”,我给大家准备好了一套比较完善的学习方法,希望能帮助大家在有限的时间里尽可能系统快速的恶补算法,通过高效的学习来提高大家面试中算法模块的通过率。


      这一套学习资料既有文字档也有视频,里面不仅仅有关键知识点的整理,还有案例的算法相关部分的讲


      《Android 学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》

      【docs.qq.com/doc/DSkNLaERkbnFoS0ZF】 完整内容开源分享


      解,可以帮助大家更好更全面的进行学习,二者搭配起来学习效果会更好。


      部分资料展示:






      有了这套学习资料,坚持刷题一周,你就会发现自己的算法知识体系有明显的完善,离大厂 Offer 的距离更加近。


      本文已被CODING开源项目:《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》收录

      用户头像

      嘟嘟侠客

      关注

      还未添加个人签名 2021.03.19 加入

      还未添加个人简介

      评论

      发布
      暂无评论
      Fragment可见性监听方案,多种case完美兼容,我凭什么拿到了阿里、腾讯、今日头条3家大厂offer