Android jetpack關(guān)于ButtomNavigationView切換fragment重繪問題

fragment的切換問題一直都比較麻煩绽诚,好在官方最新的jetpack提供了簡(jiǎn)單的解決方案监署,但也是不能拿來直接用,在此記錄下一些解決方法寄啼。

1.創(chuàng)建Android studio默認(rèn)ButtomNavigationView

MainActivity的布局很簡(jiǎn)單逮光,一個(gè)fragment一個(gè)BottomNavigationView,注意到ConstraintLayout有個(gè)paddingTop="?attr/actionBarSize"這就比較奇怪墩划,貌似如果父布局有toolbar時(shí)才有用涕刚。fragment的類是NavHostFragment,這就是jetpack的組件了走诞。注意app:menu指定了NavigationView的menu副女,fragment的app:navGraph指定了一個(gè)navigation文件。

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingTop="?attr/actionBarSize">

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/nav_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="0dp"
        android:layout_marginEnd="0dp"
        android:background="?android:attr/windowBackground"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:menu="@menu/bottom_nav_menu" />

    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@id/nav_view"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:navGraph="@navigation/mobile_navigation" />

</androidx.constraintlayout.widget.ConstraintLayout>

我們打開res/navigation/mobile_navigation蚣旱,有三個(gè)fragment標(biāo)簽碑幅,其父標(biāo)簽navigation通過app:startDestination指定了起始fragment。

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/navigation_home">

    <fragment
        android:id="@+id/navigation_home"
        android:name="com.latlaj.buttomnavigationtest.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/navigation_dashboard"
        android:name="com.latlaj.buttomnavigationtest.ui.dashboard.DashboardFragment"
        android:label="@string/title_dashboard"
        tools:layout="@layout/fragment_dashboard" />

    <fragment
        android:id="@+id/navigation_notifications"
        android:name="com.latlaj.buttomnavigationtest.ui.notifications.NotificationsFragment"
        android:label="@string/title_notifications"
        tools:layout="@layout/fragment_notifications" />
</navigation>

打開src/menu/bottom_nav_menu塞绿。

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/navigation_home"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home" />

    <item
        android:id="@+id/navigation_dashboard"
        android:icon="@drawable/ic_dashboard_black_24dp"
        android:title="@string/title_dashboard" />

    <item
        android:id="@+id/navigation_notifications"
        android:icon="@drawable/ic_notifications_black_24dp"
        android:title="@string/title_notifications" />

</menu>

每個(gè)item有和navigation中fragment同樣的id沟涨,通過下面MainActivity中onCreate用NavigationUI的方法setupWithNavController的綁定實(shí)現(xiàn)跳轉(zhuǎn)。

BottomNavigationView navView = findViewById(R.id.nav_view);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(navView, navController);

2.用ViewModel保存View數(shù)據(jù)

整個(gè)fragment代碼非常簡(jiǎn)潔异吻,簡(jiǎn)直是新手的福音裹赴,但我后來發(fā)現(xiàn)一個(gè)崩潰的事,每次切換fragment诀浪,之前的fragment已經(jīng)完全Destroy了棋返,后退時(shí)完全重走了一遍Fragment的onAttach()->onCreate()->onCreateView()...的過程,運(yùn)存倒是省了雷猪,可是需要聯(lián)網(wǎng)顯示的或滾動(dòng)界面的fragment無法恢復(fù)狀態(tài)是不可接受的睛竣。我當(dāng)然想到了項(xiàng)目默認(rèn)創(chuàng)建的ViewModel,可是依賴fragment的ViewModel在fragment destroy之后生命周期也結(jié)束了求摇。
Bottom Navigation Activity項(xiàng)目每個(gè)fragment都有ViewModel.png

最后用Activity的ViewModel解決問題射沟。新建MainViewModel.java:

package com.latlaj.buttomnavigationtest;
import android.view.View;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;
public class MainViewModel extends ViewModel {
    private MutableLiveData<View> mDashboardView;
    private MutableLiveData<View> mHomeView;
    private MutableLiveData<View> mNotificationsView;
    public MainViewModel() {
        mDashboardView = new MutableLiveData<>();
        mHomeView = new MutableLiveData<>();
        mNotificationsView = new MutableLiveData<>();
    }
    public void setDashboardView(View v) {
        mDashboardView.setValue(v);
    }
    public void setHomeView(View v) {
        mHomeView.setValue(v);
    }
    public void setNotificationsView(View v) {
        mNotificationsView.setValue(v);
    }
    public LiveData<View> getDashboardView() {
        return mDashboardView;
    }
    public LiveData<View> getHomeView() {
        return mHomeView;
    }
    public LiveData<View> getNotificationsView() {
        return mNotificationsView;
    }
}

使用方法,其實(shí)拿到的是同一個(gè)mainViewModel實(shí)例:

//在MainActivity中使用
MainViewModel mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
//在HomeFragment中使用,在onViewCreated()中或之后調(diào)用
MainViewModel mainViewModel = new ViewModelProvider(getActivity()).get(MainViewModel.class);

以HomeFragment為例与境,我們不使用ViewModel的觀察者方法验夯,直接在onDestroyView調(diào)用時(shí)保存View。

@Override
public void onDestroyView() {
    super.onDestroyView();
    mainViewModel.setView(root);
}
private View root;
private MainViewModel mainViewModel;
@Override
public View onCreateView(@NonNull LayoutInflater inflater,
                         ViewGroup container, Bundle savedInstanceState) {
    mainViewModel=new ViewModelProvider(getActivity())
            .get(MainViewModel.class);
    root=mainViewModel.getHomeView().getValue();
    if(root==null){
        //正常初始化View
        root = inflater.inflate(R.layout.fragment_home, container, false);
        //...
    }
    //...
    return root;
}

然而這有一個(gè)問題摔刁,root在這種情況下可能會(huì)指定多個(gè)parent導(dǎo)致報(bào)錯(cuò)挥转,加入去除parent的代碼:

@Override
public void onDestroyView() {
    super.onDestroyView();
    //其實(shí)以下代碼不一定寫在onDestroyView(),可以獲取指定后root的地方都可以
    ViewGroup parent = (ViewGroup) root.getParent();
    if(parent!=null){
        parent.removeView(root);
    }
    mainViewModel.setView(root);
}

3.取消煩人的fragment重建

到這里為止還有一個(gè)非常不爽的地方,就是我們單擊已經(jīng)選中的BottomNavigationView的底部Tag绑谣,居然會(huì)再次新建當(dāng)前fragment准潭,同時(shí)把舊fragment的視圖設(shè)為不可見后銷毀,這會(huì)導(dǎo)致我們重用視圖的fragment閃爍域仇、空白,于是我們要取消當(dāng)前Tag的單擊動(dòng)作寺擂,觀察NavigationUI的setupWithNavController方法源碼:
NavigationUI.setupWithNavController.png

發(fā)現(xiàn)只給navView設(shè)置了一個(gè)監(jiān)聽暇务,于是我們可以重新設(shè)置監(jiān)聽:

BottomNavigationView navView = findViewById(R.id.nav_view);
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupWithNavController(navView, navController);
navView.setOnNavigationItemSelectedListener(
        new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (item.isChecked()){
                    //...其他動(dòng)作
                    return true;//單擊事件被消耗
                }
                return onNavDestinationSelected(item, navController);
            }
        });

這樣的結(jié)果是單擊當(dāng)前Tag不再有動(dòng)作。
希望大家能提供更好的辦法怔软。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末垦细,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子挡逼,更是在濱河造成了極大的恐慌括改,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,470評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件家坎,死亡現(xiàn)場(chǎng)離奇詭異嘱能,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)虱疏,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門惹骂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人做瞪,你說我怎么就攤上這事对粪。” “怎么了装蓬?”我有些...
    開封第一講書人閱讀 162,577評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵著拭,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我牍帚,道長(zhǎng)儡遮,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,176評(píng)論 1 292
  • 正文 為了忘掉前任履羞,我火速辦了婚禮峦萎,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘忆首。我一直安慰自己爱榔,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,189評(píng)論 6 388
  • 文/花漫 我一把揭開白布糙及。 她就那樣靜靜地躺著详幽,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上唇聘,一...
    開封第一講書人閱讀 51,155評(píng)論 1 299
  • 那天版姑,我揣著相機(jī)與錄音,去河邊找鬼迟郎。 笑死剥险,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的宪肖。 我是一名探鬼主播表制,決...
    沈念sama閱讀 40,041評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼控乾!你這毒婦竟也來了么介?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,903評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤蜕衡,失蹤者是張志新(化名)和其女友劉穎壤短,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體慨仿,經(jīng)...
    沈念sama閱讀 45,319評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡久脯,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,539評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了镶骗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桶现。...
    茶點(diǎn)故事閱讀 39,703評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖鼎姊,靈堂內(nèi)的尸體忽然破棺而出骡和,到底是詐尸還是另有隱情,我是刑警寧澤相寇,帶...
    沈念sama閱讀 35,417評(píng)論 5 343
  • 正文 年R本政府宣布慰于,位于F島的核電站,受9級(jí)特大地震影響唤衫,放射性物質(zhì)發(fā)生泄漏婆赠。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,013評(píng)論 3 325
  • 文/蒙蒙 一佳励、第九天 我趴在偏房一處隱蔽的房頂上張望休里。 院中可真熱鬧,春花似錦赃承、人聲如沸妙黍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拭嫁。三九已至可免,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間做粤,已是汗流浹背浇借。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怕品,地道東北人妇垢。 一個(gè)月前我還...
    沈念sama閱讀 47,711評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像肉康,于是被迫代替她去往敵國(guó)和親修己。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,601評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容