Fragment的总结
前言
Fragment(碎片)作为Android中大屏幕开发常用的UI片段,它让程序更加合理更加充分的利用大屏幕的空间。可以这么去理解,它可以将屏幕碎片化,每个碎片处理不同的事情,可以相互通信。(结合大佬们的博客总结的,如有侵权,麻烦联系我删除此文章)
Fragment是依赖于Activity的,不能独立存在的。并且一个Activity里可以有多个Fragment,我们能在Activity运行时动态地添加或删除Fragment。。一个Fragment可以被多个Activity重用。另外,Fragment也有自己的生命周期。
Fragment的生命周期
Fragment必须是依存与Activity而存在的,因此Activity的生命周期会直接影响到Fragment的生命周期。所以,将Activity的生命周期和它的合在一起方便理解,如图:
FragmentRecycle.png介绍下Fragment的生命周期的各个方法
- onAttach(Activity):当Fragment与Activity发生关联时调用。
- onCreate():初始化Fragment
- onCreateView(LayoutInflater, ViewGroup,Bundle):创建该Fragment的视图
- onActivityCreated(Bundle):当Activity的onCreate方法返回时调用
- onStart():视图可见
- onResume():可交互
- onPause():不可交互
- onStop():不可见
- onDestoryView():与onCreateView想对应,当该Fragment的视图被移除时调用
- onDestory():销毁Fragment
- onDetach():与onAttach相对应,当Fragment与Activity关联被取消时调用
注意:除了onCreateView,其他的所有方法如果你重写了,必须调用父类对于该方法的实现,
基本使用
用法有两种:
- 静态使用
- 动态使用
静态使用
把Fragment当成普通的控件,直接写在Activity的布局文件中。
1、继承Fragment,重写onCreateView决定Fragemnt的布局,如下我们将界面分为两块(Title和Context):
Title.java
public class TitleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.title_fragment,container,false);
}
}
title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:gravity="center"
android:layout_height="45dp">
<TextView
android:layout_width="wrap_content"
android:text="我是标题"
android:id="@+id/frg_title"
android:layout_height="wrap_content" />
</LinearLayout>
Context.java
public class ContextFragment extends Fragment {
@Override
public View onCreateView( LayoutInflater inflater,ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.context_fragment,container,false);
}
}
context.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:gravity="center"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:text="我是上下文"
android:layout_height="wrap_content" />
</LinearLayout>
2、在Activity中声明此Fragment,就当和普通的View一样
FragmentsActivity.java
public class FragmentsActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.study_fragments_activity);
}
public void intoActivity(Context context){
Intent intent = new Intent(context,this.getClass());
context.startActivity(intent);
}
}
study_fragments_activity.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:layout_width="match_parent"
android:layout_height="45dp"
android:id="@+id/ac_title"
android:name="sayhallo.cn.ilikeandroid.fragments.TitleFragment"/>
<fragment
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/ac_context"
android:name="sayhallo.cn.ilikeandroid.fragments.ContextFragment"/>
</LinearLayout>
看下效果图:
静态Fragment.jpg这样子,一个Activity的工作就被两个Fragment给分了,结构就会清楚很多了。
动态使用
即动态的把Fragment加入(增删)到Activity中
第一步:新建activity,放置我们fragment显示的布局
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:layout_width="match_parent"
android:layout_marginBottom="45dp"
android:id="@+id/fragment_context"
android:layout_height="match_parent"/>
<LinearLayout
android:layout_width="match_parent"
android:id="@+id/fragment_select_tab"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:weightSum="4"
android:layout_height="45dp">
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:text="按钮1"
android:id="@+id/bt_1"
android:layout_height="match_parent" />
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:text="按钮2"
android:id="@+id/bt_2"
android:layout_height="match_parent" />
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:text="按钮3"
android:id="@+id/bt_3"
android:layout_height="match_parent" />
<Button
android:layout_width="0dp"
android:layout_weight="1"
android:id="@+id/bt_4"
android:text="按钮4"
android:layout_height="match_parent" />
</LinearLayout>
</RelativeLayout>
第二步:在java文件设置显示的逻辑
private void setDefaultFragment() {
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
Fragment1 fragment1 = new Fragment1();
transaction.replace(R.id.fragment_context, fragment1);
transaction.commit();
}
@Override
public void onClick(View v) {
FragmentManager fm = getSupportFragmentManager();
// 开启Fragment事务
FragmentTransaction transaction = fm.beginTransaction();
switch (v.getId())
{
case R.id.bt_1:
//添加、替换
break;
case R.id.bt_2:
//添加、替换
break;
}
// transaction.addToBackStack();
// 事务提交
transaction.commit();
}
第三步:编写我们需要显示的fragment即可
最后,点击相应的按钮即可显示对应的fragment了。
Fragment常用的三个类:
-
androidx.fragment.app.Fragment 主要用于定义Fragment
-
androidx.fragment.app.FragmentManager 主要用于在Activity中操作Fragment,通过getSupportFragmentManager方法得到(必须要Activity继承AppCompatActivity才有这个方法)
-
androidx.fragment.app.FragmentTransaction 事务操作,通过FragmentManager.benginTran satcion()得到实例对象,如 FragmentTransaction transaction = fm.benginTransatcion();
- transaction.add(Fragment fragment,String tag) :往Activity中添加一个Fragment
- transaction.remove(Fragment fragment):从Activity中移除一个Fragment
- transaction.replace(Fragment fragment):使用另一个Fragment替换当前的
- transaction.hide(Fragment fragment):隐藏当前的Fragment,仅仅是设为不可见,并不会销毁
- transaction.show(Fragment fragment):显示之前隐藏的Fragment
- transaction.detach(Fragment fragment):会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护。
- transaction.attach(Fragment fragment):重建view视图,附加到UI上并显示。
- transatcion.commit():提交一个事务
Fragment回退栈
和Activity的返回栈一样,如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。
怎样添加Fragment到回退栈
这里就会用到这样的一个方法
FragmentTransaction.addToBackStack(String)
如:
Fragment3 fThree = new Fragment3();
FragmentManager fm = getActivity().getSupportFragmentManager();
FragmentTransaction tx = fm.beginTransaction();
tx.hide(Fragment2.this);
tx.add(R.id.fragment_context , fThree, "THREE");
// tx.replace(R.id.id_content, fThree, "THREE");
tx.addToBackStack(null);
tx.commit();
这样子,就把当前这个fragment添加到回退栈了,并且进入了下一个Fragment
Fragment与Activity通信
所有的Fragment都是依附于Activity的,所以通信起来并不复杂,大概归纳为:
- 如果你Activity中包含自己管理的Fragment的引用,可以通过引用直接访问所有的Fragment的public方法
- 如果Activity中未保存任何Fragment的引用,那么没关系,每个Fragment都有一个唯一的TAG或者ID,可以通过getFragmentManager.findFragmentByTag()或者findFragmentById()获得任何Fragment实例,然后进行操作
- 在Fragment中可以通过getActivity得到当前绑定的Activity的实例,然后进行操作
注:如果在Fragment中需要Context,可以通过调用getActivity(),如果该Context需要在Activity被销毁后还存在,则使用getActivity().getApplicationContext()。
附加使用
1、通常用法
Activity中有个FragmentManager,其内部维护fragment队列,以及fragment事务的回退栈。
一般情况下,我们在Activity里面会这么添加Fragment:
private ContentFragment mContentFragment ;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
mContentFragment = (ContentFragment) fm.findFragmentById(R.id.id_fragment_container);
if(mContentFragment == null )
{
mContentFragment = new ContentFragment();
fm.beginTransaction().add(R.id.id_fragment_container,mContentFragment).commit();
}
}
当Activity因为配置发生改变(屏幕旋转)或者内存不足被系统杀死,造成重新创建时,我们的fragment会被保存下来,但是会创建新的FragmentManager,新的FragmentManager会首先会去获取保存下来的fragment队列,重建fragment队列,从而恢复之前的状态。
Fragment Arguments
比如我们某个按钮触发Activity跳转,需要通过Intent传递参数到目标Activity的Fragment中,那么此Fragment如何获取当前的Intent的值呢?
mArgument = getActivity().getIntent().getStringExtra(ARGUMENT);
这么写,功能上是实现了,但是呢?存在一个大问题:我们用Fragment的一个很大的原因,就是为了复用。你这么写,相当于这个Fragment已经完全和当前这个宿主Activity绑定了,所有就完全不能复用了,所以,我们需要换种写法。
public class Fragment1 extends Fragment
{
private String mArgument;
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
Bundle bundle = getArguments();
if (bundle != null)
mArgument = bundle.getString("argument");
}
public static ContentFragment newInstance(String argument)
{
Bundle bundle = new Bundle();
bundle.putString("argument", argument);
ContentFragment contentFragment = new ContentFragment();
contentFragment.setArguments(bundle);
return contentFragment;
}
}
这样就完成了Fragment和Activity间的解耦。
Fragment数据回传
两个Fragment,一个展示文章列表的Fragment(Activity1),一个显示详细信息的Fragment(Activity2)。
现在,我们点击列表Fragment中的列表项,传入相应的参数,去详细信息的Fragment展示详细的信息,在详细信息页面,用户可以进行点评,当用户点击back以后,我们以往点评结果显示在列表的Fragment对于的列表项中;
也就是说,我们点击跳转到对应Activity的Fragment中,并且希望它能够返回参数,那么我们肯定是使用Fragment.startActivityForResult ;
在Fragment中存在startActivityForResult()以及onActivityResult()方法,但是呢,没有setResult()方法,用于设置返回的intent,这样我们就需要通过调用
getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent)
如:点击跳转和监听回传的代码
Fragment1
@Override
public void onListItemClick(ListView l, View v, int position, long id)
{
mCurrentPos = position ;
Intent intent = new Intent(getActivity(),ContentActivity.class);
intent.putExtra(ContentFragment.ARGUMENT, mTitles.get(position));
startActivityForResult(intent, REQUEST_DETAIL);
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
Log.e("TAG", "onActivityResult");
super.onActivityResult(requestCode, resultCode, data);
if(requestCode == REQUEST_DETAIL)
{
mTitles.set(mCurrentPos, mTitles.get(mCurrentPos)+" -- "+data.getStringExtra(ContentFragment.RESPONSE));
mAdapter.notifyDataSetChanged();
}
}
回传的代码
Fragment2
Intent intent = new Intent();
intent.putExtra(RESPONSE, "good");
getActivity().setResult(ListTitleFragment.REQUEST_DETAIL, intent);
但是,我们发现,刚才是在两个不同的Activity宿主上的Fragment数据回传,现在我们想在同一个Activity中数据回传。怎么实现呢?
相同宿主上的Fragment数据回传
虽然我们是在同一个Activity宿主,但是我们返回的数据,依然在onActivityResult中进行接收,但是,注意添加这么一句代码:
dialog.setTargetFragment(ContentFragment.this, REQUEST_EVALUATE);
设置回传的代码
// 设置返回数据
protected void setResult(int which)
{
// 判断是否设置了targetFragment
if (getTargetFragment() == null)
return;
Intent intent = new Intent();
intent.putExtra(RESPONSE_EVALUATE, mEvaluteVals[which]);
getTargetFragment().onActivityResult(ContentFragment.REQUEST_EVALUATE,
Activity.RESULT_OK, intent);
}
监听的代码
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_EVALUATE)
{
String evaluate = data
.getStringExtra(EvaluateDialog.RESPONSE_EVALUATE);
Toast.makeText(getActivity(), evaluate, Toast.LENGTH_SHORT).show();
Intent intent = new Intent();
intent.putExtra(RESPONSE, evaluate);
getActivity().setResult(Activity.REQUEST_OK, intent);
}
}
通过回调回传数据
如:
public class FragmentOne extends Fragment implements OnClickListener
{
private Button mBtn;
public interface FOneBtnClickListener
{
void onFOneBtnClick();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_one, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_one_btn);
mBtn.setOnClickListener(this);
return view;
}
/**
* 交给宿主Activity处理,如果它希望处理
*/
@Override
public void onClick(View v)
{
if (getActivity() instanceof FOneBtnClickListener)
{
((FOneBtnClickListener) getActivity()).onFOneBtnClick();
}
}
}
public class FragmentTwo extends Fragment implements OnClickListener
{
private Button mBtn ;
private FTwoBtnClickListener fTwoBtnClickListener ;
public interface FTwoBtnClickListener
{
void onFTwoBtnClick();
}
//设置回调接口
public void setfTwoBtnClickListener(FTwoBtnClickListener fTwoBtnClickListener)
{
this.fTwoBtnClickListener = fTwoBtnClickListener;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
View view = inflater.inflate(R.layout.fragment_two, container, false);
mBtn = (Button) view.findViewById(R.id.id_fragment_two_btn);
mBtn.setOnClickListener(this);
return view ;
}
@Override
public void onClick(View v)
{
if(fTwoBtnClickListener != null)
{
fTwoBtnClickListener.onFTwoBtnClick();
}
}
}