我正在尝试实现Navigation Architecture Component附带的新引入的Jetpack。到目前为止,它对于管理应用程序的导航流程非常有用,而且非常有用。
我已经使用MainActivity中的工具栏设置了包括抽屉布局在内的基本导航,如下所示:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val navController = Navigation.findNavController(this, R.id.mainNavFragment)
// Set up ActionBar
setSupportActionBar(toolbar)
NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
// Set up navigation menu
navigationView.setupWithNavController(navController)
}
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(Navigation.findNavController(this, R.id.mainNavFragment), drawerLayout)
}
}
采用这种布局:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:popupTheme="@style/AppTheme.PopupOverlay"
app:navigationIcon="@drawable/ic_home_black_24dp"/>
</android.support.design.widget.AppBarLayout>
<fragment
android:id="@+id/mainNavFragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/nav_main"/>
</LinearLayout>
它可以工作在鳍上。但是,真正的问题是,何时为应用提供定制设计,
如何设置汉堡包的自定义图标或后退图标?
到现在为止,由NavigatoinController本身处理。
我已经尝试过下面的选项,但是不起作用:
app:navigationIcon="@drawable/ic_home_black_24dp" //1
supportActionBar.setHomeAsUpIndicator(R.drawable.ic_android_black_24dp) //2
谢谢!
答案 0 :(得分:6)
导航组件使用DrawerArrowDrawable作为基础实现,该实现实际上在汉堡包图标和后退箭头图标之间进行动画处理。可以通过样式在一定程度上修改两个图标的外观,请参见
How to style the DrawerArrowToggle from Android appcompat v7 21 library
对于我们的应用,我们使用以下属性来获取细长的箭头:
public class MyProcess
{
private Process _process;
public event EventHandler OnFinish;
public MyProcess(string pathToExe)
{
_process = new Process();
_process.StartInfo.FileName = pathToExe;
}
public int Start()
{
_process.Exited += _processExited;
_process.Start();
return _process.Id;
}
public void Stop()
{
_process.Stop();
}
private void _processExited(object sender, EventArgs e)
{
OnFinish?.Invoke();
}
}
public class MyProgram
{
private static int stoppedProcs = 0;
public static void Main(string[] args)
{
var proc = new MyProcess("foo.exe");
proc.OnFinish += proc_OnFinish;
proc.Start();
proc.Stop();
//Wait here to display 1 instead of 0
Console.WriteLine(stoppedProcs);
}
private static void proc_OnFinish(object sender, EventArgs e)
{
stoppedProcs++;
}
}
必须在应用的主题中设置此样式,如下所示:
<style name="ToolbarArrow" parent="Widget.AppCompat.DrawerArrowToggle">
<item name="color">@color/primary</item>
<item name="drawableSize">32dp</item>
<item name="arrowShaftLength">22dp</item>
<item name="thickness">1.7dp</item>
<item name="arrowHeadLength">8dp</item>
</style>
答案 1 :(得分:2)
导航版本1.0.0-alpha08
遇到相同的问题。我已经通过重新实现setupActionBarWithNavController来提供我需要的自定义行为来解决它。
setDisplayShowTitleEnabled(false) //Disable the default title
container.findNavController().addOnDestinationChangedListener { _, destination: NavDestination, _ ->
//Set the toolbar text (I've placed a TextView within the AppBar, which is being referenced here)
toolbar_text.text = destination.label
//Set home icon
supportActionBar?.apply {
setDisplayHomeAsUpEnabled((destination.id in NO_HOME_ICON_FRAGMENTS).not())
setHomeAsUpIndicator(if (destination.id in TOP_LEVEL_FRAGMENTS) R.drawable.app_close_ic else 0)
}
}
您需要做的就是将一个onDestinationChangedListener添加到导航控制器,并为每个目的地设置标题和图标。
此代码应放置在包含导航图的活动的onCreate()方法内,并且NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
应该从代码中删除。
请注意,这将删除导航抽屉的默认功能。
我希望这会有所帮助!
答案 2 :(得分:2)
由于样式AppTheme将Widget.AppCompat.DrawerArrowToggle样式用于navigationUI汉堡包和后退按钮图标,因此您需要通过继承Widget.AppCompat.DrawerArrowToggle来创建自定义样式。
我将尽力所有您可能拥有的选项以及每个选项的作用:
<style name="CustomNavigationIcons" parent="Widget.AppCompat.DrawerArrowToggle">
<!-- The drawing color for the bars -->
<item name="color">@color/colorGrey</item>
<!-- The total size of the drawable -->
<item name="drawableSize">64dp</item>
<!-- The thickness (stroke size) for the bar paint -->
<item name="thickness">3dp</item>
<!-- Whether bars should rotate or not during transition -->
<item name="spinBars">true</item>
<!-- The max gap between the bars when they are parallel to each other -->
<item name="gapBetweenBars">4dp</item>
<!-- The length of the bars when they are parallel to each other -->
<item name="barLength">27dp</item>
<!-- The length of the shaft when formed to make an arrow -->
<item name="arrowShaftLength">23dp</item>
<!-- The length of the arrow head when formed to make an arrow -->
<item name="arrowHeadLength">7dp</item>
</style>
最后,您必须确保AppTheme现在使用自定义样式作为DrawerArrowToggle,如下所示:
<style name="AppTheme" parent="Theme.MaterialComponents.Light.DarkActionBar">
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<!-- Other styles -->
<item name="drawerArrowStyle">@style/CustomNavigationIcons</item>
</style>
答案 3 :(得分:2)
实际上,我不知道这是 Google 的功能还是错误。
在给出我更改自定义后退图标的解决方案之前。让我们看看 Google 是如何实施的。
一切从我们调用开始:
val navController = findNavController(R.id.nav_host_fragment)
appBarConfiguration = AppBarConfiguration(
setOf(
R.id.homeFragment,
R.id.categoryFragment,
R.id.cartFragment,
R.id.myAccountFragment,
R.id.moreFragment
),
)
toolbar.setupWithNavController(navController, appBarConfiguration)
setupWithNavController() 是 Toolbar 的扩展。
fun Toolbar.setupWithNavController(
navController: NavController,
configuration: AppBarConfiguration = AppBarConfiguration(navController.graph)
) {
NavigationUI.setupWithNavController(this, navController, configuration)
}
在NavigationUI中,你可以看到Google只是监听目的地的变化,并在用户点击后退按钮时设置一个点击事件。
public static void setupWithNavController(@NonNull Toolbar toolbar,
@NonNull final NavController navController,
@NonNull final AppBarConfiguration configuration) {
navController.addOnDestinationChangedListener(
new ToolbarOnDestinationChangedListener(toolbar, configuration));
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
navigateUp(navController, configuration);
}
});
}
再详细一点,你可以看到这个函数:
private void setActionBarUpIndicator(boolean showAsDrawerIndicator) {
boolean animate = true;
if (mArrowDrawable == null) {
mArrowDrawable = new DrawerArrowDrawable(mContext);
// We're setting the initial state, so skip the animation
animate = false;
}
setNavigationIcon(mArrowDrawable, showAsDrawerIndicator
? R.string.nav_app_bar_open_drawer_description
: R.string.nav_app_bar_navigate_up_description);
float endValue = showAsDrawerIndicator ? 0f : 1f;
if (animate) {
float startValue = mArrowDrawable.getProgress();
if (mAnimator != null) {
mAnimator.cancel();
}
mAnimator = ObjectAnimator.ofFloat(mArrowDrawable, "progress",
startValue, endValue);
mAnimator.start();
} else {
mArrowDrawable.setProgress(endValue);
}
}
这里是 setNavigationIcon:
@Override
protected void setNavigationIcon(Drawable icon,
@StringRes int contentDescription) {
Toolbar toolbar = mToolbarWeakReference.get();
if (toolbar != null) {
boolean useTransition = icon == null && toolbar.getNavigationIcon() != null;
toolbar.setNavigationIcon(icon);
toolbar.setNavigationContentDescription(contentDescription);
if (useTransition) {
TransitionManager.beginDelayedTransition(toolbar);
}
}
}
我刚刚看到每次目标更改时,目标不是根页面。谷歌创建了一个 DrawerArrowDrawable 对象,然后谷歌将这个图标设置为导航图标。我认为这就是工具栏不显示我们自定义图标的原因。
在这一点上,我认为我们有一些解决方案来处理它。 一种简单的解决方案是在 Activity 的 onCreate() 方法中添加 addOnDestinationChangedListener() 方法,每当用户更改到新页面(目的地)时,我们将检查并决定是否显示自定义后退图标。像这样:
val navController = findNavController(R.id.nav_host_fragment)
navController.addOnDestinationChangedListener { _, destination, _ ->
(appBarConfiguration.topLevelDestinations.contains(destination.id)) {
toolbar.navigationIcon = null
} else {
toolbar.setNavigationIcon(R.drawable.ic_arrow_back_white)
}
}
答案 4 :(得分:1)
使用导航版本1.0.0-alpha07
时遇到相同的问题。我发现以下解决方法:
在我的活动中,我将工具栏视图都设置为支持操作栏并使用导航控制器:
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
toolbar.setupWithNavController(navController, appBarConfiguration)
然后在我要替换箭头图标的片段的onViewCreated
中,设置图标并将其设置为启用状态(可能在onAttached
中进行此操作):
(requireActivity() as AppCompatActivity).supportActionBar?.apply {
setHomeAsUpIndicator(R.drawable.ic_close_24dp)
setDisplayHomeAsUpEnabled(true)
}
当我这样做时,导航组件似乎仍然可以正确处理后退堆栈,动画,并删除起始片段上的图标。希望在离开Alpha之前,导航组件可以支持自定义图标,而不必诉诸旧式操作栏API。
答案 5 :(得分:1)
我遇到了同样的问题。我只是重写了SDK中的某些类:
class ToolbarOnDestinationChangedListener(toolbar: Toolbar) : OnDestinationChangedListener {
private val toolbarWeakReference = WeakReference(toolbar)
override fun onDestinationChanged(
controller: NavController,
destination: NavDestination,
arguments: Bundle?
) {
if (toolbarWeakReference.get() == null) {
controller.removeOnDestinationChangedListener(this)
return
}
if (destination is FloatingWindow) {
return
}
destination.label?.let {
val title = pickTitleFromArgs(it, arguments)
setTitle(title)
}
}
private fun pickTitleFromArgs(label: CharSequence, arguments: Bundle?): StringBuffer {
val title = StringBuffer()
val matcher = Pattern.compile("\\{(.+?)\\}").matcher(label)
while (matcher.find()) {
val argName = matcher.group(1)
if (arguments != null && arguments.containsKey(argName)) {
matcher.appendReplacement(title, "")
title.append(arguments[argName].toString())
} else {
error("Could not find $argName in $arguments to fill label $label")
}
}
matcher.appendTail(title)
return title
}
private fun setTitle(title: CharSequence) {
toolbarWeakReference.get()?.let {
it.title = title
}
}
}
以及以下扩展名:
fun Toolbar.setNavController(navController: NavController) {
navController.addOnDestinationChangedListener(ToolbarOnDestinationChangedListener(this))
toolbar.setNavigationOnClickListener { navController.navigateUp() }
}
使用:
//Fragment
toolbar.setNavController(findNavController())
此代码不控制图标。但它会考虑标签,参数,目标更改。您应该使用XML设置图标。
答案 6 :(得分:0)
我最近也遇到了同样的问题。我想将默认设置与NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout)
一起使用,也不想在每个片段中都编写逻辑。这就是在项目中具有导航组件的用例。在挖掘导航组件的源代码之后,我想出了以下解决方案:
findNavController(R.id.nav_host_fragment)
.addOnDestinationChangedListener { _, destination, _ ->
when (destination.id) {
R.id.nav_home,
R.id.nav_gallery -> {
drawerToggleDelegate!!
.setActionBarUpIndicator(
ContextCompat.getDrawable(
this,
R.drawable.custom_hamburger
),
androidx.navigation.ui.R.string.nav_app_bar_open_drawer_description
)
}
else -> {
drawerToggleDelegate!!
.setActionBarUpIndicator(
ContextCompat.getDrawable(
this,
R.drawable.custom_back
),
androidx.navigation.ui.R.string.nav_app_bar_navigate_up_description
)
}
}
}
我已将DestinationChangedListener
添加到navController
并使用了drawerToggleDelegate
的{{1}}属性来更改图标。这里AppCompatActivity
和R.id.nav_home
是我的顶级目的地;为此,将显示自定义汉堡包,对于其他汉堡,将显示自定义后退图标。
这种方法的唯一缺点是您将丢失默认动画。
但是您可以在此处轻松实现逻辑以获得动画效果。
答案 7 :(得分:0)
我不知道什么时候可以这样做,但我刚刚发现,您可以在调用 toolbar.setNavigationIcon(R.drawable.your_icon)
后调用 toolbar.setupWithNavController(findNavController())
。这将删除默认的 DrawerArrowDrawable 并立即用您的自定义 drawable 覆盖它。无需重写任何 sdk 代码或做一些疯狂的其他事情
fun Fragment.initToolbarWithCustomDrawable(@DrawableRes resId: Int) {
val toolbar = requireView().findViewById<MaterialToolbar>(R.id.your_toolbar)
with(toolbar) {
setupWithNavController(findNavController(), AppBarConfiguration(findNavController().graph))
setNavigationIcon(resId)
}
}
fun Fragment.initToolbarWithCustomDrawable(
toolbar: Toolbar
@DrawableRes resId: Int
) {
with(toolbar) {
setupWithNavController(findNavController(), AppBarConfiguration(findNavController().graph))
setNavigationIcon(resId)
}
}