请转原文学习:Using Fragmenys in Android – Tutorial

 

where I found this resource:干干货分享——Android开发中学习资源大集合(译)

很素的翻译开始:

2. Fragments

2.1. 什么是fragments?

fragments是一个能够应用于Activity中的独立组件,他封装了功能,所以在activitys和layouts中更容易被复用。

fragment在activity的上下文环境中运行,但是他有自己的生命周期和自己的用户界面,定义一个fragmengts,我们也可以不需要用户界面,也就是无头fragments(headless fragments).

fragments可以被动态或者静态地添加到一个Activity中。

 

2.2. 使用fragments的好处

Fragments使得在不同的布局文件中复用组件变得更容易,你可以给手机构建单窗格布局,给平板构建多窗格布局。这并不仅限于用在平板上;例如,你也可以在智能手机上使用fragments去支持横屏和竖屏的布局。

一个典型的例子就是在Activity中项目(items)列表。在平板电脑上,如果你点某个列表项,就能立即在同屏幕右侧看到对应的详细内容(detail),而在智能手机上,你要跳转到一个新的详情页面(detail),下面是图形描述:

 

 

下面的讨论将假定你有两个fragments(main和detail),但是你也可以有更多个。我们也会有一个,main activity和一个detailed activity。在平板电脑上main activity包含了这两个Fragments,而在手机上只包含了main fragments。

下面的屏幕截图展示了这种用法:

 

 

2.3. 如何使用Fragments

用Fragments创建不同的布局,我们可以:

  •  用一个activity,在平板上显示两个Fragments,在手机上显示一个fragemtns。在这种情况下,必要的时候要转换Fragments。这要求the fragment不能在布局文件layout file中被声明,同样地Fragments不能在运行时被移除。
  • 在手机上使用不同的activities来 host每个fragment。例如,当平板上的UI实现是:在一个activity中使用了两个Fragments,在手机上使用同一个activity,但是提供另一种只包含一个fragment的布局。当你需要切换Fragments时, 需要调用另一个带有frgment的activity。(个人理解就是,在手机上一个activity用一个fragmen

第二种方法是最灵活,也是使用Fragments一般来说更可取的方法。在这种情况下,main activity检测如果detail fragment在布局文件可用。如果存在detailed fragment,the main activity通知fragment该更新自己的fragment。如果没有detail fragment, the main activity 启动 detailed activity。

3. Fragments 的生命周期

fragment的生命周期与持有他的activity的生命周期相关联

 

         

 

表一. Fragment生命周期

Method Description
onAttach() fragemnt实例关联到Activity实例,此时这个activity还未完全初始化完
onCreate() Fragment被创建
onCreateView() fragment实例创建自己的view层级(fragment第一次话他自己的用户界面时调用)the inflated view成为activity view层级的一部分
onActivityCreated() Activity和fragment的view层级创建完的同时他们的实例也已经创建完成。此时,view可以通过findviewById方法被找到
onResume() Fragment可见并且是激活状态
onPause() Fragment可见但非激活状态,例如,另一个activity覆盖在带有fragment的activity上面
onStop() Fragment不可见

 

4. 定义和使用Fragments

4.1. 定义fragments

定义一个新的fragment,你可以继承android.app.Fragment 类或他的一个子类,例如 ListFragment, DialogFragment, PreferenceFragment或者WebViewFragment。下面的代码是一个实现的例子:

 

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Fragment;
 4 import android.os.Bundle;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 import android.widget.TextView;
 9 
10 public class DetailFragment extends Fragment {
11 
12   @Override
13   public View onCreateView(LayoutInflater inflater, ViewGroup container,
14       Bundle savedInstanceState) {
15     View view = inflater.inflate(R.layout.fragment_rssitem_detail,
16         container, false);
17     return view;
18   }
19 
20   public void setText(String item) {
21     TextView view = (TextView) getView().findViewById(R.id.detailsText);
22     view.setText(item);
23   }
24 } 

4.2. 静态添加fragments

 

要使用一个新的fragment,可以静态地将他添加到一个XML布局文件中

你可以使用FragmentManager类来检查布局文件是否包含了这个fragment

1 DetailFragment fragment = (DetailFragment) getFragmentManager().
2    findFragmentById(R.id.detail_frag);
3 if (fragment==null || ! fragment.isInLayout()) {
4   // start new Activity
5   }
6 else {
7   fragment.update(...);
8 } 

如果一个fragment在XML布局文件里已经定义了, 他的android:name属性会指向相对应的类。

 

4.3 Fragment生命周期

fragment有他自己的生命周期。但总是和持有他的activity的生命周期相关联。

fragment的onCreate()方法在activity的onCreate()方法之后,该fragment的onCreateView()方法之前被调用。

当fragment开始创建他的用户界面时,系统就调用onCreateView(),在该方法里你可以通过Inflator类的对象调用inflate()方法来inflate一个布局文件,该布局文件作为inflate方法的一个参数。对于headless fragments没有必要实现该方法。

当持有该fragment的activity被创建后,onActivityCreated()方法在onCreateView()方法之后被调用。在这你可以初始化一个需要Context对象的对象。

Fragment并不是Context的子类,所以你必须通过getActivity()方法来获得父activity

一旦fragment可见,onStart()方法就会被调用

如果一个activity停止了,它的fragment也会停止;如果一个activity被销毁,它的fragment也会被销毁。

4.4 fragments之间的通讯

为了增加fragments的复用,fragment和fragment不应该直接和彼此沟通,fragemnts之间的每次沟通都应该通过持有他们的activity来完成。

为了达到该目的,fragment应该定义一个内部接口,然后要求activity必须实现该接口。该方法可以避免fragment对该activity的有任何了解(不懂怎么翻译,原文:This way you avoid that the fragment has any knowledge about the activity which uses it)。在fragment的onAttach()方法中,可以检查activity是否正确实现了该接口。

例如,假定你有一个fragment,该fragemnt要将一个值传递给他的父activity。可以通过下面的方法来实现。

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Activity;
 4 import android.app.Fragment;
 5 import android.os.Bundle;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.Button;
10 
11 public class MyListFragment extends Fragment {
12 
13   private OnItemSelectedListener listener;
14 
15   @Override
16   public View onCreateView(LayoutInflater inflater, ViewGroup container,
17       Bundle savedInstanceState) {
18     View view = inflater.inflate(R.layout.fragment_rsslist_overview,
19         container, false);
20     Button button = (Button) view.findViewById(R.id.button1);
21     button.setOnClickListener(new View.OnClickListener() {
22       @Override
23       public void onClick(View v) {
24         updateDetail();
25       }
26     });
27     return view;
28   }
29 
30   public interface OnItemSelectedListener {
31     public void onRssItemSelected(String link);
32   }
33 
34   @Override
35   public void onAttach(Activity activity) {
36     super.onAttach(activity);
37     if (activity instanceof OnItemSelectedListener) {
38       listener = (OnItemSelectedListener) activity;
39     } else {
40       throw new ClassCastException(activity.toString()
41           + " must implemenet MyListFragment.OnItemSelectedListener");
42     }
43   }
44 
45   @Override
46   public void onDetach() {
47     super.onDetach();
48     listener = null;
49   }
50 
51   // may also be triggered from the Activity
52   public void updateDetail() {
53     // create a string just for testing
54     String newTime = String.valueOf(System.currentTimeMillis());
55 
56     // inform the Activity about the change based
57     // interface defintion
58     listener.onRssItemSelected(newTime);
59   }
60 } 

5. 在fragments中存储(持久化)数据

 

5.1 应用程序重启时保存数据

在fragments中你也需要存储你的应用数据。你可以把数据存储到一个中央地区。例如:

  • SQLite database
  • File
  • 应用对象,在应用需要去处理存储的情况下

5.2配置变化时持久化数据

如果想要在配置变化时保持数据,你可以使用应用对象(application object)。

除此之外,还可以在fragments里调用setRetainState(true)方法,这种方法在配置变化时会保持fragment的实例,但是只有在fragment未加入到回退栈时才起作用。这种方式并不被Google提倡用在有用户界面的fragment上。在这种情况下,数据必须存为成员变量。

 

如果要被保存的数据由Bundle类支持,则可以使用onSaveInstanceState()方法将数据存放到Bundle,再在onActivityCreated()方法中找回数据。

6. 运行时修改Fragments

FragmentManager类和FragmentTransaction类允许你在activity的布局中添加,删除和替换fragments。

Fragments可以通过transaction来动态修改。要动态将fragments添加到现有的布局中,通常在要添加Fragment的XML布局文件中定义一个container(这个container就相当于在xml文件中的LinearLayout(or other layout)用来装载fragment),你可以使用一个FrameLayout 元素。

 1 FragmentTransaction ft = getFragmentManager().beginTransaction(); 2 ft.replace(R.id.your_placehodler, new YourFragment()); 3 ft.commit();  

 

一个新的Fragment将会替换现有的Fragment,这个现有的Fragment就是先前被添加到container里的fragment。 
如果你想要把transaction添加到Android的回退栈中,你可以使用addToBackStack()方法。这会使该操作添加到activity的历史栈中,通过返回按钮就可以恢复Fragment的变化。

 

7. Fragment transition的动画

在fragment的事物期间,可以定义动画,该动画基于Property Animation API来使用,通过调用setCustomAnimations()方法。

也可以通过调用setTransition()方法使用多种Android提供的基础动画。这些通过以FragmentTransaction为首的内容来定义。TRANSIT_FRAGMENT_*.

两种方式都允许你定义一个实体动画和一个现有动画。

8. 将Fragment transition添加到回退栈中

你可以添加一个FragmentTransition到回退栈中以便用户可以使用回退按钮倒退到这个转变。

使用FragmentTransition对象中的addToBackStack来实现。

9. Fragments的后台处理

9.1 无界面的Fragments

使用Fragments,可以不需要用户界面。

实现一个无界面的fragment,只需要在fragment里的onCreateView()方法中返回null即可。

Tip:建议在使用无界面的fragment来处理后台操作时结合setRetainInstance()方法来使用,通过这种方式,在异步操作的时候就无需处理配置变化了(configuration changes)

9.2 无界面fragments处理配置变化

无界面的fragment通常用于封装一些配置变化的状态或者用于一个后台处理任务。因此,你应该设置无界面的fragment处于被保持(retained)的状态。一个被保持的fragment在配置变化期间不会被销毁。

 

Retained headless fragment

 

设置fragment为被保持,调用setRetainInsatance()方法即可。

你可以使用FragmentManager类的add()方法来添加这样的fragment到activity中。如果之后你还会查找到该Fragment,你需要给这个fragment添加一个标签tag,这样就可以使用FragmentManager中的findFragmentByTag()方法来查找到它。

Warging:onRetainNonConfigurationInstance()已废弃,should be replaced by retained headless fragments。

 

10. Fragments 教程

10.1 概览

接下来的教程说明了怎样使用fragment。应用将会根据横屏模式和竖屏模式分别使用带有fragment的不同布局。

在竖屏模式下,RssfeedActivity会显示一个Fragment。在这个fragment中用户可以跳转到另外一个带有fragment的activity。

在横屏模式下,RssfeedActivity会并列的显示两个fragments

 

1. 创建项目

 

按照下列内容创建一个新的Android Project。

 

Table 2. Android project

Property Value
Application Name RSS Reader
Project Name com.example.android.rssfeed
Package name com.example.android.rssfeed
Template BlankActivity
Activity RssfeedActivity
Layout activity_rssfeed

 

10.2 创建一个标准的layouts

在res/layout文件夹下创建或者改变以下布局文件。

创建一个新的布局文件:fragment_rssitem_detail.xml.该布局文件被DetailFragment使用。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <TextView
 8         android:id="@+id/detailsText"
 9         android:layout_width="wrap_content"
10         android:layout_height="match_parent"
11         android:layout_gravity="center_horizontal|center_vertical"
12         android:layout_marginTop="20dip"
13         android:text="Default Text"
14         android:textAppearance="?android:attr/textAppearanceLarge"
15         android:textSize="30dip" />
16 
17 </LinearLayout> 

创建一个新的布局文件:fragment_rsslist_overview.xml,该布局文件用于MyListFragment类。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <Button
 8         android:id="@+id/button1"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:text="Press to update"
12          />
13 
14 </LinearLayout> 

 

修改现有的activity_rssfeed.xml文件。该布局是RssfeedActivity默认的布局文件,她展示两个fragments。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="fill_parent"
 4     android:layout_height="fill_parent"
 5     android:orientation="horizontal" >
 6 
 7     <fragment
 8         android:id="@+id/listFragment"
 9         android:layout_width="0dp"
10         android:layout_weight="1"
11         android:layout_height="match_parent"
12         android:layout_marginTop="?android:attr/actionBarSize"
13         class="com.example.android.rssfeed.MyListFragment" ></fragment>
14 
15     <fragment
16         android:id="@+id/detailFragment"
17         android:layout_width="0dp"
18         android:layout_weight="2"
19         android:layout_height="match_parent"
20         class="com.example.android.rssfeed.DetailFragment" >
21         <!-- Preview: layout=@layout/details -->
22     </fragment>
23 
24 </LinearLayout> 

 

 

10.3 创建Fragment类

接下来要创建Fragment类,由DetailFragment类开始

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Fragment;
 4 import android.os.Bundle;
 5 import android.view.LayoutInflater;
 6 import android.view.View;
 7 import android.view.ViewGroup;
 8 import android.widget.TextView;
 9 
10 public class DetailFragment extends Fragment {
11 
12   @Override
13   public View onCreateView(LayoutInflater inflater, ViewGroup container,
14       Bundle savedInstanceState) {
15     View view = inflater.inflate(R.layout.fragment_rssitem_detail,
16         container, false);
17     return view;
18   }
19 
20   public void setText(String item) {
21     TextView view = (TextView) getView().findViewById(R.id.detailsText);
22     view.setText(item);
23   }
24 } 

 

创建MyListFragment类,先不管这个类的名字,他不会像他的命名一样展示一个列表,先用一个Button来代替,点击该按钮会将当前时间传递给details fragemnt。

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Activity;
 4 import android.app.Fragment;
 5 import android.os.Bundle;
 6 import android.view.LayoutInflater;
 7 import android.view.View;
 8 import android.view.ViewGroup;
 9 import android.widget.Button;
10 
11 public class MyListFragment extends Fragment {
12   
13   private OnItemSelectedListener listener;
14   
15   @Override
16   public View onCreateView(LayoutInflater inflater, ViewGroup container,
17       Bundle savedInstanceState) {
18     View view = inflater.inflate(R.layout.fragment_rsslist_overview,
19         container, false);
20     Button button = (Button) view.findViewById(R.id.button1);
21     button.setOnClickListener(new View.OnClickListener() {
22       @Override
23       public void onClick(View v) {
24         updateDetail();
25       }
26     });
27     return view;
28   }
29 
30   public interface OnItemSelectedListener {
31       public void onRssItemSelected(String link);
32     }
33   
34   @Override
35     public void onAttach(Activity activity) {
36       super.onAttach(activity);
37       if (activity instanceof OnItemSelectedListener) {
38         listener = (OnItemSelectedListener) activity;
39       } else {
40         throw new ClassCastException(activity.toString()
41             + " must implemenet MyListFragment.OnItemSelectedListener");
42       }
43     }
44   
45   
46   // May also be triggered from the Activity
47   public void updateDetail() {
48     // create fake data
49     String newTime = String.valueOf(System.currentTimeMillis());
50     // Send data to Activity
51     listener.onRssItemSelected(newTime);
52   }
53 } 

 

10.4 RssfeedActivity

 

按照下面的代码来修改RssfeedActivity。

 1 package com.example.android.rssfeed;
 2 
 3 import android.os.Bundle;
 4 import android.app.Activity;
 5 import android.view.Menu;
 6 
 7 public class RssfeedActivity extends Activity implements MyListFragment.OnItemSelectedListener{
 8 
 9     @Override
10     protected void onCreate(Bundle savedInstanceState) {
11         super.onCreate(savedInstanceState);
12         setContentView(R.layout.activity_rssfeed);
13     }
14 
15     // if the wizard generated an onCreateOptionsMenu you can delete
16     // it, not needed for this tutorial
17 
18   @Override
19   public void onRssItemSelected(String link) {
20     DetailFragment fragment = (DetailFragment) getFragmentManager()
21             .findFragmentById(R.id.detailFragment);
22         if (fragment != null && fragment.isInLayout()) {
23           fragment.setText(link);
24         } 
25   }
26     
27 } 

 

10.5 运行

 

运行该例子。在横屏模式或者竖屏模式都会显示两个fragments。如果点击了ListFragment中的按钮,DetailFragment上的当前时间会被更新。

 

11. Fragments 教程-竖屏模式的布局

11.1 为竖屏模式创建布局文件

RssfeedActivity在竖屏模式下应该使用特定的布局文件。在竖屏模式Android会检查有没有layout-port文件夹,该文件夹下是否有合适的布局文件。如果Android没有找到符合的布局文件,他就会去使用layout文件夹。

因此我们要创建res/layout-port文件夹。之后在res/layout-port文件夹下创建activity_rssfeed.xml布局文件。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="horizontal" >
 6 
 7     <fragment
 8         android:id="@+id/listFragment"
 9         android:layout_width="match_parent"
10         android:layout_height="match_parent"
11         android:layout_marginTop="?android:attr/actionBarSize"
12         class="com.example.android.rssfeed.MyListFragment" />
13 </LinearLayout> 

 

 

继续创建activity_detail.xml。该布局文件用户DetailActivity。注意我们本可以在res/layout文件夹下创建该文件,但是它只用于竖屏模式下,因此我们把它放到这个文件夹下(res/layout-port)。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="match_parent"
 5     android:orientation="vertical" >
 6 
 7     <fragment
 8         android:id="@+id/detailFragment"
 9         android:layout_width="match_parent"
10         android:layout_height="match_parent"
11         class="com.example.android.rssfeed.DetailFragment" />
12 
13 </LinearLayout> 

 

11.2 DetailActivity

按照下面的类,重新创建DetailActivity

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Activity;
 4 import android.content.res.Configuration;
 5 import android.os.Bundle;
 6 import android.widget.TextView;
 7 
 8 public class DetailActivity extends Activity {
 9   
10   public static final String EXTRA_URL = "url";
11   
12   @Override
13   protected void onCreate(Bundle savedInstanceState) {
14     super.onCreate(savedInstanceState);
15 
16     // Need to check if Activity has been switched to landscape mode
17     // If yes, finished and go back to the start Activity
18     if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
19       finish();
20       return;
21     }
22     setContentView(R.layout.activity_detail);
23     Bundle extras = getIntent().getExtras();
24     if (extras != null) {
25       String s = extras.getString(EXTRA_URL);
26       TextView view = (TextView) findViewById(R.id.detailsText);
27       view.setText(s);
28     }
29   }
30 } 

 

11.3 调整RssfeedActivity

 

调整RssfeedActivity类来显示DetailActivity以防另一个fragment不在该布局内

 1 package com.example.android.rssfeed;
 2 
 3 import android.app.Activity;
 4 import android.content.Intent;
 5 import android.os.Bundle;
 6 import android.view.Menu;
 7 
 8 public class RssfeedActivity extends Activity implements
 9     MyListFragment.OnItemSelectedListener {
10 
11   @Override
12   protected void onCreate(Bundle savedInstanceState) {
13     super.onCreate(savedInstanceState);
14     setContentView(R.layout.activity_rssfeed);
15   }
16 
17   @Override
18   public void onRssItemSelected(String link) {
19     DetailFragment fragment = (DetailFragment) getFragmentManager()
20         .findFragmentById(R.id.detailFragment);
21     if (fragment != null && fragment.isInLayout()) {
22       fragment.setText(link);
23     } else {
24       Intent intent = new Intent(getApplicationContext(),
25           DetailActivity.class);
26       intent.putExtra(DetailActivity.EXTRA_URL, link);
27       startActivity(intent);
28 
29     }
30   }
31 
32 } 

11.4 运行

 

运行你的例子。如果在竖屏模式下运行应用,你应该只看到一个Fragment,使用Ctrl+F11快捷键来改变方向。在横向模式下你会看到两个fragments。如果你点击竖屏模式的按钮,新的DetailActivity会被重启,并且显示当前的时间。在横屏模式下两个fragment都会被看到。

版权声明:本文为itachi2原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/itachi2/articles/5027084.html