在某些情况下,操作栏可能会被另一个使用ActionMode启用上下文操作的栏覆盖。例如,当用户在您的活动中选择一个或多个项目时,您可以启用一种操作模式,该模式提供针对所选项目的特定操作,并使用一个UI临时替换操作栏。尽管UI可能占用相同的空间,但ActionMode API与ActionBar的API是不同的且独立的。
https://developer.android.com/reference/android/support/v7/app/ActionBar
上下文操作模式(ActionMode)
用户长按某一元素时出现的浮动菜单,此模式在屏幕顶部栏显示影响所选内容的操作项目,并允许用户选择多项,会直接影响对应的内容。
一、为单个视图创建上下文操作模式
实现 ActionMode.Callback 接口:
回调方法中,您既可以为上下文操作栏指定操作选项(显示内容),又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。
private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() {
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
MenuInflater inflater = mode.getMenuInflater();
inflater.inflate(R.menu.context_menu, menu);
return true;
}
//该方法用于创建Menu视图
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_share:
shareCurrentItem();
mode.finish();
return true;
default:
return false;
}
}
//该方法用于对用户的操作做出相应的反馈
public void onDestroyActionMode(ActionMode mode) {
mActionMode = null;
}
//及时清除mActionMode引用,一者为了垃圾回收,二者为了后面再次进入上下文操作模式考虑
}
在 View 的 LongClickListener 中调用 startActionMode() 启用上下文操作模式
private ActionMode mActionMode;
someView.setOnLongClickListener(new View.OnLongClickListener() { //someView是一个普通的View控件
public boolean onLongClick(View view) {
if (mActionMode == null) { mActionMode = getActivity().startActionMode(mActionModeCallback)};
//根据情况如果消耗事件则返回true,没有消耗事件则返回false。
view.setSelected(true);
..............
return true;
}
});
在当前 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 进入集合;否则进行跳转、删除等操作。
原文链接:https://blog.csdn.net/evan_man/article/details/51685022
ActionMode 底层分析
分析目的是修改上下文浮动操作栏的返回图标。
getActivity().startActionMode()
@Activity.class
public ActionMode startActionMode(ActionMode.Callback callback) {
return mWindow.getDecorView().startActionMode(callback);
}
其中mWindow = new PhoneWindow(this);因此我们往下看PhoneWindow的startActionMode方法。
@PhoneWindow.class
public ActionMode startActionMode(ActionMode.Callback callback) {
if (mActionMode != null) { mActionMode.finish(); }
final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapper(callback);
ActionMode mode = null;
...
if (mode != null) {
mActionMode = mode;
} else {
if (mActionModeView == null) {//创建ActionModeView
if (isFloating()) {
mActionModeView = new ActionBarContextView(mContext);//note1
mActionModePopup = new PopupWindow(mContext, null,
com.android.internal.R.attr.actionModePopupWindowStyle);
mActionModePopup.setWindowLayoutType(
WindowManager.LayoutParams.TYPE_APPLICATION);
mActionModePopup.setContentView(mActionModeView); //mActionModeView这里是准备被显示的View
mActionModePopup.setWidth(MATCH_PARENT);
TypedValue heightValue = new TypedValue();
mContext.getTheme().resolveAttribute(
com.android.internal.R.attr.actionBarSize, heightValue, true);
final int height = TypedValue.complexToDimensionPixelSize(heightValue.data,
mContext.getResources().getDisplayMetrics());
mActionModeView.setContentHeight(height);
mActionModePopup.setHeight(WRAP_CONTENT);
mShowActionModePopup = new Runnable() {
public void run() {
mActionModePopup.showAtLocation( //note2
mActionModeView.getApplicationWindowToken(),
Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
}
};
} else {
ViewStub stub = (ViewStub) findViewById(
com.android.internal.R.id.action_mode_bar_stub);
if (stub != null) {
mActionModeView = (ActionBarContextView) stub.inflate();
}
}
}
if (mActionModeView != null) { //显示ActionModeView
mActionModeView.killMode();
mode = new StandaloneActionMode(getContext(), mActionModeView, wrappedCallback,
mActionModePopup == null);
if (callback.onCreateActionMode(mode, mode.getMenu())) {//创建菜单到ActionMode中
mode.invalidate();
mActionModeView.initForMode(mode);//note3
mActionModeView.setVisibility(View.VISIBLE);
mActionMode = mode;
if (mActionModePopup != null) {
post(mShowActionModePopup); //交给Handler去执行前面的Runnable异步方法
}
mActionModeView.sendAccessibilityEvent(
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
} else {
mActionMode = null;
}
}
}
...
return mActionMode;
}
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
public void showAtLocation(IBinder token, int gravity, int x, int y) {
...
final WindowManager.LayoutParams p = createPopupLayoutParams(token);
preparePopup(p);
...
invokePopup(p);
}
将视图显示到手机界面上。具体内容讲完note3后就会详细分析。
note3
@ActionBarContextView.class
public void initForMode(final ActionMode mode) {
if (mClose == null) {
LayoutInflater inflater = LayoutInflater.from(mContext);
mClose = inflater.inflate(mCloseItemLayout, this, false);
//看到这里都想哭了,,,,,,,找了半天就是想搞明白那个返回键究竟在哪设置的!!!!
//这里终于找到了,mCloseItemLayout就是定义了返回键的布局文件,它的定义看note1,即ActionBarContextView的构造器。
addView(mClose);
} else if (mClose.getParent() == null) {
addView(mClose);
}
View closeButton = mClose.findViewById(R.id.action_mode_close_button);
closeButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
mode.finish(); //点击返回按钮则销毁当前ActionBarContextView视图
}
});
...
}
————————————————
原文链接:https://blog.csdn.net/evan_man/article/details/51685022