最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網 會員登陸 & 注冊

Android x 來襲,F(xiàn)ragment 可見性監(jiān)聽方案 - 完美兼容多種 case

2023-02-19 01:49 作者:程序員徐公  | 我要投稿

我的公眾號徐公,四年中大廠工作經驗,回復黑馬,領取 Android 學習視頻一份,回復徐公666,可以獲得我精心整理的簡歷模板,帶你走近大廠。

前言

本篇文章主要提供一種監(jiān)聽 Fragment 可見性監(jiān)聽的方案,完美多種 case,有興趣的可以看看。廢話不多說,開始進入正文。

在開發(fā)當中, fragment 經常使用到。在很多應用場景中,我們需要監(jiān)聽到 fragment 的顯示與隱藏,來進行一些操作。比如,統(tǒng)計頁面的停留時長,頁面隱藏的時候停止播放視頻。

有些同學可能會說了,這還不容易,直接監(jiān)聽 Fragment 的 onResume,onPause。我只能說,兄弟,too young,too simple。

下面,讓我們一起來實現(xiàn) fragment 的監(jiān)聽。主要分為幾種 case

  • 一個頁面只有一個 fragment 的,使用 replace

  • Hide 和 Show 操作

  • ViewPager 嵌套 Fragment

  • 宿主 Fragment 再嵌套 Fragment,比如 ViewPager 嵌套 ViewPager,再嵌套 Fragment

Replace 操作

replace 操作這種比較簡單,因為他會正常調用 onResume 和 onPause 方法,我們只需要在 onResume 和 onPause 做 check 操作即可

????override?fun?onResume()?{
????????info("onResume")
????????super.onResume()
????????onActivityVisibilityChanged(true)
????}


????override?fun?onPause()?{
????????info("onPause")
????????super.onPause()
????????onActivityVisibilityChanged(false)
????}

Hide 和 Show 操作

Hide 和 show 操作,會促發(fā)生命周期的回調,但是 hide 和 show 操作并不會,那么我們可以通過什么方法來監(jiān)聽呢?其實很簡單,可以通過 onHiddenChanged 方法

????/**
?????*?調用?fragment?show?hide?的時候回調用這個方法
?????*/

????override?fun?onHiddenChanged(hidden:?Boolean)?{
????????super.onHiddenChanged(hidden)
????????checkVisibility(hidden)
????}

ViewPager 嵌套 Fragment

ViewPager 嵌套 Fragment,這種也是很常見的一種結構。因為 ViewPager 的預加載機制,在 onResume 監(jiān)聽是不準確的。

這時候,我們可以通過 setUserVisibleHint 方法來監(jiān)聽,當方法傳入值為true的時候,說明Fragment可見,為false的時候說明Fragment被切走了

public?void?setUserVisibleHint(boolean?isVisibleToUser)

有一點需要注意的是,個方法可能先于Fragment的生命周期被調用(在FragmentPagerAdapter中,在Fragment被add之前這個方法就被調用了),所以在這個方法中進行操作之前,可能需要先判斷一下生命周期是否執(zhí)行了。

????/**
?????*?Tab切換時會回調此方法。對于沒有Tab的頁面,[Fragment.getUserVisibleHint]默認為true。
?????*/

????@Suppress("DEPRECATION")
????override?fun?setUserVisibleHint(isVisibleToUser:?Boolean)?{
????????info("setUserVisibleHint?=?$isVisibleToUser")
????????super.setUserVisibleHint(isVisibleToUser)
????????checkVisibility(isVisibleToUser)
????}

????/**
?????*?檢查可見性是否變化
?????*
?????*?@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)?{
????????????this.visible?=?visible
????????????onVisibilityChanged(this.visible)
????????}
????}

AndroidX 的適配(也是一個坑)

在 AndroidX 當中,F(xiàn)ragmentAdapter 和 FragmentStatePagerAdapter 的構造方法,添加一個 behavior 參數(shù)實現(xiàn)的。

如果我們指定不同的 behavior,會有不同的表現(xiàn)

  1. 當 behavior 為 BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT 時,ViewPager 中切換 Fragment,setUserVisibleHint 方法將不再被調用,他會確保 onResume 的正確調用時機

  2. 當 behavior 為 BEHAVIOR_SET_USER_VISIBLE_HINT,跟之前的方式是一致的,我們可以通過 setUserVisibleHint 結合 fragment 的生命周期來監(jiān)聽

//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 是如何實現(xiàn)的呢?

已 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的狀態(tài),使得只有正在顯示的 Fragmen t執(zhí)行到 onResume() 方法,其他 Fragment 只會執(zhí)行到 onStart() 方法,并且當 Fragment 切換到不顯示狀態(tài)時觸發(fā) onPause() 方法。

  • 當 mBehavior 設置為 BEHAVIOR_SET_USER_VISIBLE_HINT 時,會當 frament 可見性發(fā)生變化時調用 setUserVisibleHint() ,也就是跟我們上面提到的第一種懶加載實現(xiàn)方式一樣。

更多詳情,可以參考這一篇博客Android Fragment + ViewPager的懶加載實現(xiàn)

宿主 Fragment 再嵌套 Fragment

這種 case 也是比較常見的,比如 ViewPager 嵌套 ViewPager,再嵌套 Fragment。

宿主Fragment在生命周期執(zhí)行的時候會相應的分發(fā)到子Fragment中,但是setUserVisibleHint和onHiddenChanged卻沒有進行相應的回調。試想一下,一個ViewPager中有一個FragmentA的tab,而FragmentA中有一個子FragmentB,F(xiàn)ragmentA被滑走了,F(xiàn)ragmentB并不能接收到setUserVisibleHint事件,onHiddenChange事件也是一樣的。

那有沒有辦法監(jiān)聽到宿主的 setUserVisibleHint 和 ,onHiddenChange 事件呢?

方法肯定是有的。

  1. 第一種方法,宿主 Fragment 提供可見性的回調,子 Fragment 監(jiān)聽改回調,有點類似于觀察者模式。難點在于子 Fragment 要怎么拿到宿主 Fragment

  2. 第二種 case,宿主 Fragment 可見性變化的時候,主動去遍歷所有的 子 Fragment,調用 子 Fragment 的相應方法

第一種方法

總體思路是這樣的,宿主 Fragment 提供可見性的回調,子 Fragment 監(jiān)聽改回調,有點類似于觀察者模式。也有點類似于 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,進行監(jiān)聽。這樣,當宿主 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)
????}

第二種方法

第二種方法,它的實現(xiàn)思路是這樣的,宿主 Fragment 生命周期發(fā)生變化的時候,遍歷子 Fragment,調用相應的方法,通知生命周期發(fā)生變化

//當自己的顯示隱藏狀態(tài)改變時,調用這個方法通知子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);
????}
}

具體的實現(xiàn)方案,可以看這一篇博客。獲取和監(jiān)聽Fragment的可見性

完整代碼

/**
?*?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)?{
????????????this.visible?=?visible
????????????onVisibilityChanged(this.visible)
????????}
????}

????/**
?????*?可見性改變
?????*/

????protected?fun?onVisibilityChanged(visible:?Boolean)?{
????????info("==>?onVisibilityChanged?=?$visible")
????????listeners.forEach?{
????????????it.onFragmentVisibilityChanged(visible)
????????}
????}

????/**
?????*?是否可見(Activity處于前臺、Tab被選中、Fragment被添加、Fragment沒有隱藏、Fragment.View已經Attach)
?????*/

????fun?isFragmentVisible():?Boolean?{
????????return?visible
????}

????private?fun?info(s:?String)?{
????????Log.i(TAG,?"${this.javaClass.simpleName}?;?$s?;?this?is?$this")
????}


}

題外話

今年有好長時間沒有更新技術博客了,主要是比較忙。拖著拖著,就懶得更新了。

這邊博客的技術含量其實不高,主要是適配。

  1. AndroidX FragmentAdapter behavior 的適配

  2. 宿主 Fragment 嵌套 Fragment,提供了兩種方式解決,一種是自上而下的,一種是自上而下的。借鑒了 Rxjava 的設計思想,下游持有上游的引用,從而控制 Obverable 的回調線程。Obsever 會有下游 Observer 的引用,從而進行一些轉換操作,比如 map,F(xiàn)latMap 操作符

  3. 如果你使用中遇到坑,也歡迎隨時 call 我,我們一起解決。如果你有更好的方案,也歡迎隨時跟我交流



Android x 來襲,F(xiàn)ragment 可見性監(jiān)聽方案 - 完美兼容多種 case的評論 (共 條)

分享到微博請遵守國家法律
都匀市| 榆中县| 寿光市| 双峰县| 宁国市| 临猗县| 阜阳市| 桃江县| 黄陵县| 犍为县| 永吉县| 平和县| 灌阳县| 淅川县| 和平区| 龙江县| 晋城| 桐乡市| 红安县| 稻城县| 上犹县| 分宜县| 五河县| 德安县| 云浮市| 黔西县| 衡东县| 淮北市| 津南区| 江油市| 开鲁县| 明光市| 松溪县| 兴安盟| 建瓯市| 营山县| 阿合奇县| 黄梅县| 台东县| 沙坪坝区| 铜梁县|