Fragment和Activity类似,同样是具备UI的属性;也就是都能用于规划UI布局...
Building a Dynamic UI with Fragments --> Fragments具备有动态UI的属性。为了在Android上为用户提供动态的、多窗口的交互体验,我们需要将UI组件和Activity操作封装成模块进行使用,使得我们可以在activity中对这些模块进行切入切出操作。可以用Fragment来创建这些模块,Fragment就像一个嵌套的activity,拥有自己的布局(layout)并管理自己的生命周期。
主题一:创建Fragment
You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
可能使用到的 Android API:Note: If you decide that the minimum API level your app requires is 11 or higher, you don‘t need to use the Support Library and can instead use the framework‘s built in Fragment class and related APIs. Just be aware that this lesson is focused on using the APIs from the Support Library, which use a specific package signature and sometimes slightly different API names than the versions included in the platform. 支持包中的Fragment类及相关API和Android系统内置的Fragment和API有不同。若使用Android API 11以上的系统,则不需要使用支持包。
Fragment和Activity类似,都具有自己的生命周期callbacks,以及相关的逻辑;创建Fragment实例时,首先要做的是继承Fragment类,必须覆写onCreateView(),已确定其Layout(具体的UI外表);同时该方法也是唯一需要覆写以便让Fragment运行。
import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.ViewGroup; public class ArticleFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment return inflater.inflate(R.layout.article_view, container, false); } }
Just like an activity, a fragment should implement other lifecycle callbacks that allow you to manage its state as it is added or removed from the activity and as the activity transitions between its lifecycle states. Fragment从Activity中添加或移除都能够获取到系统消息,同时执行相关的Fragment系统回调方法。
比如:如果Activity执行了onPause()回调,在Activity中的Fragment会收到消息以执行onPause()。
使用.xml文件为Activity添加Fragment实例
Fragments是可重用的、模块化的UI组件,每个Fragment的实例都必须与一个FragmentActivity关联。我们可以在Activity的XML布局文件中定义每一个fragment来实现这种关联。
Note: FragmentActivity is a special activity provided in the Support Library to handle fragments on system versions older than API level 11. If the lowest system version you support is API level 11 or higher, then you can use a regular Activity. 此处的LinearLayout相当于是一个容器,用于容纳Fragment。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent"> <fragment android:name="com.example.android.fragments.HeadlinesFragment" android:id="@+id/headlines_fragment" android:layout_weight="1" android:layout_width="0dp" android:layout_height="match_parent" /> <fragment android:name="com.example.android.fragments.ArticleFragment" android:id="@+id/article_fragment" android:layout_weight="2" android:layout_width="0dp" android:layout_height="match_parent" /> </LinearLayout>
上述是一个.xml文件实例,在Activity的.xml文件中添加了两个Fragment。并通过以下手法将该.xml文件布局到Activity中:
import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); } }
P.S. 上述MainActivity继承了FragmentActivity。Note: When you add a fragment to an activity layout by defining the fragment in the layout XML file, you cannot remove the fragment at runtime.使用静态方式(在.xml文件中定义了Fragment)添加了Fragment,不能实现动态添加和移除。
主题二:创建灵活的UI组件---Fragment
Fragment的一个可能的使用场景:When designing your application to support a wide range of screen sizes, you can reuse your fragments in different layout configurations to optimize the user experience based on the available screen space.
The FragmentManager class provides methods that allow you to add, remove, and replace fragments to an activity at runtime in order to create a dynamic experience.
运行状态下添加Fragment
这种方式不同于之前的使用.xml文件方式添加Fragment,而是可以在运行时动态进行添加和移除Fragment。This is necessary if you plan to change fragments during the life of the activity.
To perform a transaction such as add or remove a fragment, you must use the FragmentManager to create a FragmentTransaction, which provides APIs to add, remove, replace, and perform other fragment transactions.
If your activity allows the fragments to be removed and replaced, you should add the initial fragment(s) to the activity during the activity‘s onCreate() method. 如果需要在允许其移除或更换Fragment,必须要在Activity的onCreate()回调中初始化该Fragment。
An important rule when dealing with fragments—especially when adding fragments at runtime—is that your activity layout must include a container View in which you can insert the fragment. 另外一个需要注意的地方:Activity必须包含一个能够容纳Fragment的容器。
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/fragment_container" android:layout_width="match_parent" android:layout_height="match_parent" />
上述就是一个“空”容器---the fragment container
Inside your activity, call getSupportFragmentManager() to get a FragmentManager using the Support Library APIs. Then call beginTransaction() to create a FragmentTransaction and call add() to add a fragment.
You can perform multiple fragment transaction for the activity using the same FragmentTransaction. When you‘re ready to make the changes, you must call commit().
import android.os.Bundle; import android.support.v4.app.FragmentActivity; public class MainActivity extends FragmentActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); // Check that the activity is using the layout version with // the fragment_container FrameLayout if (findViewById(R.id.fragment_container) != null) { // However, if we‘re being restored from a previous state, // then we don‘t need to do anything and should return or else // we could end up with overlapping fragments. if (savedInstanceState != null) { return; } // Create a new Fragment to be placed in the activity layout HeadlinesFragment firstFragment = new HeadlinesFragment(); // In case this activity was started with special instructions from an // Intent, pass the Intent‘s extras to the fragment as arguments firstFragment.setArguments(getIntent().getExtras()); // Add the fragment to the ‘fragment_container‘ FrameLayout getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } }
将Fragment实例添加到R.id.fragment_container为标识的容器中。
运行状态下替换Fragment
The procedure to replace a fragment is similar to adding one, but requires the replace() method instead of add(). 使用的是replace()而不是add()。
Keep in mind that when you perform fragment transactions, such as replace or remove one, it‘s often appropriate to allow the user to navigate backward and "undo" the change. To allow the user to navigate backward through the fragment transactions, you must call addToBackStack() before you commit the FragmentTransaction. 为了编译较好的用户体验,需要提供“Undo”按键,方便退回到先前状态。可使用addToBackStack()方法让Fragment入栈,以便退回到先前状态。
Note: When you remove or replace a fragment and add the transaction to the back stack, the fragment that is removed is stopped (not destroyed). If the user navigates back to restore the fragment, it restarts. If you do not add the transaction to the back stack, then the fragment is destroyed when removed or replaced. 添加到BackStack的区别。
// Create fragment and give it an argument specifying the article it should show ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit();
主题三:Fragment之间的交互
为了重用Fragment UI组件,我们应该把每一个fragment都构建成完全的自包含的、模块化的组件,定义他们自己的布局与行为。定义好这些模块化的Fragment后,就可以让他们关联activity,使他们与application的逻辑结合起来,实现全局的复合的UI。
Often you will want one Fragment to communicate with another, for example to change the content based on a user event. All Fragment-to-Fragment communication is done through the associated Activity. Two Fragments should never communicate directly. Fragment之间的交互是通过其“宿主”---Activity,而不能直接进行交互。
为了让Fragment和Activity交互,可以在Fragment中定义一个Interface。
public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; // Container Activity must implement this interface public interface OnHeadlineSelectedListener { public void onArticleSelected(int position); } @Override public void onAttach(Activity activity) { super.onAttach(activity); // This makes sure that the container activity has implemented // the callback interface. If not, it throws an exception try { mCallback = (OnHeadlineSelectedListener) activity; } catch (ClassCastException e) { throw new ClassCastException(activity.toString() + " must implement OnHeadlineSelectedListener"); } } ... }
Now the fragment can deliver messages to the activity by calling the onArticleSelected() method (or other methods in the interface) using the mCallback instance of the OnHeadlineSelectedListener interface. Fragment --> Activity 信息流走向
或者是通过点击事件,将该事件传递到Activity中:
@Override public void onListItemClick(ListView l, View v, int position, long id) { // Send the event to the host activity mCallback.onArticleSelected(position); }
并让Activity实现该接口:OnHeadlineSelectedListener,并处理相关接口中的方法
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article } }
如何让信息从 Activity --> Fragment 中
The host activity can deliver messages to a fragment by capturing the Fragment instance with findFragmentById(), then directly call the fragment‘s public methods. 也就是在Activity中获取到Fragment实例,直接调用其方法即可。
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { // The user selected the headline of an article from the HeadlinesFragment // Do something here to display that article ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { // If article frag is available, we‘re in two-pane layout... // Call a method in the ArticleFragment to update its content articleFrag.updateArticleView(position); } else { // Otherwise, we‘re in the one-pane layout and must swap frags... // Create fragment and give it an argument for the selected article ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); // Replace whatever is in the fragment_container view with this fragment, // and add the transaction to the back stack so the user can navigate back transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // Commit the transaction transaction.commit(); } } }