简介
在Android应用开发中,Fragment
和ViewModel
是两个非常重要的概念,它们分别属于架构组件库的一部分,旨在帮助开发者构建更加模块化、健壮且易维护的应用。
Fragment
Fragment
是Android系统提供的一种可重用的UI组件,它能够作为活动(Activity)的一部分,具有自己的生命周期,并且可以在多个Activity中使用。Fragment的设计初衷是为了支持更灵活的屏幕布局,特别是在需要适配不同屏幕尺寸和方向时。通过组合多个Fragment,开发者可以创建丰富的用户界面,并且每个Fragment都可以独立地处理用户输入、保存状态等,从而提高代码的复用性和模块化。
ViewModel
ViewModel
是Android架构组件库中的一个核心类,用于存储和管理UI相关的数据。它的主要目的是分离视图(View)和数据,使得数据能够在配置变更(如屏幕旋转)时保持,避免了因Activity或Fragment重建而导致的数据丢失问题。ViewModel的生命周期独立于UI控制器(Activity或Fragment),确保了数据的持久性。此外,ViewModel还可以与LiveData
等组件结合使用,实现数据变化的自动通知,简化了UI更新的逻辑。
Fragment与ViewModel的协同工作
在实际开发中,为了实现Fragment的数据持久化和解耦,通常会为Fragment关联一个ViewModel。这样做有以下几个好处:
-
数据共享:如果多个Fragment需要共享数据,可以将这些数据放在一个共享的ViewModel中。这样,即使Fragment被重建,数据仍然保持不变,而且Fragment之间可以直接访问这些共享数据,无需通过Activity传递。
-
生命周期解耦:ViewModel不依赖于UI组件的生命周期,因此即使Fragment销毁并重新创建(比如由于配置变更),ViewModel仍然存在,保证了数据的连续性。
-
简化数据管理:ViewModel负责数据的获取、存储和处理,而Fragment专注于展示数据和处理用户交互,这使得代码结构更加清晰,易于维护。
一、开启绑定Binding
Step 1: 打开build.gradle
(Module级别)文件。
Step 2: 在android
闭包内,确保buildFeatures
块存在,然后添加viewBinding
属性并设为true
。
buildFeatures:
android {
...
buildFeatures {
viewBinding = true // 注意,新版一定要有=
}
}
- 这是启用ViewBinding的推荐方式,特别是在较新的Android Gradle插件版本中。
buildFeatures
是一个集合了各种构建特性的开关,通过在这里设置viewBinding
为true
,你告诉Gradle在构建时生成ViewBinding类。这些类让你能够以类型安全的方式访问XML布局中的视图,无需手动调用findViewById
。
dataBinding:
android {
...
dataBinding {
enabled = true // 注意,新版一定要有=
}
}
- 类似地,这是启用DataBinding的方式。通过在
dataBinding
块内设置enabled
为true
,你激活了DataBinding特性。DataBinding比ViewBinding更进一步,提供了数据和视图之间的双向绑定能力,允许在布局文件中直接使用数据对象,并支持表达式来处理数据变化,实现更复杂的UI逻辑。
viewBinding:
android {
...
viewBinding {
enabled = true // 注意,新版一定要有=
}
}
正确的配置应该遵循上述第一条提到的buildFeatures { viewBinding = true }
。实际上推荐使用buildFeatures
块来配置ViewBinding。选择哪种绑定技术取决于你的项目需求:简单视图绑定用ViewBinding,需要更复杂数据逻辑处理则使用DataBinding。
二、加载布局
ActivityMain:
// 定义MainActivity类,继承自AppCompatActivity,这是Android提供的一个Activity基类,用于兼容旧版设备
public class MainActivity extends AppCompatActivity {
// 声明一个私有成员变量binding,类型为ActivityMainBinding,用于存储由Data Binding生成的绑定对象
private ActivityMainBinding binding;
// 重写onCreate()方法,这是Activity生命周期的第一个回调方法,用于初始化Activity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); // 调用父类的onCreate()方法,执行基本的初始化工作
// 使用Data Binding的inflate方法从XML布局文件创建一个绑定对象
// getLayoutInflater()返回LayoutInflater实例,用于将XML布局转换为View对象
// ActivityMainBinding.inflate()方法将布局文件转换为ActivityMainBinding对象
binding = ActivityMainBinding.inflate(getLayoutInflater());
// 设置Activity的内容视图,即将绑定对象的根视图设置为Activity的布局
// binding.getRoot()返回inflate生成的View对象,即整个布局的根View
setContentView(binding.getRoot());
}
}
在这个过程中,以下步骤发生:
-
声明Binding对象:在
MainActivity
类中声明了一个ActivityMainBinding
类型的私有变量binding
,ActivityMainBinding
是Data Binding自动生成的类,用于封装和管理XML布局文件中的所有视图。 -
加载布局:在
onCreate()
方法中,首先调用super.onCreate()
来执行父类的初始化过程。然后使用ActivityMainBinding.inflate()
方法加载布局文件,getLayoutInflater()
提供了创建布局的能力。 -
设置内容视图:调用
setContentView()
方法,并传入binding.getRoot()
返回的根视图,将该视图设置为MainActivity
的布局视图。这意味着MainActivity
的界面将按照ActivityMainBinding
所绑定的XML布局文件来渲染。
通过使用Data Binding,开发者可以直接通过binding
对象访问布局文件中的所有视图,而无需调用findViewById()
方法,这使得代码更加简洁、可读性更强,同时也避免了一些常见的错误,如空指针异常
ViewModel:
// 定义一个继承自ViewModel的类,用于存储界面相关的数据,保证数据在配置变化时不会丢失
public class SyFragmentViewModel extends ViewModel {
// 声明一个私有成员变量mText,类型为MediatorLiveData<String>,用于存储和分发字符串数据
private MediatorLiveData<String> mText;
// 构造函数,初始化mText并设置其初始值
public SyFragmentViewModel() {
// 创建并初始化MediatorLiveData实例
mText = new MediatorLiveData<>();
// 设置mText的初始值
mText.setValue("第一个页面");
}
// 公共方法,返回mText,允许外部组件观察mText的数据变化
public LiveData<String> getText() {
return mText;
}
}
通过上述代码,SyFragmentViewModel
可以被Fragment
或Activity
使用,以观察和响应数据变化,从而实现实时更新UI的效果。
Fragment:
// 定义SyFragment类,继承自Fragment,这是Android中用于构建可重用UI块的类。
public class SyFragment extends Fragment {
// 声明一个私有成员变量binding,类型为SyActivityBinding。这是Data Binding自动生成的类,
// 它包含了对SyFragment所使用的XML布局文件中所有View的引用。
private SyActivityBinding binding;
// 重写onCreateView()方法,这是Fragment生命周期的一部分,用于创建并返回Fragment的用户界面视图。
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
// 使用Data Binding的inflate方法从XML布局文件创建一个绑定对象。
// 第一个参数是LayoutInflater,用于将XML布局转换为View对象;
// 第二个参数是ViewGroup,表示inflate出的View是否应立即附加到该ViewGroup;
// 第三个参数是一个布尔值,如果为true,inflate出的View将附加到container,否则不会。
// 这里inflate方法会根据XML布局文件生成相应的View对象,并将这些View对象封装进binding对象中。
binding = SyActivityBinding.inflate(inflater, container, false);
// 获取inflate生成的View对象,即整个布局的根View,以便返回给onCreateView()方法。
View root = binding.getRoot();
// 从binding中获取TextView的引用,这一步利用了Data Binding的便利性,可以直接通过属性名访问View。
final TextView textView = binding.textView;
// 创建并获取SyFragmentViewModel的实例。ViewModelProvider是一个工具类,用于创建和管理ViewModel实例。
// 这里的this参数告诉ViewModelProvider当前Fragment需要哪个ViewModel。
SyFragmentViewModel syFragmentViewModel = new ViewModelProvider(this).get(SyFragmentViewModel.class);
// 观察SyFragmentViewModel中getText()返回的LiveData对象。
// observe()方法用于注册观察者,getViewLifecycleOwner()确保观察者只在Fragment可见时生效。
// textView::setText是一种方法引用,表示当LiveData数据改变时,自动调用TextView的setText()方法更新UI。
syFragmentViewModel.getText().observe(getViewLifecycleOwner(), textView::setText);
// 返回inflate生成的根View,这将是Fragment的用户界面。
return root;
}
// 重写onDestroy()方法,这是Fragment生命周期的一部分,当Fragment不可见时调用。
// 这里设置binding为null,有助于回收资源,防止内存泄漏。
@Override
public void onDestroy() {
super.onDestroy();
binding = null;
}
}
这段代码展示了如何在一个Fragment中使用Data Binding和ViewModel来构建UI,并响应数据变化。通过使用Data Binding,我们可以更简洁地访问布局中的View;通过ViewModel和LiveData,我们可以在数据变化时自动更新UI,同时保证数据在配置变更时的持久性。
ViewGroup:
ViewGroup
是一个非常重要的概念,它是View
体系结构中的基础组件之一,负责组织和管理子View
(包括其他ViewGroup
)。简单来说,ViewGroup
就是一种特殊的View
,它不仅自己可以显示内容,还可以包含多个子View
,并且能够控制这些子View
的布局方式。
在SyFragment
的onCreateView()
方法中,ViewGroup
主要体现在inflater.inflate()
方法的第二个参数——container
。这里的container
实际上就是一个ViewGroup
,它是指定用于容纳由LayoutInflater
从XML布局文件中解析出来的View组件的父容器。
当你调用SyActivityBinding.inflate(inflater, container, false)
时:
inflater
是LayoutInflater
的实例,它负责读取XML布局文件,并将其转换为实际的View
对象。container
是ViewGroup
的实例,代表了onCreateView()
方法中返回的View
将要被添加到的父容器。通常情况下,container
是Fragment
将要附加到的Activity
的主布局。false
作为第三个参数,意味着从XML布局文件中inflate出来的View
不会立即被添加到container
中。这是因为Fragment
的View
应该由FragmentManager
来管理,而不是直接由ViewGroup
来管理。FragmentManager
会在适当的时机将View
添加到container
中。
所以,在这个特定的上下文中,ViewGroup
的作用主要是作为Fragment
视图层次结构的一部分,为Fragment
的布局提供一个容器。当Fragment
变得可见时,其视图将被FragmentManager
添加到指定的ViewGroup
(即container
)中。
这里,SyActivityBinding.inflate()
方法通过LayoutInflater
和ViewGroup
(container
),实现了从XML布局文件到View
对象的转换,并通过Data Binding
的方式将这些View
对象封装进SyActivityBinding
对象中,便于后续的代码访问和操作。
三、布局加载比较
binding = ActivityMainBinding.inflate(getLayoutInflater());
和 binding = YourActivityBinding.inflate(inflater, container, false);
都使用了 Android 的 Data Binding 库来从 XML 布局文件生成对应的 Java 对象,但它们之间存在一些关键区别,主要在于 inflate 方法的调用方式和参数上。
第一种情况:
binding = ActivityMainBinding.inflate(getLayoutInflater());
这里使用的是 ActivityMainBinding
类的 inflate
方法,这个方法不需要额外的容器参数。它直接使用 getLayoutInflater()
来获取 LayoutInflater 实例,然后调用 inflate
方法生成布局。这种情况下,inflate
方法会自动找到一个合适的根视图,并且返回一个 ActivityMainBinding
类型的对象,该对象包含了布局中的所有 View。
第二种情况:
binding = YourActivityBinding.inflate(inflater, container, false);
这个版本的 inflate
方法接收三个参数:
inflater
: 这是一个LayoutInflater
实例,通常从父视图或者 Activity 中获取。container
: 这是一个可选的父视图容器,如果你想要将这个布局添加到某个已存在的 View 组中时,你需要提供这个容器。如果布局是要作为独立的视图,则可以忽略此参数。attachToRoot
: 这个布尔值参数决定了是否将生成的布局视图自动添加到container
参数指定的容器中。如果设置为false
,则不会自动添加;如果设置为true
,则会自动添加到container
中。
总结:
- 如果你的布局是要直接设置为 Activity 的根布局,通常使用第一种方法,因为不需要考虑容器问题。
- 如果你的布局是要作为子布局添加到某个容器中(比如在 Fragment 或者自定义 View 中),那么你应该使用第二种方法,并且要确保
attachToRoot
参数设置正确,以便于控制布局是否应该被自动添加到容器中。
在大多数情况下,Activity
的布局会直接设置为 Activity 的根视图,因此第一种情况更为常见。然而,在更复杂的场景下,例如在 Fragment 中使用 Data Binding,第二种情况则更为适用。
四、navigation
navigation创建
布局加载
代码完善:
五、menu菜单
menu创建:
Vector图标:
new创建:
颜色、名称等设定:
finsh完成:
添加item项:
代码完善:
切记一定要与navigation的xml代码中fragment的id一致
六、导航实现
BottomNavigationView:
fragment:
一定要将NavHostFragment改fragment
activity中代码实现:
public class MainActivity extends AppCompatActivity {
// 定义一个 ActivityMainBinding 类型的成员变量 binding,
// ActivityMainBinding 是由 Data Binding 自动生成的类,用于绑定 XML 布局文件中的元素到 Java 代码。
private ActivityMainBinding binding;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
// 调用父类的 onCreate 方法,这是每个 Activity 的 onCreate 方法都应该做的。
super.onCreate(savedInstanceState);
// 使用 Data Binding 的 inflate 方法从 XML 文件加载布局。
// getLayoutInflater() 返回 LayoutInflater 实例,用于加载布局。
// ActivityMainBinding.inflate 方法会解析 res/layout/activity_main.xml 文件,
// 并返回一个 ActivityMainBinding 实例,其中包含布局文件中的所有视图组件。
binding = ActivityMainBinding.inflate(getLayoutInflater());
// 设置 Activity 的内容视图。binding.getRoot() 方法返回布局文件中的根视图。
setContentView(binding.getRoot());
// 通过 Data Binding 访问 BottomNavigationView 视图,其 ID 在 activity_main.xml 文件中定义。
BottomNavigationView bottomNavigationView = binding.BottomNavi;
// 创建 NavController 实例,用于管理应用中的导航。
// Navigation.findNavController 方法需要传入一个 Context 和一个 View 的 ID,
// 这里使用 R.id.fragmentContainerView 表示要查找的 NavController 管理的 Fragment 容器。
NavController navController = Navigation.findNavController(this, R.id.fragmentContainerView);
// 创建 AppBarConfiguration 实例,用于配置 App Bar 的行为,这里使用默认配置。
// AppBarConfiguration 的构造函数可以接收多个参数来配置不同的行为,
// 但在这个例子中,使用了默认的构造函数,没有进行任何配置。
AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder().build();
// 使用 NavigationUI.setupActionBarWithNavController 方法设置 ActionBar 与 NavController 的关联,
// 这样 ActionBar 就可以根据 NavController 的状态显示相应的标题和导航项。
// 第一个参数是当前 Activity,第二个参数是 NavController,第三个参数是 AppBarConfiguration。
NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration);
// 使用 NavigationUI.setupWithNavController 方法设置 BottomNavigationView 与 NavController 的关联,
// 这样 BottomNavigationView 就可以响应 NavController 的变化,显示正确的菜单项。
// 第一个参数是 BottomNavigationView,第二个参数是 NavController。
NavigationUI.setupWithNavController(bottomNavigationView, navController);
}
}