Обробка заголовка ActionBar із зворотним стеком фрагментів?


74

У мене є місце, Activityде я завантажую a ListFragment, після натискання він просвічує рівень і відображається новий тип ListFragment, замінюючи початковий (використовуючи showFragmentметод нижче). Це поміщається на задній стек.

На початку дія відображає заголовок за замовчуванням на панелі дій (тобто він встановлюється автоматично на основі програми android:label).

Під час показу списку для наступного рівня в ієрархії, назва елемента, на який клацнули, має стати заголовком панелі дій.

Однак при натисканні Backя хотів би відновити початковий заголовок за замовчуванням. Про це щось не FragmentTransactionвідомо, тому назва не відновлюється.

Я неясно читав про це FragmentBreadCrumbs, але, схоже, для цього потрібно використовувати власний вигляд. Я використовую ActionBarSherlock і вважаю за краще не мати власного власного подання заголовка.

Який найкращий спосіб зробити це? Чи можливо без завантаження типового коду та необхідності відстежувати заголовки, показані в дорозі?


protected void showFragment(Fragment f) {
  FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
  ft.replace(R.id.fragment_container, f);
  ft.addToBackStack(null);
  ft.commit();
}

Відповіді:


123

У кожному фрагменті та кожній діяльності я міняю заголовок так. Таким чином активна назва завжди буде правильною:

@Override
public void onResume() {
    super.onResume();
    // Set title
    getActivity().getActionBar()
        .setTitle(R.string.thetitle);
}

Бувають випадки, коли onResume не викликається всередині фрагментів. У деяких із цих випадків ми можемо використовувати:

public void setUserVisibleHint(boolean isVisibleToUser) {
    super.setUserVisibleHint(isVisibleToUser);
    if(isVisibleToUser) {
        // Set title
        getActivity().getActionBar()
            .setTitle(R.string.thetitle);
    }
}

2
Мені не було потреби кидати або отримувати панель дій (тобто я просто дзвоню getActivity().setTitle(...)), але це розумний підхід. Дякую.
Крістофер Орр,

9
-1. Такий підхід неправильний! Відповідно до вказівок щодо дизайну Android, під час перемикання фрагментів за допомогою панелі навігації заголовок панелі дій повинен змінюватися лише в панелі навігації при зворотному виклику. Ваш підхід неправильно змінить заголовок до того, як панель навігації буде закрита.
zyamys

6
@zyamys Якщо ви отримаєте кращу пропозицію, я із задоволенням підтримую її. Це була найкраща відповідь, яку я міг придумати на той час.
Warpzit

7
Це не спрацювало для мене, тому я змінив його на, getActivity().setTitle(R.string.thetitle);а потім він спрацював.
simeg

4
це працює, лише якщо ви заміните фрагмент під час FragmentTransaction. що якщо ви додасте фрагмент замість заміни?
Камлеш Карванде

28

Оскільки оригінальна відповідь досить стара, це також може допомогти. Як зазначено в документації, можна зареєструвати, listenerщоб прослуховувати зміни на задньому стеку в хостингу Activity:

getSupportFragmentManager().addOnBackStackChangedListener(
        new FragmentManager.OnBackStackChangedListener() {
            public void onBackStackChanged() {
                // Update your UI here.
            }
        });

Потім визначте ситуацію в методі зворотного виклику та встановіть правильний заголовок, не отримуючи доступу до ActionBarз Fragment.

Це більш елегантне рішення, оскільки Fragmentне потрібно знати про його ActionBarіснування, і, Activityяк правило, це місце, яке управляє бекстейком, тому його обробка здається більш доречною. Fragmentзавжди слід розглядати лише власний зміст, а не оточення.

Детальніше про тему в документації .


1
Інша відповідь стара, але я все ще вважаю, що це найпростіший і найпрагматичніший спосіб забезпечити правильність назви. Як зазначалося в моєму коментарі, вам не потрібно отримувати прямий доступ до ActionBar. Рішення тут вимагає додаткового коду, але також не допомагає вашому початковому фрагменту, який зазвичай не додається до заднього стеку.
Крістофер Орр,

1
Також немає нічого поганого у доступі до панелі дій із фрагментів. Google також надає API для зміни елементів меню всередині фрагментів.
Warpzit

2
Через деякий час я дійшов висновку, що обидва рішення чудові, це залежить лише від вашого випадку використання, іноді це буде більш доцільним для використання onResume, а іноді і прослуховувача менеджера фрагментів. Тож загалом добре знати свої інструменти, щоб ви могли адекватно застосовувати їх до своїх потреб.
Maciej Pigulski

2
@ Мацей Пігульський А потім? Ми не знаємо нічого про контекст цього зворотного дзвінка.
fralbo

2
@caBBAlainB, контекст зберігається у FragmentManager хостинг-активності, тому вам доведеться визначати заголовок, роблячи деякі громіздкі перевірки менеджера - отже, це не чудово. Сьогодні я не можу згадати, якими тут були міркування. Я думаю, що ця ідея здебільшого походить від того, що написано в документах Android, на які посилається ця відповідь (абзац над фрагментом слухача). Сьогодні я волів би піти і використати прийняту відповідь.
Maciej Pigulski

11

Нехай контролінг виконує всю роботу таким чином:

Прислухайтесь до подій, що проводяться у фоновому режимі (у onCreate () діяльності):

// Change the title back when the fragment is changed
    getSupportFragmentManager().addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
        @Override
        public void onBackStackChanged() {
            Fragment fragment = getFragment();
            setTitleFromFragment(fragment);
        }
    });

Отримайте поточний фрагмент із контейнера:

/**
 * Returns the currently displayed fragment.
 * @return
 *      Fragment or null.
 */
private Fragment getFragment() {
    Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
    return fragment;
}

Встановіть фрагмент всередині подання вмісту:

private void setFragment(Fragment fragment, boolean addToBackStack) {
    // Set the activity title
    setTitleFromFragment(fragment);
    .
    .
    .
}

Це не означає "дозволяти діяльності виконувати всю роботу", якщо у вас є реалізований якийсь інтерфейс у кожному, Fragmentщоб setTitleFromFragment()можна було отримати його заголовок. Здається, жоден з них не відповідає частині мого запитання "без навантаження на шаблонний код".
Крістофер Орр,

У моєму рішенні немає згадки про інтерфейс, ваша діяльність може перевірити екземпляр фрагмента та встановити заголовок на основі цього, дозволяючи вашій діяльності виконувати всю роботу. Усі ці методи визначаються всередині діяльності.
Meanman

Так, це звучить, мабуть, ще гірше з точки зору дизайну / поділу проблем / повторного використання. Плюс це надмірно змінює заголовок двічі для кожного фрагмента, доданого до бекстейка, оскільки метод setTitleFromFragment викликається безумовно в setFragment, я думаю? У будь-якому випадку, дякую, але я дотримуватимусь прагматичної та простої прийнятої відповіді :)
Крістофер Орр

3
Як це звучить "напевно, ще гірше з точки зору дизайну / поділу проблем / повторного використання"? Будь ласка, підкріпіть це дійсним аргументом? Ви дозволяєте діяльності бути "контролером" і встановлюєте власну назву на основі її змісту. Що робити, якщо я хочу використовувати фрагмент у кількох місцях або займатися діяльністю з кількома фрагментами, і всі вони намагаються встановити заголовок.
Meanman

Дуже корисна відповідь
VVB

7

Варпзіт має рацію. Це також вирішує проблему заголовка при зміні орієнтації пристрою. Також якщо ви використовуєте підтримку v7 для панелі дій, ви можете отримати панель дій із такого фрагмента:

@Override
public void onResume() {
    super.onResume();
    ((ActionBarActivity)getActivity()).getSupportActionBar().setTitle("Home");
}

+1 для підтримки v7. Я думаю, що кожного разу, коли дається відповідь на android, особа, яка відповідає, повинна враховувати підтримку. На сьогоднішній день, щоб досягти 80% охоплення пристрою, ПОВИНЕН повернутися до API 16. У більшості випадків це вимагає використання бібліотек compat.
akousmata

1
я вклав ваш код у public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)метод і працював нормально.
GFPF

7

Найкраще дозволити ОС виконувати якомога більше роботи. Припускаючи, що кожен фрагмент правильно названий за допомогою .addToBackStack ("заголовок"), тоді ви можете перевизначити onBackPress щось подібне, щоб досягти бажаної поведінки:

// this example uses the AppCompat support library
// and works for dynamic fragment titles
@Override
public void onBackPressed() {
    FragmentManager fragmentManager = getSupportFragmentManager();
    int count = fragmentManager.getBackStackEntryCount();
    if (count <= 1) {
        finish();
    }
    else {
        String title = fragmentManager.getBackStackEntryAt(count-2).getName();
        if (count == 2) {
            // here I am using a NavigationDrawer and open it when transitioning to the initial fragment
            // a second back-press will result in finish() being called above.
            mDrawerLayout.openDrawer(mNavigationDrawerFragment.getView());
        }
        super.onBackPressed();
        Log.v(TAG, "onBackPressed - title="+title);
        getSupportActionBar().setTitle(title);
    }
}

5

Я використовую рішення, подібне до підходу Лі , але замість onBackStackChanged()цього замінює метод.

Спочатку я встановлюю ім'я фрагмента при додаванні транзакції до заднього стеку.

getSupportFragmentManager().beginTransaction()
                .replace(R.id.frame_content, fragment)
                .addToBackStack(fragmentTitle)
                .commit();

Потім я перевизначую onBackStackChanged()метод і дзвоню setTitle()з останнім ім'ям запису.

@Override
public void onBackStackChanged() {
    int lastBackStackEntryCount = getSupportFragmentManager().getBackStackEntryCount() - 1;
    FragmentManager.BackStackEntry lastBackStackEntry =
            getSupportFragmentManager().getBackStackEntryAt(lastBackStackEntryCount);

    setTitle(lastBackStackEntry.getName());
}

3
Проблема використання імені запису backstack полягає в тому, що воно не обробляє зміни конфігурації. Цікавим і правильним способом вирішення цієї проблеми було б використання setBreadCrumbTitle (int res) для збереження ідентифікатора ресурсу заголовка, який ви могли б використовувати в onBackStackChanged для встановлення заголовка.
Девід Лю

4

Використовуйте метод фрагментів:

@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater)

Він викликається при кожному появі Фрагменту, а при Резюме - ні.


так .. жодна з відповідей не збережена моєю проблемою .. це чудово працює. Це може бути не професійним способом .. але це трохи хакі.
Ранджит Кумар,

@Bevor, щоб це працювало, вам потрібно меню опцій, ви можете активувати його для фрагмента, зателефонувавши "setHasOptionsMenu (true);" у методі OnCreateView (...) вашого фрагмента.
New2HTML

1

Найкращий підхід - використовувати метод інтерфейсу OnBackStackChangedListener, наданий андроїдом, onBackStackChanged ().

Скажімо, у нас є шухляда для навігації з 4 опціями, до яких користувач може перейти. У такому випадку ми матимемо 4 фрагменти. Давайте спочатку побачимо код, а потім я пояснить, як це працює.

    private int mPreviousBackStackCount = 0;
    private String[] title_name = {"Frag1","Frag2","Frag3","Frag4"};
    Stack<String> mFragPositionTitleDisplayed;

    public class MainActivity extends ActionBarActivity implements FragmentManager.OnBackStackChangedListener
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ....
    ....
    ....
    getSupportFragmentManager().addOnBackStackChangedListener(this);
    mFragPositionTitleDisplayed = new Stack<>();
}

public void displayFragment() {
    Fragment fragment = null;
    String title = getResources().getString(R.string.app_name);
    switch (position) {
        case 0:
            fragment = new Fragment1();
            title = title_name[position];
            break;
        case 1:
            fragment = new Fragment2();
            title = title_name[position];
            break;
        case 2:
            fragment = new Fragment3();
            title = title_name[position];
            break;
        case 3:
            fragment = new Fragment4();
            title = title_name[position];
            break;
        default:
            break;
    }
    if (fragment != null) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        fragmentManager.beginTransaction()
                .replace(R.id.container_body, fragment)
                .addToBackStack(null)
                .commit();
        getSupportActionBar().setTitle(title);
    }
}

@Override
public void onBackStackChanged() {
    int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount();
    if(mPreviousBackStackCount >= backStackEntryCount) {
        mFragPositionTitleDisplayed.pop();
        if (backStackEntryCount == 0)
            getSupportActionBar().setTitle(R.string.app_name);
        else if (backStackEntryCount > 0) {
            getSupportActionBar().setTitle(mFragPositionTitleDisplayed.peek());
        }
        mPreviousBackStackCount--;
    }
    else{
        mFragPositionTitleDisplayed.push(title_name[position]);
        mPreviousBackStackCount++;
    }

}   

У наведеному коді ми маємо метод displayFragment (). Тут я відображаю фрагмент на основі опції, вибраної з панелі навігації. Позиція змінної відповідає позиції елемента, натиснутого з ListView або RecyclerView у навігаційному ящику. Я відповідно встановлюю заголовок панелі дій за допомогою getSupportActionBar.setTitle (заголовок), де заголовок зберігає відповідну назву заголовка.

Кожного разу, коли ми клацаємо елемент із навігатора, відображається фрагмент, залежно від елемента, натиснутого користувачем. Але з тильної сторони цей фрагмент додається до бекстейка, і метод onBackStachChanged () потрапляє в удар. Що я зробив, це те, що я створив змінну mPreviousBackStackCount і ініціалізував її до 0. Я також створив додатковий стек, який буде зберігати імена заголовків рядка дій. Кожного разу, коли я додаю новий фрагмент до backstack, я додаю відповідне ім'я заголовка до мого створеного стека. На протилежному боці кожного разу, коли я натискаю кнопку "Назад", викликається onBackStackChanged (), і я висуваю останню назву заголовка зі свого стека і встановлюю заголовок на ім'я, отримане методом peek () стека.

Приклад:

Скажімо, наш Android Backstack порожній:

Натисніть Вибір 1 з навігаційної шухляди: викликається onBackStachChanged (), а фрагмент 1 додається до backstack андроїда, backStackEntryCount встановлюється в 1, а Frag1 висувається до мого стека, і розмір mFragPositionTitleDisplayed стає 1.

Натисніть Вибір 2 з навігаційної шухляди: викликається onBackStachChanged (), а фрагмент 2 додається до backstack андроїда, backStackEntryCount встановлюється на 2, а Frag2 пересувається до мого стека, і розмір mFragPositionTitleDisplayed стає 2.

Тепер у нас є 2 елементи як в андроїд-стеку, так і в моєму стеку. При натисканні кнопки повернення onBackStackChanged () викликається і значення backStackEntryCount дорівнює 1. Код вводить частину if і вискакує останній запис із мого стека. Отже, андроїд-бекстейк має лише 1 фрагмент - «Фрагмент 1», а мій стек має лише 1 заголовок - «Фраг1». Тепер я просто виглядаю () заголовок зі свого стека і встановлюю панель дій для цього заголовка.

Запам’ятайте: щоб встановити заголовок дії bat, використовуйте peek (), а не pop (), інакше ваша програма буде аварійно працювати, коли ви відкриєте більше 2 фрагментів і спробуєте повернутися назад, натиснувши кнопку назад.


Це смішна кількість коду (особливо порівняно з прийнятою відповіддю), і ігнорує мої вимоги у питанні, тобто реалізувати це без навантаження типового коду та без необхідності відстежувати показані заголовки.
Крістофер Орр,

Так, ну це ваша проблема, якщо ви не хочете надмірного коду. Вам потрібно десь піти на компроміс. Крім того, єдиною важливою частиною коду є OnBackStackChanged ().
Абхішек

1

Ви можете вирішити за допомогою onKeyDown! У мене бул mainisopen = true <- MainFragment видимий інший Фрагмент mainisopen = false

і ось мій код:

public boolean onKeyDown(int keyCode, KeyEvent event) {
    if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == false) {
        mainisopen = true;
        HomeFrag fragment = new HomeFrag();
        FragmentTransaction fragmentTransaction =
                getSupportFragmentManager().beginTransaction();
        fragmentTransaction.replace(R.id.fragmet_cont, fragment);
        fragmentTransaction.commit();
        navigationView = (NavigationView) findViewById(R.id.nav_view);
        navigationView.getMenu().findItem(R.id.nav_home).setChecked(true);
        navigationView.setNavigationItemSelectedListener(this);
        this.setTitle("Digi - Home"); //Here set the Title back
        return true;
    } else {
        if (keyCode == KeyEvent.KEYCODE_BACK && mainisopen == true) {
            AlertDialog.Builder builder = new AlertDialog.Builder(this);
            builder.setMessage("Wollen sie die App schliessen!");
            builder.setCancelable(true);

            builder.setPositiveButton("Ja!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    System.exit(1);
                }
            });

            builder.setNegativeButton("Nein!", new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                    Toast.makeText(getApplicationContext(), "Applikation wird fortgesetzt", Toast.LENGTH_SHORT).show();
                }
            });

            AlertDialog dialog = builder.create();
            dialog.show();

            return true;
        }
        return super.onKeyDown(keyCode, event);
    }

}

0

Як описано тут, моє рішення - це додавання цього коду до методу MainActivity onCreate (): і зміна заголовка панелі дій

FragmentManager fragmentManager=getSupportFragmentManager();
fragmentManager.addOnBackStackChangedListener(new FragmentManager.OnBackStackChangedListener() {
    @Override
    public void onBackStackChanged() {
        Fragment currentFragment = fragmentManager.findFragmentById(R.id.My_Container_1_ID);
        currentFragment.onResume();
    }
});

та зміна заголовка панелі дій у методі onResume () фрагмента

@Override
public void onResume() {
    super.onResume();
    AppCompatActivity activity = (AppCompatActivity) getActivity();
    ActionBar actionBar = activity.getSupportActionBar();
    if(actionBar!=null) {
        actionBar.setTitle("Fragment Title");
        actionBar.setSubtitle("Subtitle");
    }

}

-3

Щоб оновити заголовок панелі дій на задній панелі, натисніть. Простіше кажучи

getActivity.setTitle ("заголовок")

всередині методу onCreateView.


2
onCreateViewвикликається лише при ... створенні подання. Якщо натиснути кнопку Назад, а попередня активність / фрагмент все ще знаходиться у стеку, цей метод не буде викликаний.
Крістофер Орр,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.