android contextmenu 自定义,ContextMenu高级用法

51CT0不支持MarkDown,原文请至:简书

关键字: ContextMenu

##背景

我们经常在列表的页面中,点击列表中的行,一般进入详情页面,长按列表中一行,会弹出一个菜单,包含了对某一行的操作(编辑、删除等等),也知道通常的用法:

- 0x01. 在Activity中注册需要上下文菜单的View:

`registerForContextMenu(mListView);`

- 0x02. 然后在Activity中继承onCreateContextMenu方法,添加菜单项:

```

@Override

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {

Log.d(LOG_TAG, "onCreateContextMenu");

super.onCreateContextMenu(menu, v, menuInfo);

menu.setHeaderTitle(R.string.prompt);

menu.add(Menu.NONE, R.id.context_menu_item_delete_record, Menu.NONE, R.string.delete_record);//groupId, itemId, order, title

menu.add(Menu.NONE, R.id.context_menu_item_delete_record_with_file, Menu.NONE, R.string.delete_record_with_file);

}```

**PS:每次长按出现上下文菜单都会调用这个方法**

```

/** * Called when a context menu for the {@code view} is about to

be shown. * Unlike {@link #onCreateOptionsMenu(Menu)}, this will

be called every * time the context menu is about to be shown and

should be populated for * the view (or item inside the view for {@link

AdapterView} subclasses, * this can be found in the {@code

menuInfo})). *

* Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an

* item has been selected. *

* It is not safe to hold onto the

context menu after this method returns. * */

public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {

}

```

- 0x03. 接下来长按列表中一行的时候,会弹出上下文菜单:

![device-2015-11-04-141103.png](http://upload-p_w_picpaths.jianshu.io/upload_p_w_picpaths/728306-c1f997a517d009c7.png?p_w_picpathMogr2/auto-orient/strip%7Cp_w_picpathView2/2/w/1240)

- 0x04. 点击菜单后,在Activity中继承onContextItemSelected方法进行处理:

```

@Override

public boolean onContextItemSelected(MenuItem item) {

switch (item.getItemId()){

}

}

```

- 0x05. 获取Item标识(id)

我们删除数据库或者一行记录的时候,要知道主键(一般是id)才能进行操作,很多人就想办法,有的是把ListView的每个ItemView添加一个LongClickListener,然后长按的时候记录下Position,然后在进行相应处理。

其实有更优雅的做法,onContextItemSelected(MenuItem item)回调的参数item可以获取item.getMenuInfo(),在ListView和Adapter的模式中,可以强制转换成AdapterContextMenuInfo,拿到targetView(即所长按行的ItemVew,如果我们需要什么参数,直接放到View.setTag中去即可):

```

AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();

int index = info.position;

View view = info.targetView;

```

至此,常见的用法就完了,那么遇到其他自定义View呢?

- 0x06. 自定义View的ContextMenu实现

下面以用到的RecycleView为例,没有了ListView及其Adapter的封装,我们需要自己处理ContextMenu。

最重要的是继承View的两个方法:

1.上下文菜单Item的附加信息(上面item.getMenuInfo());

```

/** * Views should implement this if they have extra information to

associate * with the context menu. The return result is supplied as a

parameter to * the {@link

OnCreateContextMenuListener#onCreateContextMenu(ContextMenu,

View, ContextMenuInfo)} * callback. * * @return Extra information

about the item for which the context menu *         should be shown.

This information will vary across different *         subclasses of View. */

protected ContextMenuInfo getContextMenuInfo() {

return null;

}

```

2.ViewGroup的showContextMenuForChild,每次弹出上下文菜单都会调用此方法,需要在这里更新ContextMenuInfo;

```

/** * {@inheritDoc} */

public boolean showContextMenuForChild(View originalView) {

return mParent != null && mParent.showContextMenuForChild(originalView);

}

```

- 0x07. 自定义RecycleView的ContextMenu全部代码

```

package com.lbrant.phone.view;

import android.content.Context;

import android.support.v7.widget.RecyclerView;

import android.util.AttributeSet;

import android.util.Log;

import android.view.ContextMenu;

import android.view.View;

/**

* 作者:dell

* 时间:2015/11/3 18:34

* 文件:PhoneRecorder

* 描述:

*/

public class ContextMenuRecyclerView extends RecyclerView {

private static final String LOG_TAG = "ContextMenuRecyclerView";

private RecyclerContextMenuInfo mContextMenuInfo = new RecyclerContextMenuInfo();

public ContextMenuRecyclerView(Context context) {

super(context);

}

public ContextMenuRecyclerView(Context context, AttributeSet attrs) {

super(context, attrs);

}

public ContextMenuRecyclerView(Context context, AttributeSet attrs, int defStyle) {

super(context, attrs, defStyle);

}

@Override

protected ContextMenu.ContextMenuInfo getContextMenuInfo() {

return mContextMenuInfo;

}

@Override

public boolean showContextMenuForChild(View originalView) {

Log.d(LOG_TAG, "showContextMenuForChild");

Object tag = originalView.getTag();

if (tag instanceof RecyclerItemMarker) {

mContextMenuInfo.mRecycleItemMarker = (RecyclerItemMarker) tag;

}

return super.showContextMenuForChild(originalView);

}

public static class RecyclerItemMarker {

public final int position;

public final Object obj;

public RecyclerItemMarker(int position, Object obj) {

this.position = position;

this.obj = obj;

}

}

public static class RecyclerContextMenuInfo implements ContextMenu.ContextMenuInfo {

public RecyclerItemMarker mRecycleItemMarker;

}

}

private class RecordRecycleViewAdapter extends RecyclerView.Adapter {

private Cursor mCallRecordCursor;

private int mIdIndex;

private int mPhoneNumberIndex;

private int mCallTimeIndex;

private int mDurationIndex;

private int mPathIndex;

public RecordRecycleViewAdapter(Cursor cursor) {

mCallRecordCursor = cursor;

updateCursorColumnIndex();

}

private void updateCursorColumnIndex() {

if (mCallRecordCursor != null) {

mIdIndex = mCallRecordCursor.getColumnIndex(BaseDatabaseHelper.RECORDS_COLUMNS._ID);

mPhoneNumberIndex = mCallRecordCursor.getColumnIndex(BaseDatabaseHelper.RECORDS_COLUMNS.NUMBER);

mCallTimeIndex = mCallRecordCursor.getColumnIndex(BaseDatabaseHelper.RECORDS_COLUMNS.CALL_TIME);

mDurationIndex = mCallRecordCursor.getColumnIndex(BaseDatabaseHelper.RECORDS_COLUMNS.DURATION);

mPathIndex = mCallRecordCursor.getColumnIndex(BaseDatabaseHelper.RECORDS_COLUMNS.PATH);

}

}

@Override

public RecordRecycleViewAdapter.RecordViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

View contentView = LayoutInflater.from(parent.getContext()).inflate(R.layout.record_list_item, parent, false);

RecordViewHolder viewHolder = new RecordViewHolder(contentView);

return viewHolder;

}

@Override

public void onBindViewHolder(RecordRecycleViewAdapter.RecordViewHolder holder, final int position) {

holder.itemView.setLongClickable(true);

if (mCallRecordCursor != null && mCallRecordCursor.moveToPosition(position)) {

long id = mCallRecordCursor.getLong(mIdIndex);

String phoneNumber = mCallRecordCursor.getString(mPhoneNumberIndex);

long seconds = mCallRecordCursor.getLong(mDurationIndex);

String callTime = mCallRecordCursor.getString(mCallTimeIndex);

String path = mCallRecordCursor.getString(mPathIndex);

String duration = String.format("%1$02d:%2$02d:%3$02d", seconds / 3600, seconds % 3600 / 60, seconds % 60);

RecordInfo info = new RecordInfo();

info.setId(id);

info.setPhoneNumber(phoneNumber);

info.setSecondsDuration(seconds);

info.setCallTime(callTime);

info.setPath(path);

holder.itemView.setTag(new ContextMenuRecyclerView.RecyclerItemMarker(position, info));

holder.mTextViewPhoneNumber.setText(phoneNumber);

holder.mTextViewDuration.setText(duration);

holder.mTextviewCallTime.setText(callTime);

Cursor cursor = queryContactByPhoneNumber(ContactsContract.CommonDataKinds.Phone.NUMBER + " = '" + phoneNumber + "'");

if (cursor != null) {

if (cursor.moveToNext()) {

long contactId = cursor.getInt(0);

Cursor contactCursor = queryContact(ContactsContract.Contacts._ID + "=" + contactId);

if (contactCursor != null) {

holder.mTextViewName.setText(contactCursor.getString(1));

contactCursor.close();

}

}

cursor.close();

}

}

}

@Override

public void onViewRecycled(RecordViewHolder holder) {

super.onViewRecycled(holder);

holder.itemView.setOnCreateContextMenuListener(null);

}

@Override

public int getItemCount() {

return mCallRecordCursor == null ? 0 : mCallRecordCursor.getCount();

}

public void changeCursor(Cursor cursor) {

if (cursor != mCallRecordCursor) {

if (mCallRecordCursor != null) {

mCallRecordCursor.close();

}

mCallRecordCursor = cursor;

updateCursorColumnIndex();

notifyDataSetChanged();

}

}

public class RecordViewHolder extends RecyclerView.ViewHolder {

private ImageView mImageViewAvatar;

private TextView mTextViewPhoneNumber;

private TextView mTextViewName;

private TextView mTextviewCallTime;

private TextView mTextViewDuration;

public RecordViewHolder(View itemView) {

super(itemView);

mImageViewAvatar = (ImageView) itemView.findViewById(R.id.p_w_picpathViewAvatar);

mTextViewName = (TextView) itemView.findViewById(R.id.textViewName);

mTextViewPhoneNumber = (TextView) itemView.findViewById(R.id.textViewPhoneNumber);

mTextviewCallTime = (TextView) itemView.findViewById(R.id.textViewCallTime);

mTextViewDuration = (TextView) itemView.findViewById(R.id.textViewDuration);

}

}

}

```

**有两个地方需要注意:**

1.onBindViewHolder中给ItemView添加Tag;

2.设置ItemView的LongClickable为true,不然不会出现上下文菜单(具体原因见ContextMenu原理分析);

holder.itemView.setLongClickable(true);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android ContextMenuAndroid系统提供的一种菜单风格,其主要作用是为用户提供与所选项相关的操作,例如复制、黏贴、编辑等。开发人员可以使用系统默认的ContextMenu自定义ContextMenu。 系统默认的ContextMenu是由系统提供的,其中包含了很多默认的MenuItem,如复制、黏贴、编辑等,同时开发者可以添加自己的MenuItem。系统默认的ContextMenu通常是通过在View上长按触发的。 与其他View一样,ContextMenu也是由AndroidView类派生出来的。具体地,它是通过在View类中重写onCreateContextMenu()方法来实现的。在此方法中,开发者可以设置ContextMenu的MenuItem,添加各项操作。当用户长按View时,系统会调用此方法,以显示ContextMenu。 除了系统默认的ContextMenu,开发者还可以创建自定义ContextMenu,并在需要时使用它。创建自定义ContextMenu的过程与创建一般的Menu类似。开发者可以在ContextMenu上添加自定义的MenuItem,还可以添加回调函数以响应用户的操作。创建自定义ContextMenu后,将其与所需的View关联即可,与系统默认的ContextMenu相同。 总之,Android ContextMenu是一种很实用的界面元素,它可以为用户提供丰富的操作,使用户体验更加优秀。开发者可以使用系统默认的ContextMenu自定义ContextMenu来满足业务需求。本文简单介绍了ContextMenu的原理与创建方法,但具体实现还要参考相关的API类库。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值