1. 使用方式:
- 自己寫一個子類繼承 在子類內(nèi)部去調(diào)用setContentView(view)以及對view 的初始化
- 直接new 一個 MvpCommonPopView 實例,調(diào)用 setContentView(view)猜憎;。
- 在調(diào)用者里面對 view 進(jìn)行初始, 然后調(diào)用 showCenter(view)方法 進(jìn)行顯示,把pop 顯示在屏幕中間,view 參數(shù)可以是任意一個 view .
2. 該pop 可以顯示 一下幾個功能:<p/>
>- 可以設(shè)置pop 外部區(qū)域點(diǎn)擊時是否關(guān)閉pop :{@link #setTouchOutsideDismiss(boolean)}<p/> // 默認(rèn)不關(guān)閉
> - 可以設(shè)置按返回鍵是否可以關(guān)閉pop : {@link #setOnBackKeyDismiss(boolean)}<p/> // 默認(rèn)是按返回鍵關(guān)閉
> - 可是設(shè)置點(diǎn)擊pop 外部時事件是否傳遞到 pop 的后面 : {@link #setOutsideCanTouch(boolean)}<p/> // 默認(rèn)不傳遞
在調(diào)用者里面對 view 進(jìn)行初始, 然后調(diào)用 showCenter(view)方法 進(jìn)行顯示慎王,
package com.example.jiandao;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Gravity;
import android.widget.Toast;
import com.example.guidepageslib.guidepages.GuidePage;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GuidePage guidePage = new GuidePage(this);
guidePage.setGuidePage(new GuidePage.IGuidePage() {
@Override
public void onAgree() {
Toast.makeText(MainActivity.this, "同意", Toast.LENGTH_SHORT).show();
}
@Override
public void onRefuse() {
//關(guān)閉應(yīng)用
finish();
}
});
//獲取頂級視圖 無法添加窗口-令牌null無效;您的活動正在進(jìn)行嗎?所以使用mHandler
getWindow().getDecorView().post(new Runnable() {
@Override
public void run() {
//顯示pop
guidePage.showCenter(getWindow().getDecorView());
}
});
}
}
自己寫一個子類繼MvpCommonPopView
package com.example.guidepageslib.guidepages;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import com.example.guidepageslib.R;
import com.example.guidepageslib.util.MvpCommonPopView;
public class GuidePage extends MvpCommonPopView {
private TextView agree;
private TextView refuse;
private IGuidePage GuidePage;
public GuidePage(Context context) {
super(context);
initView(context);
}
//調(diào)用此方法 可以實現(xiàn) 同意可拒絕的2個方法
public void setGuidePage(IGuidePage guidePage) {
GuidePage = guidePage;
}
private void initView(Context context) {
//創(chuàng)建布局
View view = LayoutInflater.from(context).inflate(R.layout.layout_splash_pop, null);
//同意的控件
agree = (TextView)view.findViewById(R.id.splash_pop_btn_agree);
//拒絕的控件
refuse = (TextView)view.findViewById(R.id.splash_pop_btn_stop);
//同意的監(jiān)聽
agree.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//實現(xiàn)同意的方法
GuidePage.onAgree();
}
});
//拒絕的監(jiān)聽
refuse.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//實現(xiàn)拒絕的方法
GuidePage.onRefuse();
}
});
//在非觸摸屏設(shè)備中接收事件和處理響應(yīng)的控件是具有焦點(diǎn)(Focused)的控件张抄。
// 一個窗口中一個時間內(nèi)只能有一個具有焦點(diǎn)的控件
refuse.setFocusable(false);
agree.setFocusable(false);
setContentView(view);
setOnBackKeyDismiss(false);
}
/**
* public 此接口必須公開。無法從外部包訪問
* onAgree() 同意
* onRefuse() 拒絕
*/
public interface IGuidePage{
void onAgree();
void onRefuse();
}
}
PopupWindow工具類 - MvpCommonPopView
package com.example.guidepageslib.util;
import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.PopupWindow;
import com.example.mvplib.utils.MvpUtils;
/**
* popupwindow 底層實際上是通過 windowmanager 添加 一個 叫 DecorView(FrameLayout)的方式實現(xiàn)的洼怔。
* <p>
* popupwindow 一般設(shè)計到三個 view,{1.mDecorView(FrameLayout),2.mBackgroundView(FrameLayout),3.mContentView(我們要顯示的layout)}
* <p>
* 1 mDecorView: popupWindow 的根view署惯,它的大小就是通過 設(shè)置popouWindow 的 寬高來決定的(setHeight,setWidth)
* <p>
* 2 mBackgroundView:它不一定會創(chuàng)建镣隶,取決于 是否調(diào)用了 setBackgroundDrawable 給 popupWindow 設(shè)置背景极谊,如果設(shè)置了那么會創(chuàng)建一個 mBackgroundView
* 然后添加到mDecorView 上,如下:
* <p>
* if (mBackground != null) { // 是否設(shè)置里背景
* mBackgroundView = createBackgroundView(mContentView);
* mBackgroundView.setBackground(mBackground);
* } else {
* mBackgroundView = mContentView;
* }
* <p>
* //把我們的 mContentView 的傳進(jìn)來 創(chuàng)建 mBackgroundView
* <p>
* private PopupBackgroundView createBackgroundView(View contentView) {
* final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
* final int height;
* if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
* height = WRAP_CONTENT;
* } else {
* height = MATCH_PARENT;
* }
* <p>
* final PopupBackgroundView backgroundView = new PopupBackgroundView(mContext);
* final PopupBackgroundView.LayoutParams listParams = new PopupBackgroundView.LayoutParams(
* MATCH_PARENT, height);
* <p>
* // 把我們的 mContentView 添加到 mBackgroundView上面,
* // mContentView的寬度是 MATCH_PARENT(和mBackgroundView的寬度一致)安岂。
* // mContentView的高度 取決于 mContentView.getLayoutParams() 轻猖,要么是MATCH_PARENT,要么是 WRAP_CONTENT
* backgroundView.addView(contentView, listParams);
* <p>
* return backgroundView;
* }
* <p>
* mDecorView = createDecorView(mBackgroundView);
* <p>
* // 通過把mBackgroundView 傳進(jìn)來 創(chuàng)建 mDecorView域那,
* private PopupDecorView createDecorView(View contentView) {
* final ViewGroup.LayoutParams layoutParams = mContentView.getLayoutParams();
* final int height;
* <p>
* // 如果 mContentView 的 layoutParams 咙边!= null && layoutParams.height == WRAP_CONTENT 時,高度為 wrap_content,否則為 match_parent
* if (layoutParams != null && layoutParams.height == WRAP_CONTENT) {
* height = WRAP_CONTENT;
* } else {
* height = MATCH_PARENT;
* }
* <p>
* final PopupDecorView decorView = new PopupDecorView(mContext);
* // 把 mBackgroundView添加到了 mDecorView上
* // mBackgroundView 的寬度是 MATCH_PARENT(和 mDecorView 一樣寬次员,也就是說等于 setWidth的值)
* <p>
* //mBackgroundView 的高度 取決于mBackgroundView.getLayoutParams(),要么是MATCH_PARENT败许,要么是 WRAP_CONTENT
* <p>
* decorView.addView(contentView, MATCH_PARENT, height);
* decorView.setClipChildren(false);
* decorView.setClipToPadding(false);
* <p>
* return decorView;
* }
* <p>
* <p>
* <p>
* <p>
* 3 mContentView,這就是我們調(diào)用popupwindow 的 setContentView 方法傳進(jìn)去我們要顯示的內(nèi)容view
* <p>
* <p>
* 如果設(shè)置了pop 的backgrounddrawable 那么創(chuàng)建順序是
* <p>
* 根據(jù) contentview 創(chuàng)建 backgroundview,再根據(jù)backgroundview 創(chuàng)建 decorview淑蔚,最后 windowmanager add decorview
* <p>
* <p>
* 如果沒有設(shè)置pop 的backgrounddrawable的背景檐束,那么創(chuàng)建順序是
* <p>
* 直接把 contentview 賦值給 backgroundview。 更具 backgroundview 創(chuàng)建 decorview 束倍,最后 windowmanager add decorview
* <p>
* <p>
* <p>
* decorview 的寬高是由 setHeight 和 setWidth 決定的被丧。
* <p>
* backgroundview 和 contentview 的 寬度都是不能修改的,都為 match_parent,高度要么是 wrap_content ,要么是 match_parent绪妹,只有當(dāng)
* <p>
* contentview 的 layoutparams 不為 null甥桂, 并且其 layoutparams.height == wrap_content 時 ,高度為wrap_content 否則其他情況都為match_parent
*/
/**
*
* 使用方式:
* 1. 自己寫一個子類繼承 在子類內(nèi)部去調(diào)用setContentView(view)以及對view 的初始化
* 2. 直接new 一個 MvpCommonPopView 實例邮旷,調(diào)用 setContentView(view)黄选;。在調(diào)用者里面對 view 進(jìn)行初始化
*
* 然后調(diào)用 showCenter(view)方法 進(jìn)行顯示,把pop 顯示在屏幕中間办陷,view 參數(shù)可以是任意一個 view .
*
* 該pop 可以顯示 一下幾個功能:<p/>
* *
* 1. 可以設(shè)置pop 外部區(qū)域點(diǎn)擊時是否關(guān)閉pop :{@link #setTouchOutsideDismiss(boolean)}<p/> // 默認(rèn)不關(guān)閉
* 2. 可以設(shè)置按返回鍵是否可以關(guān)閉pop : {@link #setOnBackKeyDismiss(boolean)}<p/> // 默認(rèn)是按返回鍵關(guān)閉
* 3. 可是設(shè)置點(diǎn)擊pop 外部時事件是否傳遞到 pop 的后面 : {@link #setOutsideCanTouch(boolean)}<p/> // 默認(rèn)不傳遞
*
*/
public class MvpCommonPopView extends PopupWindow {
private boolean isOutsideTouchDismiss; // 點(diǎn)擊pop 外部是否消失
private boolean isBackKeyDismiss = true;// 按返回鍵是否消失
private Activity context;
public MvpCommonPopView(Context context, int width, int height) {
super(context);
setHeight(height);
setWidth(width);
this.context = (Activity) context;
init(context);
}
// 這個構(gòu)造方法貌夕,pop 的高度是WRAP_CONTENT,寬度是 MATCH_PARENT
public MvpCommonPopView(Context context) {
this(context,WindowManager.LayoutParams.MATCH_PARENT,WindowManager.LayoutParams.WRAP_CONTENT);
}
protected void init(Context context) {
// 在比較老的版本上,不設(shè)置背景就不能響應(yīng)返回鍵和點(diǎn)擊外部消失的民镜,但是新版本google api 已經(jīng)修改了啡专,為了保險起見,設(shè)個背景最好
setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
backgroundAlpha( 0.5f);
/**
* 設(shè)置widow 是否獲取焦點(diǎn)制圈,默認(rèn)false,如果為false,那么點(diǎn)擊pop以外的區(qū)域们童,事件會傳遞到popu后面的(behind)界面上.
* 點(diǎn)擊pop 以外的區(qū)域pop 不會消失,注意如果pop 里面有輸入框鲸鹦,那么輸入框不能彈出鍵盤
* 如果設(shè)置為true,pop 會有焦點(diǎn)慧库,點(diǎn)擊外部時 pop 會消失。
*/
setFocusable(true);
// 這個方法是控制是否當(dāng)pop 以外的區(qū)域點(diǎn)擊時是否發(fā)送 MotionEvent.ACTION_OUTSIDE 通知 pop,true 通知馋嗜,false 不通知齐板,但是 這個方法必須要 setFocusable = false ,setTouchable = true 時才有效果
setOutsideTouchable(true);
setTouchInterceptor(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
if ((event.getAction() == MotionEvent.ACTION_DOWN) // 當(dāng) setFocusable為 true 點(diǎn)擊外部會收到這個事件
&& ((x < 0) || (x >= v.getWidth()) || (y < 0) || (y >= v.getHeight()))) {
if (isOutsideTouchDismiss) {
dismiss();
}
return true;
}
/* if (event.getAction() == MotionEvent.ACTION_OUTSIDE) { // ACTION_OUTSIDE 事件是 當(dāng) setOutsideTouchable(true) 方法 設(shè)置為true 時,才會被接受葛菇。否則不會接收這個事件
if (isOutsideTouchDismiss) {
CommonPopView.super.dismiss();
return true;
}
}*/
/**
* 返回 true 不攔截甘磨,走默認(rèn)行為
*
* @Override
* public boolean onTouchEvent(MotionEvent event) {
* final int x = (int) event.getX();
* final int y = (int) event.getY();
*
* if ((event.getAction() == MotionEvent.ACTION_DOWN)
* && ((x < 0) || (x >= getWidth()) || (y < 0) || (y >= getHeight()))) {
* dismiss();
* return true;
* } else if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
* dismiss();
* return true;
* } else {
* return super.onTouchEvent(event);
* }
* }
*/
return false;
}
});
}
/**
* 設(shè)置點(diǎn)擊pop 外部是否消失,這個方法只有在給pop 設(shè)置的
* 寬高不是Match_parent 才有效,因為設(shè)置為了Match_parent這個都是pop,那就沒有點(diǎn)擊外部只說
* @param dismiss
*/
//
public void setTouchOutsideDismiss(boolean dismiss) {
isOutsideTouchDismiss = dismiss;
}
// view: 任意一個已經(jīng)顯示在界面上的view熟呛,注意如果這個view 沒有添加的 window 上時宽档,這個方法會報錯尉姨。
// 因為 pop 在顯示的時候需要通過 view.getWidowToken.
public void showCenter(View view){
showAtLocation(view, Gravity.CENTER,0,0);
}
public void setOnBackKeyDismiss(boolean dismiss) {
isBackKeyDismiss = dismiss;
}
@Override
public void dismiss() {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
StackTraceElement traceElement = stackTrace[3];
String fileName = traceElement.getFileName(); // 拿到調(diào)用 dismiss 方法的類名
fileName = fileName.substring(0, fileName.lastIndexOf("."));
if (!fileName.equals(PopupWindow.class.getSimpleName())) { // 如果調(diào)用者不是從系統(tǒng)來的庵朝,那么就是我們認(rèn)為的
destroy();
super.dismiss();
} else {
if (!isBackKeyDismiss) {
return;
} else {
destroy();
super.dismiss();
}
}
}
private void destroy(){
backgroundAlpha(1);
this.context = null;
}
/**
* 設(shè)置pop 外部是否可點(diǎn)擊,也就是點(diǎn)擊pop 的外部 能把事件傳遞到pop 的 behind
* <p>
* 注意:如果 設(shè)置為true,那么再設(shè)置點(diǎn)擊外部關(guān)閉 pop 將會無效又厉,因此在為true 想關(guān)閉 pop ,必須手動關(guān)閉,調(diào)用close
*
* @param touch
*/
public void setOutsideCanTouch(boolean touch) {
// 設(shè)置為false九府, 點(diǎn)擊pop 外部,事件會傳遞到 pop 的behind,設(shè)置為true ,不會覆致。雖然設(shè)置為false ,但是 pop 里面如果有 輸入框任然可以獲取焦點(diǎn)侄旬,彈出鍵盤,這和 setFocusable = false 不一樣煌妈。
if(MvpUtils.hasQ()){
setTouchModal(!touch);
}
}
/**
* 設(shè)置添加屏幕的背景透明度
*
* @param bgAlpha
*/
public void backgroundAlpha( float bgAlpha) {
WindowManager.LayoutParams lp = context.getWindow().getAttributes();
lp.alpha = bgAlpha;
context.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
context.getWindow().setAttributes(lp);
}
}