• 微信公众号:美女很有趣。 工作之余,放松一下,关注即送10G+美女照片!

Android:简单实现外卖购物车思路分享

互联网 diligentman 1周前 (05-04) 5次浏览

思路

要实现以下两个重要模块:
1.在中间大部分处展示所有食物的列表(需要一个RecyclerView)
2.需要一个购物车按钮,点击后从底部弹出购物车,显示选择的餐品列表,和总金额(需要一个实现从底部出现窗口的BottomSheetDiaLog和一个展示已选餐品列表的RecyclerView)

实现

1.所有食物列表
(1)select_food_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="2dp"
    android:padding="10dp"
    app:cardBackgroundColor="@null"
    app:cardCornerRadius="10dp"
    tools:context="com.example.cops.SelectFoodActivity">

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"

        android:background="@drawable/background_bear_coding"
        android:orientation="vertical">
        
        <TextView
            android:id="@+id/storeName"
            android:layout_width="134dp"
            android:layout_height="29dp"
            android:layout_marginBottom="6dp"
            android:gravity="center"
            android:textSize="30dp"
            app:layout_constraintBottom_toTopOf="@+id/storeAddress"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="1.0" />

        <TextView
            android:id="@+id/storeAddress"
            android:layout_width="253dp"
            android:layout_height="21dp"
            android:gravity="center"
            android:textSize="15dp"
            app:layout_constraintBottom_toTopOf="@+id/foods"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.505"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.854" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/foods"
            android:layout_width="396dp"
            android:layout_height="398dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="MissingConstraints" />

        <ImageButton
            android:id="@+id/back"
            android:layout_width="43dp"
            android:layout_height="35dp"
            android:layout_marginStart="16dp"
            android:layout_marginTop="16dp"
            android:background="#00000000"
            android:scaleType="centerInside"
            android:src="@drawable/button_return"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            tools:ignore="MissingConstraints" />

        <com.google.android.material.floatingactionbutton.FloatingActionButton
            android:id="@+id/check"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="end|bottom"
            android:layout_margin="16dp"
            android:layout_marginTop="8dp"
            android:layout_marginEnd="16dp"
            android:contentDescription="@string/seecart"
            android:src="@drawable/button_cart"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="1.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/foods" />

    </androidx.constraintlayout.widget.ConstraintLayout>

</androidx.cardview.widget.CardView>

注意点:
1.刚上手android的可以使用constraintlayout布局,很方便‘;
2.如果是新手一定要记得起id并且要起的详细精炼,因为后面同一个activity中可能要引入多个相似的部件,很可能搞混。

(2)food_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@null"
    android:orientation="horizontal"
    android:padding="10dp">

    <LinearLayout
        android:layout_width="393dp"
        android:layout_height="88dp"
        android:background="@drawable/item_background"
        android:gravity="center_vertical">

        <ImageView
            android:id="@+id/foodImage"
            android:layout_width="84dp"
            android:layout_height="match_parent"
            android:src="@drawable/button_baozi"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <LinearLayout
            android:layout_width="123dp"
            android:layout_height="match_parent"
            android:orientation="vertical"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <TextView
                android:id="@+id/foodName"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_gravity="center_vertical"
                android:textSize="21sp" />

            <TextView
                android:id="@+id/foodInfo"
                android:layout_width="97dp"
                android:layout_height="54sp"
                android:layout_gravity="center_vertical"
                android:layout_weight="1"
                android:textColor="@color/saddlebrown"
                android:textSize="10sp" />

        </LinearLayout>

        <TextView
            android:id="@+id/foodPrice"
            android:layout_width="58dp"
            android:layout_height="wrap_content"
            android:layout_centerInParent="false"
            android:layout_gravity="center"
            android:textSize="25sp" />

        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="86dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center">

            <ImageButton
                android:id="@+id/increase_btn"
                android:layout_width="21dp"
                android:layout_height="20dp"
                android:layout_gravity="center"
                android:background="#0000"
                android:src="@drawable/button_increase2"
                android:textSize="30sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.0"
                app:layout_constraintStart_toEndOf="@+id/foodSum"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.578" />

            <TextView
                android:id="@+id/foodSum"
                android:layout_width="38dp"
                android:layout_height="58dp"
                android:gravity="center"
                android:text="0"
                android:textSize="25dp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toEndOf="@+id/decrease_btn"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.0" />

            <ImageButton
                android:id="@+id/decrease_btn"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_gravity="center"

                android:layout_marginStart="4dp"
                android:layout_toLeftOf="@id/foodSum"
                android:background="#0000"
                android:src="@drawable/button_decrease2"
                android:textSize="30sp"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintVertical_bias="0.578" />
        </androidx.constraintlayout.widget.ConstraintLayout>
    </LinearLayout>
</LinearLayout>

*注意点:
1.通过在顶层标签中设置背景颜色为透明(@null)和padding建个属性,可以实现列表Item之间有间隔.

(3)SelectFoodActivity.java中该列表的代码
a.首先声明它

    private RecyclerView foodsListView;

b.创建对应的Adapter和ViewHolder

    private class MyFoodsViewHolder extends RecyclerView.ViewHolder{

        private TextView foodName;
        ...

        public MyFoodsViewHolder(final View itemView){
            super(itemView);
            //获取行中显示各种数据的控件
            foodName = itemView.findViewById(R.id.foodName);
            ...
        }
    }

    private class MyFoodsAdapter extends RecyclerView.Adapter<MyFoodsViewHolder>{
        private List<Food> items;

        public MyFoodsAdapter(List<Food> items) {
            this.items = items;
        }

        @NonNull
        @Override
        public MyFoodsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.food_item_view, parent, false);

            MyFoodsViewHolder viewHolder = new MyFoodsViewHolder(view);
            return viewHolder;
        }

        @Override
        public void onBindViewHolder(@NonNull final MyFoodsViewHolder holder, final int position) {
            //获取要绑定数据的行的控件
            Food food = items.get(position);
            holder.foodName.setText(food.getFood_name());
            byte[] image = food.getFood_image();
            Bitmap bitmap = BitmapFactory.decodeByteArray(image, 0, image.length);
        }

        @Override
        public int getItemCount() {
            return items.size();
        }
    }

注意点:
1.ViewHolder的onCreateViewHolder中引入item布局文件;
2.Adapter只负责生成一行的item,通过回调方法来重复生成item,每个item的数据不同是通过position参数来从集合中拿取对应的对象信息,再绑定到该Item上;
3.接收到由byte数组存储的图片后用bitmap来显示到前端。

c.声明并绑定RecyclerView

foodsListView = findViewById(R.id.foods);
foodsListView.setLayoutManager(new LinearLayoutManager(getBaseContext()));
foodsListView.setAdapter(new MyFoodsAdapter(foods));

2.点击购物车底部弹出购物车页面
(1)弹出界面需要使用到BottomSheetDialog:
a.声明:

    private BottomSheetDialog bottomSheetDialog;

b.实现:其中check是购物车按钮(这里使用的是悬浮按钮)

        bottomSheetDialog = new BottomSheetDialog(SelectFoodActivity.this);
        View view = LayoutInflater.from(this).inflate(R.layout.layout_bottom_sheet, null);
        bottomSheetDialog.setContentView(view);
        bottomSheetDialog.setCancelable(true);
        bottomSheetDialog.setCanceledOnTouchOutside(true);
        check = findViewById(R.id.check);
        check.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                bottomSheetDialog.show();//点击后显示该bottomSheetDialog
            }
        });

注意点:
1.很大的一个坑,不要用BottomSheetLayOut来代替BottomSheetDialog,会导致其中的列表无法显示(救命我搞了一天这个bug);
2.因为弹出的也是个页面,所以要新建个layout文件,布置这个页面,而已选择食物列表就在这个layout上面。

(2)已选择食物列表:
和全部食物列表的实现大同小异,不写了

3.点击食物Item的加减按钮同步更新金额和已选择食物列表
(1)点击加减更新已选食物列表和全部食物列表的数量:

            //点击加号,食物数量++
            holder.increase.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.Q)
                @Override
                public void onClick(View v) {
                    int count = Integer.parseInt(holder.foodCount.getText().toString());
                    holder.foodCount.setText(String.valueOf(count + 1));
                    Food food = foods.get(position);
                    if(count == 0){
                        //该行食物数量为1
                        food.setCount(1);
                        //增加一行已选择食物
                        selectedFoods.add(food);
                        selectedFoodsListView.getAdapter().notifyDataSetChanged();
                        Log.e(THIS, "增加了:" + food.getFood_name());
                        print(selectedFoods);
                    }else {
                        //不新增,覆盖该位置的原数量值
                        selectedFoods.remove(food);
                        food.setCount(count + 1);
                        selectedFoods.add(food);

                        selectedFoodsListView.getAdapter().notifyItemChanged(position);
                        Log.e(THIS, "增加了:" + food.getFood_name());
                        print(selectedFoods);
                    }
                    total = BigDecimal.valueOf(Double.parseDouble(total.toString())).add(food.getFood_price());
                    refresh(total);
                }
            });

            //点击减号,食物数量--
            holder.decrease.setOnClickListener(new View.OnClickListener() {
                @RequiresApi(api = Build.VERSION_CODES.Q)
                @Override
                public void onClick(View v) {
                    int count = Integer.parseInt(holder.foodCount.getText().toString());
                    Food food = foods.get(position);

                    //不让0有可乘之机,别把重复的抽出放外面!
                    if(count == 1){
                        Log.e(THIS, "减少了:" + food.getFood_name());
                        selectedFoods.remove(food);
                        selectedFoodsListView.getAdapter().notifyDataSetChanged();
                        food.setCount(count - 1);
                        holder.foodCount.setText(String.valueOf(count - 1));

                        total = BigDecimal.valueOf(Double.parseDouble(total.toString())).subtract(food.getFood_price());
                        refresh(total);
                    }else if(count > 1){
                        Log.e(THIS, "减少了:" + food.getFood_name());
                        selectedFoods.remove(food);
                        food.setCount(count - 1);
                        selectedFoods.add(food);
                        selectedFoodsListView.getAdapter().notifyItemChanged(position);
                        print(selectedFoods);
                        holder.foodCount.setText(String.valueOf(count - 1));
                        total = BigDecimal.valueOf(Double.parseDouble(total.toString())).subtract(food.getFood_price());
                        refresh(total);
                    }
                }
            });

(2)点击加减同步总金额:通知已选餐品列表的时候通知该列表即可,但是总金额怎么整呢?实时setText?事实证明就算你set了页面是不刷新的等于白set,所以用Handler实现,在onCreate中声明实现该handler:

        refreshHandler = new Handler() {
            public void handleMessage(android.os.Message msg) {
                if (msg.what == 0) {
                    totalPrice.setText(String.valueOf(total)); //View.ininvalidate()
                    sendEmptyMessageDelayed(0, 1000);
                }
            }
        };

在每次需要更新总金额的时候调用:

 refreshHandler.sendEmptyMessageDelayed(0, 1000);

常遇到的BUG

1.试图将值放到一个空部件上:大部分是获取该部件的时候没获取对,要么id没写对,要么view.findViewById的view不是存在这个部件的view。
2.使用了通知RecylcerView更新的方法但是不更新:首先检测数据集是否更新,没问题了再检查其他原因。大部分是因为你把对象列表的引用指向了别处,原本和列表绑定的内存块在角落看着你不敢说话,例如list = newData;不可以这样,需要removeAll(list),addAll(newData)。再不然可能是特殊情况,比如我遇到的bottomSheetLayout遇到RecyclerView打架问题。

建议

如果你和我一样是初学者的话,最好自己多看博客和书自己改编实现各种功能,粘代码的话永远学不到自己肚子里,过程虽然痛苦但是结果开心呀,一步一步来,一点点改bug,慢慢滴用的多了就懂了,页面就不放了,是毕设内容~


程序员灯塔
转载请注明原文链接:Android:简单实现外卖购物车思路分享
喜欢 (0)