ToolBar、ActionBar与Menu的纠葛(以及navigationIcon、setHomeButtonEnabled、setDisplayHomeAsUpEnabled)

 

因为贴图的繁琐。。。所以下面是无图版。。。完整的在这里:

探究一下toobar和actionbar在使用menu时的吊诡情况

 

一、背景介绍
  1. toolbar最近比较火,而且是出出来代替actionbar的,但是目前还没有完全代替
  2. 但其实actionbar也是在Android 3.0(API 11)中才加入到SDK中的,也并不古老
  3. 更早的还有tittle bar(不带menu),Action Bar取代了传统的tittle bar和menu,在程序运行中一直置于顶部
    1. ActionBar和TitleBar是同一个位置的  都是在状态栏下
    2. ActionBar 是3.3版本后推出的,上面可以放入按钮,或下拉式的按钮.可以有文字,logo等信息.还可以设置返回按钮等信息
    3. TitleBar只有APP图标和文字.不支持点击事件
 
二、actionbar与menu
1.titlebar不再介绍了很简单,下面稍微介绍一下actionbar
  1. ActionBar的图标,可显示软件图标,也可用其他图标代替。当软件不在最高级页面时,图标左侧会显示一个左箭头,用户可以通过这个箭头向上导航;
  2. 如果你的应用要在不同的View中显示数据,这部分允许用户来切换视图。一般的作法是用一个下拉菜单或者是Tab选项卡。如果只有一个界面,那这里可以显示应用程序的标题或者是更长一点的商标信息;
  3. 两个action按钮,这里放重要的按钮功能,为用户进行某项操作提供直接的访问;
  4. overflow按钮,放不下的按钮会被置于“更多...”菜单项中,“更多...”菜单项是以下拉形式实现的
 
2.然后详细说一下这个bar使用menu的情况
2.1.但其实menu系统应该是独立的,所以先介绍它
2.1.1.独立的menu系统的实现步骤:
  1. 当Activity启动的时候,系统会调用Activity的onCreateOptionsMenu()方法(这个方法和onCreate方法是同一级别)来取出所有的menu按钮,我们只需要在这个方法中去加载一个menu资源,并把所有的Action按钮都定义在资源文件里面就可以了
    1. 这个menu资源就是一个xml文件,细节不讲,大概是这样,一看就懂
    2. 然后重写Activity的onCreateOptionsMenu()方法,代码如下所示:
      1. 通过getMenuInflater()方法能够得到MenuInflater对象,再调用它的inflate()方法就可以给当前活动创建菜单了
      2. inflate()方法接收两个参数,第一个参数用于指定我们通过哪一个资源文件来创建菜单,这里当然传入R.menu.main,第二个参数用于指定我们的菜单项将添加到哪一个Menu对象当中,这里直接使用onCreateOptionsMenu()方法中传入的 menu参数
      3. 然后给这个方法返回true, 表示允许创建的菜单显示出来, 如果返回了false, 创建的菜单将无法显示
  2. 第一步完成后,这个菜单就可以显示出来了(具体怎么显示下面再说),但是仅仅让菜单显示出来是不够的,我们定义菜单不仅是为了看的,关键是要菜单真正可用才行,因此还要再定义菜单响应事件。
    1. 我们继续在Activity中重写 onOptionsItemSelected()方法:
      1. 当用户点击按钮的时候,系统会调用这个方法,通过方法传入的MenuItem参数,我们可以调用它的getItemId()方法和menu资源中的id进行比较,从而辨别出用户点击的是哪一个Action按钮
 
2.1.2.独立的menu系统
2.1.2.1.背景概述
  1. 你会发现到这里我都没有介绍实现的效果,而且前面我还说menu系统是独立的
  2. 对的,就是这样,menu系统是跟titlebar一个时代的东西,而titlebar中是不能定义menu的,它是两个东西,我们后来见得很多的menu放在标题栏里的样子其实是后来actionbar中才能实现的
  3. 而后来虽然titlebar被淘汰,甚至actionbar都要被淘汰,但是menu系统却一直在,只是被整合了,所以说,单独拿出menu系统来研究还是非常有价值的
 
2.1.2.2.历史与现状
一直以来menu系统的功能和实现原理没有变,就是上面那两步,但是显示方式的细节发生了变化,这里来简单缕一缕:
  1. 最初的时候只有titlebar,一定有实体(物理)的menu按键,menu系统在点击 menu 按键 后会在对应的Activity底部显示出来。这其实就是最初的menu的完全体
    1. 很多资料已经说不建议使用,因为用户看不到,不一定会去点实体菜单
  2. 之后有了actionbar,但实际中可以设置可以不设置,而实体的menu按键可能有可能没有,情况就复杂一些了
    1. 如果没有定义actionbar,那么你定义的菜单就是通过点击实体的menu按键,然后在对应的Activity底部显示出来,跟上面一样
    2. 如果你定义了actionbar,而且没有实体menu按键,那就是标准的情况,你定义的menu会全部出现在actionbar的这两个位置,根据你为了actionbar增加设置的显示属性(不设置默认不显示在actionbar上)
    3. 如果你定义了actionbar,同时有实体menu按键,那就是不标准的情况,你定义menu会一部分出现在actionbar上,另一部分原本在overflow中的按钮会出现在Activity 底部,通过你点击menu 按键召唤
 
2.2.actionbar和menu的深度结合
其实上一个已经说完了menu独立的部分,还说到到了menu与actionbar结合的部分,这里继续说
  1. 另外说一下使用actionbar的功能本身不用另外设置,因为它默认是包含在主体中的
  2. 从Android3.0(API级别 11)开始,Action bar被包含在所有的使用Theme.Hole主题的Activity(或者是这些Activity的子类)中,当targetSdkVersion或minSdkVersion属性被设置为“11”或更大的数值是,这个主题是默认的主题一
2.2.1.actionbar图标导航
  1. 默认情况下,应用程序图标显示在操作栏的左边,你能够把这个图标当做操作项来使用
    1. 从Android4.0(API 级别 14)开始,必须通过调用actionbar.setHomeButtonEnabled(true)方法确保这个图标能够作为一个操作项
    2. 在以前的版本,默认情况下,这个图标就能够作为一个操作项
  2. 当用户触摸这个图标时,系统会调用Activity带有android.R.id.home ID的onOptionsItemSelected()方法
  3. 也就是说,整个过程是:
    1. 通过调用actionbar.setHomeButtonEnabled(true)方法增加一个按钮(普通的Menu按钮是先定义xml,再通过一个方法引入这个xml)
    2. 直接在跟普通menu按钮一样的地方(onOptionsItemSelected()方法 )中定义按钮被点击后执行的方法,但它的ID固定是 android.R.id.home
  4. 这个功能一般设置为返回,但这个图标导航其实用得不多,因为用户不友好
2.2.2.actionbar返回图标导航
  1. actionbar返回图标导航在形式上就是在ActionBar图标(如果没有设置图标icon,文字标题也可以代替)的左侧添加了一个向左的箭头,通常情况下这都表示返回的意思
    1. 这里一个高级用法,其实也是actionbar图标导航本质的东西是:与返回按钮不一样的层级关系。这点很有用,但是在这里不用讲,另外的笔记中我写了非常详细的内容。这里我们就把它当成最简单的返回来用
  2. 样子大概是这样
  3. 它的使用方法跟上面图标导航非常像
    1. 通过actionBar.setDisplayHomeAsUpEnabled(true);增加一个按钮
    2. 直接在跟普通menu按钮一样的地方(onOptionsItemSelected()方法 )中定义按钮被点击后执行的方法,但它的ID固定是 android.R.id.home(跟上面那个的ID一毛一样)
  4. 这个用得还算多
2.2.3.然后还可以增加按钮的list、一个输入框view...等其他的功能跟Menu有点像但其实关系不大,就不细说了
          
2.2.4.说几个actionbar的容易被混淆的方法
上面说过的两个:
  1. actionbar.setHomeButtonEnabled(true);确保icon图标能够作为一个操作项被点击
  2. actionBar.setDisplayHomeAsUpEnabled(true);在ActionBar图标(如果没有设置图标icon,文字标题也可以代替)的左侧添加了一个向左的箭头
然后还有:
  1. actionBar.setDisplayShowHomeEnabled(true);使左上角图标是否显示,如果设成false,则没有程序图标,仅仅就个标题,否则,显示应用程序图标
  2. actionBar.setDisplayShowTitleEnabled(true) ;使左上角标题是否显示
  3. actionBar.setDisplayShowCustomEnabled(true);使自定义的普通View(比如输入框)能在title栏显示,即actionBar.setCustomView能起作用
 
三、toolbar与menu
1.背景简介
  1. 对于toolbar与menu的关系我没找到比较详细的说明,下面都是我根据实际应用的体会总结而来的,总体感觉是非常不适,非常撕裂,体验不怎么好
  2. (上面的说法已经由我自己的探索推翻了,感觉非常好,我喜欢toolbar哈哈)
  3. toolbar是5.0之后有的,是design包中的控件,它的特点是增强了跟其他东西的支持,能自定义更多的东西,简化了很多功能的使用,谷歌希望用它来代替actionbar
 
2.普通而简单的menu使用
而它在menu这块,是这样的:
  1. 先写一个menu的xml,这个跟经典的形式一样
  2. 然后在activity的xml中增加一个toolbar的view
  3. 然后在onCreate方法中为toobar绑定menu的xml和定义menu事件,跟经典的形式差不多,但不是一个东西
 
3.蛋疼很久但最终幸运地完美解决的menu使用:定义左上角导航按钮
上面这些步骤都非常正常,过程确实也比menu或者比actionbar上添加Menu要简单
直到遇到这个坑:定义左上角导航按钮
这个有两个具体情况:
 
3.1.先说简单而特殊的:设置左上角导航按钮为打开抽屉功能
定义这个的过程非常简单,是安卓特地包装过的,还带有一个挺炫的动画效果,它是通过ActionBarDrawerToggle这个类实现的
具体过程是这样:
  1. 你只需要在onCreate方法中给你的DrawerLayout配置一个ActionBarDrawerToggle就好了,其他什么都不用做,icon会自动出现,监听事件什么都不用写
 
3.2.接下来说复杂吊诡而一般的:设置左上角导航按钮为其他功能,比如返回
3.2.1.前缘
  1. 在说这个之前,我们来简单回忆一下actionbar关于这块的内容
    1. actionbar左侧可以设置两个按钮(不知道能不能同时设置)
    2. 他们的回调事件ID都是android.R.id.home
    3. 他们的icon不可更改(appicon就当成不可改),返回的icon不好看
    4. 点击事件跟actionbar上的menu的点击事件写在一起
  2. 而在toolbar中
    1. toolbar左侧可以自行添加一个任意图片的icon,(在xml中在代码中都可以,是一样的),名字叫navigationIcon
    2. 但是这个navigationIcon 无法设置点击事件(真是找遍了都找不到,跟toolbar上menu的点击事件写一起不顶用)
    3. 于是需要采用一种非常蛋疼的退化的写法:重新召唤actionbar,这种写法非常撕裂
 
3.2.2.变故
。。。真是源码大法好啊!理清了上面那些内容后,我意识到只要找到toolbar左侧这个icon的监听方法或者属性ID,就能实现我要的功能,而这个监听方法或属性ID我是在各种博客里都没有找到,但是这样就意味着它不存在吗?并不是,因为我觉得它的监听方法或者属性ID其实实现起来非常简单,而且实现它的需求也非常合理,没有理由没有实现,于是我抱着试试看的想法去翻了一下源码(在AS中能看源码真是太好了)
  1. 我使用了搜索技能,搜索我设置icon用的关键词:
  2. 找到了几次,有几个方法



  3. 其中最后这个方法我认为非常可疑,于是我尝试在代码中写了这样一个监听器,然后我运行程序,尼玛!居然成功了!
 
3.2.3.完美结局:自定义左上角导航按钮功能
有了上面的战果,我这里就能加一个很棒的完美结局了
  1. toolbar左侧可以自行添加一个任意图片的icon,(在xml中在代码中都可以,是一样的)
  2. 通过toolbar的方法设置这个按钮的监听事件
 
3.2.4.再续前缘
上面这个完美解决非常简洁,但可惜至今没看到有人用,真是白瞎了,而我之前想写的方法是看到很多大牛的写法,虽然它现在看来非常繁琐而笨拙,但本身还是非常hack的,然后我也花了不少时间才理解它,作为纪念,我还是好好记录一下
 
(抄一遍上面关于toolbar导航按钮的说法,假设就是这样:)而在toolbar中
  1. toolbar左侧可以自行添加一个任意图片的icon,(在xml中在代码中都可以,是一样的)
  2. 但是这个icon无法设置点击事件(真是找遍了都找不到,跟toolbar上menu的点击事件写一起不顶用)
  3. 于是需要采用一种非常蛋疼的退化的写法:重新召唤actionbar,这种写法非常撕裂
 
原理是这样:
  1. 召唤actionbar(不管是设置了主题是有actionbar还是noactionbar都而已)
  2. 把toolbar通过setSupportActionBar(toolbar)方法,把actionbar的内核换成toolbar,于是你获得了一个披着actionbar皮的toolbar
  3. 于是你把Toolbar当做ActionBar给设置了,menu可以像ActionBar一样用和处理
  4. 于是的navigationIcon 监听自动变成了对ID为android.R.id.home 的监听
 
具体步骤是这样(普通menu和导航按钮的结合)
  1. 先写一个menu的xml,这个跟经典的形式一样
  2. 然后在activity的xml中增加一个toolbar的view
  3. 在toolbar的view的xml中自行添加一个任意图片的navigationIcon(这里使用了系统默认的返回icon)
  4. 然后在onCreate方法中
    1. 增加调用setSupportActionBar(toolbar)方法进行狸猫换太子。这句话的位置好像非常关键,必须在这个地方才行。
    2. 减去使用toolbar绑定menu的xml
    3. 使用toobar定义menu事件的方法不变
  5. 使用menu原始的方法给toolbar绑定menu的xml
  6. 使用menu原始的方法给actionbar的home按钮绑定事件,ID是android.R.id.home ,其实就是在给我们toolbar的navigationIcon 绑定事件
  7. 结束,现在toolbar上能显示navigationIcon 和menu的按钮,而且都绑定了点击事件,整个过程非常撕裂
  8. 其实当时也有想,既然这样,为什么不直接用actionbar来实现呢,当时觉得只有一个原因,那就是actionbar的返回icon不好看,又不能自定义
posted @ 2016-04-12 13:14  赛艇队长  阅读(5669)  评论(2编辑  收藏  举报