带有导航组件的单一活动:如何处理不同的AppBar /主题

时间:2018-08-21 14:44:02

标签: android-layout android-fragments android-architecture-navigation

自从Google I / O宣布新Navigation Component以来,我就一直在使用它,并且也开始尽可能地接受单一活动。

“单一活动”使我可以在视图之间共享ViewModels,以提供令人赞叹的体验,而且如果我不被迫放弃,我真的不想回到多活动性。

但是有一些障碍:单个活动概念的AppBar /主题(状态栏)。

这是我正在设计的一部分:

AppBar design in different sections

如您所见,操作栏/状态栏的外观有不同的要求。

  1. 这是带有标准操作栏的简单抽屉
  2. 图像在半透明状态栏下的经典细节,向上滚动时应使用CollapsingToolbarLayout变成标准操作栏
  3. 在这种情况下,它是非标准的操作栏,我将其称为“浮动工具栏”,因为它无法完全展开到屏幕上,并且包含一个已经展开的SearchView / EditText
  4. 带有标签的相当标准的AppBar

离开单个活动会引起的问题列表:

  • 不能在活动之间共享ViewModel
  • 要重新使用已经在另一个活动导航图中定义的部件的复杂导航,必须将其复制/移动到专用活动中
  • 后退导航“重建”在活动之间不起作用

如果可能的话,我想避免这些问题,但是你们如何通过导航组件在单一活动中管理这种情况。有想法吗?

4 个答案:

答案 0 :(得分:0)

我也有同样的想法,但是却没有时间做一些实验。因此,这不是解决方案,这是一个实验,我想用另一个视图替换这里的视图,这里是带有包含ImageView的工具栏的工具栏。

因此,我使用“基本活动”模板创建了一个新的应用程序。然后在图形中创建两个目的地,即“首页”和“目的地”。最后,为工具栏创建了另一种布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.Toolbar xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="?actionBarSize">

    <ImageView
        android:id="@+id/image_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:srcCompat="@mipmap/ic_launcher_round" />

</androidx.appcompat.widget.Toolbar>

activity_main.xml具有:

<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    ...
    tools:context=".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </com.google.android.material.appbar.AppBarLayout>

    ...

然后在“活动”中,当然取决于设置,但是假设我要使用工具栏设置支持动作栏:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Toolbar toolbar = findViewById(R.id.toolbar);
    Toolbar toolbar2 = (Toolbar) getLayoutInflater().inflate(R.layout.destination_toolbar, null);

    AppBarLayout appBarLayout = findViewById(R.id.appbar_layout);

    navController = Navigation.findNavController(this, R.id.nav_host_fragment);
    appBarConfiguration = new AppBarConfiguration.Builder(navController.getGraph())
            .build();

    navController.addOnDestinationChangedListener((controller, destination, arguments) -> {
        switch (destination.getId()) {
            case R.id.homeFragment:
                appBarLayout.removeAllViews();
                appBarLayout.addView(toolbar);
                setSupportActionBar(toolbar);
                toolbar.setTitle("Home Fragment");
                NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
                break;
            case R.id.destinationFragment:
                appBarLayout.removeAllViews();
                appBarLayout.addView(toolbar2);
                setSupportActionBar(toolbar2);
                toolbar2.setTitle("");
                NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
                break;
        }
    });
}

因此,这可行,随着目的地的增加和新工具栏/其他视图的添加,它变得有些难看。

P.S。如前所述,这只是一个实验,如果有人有更好的解决方案,请发布新答案。

答案 1 :(得分:0)

免责声明

基于@Rajarshi原始实验,我为此问题提供了可行的解决方案。我不确定是最优雅的,还是有更好的方法。但是经过数小时的研究和调查,这是我找到的最佳解决方案。

解决方案

分别为工具栏充气并存储它们的引用,以便垃圾收集器不会选择它们。 然后将其按需加载到主AppBarLayout中,该自定义项是为navController定义的自定义OnDestinationChangedListener

示例

这是我在Kotlin中编写的示例。

在您的activity.xml布局上,定义一个空的AppBarLayout。

layout / activity.xml

<androidx.coordinatorlayout.widget.CoordinatorLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"

    ...

    <com.google.android.material.appbar.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay" />
    ...

</androidx.coordinatorlayout.widget.CoordinatorLayout>

在单独的布局文件中定义应用程序所需的工具栏。

layout / toolbar_defaul.xml

<com.google.android.material.appbar.MaterialToolbar 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:id="@+id/default_toolbar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:menu="@menu/menu_default"
    app:popupTheme="@style/AppTheme.PopupOverlay" />

layout / toolbar2.xml

<com.google.android.material.appbar.MaterialToolbar 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:id="@+id/toolbar2"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    app:menu="@menu/menu2"
    app:popupTheme="@style/AppTheme.PopupOverlay" />

在您的主要活动(也是唯一活动)中,将与AppBar相关的组件声明为类属性,以使垃圾收集器不会拾取它们。

Activity.kt

class Activity : AppCompatActivity() {

    private lateinit var appBarConfiguration: AppBarConfiguration
    private lateinit var appBarLayout: AppBarLayout

    private lateinit var defaultToolbar: MaterialToolbar
    private lateinit var toolbar2: MaterialToolbar

    ...

最后,在onCreate方法中,为navController定义一个OnDestinationChangedListener。使用它可以按需加载每个工具栏。

Activity.kt

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        setContentView(R.layout.activity_ryvod)

        // Set up AppBar
        appBarLayout = findViewById(R.id.appbar)
        appBarConfiguration = AppBarConfiguration(setOf(R.id.StartFragment))

        defaultToolbar = layoutInflater.inflate(R.layout.toolbar_default, appBarLayout, false) as MaterialToolbar
        toolbar2 = layoutInflater.inflate(R.layout.toolbar2, appBarLayout, false) as MaterialToolbar

        val host =
            supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
                ?: return

        val navController = host.navController
        navController.addOnDestinationChangedListener { _, destination, _ ->
            when (destination.id) {
                R.id.locationPickerFragment -> {
                    appBarLayout.removeAllViews()
                    appBarLayout.addView(toolbar2)
                    setSupportActionBar(toolbar2)

                }
                else -> {
                    appBarLayout.removeAllViews()
                    appBarLayout.addView(defaultToolbar)
                    setSupportActionBar(defaultToolbar)
                }
            }
            setupActionBarWithNavController(navController, appBarConfiguration)
        }
    }

应该可以解决问题

答案 2 :(得分:0)

我前一段时间遇到了这个问题,与您的UX / UI类似:

  1. Sidenav导航抽屉
  2. 带有后退箭头的“正常”应用栏
  3. 半透明的Appbar /状态栏

我的解决方案是针对每种情况使用不同的.xml应用栏,并在每个片段xml中使用<include/>标签:

<include
        android:id="@+id/include"
        layout="@layout/default_toolbar"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

case 1case 2的窗口配置是相同的,但是对于半透明的Appbar,窗口配置已更改,请参见case 3

因此,每次出现/替换片段时,我都必须进行配置更改:

public static void transparentStatusBar(Activity activity, boolean isTransparent, boolean fullscreen) {
    if (isTransparent){
        activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
                View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
        activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

    }else {
        if (fullscreen){
            View decorView = activity.getWindow().getDecorView();
            int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
            decorView.setSystemUiVisibility(uiOptions);
        } else {
            activity.getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
                    | View.SYSTEM_UI_FLAG_VISIBLE);
            activity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);

        }
    }
}

然后在半透明的appbar / status条片段的生命周期中使用此方法:

@Override
public void onResume() {
    super.onResume();
    UtilApp.transparentStatusBar(requireActivity(), true, true);
}

@Override
public void onStop() {
    super.onStop();
    UtilApp.transparentStatusBar(requireActivity(), false, false);
}

答案 3 :(得分:0)

here所述,开发人员文档说

当应用程序中每个目标位置的应用程序栏布局相似时,将顶部应用程序栏添加到活动中的效果很好。但是,如果您的顶部应用程序栏在各个目标之间发生很大变化,请考虑从活动中删除顶部应用程序栏,并在每个目标片段中进行定义。