Android Api Component---翻译Fragment组件(二)

我们接着上一篇翻译吧Android Api Component---翻译Fragment组件(一)

与activity通信



尽管一个Fragment独立于一个Activity作为一个对象被实现并且在多个activity中被使用,给定的fragment实例绑定到了包含它的那个activity中。

特别的是,这个fragment使用getActivity()可以访问activity实例并且容易的执行像在activity布局中查找一个视图的任务:

View listView = getActivity().findViewById(R.id.list);

同样的,你的activity通过从FragmentManager中请求一个到Fragment的映射可以调用fragment中的方法,使用findFragmentById()或者findFragmentByTag()。例如:

ExampleFragment fragment = (ExampleFragment)getFragmentManager().findFragmentById(R.id.example_fragment);

创建事件回调到activity

在一些例子中,你也许需要一个fragment跟那个activity共享事件。一种好的方式是在fragment中定义一个回调接口,并且要求主activity实现它。当activity通过这个接口接收一个回调的时候,当需要的时候,它可以跟其它的fragment共享信息。

例如,如果一个新闻应用程序在activity中有两个fragment-一个是展示文章的列表(fragment A),另一个是展示一个文章(fragment B),那么当一个列表项被选中的时候,这个fragment必须告诉这个activity来告诉fragment B显式这个文章。在这个例子中,fragment A中定义了接口OnArticaleSelectedListener:

public static class FragmentA extends Fragment {
    ......
    //Container Activity must implement this interface
    public Interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
}

然后fragment的主activity实现了OnArticleSelectedListener接口并且覆盖了onArticleSelected()来通知来自于fragment A的事件给fragment B。为了确保主activity实现了这个接口,fragment A的onAttach()(当framgment添加到activity的时候,系统会调用这个方法)回调方法通过映射Activity到onAttach()中来初始化一个OnArticleSelectedListener:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch(ClassCastException e) {
            throw new ClassCastException(activity.toString()+" must implement OnArticleSelectedListener");
        }
    }
}

如果这个activity还没有实现这个接口,那么fragment会抛出一个ClassCastException异常。成功的关键是mListener成员持有一个映射到activity的OnArticleSelectedListener的实现,为的是fragment A可以通过调用被定义在OnArticleSelectedListener接口中的方法来和这个activity共享事件。例如,如果fragment A是一个ListFragment的扩展,用户每次点击一个列表项的时候,系统都会在fragment中调用onListItemClick(),这个方法可以调用onArticleSelected()来和这个activity共享事件:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    
    public void onListItemClick(ListView l, View v, int position, long id) {
        //Append the clicked item‘s row ID with the content provider Uri
        Uri noteUri = ContentUris.withAppendedId(ArticleColumns.CONTENT_URI,id);
        //Send the event and Uri to the host activity
        mListener.onArticleSelected(noteUri);
    }
    ....
}

传递给onItemClick()的id参数是被点击的项的行ID,这个ID是用来让activity(或者其它fragment)从应用程序的ContentProvider中抓取文章用的。

关于更多关于ContentProvider的信息,请看它的文档。

运用fragment的生命周期



管理fragment的生命周期跟管理activity的生命周期是不一样的。像activity,fragment可能存在于三种状态:

Resumed:

在运行的activity中fragment是可见的。

Paused:

另一个activity在它前面并且获得了焦点,但是在这个activity中的fragment仍然是存活着可见(前面的activity是部分的外观或者没有覆盖整个屏幕)。

Stopped:

frgment不是可见的。要么主activity已经被停止了,要么这个fragment已经从activity中被移除了,但是被添加到了回退栈。一个被停止的fragment仍然是存活的(所有的状态和成员信息通过系统被保持)。然而,它对用户不在是可见的并且activity被杀死之后这个fragment也被杀死。

在activity和fragment的生命周期之间最重要的不同点是它如何被存储在各自的回退栈中。当activity被停止的时候,这个activity默认会被放置在被系统管理的activity的回退栈中。然而,在一个移除fragment的事务期间,当你显式的请求通过调用addToBackStack()保存的实例的时候,这个fragment会被放在被它的主activity管理的回退栈中。

否则,管理activity的生命周期和管理fragment的生命周期是非常相似的。因此,管理activty的生命周期的相同习惯也应用与fragment。你也需要理解,activity的生命如何影响着fragment的生命。

警告:在你的fragment内如果你需要一个Context对象,你可以调用getActivity()。但是,当fragment被绑定到一个activity的时候,要小心调用getActivity()。当fragment还没有被绑定的时候,或者在它的生命周期结束时松绑了,getActivity()会返回null。

跟activity的生命周期整和

activity的生命周期直接影响着在它里面的fragment的生命周期。像每一个对activity的声明周期回调会导致一个相似的对每一个fragment的回调。例如,当activity接收onPause()的时候,在activyt中的每一个fragment接收onPause()。

fragment有几个额外的生命周期回调,例如,与activity运用唯一的相互作用来执行像构建和销毁fragment的UI的这样的动作。那些额外的方法是:

onAttach()

当fragment已经被关联到activity的时候被调用(在这里传递Activity)。

onCreateView()

创建一个跟fragment关联的视图层级的时候被创建。

onActivityCreated()

当activity的onCreate()方法被返回的时候调用。

onDestroyView()

当跟fragment关联的视图层级被移除的时候被创建。

onDetach()

当fragment从activity取消关联的时候被调用。

fragment的生命周期的流程被它的主activity所影响,参考上面的图片。在这个图片中,你可以看到,每一个activity的成功的状态决定着回调哪一个它接收的fragment的回调方法。例如,当activity的onCreate()回调它被接收的时候,在activity中的fragment接收不会越过onActivityCreated()回调。

一旦activity到达了被恢复的状态,你可以给activity轻易的添加和移除fragment。因此,只有当activity的状态为恢复时,fragment的声明周期就可以独立的改变了。

例子



为了把文档中讨论的事情聚集到一起,这里给了一个使用两个fragment的activity例子来创建两个面板布局。activity下面包含一个fragment来展示Shakespeare的标题列表并且当从这个列表中选中了一个剧本的时候,另一个fragment展示这个剧本的详述。它也展示了基于屏幕配置如何提供fragment的不同配置。

注意:这个activity的完整源码在FragmentLayout.java中。

主activity在onCreate()期间用普通的方式应用了一个布局:

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

这个布局文件fragment_layout.xml:

<LinearLayout xmlns:android="    
        android:orientation="horizontal"
        android:layout_width="match_parent" 
        android:layout_height="match_parent">
        
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
        android:id="@+id/titles
        android:layout_weight="1"
        android:layout_width="0px" android:layout_height="match_parent"/>
        
    <FrameLayout android:id="@+id/details" android:layout_weight="1"
        androd:layout_width="0px" android:layout_height="match_parent"
        android:background="?android.attr/detailsElementBackground"/>
                    
</LinearLayout>

使用这个布局,当activity载入布局的时候,系统初始化TitlesFragment(剧本标题列表),然而这个FrameLayout(在这个里面fragment将展示剧本的详述)消费了屏幕右边的空间,但是起初是空的。随着你下面看到的,直到用户选择了列表中的项,它才不会是空的,并且一个fragment会被放在这个FrameLayout中。

然而,不是所有的屏幕配置都足够宽能够既显示剧本列表又显示剧本详述。因此,通过保存在res/layout-land/fragment_layout.xml中,上面的布局仅仅用于宽边屏幕配置。

因此,当屏幕在portrait方向时,系统应用下面的配置,它被保存在res/layout/fragment_layout.xml中:

<FrameLayout xmlns:android="
    android:layout_width="match_parent" android:layout_height="match_parent">
    
    <fragment class="com.example.android.apis.app.FragmentLayout$TitlesFragment"
        android?id="@+id/titles"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
</FrameLayout>

这个布局只包含了TitlesFragment。这就意味着,当设备在portrait方向时,只有剧本标题列表是可见的。因此,在这个配置中当用户点击列表项的时候,应用程序取代载入第二个fragment,将开启一个新的activity展示这个详述。

接下来,你可以看到这个在fragment类中如何被完成。首先是TitlesFragment,它展示了Shakespeare剧本列表。这个fragment继承了ListFragment并且依赖于运用大多数列表视图工作。

当你检查这个代码的时候,当用户点击列表项的时候,注意有两个可能的行为:依赖于两个布局中的哪一个是活动的,它可以要么是创建并且展示一个新fragment来在相同的activity展示剧本的详述(添加fragment到FrameLayout),要么开启一个新的activity(这里是展示fragment的地方)。

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;
    
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        
        //Populate list with our static array of titles.
        setListAdapter(new ArrayAdapter<String>(getActivity(),android.R.layout.simple_list_item_activated_1, Shakespeare.TITLES));
        
        //Check to see if we have a frame in which to embed the details
        //fragment directly in the containing UI.
        View detailsFrame = getActivity().findViewById(R.id.details);
        mDualPane = detailsFrame != null && detailsFrame.getVisibility() == View.VISIBLE.
        
        if(savedInstanceState != null) {
            //Restore last state for checked position
            mCurCheckPosition = savedInstanceState.getInt("curChoice",0);
        } 
        
        if(mDualPane) {
            //In dual-pane mode, the list view highlights the selected item.
            getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
            //Make sure our UI is in the correct state.
            showDetails(mCurCheckPosition);
        }
    }
    
    public void onSaveInsanceState(Bundle outState) {
        super.onSaveinstanceState(outState);
        outState.putInt(curChoice",mCurCheckPosition);
    }
    
    public void onListItemClick(ListView l, View v, int position, long id) {
        showDetails(position);
    }
    
    /**
    * Helper function to show the details of a selected item, either by displaying a fragment in-place in the current UI, or 
    * starting a whole new activity in which it is displayed.
    */
    void showDetails(int index) {
        mCurCheckPosition = index;
        
        if(mDualPane) {
            //We can display everything in-place with fragments, so update 
            //the list to highlight the selected item and show the data.
            getListView().setItemChecked(index, true);
            
            //Check what fragment is currently shown, replace if needed.
            DetailsFragment details = (DetailsFragment) getFragmentManager().findFragmentById(R.id.details);
            if(details == null || details.getShownIndex() != index) {
                //Make new fragment to show this selection.
                details = DetailsFragment.newInstance(index);
                
                //Execute a transaction , replacing any existing fragment
                //with this one inside the frame.
                FragmentTransaction ft = getFragmentManager().beginTransaction();
                if(index == 0) {
                    ft.replace(R.id.details,details);
                } else {
                    ft.replace(R.id.a_item,details);
                }
                ft.setTransaction(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }
        } else {
            //Otherwise we need to lanuch a new activity to display
            //the dialog fragment with selected text.
            Intent intent = new Intent();
            intent.setClass(getActivity(), DetailsActivity.class);
            intent.putExtra("index",index);
            startActivity(intent);
        }
    }
}

第二个fragment DetailsFragment展示了从TitlesFragment的列表中被选中额项的这个剧本的详述:

public static class DetailsFragment extends Fragment {
    /**
    * Create a new instance of DetailsFragment, initialized to 
    * show the text at ‘index‘.
    */
    public static DetailsFragment newInstance(int index) {
        DetailsFragment f = new DetailsFragment();
        
        //Supply index input as an argument.
        Bundle args = new Bundle();
        args.putInt("index",index);
        f.setArguments(args);
        return f;
    }
    
    public int getShownIndex() {
        return getArguments().getInt("index",0);
    }
    
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        if(container == null) {
            //We have different layouts, and in one of them this fragment‘s conatining frame doesn‘t exist. The fragment may still be 
            //created from its saved state, but there is no reason to cry to create its view hierarchy because it won‘t be displayed.
            //Note this is not needed -- we could just run the code below, where we would create and return the view hierarchy.
            //it would just never be used.
            return null;
        }
        
        ScrollView scroller = new ScrollView(getActivity());
        TextView text = new TextView(getActivity());
        int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4, getActivity().getResources().getDisplayMetrics());
        text.setPadding(padding, padding, padding, padding);
        scroller.addView(text);
        text.setText(Shakespeare.DIALOGUE[getShownIndex()]);
        return scroller;
    }
}

从TitlesFragment类中回调,如果用户点击了列表项并且当前的布局不包含在R.id.details视图中(它是DetailsFragment所属的),那么应用程序开启这个DetailsActivity的activity来展示这个项的内容。

这是DetailsActivity,当屏幕是portrait方向的时候,它简单的嵌入了DetailsFragment来展示被选中的剧本的详述:

public static class DetailsActivity extends Activity {
    protected void onCreate(Bundle savedInstanceState) {
        if(getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            //If the screen is now in landscape mode, we can show the dialog in-line with the list so we don‘t need this activity.
            finish();
            return;
        }
        
        if(savedInstanceState == null) {
            //During initial setup, plug in the details fragment.
            DetailsFragment details = new DetailsFragment();
            details.setArguments(getIntent().getExtras());
            getFragmentManager().beginTransaction().add(android.R.id.context, details).commit();
        }
    }
}

注意,如果配置是landscape,那么这个activity销毁它自己,为的是主activity能接管并且展示与TitlesFragment紧挨的DetailsFragment。这可能会发生在当portrait方向时用户开始了DetailsActivity。但是然后又翻转到landscape(它重新启动当前activity)。

时间: 2024-11-29 01:04:19

Android Api Component---翻译Fragment组件(二)的相关文章

Android Api Component---翻译Fragment组件(一)

Fragment代表了在Activity中的一种或者一部分行为,你可以在单个的activity中连接多个fragment来构建一个多面板的UI,并且在多个activity中重复使用一个fragment,你可以把fragment想象成为activity的一个模块化片段,它有它自己的生命周期,接收它自己的输入事件,还有就是当activity运行的时候,你也能添加或者移除fragment. 一个frgment必须被嵌入在一个activit中,并且这个fragment的生命周期直接被它的主activit

从零開始学android&amp;lt;ImageSwitcher图片切换组件.二十六.&amp;gt;

ImageSwitcher组件的主要功能是完毕图片的切换显示,比如用户在进行图片浏览的时候.能够通过button点击一张张的切换显示的图片,并且使用ImageSwitcher组件在每次切换的时候也能够为其添加一些动画的效果,此类定义例如以下: java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.widget.FrameLayout ? android.widget.ViewAnimator ? andro

android Application Component研究之Activity(二)

http://blog.csdn.net/windskier/article/details/7172710 本文为原创文章,欢迎转载!转载时请注明出处:http://blog.csdn.net/windskier 上篇文章分析完了task的创建或者复用,接下来接着分析activity在启动过程中还有哪些工作需要去完成?首先给出整个activity的过程图. 1. Starting Window 当该activity运行在新的task中或者进程中时,需要在activity显示之前显示一个Star

Android Fragment使用(二) 嵌套Fragments (Nested Fragments) 的使用及常见错误

嵌套Fragment的使用及常见错误 嵌套Fragments (Nested Fragments), 是在Fragment内部又添加Fragment.使用时, 主要要依靠宿主Fragment的 getChildFragmentManager() 来获取FragmentManger.虽然看起来和在activity中添加fragment差不多, 但因为fragment生命周期及管理恢复模式不同, 其中有一些需要特别注意的地方.本文内容还包括了从Fragment迁移到v4.Fragment代码中需要改

Android系列之Fragment(二)Fragment的生命周期和返回栈

Android系列之Fragment(二)Fragment的生命周期和返回栈 - Android - 次元立方网 - 电脑知识与技术互动交流平台 [正文] 上一章节中(Android系列之Fragment(一)----Fragment加载到Activity当中),我们对Fragment的生命周期进行了简单介绍,这一章节将对生命周期和返回栈进行详细介绍. 一.Fragment的生命周期初探: 因为Fragment必须嵌入在Acitivity中使用,所以Fragment的生命周期和它所在的Activ

Android Fragment解析(二)

上篇博客中已经介绍了Fragment产生原因,以及一些基本的用法和各种API. 本篇将介绍上篇博客提到的:如何管理Fragment回退栈,Fragment如何与Activity交互,Fragment与Activity交互的最佳实践,没有视图的Fragment的用处,使用Fragment创建对话框,如何与ActionBar,MenuItem集成等~~ 1.管理Fragment回退栈 类似与Android系统为Activity维护一个任务栈,我们也可以通过Activity维护一个回退栈来保存每次Fr

翻译Android API Guides: App Manifest

原文在这里:http://developer.android.com/guide/topics/manifest/manifest-intro.html *Manifest译作"清单",这里沿用英文便于理解,其它术语同理. **文中链接都会跳转到android开发者网站. App Manifest 每一个应用都必须在它的根目录有一份AndroidManifest.xml文件(必须使用这个名字).Android系统必须在运行应用的任何代码之前了解一些重要信息,这些信息就来自于这份mani

从零开始学android&lt;RatingBar评分组件.二十三.&gt;

如果现在用户要对某个应用程序打分往往会使用图所示的组件,通过选择的"五角星"的个数来决定最终的打分成绩 这样的功能在Android之中,可以使用RatingBar组件实现,使用此组件可以方便用户的输入,而且很直观,RatingBar类的定义结构如下: java.lang.Object ? android.view.View ? android.widget.ProgressBar ? android.widget.AbsSeekBar ? android.widget.RatingBa

从零开始学android&lt;ImageSwitcher图片切换组件.二十六.&gt;

ImageSwitcher组件的主要功能是完成图片的切换显示,例如用户在进行图片浏览的时候,可以通过按钮点击一张张的切换显示的图片,而且使用ImageSwitcher组件在每次切换的时候也可以为其增加一些动画的效果,此类定义如下: java.lang.Object ? android.view.View ? android.view.ViewGroup ? android.widget.FrameLayout ? android.widget.ViewAnimator ? android.wid