ContextMenu高级用法

关键字: 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})). * <p> * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an

  • item has been selected. * <p> * 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-images.jianshu.io/upload_images/728306-c1f997a517d009c7.png?imageMogr2/auto-orient/strip%7CimageView2/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<RecordRecycleViewAdapter.RecordViewHolder> {
    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.imageViewAvatar);
             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);
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容