Navigation
Navigation入门
概念
Navigation:实现应用内页面之间的切换
NavHost:容器,存放每一个页面,同时也是一个控制器
Fragment:碎片
NavController:控制导航的逻辑
NavGraph:工具,设置导航的路线
使用
-
创建两个Fragment:
public class HomeFragment extends Fragment { public HomeFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_home, container, false); } } }
public class DetailFragment extends Fragment { public DetailFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.fragment_detail, container, false); } }
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".DetailFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="365dp" /> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Detail" android:textSize="30sp" app:layout_constraintBottom_toTopOf="@+id/guideline4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline4" /> </androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout>
<?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" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".HomeFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Home" android:textSize="30sp" app:layout_constraintBottom_toTopOf="@+id/guideline2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <androidx.constraintlayout.widget.Guideline android:id="@+id/guideline2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" app:layout_constraintGuide_begin="365dp" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Button" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/guideline2" /> </androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout>
-
在res目录下创建navigation目录
-
然后创建一个Navigation文件,根节点为navigation,中间会提示添加依赖,点击确认就可以了
-
添加我们创建的Fragment
-
进行两个Fragment连接,然后可以设置FragmentActionBar的名称、路线的默认动画等
-
在activity_main.xml中添加NaviHostFragment的View
-
分别在两个Fragemnt中添加方法:
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); Button button = getView().findViewById(R.id.button); button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { //创建NavController对象 NavController controller = Navigation.findNavController(v); //设置路线 controller.navigate(R.id.action_homeFragment_to_detailFragment); } }); }
或者是
@Override public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getView().findViewById(R.id.button2).setOnClickListener(Navigation .createNavigateOnClickListener(R.id.action_detailFragment_to_homeFragment)); }
-
完成6之后就可以进行点击页面切换了,这里我们在MainActivity中来实现进入其他页面左上角的返回按钮
- 这里我们可以在onCreate方法里这样写,这里:
NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() .findFragmentById(R.id.fragmentContainerView); NavController controller = navHostFragment.getNavController(); NavigationUI.setupActionBarWithNavController(this,controller);
- 也可以在onStart方法里:
NavController controller = Navigation .findNavController(this,R.id.fragmentContainerView); NavigationUI.setupActionBarWithNavController(this,controller);
- 注意方法2中的方法不能直接写在onCreate方法内
-
重写onSupportNavigateUp方法:
@Override public boolean onSupportNavigateUp() { NavController controller = Navigation .findNavController(this,R.id.fragmentContainerView); return controller.navigateUp(); }
-
至此就完成了整个Navigation的入门
Navigation信息传递
-
在navigation的配置文件中的可以通过在Fragment界面的Arguments参数添加,类型是键值对进行参数传递,在该Fragment中直接通过如下方法获取值
getArguments().getString("key")
-
同样在navigation的配置文件中的路线中,可以修改Fragment的Arguments值,注意这里需要使用路线来进行跳转:
controller.navigate(R.id.action_homeFragment_to_detailFragment)
如果是通过Fragment的Id来进行跳转的话,跳转后Fragment中Arguments的值是不会变化的。
controller.navigate(R.id.detailFragment);
-
还可以在出发点通过Bundle来进行值传递,
Bundle bundle = new Bundle(); bundle.putString("my_name","Rose");
这里我们进行跳转时,带着这个Bundle就可以进行传递了
controller.navigate(R.id.action_homeFragment_to_detailFragment,bundle);
Navigation的跳转动画
这里我们可以通过自定义动画,然后在跳转路线中进行enter或者exit的动画引用即可
Navigation结合ViewModel使用
-
首先创建一个MyViewModel类继承ViewModel,然后进行变量定义,和方法的添加
public class MyViewModel extends ViewModel { private MutableLiveData<Integer> mNumber; public MutableLiveData<Integer> getNumber() { if (mNumber == null) { mNumber = new MutableLiveData<>(); mNumber.setValue(0); } return mNumber; } public void add(int num) { mNumber.setValue(mNumber.getValue() + num); if (mNumber.getValue() < 0) { mNumber.setValue(0); } else if (mNumber.getValue() >= 10){ mNumber.setValue(10); } } }
-
然后创建两个Fragment,然后修改XML文件的格式
<?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <variable name="viewModel" type="com.ts.navigationviewmodel.MyViewModel" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".HomeFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(viewModel.number)}" android:textSize="30sp" android:gravity="center" app:layout_constraintBottom_toTopOf="@+id/seekBar" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="跳转" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/seekBar" /> <SeekBar android:id="@+id/seekBar" android:layout_width="match_parent" android:layout_height="wrap_content" android:max="10" android:saveEnabled="false" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout> </layout>
<?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <variable name="viewModel" type="com.ts.navigationviewmodel.MyViewModel" /> </data> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".DetailFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/textView3" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@{String.valueOf(viewModel.number)}" android:textSize="30sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.541" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.179" /> <Button android:id="@+id/button2" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="-" android:onClick="@{()->viewModel.add(-1)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toStartOf="@+id/button4" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Button android:id="@+id/button4" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="+" android:onClick="@{()->viewModel.add(1)}" app:layout_constraintBottom_toBottomOf="@+id/button2" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.5" app:layout_constraintStart_toEndOf="@+id/button2" app:layout_constraintTop_toTopOf="@+id/button2" /> <Button android:id="@+id/button5" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="返回" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="0.498" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.743" /> </androidx.constraintlayout.widget.ConstraintLayout> </FrameLayout> </layout>
这里我们只做了一个简单的功能,在Home页面使用SeekBar来变换数据,通过跳转在Detail界面加减数据并显示
public class HomeFragment extends Fragment { public HomeFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment final MyViewModel viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class); FragmentHomeBinding fragmentHomeBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_home,container,false); fragmentHomeBinding.setViewModel(viewModel); fragmentHomeBinding.setLifecycleOwner(requireActivity()); fragmentHomeBinding.button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { NavController controller = Navigation.findNavController(v); controller.navigate(R.id.action_homeFragment_to_detailFragment); } }); //SeekBar初始化 fragmentHomeBinding.seekBar.setProgress(viewModel.getNumber().getValue()); //监听SeekBar的宾欢,并改变界面显示的值 fragmentHomeBinding.seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { viewModel.getNumber().setValue(progress); } @Override public void onStartTrackingTouch(SeekBar seekBar) { } @Override public void onStopTrackingTouch(SeekBar seekBar) { } }); return fragmentHomeBinding.getRoot(); } }
public class DetailFragment extends Fragment { public DetailFragment() { // Required empty public constructor } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { MyViewModel viewModel = new ViewModelProvider(requireActivity(), new ViewModelProvider.NewInstanceFactory()).get(MyViewModel.class); FragmentDetailBinding fragmentDetailBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_detail,container,false); fragmentDetailBinding.setViewModel(viewModel); fragmentDetailBinding.setLifecycleOwner(requireActivity()); fragmentDetailBinding.button5.setOnClickListener(v->{ NavController navController = Navigation.findNavController(v); navController.navigate(R.id.action_detailFragment_to_homeFragment); }); // Inflate the layout for this fragment return fragmentDetailBinding.getRoot(); } }
-
Navigation的使用就不在赘述了,但是注意这里的SeekBar,如果我们跳转界面,并在Detail界面修改值,然后通过系统的返回键来返回时,会出现SeekBar的值还是我们跳转之前的值的情况,这是由于我们返回时,相当于把DetailFragment在栈中给弹出来,系统会把资源恢复为之前的状态,然后触发SeekBar变化的监听,导致值不会变化,这里我们可以通过在Xml文件中修改
seekbar的saveEnable为false即可;还有一个问题是反复跳转的话,我们通过返回键返回会出现反复返回的情况,这与我们的实际不符,我们可以在DetailFragment返回的路线中设置popUpToclusive为True即可。