使用Fragment创建动态UI

使用Fragment创建动态UI

在Android中创建动态、多栏的UI,你需要将UI组件和Activity行为封装到模块中,以便在Activity中交换。你可以使用Fragment类创建这些模块,这种行为有点像一个可以自己定义布局和管理生命周期的嵌套Activity

当一个Fragment指定它自身的布局时,它能和Activity内的其它Fragment配置成不同的组合以便为不同的屏幕大小修改你的布局结构(小屏幕一次可能只显示一个Fragment,大屏幕则可以显示两个或更多)。

本课程展示了如何使用Fragment创建动态的用户体验以及为不同屏幕大小的设备优化App的用户体验,同时继续支持运行于低至Android1.6版本的设备。

创建Fragment

你可以认为FragmentActivity的一个模块化部分,它有自己的生命周期,有自己的输入事件,并且你还可以在Activity运行时添加或移除它(有点像可以在不同的Activity中重用的“子Activity”)。这节内容展示了如何使用支持库继承Fragment,使你的App可以和运行低至Android1.6的设备保持兼容。

注意:如果你决定了App要求的最小API级别为11以上,你可以使用框架内建的Fragment类及相关的API,而不需要使用支持库。只要知道这节课的重点在于使用支持库API,它使用特定的包签名,并且某些API名称和平台包含的版本有点不同。

在你开始课程之前,你必须设置你的Android项目使用支持库。如果你之前没有使用支持库,按“安装支持库”文档所说的把你的项目设为使用v4库,你也可以使用v7 appcompat库以便在Activity中包含操作栏,v7兼容Android2.1API级别7)并且也包含Fragment API

创建Fragment

要创建Fragment,从Fragment类继承,重写关键的生命周期方法以插入App的应用逻辑,就像你在Activity类做的一样。

不同的是,创建Fragment时,你必须在onCreateView()回调方法中定义布局。事实上,这是运行Fragment所需要的唯一回调方法。下例是一个指定自己布局的简单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) {
        // 填充fragment的布局
        return inflater.inflate(R.layout.article_view, container, false);
    }
}

就像Activity一样,Fragment应该实现其它的生命周期回调方法,这样,当它在Activity中被添加或移除时,以及Activity本身在生命周期状态中切换时,允许你管理Fragment的状态。例如,ActivityonPause()被调用时,Activity中的所有Fragment也会收到onPause()的调用。

更多有关Fragment生命周期和回调方法的信息,请参考Fragment开发者指南。

使用XMLActivity添加Fragment

Fragment是可重用的、模块化的UI组件,每个Fragment类的实例都必须关联一个父FragmentActivity。你可以在Activityr的布局XML文件中定义每一个Fragment来获取这种关联。

注意:FragmentActivityj 是一个支持库提供的特殊的Activity,用来在低于API级别11的系统版本中处理Fragment。如果你支持的最低系统版本高于API级别11,你可以使用Activity

这里是一个当设备屏幕为“大”(目录名使用了“large”修饰符)时,向Activity添加两个Fragment的布局文件的示例:

res/layout-large/news_articles.xml

<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>

提示:更多有关为不同屏幕大小创建布局的内容,参看“支持不同屏幕大小”。

在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);
    }
}

如果你使用了v7 appcompat库,Activity应该继承ActionBarActivity,这是FragmentActivity的一个子类(更多信息请参见“添加操作栏”)。

注意:当你通过在布局XML文件中的声明Fragment的方式向Activity布局添加Fragment时,你不能在运行时移除Fragment。如果你打算在用户交互时交换Fragment,你必须在Activity首次启动时添加Fragment,这将在下一节演示。

构建灵活的UI

当为较大范围的屏幕尺寸设计应用程序时,你可以基于屏幕所允许的空间,在不同的布局配置中重用Fragment以优化用户体验。

例如,在手机设备上为单面板用户界面一次只显示一个Fragment,相反,在有较宽屏幕尺寸的平板上可以并排设置Fragment来为用户显示更多信息。

 

 

 

两个Fragment,用不同配置显示在不同屏幕尺寸上的同一个Activity中。在大屏幕上,两个Fragment并排显示,而在手机设备上,一次只显示一个Fragment,必须在用户导航时用一个Fragment替换另一个。

 

 

FragmentManager类提供方法允许你在运行时为Activity添加、移除以及替换Fragment,从而创建动态用户体验。

在运行时向Activity添加Fragment

相比在布局文件中为Activity定义Fragment——就象上节课中演示的使用<fragment>元素——你可以在Activity运行时添加Fragment,如果你打算在Activity生存期内改变Fragment,你必须这样做。

要执行添加或移除Fragment的事务,你必须使用FragmentManager创建一个FragTransaction,它提供了添加、移除、替换及执行其它Fragment事务的API

如果你的Activity允许移除或替换Fragment,你应该在onCreate()方法中添加初始化Fragment的代码。

在处理Fragment(尤其是那些运行时添加的)时有一个重要的规则,在布局中必须有一个容器视图供这些Fragment布局驻留。

下面的布局是一个替换在上节内容中出现的一次只显示一个Fragment的布局。为了用一个Fragment替换另一个,Activity包含一个FrameLayout做为Fragment的容器。

注意文件名和上节内容中的布局文件名相同,但是目录名中没有“large”修饰符,因此这个布局用于设备屏幕比“large”小,不能同时填充两个Fragment的情况中。

res/layout/news_articles.xml

<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" />

Activity中插入代码,调用支持库APIgetSupportFragmentManager()方法获得FragmentManager,然后调用beginTransaction()创建FragmentTransaction,调用add()方法添加Fragment

你可以使用同一个FragmentTransaction执行多个Fragment事务,当你准备好要改变时,你必须调用commit()方法。

下面是如何向Activity中添加Fragment的例子:

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);

        // 检查Activity正在使用的包含Fragment容器FrameLayout的布局版本
        if (findViewById(R.id.fragment_container) != null) {

            // 如果我们是从较早的状态中恢复
            // 我们不需要做任何事情,直接返回
            // 否则,我们可能覆盖掉已经存在的Fragment
            if (savedInstanceState != null) {
                return;
            }

            // 创建放在Activity中的新Fragment 
            HeadlinesFragment firstFragment = new HeadlinesFragment();
            
            // 在这个例子中,Activity是被Intent对象的特殊指令启动的,

      // 把Intentextras集合传递给Fragment做为参数
            firstFragment.setArguments(getIntent().getExtras());
            
            // Fragment添加到容器FrameLayout
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}

因为Fragment在运行时添加到FrameLayout容器中(替换在Activity布局文件中用<fragment>元素定义的),Activity可以移除它,并用另一个Fragment替换它。

用一个Fragment替换另一个

替换Fragment的过程就是简单的添加一个Fragment,只不过是用replace()方法代替add()方法。

请记住,当执行Fragment事务,如替换或删除Fragment时,它通常允许用户向后导航并“撤销”改变。要允许用户通过Fragment事务向后导航,你必须在提交FragmentTransaction之前调用addToBackStack()方法。

注意:当你移除或替换一个Fragment并把事务添加到返回栈时,被移除的Fragment处于停止状态(不是被销毁)。如果用户向后导航以恢复Fragment,它会重新启动。如果你没有向返回栈中添加事务,那么移除或替换Fragment时,它被销毁。

下面是替换Fragment的例子:

// 创建一个Fragment并给它指定一个要显示的文章做为参数
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// 用这个Fragment替换Fragment容器中的内容
// 向返回栈中添加事务以便用户可以向后导航
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// 提交事务
transaction.commit();

addToBackStack()方法有一个可选参数来为事务指定唯一名称,不需要这个名称,除非你打算用FragmentManager.BackStackEntry API来执行Fragment的高级操作。

和其它Fragment通信

为了重用Fragment UI组件,你应该定义一个完全独立的、模块化的组件,它定义了自己的布局和行为。一旦你定义了这些可重用的Fragment,你可以把它们和Activity结合起来,并关连应用程序逻辑以实现整体组合UI

通常你会希望一个Fragment能和其它Fragment通信,比如基于用户事件改变内容。所有FragmentFragment的通信都是通过相关的Activity,两个Fragment之间应该永远不要直接通信。

定义接口

要允许一个Fragment和它所在的Activity通信,你可以在Fragment类中定义一个接口,在Activity中实现它。Fragment会在onAttach()生命周期回调方法中捕获接口的实现,并能调用接口方法和Activity通信。

下面是FragmentActivity通信的例子:

public class HeadlinesFragment extends ListFragment {
    OnHeadlineSelectedListener mCallback;

    // 容器Activity必须实现这个接口
    public interface OnHeadlineSelectedListener {
        public void onArticleSelected(int position);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        
        // 确保容器Activity实现了回调接口, 
        // 否则,它会抛出异常
        try {
            mCallback = (OnHeadlineSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString()
                    + " must implement OnHeadlineSelectedListener");
        }
    }
    
    ...
}

现在Fragment可以使用OnHeadlineSelectedListener接口的实例mCallback调用onArticleSelected()方法把消息发送给Activity

例如,当用户点击列表项时,Fragment中的方法使用回调接品把事件发送到父Activity:

    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // 把事件发送到父Activity
        mCallback.onArticleSelected(position);
    }

实现接口

为了从Fragment中接收事件回调,父Activity必须实现在Fragment类中定义的接口。

下面是一个实现上例中接口的例子:

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(int position) {
        // 当用户选择了HeadlinesFragment的标题时, 
        // 完成显示文章的代码
    }
}

发送消息到Fragment

Activity可以把消息发给通过findFragmentById()方法捕获的Fragment实例,然后直接调用Fragment的公开方法。

例如,假设上面的Activity包含另一个Fragment,用来显示通过上面的回调方法获得的列表项数据。在该例中,Activity可以把在回调方法中收到的信息传递给另一个Fragment并显示它。

public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // 当用户选择了HeadlinesFragment的标题时, 
        // 完成显示文章的代码

        ArticleFragment articleFrag = (ArticleFragment)
                getSupportFragmentManager().findFragmentById(R.id.article_fragment);

        if (articleFrag != null) {
            // 如果显示文章的Fragment可用,我们处于双栏布局中...

            // 调用ArticleFragment中的方法,更新它的内容
            articleFrag.updateArticleView(position);
        } else {
            // 否则,我们处于单栏布局中,需要交换Fragment...

            // 创建Fragment并把所选择的文章做为它的参数
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
      FragmentTransaction transaction = 

getSupportFragmentManager().beginTransaction();

            // 用这个Fragment替换Fragment容器中的内容
            // 并在返回栈中添加事务,以便用户可以向后导航
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // 提交事务
            transaction.commit();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

魑魅魍魉9527

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值