关于 PopupWindow ,很多博客有谈到利用 Builder 设计模式的链式写法,以下是我项目中的类似写法
/**
* 显示选择性别
*/
private void showGenderPopWindow() {
if (null == genderPopupWindow) {
CommonPop.Builder builder = new CommonPop.Builder(BundledTravelCardActivity.this);
View layView = LayoutInflater.from(BundledTravelCardActivity.this).inflate(R.layout.popup_window_bundled_travel_card, null);
genderPopupWindow = builder.setView(layView)
.setBackGroundLevel(0.7f).setOutsideTouchable(true)
.setWidthAndHeight(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
.setAnimationStyle(R.style.MyPopupWindow_alpha_style)
.create();
TextView tvOne = layView.findViewById(R.id.tv_one);
tvOne.setText(getString(R.string.man));
tvOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tvGender.setText(getString(R.string.man));
genderPopupWindow.dismiss();
}
});
TextView tvTwo = layView.findViewById(R.id.tv_two);
tvTwo.setText(getString(R.string.woman));
tvTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
tvGender.setText(getString(R.string.woman));
genderPopupWindow.dismiss();
}
});
}
genderPopupWindow.showAtLocation(getWindow().getDecorView(), Gravity.BOTTOM, 0, 0);
}
但是这种写法也有所局限性,如果该提示框在其他地方也有显示,代码无法复用,这时有人会说了,把代码写到一个工具类,不就可以达到复用了吗,问题是假如我们要对 PopupWindow 的 子 view 进行点击事件监听,那么还要进行接口回调,假设一个业务场景,点击了某一个按钮,然后我们通过接口回调触发 view 层的一个方法,最后再改变 PopupWindow 里子 view 的背景图片以表示被点击,这样我们就必须把需要改变状态的 view 申明为全局变量,而且如果类似的 PopupWindow 很多,我们是把它们写到一个工具类里面还是单独写呢,毫无疑问,放在一块是不合理的容易造成 bug,而且代码混乱违背单一原则,单独写工具栏就还不如对PopupWindow 进行抽象封装,把 view 的事件和UI都放到该实现类里面,还可以避免写重复代码,提取共性,以上是我的理解,下面是抽象 PopupWindow 代码。
/**
* 抽象的 PopupWindow 基类
*/
public abstract class BasePopupWindow extends PopupWindow {
private static final String TAG = BasePopupWindow.class.getSimpleName();
public Context mContext;
public View mView;
/**
* 屏幕灰度级别
*/
private float mLevel;
public BasePopupWindow(Context context) {
super(context);
if (null == context) {
LogUtils.e(TAG, "BasePopupWindow context 为空");
return;
}
this.mContext = context;
this.setBackgroundDrawable(new BitmapDrawable());
this.setFocusable(true);
ColorDrawable dw = new ColorDrawable(0x00000000);
this.setBackgroundDrawable(dw);
initAnimation();
initSetting();
initView();
initListener();
setContentView(mView);
}
public void setmLevel(float mLevel) {
this.mLevel = mLevel;
}
/**
* 初始化 PopupWindow 的设置
*/
public abstract void initSetting();
/**
* 初始化布局
*/
public abstract void initView();
/**
* 初始化监听事件
*/
public void initListener() {
}
private void setBackGroundLevel(float level) {
Window window = ((Activity) mContext).getWindow();
WindowManager.LayoutParams params = window.getAttributes();
params.alpha = level;
window.setAttributes(params);
}
@Override
public void dismiss() {
super.dismiss();
setBackGroundLevel(1.0f);
}
@Override
public void showAsDropDown(View anchor) {
this.showAsDropDown(anchor, 0, 0);
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff) {
this.showAsDropDown(anchor, xoff, yoff, Gravity.TOP | Gravity.START);
}
@Override
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {
super.showAsDropDown(anchor, xoff, yoff, gravity);
initBackGroundLevel();
}
/**
* 初始化背景颜色灰度
*/
private void initBackGroundLevel() {
if(mLevel == 0){
mLevel = 0.4f;
}
setBackGroundLevel(mLevel);
}
@Override
public void showAtLocation(View parent, int gravity, int x, int y) {
super.showAtLocation(parent, gravity, x, y);
initBackGroundLevel();
}
/**
* 设置默认的动画,需要设置其他动画直接重写并删除 super 实现即可
*/
public void initAnimation() {
setAnimationStyle(R.style.MyPopupWindow_alpha_style);
}
}
第二,不知道大家对商城类 APP 的我的订单页面有没有印象,它们的布局是一样的,只是 item 的类型和点击事件不一样,那么像这样的页面我们该怎么去设计呢,或许有的朋友会说干脆写一个类,把所以的处理放一块,还有的朋友会说,每一个页面都写一个类,但是关于代码复用怎么处理呢,如何从业务中提取出共性逻辑,假如我们以后业务修改了,又如何去处理呢,以下是我在项目中的业务界面。
![](https://i-blog.csdnimg.cn/blog_migrate/33e650aa9ff93db127013fc6b3c3f5df.png)
简单的说一下该页面的布局和业务逻辑,待付款页面的 item 有关闭订单和确认支付两个按钮,而待收货页面的 item 里有确认收货按钮,已关闭的页面的 item 有删除按钮,按钮的操作逻辑顾名思义,所有页面请求订单的接口一致,按参数来区分,都有上拉刷新和下拉刷新,有 loading view 和 empty view ,根据以上,我们可以提取一下共性代码来复用。
**
* 我的订单 Fragment 的基类
* 提取共有逻辑
*/
public class BaseOrderFragment extends BaseLazyLoadFragment implements IOrderListView, IOrderListListenerView {
@BindView(R.id.rc_content)
RecyclerView rcContent;
@BindView(R.id.state_view_integral_record)
MultiStateView stateViewIntegralRecord;
@BindView(R.id.swipe_relayout)
android.support.v4.widget.SwipeRefreshLayout mRefLayout;
// 是否为加载动画中
private boolean isLoading = true;
// 设置不允许上拉加载
private boolean noLoadMore;
/**
* 订单状态
*/
private String status;
public String uId;
private String paystatus;
private int page = 1;
// 获取列表数据的 P
private OrderListPImpl orderListP;
private OrderListAdapter mAdapter;
// 订单点击事件的 P
public OrderListListenerPImpl listenerP;
// 等待收货的回调
private OrderListAdapter.WaitReceivingCall mWaitReceivingCall;
// 关闭订单的回调
private OrderListAdapter.CloseOrderCall mCloseOrderCall;
// 待支付的回调
private OrderListAdapter.WaitPayCall mWaitPayCall;
public static BaseOrderFragment getInstance(String status, String uId, String paystatus) {
BaseOrderFragment fragment = new BaseOrderFragment();
Bundle bundle = new Bundle();
bundle.putString("status", status);
bundle.putString("uId", uId);
bundle.putString("paystatus", paystatus);
fragment.setArguments(bundle);
return fragment;
}
public void setmWaitReceivingCall(OrderListAdapter.WaitReceivingCall mWaitReceivingCall) {
this.mWaitReceivingCall = mWaitReceivingCall;
}
public void setmCloseOrderCall(OrderListAdapter.CloseOrderCall mCloseOrderCall) {
this.mCloseOrderCall = mCloseOrderCall;
}
public void setmWaitPayCall(OrderListAdapter.WaitPayCall mWaitPayCall) {
this.mWaitPayCall = mWaitPayCall;
}
@Override
public void fetchData() {
getDatas();
}
public void getDatas(){
if (null == orderListP) {
orderListP = new OrderListPImpl();
orderListP.attachView(this);
}
orderListP.prestOrderList(String.valueOf(page), uId, status, "order_list", paystatus);
}
@Override
protected void initViews() {
mRefLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
if(null != mAdapter){
noLoadMore = true;
mAdapter.setEnableLoadMore(false);
}
showLoading();
mRefLayout.setRefreshing(false);
getDatas();
}
});
}
@Override
protected void initDatas() {
if (null != getArguments()) {
Bundle bundle = getArguments();
status = bundle.getString("status");
uId = bundle.getString("uId");
paystatus = bundle.getString("paystatus");
}
if(null == listenerP){
listenerP = new OrderListListenerPImpl();
listenerP.attachView(this);
}
}
/**
* 初始化内容
* @param bean
*/
private void initContentView(OrderListBean bean) {
if (page == 1) {
page++;
setDataInitiated(true);
if (!checkData(bean)) {
stateViewIntegralRecord.setViewState(MultiStateView.VIEW_STATE_EMPTY);
return;
}
if(null == mAdapter){
mAdapter = new OrderListAdapter(bean.getData().getData(), mWaitReceivingCall,
mCloseOrderCall, mWaitPayCall);
rcContent.setLayoutManager(new LinearLayoutManager(mContext));
rcContent.setAdapter(mAdapter);
mAdapter.setOnLoadMoreListener(new BaseQuickAdapter.RequestLoadMoreListener() {
@Override
public void onLoadMoreRequested() {
getDatas();
}
});
}else {
mAdapter.replaceData(bean.getData().getData());
}
mAdapter.loadMoreComplete();
if(isLoading){
isLoading = false;
resetLoadMore();
// 为了避免网速很快,加载动画造成闪烁的效果,体验不佳
stateViewIntegralRecord.postDelayed(new Runnable() {
@Override
public void run() {
if(null != stateViewIntegralRecord){
stateViewIntegralRecord.setViewState(MultiStateView.VIEW_STATE_CONTENT);
}
}
}, 500);
}
} else {
if (checkData(bean)) {
page++;
mAdapter.addData(bean.getData().getData());
mAdapter.loadMoreComplete();
} else {
mAdapter.loadMoreEnd();
}
}
}
/**
* 重置可上拉加载
*/
private void resetLoadMore() {
if(noLoadMore && null != mAdapter){
noLoadMore = false;
mAdapter.setEnableLoadMore(true);
}
}
/**
* 检查数据的合法性
* @param bean
* @return
*/
private boolean checkData(OrderListBean bean) {
try {
if (null != bean &&
null != bean.getData().getData() &&
bean.getData().getData().size() > 0) {
return true;
}
} catch (NullPointerException e) {
e.printStackTrace();
}
return false;
}
@Override
protected int getContentLayoutRes() {
return R.layout.layout_rc;
}
@Override
public void showNetError() {
if(!isLoading && null != mAdapter){
mAdapter.loadMoreFail();
}else {
resetLoadMore();
showEmptyView();
}
}
// 显示空的 View
private void showEmptyView() {
isLoading = false;
stateViewIntegralRecord.postDelayed(new Runnable() {
@Override
public void run() {
if(null != stateViewIntegralRecord){
stateViewIntegralRecord.setViewState(MultiStateView.VIEW_STATE_EMPTY);
}
}
}, 500);
}
@Override
public void showDataError() {
if(!isLoading && null != mAdapter){
mAdapter.loadMoreEnd();
}else {
resetLoadMore();
showEmptyView();
}
}
@Override
public void showMsg(String msg) {
showToast(msg);
}
@Override
public Activity getMyContext() {
return mContext;
}
@Override
public void updateDatas(String msg) {
showToast(msg);
showLoading();
getDatas();
}
// 显示加载中动画
public void showLoading() {
page = 1;
isLoading = true;
setDataInitiated(false);
stateViewIntegralRecord.setViewState(MultiStateView.VIEW_STATE_LOADING);
}
@Override
public void setOrderList(OrderListBean bean) {
initContentView(bean);
}
}
IOrderListView 是查询订单数据页面的 view ,IOrderListListenerView 则是用户在操作点击时候后返回数据的 view,用来区别是否操作成功,以下是订单列表的 xml 布局代码
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/swipe_relayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.kennyc.view.MultiStateView
android:id="@+id/state_view_integral_record"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:msv_animateViewChanges="true"
app:msv_emptyView="@layout/empty_integral_record"
app:msv_loadingView="@layout/loading_default_view"
app:msv_viewState="loading">
<android.support.v7.widget.RecyclerView
android:id="@+id/rc_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</com.kennyc.view.MultiStateView>
</android.support.v4.widget.SwipeRefreshLayout>
就拿待支付页面举例,我们需要在点击确认支付的时候弹出选择支付的提示框,然后调起相应的第三方支付客户端支付
/**
* 待付款 fragment
*/
public class WaitPaymentFragment extends BaseOrderFragment implements IPayView,
OrderListAdapter.WaitPayCall{
public static WaitPaymentFragment getInstance(String status, String uId, String paystatus) {
WaitPaymentFragment fragment = new WaitPaymentFragment();
Bundle bundle = new Bundle();
bundle.putString("status", status);
bundle.putString("uId", uId);
bundle.putString("paystatus", paystatus);
fragment.setArguments(bundle);
return fragment;
}
private IWXAPI api;
private WaitPayPImpl waitPayP;
private OrderListPayPop orderListPayPop;
@Override
protected void initDatas() {
if(null == waitPayP){
waitPayP = new WaitPayPImpl();
waitPayP.attachView(this);
}
setmWaitPayCall(this);
api = WXAPIFactory.createWXAPI(mContext, Constants.WeChatId);
api.registerApp(Constants.WeChatId);
super.initDatas();
}
@Override
public void isPaySuccess(final boolean tag) {
mContext.runOnUiThread(new Runnable() {
@Override
public void run() {
if(tag){
showToast("支付宝支付成功");
showLoading();
getDatas();
}else {
showToast("支付宝支付失败");
}
}
});
}
@Override
public void setWeChatDatas(PayReq datas) {
// 在支付之前,如果应用没有注册到微信,应该先调用IWXMsg.registerApp将应用注册到微信
api.sendReq(datas);
}
@Override
public void waitPay(final String amount, final String ordersn, final String subject, int position) {
LogUtils.e("setPayParams", amount + " " + ordersn + " " + subject);
if(null == orderListPayPop){
orderListPayPop = new OrderListPayPop(mContext);
}
orderListPayPop.setAmount(amount);
orderListPayPop.setOrderListPayCall(new OrderListPayPop.OrderListPayCall() {
@Override
public void callAliPay() {
waitPayP.prestAliPayOrder(amount, ordersn, subject);
}
@Override
public void callWeChat() {
waitPayP.prestWeChatOrder(amount, ordersn, subject);
}
});
orderListPayPop.showAtLocation(mContext.getWindow().getDecorView(),
Gravity.CENTER, 0, 0);
}
@Override
public void closeWaitPayOrder(String ordersn, int position) {
LogUtils.e("closeWaitPayOrder", ordersn);
listenerP.callListener("order_close", uId, ordersn);
}
}
这里再简单说明一下,IPayView 是支付的 view 层,OrderListAdapter.WaitPayCall 是传递到 Adapter 的接口回调,用于回调点击事件。