Android Fragment & Action Bar

Android Fragment指南

Fragment代表了Activity里的一个行为,或者Activity UI的一部分。你可以在一个activity里构造多个Fragment,也可以在多个activities里复用一个Fragment。你可以认为Fragment是activity里的一个模块片段。Fragment有自己的lifecycle,接收自己的input事件。你可以在activity运行的时候添加或者删除一个Fragment。

一个fragment必须嵌在一个activity里,它的lifecycle直接受到主activity的lifecycle的影响。例如,当一个activity 处于paused状态时,所有的fragment也处于paused状态;当一个activity处于destroyed状态时,所有的fragment也是destroyed状态。然而,当一个activity处于运行状态时(it is in the resumed lifecycle state),你可以独立的操作每个fragment,例如添加或删除它。当你执行一个fragment事务处理时,你可以将该操作加入到activity管理的back stack里—每一个back stack项是记录发生的一个fragment事务。back stack允许你通过回退按钮来回退一个fragment事务处理。

当你在你的activity layout里添加一个fragment时,它驻扎于activity 的一个viewgroup里。

Fragment定义自己的视图布局。你可以在你的activity layout里使用<fragment>元素插入一个fragment,也可以在代码里添加一个fragment到已存在的ViewGroup里。然而,fragment的视图布局不是必须的,你也可以使用没有UI的fragement,作为你的activity的一个不可见的worker。

1. 设计指南

Android的fragment机制,是在Android 3.0 (API level 11)上引进的,主要是支持大尺寸屏幕上的动态和灵活的UI设计,例如平板电脑。由于平板的尺寸远远大于手机设备,它有足够的空间来进行UI界面的组合和交互。Fragment机制可以让你避免复杂的视图结构的管理,来实现UI的动态交互。将activity里的布局,分解成多个fragment,你能够在activity运行时动态改变activity的外观,在back stack里维持这些改变。

例如,一个应用可以使用一个fragment在左侧显示文章的标题,使用另一个fragment在右侧显示文章的内容—两个fragment都在同一个activity里,每个fragment有自己独立的一套lifecycle,可以处理自己的用户输入事件。这样,可以规避用一个activity来实现选择文章,另一个activity来实现阅读文章的旧方法;利用fragment,可以在一个activity里实现选择文章和阅读文章。如下图所示:

Image1

你应当设计每一个fragment,作为activity里可复用的组件模块。因为每个fragment定义自己的layout,自己的行为,自己的lifecycle callback,你可以在多个activity里使用一个fragment。一个模块化的fragment,允许在不同尺寸的设备上改变fragment的排列。当你打算涉及你的应用既支持平板又支持手机时,你可以在不同的布局配置上复用你的fragment,根据相应的显示空间优化用户的体验。例如,在手机上,fragment可能只能同时显示一屏UI界面。

2. 创建一个Fragment

创建一个fragment,你必须实现一个类继承fragment类。Fragment类包含了与activity类相似的回调函数,例如onCreate(),onStart(),onPause(),onStop()。实际上,你可以简单的将activity里实现的回调函数拷贝到你的fragment类里,进行修改。

通常,你至少需要考虑实现下面的回调函数:

onCreate()

当创建fragment时系统会调用此函数。你应当在这里初始化fragment必须的组件。

onCreateView()

当fragment绘画UI界面时系统调用此函数。这个方法必须返回一个View,作为fragment的根布局。

onPause()

当用户离开此fragment界面时,系统调用此函数。你应当在这里保存你需要维持的数据。

基于fragment类,系统已实现了下面的一些扩展子类:

  • DialogFragment
  • ListFragment
  • PreferenceFragment

Image2

3. 添加一个fragment UI界面

要为一个fragment提供布局,必须实现onCreateView()回调函数。该函数返回的View就是你的fragment的根布局。

说明:如果你的fragment是由ListFragment扩展而来,默认的onCreateView()实现会返回一个ListView,所以你的代码里不必实现它。

示例如下:

public static class ExampleFragment extends Fragment {
      @Override
      public View onCreateView(LayoutInflater inflater, ViewGroup container,
                               Bundle savedInstanceState) {
          // Inflate the layout for this fragment
          return inflater.inflate(R.layout.example_fragment, container, false);
      }
  }

4. 在activity里添加一个fragment

有两种方法实现:

  • 在activity的layout文件里声明fragment元素。示例如下:
<?xml version="1.0" encoding="utf-8"?>
  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
      android:orientation="horizontal"
      android:layout_width="match_parent"
      android:layout_height="match_parent">
      <fragment android:name="com.example.news.ArticleListFragment"
              android:id="@+id/list"
              android:layout_weight="1"
              android:layout_width="0dp"
              android:layout_height="match_parent" />
      <fragment android:name="com.example.news.ArticleReaderFragment"
              android:id="@+id/viewer"
              android:layout_weight="2"
              android:layout_width="0dp"
              android:layout_height="match_parent" />
  </LinearLayout>

<fragment>元素里的android:name属性定义了实例化此布局的Fragment类。当系统创建这个layout,它会初始化layout定义的每个fragment,调用onCreatView()来获取每个fragment的view。

  • 在代码里,添加一个fragment到一个已经存在的ViewGroup里。

在activity运行时,你随时可以在你的activity布局里添加fragment。你仅仅需要为放置的fragment指定一个ViewGroup。为了在你的activity里进行fragment事务处理,你必须使用FragmentTransaction的API函数。用如下代码获得FragmentTransaction:

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

然后调用add()来添加一个fragment:

ExampleFragment fragment = new ExampleFragment();
  fragmentTransaction.add(R.id.fragment_container, fragment);
  fragmentTransaction.commit();

一旦调用FragmentTransaction的事务处理函数后,必须在最后调用commit()函数来使其生效。

5. Fragment与Activity通信

fragment可以通过getActivity()里访问Activity的实例。例如查找Activity里的一个view:

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

同样的,activity可以通过FragmentManager的findFragmentbyId()或者findFragmentbyTag()来获取到一个需要访问的fragment:

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

6. 创建事件回调函数

在一些case里,你也许需要fragment和activity之间共享事件。一个很好的做法是,在fragment里定义回调函数接口,在主activity里去实现它。

例如,当一个应用需要在activity里实现两个fragment。fragment A用于显示文章标题,fragment B用于显示文章内容。当用户选中一个标题时,fragment A必须告之Activity,让其通知Fragment B来显示文章内容。这个case里,fragment A定义了如下的接口:

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

主activity会实现这个接口,覆写onArticleSelected()来通知Fragment B,来自于Fragment A的消息。Fragment A会在onAttach()里获取到这个接口的实现:

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");
          }
      }
      ...
  }

当用户点击一个litst item,系统会调用fragment里的onListItemClick(),在这个函数里,会调用onArticleSelected()来通知activity:

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

7. 处理fragment lifecycle

与activity类似,fragment存在如下三种状态:

  • Resumed

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

  • Paused

其他的activity在切面,部分遮住了当前的主activity,或者是背景半透明的全部遮住了当前的activity。导致当前的主activty里的fragment处于Paused状态。

  • Stopped

fragment不可见。要么是主activity已经停止,要么是fragment被移除,但是被添加到了back stack里。

Activity停止时,默认情况下会被添加到back stack;Fragment停止时,必须显示的调用addToBackStack(),才能够被添加到back stack。

主activity的life cycle会直接影响到fragment的lifecycle。如下图所示:

Image3

 

本文档完整的pdf版本,请参考“Android Fragment指南.pdf

(如需转发,请标明出处)

本条目发布于 2013年11月29日。属于 Android分类。

Action Bar指南

 

1. Action Bar设计指南

Action Bar位于应用程序顶部的一块专有空间,它存在于整个app的生命周期里。它提供如下关键功能:

  • 以可预见的方式,使很多功能的实现和显示更加优秀和易于理解,比如New和Search。
  • 支持应用程序内的导航一致性,支持试图的切换。
  • 提供一个overflow,使不经常使用的功能隐藏起来,避免杂乱。
  • 为你的应用提供一个专门可以验证身份的空间。

如果你是编写Android应用程序的新手,请注意Action Bar是需要实现的最重要的设计元素之一。在遵循这里描述的准则,使你的应用程序接口与核心的Android应用程序保持一致。

1.1通用组成

Action Bar被分成四个不同的功能区域,适用于大多数应用程序。

a1

1. App 图标

App图标确定你的应用程序的身份。如果你愿意,可以更换不同的logo或品牌图标。如果应用程序目前无法显示主界面,请确认应用程序图标的左侧显示回退符(up caret)。用户可以通过回退符回到上层界面。

a2

2.视图控制

如果您的应用程序在不同的视图中显示的数据,试图控制功能允许用户切换视图。视图切换控制可以通过下拉菜单或tab控件方法实现。如果您的应用程序不支持不同的视图切换,你也可以使用这部分空间来显示非交互式的内容,如应用程序标题或较长的品牌信息。

3.行为按钮

行为按钮部分显示您的应用程序的最重要的行为。没有显示在Action Bar上的其他行为,会自动隐藏到Action Overflow里。长按按钮图标可以查看行为的名称。

4. Action Overflow

很少使用的行为会被移动到Action Overflow里。(Action Overflow是个术语,你可以理解为菜单)

1.2 适配屏幕旋转和屏幕尺寸

创建一个应用程序时,需要考虑的一个重要UI问题是,如何在不同的屏幕尺寸调整屏幕旋转。你可以通过使用split action bar机制来处理这个问题。split action bar允许将action bar里的内容分派到多个子action bar里。这些子bar显示在主action bar的下面,或者显示在屏幕的底部。

a3

1.3 Split Action Bars的布局考虑

当需要将行为分发到多个action bars里,布局可能需要如下考量:

1. main action bar2. top bar

3. bottom bar

如果用户需要从给定的界面返回到上一层界面,则main action bar里需要有一个返回符。

在top bar里面使用tabs或者spinners,满足用户切换不同的view。

在底部显示Action Bar的overflow和其他的action buttons。

 a4

1.4 行为按钮(Action Buttons)

在action bar里的action buttons部分,显示了你的应用最核心的功能行为。考虑好你的应用的哪些行为经常使用,对他们进行排序。更具实际屏幕的显示空间,系统会显示排序靠前的action button,而其余的action button则隐藏在action overflow里。当某个行为功能在当前上下文无法使用时,则隐藏它,而不是显示为禁用。

a5

对于行为按钮的排序,建议使用FIT策略。

F-frequent (频率) I-important (重要性) T-typical(类型)
在这个界面上,用户使用这个行为按钮,十次中有七次吗? 你希望每个用户都能使用这个行为按钮吗?它有卖点或者很酷的特性吗? 在类似的应用里,同样的行为按钮会显示在action bar上吗?
在一行里,用户多次使用这个行为按钮吗? 这个行为按钮的不经常使用,会导致一些重要功能的缺失? 考虑上下文,用户会惊讶这个行为按钮隐藏到action flow里吗
每次多一步操作,会让人感觉到厌烦吗?    

如果某个行为按钮满足F、I、T其中一个策略时,则将其显示在action bar上;否则将其隐藏在action overflow里。

1.5 Action Overflow

Action Bar里的action overflow 隐藏了应用不常用的行为功能。Action overflow图标仅在没有实体菜单键的设备上显示。有实体菜单键的设备,通过用户按下菜单键来显示action overflow。

主Action Bar里可以显示几个Action button?有如下规则:

Action button的宽度不能超过主action bar整个宽度的50%。而在底部显示的action bar里的action button的宽度可以是整个action bar的宽度。

以dp为单位的屏幕宽度决定了action bar上可以显示的action button的数量:

< 360 dp = 2个action button图标

360-499 dp = 3个action button图标

500-599 dp = 4个action button图标

>= 600 dp = 5个action button图标

a6

1.6 共享数据

当你的应用想分享一些数据,例如图片或短片,你可要在action bar上使用share action provider(一种action button)。share action provider提供一个选择共享数据的选择按钮,和一个包含其他共享项的微调按钮。

a7

1.7 上下文Action Bars

上下文Action Bars(简称CAB (contextual action bar))是在某段任务特定时间段下的一种临时action bar,它遮掩了应用原有的action bar。CAB经常用于对某段文本段落进行选择操作的任务场合。

a8

当长按某段数据文本时,会触发选择模式(selection mode),从而显示出了CAB。

用户可以:

通过CAB选择额外的数据文本;

对于选择的数据文本,通过CAB触发一个行为,对选中的数据文本进行操作。

使用back button或者CAB的checkmark button,取消CAB显示。

2. Action Bar API 指南

Action Bar是一个窗口功能,可识别用户的位置,向用户提供行为操作和导航模式。使用Action Bar为应用提供熟悉和统一的界面,可以使应用优雅地适应不同的屏幕配置。

Action Bar的API的第一次加入于Android 3.0(API级别11),但也可以在Android 2.1版本(API7级)及以上使用实现Action Bar的兼容性支持库。

注意:确定你从适合的软件包里导入ActionBar 类以及相关的APIs。

如果支持的API级别<11:

import android.supprt.v7.app.ActionBar

如果支持的API级别>=11:

import android.app.ActionBar

本文档只关注API级别>=11以上的ActionBar的使用指南。

2.1 添加Action Bar

API级别>=11以上,使用Theme.Holo主题的activity均包含了action bar的功能。如果你不想在一个activity里使用action bar,将该activity的主题设置为Theme.Holo.NoActionBar。

2.2 移除Action Bar

在运行时,可以通过调用hide()来隐藏action bar。例如:

ActionBar actionBar = getSupportActionBar();
  actionBar.hide();

当action bar隐藏后,系统调整你的应用布局,填充action bar占据的屏幕空间。通过调研show()来恢复action bar的显示。

要注意的是隐藏和删除Action Bar会导致你的activity重新布局。如果你的activity经常隐藏和显示action bar,您可能要启用overlay mode。Overlay mode在你的activity布局上面绘制action bar,掩盖顶部位置。这样一来,当action bar显示或隐藏时你的应用布局仍然保持固定。要启用overlay mode,为你的activity创建一个自定义主题,设置windowActionBarOverlay为true。

2.3 使用logo替代icon

默认情况下,系统在action bar上使用你的应用图标。应用图标的icon属性一般在<application>或<activity>元素里定义。然而,如果你定义了logo属性,你可以使用logo图片来替代icon。

一个logo图片一般宽于一个icon图标,但是尽可能不要包含多余的文本。你应当使用用户认可的,可以代表你的产品品牌的logo。一个很好的例子就是YouTube logo。

a9

2.4 添加Action项(Action Items)

当你的activity启动后,系统会通过调用你的activity里的onCreatOptionsMenu()函数来构造行为项。menu resource 定义了所有action项,调用这个方法进行构造。

下面的例子,menu resource定义了一些menu项(这里menu项和Action项表达的含义是一样的):

a10

在你的activity里的onCreateOptionMenu()方法里,将menu resource定义的menu项添加到action bar里:

@Override
  public boolean onCreateOptionsMenu(Menu menu) {
      // Inflate the menu items for use in the action bar
      MenuInflater inflater = getMenuInflater();
      inflater.inflate(R.menu.main_activity_actions, menu);
      return super.onCreateOptionsMenu(menu);
  }

当请求某个menu项作为一个action button显示到action bar上(默认的menu项都是隐藏都action bar里的overflow里),应在该项的<item>标签里包含showAsAction=”ifRoom”。例如:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >
      <item android:id="@+id/action_search"
            android:icon="@drawable/ic_action_search"
            android:title="@string/action_search"
            android:showAsAction="ifRoom"  />
      ...
  </menu>

如果action bar没有足够的空间来显示action button,则action button仍然会隐藏到action overflow里。

如果一个menu项既有tible属性,又有icon属性。则这个menu项成为action button后,仅在action bar上显示icon。如果你想显示文本tible,你需要在showAsAction属性里添加”withText”。例如:

<item yourapp:showAsAction="ifRoom|withText" ... />

你应当在每个menu项里定义title属性,原因如下:

  • 隐藏到action overflow里的menu 项,仅显示menu项的文本title。
  • 针对视力受损的用户,应用的实现需要考虑读取menu项的title属性。
  • 显示在action bar上的menu项,通过长按该项图标,会显示出该item项的title。

你也可以在showAsAction属性里添加”always”,使该menu项作为action button,一直显示在action bar上。然而,这种作法不值得推荐,由于设备的屏幕宽度不同。这种做法会导致布局问题。最好是使用“ifRoom”,系统会判断如果有足够的action bar空间,则该menu项作为action button显示;否则该menu想会自动隐藏到action overflow里。

如果一个menu项里包含了action view,并且该menu项是非常重要的功能,必须显示,你可以考虑使用”alwalys”值。

2.5 处理对action项的点击事件

当用户按下一个action button时,系统会调用你的activity里的onOptionItemSelected()方法。根据这个方法的MenuItem参数,你可以通过调用getItemId()来获取是哪个action button。getItemId()方法会返回一个menu项的ID,你根据这个ID进行action项的事件响应操作。例如:

@Override
  public boolean onOptionsItemSelected(MenuItem item) {
      // Handle presses on the action bar items
      switch (item.getItemId()) {
          case R.id.action_search:
              openSearch();
              return true;
          case R.id.action_compose:
              composeMessage();
              return true;
          default:
              return super.onOptionsItemSelected(item);
      }
  }

2.6 使用 split action bar

split action bar在狭窄屏幕(例如手机纵向屏幕)的下方提供了一条独立的bar, activity运行的时候在该bar上显示所有的action项。使用这种方式可以使狭窄屏幕有更多地空间来显示action项。icon title和导航的显示则留在屏幕的顶部。

a11

要使能split action bar功能,需要:

1. 在<activity>或者<application>元素里添加uiOptions=”splitActionBarWhenNarrow”。这种做法仅在API级别>=14以上有效;

2. 对于API较老的版本,则在每个<activity>元素里添加添加<meta-data>子元素,在<meta-data>里添加”android.support.UI_OPTIONS”。如下示例:

<manifest ...>
      <activity uiOptions="splitActionBarWhenNarrow" ... >
          <meta-data android:name="android.support.UI_OPTIONS"
                     android:value="splitActionBarWhenNarrow" />
      </activity>
  </manifest>

使用split action bar时,如果你禁用显示action bar里的图标和文本title,则action bar的顶部空间会被navigation tabs占据。你可以使用setDisplayShowHomeEnabled(false)和setDisplayShowTitleEnabled(false)来禁用action bar的icon和title显示。

2.7 使用App Icon向上导航

使能app icon作为用户向上导航的按键。例如,screen A显示了一些列项,选择其中一项跳转到screen B,screen B则应当包含up button,通过该按键键重新返回到screen A。

 

说明:up导航键与back导航键有所区别。back button是根据屏幕的历史显示顺序,进行回退导航,它的实现与时间顺序有关。而up button是根据应用的界面层次结构进行导航。

通过调研setDislplayHomeAsUpEnabled()方法来使能up导航键。如下示例:

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

      ActionBar actionBar = getSupportActionBar();
      actionBar.setDisplayHomeAsUpEnabled(true);
      ...
  }

为了让up button工作,你还需要:

  • 在manifest file里定义parent activity。从Android 4.1 (API level 16)后,你仅需在<activity>元素里的parentActivityName属性里声明parent activity即可。旧的版本,需要定义<meta-data>元素,并添加”android.support.PARENT_ACTIVITY”。如下示例:
<application ... >
      ...
      <!-- The main/home activity (has no parent activity) -->
      <activity
          android:name="com.example.myfirstapp.MainActivity" ...>
          ...
      </activity>
      <!-- A child of the main activity -->
      <activity
          android:name="com.example.myfirstapp.DisplayMessageActivity"
          android:label="@string/title_activity_display_message"
          android:parentActivityName="com.example.myfirstapp.MainActivity" >
          <!-- Parent activity meta-data to support API level 7+ -->
          <meta-data
              android:name="android.support.PARENT_ACTIVITY"
              android:value="com.example.myfirstapp.MainActivity" />
      </activity>
  </application>

这样,你的activity里的up button可以正常工作了。

  • 或者在你的activity里覆写getSupportParentActivityIntent()和onCreateSupportNavigateUpTaskStack()方法。这种方法适合于当前screen的parent activity不确定的情况。如果用户有多条路径进入到当前screen,这意味着当前screen有多个不同的parent activity。up button需要返回用户使用的那条路径对应的parent Activity。

当用户按下activity上的up button在你自己的app task里进行导航时,系统会调用getSupportParentActivityIntent(),你需要覆写这个方法,返回一个intent,指明合适的parent activity。

当用户按下activity上的up button,而调用activity的task不属于自己的activity 自己所属的app时,系统会调用onCreateSupportNavigateUpTaskStack()。你通过这个方法的TaskStackBuilder参数,构建一个合适的back stack。

即使你覆写了getSupportParentActivityIntent(),你也可以通过在manifest 文件里定义default parent activity来避免onCreateSupportNavigateUpTaskStack()的覆写。onCreateSupportNavigateUpTaskStack()的默认实现会根据mainfest文件里的parent activity声明,来合成一个合适的back stack。

说明:如果你的应用层次是通过一系列Fragment来实现的,up button的导航功能,则需要覆写onSupportNavigateUP(),进行Fragment之间的处理。通常会调用popBackStack()从back stack里弹出当前的Fragment。

2.8 添加action view

action view是一个可以在action bar上显示替代对应action bar的部件。action view在不改变activities和fragments的情况下,给action项提供快速的访问。例如,如果你有一个search action,你可要在action bar里增加一个action view,作为嵌入的searchView来显示。如下图所示:

a13

要声明一个action view,要么使用actionLayout属性定义一个layout resource,要么使用actionViewClass属性定义一个widget class。如下示例添加一个serachView widget:

<?xml version="1.0" encoding="utf-8"?>
  <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
      <item android:id="@+id/action_search"
            android:title="@string/action_search"
            android:icon="@drawable/ic_action_search"
            yourapp:showAsAction="ifRoom|collapseActionView"
            yourapp:actionViewClass="android.support.v7.widget.SearchView" />
  </menu>

注意showAsAction属性包含”collapseActionView”值。这是可选的值,用于声明这个action view应当折叠到一个button里。如果你想配置action view(例如增加事件监听器),你可以在onCreateOptionsMenu()回调函数里实现。通过调用静态方法MenuItemCompat.getActionView()方法来,传递一个MenuItem参数,你就可以获得一个action view对象。例如,search widget的action view的对象如下获取:

@Override
  public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.main_activity_actions, menu);
      MenuItem searchItem = menu.findItem(R.id.action_search);
      SearchView searchView = (SearchView) MenuItemCompat.getActionView(searchItem);
      // Configure the search info and add any event listeners
      ...
      return super.onCreateOptionsMenu(menu);}

对于API级别>=11的Android版本,要获取一个item对应的action view项,使用getActionView()方法:

menu.findItem(R.id.action_search).getActionView()

2.9 处理collapsible action views

为了保持action bar空间,你可以将你的action view折叠到一个action button里。折叠后,系统会将这个action项置于action overflow里。但是,如果用户选中这个action项,这个action项的action view则显示在action bar上。你可以在showAsAction属性里添加”collapseActionView”值,来实现action view的折叠功能。

由于用户选择这个action项时,系统会展开它对应的action view,所以你不需要再onOptionItemSelected()回调函数里响应对应的item项。系统仍然会调用onOptionItemSelected(),但是如果你在该回调函数里返回true(表明你已经处理了这个item项的事件),则item项对应的action view不会展开。

当用户按up button或back button时,系统会折叠你的action view。

如果你需要在你的action view可见的情况下更新你的activity,你可以定义一个onActionExpandListener,将其传递给setOnActionExpandListener()。这个Listener会被action view的展开或折叠时的回调函数调用。例如:

@Override
  public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.options, menu);
      MenuItem menuItem = menu.findItem(R.id.actionItem);
      ...

      // When using the support library, the setOnActionExpandListener() method is
      // static and accepts the MenuItem object as an argument
      MenuItemCompat.setOnActionExpandListener(menuItem, new OnActionExpandListener() {
          @Override
          public boolean onMenuItemActionCollapse(MenuItem item) {
              // Do something when collapsed
              return true;  // Return true to collapse action view
          }

          @Override
          public boolean onMenuItemActionExpand(MenuItem item) {
              // Do something when expanded
              return true;  // Return true to expand action view
          }
      });
  }

2.10 添加Action Provider

与action view类似,action provider以定制的布局来取代一个action button。然而与action view不同的是,action provider控制所有action的行为,当被用户按下时可显示一个submenu。

要声明一个action provider,在item标签里定义actionProviderClass属性。通过继承ActionProveder 类,你可以创建自己的action provider。Android系统提供了一些预先实现好的action provider。例如ShareActionProvider,可以在action bar上直接共享数据给一些app。

a14

由于每个ActionProvider类定义了自己的action behavior,你不需要在onOptionItemSelected()方法里监听action。如果万一你需要在onOptionItemSelected()里监听action以同步处理其他action,则返回值必须返回false,这样action provider仍然可以接收到onPerformDefaultAction()回调函数,处理与它相关的action事件。

然而,如果action provider提供了action的submenu,用户在选择submenu里的项时,你的activity接收不到对onOPtionItemSelected()的调用。

2.11 使用ShareActionProvider

要使用ShareActionProvider添加 一个”share” action,需要在<item>标签里定义actionProviderClass属性。例如:

<?xml version="1.0" encoding="utf-8"?>
  <menu xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:yourapp="http://schemas.android.com/apk/res-auto" >
      <item android:id="@+id/action_share"
            android:title="@string/share"
            yourapp:showAsAction="ifRoom"
            yourapp:actionProviderClass="android.support.v7.widget.ShareActionProvider"
            />
      ...
  </menu>

现在action provider控制了这个action项的显示和行为。但是你也必须为这个action项提供title属性,它在overflow里显示的时候需要。

剩下的事就是你定义一个需要共享的Intent。编辑你的onCreateOptionMenu()方法,传递MenuItem参数并调用getActionProvider(),获取action项对应的action provider。然后调用action provider的setShareIntent()方法,向其传递一个ACTION_SEND intent。

你应当在onCreateOptionMenu()里调用setShareIntent()来初始化share action,但是由于用户的上下文也许改变,你必须重新调用setShareIntent()来更新你要发送的intent内容。例如:

private ShareActionProvider mShareActionProvider;

  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
      getMenuInflater().inflate(R.menu.main_activity_actions, menu);

      // Set up ShareActionProvider's default share intent
      MenuItem shareItem = menu.findItem(R.id.action_share);
      mShareActionProvider = (ShareActionProvider)
              MenuItemCompat.getActionProvider(shareItem);
      mShareActionProvider.setShareIntent(getDefaultIntent());

      return super.onCreateOptionsMenu(menu);
  }

  /** Defines a default (dummy) share intent to initialize the action provider.
    * However, as soon as the actual content to be used in the intent
    * is known or changes, you must update the share intent by again calling
    * mShareActionProvider.setShareIntent()
    */
  private Intent getDefaultIntent() {
      Intent intent = new Intent(Intent.ACTION_SEND);
      intent.setType("image/*");
      return intent;
  }

现在ShareActionProvider处理action项的所有用户交互,你不需要再onOtionItemSelected()回调函数函数里进行任何事件处理。

默认情况下,ShareActionProvider对于每个共享目标根据用户的使用频率保持一个次序。用户经常使用的共享目标列在下拉菜单的最前面。默认情况下,排序信息存储在一个命名为DEFAULT_SHARE_HISTORY_FILE_NAME的私有文件里。如果你只在一个action项里使用ShareActionProvider,你可以继续使用这个默认私有文件。相反的,如果你想在多个action项里使用ShareActionProvider,你需要为每一个action项维护一个存储信息排序的文件(通过调用setShareHistoryFileName(),并提供一个xml file名,例如custom_share_history.xml)。

2.12 创建可定制的action provider

创建自己可定制的action provider,允许你在独立的模块里复用和管理动态的action 项行为,而不是在activity和fragment的代码里。可以参考Android实现的ShareActionProvider。

为一个不同的action项创建action provider,首先你需要继承ActionProvider类,实现它规定的回调函数。如下:

  • ActionProvider()

在这个构造函数里,传递你的app的context,后续的后调函数会使用到它。

  • onCreateActionView(MenuItem)

这里定义action项的action view。使用context来初始化你的action view的布局,实现钩子事件监听器。如下示例:

public View onCreateActionView(MenuItem forItem) {
      // Inflate the action view to be shown on the action bar.
      LayoutInflater layoutInflater = LayoutInflater.from(mContext);
      View view = layoutInflater.inflate(R.layout.action_provider, null);
      ImageButton button = (ImageButton) view.findViewById(R.id.button);
      button.setOnClickListener(new View.OnClickListener() {
          @Override
          public void onClick(View v) {
              // Do something...
          }
      });
      return view;
  }
  • onPerformDefaultAction()

当在action overflow里选中一项menu项时,系统会调用这个函数,action provider会执行一个默认的行为操作。

然而,如果你的action provider提供了一个submenu,通过onPrepareSubMenu()回调函数,submenu会显示在actionbar里。所以,如果存在submenu,onPerformDefaultAction()不会被调用。

对于ActionProvider的扩展示例,请参考ActionBarSettingsActionProviderActivity。

2.13 添加导航tabs

action bar提供的tabs使用户很容易的访问和切换你的app的不同的view。tabs在适配屏幕宽度方面做到了完美。例如,当屏幕的宽度足够宽时,tabs显示在action bar上;当屏幕宽度比较窄时,tabs显示在一个独立的bar上(称为stacked action bar)。在一些案例中,android系统会以下拉菜单的方式显示你的tab项。

a15

a16

首先,你的布局里必须包含一个ViewGroup,在其中一个tab关联一个Fragment。确保ViewGroup有一个resource ID,在你的代码里需要使用它切换tab。作为一种选择,如果tab的内容填充了你的activity布局,则你的activities不需要自己的布局(你甚至不需要调用setContentView())。相反的,你可以将每个fragment设置在root view下,引用android.R.id.content ID。

一旦你确定了布局里的fragment显示,添加一个tab的基本流程为:

1. 实现ActionBar.TabListener接口。这个接口为tab events提供了回调函数,例如切换tab事件。

2.对于每一个要添加的tab,实例化一个ActionBar.Tab,通过调用setTabListener()设置ActionBar.TabListener。使用setText()设置tab的title(使用setIcon(),设置tab的icon,可选)。

3. 调用addTab()增加一个tab。

注意ActionBar.TabListener回调函数并没有定义tab与fragment之间的关联逻辑,仅仅是确定了哪个ActionBar.Tab被选中了。你必须自己定义每个ActionBar.Tab和对应的Fragment之间的关联逻辑。有很多方式定义这种逻辑,依赖于你的设计。

例如,你想为每个tab实现一个ActionBar.TabListener:

public static class TabListener<T extends Fragment> implements ActionBar.TabListener {
      private Fragment mFragment;
      private final Activity mActivity;
      private final String mTag;
      private final Class<T> mClass;

      /** Constructor used each time a new tab is created.
        * @param activity  The host Activity, used to instantiate the fragment
        * @param tag  The identifier tag for the fragment
        * @param clz  The fragment's Class, used to instantiate the fragment
        */
      public TabListener(Activity activity, String tag, Class<T> clz) {
          mActivity = activity;
          mTag = tag;
          mClass = clz;
      }

      /* The following are each of the ActionBar.TabListener callbacks */

      public void onTabSelected(Tab tab, FragmentTransaction ft) {
          // Check if the fragment is already initialized
          if (mFragment == null) {
              // If not, instantiate and add it to the activity
              mFragment = Fragment.instantiate(mActivity, mClass.getName());
              ft.add(android.R.id.content, mFragment, mTag);
          } else {
              // If it exists, simply attach it in order to show it
              ft.attach(mFragment);
          }
      }

      public void onTabUnselected(Tab tab, FragmentTransaction ft) {
          if (mFragment != null) {
              // Detach the fragment, because another one is being attached
              ft.detach(mFragment);
          }
      }

      public void onTabReselected(Tab tab, FragmentTransaction ft) {
          // User selected the already selected tab. Usually do nothing.
      }
  }

警告:你一定不能再每个上面的callback里为fragment事务调用commit()—你自己调用,会抛出异常。你也不能将这些fragment事务添加到back stack。

在这个例子里,当某个tab被选中,listener简单的实现了attach一个fragment到activity布局(attach()),添加一个fragment到布局(add())作为android.R.id.content view group里的一个子项。当某个tab未被选中时,listener实现了detach()。

剩下的工作就是创建每个ActionBar.Tab,并将它添加到ActionBar。此外,你必须调用setNavigationMode(NAVIGATION_MODE_TABS)来使tabs可见。例如,下面的代码使用上述的listener添加了两个tabs:

@Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      // Notice that setContentView() is not used, because we use the root
      // android.R.id.content as the container for each fragment

      // setup action bar for tabs
      ActionBar actionBar = getSupportActionBar();
      actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
      actionBar.setDisplayShowTitleEnabled(false);

      Tab tab = actionBar.newTab()
                         .setText(R.string.artist)
                         .setTabListener(new TabListener<ArtistFragment>(
                                 this, "artist", ArtistFragment.class));
      actionBar.addTab(tab);

      tab = actionBar.newTab()
                     .setText(R.string.album)
                     .setTabListener(new TabListener<AlbumFragment>(
                             this, "album", AlbumFragment.class));
      actionBar.addTab(tab);
  }

当你的activity停止,你需要维护当前选中的tab的状态,这样当用户返回时可以回到合适的tab。在保存状态时,你可要调用getSelectedNavigationIndex()来获取选中的tab,它返回选中的tab的index position。

警告:保存每个fragment的状态很重要,用户会随时切换tab到不同的fragment。有一些状态时默认保存,但是你需要手动保存一些订制的view。

说明:上述的ActionBar.TabListener的实现是一种可行的技术之一。其他的实现方法是使用ViewPager来管理fragment,用户可以使用swipe gesture来切换tab。这种方式下,你可以简单的告诉ViewPager你的tab的位置(调用onTabSelected() callback)。

2.14添加drop-down navigation

Activity导航的另一种模式,是action bar提供的一种下拉式清单(也称为spinner)。这种下拉式清单可以给activity里分类的内容提供不同的显示模式。

当你的内容需要切换,但切换频率并不频繁,则可以使用下拉式清单这种方式实现。如果内容切换频率频繁,则建议使用navigation tabs方式实现。

a17

完成一个下拉式导航的基本步骤如下:

1. 创建一个SpinnerAdapter,提供选择项清单和清单中每项绘制的布局。

2. 实现ActionBar.OnNavigationListener,定义用户选中清单中某项时的操作行为。

3. 在你的activity的onCreate()方法里,通过调用setNavigationMode(NAVIGATION_MODE_LIST)使能action bar的下拉式清单功能。

4. 通过setListNavigationCallbacks()为下拉式清单设置回调函数。例如:

actionBar.setListNavigationCallbacks(mSpinnerAdapter, mNavigationCallback);

这个方法取得你的SpinnerAdapter和ActionBar.OnNavigationListener。

上述步骤相对简洁,但是最核心的工作时实现SpinnerAdapter和ActionBar.OnNavigationListener。下面是一个供你起步的示例:

SpinnerAdapter和OnNavigationListener的实现示例SpinnerAdapter是为spinner widget提供数据的一种adapter,例如action bar上的下拉式清单。SpinnerAdapter是你需要实现的接口,但是Android已经包含了一些有用的实现,供你来扩展,例如ArrayAdapter和SimpleCursorAdapter。下面的方式就是通过扩展ArrayAdapter来实现SpinnerAdapter,使用string arrays作为data source。

 

createFromResource()方法获取3个参数:应用的Context,string array的resource ID,每个清单项的布局。

一个string array的定义像这样:

<?xml version="1.0" encoding="utf-8"?>
    <resources>
        <string-array name="action_list">
            <item>Mercury</item>
            <item>Venus</item>
            <item>Earth</item>
        </string-array>
    </pre>

createFromResource()返回一个ArrayAdapter。

当用户从下拉清单里选择某项时,在你的activity或fragment需要改变代码的地方实现ActionBar.OnNavigationListener。这个listener只有一个方法需要实现:onNavigationItemSelected()。onNavigationItemListener()方法接收清单项在list中的位置和由SpinnerAdapter提供的项ID。

如下示例显示了onNavigationListener的一个匿名实现,在R.id.fragment_container的布局容器里插入了一个Fragment:

mOnNavigationListener = new OnNavigationListener() {
      // Get the same strings provided for the drop-down's ArrayAdapter
      String[] strings = getResources().getStringArray(R.array.action_list);

      @Override
      public boolean onNavigationItemSelected(int position, long itemId) {
        // Create new fragment from our own Fragment class
        ListContentFragment newFragment = new ListContentFragment();
        FragmentTransaction ft = openFragmentTransaction();
        // Replace whatever is in the fragment container with this fragment
        //  and give the fragment a tag name equal to the string at the position selected
        ft.replace(R.id.fragment_container, newFragment, strings[position]);
        // Apply changes
        ft.commit();
        return true;
      }
    };

在这个例子里,当用户选择下拉式清单里的某项时,在当前布局里会添加一个Fragment。增加的Fragment会设置一个标识身份的tag。

下面的示例显示了ListContentFragment类定义的fragment:

public class ListContentFragment extends Fragment {
        private String mText;

        @Override
        public void onAttach(Activity activity) {
          // This is the first callback received; here we can set the text for
          // the fragment as defined by the tag specified during the fragment transaction
          super.onAttach(activity);
          mText = getTag();
        }

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            // This is called to define the layout for the fragment;
            // we just create a TextView and set its text to be the fragment tag
            TextView text = new TextView(getActivity());
            text.setText(mText);
            return text;
        }
    }

 

2.15设计action bar风格

如果你想实现表达你自己的应用品牌风格的视觉显示,action bar允许定制它的外观,包括action bar的颜色,文本颜色,按钮风格等。你需要遵循android的style and theme框架来设计你的风格。

常规外观

actionBarStyle

定义action bar的style resource。默认的style是Widget.AppCompat.ActionBar,作为父style。

style resource包含的style属性有:

  • background — drawable resource,定义action bar背景。
  • backgroundStacked —drawable resource,定义stacked action bar(tabs)背景。
  • backgroundSplit — drawable resource,定义split action bar背景。
  • actionButtonStyle — 定义action button的style resource。它的默认值为Widget.AppCompat.ActionButton。用于父style。
  • actionOverflowStyle — 定义overflow action项的style resource。默认值为Widget.AppComat.ActionButton.Overflow。用于父style。
  • displayOptions — 定义action bar显示项,包含是否用app logo,显示title,或使能up action等。
  • divider — drawable resource,定义action项之间的分割间隔。
  • titleTextStyle — 为action bar title定义style resource。默认值为TextAppearance.AppCompat.Widget.ActionBar.Title,用于父stytle。

windowActonBarOverlay

声明action bar是否使用overlay模式。默认值为false。

通常,action bar有自己独立的空间,而activity layout使用剩余的空间。当action bar使用overlay模式,activity layout使用全部的空间,系统将action bar画在layout的顶部。当action bar隐藏或显示时,如果你想保持你的内容为固定的大小和位置,就可以使用overlay模式。或者你仅仅使用overlay模式作为一种显示效果,你可以在action bar上使用一个半透明的背景图,用户可以透过action bar看到下面的activity layout。

当overlay模式使能,你的activity布局并没有意识到action bar的位置在它的顶部。因此,顶部这块背action bar覆盖的地方,你的layout布局应当小心谨慎,不要将重要的UI显示放在这一块。如果需要,你可以通过actionBarSize属性来确定action bar的高度。例如:

<SomeView
      ...
      android:layout_marginTop="?android:attr/actionBarSize" />

Action Items

actionButtonStyle —为action button定义style resource。默认值为Widget.AppCompat.AcitonButton。用于父style。

actionBarItemBackground — drawable resource,为每个action项定义背景。这应该是个state-list drawable类型,指示不同的选项状态。

actionBarDivider — drawable resource,定义action项之间的分割间隔。

actionMenuTextColor — 定义action项的文本显示颜色。

actionMenuTextAppearance — 定义action项的文本显示外观。

actionBarWidgetTheme —定义action view的theme resource。

Navigation Tabs

actionBarTabStyle — 定义action bar的tabs的style resource。默认值为Widget .AppCompat.ActionBar.TabView。用于父style。

actionBarTabBarStyle –定义显示在navigation tabs下的thin bar的style resource。默认值为Widget.AppCompat.ActionBar.TabBar。用于父style。

actionBarTabTextStyle —定义navigation tabs的文本 style resource。默认值为Widget.AppCompat.ActionBar.TabText。用于父style。

Drop-down lists

actionDropDownStyle — 定义drop-down navigation的sytle(例如,background和text style)。默认值为Widget.AppCompat.Spinner.DropDown.ActionBar。用于父style。

Theme示例

这是一个定制theme的示例,包含了对aciton bar的定制:

<?xml version="1.0" encoding="utf-8"?>
  <resources>
      <!-- the theme applied to the application or activity -->
      <style name="CustomActionBarTheme"
             parent="@style/Theme.AppCompat.Light">
          <item name="android:actionBarStyle">@style/MyActionBar</item>
          <item name="android:actionBarTabTextStyle">@style/TabTextStyle</item>
          <item name="android:actionMenuTextColor">@color/actionbar_text</item>

          <!-- Support library compatibility -->
          <item name="actionBarStyle">@style/MyActionBar</item>
          <item name="actionBarTabTextStyle">@style/TabTextStyle</item>
          <item name="actionMenuTextColor">@color/actionbar_text</item>
      </style>

      <!-- general styles for the action bar -->
      <style name="MyActionBar"
             parent="@style/Widget.AppCompat.ActionBar">
          <item name="android:titleTextStyle">@style/TitleTextStyle</item>
          <item name="android:background">@drawable/actionbar_background</item>
          <item name="android:backgroundStacked">@drawable/actionbar_background</item>
          <item name="android:backgroundSplit">@drawable/actionbar_background</item>

          <!-- Support library compatibility -->
          <item name="titleTextStyle">@style/TitleTextStyle</item>
          <item name="background">@drawable/actionbar_background</item>
          <item name="backgroundStacked">@drawable/actionbar_background</item>
          <item name="backgroundSplit">@drawable/actionbar_background</item>
      </style>

      <!-- action bar title text -->
      <style name="TitleTextStyle"
             parent="@style/TextAppearance.AppCompat.Widget.ActionBar.Title">
          <item name="android:textColor">@color/actionbar_text</item>
      </style>

      <!-- action bar tab text -->
      <style name="TabTextStyle"
             parent="@style/Widget.AppCompat.ActionBar.TabText">
          <item name="android:textColor">@color/actionbar_text</item>
      </style>
  </resources>

在你的manifest文件里,你可以对整个app使用你定制的theme:

<application android:theme="@style/CustomActionBarTheme" ... />

或对某个activity使用你定制的theme:

<activity android:theme="@style/CustomActionBarTheme" ... />

注意:每个theme和style会声明一个parent theme或parent style。你只需要覆写你需要修改的style属性,其他的属性由parent提供实现。

 

详细内容请参考“Acion Bar指南.pdf”。(如需转发,请标明出处)

感谢:http://www.lanttor.org/?m=201311
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值