Android 进阶 Fragment 介绍和使用 (二)

时间:2021-08-27 14:28:38

管理fragment

 因为FragmentManager的API是在Android 3.0,也即API level 11开始引入的,所以对于之前的版本,需要使用support library中的FragmentActivity,并且使用getSupportFragmentManager()方法。

用FragmentManager可以做的工作有:

  得到Activity中存在的fragment:

  使用findFragmentById()或findFragmentByTag()方法。

  将fragment弹出back stack:

  popBackStack():将back stack中最后一次的fragment转换弹出。如果没有可以出栈的东西,返回false。

  这个函数是异步的:它将弹出栈的请求加入队列,但是这个动作直到应用回到事件循环才会执行。

  为back stack加上监听器:

  addOnBackStackChangedListener()

Fragment Transactions 事务

  使用Fragment时,可以通过用户交互来执行一些动作,比如增加、移除、替换等。

  所有这些改变构成一个集合,这个集合被叫做一个transaction。

  可以调用FragmentTransaction中的方法来处理这个transaction,并且可以将transaction存进由activity管理的back stack中,这样用户就可以进行fragment变化的回退操作。

  可以这样得到FragmentTransaction类的实例: 

FragmentManager fragmentManager = getFragmentManager();

FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每个transaction是一组同时执行的变化的集合。

  用add(), remove(), replace()方法,把所有需要的变化加进去,然后调用commit()方法,将这些变化应用。

  在commit()方法之前,你可以调用addToBackStack(),把这个transaction加入back stack中去,这个back stack是由activity管理的,当用户按返回键时,就会回到上一个fragment的状态。

  比如下面的代码就是用一个新的fragment取代之前的fragment,并且将前次的状态存储在back stack中。

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null); // Commit the transaction
transaction.commit();

在这个例子中,newFragment将取代在R.id.fragment_container容器中的fragment,如果没有,将直接添加新的fragment。

  通过调用addToBackStack(),commit()的一系列转换作为一个transaction被存储在back stack中,用户按Back键可以返回上一个转换前的状态。

  当你移除一个fragment的时候,如果commit()之前没有调用addToBackStack(),那个fragment将会是destroyed;如果调用了addToBackStack(),这个fragment会是stopped,可以通过返回键来恢复。

关于commit()方法

1你必须最后调用commit()。

2如果你添加了多个fragment,那么它们的显示顺序跟添加顺序一至(后显示的覆盖前面的)。

如果你在执行的事务中有删除fragment的动作,而且没有调用addToBackStack(),那么当事务提交时,那些被删除的fragment就被销毁了。反之,那些fragment就不会被销毁,而是处于停止状态。当用户返回时,它们会被恢复。

密技:对于fragment事务,你可以应用动画。在commit()之前调用setTransition()就行。――一般银我不告诉他哦。

但是,调用commit()后,事务并不会马上执行。它会在activity的UI线程(其实就是主线程)中等待直到线程能执行的时候才执行(废话)。如果必要,你可以在UI线程中调用executePendingTransactions()方法来立即执行事务。但一般不需这样做,除非有其它线程在等待事务的执行。

警告:你只能在activity处于可保存状态的状态时,比如running中,onPause()方法和onStop()方法中提交事务,否则会引发异常。这是因为fragment的状态会丢失。如果要在可能丢失状态的情况下提交事务,请使用commitAllowingStateLoss()。

实例程序

  写了个小程序实践了一下fragment的管理,程序不是很完善,就是试试基本用法,先按第一个按钮添加一个fragment,第二个按钮将添加第二个fragment,按删除按钮删除相应fragment。

 相关代码:

  第一个fragment:

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; public class FirstFragment extends Fragment { @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); } @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_first, container, false);
}
}

布局:

<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"
tools:context="com.jcdh.jcli.myapplication.FirstFragment"> <!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="20sp"
android:gravity="center"
android:background="@android:color/black"
android:textColor="@android:color/white"
android:text="我是第一个Fragment" /> </FrameLayout>

第二个Fragment

import android.os.Bundle;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; public class SecondFragment extends Fragment { @Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); } @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_second, container, false);
}
}

布局:

<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"
tools:context="com.jcdh.jcli.myapplication.FirstFragment"> <!-- TODO: Update blank fragment layout -->
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="30sp"
android:gravity="center"
android:background="@android:color/darker_gray"
android:text="我是第二个Fragment" /> </FrameLayout>

主Actiivty:

import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Toast; public class MainActivity extends Activity { private FirstFragment fristFragment;
private SecondFragment secondFragment; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
} public void moveToFragment(View view) {
// 开启Fragment事务
FragmentManager fm = getFragmentManager();
FragmentTransaction transaction = fm.beginTransaction();
switch (view.getId()) {
case R.id.first_btn:
if (fristFragment == null) {
fristFragment = new FirstFragment();
transaction.replace(R.id.first_fragment, fristFragment);
} else {
Toast.makeText(MainActivity.this, "已经添加过了fristFragment,不能重复添加", Toast.LENGTH_SHORT).show();
return;
} break;
case R.id.second_btn:
if (secondFragment == null) {
secondFragment = new SecondFragment(); transaction.replace(R.id.first_fragment, secondFragment); } else {
Toast.makeText(MainActivity.this, "已经添加过了fristFragment,不能重复添加", Toast.LENGTH_SHORT).show();
return;
} break;
}
transaction.addToBackStack(null);
transaction.commit();
} public void removeFragment(View v) {
if (getFragmentManager().getBackStackEntryCount() == 0) {
Toast.makeText(MainActivity.this, "没有 Fragment 可以被 删除, ", Toast.LENGTH_SHORT).show();
return;
}
FragmentTransaction transaction = getFragmentManager().beginTransaction();
switch (v.getId()) {
case R.id.button: if (null != fristFragment) {
transaction.remove(fristFragment);
fristFragment = null;
Toast.makeText(MainActivity.this, "删除fristFragment, ", Toast.LENGTH_SHORT).show();
} else { Toast.makeText(MainActivity.this, "没有fristFragment可以被删除 ", Toast.LENGTH_SHORT).show();
} break;
case R.id.button2:
if (null != secondFragment) {
transaction.remove(secondFragment);
secondFragment = null;
Toast.makeText(MainActivity.this, "删除secondFragment, ", Toast.LENGTH_SHORT).show();
}
else
{
Toast.makeText(MainActivity.this, "没有删除secondFragment可以被删除 ", Toast.LENGTH_SHORT).show();
}
break;
}
transaction.commit();// 一定要提交才能生效
}
}

Actiivity 布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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"
tools:context="com.jcdh.jcli.myapplication.MainActivity"> <Button
android:id="@+id/first_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:onClick="moveToFragment"
android:text="第一个Fragment" /> <Button
android:id="@+id/second_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/first_btn"
android:onClick="moveToFragment"
android:layout_alignParentTop="true"
android:text="第二个Fragment" /> <fragment
android:id="@+id/first_fragment"
android:name="com.jcdh.jcli.myapplication.FirstFragment"
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_below="@+id/first_btn">
</fragment> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="del \nfirstfragment"
android:id="@+id/button"
android:onClick="removeFragment"
android:layout_alignParentBottom="true"
android:layout_alignParentRight="true"
/> <Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="del \nsecondfragment"
android:id="@+id/button2"
android:onClick="removeFragment"
android:layout_alignBottom="@+id/button"
android:layout_alignParentLeft="true"
/>
</RelativeLayout>

效果图:

Android 进阶 Fragment 介绍和使用 (二)

Fragment和Activity的交互

  一个Fragment的实例总是和包含它的Activity直接相关。

  fragment可以通过getActivity() 方法来获得Activity的实例,然后就可以调用一些例如findViewById()之类的方法。

  如:

  View listView =getActivity().findViewById(R.id.list);

  但是注意调用getActivity()时,fragment必须和activity关联(attached to an activity),否则将会返回一个null。

  相似的,activity也可以获得一个fragment的引用,从而调用fragment中的方法。

  获得fragment的引用要用FragmentManager,之后可以调用findFragmentById() 或者 findFragmentByTag().

  比如:

  ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);

创建事件回调

  一些情况下,可能需要fragment和activity共享事件,一个比较好的做法是在fragment里面定义一个回调接口,然后要求宿主activity实现它。

  当activity通过这个接口接收到一个回调,它可以同布局中的其他fragment分享这个信息。

  例如,一个新闻显示应用在一个activity中有两个fragment,一个fragment A显示文章题目的列表,一个fragment B显示文章。

  所以当一个文章被选择的时候,fragment A必须通知activity,然后activity通知fragment B,让它显示这篇文章。

  这个情况下,在fragment A中声明一个这样的接口OnArticleSelectedListener:

public static class FragmentA extends ListFragment {
...
// Container Activity must implement this interface
public interface OnArticleSelectedListener {
public void onArticleSelected(Uri articleUri);
}
...
}

之后包含这个fragment的activity实现这个OnArticleSelectedListener接口,用覆写的onArticleSelected()方法将fragment A中发生的事通知fragment B。

为了确保宿主activity实现这个接口,fragment A的onAttach() 方法(这个方法在fragment
被加入到activity中时由系统调用)中通过将传入的activity强制类型转换,实例化一个OnArticleSelectedListener对象:

public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnArticleSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
}
}
...
}

如果activity没有实现这个接口,fragment将会抛出ClassCastException异常,如果成功了,mListener将会是activity实现OnArticleSelectedListener接口的一个引用,所以通过调用OnArticleSelectedListener接口的方法,fragment
A可以和activity共享事件。

比如,如果fragment A是ListFragment的子类,每一次用户点击一个列表项目,系统调用fragment中的onListItemClick() 方法,在这个方法中可以调用onArticleSelected()方法与activity共享事件。

public static class FragmentA extends ListFragment {
OnArticleSelectedListener mListener;
...
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Append the clicked item's row ID with the content provider Uri
Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI, id);
// Send the event and Uri to the host activity
mListener.onArticleSelected(noteUri);
}
...
}

效果图片:

Android 进阶 Fragment 介绍和使用 (二)

处理Fragment的生命周期

三种停留状态

  管理fragment的生命周期和管理activity的生命周期类似,和activity一样,fragment可以在三种状态下停留:

  Resumed

  fragment在running的activity中可见。

  Paused

  另一个activity在前景运行,并且享有焦点,但是这个fragment所在的activity仍然可见(前景activity部分遮挡或者是半透明的)。

  Stopped

  fragment不可见。可能是因为宿主activity处于stopped状态,或者fragment被remove掉,然后加在了back stack中。

  一个处于stopped状态的activity还是存活状态的,所有的状态和成员信息会被系统保持。但是,它不再被用户可见,并且如果宿主activity被kill掉,它也会被kill掉。

数据存储和恢复

  和Activity类似,可以用Bundle类对象保存fragment的状态,当activity的进程被kill之后,需要重建activity时,可以用于恢复fragment的状态。

  存储时利用onSaveInstanceState()回调函数,恢复时是在 onCreate()onCreateView(),
或者onActivityCreated()里。

Back Stack

  activity和fragment生命周期最重要的不同之处是它们如何存储在各自的back stack中。

  Activity停止时,是存在一个由系统维护的back stack中,但是当fragment停止(被remove)时,需要程序员显示地调用addToBackStack() ,并且fragment是存在一个由宿主activity掌管的back
stack中。

Fragment和Activity的生命周期

  宿主activity的声明周期直接影响到fragment的生命周期,比如activity生命周期的回调函数调用时,所有在其中的fragment的相同的回调函数会同时被调用。

Activity直接影响它所包含的fragment的生命周期,所以对activity的某个生命周期方法的调用也会产生对fragment相同方法的调用。例如:当activity的onPause()方法被调用时,它所包含的所有的fragment们的onPause()方法都会被调用。

Fragment比activity还要多出几个生命周期回调方法,这些额外的方法是为了与activity的交互而设立,如下:

onAttach()

当fragment被加入到activity时调用(在这个方法中可以获得所在的activity)。

onCreateView()

当activity要得到fragment的layout时,调用此方法,fragment在其中创建自己的layout(界面)。

onActivityCreated()

当activity的onCreated()方法返回后调用此方法。

onDestroyView()

当fragment的layout被销毁时被调用。

onDetach()

当fragment被从activity中删掉时被调用。

一旦activity进入resumed状态(也就是running状态),你就可以*地添加和删除fragment了。因此,只有当activity在resumed状态时,fragment的生命周期才能独立的运转,其它时候是依赖于activity的生命周期变化的

如图:

Android 进阶 Fragment 介绍和使用 (二)

第一个demo:

http://download.csdn.net/detail/q610098308/9311169

第二个demo activity 与 fragment 交互:

http://download.csdn.net/detail/q610098308/9312377