Android開發筆記(一百二十一)列表彈窗PopupMenu和ListPopupWindow

湖前琴亭發表於2016-08-29

PopupMenu

基本用法

PopupMenu是種顯示位置不固定的彈出選單,因為它顯示在參照控制元件下方,所以展示位置隨著參照控制元件的位置變化而變化。而其他幾種選單的顯示位置都是固定的,比如說選項選單Options固定顯示在螢幕下方,上下文選單ContextMenu固定顯示在螢幕中央,溢位選單OverflowMenu固定顯示在螢幕右上角,有關其他選單的詳細說明參見《Android開發筆記(六十五)多樣的選單》。
下面是PopupMenu的常用方法說明:
建構函式 : 構造一個PopupMenu物件,並指定該物件的參照控制元件。
inflate : 根據指定的選單資原始檔,把具體的選單專案填充到PopupMenu物件中。
setOnMenuItemClickListener : 設定選單項的點選監聽器。該監聽器由介面OnMenuItemClickListener派生而來,要重寫onMenuItemClick方法來實現選單項點選事件。
show : 顯示彈出選單。
dismiss : 關閉彈出選單。
setOnDismissListener : 設定彈出選單的關閉監聽器。


下面是PopupMenu的使用截圖:



下面是PopupMenu的程式碼示例:
import java.util.Date;

import com.example.exmpopup.util.Utils;

import android.app.Activity;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.TextView;
import android.widget.Toast;

public class PopupMenuActivity extends Activity implements OnClickListener, OnMenuItemClickListener {
	
	private TextView tv_show_menu;
	private PopupMenu mPopupMenu;
	
	private String[] mFormatArray = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd",
			"yyyy年MM月dd日HH時mm分ss秒", "yyyy年MM月dd日"};
	private String mFormat = mFormatArray[0];
	private Date mNowTime = new Date();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_menu);

		tv_show_menu = (TextView) findViewById(R.id.tv_show_menu);
		Button btn_show_menu = (Button) findViewById(R.id.btn_show_menu);
		btn_show_menu.setOnClickListener(this);
		mPopupMenu = new PopupMenu(this, btn_show_menu);
		mPopupMenu.inflate(R.menu.main);
		//mPopupMenu.getMenuInflater().inflate(R.menu.main, mPopupMenu.getMenu());
		mPopupMenu.setOnMenuItemClickListener(this);
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_show_menu) {
	    	mPopupMenu.show();
		}
	}

	@Override
	public boolean onMenuItemClick(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.menu_refresh) {
			mNowTime = new Date();
			tv_show_menu.setText("當前重新整理時間: "+Utils.getFormatDateTime(mNowTime, mFormat));
        } else if (id == R.id.menu_about) {
			Toast.makeText(this, "這個是PopupMenu的演示demo", Toast.LENGTH_LONG).show();
        } else if (id == R.id.menu_quit) {
			finish();
        }
		return true;
	}
    
}


新版PopupMenu

v7-appcompat庫中引入了新版的PopupMenu,基本用法同舊版的PopupMenu。


使用新版PopupMenu時若出現如下的錯誤:java.lang.RuntimeException: Binary XML file line #17: You must supply a layout_height attribute.
則是因為使用v7-appcompat的控制元件,都要在AppCompatActivity中使用(比如Toolbar),並且要在AndroidManifest.xml中設定該Activity的android:theme為Theme.AppCompat.*派生的風格。有關v7-appcompat庫的使用方法參見《Android開發筆記(一百一十九)工具欄Toolbar》。


v7-appcompat庫中提供了新版PopupMenu,當然是做了部分功能完善,最主要的改進便是對子選單的操作更加靈活,即可以由開發者自己定義在何時開啟子選單。下面是新版PopupMenu比舊版增加的呼叫方法說明:
onOpenSubMenu : 顯示子選單。
onCloseSubMenu : 關閉子選單。
onMenuItemSelected : 選擇選單項。
getMenu : 獲得Menu物件。之後便能給Menu物件呼叫addSubMenu方法新增子選單;addSubMenu返回一個SubMenu物件,可呼叫SubMenu的add方法給子選單新增具體專案。


下面是新版PopupMenu的程式碼示例:
import java.util.Date;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.widget.PopupMenu;
import android.support.v7.widget.PopupMenu.OnMenuItemClickListener;
import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import com.example.exmpopup.util.Utils;

public class NewPopupMenuActivity extends AppCompatActivity implements OnClickListener, OnMenuItemClickListener {
	
	private TextView tv_show_menu_new;
	private PopupMenu mPopupMenu;
	
	private String[] mFormatArray = {"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd",
			"yyyy年MM月dd日HH時mm分ss秒", "yyyy年MM月dd日"};
	private String mFormat = mFormatArray[0];
	private Date mNowTime = new Date();
	private SubMenu mSubMenu;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_menu_new);

		tv_show_menu_new = (TextView) findViewById(R.id.tv_show_menu_new);
		Button btn_show_menu_new = (Button) findViewById(R.id.btn_show_menu_new);
		btn_show_menu_new.setOnClickListener(this);
		mPopupMenu = new PopupMenu(this, btn_show_menu_new);
		//mPopupMenu.inflate(R.menu.main); //兩種inflate方式都可以
		mPopupMenu.getMenuInflater().inflate(R.menu.main, mPopupMenu.getMenu());
		mPopupMenu.setOnMenuItemClickListener(this);
		mSubMenu = mPopupMenu.getMenu().addSubMenu(0, 999, 9, "更多");
		mSubMenu.add(0, 111, 0, "ccc");
		mSubMenu.add(0, 112, 1, "cca");
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.btn_show_menu_new) {
	    	mPopupMenu.show();
		}
	}

	@Override
	public boolean onMenuItemClick(MenuItem item) {
        int id = item.getItemId();
        if (id == R.id.menu_refresh) {
			mNowTime = new Date();
			tv_show_menu_new.setText("當前重新整理時間: "+Utils.getFormatDateTime(mNowTime, mFormat));
        } else if (id == R.id.menu_about) {
			Toast.makeText(this, "這個是PopupMenu的演示demo", Toast.LENGTH_SHORT).show();
			//點選“更多”會自動顯示子選單,點選“關於”也會由下面程式碼來顯示子選單
			mPopupMenu.onOpenSubMenu((MenuBuilder) mSubMenu);
        } else if (id == 111) {
			Toast.makeText(this, "您點選了編號為"+id+"的子選單項", Toast.LENGTH_SHORT).show();
        } else if (id == 112) {
			Toast.makeText(this, "您點選了編號為"+id+"的子選單項", Toast.LENGTH_SHORT).show();
        } else if (id == R.id.menu_quit) {
			finish();
        }
		return true;
	}
    
}


ListPopupWindow

基本用法

ListPopupWindow也是在參照控制元件下方顯示列表視窗,不同的是,它在展示上更加靈活,開發者可以自定義列表彈窗的大小與樣式。
下面是ListPopupWindow的常用方法說明:
setAdapter : 設定下拉選單的資料介面卡。
setModal : 設定顯示模式。通常設定為true。
setWidth : 設定下拉選單視窗的寬度。
setHeight : 設定下拉選單視窗的高度。
setAnchorView : 設定下拉選單的參照控制元件。下拉選單在顯示時將展現在參照控制元件的下方,注意:如果不設定參照控制元件就直接呼叫show函式,系統不知道要把下拉選單在何處展示,只能是異常退出了。
setDropDownGravity : 設定下拉選單的對齊方式。Gravity.START表示與參照控制元件左側對齊,Gravity.END表示與參照控制元件右側對齊。注意:該函式只在4.4.2及以上版本中使用。
setOnItemClickListener : 設定列表項的點選監聽器。
show : 顯示下拉選單視窗。
dismiss : 關閉下拉選單視窗。
setOnDismissListener : 設定下拉選單的關閉監聽器。


ListPopupWindow與EditText結合使用,效果上有點類似AutoCompleteTextView,即都會在編輯框下方彈出文字列表可供選擇。但它們之間也有明顯的區別:AutoCompleteTextView一旦設定介面卡,則它會根據編輯框的文字來自動調整文字列表;而ListPopupWindow的文字列表是固定的,不會自動調整。


下面是ListPopupWindow的程式碼示例:
import android.annotation.TargetApi;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
import android.view.Gravity;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup.LayoutParams;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListPopupWindow;

@TargetApi(Build.VERSION_CODES.KITKAT)
public class ListPopupActivity extends Activity implements OnClickListener, OnItemClickListener {

	private EditText et_list;
	private ListPopupWindow mPopup;
	private String[] mGoodArray={"pencil", "potato", "peanut", "carrot", "cabbage", "cat"};

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_list);

		et_list = (EditText) findViewById(R.id.et_list);
		et_list.setOnClickListener(this);
		Button btn_show_list = (Button) findViewById(R.id.btn_show_list);
		btn_show_list.setOnClickListener(this);
		
		mPopup = new ListPopupWindow(this);
		ArrayAdapter adapter = new ArrayAdapter(this, R.layout.spinner_item, mGoodArray);
		mPopup.setAdapter(adapter);
		mPopup.setWidth(LayoutParams.WRAP_CONTENT);
		mPopup.setHeight(LayoutParams.WRAP_CONTENT);
		mPopup.setModal(true);
		mPopup.setOnItemClickListener(this);
	}

	@Override
	public void onClick(View v) {
		if (v.getId() == R.id.et_list) {
			if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.KITKAT) {
				mPopup.setDropDownGravity(Gravity.END);
			}
			mPopup.setAnchorView(v);
			mPopup.show();
		} else if (v.getId() == R.id.btn_show_list) {
			if (Build.VERSION.SDK_INT >=  Build.VERSION_CODES.KITKAT) {
				mPopup.setDropDownGravity(Gravity.START);
			}
			mPopup.setAnchorView(v);
			mPopup.show();
		}
	}

	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
		et_list.setText(mGoodArray[position]);
		et_list.setSelection(et_list.getText().length());
		mPopup.dismiss();
	}
	
}


新舊ListPopupWindow的區別

v7-appcompat庫中也提供了新版ListPopupWindow,據我觀察在功能上沒什麼區別,主要是在彈窗的顯示風格上有所差異,如舊版的ListPopupWindow有明顯的視窗邊框與邊緣陰影,而新版的ListPopupWindow就沒有視窗邊框。


下面是舊版ListPopupWindow的UI,有視窗邊框:



下面是新版ListPopupWindow的UI,沒有視窗邊框:



PopupMenu、ListPopupWindow和Spinner的區別

PopupMenu和ListPopupWindow同樣都是列表彈窗,與spinnerMode="dropdown"的Spinner極其相似,都可以用來做下拉選單的選擇彈窗,不過也有部分差異。下面是博主總結的它們之間的區別:
1、檢視原始碼,會發現PopupMenu和Spinner內部都是使用ListPopupWindow實現下拉選單效果,所以ListPopupWindow是基礎。
2、PopMenu的列表頁面無法定製UI,只能顯示光禿禿的文字;而ListPopupWindow和Spinner可以通過介面卡來設定每項的佈局風格,當然ListPopupWindow是最靈活的,不但可在左側顯示列表,還能在右側顯示列表。
3、PopMenu可通過子選單實現多級選單效果,而ListPopupWindow和Spinner只有一級列表。
4、ListPopupWindow和Spinner可以設定預設選中項,而PopMenu沒有預設選中項。
5、Spinner既可以下拉選單來展示,也可以對話方塊來展示;而PopupMenu和ListPopupWindow只能以下拉選單展示。



點選下載本文用到的列表彈窗的工程程式碼



點此檢視Android開發筆記的完整目錄

相關文章