请尊重他人劳动成果,请勿随意剽窃,转载请注明,谢谢!
转载请注明出处:
http://blog.csdn.net/evan_man/article/details/51685022
以下说明全部针对Android3.0(Api-11)。
本指南将介绍三种基本菜单分别是PartA:操作栏(选项菜单OptionMenu)、PartB:上下文操作模式(ActionMode)、PartC:弹出菜单(PopupMenu)。
PartA:操作栏(选项菜单)——onCreateOptionsMenu()创建的
以屏幕操作项和溢出选项的组合形式呈现选项菜单中的各项。
一、创建
为 Activity 指定选项菜单,重写 onCreateOptionsMenu()(Fragment 对应 onCreateOptionsMenu() 回调)。启动 Activity 时会调用 onCreateOptionsMenu()方法,因此可以在该方法中将菜单资源(使用 XML 定义)注入到回调方法的Menu 中。
二、处理响应事件
重写 onOptionsItemSelected() 方法,方法将传递所选中的 MenuItem。您可以通过调用 getItemId() 方法来识别对应item,该方法将返回菜单项的唯一 ID(由菜单资源中的 android:id 属性定义)。
补充:动态内容菜单内容
当菜单项显示在操作栏中时,选项菜单被视为始终处于打开状态。发生事件时,如果您要执行菜单更新,则必须调用 invalidateOptionsMenu() 来请求系统调用 onPrepareOptionsMenu()。在onPrepareOptionsMenu()方法中去通过 menu.add() 等操作修改菜单项。
PartB:上下文操作模式(ActionMode)
用户长按某一元素时出现的浮动菜单,此模式在屏幕顶部栏显示影响所选内容的操作项目,并允许用户选择多项,会直接影响对应的内容。上下文操作模式是 ActionMode 的一种系统实现,它将用户交互的重点转到执行上下文操作上。
一、为单个视图创建上下文操作模式
- 实现 ActionMode.Callback 接口:
- 回调方法中,您既可以为上下文操作栏指定操作选项(显示内容),又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。
-
- 在View的LongClickListener中调用 startActionMode() 启用上下文操作模式
- 在当前Activity或者Application的样式中对ActionMode的样式进行设置,一般设置如下
- <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
- <!--该项的设置是保证Activity视图顶部不会出现两个上下文操作栏,当前ActionMode将会覆盖在Toolbar上显示-->
- <item name="windowActionModeOverlay">true</item>
- <!--该项设置ActionMode背景、字体、返回按键样式等内容;可以参考一个Android提供的样式拷贝过来然后进行修改;好像直接继承的话,子类对父类的样式修改是无效的-->
- <!--这里也强烈建议对于自己不懂的样式我们可以借鉴别人的设置,稍加修改部分参数,这也是很鼓励的做法-->
- <item name="actionModeStyle">@style/actionModeStyle</item>
- </style>
- <style name="actionModeStyle" >
- <item name="background">@color/colorPrimary</item>
- <item name="backgroundSplit">?attr/actionModeSplitBackground</item>
- <item name="height">?attr/actionBarSize</item>
- <item name="titleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionMode.Title</item>
- <item name="subtitleTextStyle">@style/TextAppearance.AppCompat.Widget.ActionMode.Subtitle</item>
- <item name="closeItemLayout">@layout/abc_action_mode_close_item_material</item>
- </style>
二、为listView等复杂视图创建上下文操作模式
- 实现 AbsListView.MultiChoiceModeListener 接口,并使用 setMultiChoiceModeListener() 为视图组设置该接口。
- 侦听器的回调方法中,您既可以为上下文操作栏指定操作,也可以响应操作项目的点击事件,还可以处理从 ActionMode.Callback 接口继承的其他回调。
- listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { ......}
- 使用 CHOICE_MODE_MULTIPLE_MODAL 参数调用 setChoiceMode()。
- listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
- 在Adapter的onCreateViewHolder方法中给view注册一个 View.OnLongClickListener()监听器,该监听器的内容会首先检测当前 mActionMode 值是否为空,即浮动上下文操作栏显示与否。如果为空则调用mActionMode = getActivity().startActionMode(mActionModeCallback)显示浮动上下文操作栏。最后不管 mActionMode 值是否为空,都会将当前view对应在Adapter中的position记录进markPosition集合中,同时调用view.setSelected(true){如果期望View在选中时有特别的显示效果可以将view的background设置为一个State List类型的Drawable}。
- 在Adapter的onBindViewHolder方法中首先检测当前position是否属于前面的集合中的值,如果不属于则调用view.setSelected(false),属于则调用调用view.setSelected(true)。
- 最后点击浮动上下文菜单栏的某个按钮时,将之前的集合元素取出,处理完后清空集合。
- 补充:如果为获得更好的用户体验,可以在view的onClickListener中检测actionMode,如果该引用不为空则记录当前位置Postion进入集合;否则进行跳转、删除等操作。
ActionMode底层分析(分析目的是修改上下文浮动操作栏的返回图标)
getActivity().startActionMode()
@Activity.class
其中mWindow = new PhoneWindow(this);因此我们往下看PhoneWindow的startActionMode方法。
@PhoneWindow.class
-------------------------------------------------------------
note1:
ActionBarContextView()@ActionBarContextView.class
ActionBarContextView(mContext)-->最终调用构造器为:public ActionBarContextView(
Context context, null,com.android.internal.R.attr.actionModeStyle, 0)
构造器内部会调用final TypedArray a = context.obtainStyledAttributes( null
, R.styleable.ActionMode, com.android.internal.R.attr.actionModeStyle, 0);即从主题中定义的actionModeStyle样式文件中和主题直接定义的属性中获取到如下属性:
<declare-styleable name="ActionMode">
<!-- title的样式. -->
<attr name="titleTextStyle" />
<!-- subtitle的样式. -->
<attr name="subtitleTextStyle" />
<!-- 背景颜色 -->
<attr name="background" />
<!-- 拆分操作模式栏背景. -->
<attr name="backgroundSplit" />
<!-- 操作栏高度. -->
<attr name="height" />
<!-- 在操作栏开始位置的close视图(返回按键)的布局. -->
<attr name="closeItemLayout" format="reference" />
</declare-styleable>
下面这一行是获取返回按键布局的非常关键的一行代码!!!也可以说closeItemLayout属性定义了整个ActionMode最左边的布局视图信息,注意如果要自定义返回按钮其id必须为@+id/action_mode_close_button。
mCloseItemLayout = a.getResourceId(
com.android.internal.R.styleable.ActionMode_closeItemLayout,
R.layout.action_mode_close_item);
note2
showAtLocation@PopupWindow.class
将视图显示到手机界面上。具体内容讲完note3后就会详细分析。
note3
@ActionBarContextView.class
-------------------------------------------------------------
最后我们可以对PopupWindow做一个总结:PopupWindow.setContentView(View v); 方法参数是PopupWindow将要具体显示的内容,而PopupWindow的任务就是在屏幕中合适的位置将该View显示出来。但是该方法并不会将View显示出来,需要调用如下两个方法才能最终显示出来:showAtLocation(View parent, int gravity, int x, int y)
、showAsDropDown(View anchor, int xoff, int yoff)。showAtLocation是在一个特定的位置中显示视图,而showAsDropDown则会首先选取指定视图的左下方或者左上方显示视图。showAtLocation()和showAsDropDown()两者底层显示过程基本一致,先后调用preparePopup()和 invokePopup()方法,前者对即将显示的视图进行初始化操作,后者调用mWindowManager.addView(decorView, p);将视图显示出来。
PopupWindow的构造器中有如下的方法:final TypedArray a = context.obtainStyledAttributes( null
, R.styleable.PopupWindow, com.android.internal.R.attr.popupWindowStyle,0);因此它从主题中定义的popupWindowStyle样式文件中和主题直接定义的属性中获取到如下属性:
<declare-styleable name="PopupWindow">
<!-- 弹出窗口的背景. -->
<attr name="popupBackground" format="reference|color" />
<!-- 弹出窗口的高度(影响阴影). -->
<attr name="popupElevation" format="dimension" />
<!-- 弹出窗口的动画样式 -->
<attr name="popupAnimationStyle" format="reference" />
<!-- 弹出窗口是否遮盖锚视图 -->
<attr name="overlapAnchor" format="boolean" />
<!-- Transition used to move views into the popup window. -->
<attr name="popupEnterTransition" format="reference" />
<!-- Transition used to move views out of the popup window. -->
<attr name="popupExitTransition" format="reference" />
</declare-styleable>
PartC:弹出菜单(PopupMenu)
PopupMenu 是锚定到 View 的模态菜单。如果空间足够,它将显示在定位视图左下方,否则显示在其左上方。适用于提供与特定内容相关的大量操作,或者为命令的另一部分提供选项。不会直接影响对应的内容。
一、实例化PopupMenu及其构造器函数
- 该函数将提取当前应用的 Context 以及菜单应锚定到的 View。
- PopupMenu popup = new PopupMenu(this, v);
二、使用 MenuInflater 将菜单资源扩充到 PopupMenu.getMenu() 返回的 Menu 对象中
- MenuInflater inflater = popup.getMenuInflater();
- inflater.inflate(R.menu.actions, popup.getMenu());
- API 级别 14 及更高版本中,您可以改为使用 PopupMenu.inflate()
三、调用 PopupMenu.show()
- PopupMenu.show();
- 这里就会显示上图的菜单选项了
四、处理点击事件
- 实现 PopupMenu.OnMenuItemClickListener 接口,并通过调用 setOnMenuItemclickListener() 将其注册到 PopupMenu
补充1:监听PopupMenu销毁
- 当用户选择项目或触摸菜单以外的区域时,系统即会清除此菜单。 您可使用 PopupMenu.OnDismissListener 侦听清除事件。
补充2:显示图标
- PopupMenu默认是不显示图标的,而且对外也不提供相应的修改方法,通过反射进行如下修改。
- //使用反射,强制显示菜单图标
-
补充3:下一个完整的用例:
补充4:主题设置(PopupMenu的
字体背景等
)
在xml文件中<item> <menu>标签中我们是无法设置背景和字体颜色的,通常情况是通过修改Theame属性来实现的,具体如下:
注意:上面的ProfileTheme在manifest.xml文件中可以加给某个Activity如:
PopupMenu底层分析(绘制流程探究)
显示流程
android.support.v7.widget.
PopupMenu
中有一个域——private MenuPopupHelper mPopup;大部分操作都是委托它去执行的。
android.support.v7.view.menu.
MenuPopupHelper,中有一个tryShow()方法,该方法负责具体绘制,完成工作有:
- ListPopupWindow mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes); //创建一个ListPopupWindow对象,该对象继承自Object
- 对mPopup进行一些初始化设置,如
- mPopup.setOnDismissListener(this);
- mPopup.setOnItemClickListener(this);
- mPopup.setAdapter(mAdapter);
- mPopup.show(); //调用该方法显示视图
- 总结:MenuPopupHelper管理了一个ListPopupWindow对象
android.support.v7.widget.
ListPopupWindow的show方法,完成工作有:
- 调用int height = buildDropDown()方法,该方法进行的操作有:
- 创建android.support.v7.widget.ListPopupWindow.DropDownListView mDropDownList = new DropDownListView(context, !mModal);对象
- 对mDropDownList进行一些初始化设置,如
- mDropDownList.setAdapter(mAdapter);
- mDropDownList.setOnItemClickListener(mItemClickListener);
- mDropDownList.setFocusable(true);
- mDropDownList.setFocusableInTouchMode(true);
- mDropDownList.setOnScrollListener(mScrollListener);
- mDropDownList.setOnItemSelectedListener
- mPopup.setContentView(mDropDownList); //mPopup是在创建ListPopupWindow时创建的,PopupWindow mPopup = new PopupWindow(context, attrs, defStyleAttr)。通过setContentView将PopupWindow和DropDownList进行绑定。
- 注意:android.support.v7.widget.ListPopupWindow.DropDownListView extends ListViewCompat 而ListViewCompat extends ListView,那么具体的itemView自然是交给对应的Adapter来提供,对于Adapter查看后面的内容
- 对mPopup进行一些初始化设置,如
- mPopup.setWidth(widthSpec);
- mPopup.setHeight(heightSpec);
- mPopup.setOutsideTouchable(...)
- mPopup.setTouchInterceptor(mTouchInterceptor);
- mPopup.showAsDropDown(View anchor, int xoff, int yoff, int gravity)方法。该方法内部完成工作有:
- {
- final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());
- preparePopup(p);//布局信息初始化
- final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);
- updateAboveAnchor(aboveAnchor);//计算当前弹出菜单应该显示在当前View的上面还是下面,对背景色进行设置
- invokePopup(p);
- //使用了mWindowManager.addView(PopupDecorView mDecorView , ViewGroup.LayoutParams params)方法。
- //PopupDecorView mDecorView = new PopupDecorView(mContext);
- //decorView.addView(contentView, ViewGroup.LayoutParams.MATCH_PARENT, height);contentView是我们在第一步通过mPopup.setContentView(mDropDownList)方法传进来的DropDownListView对象
- //decorView.setClipChildren(false);
- //decorView.setClipToPadding(false);
- }
- 总结:ListPopupWindow对象管理了一个PopupWindow对象,PopupWindow的showAsDropDown方法将一个PopupDecorView对象通过WindowManager.addView方法添加到屏幕上,PopupDecorView中具体显示的内容则是DropDownListView对象。
-------------------------------------------------------------
最后我们可以对PopupWindow做一个总结:PopupWindow.setContentView(View v); 方法参数是PopupWindow将要具体显示的内容,而PopupWindow的任务就是在屏幕中合适的位置将该View显示出来。但是该方法并不会将View显示出来,需要调用如下两个方法才能最终显示出来:showAtLocation(View parent, int gravity, int x, int y)
、showAsDropDown(View anchor, int xoff, int yoff)。showAtLocation是在一个特定的位置中显示视图,
showAsDropDown则会首先选取指定视图的左下方或者左上方显示视图
。showAtLocation()和showAsDropDown()两者底层显示过程基本一致,先后调用preparePopup()和 invokePopup()方法,前者对即将显示的视图进行初始化操作,后者调用mWindowManager.addView(decorView, p);将视图交给WindowManager显示出来。之所以showAtLocation和showAsDropDown会
得到两种不同显示效果的原因在于WindowManager的addView(View view, ViewGroup.LayoutParams params)方法的第二个参数,WindowManager.LayoutParams——它的x、y决定视图在屏幕中的起始显示位置。
MenuPopupHelper中的MenuAdapter的定义
android.support.v7.view.menu.MenuPopupHelper中有一个适配器private class MenuAdapter extends BaseAdapter提供PopupMenu所要显示的视图。
其中一个很重要的getView方法如下:
1、static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;该布局文件只有一个Title和SubTitle的布局信息。外面包裹的是一个android.support.v7.view.menu.ListMenuItemView类型(是一个继承自LinearLayout的类)
2、设置ListMenuItemView的标志位 mPreserveIconSpacing = mForceShowIcon = true。mForceShowIcon域可以通过MenuPopupHelper的setForceShowIcon方法进行设置,默认是false。
3、调用android.support.v7.view.menu.ListMenuItemView的initialize方法对该行视图进行初始化设置:设置title、icon等。最后返回当前的convertView对象。
MenuPopupHelper中的MenuAdapter的传递
android.support.v7.view.menu.MenuPopupHelper在创建android.support.v7.widget.ListPopupWindow对象的时候会将MenuAdapter传过去。android.support.v7.widget.ListPopupWindow在创建android.support.v7.widget.ListPopupWindow.DropDownListView的时候也会将MenuAdapter传过去。DropDownListView是一个继承自ListView的控件,
之后就是ListView和Adapter的情况了,该部分可以参考ListView知识。
关于Style的设置
ListPopupWindow的构造器中有如下方法:
DropDownListView的构造器中有如下方法:
final TypedArray a =
context.obtainStyledAttributes(
attrs, R.styleable.ListView, R.attr.dropDownListViewStyle, 0);
即
从主题中定义的dropDownListViewStyle样式文件中和主题直接定义的属性中获取到如下属性:
PopupWindow的构造器中有如下方法:
final TypedArray a = context.obtainStyledAttributes( null
, R.styleable.PopupWindow, com.android.internal.R.attr.popupWindowStyle,0);即从主题中定义的popupWindowStyle样式文件中和主题直接定义的属性中获取到如下属性: