深入浅出AndroidJava 核心技术读书

Fragment基本使用

2021-07-20  本文已影响0人  安卓技术砖家

Fragment概述

介绍

Fragment是一种可以嵌入在活动中的UI片段,能够让程序更加合理和充分地利用大屏幕的空间,出现的初衷是为了适应大屏幕的平板电脑,可以将其看成一个小型Activity,又称作Activity片段。

使用Fragment可以把屏幕划分成几块,然后进行分组,进行一个模块化管理。Fragment不能够单独使用,需要嵌套在Activity中使用,其生命周期也受到宿主Activity的生命周期的影响。

Fragment的特点

Fragment的优势

Fragment基本生命周期

安卓_Fragment使用_内容1.png

Fragment的一般生命周期如上图所示:

事件 说明
onAttach() Fragment和Activity相关联时调用。可以通过该方法获取Activity引用,还可以通过getArguments()获取参数。
onCreate() Fragment被创建时调用
onActivityCreated() 当Activity完成onCreate()时调用
onStart() 当Fragment可见时调用。
onResume() 当Fragment可见且可交互时调用
onPause() 当Fragment不可交互但可见时调用。
onStop() 当Fragment不可见时调用。
onDestroyView() 当Fragment的UI从视图结构中移除时调用。
onDestroy() 销毁Fragment时调用。
onDetach() 当Fragment和Activity解除关联时调用。

Fragment生命周期会经历:运行、暂停、停止、销毁

在介绍Fragment的具体使用时,先介绍一下Fragment的几个核心类

说明
Fragment Fragment的基类,任何创建的Fragment都需要继承该类
FragmentManager 管理和维护Fragment。他是抽象类,具体的实现类是FragmentManagerImpl
FragmentTransaction 对Fragment的添加、删除等操作都需要通过事务方式进行。他是抽象类,具体的实现类是BackStackRecord

扩展子类

备注:开发Fragment不建议使用android.app下的Fragment而应是android:support.v4.app,因为support库是不断更新的。


Fragment使用

使用Fragment有两种方式,分别是静态加载和动态加载

静态加载

关于静态加载的流程如下:

静态加载一旦添加就不能在运行时删除

示例

定义Fragment布局,新建left_fragment.xml和right_fragment.xml文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="Button" />
</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:background="#00ff00"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:textSize="20sp"
        android:text="this is Fragment" />
</LinearLayout>

自定义Fragment类,继承Fragment或其子类,重写onCreateView(),在方法中调用inflater.inflate()方法加载Fragment布局文件,接着返回加载的view对象

public class LeftFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.left_fragment, container,false);
        return view;
    }
}
public class RigthFragment extends Fragment {
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.right_fragment, container, false);
        return view;
    }
}

在需要加载Fragment的Activity对应的布局文件中添加Fragment标签

<fragment
    android:id="@+id/left_fragment"
    android:name="com.vivo.a11085273.secondfragmenttest.LeftFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />
<fragment
    android:id="@+id/right_fragment"
    android:name="com.vivo.a11085273.secondfragmenttest.RigthFragment"
    android:layout_width="0dp"
    android:layout_height="match_parent"
    android:layout_weight="1" />

在Activity的onCreate()方法中调用setContentView()加载布局文件即可

动态加载Fragment

动态加载Fragment的流程如下:

简单示例:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Button button = (Button) findViewById(R.id.button);
        button.setOnClickListener(this);
        replaceFragment(new RigthFragment());
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.button:
                replaceFragment(new AnotherRightFragment());
                break;
            default:
                break;
        }
    }

    private void replaceFragment(Fragment fragment) {
        FragmentManager fragmentManager = getSupportFragmentManager();
        FragmentTransaction transaction = fragmentManager.beginTransaction();   // 开启一个事务
        transaction.replace(R.id.right_layout, fragment);
        transaction.commit();
    }
}

使用注意点

Fragment的onCreateView()方法返回Fragment的UI布局,需要注意的是inflate()的第三个参数是false,因为在Fragment内部实现中,会把该布局添加到container中,如果设为true,那么就会重复做两次添加,则会抛如下异常

Caused by: java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child’s parent first.

如果在创建Fragment时要传入参数,必须要通过setArguments(Bundle bundle)方式添加,而不建议通过为Fragment添加带参数的构造函数,因为通过setArguments()方式添加,在由于内存紧张导致Fragment被系统杀掉并恢复(re-instantiate)时能保留这些数据

可以在Fragment的onAttach()中通过getArguments()获得传进来的参数。如果要获取Activity对象,不建议调用getActivity(),而是在onAttach()中将Context对象强转为Activity对象

public class Fragment1 extends Fragment{
    private static String ARG_PARAM = "param_key";
    private String mParam;
    private Activity mActivity;
    public void onAttach(Context context) {
        mActivity = (Activity) context;
        mParam = getArguments().getString(ARG_PARAM);  //获取参数
    }
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View root = inflater.inflate(R.layout.fragment_1, container, false);
        TextView view = root.findViewById(R.id.text);
        view.setText(mParam);
        return root;
    }
    public static Fragment1 newInstance(String str) {
        Fragment1 frag = new Fragment1();
        Bundle bundle = new Bundle();
        bundle.putString(ARG_PARAM, str);
        fragment.setArguments(bundle);   //设置参数
        return fragment;
    }
}

动态加载Fragment中,FragmentTransaction类提供了方法完成增删等操作,完成后调用FragmentTransaction.commit()方法提交修改

事件 说明
transaction.add() 往Activity里面添加一个片段
transaction.remove() 从Activity中移除一个Fragment,如果被移除的Fragment没有添加到回退栈,这个Fragment实例将会被销毁
transaction.replace() 使用另一个Fragment替换当前的,实际上是remove()然后add()的合体
transaction.hide() 隐藏当前Fragment,仅不可见,不会销毁
transaction.show() 显示之前隐藏的Fragment
detach() 会将view从UI中移除,和remove()不同,此时fragment的状态依然由FragmentManager维护
attach() 重建view视图,附加到UI上并显示

commit方法一定要在Activity.onSaveInstance()之前调用commit()操作是异步的,内部通过mManager.enqueueAction()加入处理队列。对应的同步方法为commitNow(),commit()内部会有checkStateLoss()操作,如果开发人员使用不当(比如commit()操作在onSaveInstanceState()之后),可能会抛出异常,而commitAllowingStateLoss()方法则是不会抛出异常版本的commit()方法,但是尽量使用commit(),而不要使用commitAllowingStateLoss()。

FragmentManager拥有回退栈(BackStack),类似于Activity的任务栈,如果添加了该语句,就把该事务加入回退栈,当用户点击返回按钮,会回退该事务(回退指的是如果事务是add(frag1),那么回退操作就是remove(frag1));如果没添加该语句,用户点击返回按钮会直接销毁Activity。

Fragment常见异常

java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState
at android.support.v4.app.FragmentManagerImpl.checkStateLoss(FragmentManager.java:1341)
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1352)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commit(BackStackRecord.java:574)

出现原因:commit()在onSaveInstanceState()后调用。

由于onSaveInstanceState()onPause()之后,onStop()之前调用。onRestoreInstanceState()在onStart()之后,onResume()之前,因此避免出现该异常的方案有:


Fragment与Activity联动

安卓_Fragment使用_内容2.png

Fragment和Activity完整的生命周期如上图所示,下面给出Activity与Fragment联动时的执行顺序,最好通过log自我验证一下。

  1. 当Fragment在Activity的onCreate()中被添加时
Activity->Fragment: onCreate()
Note right of Fragment: onAttach()
Note right of Fragment: onCreate()
Note right of Fragment: onCreateView()
Note right of Fragment: onViewCreated()
Fragment-->Activity: ""
Activity->Fragment: onStart()
Note right of Fragment: onActivityCreated()
Note right of Fragment: onViewStateRestored()
Note right of Fragment: onStart()
Fragment-->Activity: ""
Note left of Activity: onPostCreate()
Note left of Activity: onResume()
Activity->Fragment: onPostResume()
Note right of Fragment: onResume()
Fragment-->Activity: ""
Note left of Activity: onAttachedToWindow()
Activity->Fragment: onCreateOptionsMenu()
Note right of Fragment: onCreateOptionsMenu()
Fragment-->Activity: ""
Activity->Fragment: onPrepareOptionsMenu()
Note right of Fragment: onPrepareOptionsMenu()
Fragment-->Activity: ""
Note left of Activity: onWindowFocusChanged()
  1. 暂停生命周期
Activity->Fragment: onPause()
Note right of Fragment: onPause()
Fragment-->Activity: ""
Activity->Fragment: onSaveInstanceState()
Note right of Fragment: onSaveInstanceState()
Fragment-->Activity: ""
Activity->Fragment: onStop()
Note right of Fragment: onStop()
Fragment-->Activity: ""
  1. 销毁的生命周期
Activity->Fragment: onDestroy()
Note right of Fragment: onDestroyView()
Note right of Fragment: onDestroy()
Note right of Fragment: onDetach()
Fragment-->Activity: ""
  1. 重启的生命周期
Note left of Activity: onRestart()
Activity->Fragment: onStart()
Note right of Fragment: onStart()
Fragment-->Activity: ""
Note left of Activity: onResume()
Activity->Fragment: onPostResume()
Note right of Fragment: onResume()
Fragment-->Activity: ""
Note left of Activity: onWindowFocusChanged()

回退栈

类似Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fragment事务发生的变化。如果你将Fragment任务添加到回退栈,当用户点击后退按钮时,将看到上一次的保存的Fragment。一旦Fragment完全从后退栈中弹出,用户再次点击后退键,则退出当前Activity。

添加一个Fragment事务到回退栈:

FragmentTransaction.addToBackStack(String)

简单示例:

private void replaceFragment(Fragment fragment) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.replace(R.id.right_layout, fragment);
    transaction.addToBackStack(null);   //添加进回退栈
    transaction.commit();
}

replace是remove和add的合体,并且如果不添加事务到回退栈,前一个Fragment实例会被销毁。这里很明显,我们调用transaction.addToBackStack(null)将当前的事务添加到了回退栈,所以FragmentOne实例不会被销毁,但是视图层次依然会被销毁,即会调用onDestoryViewonCreateView

如果不希望视图重绘,可以将原来的Fragment隐藏:

private void replaceFragment(Fragment fragment) {
    FragmentManager fragmentManager = getSupportFragmentManager();
    FragmentTransaction transaction = fragmentManager.beginTransaction();
    transaction.hide(this);
    transaction.add(R.id.right_layout, fragment);
    transaction.addToBackStack(null);   //添加进回退栈
    transaction.commit();
}

Fragment与Activity通信

Fragment与Avtivity的交互

graph LR
    FAI(["Fragment与<br />Activity的交互"]) --> CA([组件获取])
    FAI --> DT([数据传递])
    CA --> FGAC[Fragment获取Activity中的组件]
    CA --> AGFC[Activity获取Fragment中的组件]
    DT --> ATF[Activity传递数据给Fragment]
    DT --> FTA[Fragment传递数据给Activity]
    FGAC --> FVI["getActivity().findViewById(R.id.list)"]
    AGFC --> FFI["getFragmentManager().findFragmentById(R.id.fragment)"]
    ATF --> ATFD["在Activity中创建Bundle数据包,调用<br />Fragment实例的setArguments(bundle)<br />从而将Bundle数据包传给Fragment"]
    FTA --> FTAD["在Fragment中定义一个内部回调接口,再让<br />包含该Fragment的Activity实现该回调接口,<br />Fragment就可以通过回调方法传数据了"]

:标识Fragment除了在布局文件中添加id属性标志,还可以使用Tag属性对Fragment进行标识,那么我们也可以使用findFragmentByTag找到对应的Fragment了

Fragment与Activity的通信交互如上图所示:

考虑Fragment的重复使用问题,降低与Activity的耦合,Fragment操作应该由它的管理者Activity决定

传递数据给Fragment

//创建Fragment对象,并通过Bundle对象传递值(在onCreate方法中)
MyFragment fragment = new MyFragment();
Bundle bundle = new Bundle();
bundle.putString("key", values);
fragment.setArguments(bundle);
//(在Fragment类中的onCreateView方法中)
Bundle bundle = this.getArguments();
if (bundle != null) {
    String str = bundle.getString("key");
}
TextView textView = new TextView(getActivity());
textView.setText("上上下下的享受");//是电梯,别误会

传递数据给Activity

简单示例

首先在Fragment中定义一个接口(定义抽象方法,传什么类型参数)

/*接口*/  
public interface Mylistener{
    public void thanks(String code);
}

Fragment类中定义该接口

private Mylistener listener;

在onAttach方法中,将定义的该接口强转为activity类型

@Override
public void onAttach(Activity activity) {
    // TODO Auto-generated method stub
    listener=(Mylistener) activity;
    super.onAttach(activity);
}

Activity只需实现该接口并重写该方法即可

@Override
public void thanks(String code) {
    // TODO Auto-generated method stub
    Toast.makeText(this, "已收到Fragment的消息:--"+code+"--,客气了", Toast.LENGTH_SHORT).show();;
}

除了接口回调,还可以使用EventBus进行交互通信。


Fragment间通信

setArguments()

在Fragment B中新建一个函数:newInstance()接收传过来的参数

public static Fragment2 newInstance(String text) {
    Fragment2 fragment = new Fragment2();
    Bundle args = new Bundle();
    args.putString("param", text);
    fragment.setArguments(args);
    return fragment;
}

在Fragment B的onCreateView中获取参数

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view =  inflater.inflate(R.layout.fragment2, container, false);
    if (getArguments() != null) {
        String mParam1 = getArguments().getString("param");
        TextView tv =  (TextView)view.findViewById(R.id.textview);
        tv.setText(mParam1);
    }
    return view;
}

在Fragment A中,调用Fragment B时,通过newInstance函数获取实例并传递参数:

public class Fragment1 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment1, container, false);
        Button btn = (Button)view.findViewById(R.id.load_fragment2_btn);
        btn.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(final View view) {
                Fragment2 fragment2 = Fragment2.newInstance("从Fragment1传来的参数");
                FragmentTransaction transaction = getFragmentManager().beginTransaction();
                transaction.add(R.id.main_layout, fragment2);
                transaction.addToBackStack(null);
                transaction.commit();
            }
        });
        return view;
    }
}

同Activity不同Container的Fragment交互

这种情况有三中方法解决:

方法一:直接在Activity中操作。直接在Activity中找到对应控件的实例,然后直接操控即可

方法二:直接在Fragment中操作。这里有两个问题:如何获取自己控件的引用?如何获取其他Fragment页控件的引用?

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View rootView = inflater.inflate(R.layout.fragment1, container, false);
    listView = (ListView)rootView.findViewById(R.id.list);//获取自己视图里的控件引用,方法一
    return rootView;
}

onCreateView()中,还没有创建视图,所以在这里如果使用getView()方法将返回空。

另一种方法是在onActivityCreated()中获取,其回调在onCreate()执行后再执行

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    listView = (ListView)getView().findViewById(R.id.list);//获取自己视图里的控件引用,方法二
} 

获取Activity资源,须等Activity创建完成后,必须放在onActivityCreated()回调函数中

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv); //获取其它fragment中的控件引用的唯一方法
}

总的实现示例如下:

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    mFragment2_tv = (TextView) getActivity().findViewById(R.id.fragment2_tv);//获取其它fragment中的控件引用的唯一方法
    listView = (ListView) getView().findViewById(R.id.list); //获取自己视图里的控件引用,方法二
    ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
    listView.setAdapter(arrayAdapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String str = mStrings[position];
            mFragment2_tv.setText(str);
       }
    });
}

方法三:在各自Fragment中操作

方法二在Fragment A中操作了Fragment B,违背模块分离思想,应通过Activity将其分离。在Activity中可以直接通过FragmentManager.findFragmentById()获取Fragment实例

示例

在Fragment2设置TextView函数

public class Fragment2 extends Fragment {
    private TextView mTv;
    …………
    public void setText(String text) {
        mTv.setText(text);
    }
}

在Fragment1 中定义处理方式。首先,定义接口与变量

private titleSelectInterface mSelectInterface;
public interface titleSelectInterface{
    public void onTitleSelect(String title);
}

然后,给接口变量赋值。接口给Activity使用,在Activity中给接口变量赋值,在Fragment与Activity关联时,需要强转

public void onAttach(Activity activity) {
    super.onAttach(activity);
    try {
        mSelectInterface = (titleSelectInterface) activity;
    } catch (Exception e) {
        throw new ClassCastException(activity.toString() + "must implement OnArticleSelectedListener");
    }
}

调用接口变量

public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    listView = (ListView) getView().findViewById(R.id.list);//获取自己视图里的控件引用,方法二
    ArrayAdapter arrayAdapter = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, mStrings);
    listView.setAdapter(arrayAdapter);
    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            String str = mStrings[position];
            mSelectInterface.onTitleSelect(str);
        }
    });
}

在Activity中实现接口

public class MainActivity extends FragmentActivity implements Fragment1.titleSelectInterface {
    @Override
    public void onTitleSelect(String title) {
        FragmentManager manager = getSupportFragmentManager();
        Fragment2 fragment2 = (Fragment2)manager.findFragmentById(R.id.fragment2);
        fragment2.setText(title);
    }
}
上一篇下一篇

猜你喜欢

热点阅读