管理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 介绍和使用 (二)_android

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 介绍和使用 (二)_android_02

处理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 介绍和使用 (二)_xml_03