Android:ListView+选项式菜单实现购物车功能


选项菜单其实就是位于右上角的一个隐藏菜单,通过点击右上角的竖三点或文字来创建菜单,点击菜单项可以触发菜单项点击事件。

本来选项式菜单和弹出菜单、上下文菜单应该一起写,但是因为我想把我之前写的的listView的购物车例子和选项菜单结合,所以篇幅过长就单独写了

废话不多说,先上图。

            

如上图所示分别是菜单项:管理、完成、退出。


 OptionMenu的创建步骤:

1、在app/res/menu下新建 Menu Resource File

2、选项菜单不需要绑定的View,但是需要Activity重写onCreateOptionsMenu()和onOptionsItemSelected()方法

3、在onCreateOptionsMenu(Menu menu)里,用MenuInflater的inflate()方法可以将Menu Resource File填充到Menu对象中

4、在onOptionsItemSelected(MenuItem item)里,写点击菜单项的事件。


 我这次的例子要比我上一篇的Android:ListView有了一些改进:1、加入了ViewHolder让listView更快的显示速度。2、不再使用Map记录CheckBox的状态,改用javaBean做记录,并增加一个对用户“触屏点击”的判断,来解决CheckBox在上下滑动时状态改变的问题。3、增加了选项式菜单optionMenu,可以批量删除ListView里的数据项。4、增加了“全选”CheckBox和底部的实时显示商品总价的TextView。(我这次改进是照着某宝购物车的样子模仿的,可能还是有不足的地方,希望大家不要笑话我)


 准备展示商品的图片,图片粘贴到app/res/drawable。文字描述以及图片ID可以放到app/res/values/strings.xml里

<resources>
    <string name="app_name">OptionMenu</string>
    <string-array name="detailList">
    <item>宝宝夏装2020新款洋气男孩衣服套装幼儿童装夏季短裙背带裤两件套</item>
    <item>拇指鱼儿童卫衣2019秋季童装拼色长袖T恤套头打底衫小童圆领上衣</item>
    <item>女童套装童装2020夏季新款韩版可爱猫咪蓬蓬裙儿童纱裙公主短裙</item>
    <item>女童洋气网红套装2020新款韩版儿童装短袖两件套女孩时髦运动夏装</item>
    <item>女童连衣裙2020新款夏季洋气装儿童宝宝小孩公主背心裙亲子美女装</item>
    <item>童装男童夏天短袖套装夏装2020新款中大童小儿童网红帅洋气韩版潮</item>
    <item>童装两件套2020夏装女童套装新款夏季女背带裙洋气2短袖衬衫宝宝</item>
    <item>2020卡通动漫新款童装夏牛仔背带裤套装男童短袖t恤潮部落</item>
    </string-array>
    <integer-array name="imageID">
        <item>@drawable/clothes1</item>
        <item>@drawable/clothes2</item>
        <item>@drawable/clothes3</item>
        <item>@drawable/clothes4</item>
        <item>@drawable/clothes5</item>
        <item>@drawable/clothes6</item>
        <item>@drawable/clothes7</item>
        <item>@drawable/clothes8</item>
    </integer-array>
</resources>
strings.xml

首先明确我们需要什么样的列表项,如下图这个列表项是由4个控件组成:CheckBox、ImageView、TextView、TextView

于是我们开始设计我们的列表布局,新建Layout Resource File

<?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"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <CheckBox
        android:id="@+id/checkBox"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="110dp"
        android:layout_height="110dp"
        android:padding="5dp"
        app:srcCompat="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/tv_details"
        android:layout_width="150dp"
        android:layout_height="110dp"
        android:padding="5dp"
        android:layout_gravity="center"/>

    <TextView
        android:id="@+id/tv_price"
        android:layout_width="0dp"
        android:layout_height="110dp"
        android:layout_weight="1"
        android:gravity="center"
        android:textStyle="bold" />
</LinearLayout>
listview_item_layout.xml

 为了更好地传递这一个列表项的信息,我们需要一个JavaBean,来打包这些信息以方便传递。

这里CheckBox只有两种状态,应该是Boolean类型;ImageView显示的图片在资源文件里是Int型;后面两个TextView里的数据都是String类型

public class CartBean {
    private boolean checked=false;
    private  int imageID;
    private String details;
    private  float price;

    public CartBean(int imageID, String details, float price) {
        this.imageID = imageID;
        this.details = details;
        this.price = price;
    }

    public boolean isChecked() {
        return checked;
    }

    public void setChecked(boolean checked) {
        this.checked = checked;
    }

    public int getImageID() {
        return imageID;
    }

    public void setImageID(int imageID) {
        this.imageID = imageID;
    }

    public String getDetails() {
        return details;
    }

    public void setDetails(String details) {
        this.details = details;
    }

    public float getPrice() {
        return price;
    }

    public void setPrice(float price) {
        this.price = price;
    }
}
CartBean.java

 一个列表项=一个JavaBean;那么一组列表项=List<JavaBean>。为了把这个我们打包好的数据传给ListView,我们还需要一个适配器Adapter,那么新建一个java class做适配器

关于我自定义的Adapter的介绍:

  1. 继承BaseAdapter,需要重写getCount()、getItem()、getItemId()、getView()这四个方法,前三个与List<JavaBean>数据有关,最后一个与每一行数据项显示有关,因为ListView是一行行显示,所以每显示一行都会调用一次getView方法。
  2. 因为adapter需要确定在什么上下文环境(ListView所在的环境)适配什么数据,所以adapter的构造方法传入了这两个参数。
  3. 因为每一项都是一个JavaBean,调用的方法也都一样,所以直接在适配器的getView方法里定义控件,包括填充控件信息、触发的事件等。
  4. 当ListView的项数较多一屏无法完全显示的时候,在上下滑动ListView的过程中,划入屏幕的项会通过getView得到,而划出屏幕的项的视图会被回收,这个被回收的项的视图就是convertView。我们观察getView()方法时会发现,里面有两种方法:inflate()将布局文件填充到某上下文环境、findViewById()通过id得到控件,这两个方法的参数是固定的值,也就是说,对每一个列表项来说都要在调用getView()时重复使用这两种方法。那么在划入划出时,这种重复操作必然影响显示效果,为了更快渲染ListView,我们用ViewHolder来避免findViewById的重复使用。
  5. ViewHolder其实是一个java内部类,只有属性没有方法。当covertView为空,也就是说没有被回收的项,那么就需要每次在getView方法里调用inflate和findViewById方法,当listView上下滑动时,convertView不为空,于是可以通过convertView的标志位得到ViewHolder对象,ViewHolder对象的属性就是我们需要找到的控件。
  6. getView(int position, View convertView, ViewGroup parent),这个方法里的三个参数:position--当前项的位置;converView--被回收的项;parent--项的父视图,这里指ListView。而ListView的parent就是ListView所在的视图,即R.layout.activity_main.xml(ListView在该布局文件)填充的视图。
  7. 复选框设置的监听器里重写的onCheckedChanged(CompoundButton buttonView, boolean isChecked)方法,其中buttonView.isPressed()可以判断是否有“点击”,以此来区分人为的点击带来的CheckBox状态改变。于是,我利用这一特点,当人为点击造成变化的isChecked值,就传入CartBean的"checked",然后CheckBox的实际状态由这个“checked”决定。这样避免了由于ListView上下滑动过程中,由于回收了项而无法记录CheckBox状态(因为回收再利用时会恢复初始的未选中的状态)。 
  8. 全选CheckBox也有buttonView.isPressed()的判断,但不是为了解决回收再利用的状态改变。而是为了在人为点击勾选了“全选”后,又取消列表的某一项的复选框时,“全选”自动取消勾选,但这种非人为的取消不会引起“全选”取消时商品总价=0的操作。
public class MyListViewAdapter extends BaseAdapter {
    private Context cxt;//上下文环境
    private List<CartBean> itemList;//列表数据
    private float TotalPrice=0;//商品总价
    private  TextView tv_totalPrice;//底部显示总价textView
    private CheckBox all_CheckBox;//全选复选框
    private int P_checked_NUm=0;//得到已勾选的商品的数量

    public MyListViewAdapter(Context cxt, List<CartBean> itemList) {
        this.cxt = cxt;
        this.itemList = itemList;
    }
    public void setTotalPrice(float totalPrice) {
        TotalPrice = totalPrice;
    }

    public float getTotalPrice() {
        return TotalPrice;
    }
    @Override
    public int getCount() {
        return itemList.size();
    }

    @Override
    public Object getItem(int position) {
        return itemList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }
    public static class ViewHolder{//自定义的内部类ViewHolder
        CheckBox mcheckBox;
        ImageView mimageView;
        TextView mtextView1;
        TextView mtextView2;
    }
    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
         ViewHolder holder;
        if(convertView==null){
            convertView = View.inflate(cxt,R.layout.listview_item_layout,null);
            holder=new ViewHolder();
            holder.mcheckBox=convertView.findViewById(R.id.checkBox);
            holder.mimageView=convertView.findViewById(R.id.imageView);
            holder.mtextView1=convertView.findViewById(R.id.tv_details);
            holder.mtextView2=convertView.findViewById(R.id.tv_price);
            //getView()中参数的parent是listView,那么listView的parent就是listView所在的视图
            ViewGroup primaryView=(ViewGroup)parent.getParent();
            all_CheckBox=primaryView.findViewById(R.id.checkBox2);//全选CheckBox
            tv_totalPrice=primaryView.findViewById(R.id.textView);//底部实时显示总价的textView
            convertView.setTag(holder);
        }else{
            holder=(ViewHolder)convertView.getTag();        }

        holder.mimageView.setImageResource(itemList.get(position).getImageID());
        holder.mtextView1.setText(itemList.get(position).getDetails());
        holder.mtextView2.setText("¥ "+ itemList.get(position).getPrice());
        holder.mcheckBox.setChecked(itemList.get(position).isChecked());

        //以下是列表项的CheckBox的勾选状态改变触发的事件,计算商品总价
        holder.mcheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if (!buttonView.isPressed()) return;//这句一定要有,为了让每一列表项划入划出时导致的checkBox的状态改变不会触发价格计算
                //在Bean数据包中记录复选框的勾选/取消勾选的状态
                itemList.get(position).setChecked(isChecked);
                if (isChecked) {
                    P_checked_NUm++;
                    TotalPrice += itemList.get(position).getPrice();
                } else {
                    P_checked_NUm--;
                    TotalPrice -= itemList.get(position).getPrice();
                }
                //当勾选的项等于列表的所有项数,将自动勾选“全选",否则取消"全选"
                if(P_checked_NUm==itemList.size()){
                    all_CheckBox.setChecked(true);
                }else{
                    all_CheckBox.setChecked(false);
                }
                tv_totalPrice.setText("¥ "+TotalPrice);
            }
        });//end_holder.mcheckBox.setOnCheckedChangeListener()

        //以下是全选CheckBox勾选事件
        all_CheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                if(!buttonView.isPressed())return;
                //当不是人为点击使全选发生状态改变时(如全选状态下,取消勾选某一项商品或一项项全部勾选),不触发以下操作
                if(isChecked){
                    for(int i=0;i<itemList.size();i++){
                        if(!itemList.get(i).isChecked()){//未勾选的项改为勾选
                            P_checked_NUm=itemList.size();
                            itemList.get(i).setChecked(true);
                            TotalPrice+=itemList.get(i).getPrice();
                        }//已勾选的项不用改变
                    }
                }else{
                    for(int i=0;i<itemList.size();i++){
                        if(itemList.get(i).isChecked()) {//已勾选的项改为未勾选
                            P_checked_NUm=0;
                            itemList.get(i).setChecked(false);
                            TotalPrice -= itemList.get(i).getPrice();
                        }//未勾选的项不用改变
                    }
                }
                notifyDataSetChanged();//通知适配器,列表数据已更新
                tv_totalPrice.setText("¥ "+TotalPrice);
            }
        });//end_all_CheckBox.setOnCheckedChangeListener()

        return convertView;
    }
}
MyListViewAdapter.java

 然后就是我们的重点(并不是)!Option Menu !

在app/res/menu下新建option_menu.xml

  1. app:showAsAction="always"  :让菜单项以文字的形式直接显示在右上角
  2. “完成”这个菜单项一开始是隐藏的,只有当点击“管理”后,“完成”菜单项才会显示
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/manage"
        android:title="管理"
        app:showAsAction="always" />
    <item
        android:id="@+id/finish"
        android:title="完成"
        android:visible="false"
        app:showAsAction="always" />
    <item
        android:id="@+id/quit"
        android:title="退出" />
</menu>
option_menu.xml

突然发现还没有创建ListView这个控件,其实是在Activity的布局文件里设置

如下图,是一个购物车页面,ListView在上,底部分别是CheckBox、TextView、Button

  1. 这里的ListView一定要设置高度为“match_parent”,我开始设成“wrap_content”,结果发现getView方法会被反复调用,我找了一天才找到这个问题。
  2. 两个Button都有android:background="@drawable/button_bg",其实是一个自定义的drawable文件作为按钮背景,具体方法看:Android:Button,这里不粘贴按钮背景的xml代码了。
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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:orientation="vertical"
    tools:context=".MainActivity">
    <!--ListView的layout_height只能是match_parent-->
    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:paddingBottom="50dp">
    </ListView>

    <FrameLayout
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:layout_gravity="bottom">

        <CheckBox
            android:id="@+id/checkBox2"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:text="全选" />

        <TextView
            android:id="@+id/textView"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:textColor="#ff0000"
            android:gravity="center"
            android:layout_gravity="right"
            android:textSize="20sp"
            android:text="¥ 0.0"
            android:layout_marginRight="100dp" />

        <Button
            android:id="@+id/jiezhang"
            android:layout_width="100dp"
            android:layout_height="match_parent"
            android:layout_gravity="right"
            android:textColor="#ffffff"
            android:background="@drawable/button_bg"
            android:text="结账离开" />
    </FrameLayout>

    <Button
        android:id="@+id/deleteButton"
        android:layout_width="match_parent"
        android:layout_height="50dp"
        android:visibility="invisible"
        android:layout_gravity="bottom"
        android:textColor="#ff0000"
        android:textSize="20dp"
        android:background="@drawable/del_button_bg"
        android:text="删除" />

</FrameLayout>
activity_main.xml

 

 关于MainActivity.java的介绍:

  1. 从strings.xml读取数据的参考文章:Android开发values目录里定义数组、颜色、文本、尺寸xml配置文件并且获取数据
  2. ListView设置适配器需要:先新建Adapter对象,并通过构造方法传入一组JavaBean的数据列表;再让ListView调用setAdapter方法设置适配器。
  3. 选项式菜单需要Activity重写两个方法:onCreateOptionsMenu(Menu menu)和onOptionsItemSelected(@NonNull MenuItem item),我的菜单比较简单,仅仅是为隐藏或显示底部的删除按钮
  4. 我给删除按钮加了事件,让ListView的数据列表remove被勾选的项,先记录列表项的position,然后倒序删除列表项(因为删除后后面各项位置会前移,从后往前删不用担心这个,因为删除后只影响被删除项之后的项,不影响前面的项)(最后别忘了删完把商品总价清零)参考文章:Android:ListView批量删除
  5. 最后的showMyToast()方法是自定义显示时间长短的Toast 参考文章:如何将Toast的显示时间随意设置
public class MainActivity extends AppCompatActivity {
    private Button deleteButton;//为了设置隐藏的全局删除按钮
    private MenuItem manageItem,finishItem;//为了设置可视/不可视的全局菜单项对象
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final TextView tv_TotalPrice=findViewById(R.id.textView);//底部显示总价的textView
        ListView mylistView=findViewById(R.id.listView);//显示购物清单的listView
        deleteButton=findViewById(R.id.deleteButton);//底部的删除按钮
        
        final List<CartBean> beansData=init();//初始化数据

        final MyListViewAdapter myAdapter=new MyListViewAdapter(this,beansData);//适配器与数据适配
        mylistView.setAdapter(myAdapter);//Listview与适配器适配

        deleteButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                CartBean bean;
                List<Integer> position_checkedItem=new ArrayList<>();//记录被勾选项的位置
                int i;
                //倒叙遍历myBeans,选中的列表项先记录位置到position_checkedItem
                for(i=beansData.size()-1;i>=0;i--){
                    bean=beansData.get(i);
                    if(bean.isChecked()){
                        position_checkedItem.add(i);
                    }
                }
                //被记录位置的列表项删掉
                if(position_checkedItem.size()!=beansData.size()) {
                    for (int position : position_checkedItem) {
                        beansData.get(position).setChecked(false);
                        beansData.remove(position);
                    }
                }else{
                    beansData.clear();
                }
                //删除时需要勾选商品,会触发价格计算,所以当点击删除按钮后,需要把价格清零
                myAdapter.setTotalPrice(0);
                tv_TotalPrice.setText("¥ "+myAdapter.getTotalPrice());
                myAdapter.notifyDataSetChanged();

                Toast toast=Toast.makeText(getApplicationContext(),"已删除",Toast.LENGTH_SHORT);
                showMyToast(toast,1000);
            }
        });//删除按钮
        
    }//end_onCreate()

    private  List init(){//初始化数据
        List<CartBean> myBeans=new ArrayList<>();
        //从资源文件,得到商品图片ID
         TypedArray typedArray =getResources().obtainTypedArray(R.array.imageID);
         int[] P_imagesID=new int[typedArray.length()];
         for(int i=0;i< typedArray.length();i++){
             P_imagesID[i]=typedArray.getResourceId(i,0);
         }
         typedArray.recycle();
         //从资源文件,得到商品描述
        String[] P_details =getResources().getStringArray(R.array.detailList);
        //float不能放在资源文件里,我就这样定义吧,以后学了数据库可以从数据库得到这些数据
        float[] P_prices = new float[]{78.0f, 160.0f, 67.5f, 188.0f, 189.0f, 79.0f, 38.0f, 68.0f};

        //添加数据列表List
        CartBean item;
        for(int i = 0; i< P_imagesID.length; i++){
            item=new CartBean(P_imagesID[i], P_details[i], P_prices[i]);
            myBeans.add(item);
        }
        return myBeans;
    }//end_init()

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //得到menu里的各菜单项对象
        this.getMenuInflater().inflate(R.menu.option_menu,menu);
        manageItem=menu.findItem(R.id.manage);
        finishItem=menu.findItem(R.id.finish);
        return true;
    }//end_onCreateOptionMenu()

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        switch(item.getItemId()){
            /*
            * 1.点击管理菜单项,出现底部删除按钮
            * 2.点击完成菜单项,隐藏底部删除按钮
            * 3.点击退出菜单项,退出购物页面*/

            case R.id.manage:
                manageItem.setVisible(false);
                finishItem.setVisible(true);
                deleteButton.setVisibility(View.VISIBLE);
                break;
            case R.id.finish:
                manageItem.setVisible(true);
                finishItem.setVisible(false);
                deleteButton.setVisibility(View.INVISIBLE);
                break;
            case R.id.quit:
                MainActivity.this.finish();
                break;
        }
        return true;
    }//end_onOptionItemSelected()

    //以下是自定义Toast显示时间的方法
    public void showMyToast(final Toast toast, final int cnt) {
        final Timer timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                toast.show();
            }
        }, 0, 3000);
        new Timer().schedule(new TimerTask() {
            @Override
            public void run() {
                toast.cancel();
                timer.cancel();
            }
        }, cnt );
    }//end_showMyToast()

}
MainActivity.java

 

好了,那么我的购物车就做好了。

  


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM