ActionBar最近学习整理之三:焦点控制及菜单项构建
2013-08-25 17:42
218 查看
上一篇博文介绍了ActionBar的风格自定义的相关内容,在小结里引出了关于AB选项菜单焦点控制的话题,目前市场主流的Android手机大多都是触屏了,但是也有一些逆历史潮流而动的手机型号会强势出现,比如某双屏翻盖的“W系列神器”;对于一些有实体方向导航键的设备,AB的焦点控制十分重要了。从Google官方文档一坨坨的API里没有找到让菜单项获取焦点的方法,木有关系,研究下源码看看肿么实现。
ActionBar中焦点控制分析
比如,我要完成这样一个目标,在app启动时,让Actionbar上的某个指定选项菜单键获得焦点,默认启动时,焦点框是落在home区域,而我想要如下显示:
[align=center] [/align]
让Done菜单项获取焦点,想实现这个目的,先看下Android是怎么实现这个AB布局的,打开HierarchyView工具,关于AB部分的布局如下图所示,
每个矩形框表示一个View,上面展示了View的类型、索引号和id,三个颜色球表示的分别是该view的测量、布局和绘画的时间性能,红的最慢,绿的最快,如果你把鼠标放到矩形框上可以看到具体时间,右下角的数字表示该view在父视图的位置,以0开始,这张截图是hierarchyView给出截图的一部分,由于我们只关注AB的布局,content和其它内容先不看。
在hierarchyView工具界面中点击每个矩形框,会显示与之对应的布局视图,结合第一张图片显示的布局样式,对号入座,不难发现不同的矩形框对应哪个控件,得到了如下结论
a) ActionBar的基本布局被放置在一个ActionBarContainer中,该Container包括ActionBarView和ActionBarContextView,前者是AB默认显示的基本布局,后者是和ActionMode相关。
b) ActoinBarView是个水平方向的LinearLayout,从左至右依次ActionBarView$HomeView、标题LinearLayout和ActionMenuView,HomeView在第一篇AB博文中已经提到,就是up箭头和应用图标组合,布局源码是action_bar_home.xml,标题LinearLayout包含主标题和子标题,眼尖的同学发现在标题栏布局中还有个up箭头图标,确实是,第一篇AB博文中提到不同的选项开关控制不同的组件显示,那个up箭头不是仅仅在HomeView区里有,在标题区也有,只是源码控制不让他们同时出现,主要是便于视图控制。
c) ActionMenuView显示是存储菜单项的地方,每个菜单项都是一个ActionMenuItemView布局,最右边的溢出菜单,是一个OverFlowMenuButton类型按钮。从ActionMenuItemView上的id可以看出,这就是要获取焦点的控件,源码里显示该类型本质上又是一个TextView,而TextView的requestFocus仿佛在向我们招手~
按照上述思路,根据布局位置进行遍历,得到了最终的view_menu_done,获取焦点,上述代码实现了该过程,有兴趣的同学可以查看log输出,进一步验证HierarchyView中的结论。本文举例是cancel按钮获取焦点方法,AB上其它控件的焦点属性控制,同理可证。
ActionBar中的菜单创建流程
上述似乎是从结论得到的方法,先看到了布局,然后得到了方法,那么源码中AB上的视图到底是如何构建的呢,仍然以菜单项为例,简要整理下AB上的菜单创建流程。
[align=center] [/align]
[align=center] [/align]
[align=left] 上图是我整理的AB菜单项创建序列图,因为不影响阅读水印懒得去了,AB菜单项的构建类似于MVC的控制方式,数据源是用户在onCreateOptionMenu中添加的菜单项,V是在ActionBarView中的addView显示,而C则是适配器MenuPresenter,他在这个过程中类似于ListAdapter在列表构建中的作用,创建ActionMenuItem的视图并绑定数据。下面简要介绍下上述序列图的主要内容。[/align]
[align=left] [/align]
[align=left] 第一阶段:Window初始化阶段 ( 1.1~1.4)[/align]
[align=left] [/align]
[align=left] 在窗口被创建并实例化后,Activity会调用窗口的setContentView方法,布局decorView并且为Menu创建坐准备,decorView是整个Window界面最顶层的View,也就是根视图,窗口中包括AB和Content内容,第一阶段任务就是创建根视图及创建菜单实例的过程。DecorView布局文件时screen_action_bar.xml中: [/align]
以上代码取自Master分支,不同版本略有不同。
[align=left] [/align]
[align=left] 第二阶段:AB菜单项布局显示(适配器设置)阶段 1.5.1 ~ 1.5.3[/align]
[align=left] [/align]
[align=left] ActionBarView会为MenuBuilder对象配置MenuPresenter,MenuBuilder是个实现了Menu接口的构建器,主要作用就是把包含于其中的菜单数据展示出来,听起来类似MenuPresenter,其实有区别,MenuBuilder中存有菜单的内容数据,包括普通菜单项内容,ActionItem菜单项内容、可视的、可扩展的等,以列表的形式贯穿起来;每个菜单项以MenuItemImpl的形式存储于该列表中,MenuBuilder中也同时包括MenuPresenter对象,Presenter的侧重点是以何种形式将菜单项内容展现出来。[/align]
[align=left] 序列图中1.5.1 即时为MenuBuilder添加MenuPresenter对象,第二阶段是从PhoneWindow调用ActionBarView的setMenu函数开始的。[/align]
该阶段完成了对菜单视图的配置,ActionMenuPresenter.getMenuView完成具体的菜单项视图创建工作,源代码不难理解但较为繁杂,我画了一张类图来大致表示在该视图配置创阶段的类关系,类图可能会有些UML规范问题,领会精神。
[align=left] 该类图中,左边MenuItemImpl表示每个具体的菜单项数据,包括id、分组、排序、图标等信息,MenuBuilder中的mActionItems就表示一系列被设置成AB菜单项的内容列表。中间的MenuBuilder,是PhoneWindow在setMenu是传进来的构造器参数,该对象左有数据源(mActionItems),右有显示适配器(mPresenters),负责为AB菜单项显示的适配器实际上是个ActionMenuPresenter类型,通过调用该适配器的getMenuView方法获取菜单布局(mMenuItemLayoutRes)和每个菜单项的视图(mItemLayoutRes),适配器通过调用bindItemView来为每个视图布局绑定数据。 [/align]
每个AB菜单项都会把itemData绑定到菜单视图中,而itemData的数据,则早在为1.5.1.1 initForMenu时,Presenter就获得了含有itemData的MenuBuilder。
第三阶段 菜单项内容数据的导入阶段 1.6
这个阶段其实就是PhoneWindow通过回调Activity的onCreateOptionMenu函数,将开发者的菜单内容导入到MenuBuilder中,该阶段发生在setMenu之后,源代码如下:
如果在Activity中没有创建AB选项菜单内容,则setMenu的MenuBuilder为空,AB上也就不会显示任何内容。反之,对于在onCreateOptionMenu中使用MenuInflater导入菜单布局文件的情况(通常大家设置菜单的方式),如果菜单布局文件非空,MenuInflater调用inflater函数向目标menu对象填充菜单内容,
填充内容的menu对象也就是作为MenuBuilder传递给ActionBarView来进行菜单设置(setMenu函数)的MenuBuilder。MenuBuilder拥有了这些菜单项内容后,才能供Presenter调用显示。
小结
本文对AB上的基本布局及焦点控制做了分析,以源码为基础,梳理了AB上Menu的构建过程,由于用的是Master分支源码,可能会和已经发布的其它版本有所出入。前三篇关于AB的博文都是关于AB显示选项内容,由于项目原因,AB的整理暂且告一段落,关于AB的其它内容,如Tab、导航模式、ActionMode、ActionProvider等,会在以后的博文中陆续分享。
~~~版权所有,转载请声明~~~
ActionBar中焦点控制分析
比如,我要完成这样一个目标,在app启动时,让Actionbar上的某个指定选项菜单键获得焦点,默认启动时,焦点框是落在home区域,而我想要如下显示:
[align=center] [/align]
让Done菜单项获取焦点,想实现这个目的,先看下Android是怎么实现这个AB布局的,打开HierarchyView工具,关于AB部分的布局如下图所示,
每个矩形框表示一个View,上面展示了View的类型、索引号和id,三个颜色球表示的分别是该view的测量、布局和绘画的时间性能,红的最慢,绿的最快,如果你把鼠标放到矩形框上可以看到具体时间,右下角的数字表示该view在父视图的位置,以0开始,这张截图是hierarchyView给出截图的一部分,由于我们只关注AB的布局,content和其它内容先不看。
在hierarchyView工具界面中点击每个矩形框,会显示与之对应的布局视图,结合第一张图片显示的布局样式,对号入座,不难发现不同的矩形框对应哪个控件,得到了如下结论
a) ActionBar的基本布局被放置在一个ActionBarContainer中,该Container包括ActionBarView和ActionBarContextView,前者是AB默认显示的基本布局,后者是和ActionMode相关。
b) ActoinBarView是个水平方向的LinearLayout,从左至右依次ActionBarView$HomeView、标题LinearLayout和ActionMenuView,HomeView在第一篇AB博文中已经提到,就是up箭头和应用图标组合,布局源码是action_bar_home.xml,标题LinearLayout包含主标题和子标题,眼尖的同学发现在标题栏布局中还有个up箭头图标,确实是,第一篇AB博文中提到不同的选项开关控制不同的组件显示,那个up箭头不是仅仅在HomeView区里有,在标题区也有,只是源码控制不让他们同时出现,主要是便于视图控制。
c) ActionMenuView显示是存储菜单项的地方,每个菜单项都是一个ActionMenuItemView布局,最右边的溢出菜单,是一个OverFlowMenuButton类型按钮。从ActionMenuItemView上的id可以看出,这就是要获取焦点的控件,源码里显示该类型本质上又是一个TextView,而TextView的requestFocus仿佛在向我们招手~
mHandler.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub handle_focus(); } }, 300); private void handle_focus(){ Log.i(TAG, "handle_focus begin!"); int actionMenuViewIndex=-1; int actionBarViewIndex=-1; int actionBarContainerIndex=-1; ViewGroup rView = (ViewGroup) getWindow().getDecorView(); ViewGroup ll = (ViewGroup)rView.getChildAt(0); for( int i=0;i<ll.getChildCount();i++){ if(ll.getChildAt(i) instanceof ActionBarContainer){ actionBarContainerIndex = i; break; } } ActionBarContainer action_bar = (ActionBarContainer)ll.getChildAt(actionBarContainerIndex); if(action_bar!=null){ for( int i=0;i<action_bar.getChildCount();i++){ if(action_bar.getChildAt(i) instanceof ActionBarView){ actionBarViewIndex =i; break; } } ActionBarView ABview =(ActionBarView) action_bar.getChildAt(actionBarViewIndex); if(ABview!=null){ if (DEBUG) Log.d(TAG, "Aview != null + count = " + ABview.getChildCount()); for( int i=0;i<ABview.getChildCount();i++){ if (DEBUG) Log.d(TAG, "i = " + i + " " + ABview.getChildAt(i)); if(ABview.getChildAt(i) instanceof ActionMenuView){ if (DEBUG) Log.d(TAG, i + "th Aview's child is instanceof ActionMenuView"); actionMenuViewIndex =i; break; } } } if (DEBUG) Log.d(TAG, "indexOf_ActionBarContainer = " + actionBarContainerIndex + "indexOf_ActionBarView = " + actionBarViewIndex + "indexOf_ActionmenuView = " + actionMenuViewIndex); if(actionMenuViewIndex!=-1){ ActionMenuView AmView =(ActionMenuView)ABview.getChildAt(actionMenuViewIndex); view_menu_cancel=(ActionMenuItemView)AmView.getChildAt(0); view_menu_down = (ActionMenuItemView)AmView.getChildAt(1); if (view_menu_down != null) { Log.i(TAG, "view_pictrue request focus!"); view_menu_down.requestFocus(); } if (view_menu_cancel != null) { view_menu_cancel.setNextFocusUpId(view_menu_down.getId()); } } } }
按照上述思路,根据布局位置进行遍历,得到了最终的view_menu_done,获取焦点,上述代码实现了该过程,有兴趣的同学可以查看log输出,进一步验证HierarchyView中的结论。本文举例是cancel按钮获取焦点方法,AB上其它控件的焦点属性控制,同理可证。
ActionBar中的菜单创建流程
上述似乎是从结论得到的方法,先看到了布局,然后得到了方法,那么源码中AB上的视图到底是如何构建的呢,仍然以菜单项为例,简要整理下AB上的菜单创建流程。
[align=center] [/align]
[align=center] [/align]
[align=left] 上图是我整理的AB菜单项创建序列图,因为不影响阅读水印懒得去了,AB菜单项的构建类似于MVC的控制方式,数据源是用户在onCreateOptionMenu中添加的菜单项,V是在ActionBarView中的addView显示,而C则是适配器MenuPresenter,他在这个过程中类似于ListAdapter在列表构建中的作用,创建ActionMenuItem的视图并绑定数据。下面简要介绍下上述序列图的主要内容。[/align]
[align=left] [/align]
[align=left] 第一阶段:Window初始化阶段 ( 1.1~1.4)[/align]
[align=left] [/align]
[align=left] 在窗口被创建并实例化后,Activity会调用窗口的setContentView方法,布局decorView并且为Menu创建坐准备,decorView是整个Window界面最顶层的View,也就是根视图,窗口中包括AB和Content内容,第一阶段任务就是创建根视图及创建菜单实例的过程。DecorView布局文件时screen_action_bar.xml中: [/align]
<com.android.internal.widget.ActionBarOverlayLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/action_bar_overlay_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:splitMotionEvents="false"> <FrameLayout android:id="@android:id/content" android:layout_width="match_parent" android:layout_height="match_parent" /> <LinearLayout android:id="@+id/top_action_bar" android:layout_width="match_parent" android:layout_height="wrap_content"> <com.android.internal.widget.ActionBarContainer android:id="@+id/action_bar_container" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentTop="true" style="?android:attr/actionBarStyle" android:gravity="top"> <com.android.internal.widget.ActionBarView android:id="@+id/action_bar" android:layout_width="match_parent" android:layout_height="wrap_content" style="?android:attr/actionBarStyle" /> <com.android.internal.widget.ActionBarContextView android:id="@+id/action_context_bar" android:layout_width="match_parent" android:layout_height="wrap_content" android:visibility="gone" style="?android:attr/actionModeStyle" /> </com.android.internal.widget.ActionBarContainer> <ImageView android:src="?android:attr/windowContentOverlay" android:scaleType="fitXY" android:layout_width="match_parent" android:layout_height="wrap_content" /> </LinearLayout> <com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar" android:layout_width="match_parent" android:layout_height="wrap_content" style="?android:attr/actionBarSplitStyle" android:visibility="gone" android:gravity="center"/> </com.android.internal.widget.ActionBarOverlayLayout>
以上代码取自Master分支,不同版本略有不同。
[align=left] [/align]
[align=left] 第二阶段:AB菜单项布局显示(适配器设置)阶段 1.5.1 ~ 1.5.3[/align]
[align=left] [/align]
[align=left] ActionBarView会为MenuBuilder对象配置MenuPresenter,MenuBuilder是个实现了Menu接口的构建器,主要作用就是把包含于其中的菜单数据展示出来,听起来类似MenuPresenter,其实有区别,MenuBuilder中存有菜单的内容数据,包括普通菜单项内容,ActionItem菜单项内容、可视的、可扩展的等,以列表的形式贯穿起来;每个菜单项以MenuItemImpl的形式存储于该列表中,MenuBuilder中也同时包括MenuPresenter对象,Presenter的侧重点是以何种形式将菜单项内容展现出来。[/align]
[align=left] 序列图中1.5.1 即时为MenuBuilder添加MenuPresenter对象,第二阶段是从PhoneWindow调用ActionBarView的setMenu函数开始的。[/align]
public void setMenu(Menu menu, MenuPresenter.Callback cb) { if (menu == mOptionsMenu) return; if (mOptionsMenu != null) { mOptionsMenu.removeMenuPresenter(mActionMenuPresenter); mOptionsMenu.removeMenuPresenter(mExpandedMenuPresenter); } MenuBuilder builder = (MenuBuilder) menu; mOptionsMenu = builder; if (mMenuView != null) { final ViewGroup oldParent = (ViewGroup) mMenuView.getParent(); if (oldParent != null) { oldParent.removeView(mMenuView); } } if (mActionMenuPresenter == null) { mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setCallback(cb); mActionMenuPresenter.setId(com.android.internal.R.id.action_menu_presenter); mExpandedMenuPresenter = new ExpandedActionViewMenuPresenter(); } ActionMenuView menuView; final LayoutParams layoutParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT); if (!mSplitActionBar) { mActionMenuPresenter.setExpandedActionViewsExclusive( getResources().getBoolean( com.android.internal.R.bool.action_bar_expanded_action_views_exclusive)); configPresenters(builder); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); final ViewGroup oldParent = (ViewGroup) menuView.getParent(); if (oldParent != null && oldParent != this) { oldParent.removeView(menuView); } addView(menuView, layoutParams); } else { mActionMenuPresenter.setExpandedActionViewsExclusive(false); // Allow full screen width in split mode. mActionMenuPresenter.setWidthLimit( getContext().getResources().getDisplayMetrics().widthPixels, true); // No limit to the item count; use whatever will fit. mActionMenuPresenter.setItemLimit(Integer.MAX_VALUE); // Span the whole width layoutParams.width = LayoutParams.MATCH_PARENT; configPresenters(builder); menuView = (ActionMenuView) mActionMenuPresenter.getMenuView(this); if (mSplitView != null) { final ViewGroup oldParent = (ViewGroup) menuView.getParent(); if (oldParent != null && oldParent != mSplitView) { oldParent.removeView(menuView); } menuView.setVisibility(getAnimatedVisibility()); mSplitView.addView(menuView, layoutParams); } else { // We'll add this later if we missed it this time. menuView.setLayoutParams(layoutParams); } } mMenuView = menuView; }
该阶段完成了对菜单视图的配置,ActionMenuPresenter.getMenuView完成具体的菜单项视图创建工作,源代码不难理解但较为繁杂,我画了一张类图来大致表示在该视图配置创阶段的类关系,类图可能会有些UML规范问题,领会精神。
[align=left] 该类图中,左边MenuItemImpl表示每个具体的菜单项数据,包括id、分组、排序、图标等信息,MenuBuilder中的mActionItems就表示一系列被设置成AB菜单项的内容列表。中间的MenuBuilder,是PhoneWindow在setMenu是传进来的构造器参数,该对象左有数据源(mActionItems),右有显示适配器(mPresenters),负责为AB菜单项显示的适配器实际上是个ActionMenuPresenter类型,通过调用该适配器的getMenuView方法获取菜单布局(mMenuItemLayoutRes)和每个菜单项的视图(mItemLayoutRes),适配器通过调用bindItemView来为每个视图布局绑定数据。 [/align]
public void bindItemView(MenuItemImpl item, MenuView.ItemView itemView) { itemView.initialize(item, 0); //此itemVIew的运行时类型时ActionMenuItemView final ActionMenuView menuView = (ActionMenuView) mMenuView; ActionMenuItemView actionItemView = (ActionMenuItemView) itemView; actionItemView.setItemInvoker(menuView); } public void initialize(MenuItemImpl itemData, int menuType) { mItemData = itemData; setIcon(itemData.getIcon()); setTitle(itemData.getTitleForItemView(this)); // Title only takes effect if there is no icon setId(itemData.getItemId()); setVisibility(itemData.isVisible() ? View.VISIBLE : View.GONE); setEnabled(itemData.isEnabled()); }
每个AB菜单项都会把itemData绑定到菜单视图中,而itemData的数据,则早在为1.5.1.1 initForMenu时,Presenter就获得了含有itemData的MenuBuilder。
第三阶段 菜单项内容数据的导入阶段 1.6
这个阶段其实就是PhoneWindow通过回调Activity的onCreateOptionMenu函数,将开发者的菜单内容导入到MenuBuilder中,该阶段发生在setMenu之后,源代码如下:
if ((cb == null) || !cb.onCreatePanelMenu(st.featureId, st.menu)) { // Ditch the menu created above st.setMenu(null); if (isActionBarMenu && mActionBar != null) { // Don't show it in the action bar either mActionBar.setMenu(null, mActionMenuPresenterCallback); } return false; }
如果在Activity中没有创建AB选项菜单内容,则setMenu的MenuBuilder为空,AB上也就不会显示任何内容。反之,对于在onCreateOptionMenu中使用MenuInflater导入菜单布局文件的情况(通常大家设置菜单的方式),如果菜单布局文件非空,MenuInflater调用inflater函数向目标menu对象填充菜单内容,
public void inflate(int menuRes, Menu menu) { XmlResourceParser parser = null; try { parser = mContext.getResources().getLayout(menuRes); AttributeSet attrs = Xml.asAttributeSet(parser); parseMenu(parser, attrs, menu); // xml菜单文件的具体解析过程 } catch (XmlPullParserException e) { throw new InflateException("Error inflating menu XML", e); } catch (IOException e) { throw new InflateException("Error inflating menu XML", e); } finally { if (parser != null) parser.close(); } }
填充内容的menu对象也就是作为MenuBuilder传递给ActionBarView来进行菜单设置(setMenu函数)的MenuBuilder。MenuBuilder拥有了这些菜单项内容后,才能供Presenter调用显示。
小结
本文对AB上的基本布局及焦点控制做了分析,以源码为基础,梳理了AB上Menu的构建过程,由于用的是Master分支源码,可能会和已经发布的其它版本有所出入。前三篇关于AB的博文都是关于AB显示选项内容,由于项目原因,AB的整理暂且告一段落,关于AB的其它内容,如Tab、导航模式、ActionMode、ActionProvider等,会在以后的博文中陆续分享。
~~~版权所有,转载请声明~~~
相关文章推荐
- ActionBar最近学习整理之三:焦点控制及菜单项构建
- ActionBar最近学习整理之二:风格自定义
- ActionBar最近学习整理之一:显示选项内容及菜单项
- 最近用到的JavaScript框架整理/学习
- 深度学习 13. 能力提升, 一步一步的介绍如何自己构建网络和训练,利用MatConvNet(二),思路整理
- 对最近学习的知识整理(回调函数和简单的设计思路)
- 韩顺平_轻松搞定网页设计(html+css+javascript)_第22讲_js三大流程控制(顺序流程、分支控制、循环控制)_学习笔记_源代码图解_PPT文档整理
- MongoDB学习整理之访问控制
- 蓝牙协议学习整理(二)蓝牙协议规范(射频、基带链路控制、链路管理)
- 最近要写CMS,因为是学java的,所以找了一些开源的javaCMS学习,特别整理一下方便大家使用
- 重新整理后的Oracle OAF学习笔记——4.应用构建基础之实现视图
- 对最近的RTP和H264学习进行总结整理-04.20
- 最近学习AJAX和版本控制,收集到的网站
- Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
- 韩顺平_轻松搞定网页设计(html+css+javascript)_第23讲_js三大流程控制(顺序流程、分支控制、循环控制)_js调式技巧_学习笔记_源代码图解_PPT文档整理
- Android TV开发总结(三)构建一个TV app的焦点控制及遇到的坑
- 整理Javascript流程控制语句学习笔记
- 谈下最近学习hmtl5的一些知识:控制结构
- 整理一下最近学习使用的一些IT工具,老的新的都有之一基本篇
- 最近要深一步用到GPIO口控制,写个博客记录下Kernel层的GPIO学习过程