17

自从在 Google I/O 上宣布新的导航组件后不久,我就一直在使用它,并且也开始尽可能地接受单一活动。

Single Activity 允许我在视图之间共享 ViewModel 以获得很棒的体验,如果我不是被迫的话,我真的不想回到多 Activity 中。

但是有一些东西妨碍了:AppBar / Themes(状态栏)到单一活动概念。

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

不同部分的AppBar设计

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

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

离开单一活动产生的问题列表:

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

如果可能的话,这些是我想避免的问题,但是你们如何在带有导航组件的单个活动上管理这些情况。任何想法?

4

4 回答 4

1

正如这里提到的,开发者文档说

当应用程序栏的布局对于您应用程序中的每个目标都相似时,将顶部应用程序栏添加到您的 Activity 效果很好。但是,如果您的顶部应用栏在目的地之间发生了很大变化,那么请考虑从您的活动中删除顶部应用栏并在每个目的地片段中定义它。

于 2020-09-18T04:46:40.013 回答
1

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

所以我使用“基本活动”模板创建了一个新的应用程序。然后在图表中创建了两个目的地,家庭和目的地。最后,为 Toolbar创建了另一个布局:

<?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>

    ...

然后在 Activity 中,当然取决于设置,但是假设我想设置一个带有工具栏的支持操作栏:

@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;
        }
    });
}

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

PS正如我之前所说,这只是一个实验,如果有人有更好的解决方案,请发布新答案。

于 2019-03-04T11:54:31.367 回答
0

免责声明

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

解决方案

分别膨胀工具栏并存储它们的引用,这样它们就不会被垃圾收集器拾取。OnDestinationChangedListener然后在为您的 navController 自定义定义的主 AppBarLayout 中按需加载每个

例子

这是我用 Kotlin 编写的示例。

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

布局/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>

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

布局/工具栏_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" />

布局/工具栏2.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 相关组件声明为类属性,以便垃圾收集器不会拾取它们。

活动.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方法中,OnDestinationChangedListener为 navController 定义一个。使用它按需加载每个工具栏。

活动.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)
        }
    }

这应该够了吧

于 2020-01-19T08:47:42.400 回答
0

不久前我遇到了这个问题,与您的 UX/UI 类似:

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

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

<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" />

案例 1案例 2的窗口配置相同,但对于半透明的 Appbar,窗口配置发生了变化,请参见案例 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);

        }
    }
}

然后在半透明应用栏/状态栏片段的生命周期中使用此方法:

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

@Override
public void onStop() {
    super.onStop();
    UtilApp.transparentStatusBar(requireActivity(), false, false);
}
于 2020-03-27T21:35:44.137 回答