Android导航组件后退按钮不起作用

时间:2020-01-21 04:58:13

标签: android android-fragments android-navigation

我正在使用android中的导航组件,其中最初设置了6个片段。问题是当我添加新片段(ProfileFragment)时。

当我从起始位置导航到此新片段时,按本机后退按钮不会弹出当前片段。相反,它只是停留在我所在的片段上-后退按钮什么也不做。

这是我的 navigation.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/dashboard_navigation"
    app:startDestination="@id/dashboardFragment"
    >

                <fragment
                    android:id="@+id/dashboardFragment"
                    android:name="com.devssocial.localodge.ui.dashboard.ui.DashboardFragment"
                    android:label="DashboardFragment"
                    >
                                <action
                                    android:id="@+id/action_dashboardFragment_to_newPostFragment"
                                    app:destination="@id/newPostFragment"
                                    app:enterAnim="@anim/slide_in_up"
                                    app:exitAnim="@anim/slide_out_down"
                                    app:popEnterAnim="@anim/slide_in_up"
                                    app:popExitAnim="@anim/slide_out_down"
                                    />
                                <action
                                    android:id="@+id/action_dashboardFragment_to_notificationsFragment"
                                    app:destination="@id/notificationsFragment"
                                    app:enterAnim="@anim/slide_in_up"
                                    app:exitAnim="@anim/slide_out_down"
                                    app:popEnterAnim="@anim/slide_in_up"
                                    app:popExitAnim="@anim/slide_out_down"
                                    />
                                <action
                                    android:id="@+id/action_dashboardFragment_to_mediaViewer"
                                    app:destination="@id/mediaViewer"
                                    app:enterAnim="@anim/slide_in_up"
                                    app:exitAnim="@anim/slide_out_down"
                                    app:popEnterAnim="@anim/slide_in_up"
                                    app:popExitAnim="@anim/slide_out_down"
                                    />
                                <action
                                    android:id="@+id/action_dashboardFragment_to_postDetailFragment"
                                    app:destination="@id/postDetailFragment"
                                    app:enterAnim="@anim/slide_in_up"
                                    app:exitAnim="@anim/slide_out_down"
                                    app:popEnterAnim="@anim/slide_in_up"
                                    app:popExitAnim="@anim/slide_out_down"
                                    />

                            ====================== HERE'S THE PROFILE ACTION ====================                                
                                <action
                                    android:id="@+id/action_dashboardFragment_to_profileFragment"
                                    app:destination="@id/profileFragment"
                                    app:enterAnim="@anim/slide_in_up"
                                    app:exitAnim="@anim/slide_out_down"
                                    app:popEnterAnim="@anim/slide_in_up"
                                    app:popExitAnim="@anim/slide_out_down"
                                    />
                            =====================================================================                                

                </fragment>



                <fragment
                    android:id="@+id/profileFragment"
                    android:name="com.devssocial.localodge.ui.profile.ui.ProfileFragment"
                    android:label="fragment_profile"
                    tools:layout="@layout/fragment_profile"
                    />
</navigation>

enter image description here

在上图中,突出显示的箭头(在左侧)是我遇到麻烦的导航操作。

在我的 Fragment 代码中,我的导航方式如下:

findNavController().navigate(R.id.action_dashboardFragment_to_profileFragment)

其他导航操作正在按预期方式工作。但是由于某种原因,这个新添加的片段无法达到预期的效果。

当我导航到ProfileFragment以及按返回按钮时,没有日志显示。

我错过了什么吗?还是我的动作/片段配置有问题?

编辑: 我没有在 ProfileFragment 中执行任何操作。这是它的代码:

class ProfileFragment : Fragment() {

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_profile, container, false)
    }


}

还有包含导航主机的活动xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
        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="match_parent">

    <fragment
            android:id="@+id/dashboard_navigation"
            app:layout_behavior="@string/appbar_scrolling_view_behavior"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:navGraph="@navigation/dashboard_navigation"
            app:defaultNavHost="true"/>

</FrameLayout>

9 个答案:

答案 0 :(得分:16)

对于在上一个片段(即家庭片段)中使用LiveData的任何人,只要您通过按“后退”按钮返回到上一个片段,片段就开始观察数据,并且由于ViewModel幸免于此操作,它会立即发出最后一个发出的值在我的情况下,这打开了我按下后退按钮的片段,因此后退按钮似乎不起作用,解决方案是使用仅发射一次数据的东西。我用了这个:

class SingleLiveData<T> : MutableLiveData<T>() {

private val pending = AtomicBoolean()

/**
 * Adds the given observer to the observers list within the lifespan of the given
 * owner. The events are dispatched on the main thread. If LiveData already has data
 * set, it will be delivered to the observer.
 *
 * @param owner The LifecycleOwner which controls the observer
 * @param observer The observer that will receive the events
 * @see MutableLiveData.observe
 */
@MainThread
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
    super.observe(owner, Observer { t ->
        if (pending.compareAndSet(true, false)) {
            observer.onChanged(t)
        }
    })
}

/**
 * Sets the value. If there are active observers, the value will be dispatched to them.
 *
 * @param value The new value
 * @see MutableLiveData.setValue
 */
@MainThread
override fun setValue(value: T?) {
    pending.set(true)
    super.setValue(value)
}

答案 1 :(得分:11)

如果要在导航组件中使用setupActionBarWithNavController,例如:

 setupActionBarWithNavController(findNavController(R.id.fragment))

然后还在您的主要活动中覆盖并配置此方法:

 override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.fragment)
    return navController.navigateUp() || super.onSupportNavigateUp()
}

我的MainActivity.kt

class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)

    setupActionBarWithNavController(findNavController(R.id.fragment))
}

override fun onSupportNavigateUp(): Boolean {
    val navController = findNavController(R.id.fragment)
    return navController.navigateUp() || super.onSupportNavigateUp()
}
}

答案 2 :(得分:6)

您可以将以下内容用于Activity

onBackPressedDispatcher.addCallback(
                this,
                object : OnBackPressedCallback(true) {
                    override fun handleOnBackPressed() {
                        onBackPressed()
                        // if you want onBackPressed() to be called as normal afterwards

                    }
                }
        )

对于fragment,将需要requireActivity()和回叫

requireActivity().onBackPressedDispatcher.addCallback(
                    this,
                    object : OnBackPressedCallback(true) {
                        override fun handleOnBackPressed() {
                            requireActivity().onBackPressed()
                            // if you want onBackPressed() to be called as normal afterwards

                        }
                    }
            )

如果您有Button或其他东西来执行操作,则可以使用

this.findNavController().popBackStack()

答案 3 :(得分:6)

在使用MutableLiveData在片段之间导航时,我发生了这个问题,并在多个片段上观察实时数据对象。

我仅通过一次观察实时数据对象或使用SingleLiveEvent而不是MutableLiveData来解决了这个问题。因此,如果您在此处遇到相同的情况,请尝试仅观察一次实时数据对象或使用SingleLiveEvent

答案 4 :(得分:1)

导航完成后,您需要将MutableLiveData设置为null。

例如

private val _name = MutableLiveData<String>()
val name: LiveData<String>
    get() = _name

fun printName(){
    _name.value = "John"
}
fun navigationComplete(){
    _name.value = null
}

现在说您正在观察片段中的“名称”,并且一旦名称为John,便在进行导航,那么应该是这样的:

        viewModel.name.observe(viewLifecycleOwner, Observer { name ->
        when (name) {
            "John" -> {
                this.findNavController() .navigate(BlaBlaFragmentDirections.actionBlaBlaFragmentToBlaBlaFragment())
                viewModel.navigationComplete()
            }          
        }
    })

现在,您的后退按钮将可以正常工作。

有些数据几乎只使用一次,例如Snackbar消息或导航事件,因此一旦使用完毕,您必须告知将值设置为null。

问题在于_name中的值保持为true,并且无法返回上一个片段。

答案 5 :(得分:0)

如果您使用Moxy或类似的库,请在从一个片段导航到另一个片段时签出该策略。 策略为AddToEndSingleStrategy时,我遇到了同样的问题。 您需要将其更改为SkipStrategy

interface ZonesListView : MvpView {

    @StateStrategyType(SkipStrategy::class)
    fun navigateToChannelsList(zoneId: String, zoneName: String)
}

答案 6 :(得分:0)

在OnCreateView中调用onBackPressed

import JobCard from './JobCard';
import BlocSection from './BlocSection';
import axios from 'axios';
import '../assets/Carousel.scss';
import next from '../assets/img/next.png';
import back from '../assets/img/back.png';



export default class CarouselNew extends Component {

   constructor(props) {
   super(props);
   this.state ={
       jobs : [],
       position : 0
   };
}

  async componentDidMount() {

       try{
               const jobs = await axios.get("API LINK", {responseType : "json"})
               console.log('response : ', jobs)
               this.setState({jobs : jobs.data})

       }catch(e){
           console.log(e)
       }

 }


  changePositionNext(){
       {this.state.position < this.state.jobs.length-3 ? (this.setState({position : this.state.position +3})) 
       : (this.setState({position : 0})) }

}

   changePositionPrev(){
       {this.state.position == 0 ? (this.setState({position : this.state.jobs.length-3})) 
       : (this.setState({position :  this.state.position -3})) }
}





   render(){
       let selected = [];

      selected = this.state.jobs.length >0 ? this.state.jobs.slice(this.state.position, this.state.position +3) : []

       console.log('position : ', this.state.position);
       console.log('selected : ', selected);

       return(
           <div className='main_container' id='job'>
               <BlocSection section_title={"Nos offres d'emploi"} section_intro={'Lorem ipsum dolor sit amet consectetur, adipisicing elit. Sequi alias iste ducimus tenetur saepe reprehenderit quasi reiciendis ab architecto.'} />
               <div className='slider_container'>
                   <div onClick={() => this.changePositionPrev()} className="arrow_container"><img src={back} alt="" className="arrow"/></div>

                   {this.state.position === this.state.jobs.length-2 || this.state.position === this.state.jobs.length-1 && this.state.jobs.length %3 != 0 ? (this.setState({position : this.state.jobs.length-3})) : null}

                   {this.state.position === -2 || this.state.position === -1 && this.state.jobs.length %3 != 0 ? (this.setState({position : 0})) : null}

                   <div className="card_container">

                       {selected.map(job =>{
                           const title = job.title.rendered;
                           const description = job.acf.job_description;
                           const lien = job.acf.job_linkedin_url;
                           console.log('title : ', title);
                           return(
                               <JobCard key={title} title={title} description={description} lien={lien} />     
                               );
                           })}

                   </div>



                   <div onClick={() => this.changePositionNext()} className="arrow_container"><img src={next} alt="" className="arrow"/></div>

               </div>      

           </div>

       )



}
}

答案 7 :(得分:0)

导航后将MutableLiveData设置为false

将此代码放入您的ViewModel.kt

private val _eventNextFragment = MutableLiveData<Boolean>()
    val eventNextFragment: LiveData<Boolean>
        get() = _eventNextFragment

    fun onNextFragment() {
        _eventNextFragment.value = true
    }

    fun onNextFragmentComplete(){
        _eventNextFragment.value = false
    }

假设您要导航到另一个片段,请在导航操作后立即从viewModel调用onNextFragmentComplete方法。

将此代码放入您的Fragment.kt

private fun nextFragment() {
        val action = actionFirstFragmentToSecondFragment()
        NavHostFragment.findNavController(this).navigate(action)
        viewModel.onNextFragmentComplete()
    }

答案 8 :(得分:0)

对于使用 LiveData 设置导航ID的每个人,都无需使用SingleLiveEvent。设置初始值后,您可以将destinationId设置为null。

例如,如果您想从片段A导航到片段B。

ViewModel A:

 "@aws-cdk/core:newStyleStackSynthesis": "true"

这仍然会触发片段中的Observer并进行导航。

片段A

val destinationId = MutableLiveData<Int>()

fun onNavigateToFragmentB(){
    destinationId.value = R.id.fragmentB
    destinationId.value = null
}