Android之三种Menu的使用与分析

请尊重他人劳动成果,请勿随意剽窃,转载请注明,谢谢! 转载请注明出处: 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() 等操作修改菜单项。

备注限于篇幅原因该部分详细内容参考ToolBar博客 http://blog.csdn.net/evan_man/article/details/51684947


PartB:上下文操作模式(ActionMode

    用户长按某一元素时出现的浮动菜单,此模式在屏幕顶部栏显示影响所选内容的操作项目,并允许用户选择多项,会直接影响对应的内容。上下文操作模式是 ActionMode 的一种系统实现,它将用户交互的重点转到执行上下文操作上。

一、为单个视图创建上下文操作模式
  • 实现 ActionMode.Callback 接口:
    • 回调方法中,您既可以为上下文操作栏指定操作选项(显示内容),又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。
    • [java]  view plain  copy
        在CODE上查看代码片 派生到我的代码片
      1. private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {  
      2. public boolean onCreateActionMode(ActionMode mode, Menu menu) {  
      3.         MenuInflater inflater = mode.getMenuInflater();  
      4.         inflater.inflate(R.menu.context_menu, menu);  
      5.         return true;  
      6. }  
      7. //该方法用于创建Menu视图  
      8. public boolean onActionItemClicked(ActionMode mode, MenuItem item) {  
      9.         switch (item.getItemId()) {  
      10.             case R.id.menu_share:  
      11.                 shareCurrentItem();  
      12.                 mode.finish();   
      13.                 return true;  
      14.             default:  
      15.                 return false;  
      16.         }  
      17. }  
      18. //该方法用于对用户的操作做出相应的反馈  
      19. public void onDestroyActionMode(ActionMode mode) {  
      20.         mActionMode = null;  
      21. }  
      22. //及时清除mActionMode引用,一者为了垃圾回收,二者为了后面再次进入上下文操作模式考虑  
      23. }  


  • 在View的LongClickListener中调用 startActionMode() 启用上下文操作模式
  • [java]  view plain  copy
      在CODE上查看代码片 派生到我的代码片
    1. private ActionMode mActionMode;  
    2. someView.setOnLongClickListener(new View.OnLongClickListener() { //someView是一个普通的View控件  
    3.     public boolean onLongClick(View view) {  
    4.         if (mActionMode == null) { mActionMode = getActivity().startActionMode(mActionModeCallback)};  
    5.         //根据情况如果消耗事件则返回true,没有消耗事件则返回false。  
    6.         view.setSelected(true);  
    7.         ..............  
    8.         return true;  
    9.     }  
    10. });  
  • 在当前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);
注意RecyclerView并没有提供setChoiceMode这样的一个方法。但是要实现上述功能也不难,大体思路可以如下:( Adapter中需要声明SparseArray markPosition集合; ActionMode actionMode浮动上下文操作栏引用;  )
  • 在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
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public ActionMode startActionMode(ActionMode.Callback callback) {  
  2.         return mWindow.getDecorView().startActionMode(callback);  
  3. }  
其中mWindow = new PhoneWindow(this);因此我们往下看PhoneWindow的startActionMode方法。
@PhoneWindow.class
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public ActionMode startActionMode(ActionMode.Callback callback) {  
  2.             if (mActionMode != null) { mActionMode.finish(); }  
  3.             final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);  
  4.             ActionMode mode = null;  
  5.             ...........  
  6.             if (mode != null) {  
  7.                 mActionMode = mode;  
  8.             } else {  
  9.                 if (mActionModeView == null) {//创建ActionModeView  
  10.                     if (isFloating()) {  
  11.                         mActionModeView = new ActionBarContextView(mContext);//note1  
  12.                         mActionModePopup = new PopupWindow(mContext, null,  
  13.                                 com.android.internal.R.attr.actionModePopupWindowStyle);   
  14.                         mActionModePopup.setWindowLayoutType(  
  15.                                 WindowManager.LayoutParams.TYPE_APPLICATION);  
  16.                         mActionModePopup.setContentView(mActionModeView); //mActionModeView这里是准备被显示的View  
  17.                         mActionModePopup.setWidth(MATCH_PARENT);  
  18.   
  19.                         TypedValue heightValue = new TypedValue();  
  20.                         mContext.getTheme().resolveAttribute(  
  21.                                 com.android.internal.R.attr.actionBarSize, heightValue, true);  
  22.                         final int height = TypedValue.complexToDimensionPixelSize(heightValue.data,  
  23.                                 mContext.getResources().getDisplayMetrics());  
  24.                         mActionModeView.setContentHeight(height);  
  25.                         mActionModePopup.setHeight(WRAP_CONTENT);  
  26.                         mShowActionModePopup = new Runnable() {  
  27.                             public void run() {  
  28.                                 mActionModePopup.showAtLocation( //note2  
  29.                                         mActionModeView.getApplicationWindowToken(),  
  30.                                         Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);  
  31.                             }  
  32.                         };  
  33.                     } else {  
  34.                         ViewStub stub = (ViewStub) findViewById(  
  35.                                 com.android.internal.R.id.action_mode_bar_stub);  
  36.                         if (stub != null) {  
  37.                             mActionModeView = (ActionBarContextView) stub.inflate();  
  38.                         }  
  39.                     }  
  40.                 }  
  41.   
  42.                 if (mActionModeView != null) { //显示ActionModeView  
  43.                     mActionModeView.killMode();  
  44.                     mode = new StandaloneActionMode(getContext(), mActionModeView, wrappedCallback,  
  45.                             mActionModePopup == null);  
  46.                     if (callback.onCreateActionMode(mode, mode.getMenu())) {//创建菜单到ActionMode中  
  47.                         mode.invalidate();  
  48.                         mActionModeView.initForMode(mode);//note3  
  49.                         mActionModeView.setVisibility(View.VISIBLE);  
  50.                         mActionMode = mode;  
  51.                         if (mActionModePopup != null) {  
  52.                             post(mShowActionModePopup); //交给Handler去执行前面的Runnable异步方法  
  53.                         }  
  54.                         mActionModeView.sendAccessibilityEvent(  
  55.                                 AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);  
  56.                     } else {  
  57.                         mActionMode = null;  
  58.                     }  
  59.                 }  
  60.             }  
  61.             ....  
  62.             return mActionMode;  
  63. }  
-------------------------------------------------------------
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
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void showAtLocation(IBinder token, int gravity, int x, int y) {  
  2.         .........  
  3.         final WindowManager.LayoutParams p = createPopupLayoutParams(token);  
  4.         preparePopup(p);  
  5.         .....  
  6.         invokePopup(p);  
  7. }  
将视图显示到手机界面上。具体内容讲完note3后就会详细分析。

note3
@ActionBarContextView.class
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public void initForMode(final ActionMode mode) {  
  2.         if (mClose == null) {  
  3.             LayoutInflater inflater = LayoutInflater.from(mContext);  
  4.             mClose = inflater.inflate(mCloseItemLayout, thisfalse);    
  5.             //看到这里都想哭了,,,,,,,找了半天就是想搞明白那个返回键究竟在哪设置的!!!!  
  6.             //这里终于找到了,mCloseItemLayout就是定义了返回键的布局文件,它的定义看note1,即ActionBarContextView的构造器。  
  7.             addView(mClose);  
  8.         } else if (mClose.getParent() == null) {  
  9.             addView(mClose);  
  10.         }  
  11.         View closeButton = mClose.findViewById(R.id.action_mode_close_button);  
  12.         closeButton.setOnClickListener(new OnClickListener() {  
  13.             public void onClick(View v) {  
  14.                 mode.finish(); //点击返回按钮则销毁当前ActionBarContextView视图  
  15.             }  
  16.         });  
  17.         ....  
  18. }  
-------------------------------------------------------------
最后我们可以对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默认是不显示图标的,而且对外也不提供相应的修改方法,通过反射进行如下修改。
  • //使用反射,强制显示菜单图标  
  • [cpp]  view plain  copy
      在CODE上查看代码片 派生到我的代码片
    1. try {    
    2.            Field field = popupMenu.getClass().getDeclaredField("mPopup");    
    3.            field.setAccessible(true);    
    4.            MenuPopupHelper mHelper = (MenuPopupHelper) field.get(popupMenu);    
    5.            mHelper.setForceShowIcon(true);    
    6.  catch (IllegalAccessException | NoSuchFieldException e) {      e.printStackTrace();   }   


补充3:下一个完整的用例:
[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. private void showPopupMenu(View v){  
  2.         PopupMenu popup = new PopupMenu(this, v);  
  3.         MenuInflater inflater = popup.getMenuInflater();  
  4.         inflater.inflate(R.menu.popmenu, popup.getMenu());  
  5.         try {  
  6.             Field field = popup.getClass().getDeclaredField("mPopup");  
  7.             field.setAccessible(true);  
  8.             MenuPopupHelper mHelper = (MenuPopupHelper) field.get(popup);  
  9.             mHelper.setForceShowIcon(true);  
  10.         } catch (IllegalAccessException | NoSuchFieldException e) {  
  11.             e.printStackTrace();   }  
  12.         popup.setOnMenuItemClickListener(new OnPopupMenuItemClickListener(this));  
  13.         popup.setGravity(Gravity.RIGHT);  
  14.         popup.show();  
  15. }  
补充4:主题设置(PopupMenu的 字体背景等
在xml文件中<item> <menu>标签中我们是无法设置背景和字体颜色的,通常情况是通过修改Theame属性来实现的,具体如下:
    
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <style name="ProfileTheme" parent="Theme.AppCompat.Light.NoActionBar">  
  2.       <!--修改PopupMenu的item背景颜色--><!--这里为何一个使用android:而另一个没有android: 是因为前者只有在android 5.0以后版本中才能被使用,后者是兼容模式任何版本都能使用(推荐使用后者)-->  
  3.       <item name="android:popupMenuStyle">@style/popupMenuProfile</item>  
  4.       <item name="popupMenuStyle">@style/popupMenuProfile</item>  
  5.   
  6.       <!--修改PopupMenu的分界线  注意添加这个会导致menuItem的点击动画发生变化-->  
  7.       <item name="android:dropDownListViewStyle">@style/dropDownStyle</item>  
  8.       <item name="dropDownListViewStyle">@style/dropDownStyle</item>  
  9.   
  10.       <!--修改PopupMenu的字体颜色-->  
  11.       <item name="android:textAppearanceLargePopupMenu">@style/popupTextProfile</item>  
  12.       <item name="textAppearanceLargePopupMenu">@style/popupTextProfile</item>  
  13.   
  14.       <!--此处的值也控制ActionBar背景-->  
  15.       <item name="colorPrimary">@color/black</item>  
  16.       <!--此处的值也控制ActionBar上面显示电量、信号那行视图的背景-->  
  17.       <item name="colorPrimaryDark">@color/black</item>  
  18.       <item name="colorAccent">@color/white</item>  
  19.   </style>  
  20.   
  21.   <style name="popupMenuProfile">  
  22.       <item name="android:popupBackground">@color/colorAlphaBlack</item>  
  23.   </style>  
  24.   <style name="dropDownStyle" parent="android:style/Widget.Holo.ListView.DropDown">  
  25.       <!--定义这样的style必须定义android:listSelector,否则会使用系统自带的selector那就不知道出什么幺蛾子-->  
  26.       <item name="android:listSelector">@drawable/profile_popupmenu_selector</item>  
  27.       <item name="android:divider">#80FFFFFF</item>  
  28.       <item name="android:dividerHeight">0.5dp</item>  
  29.   </style>  
  30.   <style name="popupTextProfile" parent="@style/TextAppearance.Widget.AppCompat.ExpandedMenu.Item">  
  31.       <item name="android:textColor">@android:color/white</item>  
  32.   </style>  

注意:上面的ProfileTheme在manifest.xml文件中可以加给某个Activity如:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <activity  
  2.             android:name=".activity.others.ProfileActivity"  
  3.             android:theme="@style/ProfileTheme">  
  4. </activity>  

PopupMenu底层分析(绘制流程探究)

显示流程

android.support.v7.widget. PopupMenu 中有一个域——private MenuPopupHelper mPopup;大部分操作都是委托它去执行的。
android.support.v7.view.menu. MenuPopupHelper,中有一个tryShow()方法,该方法负责具体绘制,完成工作有:
  1. ListPopupWindow mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes); //创建一个ListPopupWindow对象,该对象继承自Object
  2. 对mPopup进行一些初始化设置,如
    • mPopup.setOnDismissListener(this);
    • mPopup.setOnItemClickListener(this);
    • mPopup.setAdapter(mAdapter);
  3. mPopup.show(); //调用该方法显示视图
  • 总结:MenuPopupHelper管理了一个ListPopupWindow对象
android.support.v7.widget. ListPopupWindow的show方法,完成工作有:
  1. 调用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查看后面的内容
  2. 对mPopup进行一些初始化设置,如
    1. mPopup.setWidth(widthSpec);
    2. mPopup.setHeight(heightSpec);
    3. mPopup.setOutsideTouchable(...)
    4. mPopup.setTouchInterceptor(mTouchInterceptor);
  3. 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方法如下:
[java]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. public View getView(int position, View convertView, ViewGroup parent) {  
  2.             if (convertView == null) {  
  3.                 convertView = mInflater.inflate(ITEM_LAYOUT, parent, false); //note1  
  4.             }  
  5.             MenuView.ItemView itemView = (MenuView.ItemView) convertView;  
  6.             if (mForceShowIcon) {  
  7.                 ((ListMenuItemView) convertView).setForceShowIcon(true); //note2  
  8.             }  
  9.             itemView.initialize(getItem(position), 0); //note3  
  10.             return convertView;  
  11. }  

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的构造器中有如下方法:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ListPopupWindow,  R.attr.popupMenuStyle, 0);即从主题中定义的popupMenuStyle样式文件中和主题直接定义的属性中获取到如下属性:  
  2. <declare-styleable name="ListPopupWindow">  
  3.         <!-- 下拉垂直偏移距离. -->  
  4.         <attr name="dropDownVerticalOffset" format="dimension" />  
  5.         <!-- 下拉水平偏移距离. -->  
  6.         <attr name="dropDownHorizontalOffset" format="dimension" />  
  7. </declare-styleable>  

DropDownListView的构造器中有如下方法:
final TypedArray a =  context.obtainStyledAttributes(   attrs, R.styleable.ListView, R.attr.dropDownListViewStyle, 0); 从主题中定义的dropDownListViewStyle样式文件中和主题直接定义的属性中获取到如下属性:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <declare-styleable name="ListView">  
  2.         <!-- 为当前ListView指定静态数组资源,而不需要编写Adapter -->   
  3.         <attr name="entries" />  
  4.         <!-- listview中item之前的颜色或者Drawable. -->  
  5.         <attr name="divider" format="reference|color" />  
  6.         <!-- listview中item之前的相隔距离 . -->   
  7.         <attr name="dividerHeight" format="dimension" />  
  8.         <!-- 值为真则lsitview中的Header间不绘制divider,默认值为真 -->   
  9.         <attr name="headerDividersEnabled" format="boolean" />  
  10.         <!-- 值为真则lsitview中的Footer间不绘制divider,默认值为真  -->   
  11.         <attr name="footerDividersEnabled" format="boolean" />  
  12.         <!-- Drawable to draw above list content. -->  
  13.         <attr name="overScrollHeader" format="reference|color" />  
  14.         <!-- Drawable to draw below list content. -->  
  15.         <attr name="overScrollFooter" format="reference|color" />  
  16.     </declare-styleable>  


PopupWindow的构造器中有如下方法:
final TypedArray a = context.obtainStyledAttributes( null , R.styleable.PopupWindow, com.android.internal.R.attr.popupWindowStyle,0);即从主题中定义的popupWindowStyle样式文件中和主题直接定义的属性中获取到如下属性:
[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. <declare-styleable name="PopupWindow">  
  2.         <!-- 弹出窗口的背景. -->  
  3.         <attr name="popupBackground" format="reference|color" />  
  4.         <!-- 弹出窗口的高度(影响阴影). -->  
  5.         <attr name="popupElevation" format="dimension" />  
  6.         <!-- 弹出窗口的动画样式 -->  
  7.         <attr name="popupAnimationStyle" format="reference" />  
  8.         <!-- 弹出窗口是否遮盖锚视图 -->  
  9.         <attr name="overlapAnchor" format="boolean" />  
  10.         <!-- Transition used to move views into the popup window. -->  
  11.         <attr name="popupEnterTransition" format="reference" />  
  12.         <!-- Transition used to move views out of the popup window. -->  
  13.         <attr name="popupExitTransition" format="reference" />  
  14. </declare-styleable>  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值