对于 Android 业务开发的一些理解总结

来自:萬物並作吾以觀復

关于 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, 00);
    }

但是这种写法也有所局限性,如果该提示框在其他地方也有显示,代码无法复用,这时有人会说了,把代码写到一个工具类,不就可以达到复用了吗,问题是假如我们要对 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, 00);
    }

    @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 IOrderListViewIOrderListListenerView {

    @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, 00);
    }

    @Override
    public void closeWaitPayOrder(String ordersn, int position) {
        LogUtils.e("closeWaitPayOrder", ordersn);
        listenerP.callListener("order_close", uId, ordersn);
    }
}

这里再简单说明一下,IPayView 是支付的 view 层,OrderListAdapter.WaitPayCall 是传递到 Adapter 的接口回调,用于回调点击事件。

推荐↓↓↓
安卓开发
上一篇:React Native 实践与感悟 下一篇:2019 年 Android 面试题汇总