Android 导航

一.导航抽屉Navigation Drawer

想必用过Android的人都熟悉导航抽屉,这算是一个比较经典的导航方式了,个人也非常喜欢这种导航方式。下面介绍这种导航方式的开发方法。


导航抽屉
导航抽屉

创建一个抽屉布局DrawerLayout


<android.support.v4.widget.DrawerLayout    
xmlns:android="http://schemas.android.com/apk/res/android"    
android:id="@+id/drawer_layout"    
android:layout_width="match_parent"    
android:layout_height="match_parent">    

<!-- The main content view -->    
<FrameLayout        
android:id="@+id/content_frame"        
android:layout_width="match_parent"        
android:layout_height="match_parent" />    

<!-- The navigation drawer -->    
<ListView android:id="@+id/left_drawer"        
android:layout_width="240dp"        
android:layout_height="match_parent"        
android:layout_gravity="start"        
android:choiceMode="singleChoice"        
android:divider="@android:color/transparent"        
android:dividerHeight="0dp"        
android:background="#111"/>
</android.support.v4.widget.DrawerLayout>

这里有一些需要注意的地方:

  • 要添加一个导航抽屉,首先应该用support库提供的DrawerLayout布局来作为你的主布局的根布局,在根布局中,应该包含显示主要内容的布局和drawer的布局。
  • 第一个布局必须是你的主要内容的布局(因为XML文件是根据z轴的顺序来显示,而抽屉要在主要内容的上面)
  • 主要内容布局需要将长宽都设置为铺满父容器,因为当抽屉隐藏时,它是主要显示的容器。
  • 抽屉view(the ListView)必须指定horizontal_gravity,通过设置android:layout_gravity。为了支持从右向左的语言,最好指定值为"start"而不是left
  • 为了让用户能看见主页面的一部分,抽屉view的宽度应该用dp来设置,并且尽量不要超过320dp。

初始化Drawer List


在你的activity中,第一件事就应该去初始化导航抽屉列表的每一项。不管你怎么去组织你的app内容,一个导航抽屉通常都是由一个列表组成的,所以我们应该从一个适配器Adapter开始入手(比如ArrayAdapter或者SimpleCursorAdapter)。
例如,这段代码演示了如何通过一个string array来初始化你的列表。

public class MainActivity extends Activity {    
  private String[] mPlanetTitles;   
  private DrawerLayout mDrawerLayout;  
  private ListView mDrawerList;    
  ...    
  @Override    
  public void onCreate(Bundle savedInstanceState) {        
    super.onCreate(savedInstanceState);        
    setContentView(R.layout.activity_main);        
    mPlanetTitles =  getResources().getStringArray(R.array.planets_array);        
    mDrawerLayout = (DrawerLayout)  findViewById(R.id.drawer_layout);        
    mDrawerList = (ListView) findViewById(R.id.left_drawer);    //  为list设置一个适配器    
    mDrawerList.setAdapter(new ArrayAdapter<String>(this, R.layout.drawer_list_item, mPlanetTitles));   // 设置一个监听器
    mDrawerList.setOnItemClickListener(new DrawerItemClickListener());        ...    
  }
}

通过setOnItemClickListener()接收导航抽屉的列表点击事件。接下来展示如何实现这个接口和当选择一项时改变主页面。

处理导航点击事件


当用户点击抽屉的列表时,系统会在你通过setOnItemClickListener()设置的监听器OnItemClickListener()上调用onItemClick()
你的onItemClick()的功能取决于你的app的功能。在下面的例子中,选择每一项都会动态的插入一个不同的Fragment到主页面(FrameLayout)中。

private class DrawerItemClickListener implements ListView.OnItemClickListener {    
  @Override    
  public void onItemClick(AdapterView parent, View view, int position, long id) {
        selectItem(position);
    }
}
/** 替换主页面中的fragment */
private void selectItem(int position) {
    // Create a new fragment and specify the planet to show based on position
    Fragment fragment = new PlanetFragment();
    Bundle args = new Bundle();
    args.putInt(PlanetFragment.ARG_PLANET_NUMBER, position);
    fragment.setArguments(args);
    // 通过替换已存在的fragment来插入
    FragmentManager fragmentManager = getFragmentManager();
    fragmentManager.beginTransaction()// 开始事务
                   .replace(R.id.content_frame, fragment)// 替换
                   .commit();// 提交
    // 高亮选中项,设置标题,关闭抽屉
    mDrawerList.setItemChecked(position, true);
    setTitle(mPlanetTitles[position]);
    mDrawerLayout.closeDrawer(mDrawerList);
}
@Overridepublic void setTitle(CharSequence title) {
    mTitle = title;
    getActionBar().setTitle(mTitle);
}

监听打开和关闭抽屉事件


要监听打开和关闭事件,首先应该在你的DrawerLayout上设置监听器setDrawerListener(),还要实现DrawerLayout.DrawerListener,这个接口提供回调函数,比如 onDrawerOpened()onDrawerClosed()

回调函数解析

然而,与其实现这个接口,如果你的activity包含了actionbar,你可以替换为使用ActionBarDrawerToggle类,这个类实现了DrawerLayout.DrawerListener这个接口,你仍可以去重载那些回调函数,但是使用ActionBarDrawerToggle可以促进action bar的图标和导航抽屉的正确的交互。

当抽屉可见时,你应该改变action bar的内容,像是改变标题,移除action items来适应主页面的内容,下面的代码通过一个ActionBarDrawerToggle类展示了如何去重载DrawerLayout.DrawerListener回调函数。

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    private CharSequence mDrawerTitle;
    private CharSequence mTitle;
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ...
        mTitle = mDrawerTitle = getTitle();
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
                R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close) {
            /** 当抽屉完全关闭时调用*/
            public void onDrawerClosed(View view) {                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
                invalidateOptionsMenu(); // 创建调用onPrepareOptionsMenu()
            }
            /** 当抽屉完全打开时调用 */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
                invalidateOptionsMenu(); // 创建调用onPrepareOptionsMenu()
            }
        };
        // 设置toggle为抽屉监听器
        mDrawerLayout.setDrawerListener(mDrawerToggle);
    }
    /* 当调用invalidateOptionsMenu()时调用 */
    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        // 如果导航抽屉式打开的,根据内容隐藏action items
        boolean drawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
        menu.findItem(R.id.action_websearch).setVisible(!drawerOpen);
        return super.onPrepareOptionsMenu(menu);
    }
}

下个部分介绍ActionBarDrawerToggle的构造函数参数和与action icon交互的其他要求。

通过App Icon来打开和关闭抽屉


用户能够通过右滑来打开抽屉,但是如果你使用action bar,那么你也应该允许用户通过点击App Icon来打开和关闭抽屉。同时也应该通过一个特殊的icon来显示隐藏抽屉的存在。你能够通过之前介绍的ActionBarDrawerToggle来实现。

为了使ActionBarDrawerToggle工作,在这里创建一个例子和他的构造函数,他需要以下的元素:

  • 托管抽屉的Activity
  • DrawerLayout
  • 一个图片资源用作抽屉开关的指示器
    图片资源
  • 一个描述抽屉打开的string(为了用户友好)
  • 一个描述抽屉关闭的string(为了用户友好)

不管你是否创建了ActionBarDrawerToggle的子类作为你抽屉监听器,你需要在你的activity的整个生命周期中能够调用ActionBarDrawerToggle

public class MainActivity extends Activity {
    private DrawerLayout mDrawerLayout;
    private ActionBarDrawerToggle mDrawerToggle;
    ...
    public void onCreate(Bundle savedInstanceState) {
        ...
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        mDrawerToggle = new ActionBarDrawerToggle(                this,                  /* host Activity */
                mDrawerLayout,         /* DrawerLayout object */
                R.drawable.ic_drawer,  /* nav drawer icon to replace 'Up' caret */
                R.string.drawer_open,  /* "open drawer" description */
                R.string.drawer_close  /* "close drawer" description */
                ) {

            /** Called when a drawer has settled in a completely closed state. */
            public void onDrawerClosed(View view) {
                super.onDrawerClosed(view);
                getActionBar().setTitle(mTitle);
            }
            /** Called when a drawer has settled in a completely open state. */
            public void onDrawerOpened(View drawerView) {
                super.onDrawerOpened(drawerView);
                getActionBar().setTitle(mDrawerTitle);
            }        };

        // Set the drawer toggle as the DrawerListener        mDrawerLayout.setDrawerListener(mDrawerToggle);

        getActionBar().setDisplayHomeAsUpEnabled(true);
        getActionBar().setHomeButtonEnabled(true);
    }
    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        // Sync the toggle state after onRestoreInstanceState has occurred.
        mDrawerToggle.syncState();
    }    @Override
    public void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);
        mDrawerToggle.onConfigurationChanged(newConfig);
    }
    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Pass the event to ActionBarDrawerToggle, if it returns
        // true, then it has handled the app icon touch event
        if (mDrawerToggle.onOptionsItemSelected(item)) {
          return true;
        }
        // Handle your other action bar items...
        return super.onOptionsItemSelected(item);
    }
    ...
}

最后给一个例子的完整版
导航抽屉完整版

未完待续……

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容