安卓 Fragment 碎片详解
Android Fragment 是可以看成是一个小型的 Activity
,又称 Activity 片段
想想,如果一个很大的界面,就一个布局,写起界面来会有多麻烦,而且如果组件多的话是管理起来也很麻烦
使用 Fragment
则可以把屏幕划分成几块,然后进行分组,进行一个模块化的管理,从而可以更加方便的在 运行过程中动态地更新 Activity
的用户界面
下图是文档中给出的一个 Fragment 分别对应手机与平板间不同情况的处理图
Fragment
不能单独使用,需要嵌套在 Activity
中使用,会受到宿主 Activity
的生命周期的影响,比如 Activity
被 destory()
销毁了,它也会跟着销毁
Activity 和Fragment 的关系
- Fragment是依赖于Activity的,不能独立存在的。
- 一个Activity里可以有多个Fragment。
- 一个Fragment可以被多个Activity重用。
- Fragment有自己的生命周期,并能接收输入事件。
- 我们能在Activity运行时动态地添加或删除Fragment。
Fragment的生命周期
Activity 加载 Fragment
的时候,依次调用下面的方法
onAttach() -> onCreate() -> onCreateView() -> onActivityCreated() -> onStart() -> onResume()
-
当
Fragment
所在的Activity
可见,但不获得焦点时,比如悬浮的对话框风格的Activity
,就会调用 onPause -
当对话框关闭,
Activity
又获得了焦点,就会调用 onResume -
替换
Fragment
,并调用addToBackStack()
将它添加到Back
栈中onPause -> onStop -> onDestoryView
注意 ,此时
Fragment
还没有被销毁 -
按下键盘的回退键,
Fragment
会再次显示出来onCreateView -> onActivityCreated -> onStart -> onResume
-
如果替换后, 在事务
commit
之前 没有调用addToBackStack()
方法将Fragment
添加到back
栈中的话;又或者退出了Activity
的话,那么Fragment
将会被完全结束, Fragment会进入销毁状态onPause -> onStop -> onDestoryView -> onDestory -> onDetach
-
官方文档说创建
Fragment
时至少需要实现三个方法:onCreate()
,onCreateView()
,onPause()
,其实好像只要实现onCreateView()
就可以了 -
Fragment
的生命周期和Activity
有点类似,有三种状态-
Resumed
:在允许中的Fragment
可见 -
Paused
: 所在 Activity 可见,但是得不到焦点 -
Stoped
: 片段不可见。宿主 Activity 已停止,或片段已从 Activity 中移除,但已添加到返回栈。 停止片段仍然处于活动状态(系统会保留所有状态和成员信息)。 不过,它对用户不再可见,如果 Activity 被终止,它也会被终止 -
调用
addToBackStack()
,Fragment
被添加到Bcak 栈
-
该
Activity
转向后台,或者该Fragment
被替换/删除停止状态的fragment仍然活着(所有状态和成员信息被系统保持着),然而,它对用户 不再可见,并且如果activity被干掉,他也会被干掉.
-
可以看到Fragment比Activity多了几个额外的生命周期回调方法:
- onAttach(Context context):Fragment和Activity相关联时调用。如果不是一定要使用具体的宿主 Activity 对象的话,可以使用这个方法或者
getContext()
获取 Context 对象,用于解决Context上下文引用的问题。同时还可以在此方法中可以通过getArguments()
获取到需要在Fragment创建时需要的参数。 - onCreate():Fragment被创建时调用。
- onCreateView():创建Fragment的布局。
- onActivityCreated():当Activity完成onCreate()时调用。
- onStart():当Fragment可见时调用。
- onResume():当Fragment可见且可交互时调用。
- onPause():当Fragment不可交互但可见时调用。
- onStop():当Fragment不可见时调用。
- onDestroyView():当Fragment的UI从视图结构中移除时调用。
- onDestroy():销毁Fragment时调用。
- onDetach():当Fragment和Activity解除关联时调用。
上面的方法中,只有onCreateView()在重写时不用写super方法
,其他都需要。
Fragment的onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()都是在Activity的onStart()中调用的。
package com.example.newdemo.myfragment;
import android.content.Context;
import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.newdemo.R;
public class TestFragment extends Fragment {
private final String TAG = "TestFragment";
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
Log.d(TAG, "---- onAttach ----");
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "---- onCreate ----");
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.d(TAG, "---- onCreateView ----");
View view = inflater.inflate(R.layout.fragment_test, container, false);
TextView textView = view.findViewById(R.id.frag_text);
textView.setText("哈哈哈,this is fragment");
return view;
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
Log.d(TAG, "---- onActivityCreated ----");
super.onActivityCreated(savedInstanceState);
}
@Override
public void onStart() {
Log.d(TAG, "---- onStart ----");
super.onStart();
}
@Override
public void onResume() {
Log.d(TAG, "---- onResume ----");
super.onResume();
}
@Override
public void onPause() {
Log.d(TAG, "---- onPause ----");
super.onPause();
}
@Override
public void onStop() {
Log.d(TAG, "---- onStop ----");
super.onStop();
}
@Override
public void onDestroy() {
Log.d(TAG, "---- onDestroy ----");
super.onDestroy();
}
@Override
public void onDetach() {
Log.d(TAG, "---- onDetach ----");
super.onDetach();
}
}
Activity调用顺序:
D/FragmentActivity: --- onCreate ---
D/FragmentActivity: --- onStart --
当打开Fragment调用顺序
D/TestFragment: ---- onAttach ----
D/TestFragment: ---- onCreate ----
D/TestFragment: ---- onCreateView ----
D/TestFragment: ---- onActivityCreated ----
D/TestFragment: ---- onStart ----
D/TestFragment: ---- onResume ----
当退出Fragment的时候:
D/TestFragment: ---- onPause ----
D/TestFragment: ---- onStop ----
D/TestFragment: ---- onDestroy ----
D/TestFragment: ---- onDetach ----
Fragment的几个核心类
Fragment
:Fragment的基类,任何创建的Fragment都需要继承该类。FragmentManager
:管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl。FragmentTransaction
:对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord。
两种创建Fragment 的方法
1)静态加载Fragment
步骤:
-
定义
Fragment
的布局,就是fragment
显示内容的 -
自定义一个继承
Fragment
或者它的子类的类,然后重写onCreateView()
方法在该方法中调用
inflater.inflate()
方法加载Fragment
的布局文件,接着返回加载的 view 对象public class Fragmentone extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment1, container,false); return view; } }
-
在需要加载
Fragment
的Activity
对应的布局文件中添加fragment
的标签注意,
android:name
属性是全限定类名,就是要包含Fragment
的包名<fragment android:id="@+id/fragment1" android:name="com.jay.example.fragmentdemo.Fragmentone" android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" />
-
Activity
在onCreate()
方法中调用setContentView()
加载布局文件即可
2)动态 加载Fragment
-
通过调用方法
getFragmentManager()
获得FragmentManager
对象FragmentManager fm = getFragmentManager();
-
通过调用方法
fm.beginTransaction()
获得FragmentTransaction
对象FragmentTransaction ft = fm.fm.beginTransaction();
-
通过调用
ft.add()
或者ft.replace()
方法加载Fragment
add(要传入的容器,fragment 对象)
-
-
然后调用
ft.commit()
方法提交事务,或调用ft.remove()
删除事务
// Activity主要依赖于 FragmentManager 管理 Fragment
FragmentManager fm = getSupportFragmentManager();
// Fragment事务,FragmentTransaction 对象提供了 commit() 方法用于提交事务
FragmentTransaction fragmentTransaction = fm.beginTransaction();
// 将 TestFragment 加载到 Activity 容器上
fragmentTransaction.add(R.id.framelayout, new TestFragment());
fragmentTransaction.commit();
管理Fragment
Activity
管理 Fragment
主要依赖 FragmentManager
FragmentManager
提供了一些方法用于管理 Fragment
方法 | 说明 |
---|---|
findFragmentById() | 获得指定的 Fragment
|
popBackStack() | 方法弹出后台 Fragment
|
addToBackStack(null) | 加入 Back 栈 |
也提供了一个事件监听器用于监听后台栈的变化 addOnBackStackChangeListener
加入到 Back 栈的区别:
- 当点击按钮,调用
replace()
将FragmentTest
替换为FragmentTestReplace
,加addToBackStack()时,日志如下:
可以看到,F1被替换时,最后只调到了onDestroyView()
,并没有调用onDestroy()和onDetach()
。当用户点返回按钮回退事务时,F1会调onCreateView()->onStart()->onResume()
,因此在Fragment事务中加不加addToBackStack()会影响Fragment的生命周期。
链接:https://juejin.im/post/6844903816240857095
FragmentTransaction有一些基本方法,下面给出调用这些方法时,Fragment生命周期的变化:
add()
: onAttach()->onCreate()->onCreateView()->onActivityCreated()->onStart()->onResume(),在执行add()时,同一个Fragment不允许被add()两次
remove()
: onPause()->onStop()->onDestroyView()->onDestroy()->onDetach()。replace()
:相当于新Fragment调用add(),旧Fragment调用remove()
,replace() 方法不会保留 Fragment 的状态
,也就是说诸如 EditText 内容输入等用户操作在 remove() 时会消失。分为两种情况
- 不加
addToBackStack()
: new onAttach() -> new onCreate() -> old onPause()-> old onStop()-> old onDestroyView()-> old onDestroy()-> old onDetach() -> new onCreateView() -> new onActivityCreated() -> new onStart()。- 加
addToBackStack()
: new onAttach() -> new onCreate() -> old onPause()-> old onStop()-> old onDestroyView() -> new onCreateView -> new onActivityCreated() -> new onStart()。show()
: 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为true。hide()
: 不调用任何生命周期方法,调用该方法的前提是要显示的Fragment已经被添加到容器,只是纯粹把Fragment UI的setVisibility为false。
Fragment 事务:
增删替换 Fragment
需要借助 FragmentTransaction
对象
FragmentTransaction
对象提供了 commit()
方法用于提交事务,提供了 remove()
方法用于删除事务
Fragment 和Activity 交互
1)获取组件的方法
Fragment
获得 Activity
中的组件
getActivity().findViewById(R.id.list);
Activity
获得 Fragment
中的组件(根据 id 或 tag 都可以)
getFragmentManager.findFragmentByid(R.id.fragment1);
2)数据传递
2-1)Activity 给 Fragment 传递数据
在 Activity
中创建 Bundle
数据包,调用 Fragment
实例的 setArguments(bundle)
从而将 Bundle
数据包传给 Fragment
,然后 Fragment
中调用 getArguments()
获得 Bundle
对象,最后进行解析就可以了
2-2)Fragment 给Activity 传递数据
在 Fragment
中定义一个内部回调接口,再让包含该 Fragment
的 Activity
实现该回调接口,Fragment
就可以通过回调接口传数据了
-
在
Fragment
中定义一个回调接口public interface CallBack{ /*定义一个获取信息的方法*/ public void getResult(String result); }
-
在
Fragment
中实现接口回调/*接口回调*/ public void getData(CallBack callBack){ /*获取文本框的信息,当然你也可以传其他类型的参数,看需求咯*/ String msg = editText.getText().toString(); callBack.getResult(msg); }
-
在
Activity
中使用接口回调方法读数据/* 使用接口回调的方法获取数据 */ leftFragment.getData(new CallBack() { @Override public void getResult(String result) { /*打印信息*/ Toast.makeText(MainActivity.this, "-->>" + result, 1).show(); } });
2-3)Fragment 给Fragment 传递数据
找到要接受数据的 Fragment
对象,直接调用 setArguments()
传数据进去就可以了
一般情况下时 replace
时,即 fragment
跳转的时候传数据的,那么只需要在初始化要跳转的 Fragment
后调用 `setArguments() 方法传入数据即可
如果是两个 Fragment
需要即时传数据,而非跳转的话,就需要以Activity为媒介,先在 Activity
获得 f1
传过来的数据,再传到f2了,就是~
FragmentManager fManager = getSupportFragmentManager( );
FragmentTransaction fTransaction = fManager.beginTransaction();
Fragmentthree t1 = new Fragmentthree();
Fragmenttwo t2 = new Fragmenttwo();
Bundle bundle = new Bundle();
bundle.putString("key",id);
t2.setArguments(bundle);
fTransaction.add(R.id.fragmentRoot, t2, "~~~");
fTransaction.addToBackStack(t1);
fTransaction.commit();
Fragment的一个基本的使用
1. 创建一个TestFragment 继承于Fragment
package com.example.newdemo.myfragment;
import android.os.Bundle;
import androidx.fragment.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import com.example.newdemo.R;
public class TestFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.fragment_test, container, false);
TextView textView = view.findViewById(R.id.frag_text);
textView.setText("哈哈哈,this is fragment");
return view;
}
}
2. 创建Fragment 对应的 xml 文件,一个TextView,主要用于显示
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:context=".myfragment.TestFragment">
<!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:id="@+id/frag_text"
android:text="@string/hello_blank_fragment" />
</FrameLayout>
3.创建 Activity 及其对应的xml【xml中要有对应的 FrameLayout ,用于显示 fragment】
package com.example.newdemo.myfragment;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.example.newdemo.R;
public class FragmentActivity extends AppCompatActivity {
private Button frame_btn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
bindViews();
frame_btn.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View v) {
// Activity主要依赖于 FragmentManager 管理 Fragment
FragmentManager fm = getSupportFragmentManager();
// Fragment事务,FragmentTransaction 对象提供了 commit() 方法用于提交事务
FragmentTransaction fragmentTransaction = fm.beginTransaction();
// 将 TestFragment 加载到 Activity 容器上
fragmentTransaction.add(R.id.framelayout, new TestFragment());
fragmentTransaction.commit();
}
});
}
public void bindViews(){
frame_btn = findViewById(R.id.frame_btn);
}
}
对应的xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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=".myfragment.FragmentActivity"
android:orientation="horizontal">
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/frame_btn"
android:text="显示fragement"/>
<!-- <fragment-->
<!-- android:layout_width="wrap_content"-->
<!-- android:layout_height="wrap_content"-->
<!-- android:id="@+id/frame"-->
<!-- android:layout_below="@+id/frame_btn"-->
<!-- android:name="com.example.newdemo.myfragment.TestFragment"/>-->
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/frame_btn"
android:id="@+id/framelayout"/>
</RelativeLayout>
-
Fragment有一个常见的问题,即Fragment重叠问题,这是由于Fragment被系统杀掉,并重新初始化时再次将fragment加入activity,因此通过在外围加if语句能判断此时是否是被系统杀掉并重新初始化的情况。解决方法有三种
- 第一种方式,在 Activity 提供的 onAttachFragment() 方法中处理:
@Override public void onAttachFragment(Fragment fragment) { super.onAttachFragment(fragment); if (fragment instanceof OneFragment){ oneFragment = (OneFragment) fragment; } }
- 第二种方式,在创建 Fragment 前添加判断,判断是否已经存在:
Fragment tempFragment = getSupportFragmentManager() .findFragmentByTag("OneFragment"); if (tempFragment==null) { oneFragment = OneFragment.newInstance(); ft.add(R.id.fl_content, oneFragment, "OneFragment"); } else { oneFragment = (OneFragment) tempFragment; }
- 第三种方式,更为简单,直接利用 savedInstanceState 判断即可:
if (savedInstanceState==null) { oneFragment = OneFragment.newInstance(); ft.add(R.id.fl_content, oneFragment, "OneFragment"); }else { oneFragment = (OneFragment) getSupportFragmentManager().findFragmentByTag("OneFragment"); }
很好的参考文章:
本文地址:https://blog.csdn.net/mike_jungle/article/details/110630940
推荐阅读
-
逍遥安卓模拟器怎么用?逍遥安卓模拟器安装及使用教程图文详解
-
小程序scroll-view安卓机隐藏横向滚动条的实现详解
-
微信JS-SDK updateAppMessageShareData安卓不能自定义分享详解
-
逍遥安卓模拟器怎么用?逍遥安卓模拟器安装及使用教程图文详解
-
详解一次Vue低版本安卓白屏问题的解决过程
-
Ubuntu 14.04下创建Genymotion安卓虚拟机的步骤详解
-
一文看懂!余承东详解鸿蒙OS:比安卓性能更强、更面向未来
-
android选择文件(安卓系统文件夹详解)
-
微信JS-SDK updateAppMessageShareData安卓不能自定义分享详解
-
小程序scroll-view安卓机隐藏横向滚动条的实现详解