前言
設(shè)計(jì)圖給出的效果
最終的效果
最終代碼
common_tv_right_more 的點(diǎn)擊事件
if (view.getId() == R.id.common_tv_right_more) {
new PopTop.Builder(this)
.setView(findViewById(R.id.common_tv_right_more)) // 在某個(gè)空間的下面
.setPopTopOnClick(new PopTop.PopTopOnClick() {
@Override
public void EditOnclick() {
/* 編輯 監(jiān)聽*/
}
@Override
public void DelOnclick() {
/*刪除 監(jiān)聽*/
}
})
.show();
}
接著開始想和學(xué)習(xí)怎么做了。
使用場景
PopupWindow屏鳍,顧名思義悯森,就是彈窗盅蝗,在很多場景下都可以見到它议薪。例如ActionBar/Toolbar的選項(xiàng)彈窗尤蛮,一組選項(xiàng)的容器,或者列表等集合的窗口等等斯议。
基本使用
使用PopupWindow很簡單,可以總結(jié)為三個(gè)步驟:
1醇锚、創(chuàng)建PopupWindow對象實(shí)例哼御;
2、設(shè)置背景焊唬、注冊事件監(jiān)聽器和添加動畫恋昼;
3、顯示PopupWindow赶促。
// 用于PopupWindow的View
View contentView=LayoutInflater.from(context).inflate(layoutRes, null, false);
// 創(chuàng)建PopupWindow對象液肌,其中:
// 第一個(gè)參數(shù)是用于PopupWindow中的View,第二個(gè)參數(shù)是PopupWindow的寬度鸥滨,
// 第三個(gè)參數(shù)是PopupWindow的高度嗦哆,第四個(gè)參數(shù)指定PopupWindow能否獲得焦點(diǎn)
PopupWindow window=new PopupWindow(contentView, 100, 100, true);
// 設(shè)置PopupWindow的背景
window.setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
// 設(shè)置PopupWindow是否能響應(yīng)外部點(diǎn)擊事件
window.setOutsideTouchable(true);
// 設(shè)置PopupWindow是否能響應(yīng)點(diǎn)擊事件
window.setTouchable(true);
// 顯示PopupWindow谤祖,其中:
// 第一個(gè)參數(shù)是PopupWindow的錨點(diǎn),第二和第三個(gè)參數(shù)分別是PopupWindow相對錨點(diǎn)的x老速、y偏移
window.showAsDropDown(anchor, xoff, yoff);
// 或者也可以調(diào)用此方法顯示PopupWindow粥喜,其中:
// 第一個(gè)參數(shù)是PopupWindow的父View,第二個(gè)參數(shù)是PopupWindow相對父View的位置橘券,
// 第三和第四個(gè)參數(shù)分別是PopupWindow相對父View的x额湘、y偏移
// window.showAtLocation(parent, gravity, x, y);
使用showAsDropDown方法顯示PopupWindow
通常情況下,調(diào)用showAsDropDown方法后PopupWindow將會在錨點(diǎn)的左下方顯示(drop down)旁舰。但是锋华,有時(shí)想讓PopupWindow在錨點(diǎn)的上方顯示,或者在錨點(diǎn)的中間位置顯示箭窜,此時(shí)就需要用到showAsDropDown方法的xoff和yoff參數(shù)了毯焕。
這里我們的目的不僅包括上面提到的兩種情況(錨點(diǎn)上方或錨點(diǎn)中部),而是囊括了水平和垂直方向各5種顯示方式:
- 水平方向:
- ALIGN_LEFT:在錨點(diǎn)內(nèi)部的左邊绽快;
- ALIGN_RIGHT:在錨點(diǎn)內(nèi)部的右邊芥丧;
- CENTER_HORI:在錨點(diǎn)水平中部;
- TO_RIGHT:在錨點(diǎn)外部的右邊坊罢;
- TO_LEFT:在錨點(diǎn)外部的左邊续担。
- 垂直方向:
- ALIGN_ABOVE:在錨點(diǎn)內(nèi)部的上方;
- ALIGN_BOTTOM:在錨點(diǎn)內(nèi)部的下方活孩;
- CENTER_VERT:在錨點(diǎn)垂直中部物遇;
- TO_BOTTOM:在錨點(diǎn)外部的下方;
- TO_ABOVE:在錨點(diǎn)外部的上方憾儒。
下面來看張圖:
showAsDropDown 可以做哪些效果
想要的效果 是在某個(gè)控件的下面 并且還要有偏移 角度询兴,所以在偏移角度上
大體是多少再去調(diào)試。
所以在主要的方法落在了showAsDropDown 的下面起趾。 想要寫成通用的也不是不可以的诗舰,先不要想這么多,先去做训裆,實(shí)現(xiàn)目前的效果再說眶根。
由上面的基本方法中我可以看到
View contentView=LayoutInflater.from(context).inflate(layoutRes, null, false);
這個(gè)view 的產(chǎn)出,所以就可以把它單獨(dú)拿起來边琉,在單獨(dú)頁面中和xml 處理属百,底色背景和角度了,
以及整個(gè)view 中的點(diǎn)擊事件處理变姨,并且顯得不那么啰嗦了族扰,看簡單的代碼,就用到build設(shè)計(jì)模式,
突出了這個(gè)模式的優(yōu)點(diǎn)渔呵,不斷添加 各個(gè)設(shè)置條件怒竿。
自己之前也寫過關(guān)于設(shè)計(jì)模式的問題。
首先看是build 的時(shí)候創(chuàng)建view 或者說產(chǎn)出view 為了提供顯示用
public PopTop(Builder builder) {
//窗口布局
super(builder.context);
this.builder = builder;
Create();
}
public void Create() {
//窗口布局
setContentView(mainView = LayoutInflater.from(builder.context).inflate(R.layout.pop_top, null));
//設(shè)置寬度
setWidth(dip2px(builder.context, 100));
//設(shè)置高度
setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
setTouchable(true);
setFocusable(true);
//設(shè)置顯示隱藏動畫
// setAnimationStyle(R.style.AnimTools);
//設(shè)置背景透明
setBackgroundDrawable(new ColorDrawable());
/* //監(jiān)聽窗口的焦點(diǎn)事件厘肮,點(diǎn)擊窗口外面則取消顯示*/
getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
dismiss();
}
}
});
//初始化 控件點(diǎn)擊事件處理
initView(mainView);
}
再看build 做了哪些處理
public static class Builder {
protected final Context context;
protected View view;
PopTopOnClick popTopOnClick;
public Builder setPopTopOnClick(PopTopOnClick popTopOnClick) {
this.popTopOnClick = popTopOnClick;
return this;
}
public Builder setView(View view) {
this.view = view;
return this;
}
public Builder(@NonNull Context context) {
this.context = context;
}
@UiThread
public PopupWindow build() {
return new PopTop(this);
}
@UiThread
public PopupWindow show() {
PopupWindow popTop = build();
int windowPos[] = calculatePopWindowPos(view);
windowPos[0] -= 15; //x 軸向左偏移15像素
windowPos[1] -= 10; //y 軸向上偏移10像素
popTop.showAtLocation(view, Gravity.TOP | Gravity.START, windowPos[0], windowPos[1]);
return popTop;
}
}
主要在show 中
對偏移量的處理愧口,并且在合適的位置處理,在上面基本中說道类茂。
/**
* 計(jì)算出來的位置耍属,y方向就在anchorView的上面和下面對齊顯示,x方向就是與屏幕右邊對齊顯示
* 如果anchorView的位置有變化巩检,就可以適當(dāng)自己額外加入偏移來修正
*
* @param anchorView 呼出window的view
* @return window顯示的左上角的xOff, yOff坐標(biāo)
*/
public static int[] calculatePopWindowPos(final View anchorView) {
final int windowPos[] = new int[2];
final int anchorLoc[] = new int[2];
// 獲取錨點(diǎn)View在屏幕上的左上角坐標(biāo)位置
anchorView.getLocationOnScreen(anchorLoc);
final int anchorHeight = anchorView.getHeight();
// 獲取屏幕的高寬
final int screenHeight = getScreenHeight(anchorView.getContext());
final int screenWidth = getScreenWidth(anchorView.getContext());
mainView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
// 計(jì)算contentView的高寬
final int windowHeight = mainView.getMeasuredHeight();
final int windowWidth = mainView.getMeasuredWidth();
// 判斷需要向上彈出還是向下彈出顯示
final boolean isNeedShowUp = (screenHeight - anchorLoc[1] - anchorHeight < windowHeight);
if (isNeedShowUp) {
windowPos[0] = screenWidth - windowWidth;
windowPos[1] = anchorLoc[1] - windowHeight;
} else {
windowPos[0] = screenWidth - windowWidth;
windowPos[1] = anchorLoc[1] + anchorHeight;
}
return windowPos;
}
大體流程就是這些了厚骗,至于點(diǎn)擊事件,進(jìn)行事件監(jiān)聽兢哭,也是從產(chǎn)生的pop的時(shí)候傳遞過來的领舰。
所有代碼
import android.content.Context;
import android.graphics.drawable.ColorDrawable;
import android.support.annotation.NonNull;
import android.support.annotation.UiThread;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import android.widget.TextView;
public class PopTop extends PopupWindow implements View.OnClickListener {
public static View mainView;
protected final Builder builder;
protected TextView popTvCustomerEdit;
protected TextView popTvCustomerDel;
protected LinearLayout popLlEdit;
protected TextView popTvCustomerSys;
protected TextView popTvCustomerCreate;
protected LinearLayout popLlCustomer;
public PopTop(Builder builder) {
//窗口布局
super(builder.context);
this.builder = builder;
Create();
}
public void Create() {
//窗口布局
setContentView(mainView = LayoutInflater.from(builder.context).inflate(R.layout.pop_top, null));
//設(shè)置寬度
setWidth(dip2px(builder.context, 100));
//設(shè)置高度
setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
setTouchable(true);
setFocusable(true);
//設(shè)置顯示隱藏動畫
// setAnimationStyle(R.style.AnimTools);
//設(shè)置背景透明
setBackgroundDrawable(new ColorDrawable());
/* //監(jiān)聽窗口的焦點(diǎn)事件,點(diǎn)擊窗口外面則取消顯示*/
getContentView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus) {
dismiss();
}
}
});
initView(mainView);
}
@Override
public void onClick(View view) {
dismiss();
if (view.getId() == R.id.pop_tv_customer_edit) {
if(builder.popTopOnClick != null){
builder.popTopOnClick.EditOnclick();
}
} else if (view.getId() == R.id.pop_tv_customer_del) {
if(builder.popTopOnClick != null){
builder.popTopOnClick.hashCode();
}
} else if (view.getId() == R.id.pop_tv_customer_sys) {
if(builder.popTopOnClick != null){
builder.popTopOnClick.SysOnclick();
}
} else if (view.getId() == R.id.pop_tv_customer_create) {
if(builder.popTopOnClick != null){
builder.popTopOnClick.CreateOnclick();
}
}
}
private void initView(View rootView) {
popTvCustomerEdit = (TextView) rootView.findViewById(R.id.pop_tv_customer_edit);
popTvCustomerEdit.setOnClickListener(PopTop.this);
popTvCustomerDel = (TextView) rootView.findViewById(R.id.pop_tv_customer_del);
popTvCustomerDel.setOnClickListener(PopTop.this);
popLlEdit = (LinearLayout) rootView.findViewById(R.id.pop_ll_edit);
popTvCustomerSys = (TextView) rootView.findViewById(R.id.pop_tv_customer_sys);
popTvCustomerSys.setOnClickListener(PopTop.this);
popTvCustomerCreate = (TextView) rootView.findViewById(R.id.pop_tv_customer_create);
popTvCustomerCreate.setOnClickListener(PopTop.this);
popLlCustomer = (LinearLayout) rootView.findViewById(R.id.pop_ll_customer);
popLlEdit.setVisibility(View.GONE);
popLlCustomer.setVisibility(View.GONE);
}
public static class Builder {
protected final Context context;
protected View view;
PopTopOnClick popTopOnClick;
public Builder setPopTopOnClick(PopTopOnClick popTopOnClick) {
this.popTopOnClick = popTopOnClick;
return this;
}
public Builder setView(View view) {
this.view = view;
return this;
}
public Builder(@NonNull Context context) {
this.context = context;
}
@UiThread
public PopupWindow build() {
return new PopTop(this);
}
@UiThread
public PopupWindow show() {
PopupWindow popTop = build();
int windowPos[] = calculatePopWindowPos(view);
windowPos[0] -= 15; //x 軸向左偏移15像素
windowPos[1] -= 10; //y 軸向上偏移10像素
popTop.showAtLocation(view, Gravity.TOP | Gravity.START, windowPos[0], windowPos[1]);
return popTop;
}
}
public int dip2px(Context context, float dipValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * scale + 0.5f);
}
/**
* 計(jì)算出來的位置迟螺,y方向就在anchorView的上面和下面對齊顯示冲秽,x方向就是與屏幕右邊對齊顯示
* 如果anchorView的位置有變化,就可以適當(dāng)自己額外加入偏移來修正
*
* @param anchorView 呼出window的view
* @return window顯示的左上角的xOff, yOff坐標(biāo)
*/
public static int[] calculatePopWindowPos(final View anchorView) {
final int windowPos[] = new int[2];
final int anchorLoc[] = new int[2];
// 獲取錨點(diǎn)View在屏幕上的左上角坐標(biāo)位置
anchorView.getLocationOnScreen(anchorLoc);
final int anchorHeight = anchorView.getHeight();
// 獲取屏幕的高寬
final int screenHeight = getScreenHeight(anchorView.getContext());
final int screenWidth = getScreenWidth(anchorView.getContext());
mainView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
// 計(jì)算contentView的高寬
final int windowHeight = mainView.getMeasuredHeight();
final int windowWidth = mainView.getMeasuredWidth();
// 判斷需要向上彈出還是向下彈出顯示
final boolean isNeedShowUp = (screenHeight - anchorLoc[1] - anchorHeight < windowHeight);
if (isNeedShowUp) {
windowPos[0] = screenWidth - windowWidth;
windowPos[1] = anchorLoc[1] - windowHeight;
} else {
windowPos[0] = screenWidth - windowWidth;
windowPos[1] = anchorLoc[1] + anchorHeight;
}
return windowPos;
}
/**
* 獲取屏幕高度(px)
*/
public static int getScreenHeight(Context context) {
return context.getResources().getDisplayMetrics().heightPixels;
}
/**
* 獲取屏幕寬度(px)
*/
public static int getScreenWidth(Context context) {
return context.getResources().getDisplayMetrics().widthPixels;
}
public interface PopTopOnClick{
void EditOnclick();
void DelOnclick();
void SysOnclick();
void CreateOnclick();
}
}
最終你可以
if (view.getId() == R.id.common_tv_right_more) {
new PopTop.Builder(this)
.setView(findViewById(R.id.common_tv_right_more)) // 在某個(gè)空間的下面
.setPopTopOnClick(new PopTop.PopTopOnClick() {
@Override
public void EditOnclick() {
/* 編輯 監(jiān)聽*/
}
@Override
public void DelOnclick() {
/*刪除 監(jiān)聽*/
}
})
.show();
}
最后 同類想法
你可以寫一個(gè)評論的pop
例如
點(diǎn)擊彈出鍵盤 pop 顯示處理矩父,鍵盤消失pop 消失锉桑。
這個(gè)之前也有個(gè)項(xiàng)目里面有,但是沒有這么寫窍株,就堆在一起民轴,也沒有反思,那個(gè)時(shí)候還沒有blog球订,現(xiàn)在想一想真的太像了后裸。