现在的位置: 首页 > 综合 > 正文

安卓翻译(二)Activities—Fragments

2018年02月18日 ⁄ 综合 ⁄ 共 13867字 ⁄ 字号 评论关闭

执行Fragment 事件

关于在你的activity中使用fragments一个最强悍的特性是添加,移除代替,和用他们来执行其它行为,为了回应用户交互。你对activity做出的每个改变个改变的集合都会被系统调用一个事件,并且你能用在FragmentTransaction中的APIs来执行。你也能将每个事件保存在被activity管理的返回栈中,允许用户通过fragment改变来向后导航(与通过activities向后导航相似)。

你能像这样从FragmentManager获取一个FragmentTransaction实例:

FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

每个事件是一个你想要在同一事件变动的集合。你能将所有你想要执行的变动装进一个给出的事件中,通过使用例如add() , remove(),  replace() 的方法。然后,将这个时事件应用到activity中,你必须调用commit().

例如,这里是你如何用另外一个fragment代替一个fragment,并且保存之前的状态到返回栈中:

// Create new fragment and transaction
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// Commit the transaction
transaction.commit();

在这个例子中,newFragment代替了在被R.id.fragment_container标示的布局容器中的任何fragment(如果有的话)。通过调用addToBackStack(),代替的事件被保存在返回栈中,因此用户能保存事件并且通过点击返回键回到早前的fragment。

如果你添加多个变化到事件中(例如另外一个add()或者remove())和调用addToBackStack(),然后所有的变化作为一个单独的事件在你调用commit()之前被添加到返回栈中并且返回按钮会同时保存它们。

你添加变动到FragmentTransaction中的顺序是没有关系的,除了:

你必须在最后调用commit()

如果你正在向同一个容器中添加多个fragments,那么你添加它们的顺序就决定了它们在视图层级中的显示的顺序。

如果当你执行一个移除一个fragment的事件时没有调用addToBackStack,那么当事件被执行时那个fragment会被摧毁,并且用户不能在返回到这个fragment。然而,如果当你在移除一个fragment时调用了addToBackStack(),那么这个fragment被停止并且如果用户再返回的时候会被重现。

Tip:对每个fragment事件,你能在你执行它之前通过调用setTransition()来应用一个事件动画。

调用commit()不会直接执行事件。当然了,它将它安排运行在activity的UI线程上(主线程)一旦线程能够这样做的时候。然而如果必要,你可以调用executePendingTransactions()从你的UI线程中直接执行被commit()提供的事件。这样做通常是不必须的,除非事件对在其它线程中的任务来说是个从属。

警示:你能使用commit()执行一个事件在activity保存它的状态之前(当用户离开activity的时候)。如果你尝试在保存状态之后执行,一个异常会被抛出。这是因为在执行之后状态可能丢失,如果activity需要被重新存储的话。对你可以For
situations in which its okay that you lose the commit, use 
commitAllowingStateLoss().

与Activity交流

    虽然Fragment是被作为一个与activity相对于独立的对象来实现的,并且能被用于多个activities中,但是一个被给出的fragm实例是被直接绑定于容纳它的activity中。

特别地,fragm能通过getActivity()获取Activity实例,并且容易执行像在activity布局中找到一个view的任务:

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

同样地,你的activity能通过从FragmentManager获取一个到Fragment的引用来调用在fragment中的方法,使用findFragmentById()或者findFragmentByTag()。例如:

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

Creating event callbacks to activity

在一些案例中,你可能需要一个fragment与activity分享events。一个较好的方式是在fragment中定义一个回调接口并且要求宿主activity实现它。当activity从接口华总接收到一个回调时,它能与其它在布局中的fragments分享信息作为一种必需手段。


例如,如果在一个activity中有两个fragments  
  一个用来展示一列文章标题(fragment A),另外一个用来展示一篇文章(fragment B)-- 然后当一个列表项目被选中的时候fragment A 必需告诉activity 以便 activity能告诉fragment B 显示文章。在这个例子中,OnArticleSelectedListener 接口是在fragment A中被声明:

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

然后拥有fragment的activity实现OnArticleSelectedListener接口和重载onAritcleSelected() 通知fragment B 来自fragment A的事件。为了确保宿主activity实现这个接口,fragment的onAttach()
回调函数(当将fragment添加到activity的时候系统会调用)通过抛出被传进onAttach()的Activity来实例化一个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会抛出一个ClassCaseException。成功情况下,mListener成员持有一个OnArticleSelectedListener的activity实现引用,一遍fragment
A 能通过调用被OnArticleSelectedListener接口调用的方法来与activity分享事件。例如,如果fragm A 是一个ListFragment的扩展,用户每次点击一个列表项目,系统会调用在fragment中的onListItemClick(),然后调用onArticleSelected()与activity分享event:

public static class FragmentA extends ListFragment {
    OnArticleSelectedListener mListener;
    ...
    @Override
    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);
    }
    ...
}

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


更多关于使用content
provider 的信息在Content Provider 文档中可以找到。


给Action Bar 增加项目

你的fragments能通过实现onCreateOptionsMenu()给activity的 Options Menu (更多的是,Action Bar)增添菜单项目。然而为了这个方法能接收调用,你必须在onCreate()过程中调用setHasOptionsMenu(),来指示fragment想给Options
Menu 添加项目(否则,fragment将不会接收到对onCreateOptionsMenu()的调用)。


任何你从fragment中添加到选择菜单中的项目都会被附加到已经存在的菜单项目上。Fragment也在一个目录项目被选择的时候接收对onOptionsItemSelected()的调用。


你也能通过调用registerForContextMenu()在你的fragment布局中提供一个context 目录来注册一个视图。当用户打开 context 目录时,fragment接收一个对onCreateContextMenu()的调用。当用户选择一个项目时,fragment接收一个对onContextItemSelected()的调用。


注意:尽管你的fragment对每个它添加的菜单选项的 对被选择的项目的调用,但是activity是在用户选择菜单选项时第一个接收独立回调的。如果被选择的项目回调的activity实现没有处理被选择的项目,那么这个event被传递给fragment的回调。对选择菜单和context菜单都是这样的。


关于菜单的更多信息,请查看Menus和Action Bar 开发者向导。


处理Fragment生命周期

管理一个fragment的生命周期与管理activity的生命周期很像。像一个activity一样,一个fragment能在三种状态下生存:

Resumed 

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

Paused 

虽然activity运行在前台并且有着焦点,但是有fragment生存的这个activity是仍然可见的(前台的activity是部分透明或者没有覆盖了屏幕的全部)。

Stopped

fragment是不可见的。宿主activity被停止或者fragment从activity中被移除但是被添加到返回栈中。一个停止了的fragment是仍然生存着的(所有状态和成员信息被系统保存了)。然而,它不再对用户可见并且如果activity被杀死它也会被杀死。

它也像一个activity一样,你能保存fragment的状态使用Bundle,万一在activity的进程被杀死,当activity被重新创建的时候你需要重新存储fragment状态。在fragment的onSaveInstanceState()回调过程中,你能保存fragment的状态并且在onCreate(), onCreateView()或者ONActivityCreated()过程中重新存储它。关于存储状态的更多信息,请查看Activities文档。

activity与fragment之间在生命周期的最明显的不同之处是他们如何被存储在各自的返回栈中。当activity被停止后它被放置在一个被系统管理的activities的返回栈中,默认情况下是这样(以便用户用返回键返回到它,Tasks and Back Stack 中讨论的有)。然而,一个fragment被放置在宿主activit管理的返回栈中,仅仅当在一个移除fragment的事件中你明确要求通过addToBackStack()保存实例。

否则,管理fragment生命周期与activity生命周期非常相似。那么,对管理activities的生命周期的练习也使用与fragment。尽管这样,你需要懂得是activity怎么影响fragment的生命。

警示:如果在你的Fragment中需要一个Context,你可以调用getActivity()。然而,要小心地调用getActivity(),仅仅当fragment依附到activity时调用。当fragment还没依附的时候,或者在它的生命周期最后被分离时,getActivity()将会返回null。

与activity生命周期相互协调

fragment寄存的activity的生命周期直接影响这fragment的生命周期,如此对activity的每个生命周期回调会导致对每个fragment的相似回调。例如,当activity接收onPause(),在activity中的每个fragment接收onPause()。

然而fragments 有一些额外的生命周期回调,那就是处理与activity独一无二的交互为了执行像建立和摧毁fragment的UI的行为。这些额外的回调函数是:

onAttach()

当fragment与activity关联的时候(activity在这里会被传递)。

onCreateView()

被调用创建于fragment相关联的视图层级。

onActivityCreated()

当activity的onCreate()返回后时被调用

onDestroyView()

fragment相关联的视图层级被摧毁后调用。

onDetach()

当fragment与activity相分离时被调用。

fragment的生命周期的流图,被它的宿主activity影响,在图3中被展示。在这张图中,你能看到每个activity的成功状态如何决定一个fragment可能接收的回调函数。例如,当activity接收了它的onCreate()回调,在activity中的fragment就不能接收到除了onActivityCreated()的回调。

一旦activity接收到重新状态,你能自由地添加和移除fragments到activity中。因此,仅仅当activity在重现状态时,fragment能够独立地改变。

然而,当activity离开重现状态后,fragment又被重新拉入到它的生命周期中。

例子

将在文献中讨论的每个东西放在一起,这儿是一个activity的例子用两个fragments创建一个两个面板的布局。activity在包含了一个fragment来展示一列莎士比亚剧标题,当从列表中选择后另外一个展示剧本的概要。它也证明了基于屏幕配置怎样提供不同的fragments配置。

注意:这个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="http://schemas.android.com/apk/res/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"
            android:layout_width="0px" android:layout_height="match_parent"
            android:background="?android:attr/detailsElementBackground" />

</LinearLayout>

使用这个布局,系统初始化了TitlesFragment(列出剧本目录)一旦activity装载布局的时候,当FrameLayout(fragment展示剧本概要的地方)占据了屏幕的右边空间,但是在首先会保持空白。就如你在下面将要看到的,它直到用户从列表中选择一个项目,然后fragment才被放置在FrameLayout中。

然而,不是所有的屏幕配置都足够宽来边挨边的显示剧本列表和概要。因此,上面的布局仅仅用于那些屏幕空间足够大的配置, by  saving it at res/layout-land/fragment_layout.xml。

因此,当屏幕在纵向时,系统会应用下列布局,这个保存在res/layout-land/fragment_layout.xml 中:

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/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.这意味着,当系统在纵向时,仅仅列表项可见。因此,当用户点击了在这个配置中的一个目录时,应用会启动一个新的activity来展示概要,代替装载第二个fragment。

接下来,你能看到这是如何在fragment类中被完成的。首先是 TitlesFragment,展示莎士比亚剧目录。这个fragment继承自ListFragment,依靠它来处理大多数的列表视图工作。

一旦你检查这段代码,注意到当用户点击列表项时会有两个可能得行为:依靠两个布局哪个被激活,你既能创建和显示一个新的fragment来在同一个activity中显示细节(添加fragment到FrameLayout),或者启动一个新的activity(fragment被展示)。

public static class TitlesFragment extends ListFragment {
    boolean mDualPane;
    int mCurCheckPosition = 0;

    @Override
    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);
        }
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);
        outState.putInt("curChoice", mCurCheckPosition);
    }

    @Override
    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.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
                ft.commit();
            }

        } else {
            // Otherwise we need to launch 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);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        if (container == null) {
            // We have different layouts, and in one of them this
            // fragment's containing frame doesn't exist.  The fragment
            // may still be created from its saved state, but there is
            // no reason to try 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类  recall, 如果用户点击一个列表项并且当且布局没有包含R.id.details视图(DetailsFragment属于的地方),然后应用启动Detail说Activity activity来显示项的内容。

这里是DetailsActivity ,仅仅将DetailsFragment嵌入到其中来现在屏幕在纵向时被选择的剧本概要:

public static class DetailsActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(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.content, details).commit();
        }
    }
}

注意如果配置是 landspace 时这个activity会终结掉它自己,以便主activity能够接管并且在TitlesFragment旁边展示DetailsFragment。当在纵向时如果用户启动DetailsActivity这就会发生,但是然后旋转到 landspace。(重启当前的activity)

更多使用fragments 的例子(还有这个例子的源码),请看在ApiDemos中的API Demos 例子app(从Samples SDK component可被下载)。

抱歉!评论已关闭.