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