Android带侧滑菜单和ToolBar的BaseActivity


写Android的时候,可能有多个界面。在风格统一的软件中,写Activity时会有很多重复。例如我所在软工课程小组的项目:Github链接 ,里面的TaskListActivity和TeacherListActivity就在Navigation的处理上有重复。还有一个双击退出APP的方法onBackPressed()也重复实现了。之前让负责界面的同学把这些代码放到一个BaseActivity里面,让其他Activity继承它。他说不好做,他尝试过,但失败了。

于是这次我独自做 英语词典APP 的时候 ,经过在Google上的一番搜索和实践探索,写出一个还可以的BaseActivity。现在做个记录,以后还会用得到。
BaseActivity项目的Github链接

这个BaseActivity包括侧滑菜单(Navigation Drawer)和工具栏(Tool Bar)。
先看最终效果:

  • 一、Navigation Drawer
  • 二、ToolBar
  • 三、参考链接

Creating a Navigation Drawer
根据说明,将 layout/activity_base.xml 设置为以下内容:

<?xml version="1.0" encoding="utf-8"?>

    
    
    
    

Android Studio不会提示android:layout_gravity这一项,但是整行敲完之后,可以正常运行。

  • 侧滑菜单列表布局
    这里只做简单的布局,所以侧滑菜单项都是TextView。
    创建 layout/list_item_drawer.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    
    

    因为等会儿要用ArrayAdapter,所以这里最外层一定要是TextView。

  • 侧滑菜单项的文本
    StringArray
    创建 values/string_array_test.xml

    <?xml version="1.0" encoding="utf-8"?>
    
        
            主界面
            关于
            设置
            退出
        
    
    
  • Activity初始化
    Initialize the Drawer List
    这里由于要将Activity做成BaseActivity,所以和官方文档上的代码不太一样。

    public class BaseActivity extends AppCompatActivity {
        protected String[] planetTitles;
        protected DrawerLayout drawerLayout;
        protected ListView drawerList;
        protected FrameLayout frameLayout;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
        }
    
        /**
        * 重写setContentView,以便于在保留侧滑菜单的同时,让子Activity根据需要加载不同的界面布局
        */
        @Override
        public void setContentView(@LayoutRes int layoutResID) {
            drawerLayout = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_base, null);
            frameLayout = (FrameLayout) drawerLayout.findViewById(R.id.content_frame);
            // 将传入的layout加载到activity_base的content_frame里面
            getLayoutInflater().inflate(layoutResID, frameLayout, true);
            super.setContentView(drawerLayout);
    
            setUpNavigation();
        }
    
        private void setUpNavigation() {
            planetTitles = getResources().getStringArray(R.array.planets_array);
            drawerList = (ListView) findViewById(R.id.left_drawer);
            drawerList.setAdapter(new ArrayAdapter<>(BaseActivity.this,
                    R.layout.list_item_drawer, planetTitles));
        }
    }
    
  • MainActivity
    先创建个Activity查看效果。
    创建MainActivity继承BaseActivity :

    public class MainActivity extends BaseActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
        }
    }
    

    layout_main用的是默认布局。
    在启动前要更改 AndroidManifest.xml ,将MainActivity设置成Launcher:

    
        
            
    
            
        
    
    

    启动后,手指从最左往右划,打开侧滑菜单。

  • 添加侧滑菜单点击事件
    Handle Navigation Click Events
    先创建ClickListener,这里简单地设置点击后显示所点击的文本。
    在BaseActivity中添加:

    private class DrawerItemClickListener implements ListView.OnItemClickListener {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            selectItem(position);
        }
    }
    
    private void selectItem(int position) {
        Toast.makeText(BaseActivity.this, planetTitles[position], Toast.LENGTH_SHORT).show();
    }
    

    在BaseActivity的 setUpNavigation() 中添加:

    drawerList.setOnItemClickListener(new DrawerItemClickListener());
    

  • Setting Up the App Bar
    打开AndroidManifest.xml,将里面的 android:theme="@style/AppTheme" 替换成 android:theme="@style/Theme.AppCompat.Light.NoActionBar",如下:

    
    ...(三点表示省略,下同)
    

    创建 layout/toolbar.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    
        
    
    
    
  • ToolBar 初始化
    在BaseActivity里添加:

    public class BaseActivity extends AppCompatActivity {
        ...
        private Toolbar toolbar;
    
        ...
        private void setUpToolBar() {
            toolbar = (Toolbar) findViewById(R.id.toolbar);
            setSupportActionBar(toolbar);
        }
    }
    

    在BaseActivity的 setContentView() 里的 setUpNavigation();下面添加setUpToolBar();,如下:

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        ...
        setUpNavigation();
        setUpToolBar();
    }
    

    如果想要在MainActivity里显示ToolBar,还需要在 activity_main.xml 里include一个ToolBar的布局:

    <?xml version="1.0" encoding="utf-8"?>
    
    
        
    
    
    

    显示的效果如下:

  • 加入侧滑菜单打开键
    到 Material Design Icons 下载图标。搜索menu,下载PNG版本。

    解压后,将 ic_menu_black_24dp/android 文件夹下面的所有文件夹复制到 res/ 里面。

    在BaseActivity的 setUpToolBar() 中添加:

    toolbar.setNavigationIcon(R.drawable.ic_menu_black_24dp);
    


    虽然有图标,但是还没设置点击事件,所以点击的时候没有任何反应。

  • 点击ToolBar的home按钮打开Navigation Drawer
    在BaseActivity里添加:

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                drawerLayout.openDrawer(GravityCompat.START);
                return true;
            default:
                break;
        }
        return super.onOptionsItemSelected(item);
    }
    

    这里就省略ActionBarDrawerToggle的部分了,影响不是特别大。实际应用中再去添加。

  • 添加Option Menu
    values/strings.xml 里添加等会儿会用到的字符串:

    
        ...
        编辑
        搜索
        变更记录
    
    

    去 Material Design Icons 下载相关图标。
    创建 res/menu/menu_main.xml

    <?xml version="1.0" encoding="utf-8"?>
    
    
        
    
        
    
        
    
    
    

    其中ifRoom表示如果ToolBar空间足够,则将其图标显示在ToolBar上面。never表示永不显示在ToolBar上面,只有当点击option menu按钮的时候,才会出现。
    在BaseActivity中添加:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        super.onCreateOptionsMenu(menu);
        return true;
    }
    

    启动APP:

    点击menu按钮后:

  • Option Menu的点击事件
    在BaseActivity里的 onOptionsItemSelected() 增加case即可。

  • 隐藏ActionBar的Option Menu
    有时候不想在某个Activity中显示Option Menu,于是修改BaseActivity:

    public class BaseActivity extends AppCompatActivity {
        ...
        private boolean showOptionMenu = true;
    
        ...
        protected void hideOptionMenu() {
        	showOptionMenu = false;
        }
    
        @Override
        public boolean onCreateOptionsMenu(Menu menu) {
            if (showOptionMenu) {
                getMenuInflater().inflate(R.menu.menu_main, menu);
            }
            super.onCreateOptionsMenu(menu);
            return true;
        }
    
    }
    
  • https://developer.android.com/training/appbar/setting-up.html
    https://developer.android.com/training/appbar/actions.html
    http://stackoverflow.com/questions/32367041/calling-toolbar-on-each-activity
    http://stackoverflow.com/questions/30824324/clicking-hamburger-icon-on-toolbar-does-not-open-navigation-drawer
    http://stackoverflow.com/questions/19724567/how-to-add-menu-indicator-next-to-action-bars-app-icon
    http://stackoverflow.com/questions/26582075/cannot-catch-toolbar-home-button-click-event

  • Navigation Drawer
    https://developer.android.com/training/implementing-navigation/nav-drawer.html
    http://stackoverflow.com/questions/33009469/baseactivity-for-navigation
    http://stackoverflow.com/questions/22652556/creating-base-activity-with-navigation-drawer-in-android

  • ArrayAdapter
    http://stackoverflow.com/questions/9280965/arrayadapter-requires-the-resource-id-to-be-a-textview-xml-problems
    http://stackoverflow.com/questions/29591546/unable-to-use-layout-gravity-for-listview-inside-drawerlayout

  • String Array
    https://developer.android.com/guide/topics/resources/string-resource.html#StringArray

  • Material icons
    https://design.google.com/icons/