Activity用户界面
阅读原文时间:2021年04月21日阅读:1

实现有效导航

       在介绍有效导航之前我们要对App bar,Fragments和多屏幕设计有一些了解。

App bar 

       最基本的操作栏会在一侧显示 Activity 的标题。即便是这种简单的应用栏也能为用户提供有用的信息,并有助于赋予 Android 应用一致的观感。如图:

图一:应用栏中显示标题

       从 Android 3.0(API 级别 11)开始,所有使用默认主题的 Activity 均使用 [ActionBar](https://developer.android.google.cn/reference/android/app/ActionBar.html) 作为应用栏。 但是由于不同Android版本的演化,应用栏功能已逐渐添加到原生ActionBar中。因此,原生的ActionBar会随设备使用的Android版本而发生变化。

       因此我们更倾向于使用支持库的 [Toolbar](https://developer.android.google.cn/reference/android/support/v7/widget/Toolbar.html) 类来实现 Activity 的应用栏。使用支持库的工具栏有助于确保我们的应用在最大范围的设备上保持一致的行为。

向Activity添加工具栏


如何将ToolBar设置为Activity的应用栏,步骤如下:

1. 向项目中添加V7 appcompat支持库;

2. Activity继承自AppCompatActivity;注:应用中每一个使用ToolBar作为应用栏的Activity都进行此更改。

3. 在AndroidManifest.xml中,将元素设置为 appcompat 其中的一个NoActionBar主题。来防止应用使用原生ActionBar类提供应用栏。

4. 向Activity的布局文件中添加一个ToolBar。例如:

<android.support.v7.widget.Toolbar
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/myToolBar"
    android:layout_width="match_parent"
    android:layout_height="?attr/actionBarSize"
    android:background="@color/colorPrimary"
    android:elevation="4dp"
    android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
<!--在这里注意:Material Design 规范建议应用栏具有 4 dp 的仰角。-->

5. 在Activity的onCreate方法中调用Activity的setSupportActionBar()方法,传递Activity的工具栏。通过该方法可以将工具栏设置为Activity的应用栏。 

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar mToolbar = (Toolbar) findViewById(R.id.myToolBar);
        setSupportActionBar(mToolbar);
    }

       此时,一个基本的操作栏就完成了,但是现在这个操作栏只包含应用的名称。接下来我们可以给向操作栏添加更多操作。

添加和处理操作

添加活动按钮

       操作溢出中可用的所有操作按钮和其他项都在XML菜单资源中定义。要将操作添加到操作栏,请在项目的res/menu/目录中创建一个新的XML文件。 

       为要包含在操作栏中的每个项添加一个元素,如以下菜单XML文件的代码所示:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <!--收藏操作按钮-->
    <item
        android:id="@+id/action_favorite"
        android:icon="@drawable/ic_favorite_black_48dp"
        android:title="@string/action_favorite"
        app:showAsAction="ifRoom"/>

    <!--设置,设置在溢出菜单中-->
    <item android:id="@+id/action_settings"
          android:title="@string/action_settings"
          app:showAsAction="never"/>

</menu>
<!--app:showsAction属性指定是否应将该操作显示为应用程序栏上的按钮。如果设置app:showsaction=“if room”如果应用程序栏中有空间,则该操作将显示为按钮;如果没有足够的空间,则多余的操作将发送到溢出菜单。如果设置app:showsaction=“never”,则该操作始终列在溢出菜单中,而不是显示在应用程序栏中。如果操作显示在应用程序栏中,系统将使用操作图标作为操作按钮。-->

活动的响应操作

       当用户选择某个应用程序栏项时,系统调用活动的OnOptionsitemselected()回调方法,并传递一个menuItem对象以指示单击了哪个项。在OnOptionsitemselected()的实现中,调用menuitem.getitemID()方法来确定按下了哪个项。返回的ID与您在相应的元素的android:id属性中声明的值匹配。

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    switch (item.getItemId()) {
        case R.id.action_settings:
            //用户选择了“设置”项,显示应用设置界面
            return true;

        case R.id.action_favorite:
            // 用户选择“收藏”操作,标记当前项目
            return true;

        default:
            //无法识别用户的操作,适用于超类来处理。
            return super.onOptionsItemSelected(item);

    }
}

添加向上操作

       在应用程序栏上为除主活动以外的所有活动提供一个向上按钮。当用户选择向上按钮时,应用程序将导航到父活动。

声明父Activity

       通过设置android:parentactivityname属性,可以在AndroidManifestxml中执行此操作。在这里要注意:android:parentactivityname属性在android4.1(API16)时引入的,因此我们的应用程序要支持旧版本的Android设备,就需要定义一个name-value对,其中name为“android.support.parent_activity”,value为主活动的名称。

    <application...>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <activity
            android:name=".MyChildActivity"
            android:parentActivityName=".MainActivity">
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".MainActivity"/>
        </activity>

    </application>

启动向上按钮

       要为具有父活动的活动启用向上按钮,请调用应用程序栏的setDisplayHomeAsupEnabled()方法。通常,您会在创建活动时执行此操作。例如:

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

    // 布局文件中定义了子工具栏
    Toolbar myChildToolbar =
        (Toolbar) findViewById(R.id.my_child_toolbar);
    setSupportActionBar(myChildToolbar);

    // 获取与此工具栏对应的支持操作栏
    ActionBar ab = getSupportActionBar();

    // 启用向上按钮
    ab.setDisplayHomeAsUpEnabled(true);
}

操作视图和操作提供程序

  • 操作视图是在应用程序栏中提供丰富功能的操作。例如,搜索操作视图允许用户在应用程序栏中键入搜索文本,而不必更改活动或片段。

  • 操作提供者是一个具有自己自定义布局的操作。该操作最初显示为按钮或菜单项,但当用户单击该操作时,操作提供程序以您想要定义的任何方式控制该操作的行为。

           Android支持库提供了几个专门的操作视图和操作提供程序小部件。例如,SearchView小部件实现用于输入搜索查询的操作视图,而ShareActionProvider小部件实现用于与其他应用程序共享信息的操作提供程序。

详细信息可以参考google官方文档:使用操作视图和操作提供程序

Fragments 

       Fragment:碎片。顾名思义,其是Activity中的部分,我们可以通过将多个碎片组合在一个Activity中来构建出一个多窗格的UI。它可以视为Activity模块化的组成部分,它具有自己的生命周期,能接收自己的输入事件,在Activity运行时也可以对其进行增加和移除。

       碎片必须嵌套在Activity,其生命周期直接受宿主Activity生命周期的影响(Activity暂停,碎片也会暂停;Activity销毁,碎片也会销毁),但当Activity在运行时,可以独立操作某个碎片,对其进行添加/移除等操作。我们可以将其添加到由Activity管理得返回栈——Activity的每个返回栈条目就是一条已经运行的碎片事务的记录,返回栈让用户可以通过按返回键来撤销碎片(即后退)。

       把碎片当做Activity布局的一部分进行添加时,它存在于Activity视图层次结构的ViewGroup内部,并且碎片会定义其自己的视图布局,我们可以通过:

1、在Activity的布局文件中声明碎片,将其作为碎片添加到Activity布局中;

2、通过将其添加到一个现有的ViewGroup,通过利用应用代码进行插入;

       当然,上面是把碎片当做Activity布局的一部分进行添加,那么碎片肯定也可以不作为Activity布局的一部分,例如:将一段无自己UI的碎片用做Activity的不可见工作线程。

创建碎片


        要创建一个碎片,首先必须创建Fragment的子类。Fragment类的代码与Activity代码类似,其也有很多和与Activity类似的回调方法。Fragment通常至少应该实现以下生命周期方法:

onCreate:系统在创建碎片时调用此方法。在该方法中初始化您想在碎片暂停或停止后恢复时保留的必需碎片组件。

onCreateView:碎片首次绘制用户界面时调用此方法。要想为碎片绘制UI,从此方法中返回的view必须是碎片布局的根视图。若碎片中没有UI,则返回null。

onPause:系统将此方法作为用户离开片段的第一个信号(但并不总是意味着此片段会被销毁)进行调用。 您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。

其他生命周期方法:

在这里我们还可能用到的几个子类,非Fragment基类:

DialogFragment:显示浮动对话框。使用此类可以代替Activity中的对话框方法,因为碎片对话框我们可以将其纳入由Activity管理得返回栈,从而使用户能够返回清除的碎片。

ListFragment:显示由适配器管理的一系列项目,类似于ListActivity。它提供了几种管理列表视图的方法。

PreferenceFragment:以列表形式显示Preference对象的层次结构,类似于PreferenceActivity。对我们为应用创建“设置”Activity时很有帮助。

添加用户界面

        碎片通常用作Activity用户界面的一部分,将自己的布局融入Activity。

        要想为片段提供布局,您必须实现onCreateView()回调方法,Android 系统会在片段需要绘制其布局时调用该方法。您对此方法的实现返回的View必须是片段布局的根视图。【:如果您的片段是ListFragment的子类,则默认实现会从 onCreateView()返回一个ListView,因此您无需实现它。】

        要想从onCreateView()返回布局,可以通过XML中定义的资源文件来拓展布局。为了帮助执行该操作,onCreateView()提供了一个LayoutInflater对象。

public static class DataFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        return inflater.inflate(R.layout.data_fragment, container, false);
    }
}

        传递至onCreateView()的container参数是碎片布局将插入到父ViewGroup;saveInstanceState参数是恢复碎片时,提供上一碎片实例的相关数据的Bundle

inflate方法带有三个参数:

  • 想要拓展的布局文件的资源ID;
  • 作为拓展布局父项的ViewGroup。传递container对系统向拓展布局的根布局(由父布局指定)应用布局参数具有重要意义。
  • 指定是否应该在拓展布局附加至ViewGroup(第二个参数)的布尔值。(在本例中,其值为 false,因为系统已经将扩展布局插入 container — 传递 true 值会在最终布局中创建一个多余的视图组。)

创建布局

        如何在 XML 中创建布局的信息,请参阅用户界面文档。

向Activity中添加碎片

        碎片向宿主 Activity 贡献一部分 UI,作为 Activity 总体视图层次结构的一部分嵌入到 Activity 中。可以通过两种方式向 Activity 布局添加碎片:

  • 在 Activity 的布局文件内声明碎片


中的android:name属性指定要在布局中实例化的Fragment类。

        当系统创建此Activity布局时,会实例化在布局中指定的每个碎片,并为每个碎片调用onCreateView()方法,以检索每个碎片的布局。系统会直接插入碎片返回的View来替代元素。

注:每个碎片都需要一个唯一的标识符,重启Activity时,系统可以使用该标识符来恢复碎片(也可以通过该标识符来捕获碎片来执行某些事务),我们可以通过下面三种方式为碎片提供ID:

  • 为android:id属性提供唯一ID。
  • 为android:tag属性提供唯一字符串。
  • 若没有给以上两个属性赋值,系统会使用容器视图ID。
  • 通过编程方式将片段添加到某个现有ViewGroup

        在Activity运行期间随时将碎片添加到Activity布局中,我们只需要指定将碎片放入哪个ViewGroup。

        要在Activity中执行碎片事务,需要使用FragmentTransaction

1、首先获取一个FragmentTransaction实例:

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

 2、使用add()方法添加一个碎片,指定要添加的碎片以及将其插入哪个视图。

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

        传递到add()的第一个参数是ViewGroup,即应该放置碎片的位置,由资源ID指定,第二个参数是要添加的碎片。一旦通过 FragmentTransaction做出更改,就必须调用commit()使其更改生效。


        添加没有UI的碎片      

管理碎片 

        要管理Activity中的碎片,需要使用到FragmentManager,想要获取它,需要从Activity调用getFragmentMangaer()。

        可以使用FragmentManager执行的操作包括:

        当然,除此以外,也可以通过上面提过的,使用FragmentManager打开一个FragmentTransaction,通过它来执行某些事务。

执行碎片事务

        在Activity使用碎片一大优点就是它可以根据用户行为通过它们执行添加、移除、替换以及其他操作。提交给Activity的每组更改都称之为事务,可以使用FragmentTransaction中的API来执行一项事务。也可以将每个事务保存在由Activity管理得返回栈中,从而让用户能回退更改。

        获取一个FragmentTransaction实例:

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

        每个事务都是想要同时执行的一组更改,可以使用add()、remove()、replace()等方法给定事务设置想要执行的所有更改,注意:要想让事务应用到Activity,必须调用commit()。

        调用commit()之前,有时可能会调用addToBackStack(),以将事务添加到片段事务返回栈。 该返回栈由 Activity 管理,允许用户通过按返回按钮返回上一片段状态。

// 创建新碎片和事务
Fragment newFragment = new ExampleFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();

// 用这个碎片替换fragment容器视图中的任何内容,
// 并将事务添加到返回栈
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);

// 提交事务
transaction.commit();

与Activity通信

        Fragment独立于Activity的对象实现,并且可以在多个Activity中使用,碎片的给定实例会直接绑定到包含它的 Activity。

        碎片可以通过getActivity()访问Activity实例,执行在Activity布局中查找视图等任务。

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

        Activity也可以使用findFragmentById()或findFragmentByTag(),通过从FragmentManager获取对Fragment的引用来调用碎片中的方法。

DataFragment fragment = (DataFragment) getFragmentManager().findFragmentById(R.id.data_fragment);

创建对Activity的事件回调

        在某些情况下,可能需要通过碎片与 Activity 共享事件。执行此操作的方法是,在碎片内定义一个回调接口,并要求宿主 Activity 实现它。 当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他碎片共享这些信息。

        例如:一个新闻应用Activity有两个碎片,左碎片用来显示文章列表,右碎片用来显示文章内容——那么左碎片必须在列表项被选定后告知Activity,然后告知右碎片来显示该文章。

public static class FragmentA extends ListFragment {
    ...
    // 容器活动必须实现此接口
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }
    ...
}

        接下来,该碎片的宿主Activity会实现OnArticleSelectedListener接口并替代onArticleSelected(),将来通过左碎片通知右碎片。为确保宿主Activity实现此接口,左碎片的onAttach()回调方法会通过转换传递到onAttach()中的Acitvity来实例化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没有实现接口,碎片就会引发ClassCastExceotion。实现时,mListener 成员会保留对 Activity 的 OnArticleSelectedListener实现的引用,以便左碎片可以通过调用 OnArticleSelectedListener接口定义的方法与 Activity 共享事件

向应用栏添加项目

        碎片可以通过实现onCreateOptionsMenu()向Activity的选项菜单贡献菜单项。为了使此方法能够收到调用,必须在onCreate()期间调用setHasOptionsMenu(),以指示片段想要向选项菜单添加菜单项(否则,碎片将不会收到对onCreateOptionsMenu()的调用)。

        之后从片段添加到选项菜单的任何菜单项都将追加到现有菜单项之后。 选定菜单项时,片段还会收到对onOptionsItemSelected()的回调。

        我们可以通过调用registerForContextMenu(),在碎片布局中注册一个视图来提供上下文菜单。用户打开上下文菜单时,碎片会收到对onCreateContextMenu()的调用。当用户选择某个菜单项时,碎片会收到对onContextItemSelected()的调用。

:尽管您的片段会收到与其添加的每个菜单项对应的菜单项选定回调,但当用户选择菜单项时,Activity 会首先收到相应的回调。 如果 Activity 对菜单项选定回调的实现不会处理选定的菜单项,则系统会将事件传递到片段的回调。 这适用于选项菜单和上下文菜单。

多屏幕设计 

       多屏幕设计可以参考Google官方文档中的屏幕兼容性概览