第二部分:开发简要指南-第六章 使用Fragments构建动态UI
第6章 使用Fragments构建动态UI
为了在Android创建一个动态的多面的用户界面,你需要封装UI组件和activity的行为到一种可以相互交换的act的模块中。我们能使用Fragment类创建这些模块,这行为有点像一个嵌套的act,它可以定义自己的布局和管理自己的生命周期。Fragment的好处已经越发明显,它是Android3.0新增的API。当一个fragment指定它的布局,它能以不同的组合配置到act中,为不同的屏幕大小修改你的布局配置,一个小屏幕可能只显示一个fragment,而在大屏幕中可能显示2个或2个以上的fragment。本章说明怎样使用fragment创建动态的用户体验并为不同屏幕大小的设备优化用户体验,同时继续支持例如Android1.6这样的老版本。以下是内容预览:
1. 使用Android支持的库
通过绑定Android支持库,学习怎样使用近期新版本的APIs
2. 创建一个Fragment
学习怎样创建fragment并实现一些基本行为
3.构建一个灵活的UI
学习怎样为不同的屏幕提供不同的fragment配置布局
4. 与其他Fragment通信
学习如何为fragment设置通信路径来与启动fragment或Activity通信
6.1 使用Android支持库
Android支持库(Support Library)提供了一个支持API库的Jar文件,这个库允许你在早期的系统版本中使用近期系统版本中的API,一个好消息就是Support Library提供一个Fragment版本的APIs,所以我们可以在1.6-3.0之间的系统版本也能使用它了。本小节主要说明怎样设置Support Library来支持fragments让我们的低系统版本的应用夜支持动态UI。
6.1.1根据支持库设置我们的工程
1. 使用SDK Manager下载Android Support包,如图6-1所示:
图6-1 下载Android Support的截图
2. 在你的Android工程中创建一个libs目录
3.定位你的jar文件,把你想要使用的库复制到ligs/目录中,例如
<sdk>/extras/android/support/v4/android-support-v4.jar
4. 更新你的manifest文件设置最小API Level为4,目标API Level为15(截止此刻最新的系统版本4.0.3)
<uses-sdk android:minSdkVersion="4" android:targetSdkVersion="15" />
6.1.2导入我们支持库的APIs
Support Library包含各种各样的APIs,这些APIs可能你很想使用,为了考虑使用低系统版本的用户,可能他们的系统中没有此APIs。SL就是专为它而生。你能找到所有关于SL的API的文档,请参考官方API文档中的android.support.v4.*
当你使用了SL后,特别注意如果你自己的开发环境使用的是新系统,并使用了新的Fragment类,千万注意兼容性,不是所有设备都跟你一样使用的是新系统,你应该导入如代码清单6-1所示:
import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; ...
代码清单6-1
6.2 创建一个Fragment
你可以认为一个Fragment作为act模块化的一个部分,它有其自己的生命周期,接收它自己的输入事件,在该act运行时,你可以添加或删除。本节说明如何在支持库的情况下使用Fragment类,我们的应用程序仍然运行旧的Android 1.6系统版本上。当然如果你是直接声明你的应用支持的最小API level为11(android 3.0)的话,就不需要使用支持库了,不过这种做法目前不可取。
6.2.1创建一个Fragment类
首先我们需要继承Fragment类,然后重写关键的生命周期方法,就像Activity一样。实际上我们需要重写的方法仅仅是onCreateView()这一个方法,因为必须在这里创建一个布局,Fragment才能正确运行,我们可以参考下面的代码清单6-2:
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); } }
代码清单6-2
就像activity一样,一个fragment也应该实现其他声明周期的回调方法,以允许你管理状态(例如从activity中添加或删除),例如,当act调用onPause()时,在act中的任意fragments也将接收到onPause()回调。关于fragment更详细的使用方法,会在API框架中讲解。
6.2.2使用XML添加一个Fragment到Activity中
fragments是可复用的,模块化的UI组件,每一个Fragment类实例都必须和父对象FragmentActivity关联起来。你能通过在activity中的layout XML文件来定义每个fragment以完成这个关联。注意如果你支持的最低系统版本为API Level 11那么你可以直接使用Activity,因为FragmentActivity是专门为早期系统版本,并使用支持库中的Fragment而创建的。下面是一个布局文件的例子,当你设备的屏幕为“large”时,允许你在一个act中添加2个fragments。如代码清单6-3所示:
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>
代码清单6-3
以下是在代码中如何使用以上的layout xml文件,如代码清单6-4所示:
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); } }
代码清单6-4
注意:当你通过xml来定义fargment时,在运行时是不能移除的。下一小节我们会讲解如何动态与用户交互
6.3 创建一个灵活的Fragment
当你为了支持广泛的屏幕大小而设计你的App时,你能在不同的布局配置中重用你的fragments以优化各种屏幕大小的用户体验。例如,某个时刻在手机上显示一个fragment,相反的在平板中由于屏幕更大可能显示更多的fragment。
下面让我们看一下图6-2:
图6-2 平板和手机上使用Fragment的不同之处
FragmentManager类提供添加,删除,替换fragment的方法,可以在Activity运行时的使用,以创建一个动态的用户体验。
6.3.1在Activity运行时添加一个Fragment
与其用xml定义fragments ,不如在运行时动态添加更灵活。如果你计划在act的生命周期中改变fragments ,那么使用动态机制是必要的。例如执行添加或移除一个fragment的事务处理,我们必须使用FragmentManager类创建一个FragmentTransction,它提供添加,移除,替换fragment的各种APIs。如果你的act允许fragments被移除和替换的话,那么你应该添加初始的(也可理解为原始父类)fragment到act的onCreate()中。一个很重要的规则就是当处理fragments,尤其是那些运行时添加的fragment时,必须有一个View容器包裹fragment。我们依旧使用上一节的布局xml文件但仅仅显示一个fragment,因为我们支持的是layout而不是layout-large,由于我们使用代码动态添加fragments,所以XML中是不能有fragment节点声明的。并且XML中只有一个FrameLayout,如代码清单6-5所示:
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" />
代码清单6-5
在我们的activity中,使用支持库APIs调用getSupportFragmentManager()来获得一个FragmentManger。然后调用beginTransaction()创建一个FragmentTransaction以通过它的add()方法来添加一个fragment。你能在act中使用同一个FragmentTransaction执行多个fragment处理事务。当你确定改变时,需要调用commit()来提交我们的操作。具体操作,如代码清单6-6所示:
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); //检查FrameLayout是否为空 if (findViewById(R.id.fragment_container) != null) { // 如果我们从先前的状态恢复,那么我们不需要做任何事,直接返回 if (savedInstanceState != null) { return; } //创建一个HeadlinesFragment对象,光盘资源中有此类的实现 HeadlinesFragment firstFragment = new HeadlinesFragment(); // 给fragment设置一个Intent's extras参数 firstFragment.setArguments(getIntent().getExtras()); //添加fragment到'fragment_container'(FrameLayout) getSupportFragmentManager().beginTransaction() .add(R.id.fragment_container, firstFragment).commit(); } } }
代码清单6-6
因为fragment在运行时被添加到FrameLayout,而不是我们6.2中使用layout XML文件把fragment写到<fragment>中,所以我们可以动态添加,移除,替换fragment。
6.3.2替换Fragment
替换fragment ,不是用add()而是使用replace()。请记住,当你使用FragmentTransaction执行更换或删除一个fragment交易,它常常是适当允许用户向后导航“撤消”的转变。在你提交之前请调用FragmentTransaction.addToBackStack()方法。当删除或替换一个fragment,并添加回栈的操作时,被删除的fragment已停止(没有被destroyed)。如果用户后退导航,那么fragment恢复并重新启动。如果你不添加addToBackStack(),则当fragment被移除和替换时,直接被destroyed。下面我们看下代码清单6-7:
// 创建fragment并给他一个参数用于确定选择文章的位置 ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); FragmentTransaction transaction = getSupportFragmentManager().beginTransaction(); //我们需要把文章标题Fragment替换为文章内容Fragment // 我们添加了addToBackStack(),表示用户向后导航的时候fragment不会被destroyed transaction.replace(R.id.fragment_container, newFragment); transaction.addToBackStack(null); // 提交 transaction.commit();
代码清单6-7
如果在addToBackStack()中传入null,表示你可能更高级的操作,例如使用FragmentManager.BackStackEntry。
6.4 与其他Fragment通信
为了重用Fragment的UI组件,你应该建立一个定义了自己的布局和行为的完全独立并模块化的组件。一旦你定义了这些可重用的Fragment,你可以与Activity相关联,并与应用逻辑连接,以实现整体复合UI。通常情况下,你会想几个Fragment相互通信,例如基于用户事件改变内容。所有Fragment的通信是通过相关的Activity。两个Fragment,不应该直接通信。
6.4.1定义一个接口
为了允许一个Fragment 与Activity通信, 你可以在Fragment类里定义一个接口并在Activity中实现此接口。Fragment在它的onAttach()生命周期方法中捕获接口的实现,然后调用接口方法为了与Activity通信。下面让我们来看下代码清单6-8:
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"); } } ... }
代码清单6-8
现在fragment能通过调用Activity中的onArticleSelected() 方法来传递消息了。等会我们将展示实现的代码。
例如下面的onListItemClick()方法,当用户点击fragment 中的list item时会调用此方法。我们这个fragment 使用这个回调接口传递事件到父类Activity。如代码清单6-9所示:
@Override public void onListItemClick(ListView l, View v, int position, long id) { //通知父类activity选择的item mCallback.onArticleSelected(position); }
代码清单6-9
6.4.2 实现一个接口
为了从fragment接收事件回调, 父类activity必须实现这个在fragment类中定义的接口。例如下面的代码实现了这个接口,如代码清单6-10所示:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(Uri articleUri) { //用户从HeadlinesFragment选择文章的标题 // do something } }
代码清单6-10
6.4.3 传递消息到Fragment
我们通过Fragment实例的findFragmentById()方法直接获取ArticleFragment。
关于接口的具体实现,下面让我们看下代码清单6-11:
public static class MainActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener{ ... public void onArticleSelected(int position) { //用户从HeadlinesFragment选择文章的标题 //从 res/layout-large/获得文章 fragment ArticleFragment articleFrag = (ArticleFragment) getSupportFragmentManager().findFragmentById(R.id.article_fragment); if (articleFrag != null) { //如果articleFrag不为空,我们正在使用的是双面板布局(平板设备) //调用 ArticleFragment的这个方法来更新内容 articleFrag.updateArticleView(position); } else { //否则,我们使用的是单面板(手机设备) //创建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提交 transaction.commit(); } } }
代码清单6-11
完整的项目下载地址:http://url.cn/J49JRr
本文来自jy02432443,QQ78117253。转载请保留出处,并保留追究法律责任的权利