1. 程式人生 > >fragment重疊的完美解決方案

fragment重疊的完美解決方案

Fragment的應用場景有很多,比如我們底部一個導航欄,點選導航項顯示不同的fragment,或者和ViewPager配合使用等.

比如這樣:

package com.example.yzq.testfragment;

import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import
android.view.MenuItem; import android.widget.FrameLayout; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener { private FrameLayout contentLayout;//容器 private BottomNavigationView mainBottomView;//底部導航
private HomeFragment homeFragment; private DashboardFragment dashboardFragment; private NoticeFragment noticeFragment; private List<Fragment> fragmentList = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); L.i("onCreate"
); initView(); initFragment(); } private void initView() { contentLayout = (FrameLayout) findViewById(R.id.bottom_nav_content); mainBottomView = (BottomNavigationView) findViewById(R.id.mainBottomView); mainBottomView.setOnNavigationItemSelectedListener(this); } private void initFragment() { /* 預設顯示home fragment*/ homeFragment = new HomeFragment(); addFragment(homeFragment); showFragment(homeFragment); } /*新增fragment*/ private void addFragment(Fragment fragment) { /*判斷該fragment是否已經被新增過 如果沒有被新增 則新增*/ if (!fragment.isAdded()) { getSupportFragmentManager().beginTransaction().add(R.id.bottom_nav_content, fragment).commit(); /*新增到 fragmentList*/ fragmentList.add(fragment); } } /*顯示fragment*/ private void showFragment(Fragment fragment) { for (Fragment frag : fragmentList) { if (frag != fragment) { /*先隱藏其他fragment*/ getSupportFragmentManager().beginTransaction().hide(frag).commit(); } } getSupportFragmentManager().beginTransaction().show(fragment).commit(); } @Override public boolean onNavigationItemSelected(@NonNull MenuItem item) { switch (item.getItemId()) { case R.id.navigation_home: if (homeFragment == null) { homeFragment = new HomeFragment(); } addFragment(homeFragment); showFragment(homeFragment); break; case R.id.navigation_dashboard: if (dashboardFragment == null) { dashboardFragment = new DashboardFragment(); } addFragment(dashboardFragment); showFragment(dashboardFragment); break; case R.id.navigation_notifications: if (noticeFragment == null) { noticeFragment = new NoticeFragment(); } addFragment(noticeFragment); showFragment(noticeFragment); break; } return true; } }

上面的程式碼很簡單,就是一個對fragment的新增,隱藏,顯示的操作。

示例圖:

這裡寫圖片描述

但是當我們橫豎屏切換時,或者鎖屏過段時間再進入app時,很可能會遇見重疊的問題.

比如這樣:

這裡寫圖片描述

旋轉了個螢幕,發現就已經重疊了。

出現這種問題的原因是:當我們旋轉螢幕的時候,activity會被銷燬並重新建立,並且在銷燬之前執行了onSaveInstanceState(Bundle outState)這個方法。這個方法會儲存activity的一些資訊,其中就包括新增過的fragment,當activity被重新建立時,會初始化其中的變數,這個時候點選底部導航的話會重新去新增fragment,也就導致了重疊的問題。

首先,我們來列印一下生命週期方法的log,看看是不是這樣的。

package com.example.yzq.testfragment;

import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.widget.FrameLayout;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {


    private FrameLayout contentLayout;//容器
    private BottomNavigationView mainBottomView;//底部導航


    private HomeFragment homeFragment;
    private DashboardFragment dashboardFragment;
    private NoticeFragment noticeFragment;


    private List<Fragment> fragmentList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        L.i("MainActivity  onCreate");
        initView();
        initFragment();

    }

    private void initView() {
        contentLayout = (FrameLayout) findViewById(R.id.bottom_nav_content);
        mainBottomView = (BottomNavigationView) findViewById(R.id.mainBottomView);

        mainBottomView.setOnNavigationItemSelectedListener(this);
    }

    private void initFragment() {
       /* 預設顯示home  fragment*/
        homeFragment = new HomeFragment();
        addFragment(homeFragment);
        showFragment(homeFragment);


    }

    /*新增fragment*/
    private void addFragment(Fragment fragment) {

        /*判斷該fragment是否已經被新增過  如果沒有被新增  則新增*/
        if (!fragment.isAdded()) {
            getSupportFragmentManager().beginTransaction().add(R.id.bottom_nav_content, fragment).commit();
        /*新增到 fragmentList*/
            fragmentList.add(fragment);
        }


    }


    /*顯示fragment*/
    private void showFragment(Fragment fragment) {


        L.i("fragmentList數量:" + fragmentList.size());
        for (Fragment frag : fragmentList) {

            if (frag != fragment) {
                /*先隱藏其他fragment*/
                L.i("隱藏" + fragment);
                getSupportFragmentManager().beginTransaction().hide(frag).commit();
            }
        }
        getSupportFragmentManager().beginTransaction().show(fragment).commit();

    }


    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {

        switch (item.getItemId()) {
            case R.id.navigation_home:

                if (homeFragment == null) {
                    L.i("homeFragment 為空  建立");
                    homeFragment = new HomeFragment();
                }


                addFragment(homeFragment);
                showFragment(homeFragment);
                break;

            case R.id.navigation_dashboard:
                if (dashboardFragment == null) {
                    L.i("dashboardFragment 為空  建立");
                    dashboardFragment = new DashboardFragment();
                }
                addFragment(dashboardFragment);
                showFragment(dashboardFragment);
                break;

            case R.id.navigation_notifications:

                if (noticeFragment == null) {
                    L.i("noticeFragment 為空  建立");
                    noticeFragment = new NoticeFragment();
                }
                addFragment(noticeFragment);
                showFragment(noticeFragment);
                break;


        }
        return true;
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        L.i("MainActivity onSaveInstanceState");
        super.onSaveInstanceState(outState);
    }


    @Override
    protected void onStart() {
        super.onStart();
        L.i("MainActivity onStart");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        L.i("MainActivity onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        L.i("MainActivity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        L.i("MainActivity onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        L.i("MainActivity onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        L.i("MainActivity onDestroy");
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {

        super.onConfigurationChanged(newConfig);
        L.i("MainActivity onConfigurationChanged");
    }
}

上面的程式碼只是添加了列印的log。我們來看看列印的日誌。

這裡寫圖片描述

動圖有點看不清,我們來看看截圖。

一開始進去時走的就是正常的oncreate到onresume方法。

這裡寫圖片描述

旋轉螢幕時,activity是會被onDestory的,在onDestory之前執行了onSaveInstanceState(Bundle outState)這個方法,這個方法儲存了activity的一些狀態資訊,其中就包括所新增過的fragment。

這裡寫圖片描述

然後,activity會被重新建立,同樣的,之前所新增過的fragment也會被重新建立,而且是預設顯示的,當我們點選底部導航的時候,由於activity重新建立了,所以其中的fragment變數是為空的,此時點選導航的時候還會建立新的fragment,這樣 ,就導致了重疊的問題。

這裡寫圖片描述

解決的辦法:

其實解決辦法很簡單,上面的一堆只是讓我們瞭解為什麼會出現這種問題。
1.想辦法不讓activity儲存資訊。(不推薦)


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        L.i("MainActivity onSaveInstanceState");
        //super.onSaveInstanceState(outState);
    }

這裡寫圖片描述

我們可以直接註釋掉super.onSaveInstanceState(outState);
這樣一來activity就不會儲存資訊了,但是,從圖片上也可以看到,當螢幕旋轉時,每次都會跟重新開啟一樣。如果你旋轉螢幕之前顯示的是noticeFragment時,螢幕切換後就會自動切換到預設的homeFragment上。這並不是我們想要的結果。

2。旋轉螢幕時不讓activity走生命週期方法(推薦)

這個方法最簡單也最省事,只需要在相應的activity中宣告
android:configChanges=”keyboardHidden|orientation|screenSize”> 即可。

        <activity
            android:name=".MainActivity"
            android:configChanges="keyboardHidden|orientation|screenSize">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

宣告這個屬性後,當我們切換螢幕時,也就不會在走activity的生命週期方法了,也就不會造成fragment重疊的問題了。

還有一種可能也會造成fragment重疊的問題,就是當記憶體不足時activity被系統回收時,再次進入也會造成重疊的問題,原因也是因為onSaveInstanceState(outState);方法儲存了activity的一些資料。

因為現在的手機記憶體已經很大了,可以說出現這種問題的機率也不是很大了,我們就模擬一下,將手機開發者選項中的不保留活動開啟,這樣一來,當我們按home鍵時,activity就會被回收掉。

進入開發者選項很簡單, 設定–>關於手機–>連續點選版本號六七次,即可。

這裡寫圖片描述

再次執行我們來看看結果:

這裡寫圖片描述

可以看到,但我們點選home鍵再重新進入app時,就出現了重疊的問題。

因為是系統回收的activity,所以,我們就沒法去控制activity不讓他走生命週期方法,我們可以從另一個方面著手去解決。

解決辦法:
在onSaveInstanceState(outState);中去儲存fragment,當activity被恢復時,取出這些fragment即可。

程式碼如下:

package com.example.yzq.testfragment;

import android.content.res.Configuration;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.BottomNavigationView;
import android.support.v4.app.Fragment;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.widget.FrameLayout;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {


    private FrameLayout contentLayout;//容器
    private BottomNavigationView mainBottomView;//底部導航


    private HomeFragment homeFragment;
    private DashboardFragment dashboardFragment;
    private NoticeFragment noticeFragment;


    private static final String HOME_FRAGMENT_KEY = "homeFragment";
    private static final String DASHBOARD_FRAGMENT_KEY = "DashboardFragment";
    private static final String NOTICE_FRAGMENT_KEY = "NoticeFragment";


    private List<Fragment> fragmentList = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        L.i("MainActivity  onCreate");
        initView();


        if (savedInstanceState != null) {

            /*獲取儲存的fragment  沒有的話返回null*/
            homeFragment = (HomeFragment) getSupportFragmentManager().getFragment(savedInstanceState, HOME_FRAGMENT_KEY);
            dashboardFragment = (DashboardFragment) getSupportFragmentManager().getFragment(savedInstanceState, DASHBOARD_FRAGMENT_KEY);
            noticeFragment = (NoticeFragment) getSupportFragmentManager().getFragment(savedInstanceState, NOTICE_FRAGMENT_KEY);


            addToList(homeFragment);
            addToList(dashboardFragment);
            addToList(noticeFragment);


        } else {
            initFragment();
        }


    }

    private void addToList(Fragment fragment) {
        if (fragment != null) {
            fragmentList.add(fragment);
        }

        L.i("fragmentList數量" + fragmentList.size());
    }

    private void initView() {
        contentLayout = (FrameLayout) findViewById(R.id.bottom_nav_content);
        mainBottomView = (BottomNavigationView) findViewById(R.id.mainBottomView);

        mainBottomView.setOnNavigationItemSelectedListener(this);
    }

    private void initFragment() {
       /* 預設顯示home  fragment*/
        homeFragment = new HomeFragment();
        addFragment(homeFragment);
        showFragment(homeFragment);


    }

    /*新增fragment*/
    private void addFragment(Fragment fragment) {

        /*判斷該fragment是否已經被新增過  如果沒有被新增  則新增*/
        if (!fragment.isAdded()) {
            getSupportFragmentManager().beginTransaction().add(R.id.bottom_nav_content, fragment).commit();
        /*新增到 fragmentList*/
            fragmentList.add(fragment);
        }


    }


    /*顯示fragment*/
    private void showFragment(Fragment fragment) {


        L.i("fragmentList數量:" + fragmentList.size());
        for (Fragment frag : fragmentList) {

            if (frag != fragment) {
                /*先隱藏其他fragment*/
                L.i("隱藏" + fragment);
                getSupportFragmentManager().beginTransaction().hide(frag).commit();
            }
        }
        getSupportFragmentManager().beginTransaction().show(fragment).commit();

    }


    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {

        switch (item.getItemId()) {
            case R.id.navigation_home:

                if (homeFragment == null) {
                    L.i("homeFragment 為空  建立");
                    homeFragment = new HomeFragment();
                }


                addFragment(homeFragment);
                showFragment(homeFragment);
                break;

            case R.id.navigation_dashboard:
                if (dashboardFragment == null) {
                    L.i("dashboardFragment 為空  建立");
                    dashboardFragment = new DashboardFragment();
                }
                addFragment(dashboardFragment);
                showFragment(dashboardFragment);
                break;

            case R.id.navigation_notifications:

                if (noticeFragment == null) {
                    L.i("noticeFragment 為空  建立");
                    noticeFragment = new NoticeFragment();
                }
                addFragment(noticeFragment);
                showFragment(noticeFragment);
                break;


        }
        return true;
    }


    @Override
    protected void onSaveInstanceState(Bundle outState) {
        L.i("MainActivity onSaveInstanceState");
        /*fragment不為空時 儲存*/
        if (homeFragment != null) {
            getSupportFragmentManager().putFragment(outState, HOME_FRAGMENT_KEY, homeFragment);
        }
        if (dashboardFragment != null) {
            getSupportFragmentManager().putFragment(outState, DASHBOARD_FRAGMENT_KEY, dashboardFragment);
        }
        if (noticeFragment != null) {
            getSupportFragmentManager().putFragment(outState, NOTICE_FRAGMENT_KEY, noticeFragment);
        }


        super.onSaveInstanceState(outState);
    }


    @Override
    protected void onStart() {
        super.onStart();
        L.i("MainActivity onStart");
    }

    @Override
    protected void onRestart() {
        super.onRestart();
        L.i("MainActivity onRestart");
    }

    @Override
    protected void onResume() {
        super.onResume();
        L.i("MainActivity onResume");
    }

    @Override
    protected void onPause() {
        super.onPause();
        L.i("MainActivity onPause");
    }

    @Override
    protected void onStop() {
        super.onStop();
        L.i("MainActivity onStop");
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        L.i("MainActivity onDestroy");
    }

    @Override
    public void onConfigurationChanged(Configuration newConfig) {

        super.onConfigurationChanged(newConfig);
        L.i("MainActivity onConfigurationChanged");
    }
}

主要就是在onSaveInstanceState中將fragment儲存起來。

這裡寫圖片描述

在oncreate的時候判斷 一下savedInstanceState 是為空,不為空的話就是有儲存的fragment資訊,然後將儲存的fragment取出來賦給對您的變數即可。

這裡寫圖片描述

我們再來看看效果圖(依舊是在不保留活動的情況下執行):

這裡寫圖片描述

可以看到,既保留了activity檢視狀態,也完美解決了fragment的重疊問題。

好了,希望能幫到你,如果你覺得對你有幫助,麻煩動動手指頂一下,算是對我的一個鼓勵,謝謝!