Android Training - 使用碎片创建一个动态UI

Android提供一个JAR文件的支持库,允许你在比较低的版本中使用一些新版本的API。例如,支持库提供了碎片API让你在Android1.6中可以使用。


这个课程教你怎么设置支持库,使用碎片创建一个动态程序UI。

使用支持库建立你的工程

  1. 使用SDK管理器下载支持库。

  2. 在工程更目录创建libs文件夹。
  3. 把JAR文件复制到libs目录中。
    比如,API级别4的支持库的位置是:/extras/android/support/v4/android-support-v4.jar
  4. 修改你的清单文件,设置最低版本API级别为4,目标版本级别为最新:
     android:minSdkVersion="4" android:targetSdkVersion="15" />

引用支持库API

支持库包含的各种API要么被已经被添加到最新的android版本中,要么更本不存在于平台中,而仅仅为低版本开发特别特征提供额外的支持。

你可以在 android.support.v4.*找到平台文档中支持库的API参考文档。

警告:确保你不是在老版本中偶尔使用新的API,确定你从android.support.v4.app包中引用Fragment类和相关API。
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
...
当使用支持库创建一个主碎片activity,你必须继承FragmentActivity类,而不是继承传统的Activity类。下一个课程有更多介绍。
你可以认为碎片是部分activity的模块化。它有自己的生命周期,接受自己输入事件,可以在activity运行时被添加和删除(类似一个子activity,你可以在不同的activity中复用)。这个课程展示了怎么使用支持库扩展Fragment类,让Android1.6这样的老版本也能兼容你的程序。

提示:如果你的最小API级别是11或者更高,你就不需要使用支持库,可以直接使用框架构建Fragment类和相关API。这个课程关注的是使用支持库中的API,和那些直接包含在平台中的版本,这些API使用特别的包名和稍微不同的API名称。

创建一个碎片类

创建一个碎片需要扩展Fragment类,在程序中覆盖关键生命周期方法,类似于使用Activity类。

一个不同点是,你需要在onCreateView()回调中定义布局,事实上,这是让碎片运行起来唯一一个使用的回调。例如,下面是一个简单碎片,指定了它自己的布局:
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) {
        // 给碎片填充布局
        return inflater.inflate(R.layout.article_view, container, false);
    }
}
和activity一样,一个fragment需要实现其他生命周期回调,允许你管理它从activity被添加或者删除时的状态,像activity转换她的生命周期状态一样。例如,当activity的onPause()函数被调用时,activity中所有的碎片也都会接收一个onPause()调用。

更多关于碎片生命周期函数的信息,可以参考这里:   Fragments

使用XML添加一个碎片到activity中

虽然碎片是可重用,模块化的UI组件,每个Fragment类的实例都必须关联一个父类FragmentActivity。你可以定义每个碎片在你的activity布局XML文件中来实现这个联系。

提示:FragmentActivity是一个特别的activity,支持库提供它来管理API级别11以下的碎片。如果你支持的最低版本是11或者更高,你可以直接使用Activity。

下面是一个布局文件的实例,在设备是large屏幕(在目录名中指定了large限定符)时,添加两个碎片到一个activity中。

res/layout-large/news_articles.xml:
 xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent">

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

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

补充:更多关于不同尺寸布局文件的支持,请看   Supporting Different Screen Sizes .

这里是一个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);
    }
}
提示:当你在布局XML文件中为activity添加碎片时,你不可以在运行时移除碎片。如果你打算在用户交互时换入和换出碎片,你必须在activity第一启动时添加碎片,请看下一课。


当设计你的程序去支持大部分屏幕尺寸时,你可以在不同的布局文件中复用你的碎片,在不同的屏幕空间中优化你的用户体验。


例如,手机中可能一次只适合显示一个碎片,相反的,在大屏幕设备中,你可能想在一个界面中挨着显示很多个碎片。

图解:一个activity在不同屏幕尺寸的显示效果。在大屏幕中,两个碎片紧挨着显示,在手机中,一次只显示一个碎片。

FragmentManager类提供一些函数,让你可以在程序运行时添加,删除和替换碎片,从而产生动态显示效果。

运行中添加一个碎片

上一个教程中,我们在XML文件中添加碎片,不同的是,你现在可以在activity运行期间添加一个碎片,在activity生命周期中改变碎片是非常有必要的。

要实现碎片的添加和删除,你需要使用FragmentManager去创建一个FragmentTransaction,它提供API去添加,删除,替换和执行其他碎片转换。

如果你想允许碎片被移除和替换,你需要在activity的onCreate()函数中初始化碎片。

一个重要的规定是:要操作碎片,尤其是运行中添加碎片,碎片必须有一个view容器,这个容器包含了碎片的布局。

下面这个布局和上一个课程的有所不同,它一次只显示一个碎片。为了能用其他碎片替换这一个碎片,activity布局需要包含一个空的FrameLayout碎片容器。

需要提醒的是,下面的文件名和上一课的一样,但是目录没有large限定符,所以,这个布局是当设备屏幕尺寸小于large的时候才会被应用,因为屏幕太小,不够同时显示多个碎片。

res/layout/news_articles.xml:
 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中,调用getSupportFragmentManager()去取得一个FragmentManager。然后调用beginTransaction()去创建一个FragmentTransaction,再调用add()去添加一个碎片。

你可以使用FragmentTransaction去处理多个碎片,当你确认好要改变碎片时,你必须调用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);

        // 检查activity使用的布局中是否有碎片容器
        if (findViewById(R.id.fragment_container) != null) {

            // 如果我们只是恢复先前的状态,
            // 那么我们什么都不用做,只是返回或者我们可以覆盖先前的碎片
            if (savedInstanceState != null) {
                return;
            }

            // 创建一个碎片实例
            HeadlinesFragment firstFragment = new HeadlinesFragment();
            
            // 使用特别的指令从一个Intent开始一个activity,
            // 把Intent中附加的数据做为碎片的参数。
            firstFragment.setArguments(getIntent().getExtras());
            
            // 添加碎片到碎片容器中。
            getSupportFragmentManager().beginTransaction()
                    .add(R.id.fragment_container, firstFragment).commit();
        }
    }
}
因为随便是在运行中被添加到FrameLayout容器中的,所以activity可以用一个不同的碎片替换它,或者是删除它。

用另外一个碎片替换当前碎片

替换一个碎片的步骤和添加一样简单,只需要把add改为replace就可以了。

记住,当你进行碎片操作时,比如替换或者删除,你需要让用户可以返回和撤销改变。为了实现这些,你需要在提交碎片操作前调用addToBackStack()。

提示:当你删除或者替换一个碎片,并且添加了这些操作在后退堆栈,碎片移除会被停止(没有被销毁),如果用户想返回恢复碎片,它就会被重新开启。如果没有加入后退堆栈,这个碎片就会在替换或者删除的时候被销毁。

替换的例子:
// 创建一个碎片,传递一个参数给它,指定显示的内容。
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);

FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

// 替换碎片容器中的碎片,
// 添加操作到后退堆栈中,以便用户可以返回。
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// 提交操作
transaction.commit();
addToBackStack()函数需要传递一个字符串参数,为操作指定一个独一无二的名称,这个名称不是必须的,除非你计划开始使用FragmentManager.BackStackEntryAPI执行高级的碎片操作。


了能重复使用碎片UI组件,你需要创建一个完全独立,模块化的组件,这个组件可以定义自己的样式和行为。一旦你定义了一个可重用的碎片,你可以使用一个activity关联它们,通过程序逻辑链接它们成为一个完整的组合UI。

通常你希望碎片间可以通信,比如基于用户事件改变内容。所有碎片间的通信都是通过activity实现,两个碎片不能直接通信。

定义一个接口

为了实现碎片和activity的通信,你可以在碎片类中定义一个接口,然后在activity中实现这个接口。碎片可以在onAttach()生命周期函数中取得接口的实现,然后调用接口方法实现通信。

这里是一个碎片和activity通信的例子:
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");
        }
    }
    
    ...
}
现在碎片可以使用OnHeadloneSelectedListener接口的mCallback实例调用onArticleSelected()函数(或者接口中其他函数)发送信息给activity了。

例如,当用户点击list选项时,碎片中的下面这个方法会被调用。碎片使用回调接口发送事件给父类activity。
    @Override
    public void onListItemClick(ListView l, View v, int position, long id) {
        // Send the event to the host activity
        mCallback.onArticleSelected(position);
    }

实现接口

为了从碎片中接收事件回调,拥有这个碎片的activity必须实现定义在碎片类中的接口。

例如,下面的代码实现了上面例子中定义的接口:
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...
    
    public void onArticleSelected(Uri articleUri) {
        // The user selected the headline of an article from the HeadlinesFragment
        // Do something here to display that article
    }
}

发送一个信息给碎片

通过findFragmentById()取得Fragment实例,然后activity就可以直接调用碎片的公共函数发送信息给这个fragment。

例如,上面的函数让activity显示了一个碎片选项的数据,activity也可以通过信息接收函数去显示其他碎片数据:
public static class MainActivity extends Activity
        implements HeadlinesFragment.OnHeadlineSelectedListener{
    ...

    public void onArticleSelected(int position) {
        // 用户从文章标题碎片选择标题
        // 这里可以写一些显示文章的代码

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

        if (articleFrag != null) {
            // 如果是文章显示页面碎片

            // 呼叫一个更新内容的方法
            articleFrag.updateArticleView(position);
        } else {
            // 否则我们在第一个布局页面

            // 创建一个碎片,给选择项传递一个参数
            ArticleFragment newFragment = new ArticleFragment();
            Bundle args = new Bundle();
            args.putInt(ArticleFragment.ARG_POSITION, position);
            newFragment.setArguments(args);
        
            FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

            // 替换碎片的内容,
            // 添加改变到回退堆栈,以便用户返回
            transaction.replace(R.id.fragment_container, newFragment);
            transaction.addToBackStack(null);

            // 提交更改
            transaction.commit();
        }
    }
}


你可能感兴趣的:(Android)