lightbulb_outline Help shape the future of the Google Play Console, Android Studio, and Firebase. Start survey

实现自适应 UI 流

UI 流可能视您的应用当前显示的布局而有所不同。例如,如果您的应用处于双窗格模式,点击左侧窗格中的某个项目会直接在右侧窗格中显示内容;如果是处于单窗格模式,内容应该会独立显示(在不同的 Activity 中)。

确定当前布局

由于您对每个布局的实现都略有差异,您需要优先完成的一项工作可能是确定用户目前查看的布局。 例如,您可能想了解用户是处于“单窗格”模式还是“双窗格”模式。 您可以通过查询给定视图是否存在并且是否可见来实现此目的:

public class NewsReaderActivity extends FragmentActivity {
    boolean mIsDualPane;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main_layout);

        View articleView = findViewById(R.id.article);
        mIsDualPane = articleView != null &&
                        articleView.getVisibility() == View.VISIBLE;
    }
}

请注意,此代码查询“article”窗格是否可用,这种方法要比针对特定布局对查询进行硬编码灵活得多。

还有一个示例可以说明如何适应不同组件的存在,其内容是检查组件是否存在,然后再对它们执行操作。 例如,在 News Reader 示例应用中,有一个用于打开菜单的按钮,但该按钮只在运行的版本低于 Android 3.0 时才存在(因为从 API 级别 11 开始,该功能已被 ActionBar 取代)。因此,如需为此按钮添加事件侦听器,您可以这样做:

Button catButton = (Button) findViewById(R.id.categorybutton);
OnClickListener listener = /* create your listener here */;
if (catButton != null) {
    catButton.setOnClickListener(listener);
}

根据当前布局作出反应

某些操作可能视当前布局而有不同的结果。例如,在 News Reader 示例应用中,如果 UI 处于双窗格模式,则点击标题列表中的某个标题会在右侧窗格中打开该文章,但如果 UI 处于单窗格模式,则会启动不同的 Activity:

@Override
public void onHeadlineSelected(int index) {
    mArtIndex = index;
    if (mIsDualPane) {
        /* display article on the right pane */
        mArticleFragment.displayArticle(mCurrentCat.getArticle(index));
    } else {
        /* start a separate activity */
        Intent intent = new Intent(this, ArticleActivity.class);
        intent.putExtra("catIndex", mCatIndex);
        intent.putExtra("artIndex", index);
        startActivity(intent);
    }
}

同样,如果应用处于双窗格模式,其设置的操作栏应包含用于导航的标签;而如果应用处于单窗格模式,则应设置具有微调框小部件的导航。 因此您的代码还应检查哪一种情况适当:

final String CATEGORIES[] = { "Top Stories", "Politics", "Economy", "Technology" };

public void onCreate(Bundle savedInstanceState) {
    ....
    if (mIsDualPane) {
        /* use tabs for navigation */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_TABS);
        int i;
        for (i = 0; i < CATEGORIES.length; i++) {
            actionBar.addTab(actionBar.newTab().setText(
                CATEGORIES[i]).setTabListener(handler));
        }
        actionBar.setSelectedNavigationItem(selTab);
    }
    else {
        /* use list navigation (spinner) */
        actionBar.setNavigationMode(android.app.ActionBar.NAVIGATION_MODE_LIST);
        SpinnerAdapter adap = new ArrayAdapter(this,
                R.layout.headline_item, CATEGORIES);
        actionBar.setListNavigationCallbacks(adap, handler);
    }
}

在其他 Activity 中重复使用 Fragment

在面向多种屏幕的设计中采用的一种固定模式是,让界面的某一部分在一些屏幕配置下以窗格形式实现,在其他配置下则以一个单独 Activity 的形式实现。 例如,在 News Reader 示例应用中,新闻文章文字在较大屏幕上显示在右侧窗格中,但在较小屏幕上则显示在一个单独的 Activity 内。

在这类情况下,您通常可以通过在几个 Activity 中重复使用同一 Fragment 子类来避免代码重复。例如,双窗格布局中使用了 ArticleFragment

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal">
    <fragment android:id="@+id/headlines"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.HeadlinesFragment"
              android:layout_width="400dp"
              android:layout_marginRight="10dp"/>
    <fragment android:id="@+id/article"
              android:layout_height="fill_parent"
              android:name="com.example.android.newsreader.ArticleFragment"
              android:layout_width="fill_parent" />
</LinearLayout>

并在适用于较小屏幕的 Activity 布局 (ArticleActivity) 中重复使用(无布局):

ArticleFragment frag = new ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();

当然,这与在 XML 布局中声明 Fragment 的效果相同,但在此情况下,XML 布局是无用功,因为文章 Fragment 是该 Activity 的唯一组件。

设计 Fragment 时需要牢记的一个要点是不要创建与特定 Activity 的强耦合。 为此,您通常可以定义一个界面,将 Fragment 与其宿主 Activity 进行交互时需要使用的所有方式抽象化,然后宿主 Activity 会实现该界面:

例如,News Reader 应用的 HeadlinesFragment 发挥的就是这个作用:

public class HeadlinesFragment extends ListFragment {
    ...
    OnHeadlineSelectedListener mHeadlineSelectedListener = null;

    /* Must be implemented by host activity */
    public interface OnHeadlineSelectedListener {
        public void onHeadlineSelected(int index);
    }
    ...

    public void setOnHeadlineSelectedListener(OnHeadlineSelectedListener listener) {
        mHeadlineSelectedListener = listener;
    }
}

然后,当用户选择某个标题时,Fragment 便会通知宿主 Activity 指定的侦听器(而不是通知特定硬编码 Activity):

public class HeadlinesFragment extends ListFragment {
    ...
    @Override
    public void onItemClick(AdapterView<?> parent,
                            View view, int position, long id) {
        if (null != mHeadlineSelectedListener) {
            mHeadlineSelectedListener.onHeadlineSelected(position);
        }
    }
    ...
}

支持平板电脑和手机指南中将进一步阐述此技巧。

处理屏幕配置变更

如果您要使用不同的 Activity 来实现界面的不同部分,您需要牢记的是,可能需要对某些配置变更(如旋转变化)作出反应,以便保持界面的一致性。

例如,在一台运行 Android 3.0 或更高版本的典型 7 英寸平板电脑上,当平板电脑在纵向模式下运行时,News Reader 示例应用使用单独的 Activity 来显示新闻文章,但在横向模式下则使用双窗格布局。

这意味着当用户处于纵向模式,并且用于查看文章的 Activity 位于屏幕上时,您需要检测屏幕方向已变为横向模式的情况并作出相应的反应:结束该 Activity 并返回主 Activity,以便内容可以显示在双窗格布局中:

public class ArticleActivity extends FragmentActivity {
    int mCatIndex, mArtIndex;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mCatIndex = getIntent().getExtras().getInt("catIndex", 0);
        mArtIndex = getIntent().getExtras().getInt("artIndex", 0);

        // If should be in two-pane mode, finish to return to main activity
        if (getResources().getBoolean(R.bool.has_two_panes)) {
            finish();
            return;
        }
        ...
}