C:实施自适应用户界面流程
根据当前的布局为应用提供界面,显示出的UI界面也是可以不同的。例如,你的应用工作在双面板模式下,点击左侧面板中的一项将在右侧显示详细内容,如果是在单面板模式下,详细内容将在一个新的页面中显示(在不同的activity中)。
C.a 决定当前的布局
因为你实现的每个布局都会有些不同,第一件你不得不做事可能就是决定当前给用户看哪个布局。例如,你想知道用户现在是在双面板模式下还是在单面板模式下,你可以通过查询一个view是否存在并且可见来实现这个操作。
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”面板可用或者不可用,这要比使用硬编码查询一个特点的布局文件灵活的多。
另一个例子,你可以适应不同组件的存在就是检查他们是否可用在向他们执行一个操作之前。例如,在样例这个应用中,有一个打开菜单的按钮,但是这个按钮只会在安卓版本3.0之前出现(因为他的功能在3.0以后被ActionBar接管)。所以,给他设置监听时,你可以这样做:
Button catButton = (Button) findViewById(R.id.categorybutton);
OnClickListener listener = /* create your listener here */;
if (catButton != null) {
catButton.setOnClickListener(listener);
}
C.b根据当前的布局做出反应
根据当前的布局,一些操作可以有不同的结果。例如,在样例这个应用中,如果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);
}
}
同样的,如果在双面板模式下,应该把ActionBar设置成tabs式的导航模式,而在单面板的情况下,则应该用一个下拉列表来构成导航。所以你的代码也应该哪种情况是合适的:
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);
}
}
C.c 在其他的Activity中复用Fragment
在支持多尺寸屏幕时一个反复使用的设计模式是在一些屏幕尺寸下你布局的一部分可以在其他的屏幕布局下作为一个独立的Activity布局。例如,在样例应用中,文章的内容在大屏幕尺寸手机下是在右边的面板中显示的,而在小屏幕尺寸下则是一个独立的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布局中复用(没有在布局文件中定义):
ArticleFragment frag = new ArticleFragment();
getSupportFragmentManager().beginTransaction().add(android.R.id.content, frag).commit();
当然,这和在布局文件中定义fragment有一样的效果,但是这种情况下在布局文件中定义是不必要的工作,因为article fragment是Activity中唯一的控件。
必须牢牢记住的一点是当你创建一个fragment时不要强耦合一个特定的Activity。你可以在创建时把fragment需要与Activity交互时的方式抽象成一个接口,然后用到这个fragment的Activity继承这个接口。
例如,在样例应用中,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);
}
}
...
}
C.d 处理屏幕配置的改变
如果你是要单独的Activity来实现你用户界面独立的部分,你必须牢记你可能必须对一些配置的变化做出反映(例如屏幕放心的变化),从而保证你的用户界面的一致性。
例如,在一般的运行3.0或者更高系统的7寸平板电脑上,样例应用会在竖屏模式下用单独的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;
}
...
}