关于 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 的类型和点击事件不一样,那么像这样的页面我们该怎么去设计呢,或许有的朋友会说干脆写一个类,把所以的处理放一块,还有的朋友会说,每一个页面都写一个类,但是关于代码复用怎么处理呢,如何从业务中提取出共性逻辑,假如我们以后业务修改了,又如何去处理呢,以下是我在项目中的业务界面。简单的说一下该页面的布局和业务逻辑,待付款页面的 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 的接口回调,用于回调点击事件。免费获取安卓开发架构的资料(包括Fultter、高级UI、性能优化、架构师课程、 NDK、Kotlin、混合式开发(ReactNative+Weex)和一线互联网公司关于android面试的题目汇总可以加:936332305 / 链接:点击链接加入【安卓开发架构】