1. 程式人生 > >仿京東——購物車

仿京東——購物車

介面佈局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="9"
        android:orientation="vertical">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center"
            android:text="購物車"
            android:textColor="@color/black" />

        <LinearLayout
            android:id="@+id/cart_unlogin"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/cart_gologin"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="20dp"
                android:text="登入" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginLeft="35dp"
                android:text="登入後可檢視購物車中的商品" />
        </LinearLayout>

        <ExpandableListView
            android:id="@+id/cart_ex"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:groupIndicator="@null"></ExpandableListView>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/cart_foot"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:orientation="horizontal">

        <LinearLayout
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="7">
            <CheckBox
                android:text="全選"
                android:id="@+id/cart_quanxuan"
                android:layout_width="wrap_content"
                android:layout_height="match_parent" />
            <TextView
                android:layout_marginLeft="20dp"
                android:gravity="center_vertical"
                android:text="合計:¥"
                android:id="@+id/cart_total"
                android:layout_width="wrap_content"
                android:layout_height="match_parent" />
        </LinearLayout>

        <TextView
            android:id="@+id/cart_sum"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="3"
            android:background="@color/cheng"
            android:gravity="center"
            android:text="去結算()"
            android:textColor="@color/white" />
    </LinearLayout>
</LinearLayout>

程式碼

public class CartFragment extends Fragment implements CartView {
    @BindView(R.id.cart_ex)
    ExpandableListView cartEx;
    Unbinder unbinder;
    @BindView(R.id.cart_unlogin)
    LinearLayout cartUnlogin;
    @BindView(R.id.cart_quanxuan)
    CheckBox cartQuanxuan;
    @BindView(R.id.cart_total)
    TextView cartTotal;
    @BindView(R.id.cart_foot)
    LinearLayout cartFoot;
    @BindView(R.id.cart_gologin)
    TextView cartGologin;
    @BindView(R.id.cart_sum)
    TextView cartSum;
    private SharedPreferences preferences;
    private CartPresenter presenter = new CartPresenter(this);
    private CartAdapter cartAdapter;
    private int uid;
    private int islogin;
    private List<Cart.DataBean> list = new ArrayList<>();
    private int sum;
    private float totalPrice;

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = View.inflate(getActivity(), R.layout.cart_fg, null);
        unbinder = ButterKnife.bind(this, view);
        return view;
    }

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
//        presenter=new CartPresenter(this);
        Log.d("aaa", "重新載入了onActivityCreated");
        initDialog();
    }

    private void initDialog() {
    }

    @Override
    public void onGetCartSuccess(Cart cart) {
        final List<Cart.DataBean> data = cart.getData();
        //***京東未解之謎2.使用請求資料得到的集合不行,會造成頁面資料不更新
        list.clear();
        list.addAll(data);
        if (cartAdapter == null) {
            cartAdapter = new CartAdapter(list);
            cartEx.setAdapter(cartAdapter);
        } else {
            cartAdapter.notifyDataSetChanged();
        }

        //購物車介面的實現
        //***************************************************************
        cartAdapter.setOnCartListener(new CartAdapter.OnCartListener() {
            @Override
            public void onGroupChecked(int groupPosition) {
                boolean allCheckedOnGroup = cartAdapter.isAllCheckedOnGroup(groupPosition);
                cartAdapter.setGoodsCheckedOnGroup(groupPosition, !allCheckedOnGroup);
                cartAdapter.notifyDataSetChanged();
                refreshBottom();
            }

            @Override
            public void onGoodsChecked(int groupPosition, int childPosition) {
                cartAdapter.setGoodsChecked(groupPosition, childPosition);
                cartAdapter.notifyDataSetChanged();
                refreshBottom();
            }

            @Override
            public void onGoodsNumChang(int sellerid, int pid, int num, String selected, int groupPosition, int childPosition) {
                cartAdapter.setGoodsNum(groupPosition, childPosition, num);
                cartAdapter.notifyDataSetChanged();
                refreshBottom();
                //聯網更新資料
                presenter.getUpdateCart(uid + "", sellerid + "", pid + "", selected, num + "");
            }

            @Override
            public void onRemoveGoods(final int groupPosition, final int childPosition, final String pid) {
                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
                builder.setMessage("刪除當前商品?");
                builder.setPositiveButton("確認", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        cartAdapter.removeGoods(groupPosition, childPosition);
                        //判斷group的長度是否為0
                        cartAdapter.setGroup(groupPosition);
                        cartAdapter.notifyDataSetChanged();
                        refreshBottom();
                        //聯網刪除
                        presenter.deleteCart(uid, Integer.parseInt(pid));
                    }
                });
                builder.setNegativeButton("取消", null);
                AlertDialog alertDialog = builder.create();
                alertDialog.show();
            }
        });
        //************************************************************
        //展開所有一級條目
        for (int i = 0; i < data.size(); i++) {
            cartEx.expandGroup(i);
        }
        //重新整理底部
        refreshBottom();
        //全選反選,不能使用setOnCheckedChangeListener監聽,而要用點選監聽
        cartQuanxuan.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                boolean allGoodsSelected = cartAdapter.isAllGoodsSelected();
                cartAdapter.setAllGoodsChecked(!allGoodsSelected);
                cartAdapter.notifyDataSetChanged();
                //更新ui
                refreshBottom();
            }
        });
    }

    @Override
    public void onGetUpdateCart(UpdateCart updateCart) {
        String code = updateCart.getCode();
        if (code.equals("0")) {
            Toast.makeText(getActivity(), "更新商品數量成功", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onDeleteSuccess(DeleteCart deleteCart) {
        String code = deleteCart.getCode();
        if (code.equals("0")) {
            Toast.makeText(getActivity(), "刪除商品成功", Toast.LENGTH_SHORT).show();
        }
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        unbinder.unbind();
        presenter.onDestroy();
        presenter = null;
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.d("aaa", "onResume");
        preferences = getActivity().getSharedPreferences("user", 0);
        presenter = new CartPresenter(this);

        //判斷是否已經登入
        islogin = preferences.getInt("islogin", 0);
        uid = preferences.getInt("uid", 0);
        if (islogin == 1) {
            cartUnlogin.setVisibility(View.GONE);
            cartEx.setVisibility(View.VISIBLE);
            cartFoot.setVisibility(View.VISIBLE);
            //請求資料,設定二級列表介面卡
            presenter.getCart(uid + "");
        } else {
            cartUnlogin.setVisibility(View.VISIBLE);
            cartEx.setVisibility(View.GONE);
            cartFoot.setVisibility(View.GONE);
        }
    }

//    @OnClick({R.id.cart_gologin, R.id.cart_sum})
//    public void onViewClicked() {
//        startActivity(new Intent(getActivity(), LoginActivity.class));
//    }


    //當fragment隱藏狀態發生改變時回撥
    @Override
    public void onHiddenChanged(boolean hidden) {
        super.onHiddenChanged(hidden);
//判斷是否已經登入
        if (!hidden) {
            Log.d("aaa", "被show出來了onHiddenChanged");
        }
        islogin = preferences.getInt("islogin", 0);
        uid = preferences.getInt("uid", 0);
        if (islogin == 1 && !hidden) {
            cartUnlogin.setVisibility(View.GONE);
            cartEx.setVisibility(View.VISIBLE);
            cartFoot.setVisibility(View.VISIBLE);
            //請求資料,設定二級列表介面卡
            presenter.getCart(uid + "");
        } else {
            cartUnlogin.setVisibility(View.VISIBLE);
            cartEx.setVisibility(View.GONE);
            cartFoot.setVisibility(View.GONE);
        }
    }

    //    @Override
//    public void setUserVisibleHint(boolean isVisibleToUser) {
//        super.setUserVisibleHint(isVisibleToUser);
//        Log.d("aaa", "setUserVisibleHint");
//        if (isVisibleToUser) {
//            Log.d("aaa", "對使用者可見了isVisibleToUser");
//            presenter.getCart(uid + "");
//        }
//    }
    public void refreshBottom() {
        //所有商品都選中,將全選按鈕選中
        boolean allGoodsSelected = cartAdapter.isAllGoodsSelected();
        cartQuanxuan.setChecked(allGoodsSelected);
        //設定所有選中商品數量
        sum = cartAdapter.getSum();
        cartSum.setText("去結算(" + cartAdapter.getSum() + ")");
        //設定所有選中商品的總價
        totalPrice = cartAdapter.getTotalPrice();
        cartTotal.setText("合計:¥" + cartAdapter.getTotalPrice());
    }

    @OnClick({R.id.cart_gologin, R.id.cart_sum})
    public void onViewClicked(View view) {
        switch (view.getId()) {
            case R.id.cart_gologin:
                startActivity(new Intent(getActivity(), LoginActivity.class));
                break;
            case R.id.cart_sum:
                //商品數量大於0才建立訂單
                int sum = cartAdapter.getSum();
                if (sum > 0) {
                    //使用rxjava過濾資料,選中的資料取出來
                    EventBus.getDefault().postSticky(new OrderEvent(list, totalPrice));
            //使用者登入,獲得userid,sessionid
                    userLogin();
            //                    getActivity().startActivity(new Intent(getActivity(), CreateOrderActivity.class));
                }
                Toast.makeText(getActivity(), "所選商品數量:" + this.sum, Toast.LENGTH_SHORT).show();
                break;
        }
    }

    private void userLogin() {
        OkHttpClient client = genericClient();
        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(Api.url)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .client(client)
                .build();
        ApiService apiService = retrofit.create(ApiService.class);

        String phone = "13522219872";
        String pwd = "123456";
        //對稱加密
        String pssword = AESEncryptUtil.encrypt(pwd);
        Log.i("aaa", "對稱加密pssword:" + pssword);
        Flowable<LoginInfo> flowable = apiService.login(phone, pssword);
        flowable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribeWith(new DisposableSubscriber<LoginInfo>() {
                    @Override
                    public void onNext(LoginInfo loginInfo) {
                        LoginInfo.ResultBean result = loginInfo.getResult();
                        int userId = result.getUserId();
                        String sessionId = result.getSessionId();

//                        getActivity().startActivity(new Intent(getActivity(), CreateOrderActivity.class));

                        Intent intent = new Intent(getActivity(), CreateOrderActivity.class);
                        //正常是把登入成功後的使用者資訊儲存在SharedPreferences需要的時候取
                        intent.putExtra("userId", userId);
                        intent.putExtra("sessionId", sessionId);
                        getActivity().startActivity(intent);
                        Log.i("aaa", "登入成功 userId:" + userId + ",登入成功sessionId:" + sessionId);
//                        Toast.makeText(UserActivity.this, loginInfo.getMessage(), Toast.LENGTH_SHORT).show();


                        //放在這裡純粹為了拿到 userId,sessionId
//                        push(token, userId, sessionId);
                    }

                    @Override
                    public void onError(Throwable t) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }

    //把請求頭新增給OkHttpClient的攔截器
    public OkHttpClient genericClient() {
        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(new Interceptor() {
                    @Override
                    public Response intercept(Chain chain) throws IOException {
                        //把Request給Chain
                        Request request = chain.request()
                                .newBuilder()
                                .addHeader("Content-Type", "application/x-www-form-urlencoded")
                                .addHeader("ak", "0110010010001")
                                .build();
                        return chain.proceed(request);
                    }

                })
                .build();

        return httpClient;
    }

}

mvp-model-CarModel

public class CartModel {
    public Observable<Cart> getCart(String uid){
        return RetrofitUtil.getDefault().create(MyRetrofit.class).getCart(uid);
    }
    public Observable<UpdateCart> getUpdateCart(String uid,String sellerid,String pid,String selected,String num){
        return RetrofitUtil.getDefault().create(MyRetrofit.class).getUpdateCart(uid,sellerid,pid,selected,num);
    }
    public Observable<DeleteCart> deleteCart(int uid,int pid){
        return RetrofitUtil.getDefault().create(MyRetrofit.class).deleteCart(uid,pid);
    }

}

mvp-presenter-CarPresenter

public class CartPresenter extends BasePresenter<CartView> {

    private CartModel cartModel;

    public CartPresenter(CartView view) {
        super(view);
    }

    @Override
    public void initModel() {
        cartModel = new CartModel();
    }
    public void getCart(String uid) {
        cartModel.getCart(uid)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<Cart>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        compositeDisposable.add(d);
                    }

                    @Override
                    public void onNext(Cart cart) {
                        view.onGetCartSuccess(cart);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
    public void getUpdateCart(String uid,String sellerid,String pid,String selected,String num) {
        cartModel.getUpdateCart(uid,sellerid,pid,selected,num)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<UpdateCart>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        compositeDisposable.add(d);
                    }

                    @Override
                    public void onNext(UpdateCart updateCart) {
                        view.onGetUpdateCart(updateCart);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
    public void deleteCart(int uid,int pid) {
        cartModel.deleteCart(uid,pid)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<DeleteCart>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        compositeDisposable.add(d);
                    }

                    @Override
                    public void onNext(DeleteCart deleteCart) {
                        view.onDeleteSuccess(deleteCart);
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {

                    }
                });
    }
}

mvp-view-CarView

public interface CartView extends IView {
    void onGetCartSuccess(Cart cart);
    void onGetUpdateCart(UpdateCart updateCart);
    void onDeleteSuccess(DeleteCart deleteCart);
}

CarAdapter

public class CartAdapter extends BaseExpandableListAdapter {
    private List<Cart.DataBean> list;
    private CheckBox seller_check;
    private CheckBox goods_check;
    private Cart.DataBean.ListBean bean;
    private Context context;

    public CartAdapter(List<Cart.DataBean> list) {
        this.list = list;
    }

    @Override
    public int getGroupCount() {
        return list.size();
    }

    @Override
    public int getChildrenCount(int groupPosition) {
        return list.get(groupPosition).getList().size();
    }

    @Override
    public Object getGroup(int groupPosition) {
        return list.get(groupPosition);
    }

    @Override
    public Object getChild(int groupPosition, int childPosition) {
        return list.get(groupPosition).getList().get(childPosition);
    }

    @Override
    public long getGroupId(int groupPosition) {
        return groupPosition;
    }

    @Override
    public long getChildId(int groupPosition, int childPosition) {
        return childPosition;
    }

    @Override
    public boolean hasStableIds() {
        return true;
    }

    @Override
    public View getGroupView(final int groupPosition, boolean isExpanded, View convertView, ViewGroup parent) {
        context = parent.getContext();
        if (convertView == null) {
            convertView = View.inflate(parent.getContext(), R.layout.seller_item, null);
        }
        TextView seller_name = convertView.findViewById(R.id.seller_name);
        seller_check = convertView.findViewById(R.id.seller_check);
        //************************************group點選監聽
        seller_check.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onCartListener != null) {
                    onCartListener.onGroupChecked(groupPosition);
                }
            }
        });
        //如果當前group中的所有商品都被選中,那就把group選中
        boolean selectedOnGroup = isSelectedOnGroup(groupPosition);
        seller_check.setChecked(selectedOnGroup);
        seller_name.setText(list.get(groupPosition).getSellerName());
        return convertView;
    }

    private boolean isSelectedOnGroup(int groupPosition) {
        List<Cart.DataBean.ListBean> list = this.list.get(groupPosition).getList();
        for (Cart.DataBean.ListBean bean : list) {
            if (bean.getSelected() == 0) {
                return false;
            }
        }
        return true;
    }

    @Override
    public View getChildView(final int groupPosition, final int childPosition, boolean isLastChild, View convertView, final ViewGroup parent) {
        if (convertView == null) {
            convertView = View.inflate(parent.getContext(), R.layout.goods_item, null);
        }
        bean = list.get(groupPosition).getList().get(childPosition);
        SimpleDraweeView goods_img = convertView.findViewById(R.id.goods_img);
        TextView goods_title = convertView.findViewById(R.id.goods_title);
        TextView goods_price = convertView.findViewById(R.id.goods_price);
        goods_check = convertView.findViewById(R.id.goods_check);
        //處理imgurl
        String imgUrl = getImgUrl(bean.getImages());
        goods_img.setImageURI(Uri.parse(imgUrl));
        goods_title.setText(bean.getTitle());
        goods_price.setText("¥"+bean.getBargainPrice());
        //根據集合資料設定複選框選中狀態
        goods_check.setChecked(bean.getSelected() == 1);
        //************************************
        goods_check.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onCartListener != null) {
                    onCartListener.onGoodsChecked(groupPosition, childPosition);
                }
            }
        });
        //設定加減器監聽
        AddDeleteView goods_ad = convertView.findViewById(R.id.goods_ad);
        goods_ad.setOnAddDeleteViewClick(new AddDeleteView.OnAddDeleteViewClick() {
            @Override
            public void onNumChange(int num) {
                if (onCartListener != null) {
                    Cart.DataBean.ListBean bean = list.get(groupPosition).getList().get(childPosition);
                    onCartListener.onGoodsNumChang(bean.getSellerid(), bean.getPid(), num,bean.getSelected()+"", groupPosition, childPosition);
                }
            }
        });
        //長按刪除當前條目
        convertView.setOnLongClickListener(new View.OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                Cart.DataBean.ListBean bean = CartAdapter.this.list.get(groupPosition).getList().get(childPosition);
                if (onCartListener!=null){
                    onCartListener.onRemoveGoods(groupPosition,childPosition,bean.getPid()+"");
                }
                return true;
            }
        });
        goods_ad.setSum(bean.getNum());
        return convertView;
    }

    private String getImgUrl(String images) {
        int i = images.indexOf("!");
        return images.substring(0, i);
    }

    @Override
    public boolean isChildSelectable(int groupPosition, int childPosition) {
        return true;
    }

    //計算總數
    public int getSum() {
        int sum = 0;
        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j < list.get(i).getList().size(); j++) {
                if (list.get(i).getList().get(j).getSelected() == 1) {
                    int num = list.get(i).getList().get(j).getNum();
                    sum += num;
                }
            }
        }
        return sum;
    }

    //結算總價
    public float getTotalPrice() {
        int sum = 0;
        float price = 0f;
        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j < list.get(i).getList().size(); j++) {
                if (list.get(i).getList().get(j).getSelected() == 1) {
                    int num = list.get(i).getList().get(j).getNum();
                    double bargainPrice = list.get(i).getList().get(j).getBargainPrice();
                    price += (float) (num * bargainPrice);
                }
            }
        }
        return price;
    }

    //判斷所有商品是都都被選中
    public boolean isAllGoodsSelected() {
        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j < list.get(i).getList().size(); j++) {
                if (list.get(i).getList().get(j).getSelected() == 0) {
                    //如果有一個沒選中返回false
                    return false;
                }
            }
        }
        return true;
    }

    //改變單個group中所有商品的選中狀態
    public void setGoodsCheckedOnGroup(int groupPosition, boolean groupChecked) {
        List<Cart.DataBean.ListBean> list = this.list.get(groupPosition).getList();
        for (Cart.DataBean.ListBean listBean : list) {
            listBean.setSelected(groupChecked ? 1 : 0);
        }
    }

    //判斷單個group中所有商品是否都被選中
    public boolean isAllCheckedOnGroup(int groupPosition) {
        List<Cart.DataBean.ListBean> list = this.list.get(groupPosition).getList();
        for (Cart.DataBean.ListBean listBean : list) {
            if (listBean.getSelected() == 0) {
                return false;
            }
        }
        return true;
    }

    //改變集合中所有商品的狀態
    public void setAllGoodsChecked(boolean check) {
        for (int i = 0; i < list.size(); i++) {
            for (int j = 0; j < list.get(i).getList().size(); j++) {
                list.get(i).getList().get(j).setSelected(check ? 1 : 0);
            }
        }
    }

    //改變某個商品的狀態
    public void setGoodsChecked(int groupPosition, int childPosition) {
        //***京東未解之謎1.複用bean會造成複選框取消不了
//        list.get(groupPosition).getList().get(childPosition).setSelected(bean.getSelected() == 0 ? 1 : 0);
        Cart.DataBean.ListBean bean = list.get(groupPosition).getList().get(childPosition);
        bean.setSelected(bean.getSelected() == 0 ? 1 : 0);
    }

    //修改某個商品的數量
    public void setGoodsNum(int groupPosition, int childPosition, int num) {
        list.get(groupPosition).getList().get(childPosition).setNum(num);
    }

    //從集合刪除某個商品
    public void removeGoods( int groupPosition,  int childPosition){
        list.get(groupPosition).getList().remove(childPosition);
    }
    //判斷某個group長度是否為0
    public void setGroup(int groupPosition){
        List<Cart.DataBean.ListBean> listBeans =list.get(groupPosition).getList();
        if(listBeans.size()==0){
            list.remove(groupPosition);
        }
    }
    public interface OnCartListener {
        void onGroupChecked(int groupPosition);

        void onGoodsChecked(int groupPosition, int childPosition);

        void onGoodsNumChang(int sellerid, int pid, int num,String selected, int groupPosition, int childPosition);
        void onRemoveGoods(int groupPosition, int childPosition,String pid);
    }

    private OnCartListener onCartListener;

    public void setOnCartListener(OnCartListener onCartListener) {
        this.onCartListener = onCartListener;
    }
}

自定義加減器的佈局

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
<TextView
    android:gravity="center"
    android:textSize="30dp"
    android:text="-"
    android:id="@+id/delete"
    android:layout_width="40dp"
    android:layout_height="wrap_content" />
    <TextView
        android:gravity="center"
        android:textSize="30dp"
        android:text="0"
        android:id="@+id/num"
        android:layout_width="60dp"
        android:layout_height="wrap_content" />
    <TextView
        android:gravity="center"
        android:textSize="30dp"
        android:text="+"
        android:id="@+id/add"
        android:layout_width="40dp"
        android:layout_height="wrap_content" />
</LinearLayout>

自定義加減器

public class AddDeleteView extends LinearLayout {
    private int sum = 0;
    private TextView delete;
    private TextView num;
    private TextView add;

    public AddDeleteView(Context context) {
        this(context, null, 0);
    }

    public AddDeleteView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AddDeleteView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.adddeleteview, this);
        add = findViewById(R.id.add);
        delete = findViewById(R.id.delete);
        num = findViewById(R.id.num);

        add.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                sum++;
                num.setText(sum + "");
                if (onAddDeleteViewClick != null) {
                    onAddDeleteViewClick.onNumChange(sum);
                }
            }
        });
        delete.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (sum > 1) {
                    sum--;
                    num.setText(sum + "");
                    if (onAddDeleteViewClick != null) {
                        onAddDeleteViewClick.onNumChange(sum);
                    }
                }
            }
        });
    }

    public void setSum(int sum) {
        this.sum = sum;
        num.setText(sum + "");
    }

    public interface OnAddDeleteViewClick {
        void onNumChange(int num);
    }

    private OnAddDeleteViewClick onAddDeleteViewClick;

    public void setOnAddDeleteViewClick(OnAddDeleteViewClick onAddDeleteViewClick) {
        this.onAddDeleteViewClick = onAddDeleteViewClick;
    }
}