webentwicklung-frage-antwort-db.com.de

BottomNavigationView - So vermeiden Sie die Wiederherstellung von Fragmenten und deren Wiederverwendung

Ich möchte in meinem Projekt eine untere Navigationsleiste erstellen. Jede Ansicht hat ein eigenes Fragment. Das Problem ist, dass jedes Mal, wenn ich auf die Schaltfläche klicke, um die Ansicht zu ändern, beispielsweise ein neues Fragment mit völlig neuen Zuständen erstellt wird (z. B. Bildlaufposition, Text geändert, was auch immer in meinem Fragment enthalten ist). Ich weiß, dass in der offiziellen Android-Dokumentation geschrieben wurde, dass die untere Navigationsleiste die Taskzustände zurücksetzen sollte, aber ich denke, dass dies für Benutzer zu unbequem ist ... Ich hätte gerne eine ähnliche Funktionalität wie instagram, die Sie von Feed zu ändern erkunden und zurück, um die Bildlaufposition einzugeben, das Bild speichert alle zwischengespeicherten Objekte. Ich habe fast alle Möglichkeiten ausprobiert, um dieses Problem zu lösen. Das einzige, was funktioniert hat, ist, die Sichtbarkeit GONE und die Sichtbarkeit SICHTBAR je nach Situation einzustellen. Ich verstehe jedoch, dass es nicht RECHTS ist, es besser zu machen, und ich spreche nicht manuell benötigte Instanzen speichern. Ich habe fast jedes Tutorial über Bottom-Nav-Fragmente verfolgt, aber das Interessante ist, dass niemand daran interessiert ist, es jedes Mal neu zu verwenden, ohne es neu zu nennen.

 enter image description here

 enter image description here

FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frameLayout, FirstFragment.newInstance());
fragmentTransaction.commit();

bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        Fragment fragment = null;
        switch (item.getItemId()) {
            case R.id.menu_dialer:
                fragment = FirstFragment.newInstance();
                break;
            case R.id.menu_email:
                fragment = SecondFragment.newInstance();
                break;
            case R.id.menu_map:
                fragment = ThirdFragment.newInstance();
                break;
        }
        if (fragment != null) {
            FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
            fragmentTransaction.replace(R.id.frameLayout, fragment);
            fragmentTransaction.commit();
        }
        return true;
    }
});

Ich habe auch die onAttach- und Deattach-Lösungen ausprobiert, aber wieder kein Erfolg.

VIDEO LINK: Neue, ich habe Nino Handler-Version ausprobiert. Es funktioniert nur, wenn ich auf die gleiche Fragmenttaste tippe

Vielleicht hängt es damit zusammen, dass ich eine kanarische Version oder etwas Falsches in meinen gradle Abhängigkeiten verwende? enter image description here

 NEW UPDATES:

NEUE UPDATES:

public class MainActivity extends AppCompatActivity {

    private TextView mTextMessage;


    private static final String TAG_FRAGMENT_ONE = "fragment_one";
    private static final String TAG_FRAGMENT_TWO = "fragment_two";

    private FragmentManager fragmentManager;
    private Fragment currentFragment;

    String TAG = "babken";
    private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
            = new BottomNavigationView.OnNavigationItemSelectedListener() {

        Fragment fragment = null;
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.navigation_home:
                   fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FragmentFirst.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);

                    break;
                case R.id.navigation_dashboard:

                     fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = FragmentSecond.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);

                    break;
            }
            return true;

        }
    };

    private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
        if (!fragment.equals(currentFragment)) {
            fragmentManager
                    .beginTransaction()
                    .replace(R.id.armen, fragment, tag)
                    .commit();
            currentFragment = fragment;
        }
    }
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        fragmentManager = getSupportFragmentManager();
        Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
        if (fragment == null) {
            fragment = FragmentFirst.newInstance();
        }
        replaceFragment(fragment, TAG_FRAGMENT_ONE);
        BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
        navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);
    }

}
10

Daher habe ich lange nach diesem Thema gesucht und herausgefunden, dass es keine Möglichkeit gibt, Fragment mit automatisch gesicherten Zuständen wiederzuverwenden. Sie müssen die erforderlichen Zustände manuell speichern und dann immer abrufen, wenn ein neues Fragment erstellt wird zu hart und sogar in einigen Fällen ist es nicht möglich, den Status der Bildlaufansicht zu speichern (z. B. Recycleransicht). Also habe ich das Konzept namens VISIBILITY verwendet, wenn ich auf die Schaltfläche klicke, dass das Fragment sichtbar wird, und andere werden automatisch ausgeblendet.

1

Ich hatte ein ähnliches Problem, aber dieser Code hat mein Problem gelöst.

public class MainActivity extends AppCompatActivity {

final Fragment fragment1 = new HomeFragment();
final Fragment fragment2 = new DashboardFragment();
final Fragment fragment3 = new NotificationsFragment();
final FragmentManager fm = getSupportFragmentManager();
Fragment active = fragment1;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);


    BottomNavigationView navigation = (BottomNavigationView) findViewById(R.id.navigation);
    navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener);

    fm.beginTransaction().add(R.id.main_container, fragment3, "3").hide(fragment3).commit();
    fm.beginTransaction().add(R.id.main_container, fragment2, "2").hide(fragment2).commit();
    fm.beginTransaction().add(R.id.main_container,fragment1, "1").commit();

}


private BottomNavigationView.OnNavigationItemSelectedListener mOnNavigationItemSelectedListener
        = new BottomNavigationView.OnNavigationItemSelectedListener() {

    @Override
    public boolean onNavigationItemSelected(@NonNull MenuItem item) {
        switch (item.getItemId()) {
            case R.id.navigation_home:
                fm.beginTransaction().hide(active).show(fragment1).commit();
                active = fragment1;
                return true;

            case R.id.navigation_dashboard:
                fm.beginTransaction().hide(active).show(fragment2).commit();
                active = fragment2;
                return true;

            case R.id.navigation_notifications:
                fm.beginTransaction().hide(active).show(fragment3).commit();
                active = fragment3;
                return true;
        }
        return false;
    }
};

Hoffe das hilft.

source: BottomNavigationView With Fragments (Keine Fragmentwiederherstellung).

8
Bukunmi

Ich würde die Fragmentinstanzen nicht global beibehalten. Fügen Sie dem Fragment stattdessen ein Tag hinzu, wenn Sie sie erstellen

getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, new PlaceholderFragment(), TAG_PLACEHOLDER)
            .commit();

Dann können Sie es immer so abrufen:

Fragment fragment = getSupportFragmentManager().findFragmentByTag(TAG_PLACEHOLDER);
    if (fragment == null) {
        fragment = new PlaceholderFragment();
    }
    getSupportFragmentManager()
            .beginTransaction()
            .replace(R.id.container, fragment, TAG_PLACEHOLDER)
            .commit();

UPDATE: Ich habe meine Antwort aktualisiert und eine vollständige Lösung bereitgestellt:

private static final String TAG_FRAGMENT_ONE = "fragment_one";
private static final String TAG_FRAGMENT_TWO = "fragment_two";
private static final String TAG_FRAGMENT_THREE = "fragment_three";

private FragmentManager fragmentManager;
private Fragment currentFragment;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    // instantiate the fragment manager
    fragmentManager = getSupportFragmentManager();

    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
    if (fragment == null) {
        fragment = FirstFragment.newInstance();
    }
    replaceFragment(fragment, TAG_FRAGMENT_ONE);

    bottomNavigationView = (BottomNavigationView) findViewById(R.id.navigation);
    bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            Fragment fragment = null;
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    // I'm aware that this code can be optimized by a method which accepts a class definition and returns the proper fragment
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_ONE);
                    if (fragment == null) {
                        fragment = FirstFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_ONE);
                    break;
                case R.id.menu_email:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_TWO);
                    if (fragment == null) {
                        fragment = SecondFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_TWO);
                    break;
                case R.id.menu_map:
                    Fragment fragment = fragmentManager.findFragmentByTag(TAG_FRAGMENT_THREE);
                    if (fragment == null) {
                        fragment = ThirdFragment.newInstance();
                    }
                    replaceFragment(fragment, TAG_FRAGMENT_THREE);
                    break;
            }
            return true;
        }
    });
}

private void replaceFragment(@NonNull Fragment fragment, @NonNull String tag) {
    if (!fragment.equals(currentFragment)) {
        fragmentManager
            .beginTransaction()
            .replace(R.id.frameLayout, fragment, tag)
            .commit();
        currentFragment = fragment;
    }
}

ZUSÄTZLICHE INFORMATIONEN: Wenn Sie sicherstellen möchten, dass sich der Fragmentstatus nicht ändert, und wenn Sie die Fragmente auch überziehen möchten, sollten Sie einen ViewPager mit einem FragmentStatePagerAdapter verwenden und den aktuellen Wert ändern Fragment im Adapter bei jedem Klickereignis

2
Nino Handler

Sie können die Methoden attach () und detach () verwenden:

private FirstFragment firstFragment = FirstFragment.newInstance();
private SecondFragment secondFragment= SecondFragment.newInstance();
private ThirdFragment thirdFragment = ThirdFragment.newInstance();

navigation.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem item) {
            switch (item.getItemId()) {
                case R.id.menu_dialer:
                    changeFragment(firstFragment, "firstFragment");
                    return true;
                case R.id.menu_email:
                    changeFragment(secondFragment, "secondFragment");
                    return true;
                case R.id.menu_map:
                    changeFragment(thirdFragment, "thirdFragment");
                    return true;
            }
            return false;
        }
    });

public void changeFragment(Fragment fragment, String tagFragmentName) {

    FragmentManager mFragmentManager = getSupportFragmentManager();
    FragmentTransaction fragmentTransaction = mFragmentManager.beginTransaction();

    Fragment currentFragment = mFragmentManager.getPrimaryNavigationFragment();
    if (currentFragment != null) {
        fragmentTransaction.detach(currentFragment);
    }

    Fragment fragmentTemp = mFragmentManager.findFragmentByTag(tagFragmentName);
    if (fragmentTemp == null) {
        fragmentTemp = fragment;
        fragmentTransaction.add(R.id.content, fragmentTemp, tagFragmentName);
    } else {
        fragmentTransaction.attach(fragmentTemp);
    }

    fragmentTransaction.setPrimaryNavigationFragment(fragmentTemp);
    fragmentTransaction.setReorderingAllowed(true);
    fragmentTransaction.commitNowAllowingStateLoss();
}
1
Albert

Ich habe eine Kotlin-Erweiterungsfunktion für die FragmentManager-Klasse geschrieben

fun FragmentManager.switch(containerId: Int, newFrag: Fragment, tag: String) {

    var current = findFragmentByTag(tag)
    beginTransaction()
        .apply {

            //Hide the current fragment
            primaryNavigationFragment?.let { hide(it) }

            //Check if current fragment exists in fragmentManager
            if (current == null) {
                current = newFrag
                add(containerId, current!!, tag)
            } else {
                show(current!!)
            }
        }
        .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
        .setPrimaryNavigationFragment(current)
        .setReorderingAllowed(true)
        .commitNowAllowingStateLoss()
}

Dies kann von supportFragmentManager.swtich(R.id.container,newFrag,newFrag.TAG) aus onNavigationItemSelected aufgerufen werden

1

Alle vorherigen Antworten verwenden fragmentTransaction.replace(...). Dadurch wird das aktuelle Fragment ersetzt, indem es zerstört wird (was das Problem verursacht). Daher funktionieren alle diese Lösungen nicht wirklich. 

Dies ist das nächste Problem, das ich als Lösung für dieses Problem bekommen könnte:

private void selectContentFragment(Fragment fragmentToSelect)
{
    FragmentTransaction fragmentTransaction = this.getSupportFragmentManager().beginTransaction();

    if (this.getSupportFragmentManager().getFragments().contains(fragmentToSelect)) {
        // Iterate through all cached fragments.
        for (Fragment cachedFragment : this.getSupportFragmentManager().getFragments()) {
            if (cachedFragment != fragmentToSelect) {
                // Hide the fragments that are not the one being selected.
                fragmentTransaction.hide(cachedFragment);
            }
        }
        // Show the fragment that we want to be selected.
        fragmentTransaction.show(fragmentToSelect);
    } else {
        // The fragment to be selected does not (yet) exist in the fragment manager, add it.
        fragmentTransaction.add(R.id.fragment_container, fragmentToSelect);
    }

    fragmentTransaction.commit();
}

Damit dies funktioniert, sollten Sie die Fragmente in einem Array (oder in separaten Variablen) in Ihrer Aktivität nachverfolgen. Ich habe alle Fragmente in einem SparseArray vorab instanziiert.  

0

Wie ist es damit. Sie erklären die Fragmente in der Klasse.

Fragment firstFragment,secondFragment,thirdFragment;

dann in der switch-case können Sie als das kodieren:

switch (item.getItemId()) {
    case R.id.menu_dialer:
        if(firstFragment != null) {
            fragment = firstFragment;
        }else{
            fragment = FirstFragment.newInstance();
        }
        break;
    case R.id.menu_email:
        // the same ...
        break;
    case R.id.menu_map:
        //the same ...
        break;
}
0
Relish Wang

Lösung mit Ausblenden/Anzeigen von Fragmenten ist schlecht, weil dadurch der Speicher beeinträchtigt wird. Wenn in anderen Fragmenten eine schwierige Arbeit stattfindet (z. B. Laden von Daten mit API), wird dies alles gleichzeitig geladen. Wenn die Anwendung gestartet wird, dauert dies jedoch sehr lange. Ich denke, es ist eine gute Idee, die Fragmente nur dann zu speichern, wenn Sie sie drücken. Bei wiederholtem Übergang wird das vorhandene Objekt aufgerufen. Höchstwahrscheinlich gibt es einige Fallstricke, schreiben Sie, wenn Sie auftauchen;) (ps. Ich bin kein Muttersprachler)

Hauptaktivität

public class MainActivity extends AppCompatActivity implements BottomNavigationView.OnNavigationItemSelectedListener {

private Fragment fragment1;
private Fragment fragment2;
private Fragment fragment3;

private FragmentManager fragmentManager;

private RelativeLayout fullLayout;
private BottomNavigationView bottomNavigationMenuView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    fullLayout = (RelativeLayout) getLayoutInflater().inflate(R.layout.activity_body, null);
    super.setContentView(R.layout.activity_body);


    // Bottom navigation
    initBottomNavigation();

    // Init fragments
    initFragments();
}

private void initFragments() {
    fragmentManager = getSupportFragmentManager();
    fragment1 = new Fragment1();
    replaceFragment(fragment1);
}

protected void initBottomNavigation() {
    bottomNavigationMenuView = findViewById(R.id.bottomNavigation);
    bottomNavigationMenuView.setOnNavigationItemSelectedListener(this);
}

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
    switch (menuItem.getItemId()) {
        case R.id.bottom_menu_home:
            if (fragment1 == null) {
                fragment1 = new Fragment1();
            }
            replaceFragment(fragment1);
            return true;

        case R.id.bottom_menu_add:
            if (fragment2 == null) {
                fragment2 = new Fragment2();
            }
            replaceFragment(fragment2);
            return true;

        case R.id.bottom_menu_profile:
            if (fragment3 == null) {
                fragment3 = new Fragment3();
            }
            replaceFragment(fragment3);
            return true;
    }

    return true;
}

private void replaceFragment(@NonNull Fragment fragment) {
    fragmentManager
            .beginTransaction()
            .replace(R.id.mainContainer, fragment, fragment.getTag())
            .commit();
}
0
izmail

Erstellen Sie die drei Fragmente als Mitglieder der Klasse und verwenden Sie sie erneut.

public class MainActivity extends AppCompatActivity {
    private final Fragment mFirstFragment = new FirstFragment();
    private final Fragment mSecondFragment = new SecondFragment();
    private final Fragment mThirdFragment = new ThirdFragment();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...... 
        ......
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, mFirstFragment);
        fragmentTransaction.commit();

        bottomNavigationView.setOnNavigationItemSelectedListener(new BottomNavigationView.OnNavigationItemSelectedListener() {
            @Override
            public boolean onNavigationItemSelected(@NonNull MenuItem item) {
                if (bottomNavigationView.getSelectedItemId() != item.getItemId()) {
                    switch (item.getItemId()) {
                        R.id.menu_dialer:
                            replaceFragment(mFirstFragment);
                            break;
                        case R.id.menu_email:
                            replaceFragment(mSecondFragment);
                            break;
                        case R.id.menu_map:
                            replaceFragment(mThirdFragment);
                            break;
                    }
                }
                return true;
            }
        });
    }

    private void replaceFragment(Fragment fragment) {
        FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.frameLayout, fragment);
        fragmentTransaction.commit();
    }

}
0
kws

Die Antwort von Thomas hat mir beinahe geholfen, hatte aber das Problem, dass sich neue Fragmente beim ersten Öffnen überlappen, aber nicht überlappen, wenn ich sie erneut öffne, indem ich die Menütasten drücke.

Also habe ich seinen Code geändert und die Lösung mit dem folgenden Code erhalten:

 private fun selectContentFragment(fragmentToSelect: Fragment) {
        val fragmentTransaction = fragmentManager?.beginTransaction()
        if (fragmentManager?.fragments?.contains(fragmentToSelect)!!) {
            // Show the fragment that we want to be selected.
            fragmentTransaction?.show(fragmentToSelect)
        } else {
            // The fragment to be selected does not (yet) exist in the fragment manager, add it.
            fragmentTransaction?.add(R.id.container, fragmentToSelect)
        }
        // Iterate through all cached fragments.
        for (cachedFragment in fragmentManager?.fragments!!) {
            if (cachedFragment !== fragmentToSelect) {
                // Hide the fragments that are not the one being selected.
                // Uncomment following line and change the name of the fragment if your Host isn't an activity and a fragment otherwise whole view will get hidden.
                // if (!cachedFragment.toString().contains("HomeContainerFragment"))

                fragmentTransaction?.hide(cachedFragment)
            }
        }
        fragmentTransaction?.commit()
    }

Stellen Sie sicher, dass Sie die neue Instanz des Fragments nicht jedes Mal übergeben.

Das wird funktionieren:

selectContentFragment(
                    when (item.itemId) {
                        R.id.home -> frag1
                        R.id.photoGallery -> frag2
                        else -> Home()
                    }
            )

wo frag1 und frag2 sind globale Variablen definiert als:

 val frag1 = Home()
 val frag2 = PhotoGallery()

Das wird nicht funktionieren:

selectContentFragment(
                    when (item.itemId) {
                        R.id.home -> Home()
                        R.id.photoGallery -> PhotoGallery()
                        else -> Home()
                    }
            )

Es verschwendete meine Stunden. Hoffe es hilft anderen!

0
Paras Sidhu