Android学习笔记 -- 上下文菜单

November . 11 . 2018

上下文菜单在手机App中有着大量的应用, 也是最常用的显示菜单的方式之一, 具体应用在长按某一项需要出现一个菜单栏的情况, 如下图:

contextMenu.PNG

在开始介绍上下文菜单之前, 我们还需要了解一个控件PopupWindow, 顾名思义,就是弹窗,因为popUpwindow的特性完美的符合使用上下文菜单的使用场景, 所以一般的上下文菜单都要用Popupwindow作为载体来实现.

使用 PopupWindow 很简单,可以总结为三个步骤:

  1. 创建PopupWindow对象实例.
  2. 设置背景、注册事件监听器和添加动画.
  3. 显示PopupWindow.

关于 PopupWindow 知道这么多就够了, 现在我们就可以开始实现上下文菜单了.

1. 主页布局

添加一个 ListView 用来显示多条数据.

activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:orientation="vertical">
     <ListView
         android:id="@+id/id_listView_home"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:background="#ebebeb"
         android:divider="@null"/>
 </LinearLayout>

有了 ListView 自然还需要一个 ListViewItem, 来表示每一项.

home_item.xml
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="10dp"
     android:orientation="horizontal">
 
     <TextView
         android:id="@+id/id_textView_home_item"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:background="@drawable/bg_msg_item"
         android:padding="10dp"
         android:text="ITEM"
         android:textColor="#626262"
         android:textSize="20sp"/>
 </LinearLayout>

主页布局完成.

2. 上线文菜单布局

添加一个 ListView 来显示多条命令.

context_list.xml
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:background="#dcdcdc"
     android:padding="2dp"
     android:orientation="vertical">
     <ListView
         android:id="@+id/id_listView_context"
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
         android:background="#FFF"
         android:divider="@null"/>
 </LinearLayout>

有了 ListView 自然需要 item 来描述每一项长啥样.

context_item.xml
<?xml version="1.0" encoding="utf-8"?>
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:padding="10dp">
 
     <TextView
         android:id="@+id/id_textView_context_item"
         android:layout_width="0dp"
         android:layout_height="wrap_content"
         android:layout_weight="1"
         android:background="@drawable/bg_msg_item"
         android:padding="10dp"
         android:text="TextView"
         android:textColor="#626262"
         android:textSize="20sp"/>
 </LinearLayout>

上下文菜单的布局也完成了, 下面来为布局添加具体的逻辑.

3. 上下文菜单的逻辑

该类用来显示一个上下文菜单, 外部可以通过

addList(String option, String value)添加菜单项, 两个参数分别为显示的值以及命令的代号.

show(Context context, View anchor, int xOff, int yOff)方法用来显示菜单项, 四个参数分别是当前的上下文, 点击的控件, 在X轴上显示的位置以及在Y轴上显示的位置.

ContextMenu.java
package example.contextmenu2;
 
 import android.content.Context;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.ListView;
 import android.widget.PopupWindow;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 
 public class ContextMenu {
 
     // popupWindow
     View contentView;
     PopupWindow popupWindow;
 
     // listView
     LayoutInflater layoutInflater;
     ListAdapter listAdapter;
     ArrayList listData = new ArrayList<>();
 
     // 回调(点击之后要做的事情)
     public OnMenuItemClickedListener listener;
 
     public ContextMenu()
     {
 
     }
     
     // 添加命令
     public void addList(String option, String value) {
         ListItem it = new ListItem();
         it.option = option;
         it.value = value;
         listData.add(it);
     }
     
     // 显示ContextMenu
     public void show(Context context, View anchor, int xOff, int yOff) {
         layoutInflater = LayoutInflater.from(context);
         contentView = layoutInflater.inflate(R.layout.context_list, null);
 
         // 初始化窗口内容
         initContentView();
 
         // 创建PopupWindow, 宽度恒定, 高度自适应
         popupWindow = new PopupWindow(contentView, 450 , ViewGroup.LayoutParams.WRAP_CONTENT);
         popupWindow.setFocusable(true);       // 设置焦点, 为true是点击外部popUpwindow会消失
         popupWindow.showAsDropDown(anchor, xOff, yOff);
     }
 
     // 初始化窗口
     private void initContentView() {
         listAdapter = new ListAdapter();
         ListView listView = contentView.findViewById(R.id.id_listView_context);
         listView.setAdapter(listAdapter);
         listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
             @Override
             public void onItemClick(AdapterView parent, View view, int position, long id) {
                 ListItem it = (ListItem) listAdapter.getItem(position);
                 onItemClicked(view, it);
             }
         });
     }
 
     // 回调
     public interface OnMenuItemClickedListener
     {
          void onMenuItemClicked(String option, String value);
     }
 
     // 点击后的操作
     private void onItemClicked(View view, ListItem it) {
         // 关闭窗口
         popupWindow.dismiss();
 
         // 回调
         if (listener != null)
         {
             listener.onMenuItemClicked(it.option, it.value);
         }
     }
 
     // item 类
     public static class ListItem {
         public String option;      // 属性名
         public String value;       // 对应命令
     }
 
     // 适配器
     private class ListAdapter extends BaseAdapter {
 
         @Override
         public int getCount() {
             return listData.size();
         }
 
         @Override
         public Object getItem(int position) {
             return listData.get(position);
         }
 
         @Override
         public long getItemId(int position) {
             return position;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
 
             if (convertView == null) {
                 convertView = layoutInflater.inflate(R.layout.context_item, parent, false);
             }
             // 显示数据
             ListItem it = (ListItem) getItem(position);
             ((TextView)convertView.findViewById(R.id.id_textView_context_item)).setText(it.option);
             return convertView;
         }
     }
 }

4.主页面逻辑

注意看注释

MainActivity.java
package example.contextmenu2;
 
 import android.os.Bundle;
 import android.support.v7.app.AppCompatActivity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.AdapterView;
 import android.widget.BaseAdapter;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.ArrayList;
 
 public class MainActivity extends AppCompatActivity {
 
     final String TAG = "测试: ";
 
     MyListAdapter listAdapter;
     ArrayList listData = new ArrayList<>();
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
 
         // 初始化 ListView
         listAdapter = new MyListAdapter();
         ListView listView = (ListView) findViewById(R.id.id_listView_home);
         listView.setAdapter(listAdapter);
 
         listView.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
             @Override
             public boolean onItemLongClick(AdapterView parent, View view, int position, long id) {
                 MyListItem it = (MyListItem) listAdapter.getItem(position);
                 onItemLongClicked(view, it);
                 return true;
             }
         });
 
         // 初始化数据
         demo();
     }
 
     // 长按事件
     private void onItemLongClicked(View view, MyListItem it) {
         ContextMenu menu = new ContextMenu();
         menu.addList("发送给朋友", "sendto");
         menu.addList("收藏", "favorite");
         menu.addList("删除", "remove");
         menu.addList("更多", "more");
         menu.listener = new ContextMenu.OnMenuItemClickedListener(){
             @Override
             public void onMenuItemClicked(String option, String value) {
                 Toast.makeText(MainActivity.this, "点击了: " + option, Toast.LENGTH_SHORT).show();
             }
         };
 
         menu.show(this, view, view.getWidth()/2, -view.getHeight()/2);
     }
 
     // 初始化数据
     private void demo() {
         MyListItem it;
         it = new MyListItem();
         it.value = "生命里,一些缱绻,无论素净,还是喧哗,都已经被岁月赋予了清喜的味道,一些闲词,或清新,或淡雅,总会在某一个回眸的时刻醉了流年,濡湿了柔软的心,冥冥之中,我们沿着呼唤的风声,终于在堆满落花的秋里,再次重逢,念在天涯,心在咫尺,我相信,一米阳光,才是我们最好的距离。";
         listData.add(it);
 
         it = new MyListItem();
         it.value = "缘起是诗,缘离是画,这些关于岁月,关于记忆的章节,终会被时光搁置在无法触及的红尘之外,曾经,你我一别经年,可风里,总有一段美丽会与我们不期而遇,一盏琉璃,半杯心悦,端然着那一份醉人的静,这安静行走的流年,总会被过往赋予一份清喜,一份浪漫。";
         listData.add(it);
 
         it = new MyListItem();
         it.value = "或许,习惯了着布衣素颜,让清心若雪,不喜张扬,不畏喧哗,守着一怀自己的素韵安静,在自己心中的半亩桃源,修篱种菊,喜欢与山水相依,与流水对话,让文字的墨香,依附在心灵的每一个角落,也喜欢,在闲时,端坐时光一隅,将一本书读到无字,将一盏茶喝到无味,将一个故事看到流泪.……心染尘香,不须有多少的柔情话语去讲,只要能够念起,便是一份温暖。";
         listData.add(it);
 
         it = new MyListItem();
         it.value = "再打开记忆的栅栏,取一壶往昔,与流年对坐,情有多深,心就有多疼,触不到的可惜,在挥手袖的风里滋养着忧伤,捻一缕清芬,看三千浮华,历历往事,素淡清雅,研一池墨香,植于眉心,相遇如梦,一直充满虔诚的无暇,这个十月,我用满怀的深情,打开文字的苍白,灵动的心事,穿过岁月的轩窗,迎面而来,我未曾离开,为何不见了原来这世间的繁华与葱笼?是时光的无情,还是流年容易沧桑,让所有的一切都如逝水匆匆,去了遥远的天涯?";
         listData.add(it);
 
         listAdapter.notifyDataSetChanged();
     }
 
     // item 类
     public static class MyListItem {
         public String value;
     }
 
     // 适配器
     public class MyListAdapter extends BaseAdapter {
 
         @Override
         public int getCount() {
             return listData.size();
         }
 
         @Override
         public Object getItem(int position) {
             return listData.get(position);
         }
 
         @Override
         public long getItemId(int position) {
             return position;
         }
 
         @Override
         public View getView(int position, View convertView, ViewGroup parent) {
 
             // 新建视图
             if (convertView == null) {
                 convertView = getLayoutInflater().inflate(R.layout.home_item, parent, false);
             }
 
             MyListItem it = (MyListItem) getItem(position);
 
             ((TextView)convertView.findViewById(R.id.id_textView_home_item)).setText(it.value);
 
             return convertView;
         }
     }
 }
 

到此一个上下文菜单就制作完成了.