Fragments
Design Philosophy
从android3.0开始引进,主要是为了支持在大屏幕上能够有更加灵活的UI设计。例子如下:
在设计应用的时候,就应该把fragment设计成独立可复用的模块(activity的一块组件)。在不同的屏幕上,利用不同的组合达到复用的目的。
如上,平板的Activity A包含Fragment A 和 Fragment B,手机的Activity A包含Fragment A,Activity B包含Fragment B,Fragment A和B都是可完全复用的。
Creating a Fragment
一个Fragment至少应该实现下面3个方法:
The system calls this when creating the fragment. Within your implementation, you should initialize essential components of the fragment that you want to retain when the fragment is paused or stopped, then resumed.
主要是用来初始化一些东西
The system calls this when it's time for the fragment to draw its user interface for the first time. To draw a UI for your fragment, you must return a View
from this method that is the root of your fragment's layout. You can return null if the fragment does not provide a UI.
UI的初始化,如果Fragment只是用来在后台工作没有UI,可以返回null。
The system calls this method as the first indication that the user is leaving the fragment (though it does not always mean the fragment is being destroyed). This is usually where you should commit any changes that should be persisted beyond the current user session (because the user might not come back).
数据的持久化
对话框样式的Fragment,好处在于可加入回退栈(毕竟也是一个Fragment)
Displays a floating dialog. Using this class to create a dialog is a good alternative to using the dialog helper methods in the Activity
class, because you can incorporate a fragment dialog into the back stack of fragments managed by the activity, allowing the user to return to a dismissed fragment.
Adding a user interface
创建Fragment的Layout,实例代码如下:
public static class ExampleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.example_fragment, container, false); } }
inflate() 方法的3个参数:
1.布局layout的id:
The resource ID of the layout you want to inflate.
2.布局layout的父布局,这个参数非常重要,有了这个参数才能正常测量布局的宽和高,onCreateView的container参数是从activity的layout中传递过来的(Fragment是嵌套在Activity里面的):
The ViewGroup
to be the parent of the inflated layout. Passing the container
is important in order for the system to apply layout parameters to the root view of the inflated layout, specified by the parent view in which it's going.
3.布局layout是否需要添加到父布局中,上面传递false,是因为系统已经自动添加过了:
A boolean indicating whether the inflated layout should be attached to the ViewGroup
(the second parameter) during inflation.
(In this case, this is false because the system is already inserting the inflated layout into the container
—passing true would create a redundant view group in the final layout.)
Adding a fragment to an activity
添加Fragment到Activity的Layout上有两种方式:
Declare the fragment inside the activity's layout file:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent"> <fragment android:name="com.example.news.ArticleListFragment" android:id="@+id/list" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.news.ArticleReaderFragment" android:id="@+id/viewer" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
当系统创建这个activity的layout的时候,会实例化name标签对应的fragment,并且调用其onCreateView方法,然后拿到返回的view,插入到fragment标签的位置,替换它。
每个fragment需要一个唯一标识符,当activity重启的时候以便系统恢复(或者去执行其他的事务,移除/添加某个fragment),有3中方式添加标识:
1.android:id
2.android:tag
3.如果都没有,系统会使用container view的id
Or,programmatically add the fragment to an existing ViewGroup
.
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); ExampleFragment fragment = new ExampleFragment(); fragmentTransaction.add(R.id.fragment_container, fragment); fragmentTransaction.commit();
Adding a fragment without a UI
添加一个没UI的fragment,可以使用add(Fragment, String)
方法,使用这个方法,fragment的onCreateView不会被调用,也就没必要复写。
对于没有UI的fragment,在activity里找到这个fragment只能使用findFragmentByTag(),而string tag也是唯一标识。
Supplying a string tag for the fragment isn't strictly for non-UI fragments—you can also supply string tags to fragments that do have a UI—but if the fragment does not have a UI, then the string tag is the only way to identify it. If you want to get the fragment from the activity later, you need to use findFragmentByTag()
.
Managing Fragments
- Get fragments that exist in the activity, with
findFragmentById()
(for fragments that provide a UI in the activity layout) orfindFragmentByTag()
(for fragments that do or don't provide a UI). - Pop fragments off the back stack, with
popBackStack()
(simulating a Back command by the user). - Register a listener for changes to the back stack, with
addOnBackStackChangedListener()
.
Performing Fragment Transactions
执行事务:
FragmentManager fragmentManager = getFragmentManager(); FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); // 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();
事务操作完成之后,需要调用commit方法,commit之前的所有操作,都认为是一次事务。
回退栈,addToBackStack(String name),name是fragment的别名。
当你remove一个fragment后,并commit了,如果没有调用addToBackStack(String name)方法,fragment会被destroy,如果调用了这个方法,fragment会被stop,用户按了后退键还能resume。
If you do not call addToBackStack()
when you perform a transaction that removes a fragment, then that fragment is destroyed when the transaction is committed and the user cannot navigate back to it. Whereas, if you do call addToBackStack()
when removing a fragment, then the fragment is stopped and will be resumed if the user navigates back.
Tip:setTransition()
可以设置动画。
commit(): 不会立即执行事务操作,而是等待主线程允许。
executePendingTransactions():会立即执行,一般都不需要这样,除非别的线程在等待事务的完成。
commit():这个方法必须要在activity保存自己状态之前(用户离开activity),否则会抛出异常。因为activity需要被回复的时候,commit之后的状态可能会丢失。
commitAllowingStateLoss():使用这个就不会出异常了。
Communicating with the Activity
fragment获取activity的对象:
View listView = getActivity().findViewById(R.id.list);
activity获取fragment对象:
ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment);
Creating event callbacks to the activity
使用接口来进行activity和fragment之间的通信:
public static class FragmentA extends ListFragment { ... // Container Activity must implement this interface public interface OnArticleSelectedListener { public void onArticleSelected(Uri articleUri); } ... } 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"); } } ... } 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); } ... }
显然,Activity需要实现上面的接口。
Adding items to the App Bar
Handling the Fragment Lifecycle
Managing the lifecycle of a fragment is a lot like managing the lifecycle of an activity. Like an activity, a fragment can exist in three states:
Resumed
The fragment is visible in the running activity.
Paused
Another activity is in the foreground and has focus, but the activity in which this fragment lives is still visible (the foreground activity is partially transparent or doesn't cover the entire screen).
Stopped
The fragment is not visible. Either the host activity has been stopped or the fragment has been removed from the activity but added to the back stack. A stopped fragment is still alive (all state and member information is retained by the system). However, it is no longer visible to the user and will be killed if the activity is killed.
使用onSaveInstanceState()储存恢复数据,和activity一样。
Caution:
getActivity() 有可能返回null。
If you need a Context
object within your Fragment
, you can call getActivity()
. However, be careful to call getActivity()
only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getActivity()
will return null.
fragment和activity最大的不同就是,fragment可以被添加到会退栈。
Coordinating with the activity lifecycle
fragment生命周期很多方法都是受到activity影响的,比如onPause方法等。但是fragment也有一些额外的生命周期方法:
Called when the fragment has been associated with the activity (the Activity
is passed in here).
Called to create the view hierarchy associated with the fragment.
Called when the activity's onCreate()
method has returned.
Called when the view hierarchy associated with the fragment is being removed.
Called when the fragment is being disassociated from the activity.