情境
在之前如何自訂Dialog之二-客製化Menu這篇文章
是透過自己做的假Toolbar
在上面蓋一層Dialog, 模擬出假Menu的效果
那如果在真的Toolbar想要客製化自己的Menu Popupwindow該怎麼做呢?
程式碼說明
首先我們建立一個新專案
接著在MainActivity內建立一個Toolbar
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context="com.example.givemepass.custommenudemo.MainActivity">
<android.support.v7.widget.Toolbar
android:background="?attr/colorPrimary"
android:id="@+id/toolbar"
app:titleTextAppearance="@style/toolbar_style"
android:minHeight="?actionBarSize"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
</android.support.v7.widget.Toolbar>
</RelativeLayout>
而這個Toolbar讓它呈現白色的字樣, 因此我們定義一個Theme
<style name="toolbar_style" >
<item name="android:textColor">@android:color/white</item>
</style>
接著在menufest內的AppTheme進行一些修改
將原本的ActionBar改成NoActionBar
避免之後再將ToolBar塞進ActionBar的時候會閃退
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
所以當我們的程式碼將Toolbar設定為ActionBar
private Toolbar mToolbar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
}
就會出現這樣的畫面
接著讓我們產生一個menu的按鈕,
首先要先產生一個icon是垂直的點點點,
可以透過之前如何透過Android Studio產生向量圖來產生一個向量圖,
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>
當產生好了就新增一個menu檔案在menu資料夾內。
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
app:showAsAction="always"
android:title="menu"
android:icon="@drawable/ic_more_vert_black_24dp"
android:id="@+id/menu_more"
/>
</menu>
如果要在Activity內產生Menu必須覆寫onCreateOptionsMenu方法,
而不是透過Toolbar去inflateMenu, 這點可能要注意一下。
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.toolbar_menu, menu);
return true;
}
這樣就可以看到我們的Menu被產生出來了。
接下來我們要做出自訂的Menu popupwindow,
可以先參考
如何自訂Dialog之二-客製化Menu內的Dialog部分,
生成一個自訂的Dialog。
首先先來宣告一個Dialog,
public class OptionDialog extends Dialog{
public OptionDialog(Context context) {
super(context);
getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.option_dialog);
}
}
這邊將Dialog設定為背景透明且沒有title的Dialog,
然後在MainAcitivty內設定Menu按下去的事件。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mToolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(mToolbar);
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
new OptionDialog(MainActivity.this).show();
return false;
}
});
}
記住這邊一定要寫在setSupportActionBar(mToolbar)之下,
否則事件會無效。
而我們的Dialog背景目前沒有任何東西
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
</RelativeLayout>
所以按下去只會出現暗暗的畫面。
如果不想讓畫面看起來暗暗的, 那麼就改變一下Dialog的Theme。
public class OptionDialog extends Dialog{
public OptionDialog(Context context) {
super(context, R.style.AppTheme);
getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.option_dialog);
}
}
讓它使用我們一開始定義好的AppTheme。
就算點了感覺也沒甚麼效果, 沒關係,
我們加一個icon就可以看到有東西呈現,
一樣使用Vector產生一個箭頭向上的小三角形。
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#ffffff"
android:pathData="M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
</vector>
在Dialog上面放一個ImageView, 並且指派src為這張向量圖。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_arrow_drop_up_black_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
讓這個小三角形位置為右上, 因此指派它在最外圈Parent的右邊跟上面,
當我們點Menu按鈕的時候, 就可以看到這個小三角形出現了。
不過位置好像怪怪的,
我們希望它出現在ToolBar的點點點下方,
所以調整一下位置。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:layout_marginRight="13dp"
android:layout_marginTop="35dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_arrow_drop_up_black_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
透過margin來調整一下位置,
- 注意:這邊會根據解析度大小不同, 而做不一樣的dp調整, 你應該根據各種解析度去調整dimens,
找到對應的dp。
接著我們建構下面那一塊List。
建立一塊Layout讓它黏在箭頭的下方,
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/arrow"
android:layout_marginRight="13dp"
android:layout_marginTop="35dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:src="@drawable/ic_arrow_drop_up_black_24dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_marginRight="13dp"
android:layout_marginTop="49dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_below="@id/arrow"
android:background="#000000"
android:layout_width="80dp"
android:layout_height="80dp">
</LinearLayout>
</RelativeLayout>
- 注意:同樣的不同解析度, 所取的margin就要根據解析度做不同的dimens的調整。
這個畫面調整出來就會長這樣。
不過正方形可以再調整一下做成圓角會更好看,
所以我們在drawable內新增了一個圓角的xml。
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle" >
<corners android:radius="5dp" />
<solid android:color="#49494b" />
</shape>
看起來好多了,
接著就是要加入List的內容了。
首先加入兩個TextView, 讓外面的LinearLayout設定權重,
接著將字的顏色設定為白色, 畢竟我們的背景是黑色的。
<LinearLayout
android:layout_marginRight="13dp"
android:layout_marginTop="49dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_below="@id/arrow"
android:background="@drawable/coner"
android:layout_width="80dp"
android:layout_height="80dp"
android:weightSum="2"
android:orientation="vertical">
<TextView
android:gravity="center"
android:text="Row 1"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<TextView
android:gravity="center"
android:text="Row 2"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
越來越有Menu的樣子了。
加一條分隔線好了。
<LinearLayout
android:layout_marginRight="13dp"
android:layout_marginTop="49dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_below="@id/arrow"
android:background="@drawable/coner"
android:layout_width="80dp"
android:layout_height="80dp"
android:weightSum="2"
android:orientation="vertical">
<TextView
android:gravity="center"
android:text="Row 1"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_gravity="center"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="1px" />
<TextView
android:gravity="center"
android:text="Row 2"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
你會發現當我們點擊menu以外的地方怎麼會沒有反應呢?
因為我們的Dialog事件並沒有寫這個處理,
所以我們給最外圍的Layout一個名稱並且增加關閉Dialog的事件給它。
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:id="@+id/dialog_outside"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--menu內的內容 -->
</RelativeLayout>
接著在ToolBar的setOnMenuItemClickListener內改寫一些東西。
mToolbar.setOnMenuItemClickListener(new Toolbar.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
final OptionDialog mOptionDialog = new OptionDialog(MainActivity.this);
mOptionDialog.findViewById(R.id.dialog_outside).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
mOptionDialog.dismiss();
}
});
mOptionDialog.show();
return false;
}
});
這時候你就會發現點擊menu外部以及menu內部都會讓menu消失,
為什麼會這樣?
因為我們還未對menu內的每一個item給予事件,
因此我們可以在Dialog內部寫入事件看看。
<LinearLayout
android:layout_marginRight="13dp"
android:layout_marginTop="49dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_below="@id/arrow"
android:background="@drawable/coner"
android:layout_width="80dp"
android:layout_height="80dp"
android:weightSum="2"
android:orientation="vertical">
<TextView
android:id="@+id/item1"
android:gravity="center"
android:text="Row 1"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_gravity="center"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="1px" />
<TextView
android:id="@+id/item2"
android:gravity="center"
android:text="Row 2"
android:layout_weight="1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
各自給TextView一個id, 分別是item1, item2,
接著在Dialog內加入事件。
public class OptionDialog extends Dialog{
public OptionDialog(Context context) {
super(context, R.style.AppTheme);
getWindow().setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT));
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.option_dialog);
findViewById(R.id.item1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
findViewById(R.id.item2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
}
});
}
}
這時候你就再執行一次就會發現,
點擊每個item都不會讓Dialog消失了。
現在又出現一個問題了,
當我們點擊item的時候, 沒有一些特效, 沒有點下去的感覺,
這時候可以參考如何使用第三方達成Ripple的效果這篇文章,
來製造Ripple的效果,
首先先把lib import進來。
compile 'com.balysv:material-ripple:1.0.2'
然後把原本依附在TextView上面的屬性全部掛到第三方上面。
<LinearLayout
android:layout_marginRight="13dp"
android:layout_marginTop="49dp"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_below="@id/arrow"
android:background="@drawable/coner"
android:layout_width="80dp"
android:layout_height="80dp"
android:weightSum="2"
android:orientation="vertical">
<com.balysv.materialripple.MaterialRippleLayout
android:layout_weight="1"
app:mrl_rippleColor="@android:color/darker_gray"
android:id="@+id/item1"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:text="Row 1"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.balysv.materialripple.MaterialRippleLayout>
<View
android:layout_marginRight="5dp"
android:layout_marginLeft="5dp"
android:layout_gravity="center"
android:background="@android:color/white"
android:layout_width="match_parent"
android:layout_height="1px" />
<com.balysv.materialripple.MaterialRippleLayout
android:layout_weight="1"
app:mrl_rippleColor="@android:color/darker_gray"
android:id="@+id/item2"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:gravity="center"
android:text="Row 2"
android:textColor="@android:color/white"
android:textSize="16sp"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.balysv.materialripple.MaterialRippleLayout>
</LinearLayout>
如此一來menu就看起來很完整了。
等等, 那Activity那邊要怎麼知道我按下了哪一個,
其實這個很簡單, 只要寫好Listener就好,
對於Listener不熟悉的可以參考
如何寫一個Listener之一
如何寫一個Listener之二
一開始先宣告兩個變數, 分別是ITEM1跟ITEM2,
這邊是要通知哪一個item被按下去了。
public static final int ITEM1 = 0;
public static final int ITEM2 = 1;
接著就可以寫入我們宣告的Listener
private OnItemClickListener mOnItemClickListener;
public interface OnItemClickListener{
void onItemClick(int pos);
}
public void setOnItemClickListener(OnItemClickListener listener){
mOnItemClickListener = listener;
}
接著在事件內進行判斷。
findViewById(R.id.item1).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mOnItemClickListener != null){
mOnItemClickListener.onItemClick(ITEM1);
}
}
});
findViewById(R.id.item2).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mOnItemClickListener != null){
mOnItemClickListener.onItemClick(ITEM2);
}
}
});
如果item被按下去, 就通知已經註冊的Listener回傳對應的位置,
回到我們的MainActivity內接收callback。
mOptionDialog.setOnItemClickListener(new OptionDialog.OnItemClickListener() {
@Override
public void onItemClick(int pos) {
Toast.makeText(MainActivity.this, "item " + (pos + 1) + " 被按下。", Toast.LENGTH_SHORT).show();
}
});
pos是從0開始, 因此要+1。
按下item之後沒有把dialog關閉, 補一下。
mOptionDialog.setOnItemClickListener(new OptionDialog.OnItemClickListener() {
@Override
public void onItemClick(int pos) {
Toast.makeText(MainActivity.this, "item " + (pos + 1) + " 被按下。", Toast.LENGTH_SHORT).show();
mOptionDialog.dismiss();
}
});
到目前為止, 我們客製化的Dialog menu就算完成了。