Android-Fragments的使用

请转原文学习: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生命周期

MethodDescription
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。下面的代码是一个实现的例子:

package com.example.android.rssfeed;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailFragment extends Fragment {

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_rssitem_detail,
        container, false);
    return view;
  }

  public void setText(String item) {
    TextView view = (TextView) getView().findViewById(R.id.detailsText);
    view.setText(item);
  }
} 

4.2. 静态添加fragments

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

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

DetailFragment fragment = (DetailFragment) getFragmentManager().
   findFragmentById(R.id.detail_frag);
if (fragment==null || ! fragment.isInLayout()) {
  // start new Activity
  }
else {
  fragment.update(...);
} 
如果一个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。可以通过下面的方法来实现。

package com.example.android.rssfeed;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class MyListFragment extends Fragment {

  private OnItemSelectedListener listener;

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_rsslist_overview,
        container, false);
    Button button = (Button) view.findViewById(R.id.button1);
    button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        updateDetail();
      }
    });
    return view;
  }

  public interface OnItemSelectedListener {
    public void onRssItemSelected(String link);
  }

  @Override
  public void onAttach(Activity activity) {
    super.onAttach(activity);
    if (activity instanceof OnItemSelectedListener) {
      listener = (OnItemSelectedListener) activity;
    } else {
      throw new ClassCastException(activity.toString()
          + " must implemenet MyListFragment.OnItemSelectedListener");
    }
  }

  @Override
  public void onDetach() {
    super.onDetach();
    listener = null;
  }

  // may also be triggered from the Activity
  public void updateDetail() {
    // create a string just for testing
    String newTime = String.valueOf(System.currentTimeMillis());

    // inform the Activity about the change based
    // interface defintion
    listener.onRssItemSelected(newTime);
  }
} 

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 元素。

FragmentTransaction ft = getFragmentManager().beginTransaction();
ft.replace(R.id.your_placehodler, new YourFragment());
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

PropertyValue
Application NameRSS Reader
Project Namecom.example.android.rssfeed
Package namecom.example.android.rssfeed
TemplateBlankActivity
ActivityRssfeedActivity
Layoutactivity_rssfeed


10.2 创建一个标准的layouts

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/detailsText"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal|center_vertical"
        android:layout_marginTop="20dip"
        android:text="Default Text"
        android:textAppearance="?android:attr/textAppearanceLarge"
        android:textSize="30dip" />

</LinearLayout> 

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Press to update"
         />

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listFragment"
        android:layout_width="0dp"
        android:layout_weight="1"
        android:layout_height="match_parent"
        android:layout_marginTop="?android:attr/actionBarSize"
        class="com.example.android.rssfeed.MyListFragment" ></fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:layout_width="0dp"
        android:layout_weight="2"
        android:layout_height="match_parent"
        class="com.example.android.rssfeed.DetailFragment" >
        <!-- Preview: layout=@layout/details -->
    </fragment>

</LinearLayout> 

10.3 创建Fragment类

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

package com.example.android.rssfeed;

import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class DetailFragment extends Fragment {

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_rssitem_detail,
        container, false);
    return view;
  }

  public void setText(String item) {
    TextView view = (TextView) getView().findViewById(R.id.detailsText);
    view.setText(item);
  }
} 

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

package com.example.android.rssfeed;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

public class MyListFragment extends Fragment {
  
  private OnItemSelectedListener listener;
  
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_rsslist_overview,
        container, false);
    Button button = (Button) view.findViewById(R.id.button1);
    button.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        updateDetail();
      }
    });
    return view;
  }

  public interface OnItemSelectedListener {
      public void onRssItemSelected(String link);
    }
  
  @Override
    public void onAttach(Activity activity) {
      super.onAttach(activity);
      if (activity instanceof OnItemSelectedListener) {
        listener = (OnItemSelectedListener) activity;
      } else {
        throw new ClassCastException(activity.toString()
            + " must implemenet MyListFragment.OnItemSelectedListener");
      }
    }
  
  
  // May also be triggered from the Activity
  public void updateDetail() {
    // create fake data
    String newTime = String.valueOf(System.currentTimeMillis());
    // Send data to Activity
    listener.onRssItemSelected(newTime);
  }
} 

10.4 RssfeedActivity

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

package com.example.android.rssfeed;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;

public class RssfeedActivity extends Activity implements MyListFragment.OnItemSelectedListener{

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_rssfeed);
    }

    // if the wizard generated an onCreateOptionsMenu you can delete
    // it, not needed for this tutorial

  @Override
  public void onRssItemSelected(String link) {
    DetailFragment fragment = (DetailFragment) getFragmentManager()
            .findFragmentById(R.id.detailFragment);
        if (fragment != null && fragment.isInLayout()) {
          fragment.setText(link);
        } 
  }
    
} 

10.5 运行

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

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

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

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

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <fragment
        android:id="@+id/listFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="?android:attr/actionBarSize"
        class="com.example.android.rssfeed.MyListFragment" />
</LinearLayout> 

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment
        android:id="@+id/detailFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.example.android.rssfeed.DetailFragment" />

</LinearLayout> 

11.2 DetailActivity

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

package com.example.android.rssfeed;

import android.app.Activity;
import android.content.res.Configuration;
import android.os.Bundle;
import android.widget.TextView;

public class DetailActivity extends Activity {
  
  public static final String EXTRA_URL = "url";
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Need to check if Activity has been switched to landscape mode
    // If yes, finished and go back to the start Activity
    if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
      finish();
      return;
    }
    setContentView(R.layout.activity_detail);
    Bundle extras = getIntent().getExtras();
    if (extras != null) {
      String s = extras.getString(EXTRA_URL);
      TextView view = (TextView) findViewById(R.id.detailsText);
      view.setText(s);
    }
  }
} 

11.3 调整RssfeedActivity

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

package com.example.android.rssfeed;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.Menu;

public class RssfeedActivity extends Activity implements
    MyListFragment.OnItemSelectedListener {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_rssfeed);
  }

  @Override
  public void onRssItemSelected(String link) {
    DetailFragment fragment = (DetailFragment) getFragmentManager()
        .findFragmentById(R.id.detailFragment);
    if (fragment != null && fragment.isInLayout()) {
      fragment.setText(link);
    } else {
      Intent intent = new Intent(getApplicationContext(),
          DetailActivity.class);
      intent.putExtra(DetailActivity.EXTRA_URL, link);
      startActivity(intent);

    }
  }

} 

11.4 运行

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

Screenshot





















  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值