【Android Fragment】Fragment基础
1、fragment “分段”、“碎片”的意思,一般与Activity一起使用,嵌套在activity中表示为Activity界面的一部分。2、它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除片段(有点像您可以在不同 Activity 中重复使用的“子 Activity”)。3、当您将片段作为 Activity 布局的一部分添加时,它存在于 Act...
fragment “分段”、“碎片”的意思,一般与Activity一起使用,嵌套在activity中表示为Activity界面的一部分。
它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或移除片段,有点像您可以在不同 Activity 中重复使用的“子 Activity”。
当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 ViewGroup 内部,并且片段会定义其自己的视图布局。
知识点
一、简介
1、Fragment的产生
安卓3.0(api 11)引入了fragment,主要就是为大屏幕(平板)添加更加灵活、动态的支持。
由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 UI 组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。
2、好处
- 有自己的生命周期
- 依附于activity使activity更加灵活
3、生命周期
由于fragment依赖于activity所以其生命周期受activity生命周期影响。fragment的生命周期图如下:
(1)fragment的生命周期图( Activity 运行时)
(2)Activity 生命周期对片段生命周期的影响图
二、使用
要想创建fragment,您必须创建 Fragment 的子类(或已有其子类)。Fragment 类的代码与 Activity 非常相似。它包含与 Activity 类似的回调方法,如 onCreate()、onCreateView()、onStart()、onPause() 和 onStop()。
1、使用步骤
(1)创建类继承Fragment
(2)实现onCreateView方法。
系统会在fragment首次绘制其用户界面时调用此方法。 要想为您的片段绘制 UI,您从此方法中返回的 View 必须是片段布局的根视图。如果片段未提供 UI,您可以返回 null。
(3)使用
- 静态使用:activity的xml布局中引入即可
- 动态调用:通过FragmentManager、FragmentTransaction来进行动态添加、替换、移除、fragment从相应的容器中。
2、静态使用
(1)创建子类 实现核心方法
/**
* LayoutInflater 详情参考:https://blog.csdn.net/u012702547/article/details/52628453
* */
public class BlankFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
/**
* 参数:
* 1、R.layout.fragment_blank 自定义布局的资源id
*
* 2、container 将作为自定义布局的父ViewGroup。是否将自定义布局添加到 container(由第三个参数的boolean值决定)
*
* 3、boolean值
*
* */
return inflater.inflate(R.layout.fragment_blank, container, false);
}
}
(2)MainActivity的xml中使用
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Fragment"/>
<fragment
android:id="@+id/ft"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.sunnyday.fragmentlearn.BlankFragment"/>
</LinearLayout>
注意:这里fragment必须要有id或者tag的否则崩溃(吧上代码中id去掉时)
Caused by: java.lang.IllegalArgumentException: Binary XML file line #16: Must specify unique android:id, android:tag, or have a parent with an id for com.sunnyday.fragmentlearn.BlankFragment
(3)拓展:为啥需要id这样的标识
每个fragment都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复fragment(也可以使用该标识符来获取 fragment 以执行某些事务,比如将其移出),可以通过三种方式为 fragment 提供 ID:
- 通过 android:id 属性提供唯一 ID
- 通过 android:tag 属性提供唯一字符串
- 如果都没设置,系统会使用容器视图的 ID
3、动态使用
(1)如上修改MainActivity的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<TextView
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Hello Fragment"/>
<FrameLayout
android:id="@+id/fl_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
(2)代码动态添加
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获得FragmentManager对象
FragmentManager fragmentManager = getSupportFragmentManager();
// 通过FragmentManager对象获得FragmentTransaction对象(开启事务)
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fl_layout, new BlankFragment());//动态添加
fragmentTransaction.commit();//提交 提交 提交(很重要)
}
}
上例展示了如何向 Activity 添加fragment以提供 UI。不过,您还可以使用fragment为 Activity 提供后台行为,而不展示 UI:
- 要想添加没有 UI 的fragment,请使用 add(Fragment, String) 从 Activity 添加fragment(为fragment提供一个唯一的字符串“标记”,而不是视图 ID)。 这会添加fragment,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 onCreateView() 的调用。因此,您不需要实现该方法。
- 并非只能为非 UI fragment提供字符串标记 , 您也可以为具有 UI 的fragment提供字符串标记 ,但如果fragment没有 UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 findFragmentByTag()。
4、派生子类
系统也提供了fragment 派生的一些子类
三、相关类
1、FragmentManager
对fragment进行相关操作,提供了一些方法:
1、调用findFragmentById 或 findFragmentByTag 获取Activity Fragment
2、调用 popBackStack 将 Fragment 从返回栈中弹出
3、调用addOnBackStackChangedListener() 注册一个侦听返回栈变化的侦听器。
4、调用beginTransaction 获取 FragmentTransaction
2、FragmentTransaction
在 Activity 中使用 Fragment 一大优点是:可以根据用户行为通过它们执行添加、移出、替换fragment以及其操作。
(1)FragmentTransaction常用方法
- add()
- remove()
- replace()
(2)事务注意点
- 进行事务操作之后要进行commit 否则操作不会生效。
- 如果您向事务添加了多个更改(如又一个 add() 或 remove()),并且调用了 addToBackStack(),则在调用 commit() 前应用的所有更改都将作为单一事务添加到返回栈,并且返回按钮会将它们一并撤消。
- 对于每个片段事务,您都可以通过在提交前调用 setTransition() 来应用过渡动画。
- 调用 commit() 不会立即执行事务,而是在 Activity 的 UI 线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 executePendingTransactions(FragmentManager的方法)或者 commitNow(FragmentTransaction的方法) 以立即执行 commit() 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。
- 只能在 Activity 保存其状态之前使用 commit 提交事务,如果试图在该时间之后提交,则会引发异常。这是因为如需恢复 Activity,则提交后的状态可能会丢失。对于丢失提交无关紧要的情况,可以使用 commitAllowingStateLoss
四、Fragment与activity进行通信
尽管 Fragment 是作为独立于 Activity 的对象实现,并且可在多个 Activity 内使用,但fragment实例会直接绑定到包含它的 Activity
1、activity获取fragment实例
activity可以先获得fragmentManager的实例,然后通过fragmentManager实例的对象调用findFragmentById() 或 findFragmentByTag()来获得Fragment的实例。
2、fragment获取所在activity的实例
fragment可以通过 getActivity() 获得其所依附的 Activity 实例
3、fragmentA与fragmentB通信
由于二者依附同一activity时,我们可以这样做
1、首先在fragmentA中获取activity的实例,
2、然后通过activity的实例获得fragmentManager实例
3、通过fragmentManager实例的对象调用findFragmentById() 或 findFragmentByTag()来获得FragmentB的实例。
4、创建对Activity的事件回调
使用接口回调也可让fragment之间进行通信。具体可看下官方文档的实例。
五、常见问题解决方案
1、创建 Fragment 实例传递数据时使用无参数构造
之所以不用带参的构造方法,原因在于 Activity 在一些特殊情况下会发生销毁并重建的情形,比如屏幕旋转、内存吃紧等;对应的,依附于 Activity 存在的 Fragment 也会发生类似的状况。而一旦重建Fragment 便会调用默认的无参构造函数,导致无法执行有参构造函数进行初始化工作。
public static OneFragment newInstance(int args){
OneFragment oneFragment = new OneFragment();
Bundle bundle = new Bundle();
bundle.putInt("someArgs", args);
oneFragment.setArguments(bundle);
return oneFragment;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
int args = bundle.getInt("someArgs");
}
2、getActivity() 引用问题
当 Fragment 中存在类似网络请求之类的异步耗时任务时,该任务执行完毕回调 Fragment 的方法用到 Activity 对象时,可能宿主 Activity 已经销毁,从而引发空指针异常,所以最好都判空。一般情况下,获取 Context,可以通过 getContext() 获取。
3、Fragment 重叠问题
异常情况下:当 Activity 销毁并重建的时候,Activity 重新执行 onCreate 方法,那么就创建两次 Fragment 而导致 UI 重叠。解决如下。
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
mFrameLayout = findViewById(R.id.fl_content);
if (savedInstanceState != null) {
mFirstFragment = (FirstFragment) mFragmentManager.findFragmentByTag("fragment1");
} else {
mFirstFragment = FirstFragment.newInstance();
mFragmentTransaction.add(mFirstFragment, "fragment1");
}
}
4、fragment 嵌套fragment问题
fragmentA中要使用getChildFragmentManager来获得FragmentManager对象,否则你虽然动态的添加了fragmentB当是FragmentA会被覆盖、或者移除。
FragmentManager fragmentManager = getChildFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.container,new TestFragment());
fragmentTransaction.commit();
5、常见问题(点击按钮切换不同的fragment)
fragment 是基于事务的,每次事件就是一个一次性任务。所以每次操作时重新获得一个FragmentTransaction 再操作即可。
buttonBottom.setTabSelectedListener(new BottomNavigationBar.OnTabSelectedListener() {
@Override
public void onTabSelected(int position) {
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
if (position == 0) {
fragmentTransaction.replace(R.id.container, new TestFragment());
} else if (position == 1) {
fragmentTransaction.replace(R.id.container, new DiscFragment());
} else {
fragmentTransaction.replace(R.id.container, new GameFragment());
}
fragmentTransaction.commit();
}
@Override
public void onTabUnselected(int position) {
}
@Override
public void onTabReselected(int position) {
}
});
}
6、fragment中重写startActivityForResult不起作用
这里需要注意startActivityForResult不要使用activity的,要使用fragment自身的
7、禁用back键
private fun handleOnSystemBackButtonPressed() {
(requireActivity() as MainActivity).disableBackPressed(
this,
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
// 1、啥也不做,back失效
// 2、这里也可主动加其他逻辑然后finish()
}
})
}
Tnd
参考:
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)