商城購物車加減控制元件的簡單封裝

四級五次郎發表於2019-03-02

我們都知道,購物車是做商城專案必不可少的一個環節,購物車中的加減控制元件就是商城中的重中之重,最近專案中也用到了加減控制元件,但是使用起來樣式不能隨便更改,決定簡單封裝一下,以後用到的時候就不那麼麻煩了,幾行程式碼就搞定。本文主要是對封裝的過程進行一下整理。

1. 先看下效果圖

效果圖:

AddSubUtils.gif
AddSubUtils.gif

Github地址:AddSubUtils

同步csdn和簡書:
csdn地址:商城購物車加減控制元件的簡單封裝
簡書地址:商城購物車加減控制元件的簡單封裝

2. 需求分析

  • 可以手動輸入,也可以加減控制元件微調。
  • 加入購買的最大值(實際開發中可能有)
  • 加入商品的庫存(實際開發中可能有)
  • 加入步長,當數大的時候不可能一直按1去加
  • 加入購買的最小值
  • 加入輸入框可以在左中右(實際開發中可能有)
  • 可以自由指定控制元件的樣式

這裡涵蓋了大部分的加減控制元件的需求,對於我們來說,我們只需要簡單的呼叫就ok了,所以接下來會把邏輯處理部分封裝在一個類中,並且自定義屬性,可以讓我們隨時更改樣式。

3. 實現思路

簡單理一下思路,第一點如果支援手動輸入,TextView是滿足不了需求的,這裡使用EditText,第二點,我們們把關於樣式的放在自定義屬性中,在xml程式碼中去控制,至於最大值、庫存、步長、最小值等放在程式碼中呼叫的時候賦值,預設最大值和庫存都為int的最大值,即Integer.MAX_VALUE,步長、當前值和最小值為1。

3.1 自定義加減控制元件的佈局和樣式

佈局:add_sub_layout.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:divider="@drawable/divider_horizontal"
    android:background="@drawable/addsubutils_add_sub_bg"
    android:orientation="horizontal">

    <ImageView
        android:id="@+id/ic_minus"
        android:layout_width="@dimen/addsubutils_btn_width"
        android:layout_height="match_parent"
        android:background="@drawable/addsubutils_left_selector"
        android:clickable="true"
        android:padding="@dimen/addsubutils_btn_padding"
        android:scaleType="centerInside"
        android:src="@drawable/addsubutils_ic_minus" />

    <EditText
        android:id="@+id/et_input"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@null"
        android:cursorVisible="true"
        android:digits="0123456789"
        android:gravity="center"
        android:singleLine="true"
        android:inputType="number"
        android:minWidth="@dimen/addsubutils_et_minwidth"
        android:text="1"
        android:textColor="@color/addsubutils_text"
        android:textCursorDrawable="@null"
        android:textSize="@dimen/addsubutils_textsize"/>

    <ImageView
        android:id="@+id/ic_plus"
        android:layout_width="@dimen/addsubutils_btn_width"
        android:layout_height="match_parent"
        android:background="@drawable/addsubutils_right_selector"
        android:clickable="true"
        android:padding="@dimen/addsubutils_btn_padding"
        android:scaleType="centerInside"
        android:src="@drawable/addsubutils_ic_plus" />
</LinearLayout>複製程式碼

這裡非常簡單,就是一個水平的Linear包裹了兩個ImageView和一個EditText,這裡用到幾種的樣式

整體背景樣式:addsubutils_add_sub_bg.xml

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <stroke
        android:width="1dp"
        android:color="@color/divider"/>
    <corners android:radius="3dp"/>
</shape>複製程式碼

左邊按鈕預設背景樣式:addsubutils_left_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:enterFadeDuration="200" android:exitFadeDuration="200">
    <item android:state_pressed="true" >
        <shape>
            <stroke android:color="@color/divider"
                android:width="1dp"/>
            <solid android:color="@color/divider"/>
            <corners android:topLeftRadius="3dp"
                android:bottomLeftRadius="3dp"/>
        </shape>
    </item>
    <item android:state_pressed="false" >
        <shape>
            <stroke android:color="@color/divider"
                android:width="1dp"/>
            <corners android:topLeftRadius="3dp"
                android:bottomLeftRadius="3dp"/>
        </shape>
    </item>
</selector>複製程式碼

右邊按鈕預設背景樣式:addsubutils_right_selector.xml

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:enterFadeDuration="200" android:exitFadeDuration="200">
    <item android:state_pressed="true" >
        <shape>
            <stroke android:color="@color/divider"
                android:width="1dp"/>
            <solid android:color="@color/divider"/>
            <corners android:topRightRadius="3dp"
                android:bottomRightRadius="3dp"/>
        </shape>
    </item>
    <item android:state_pressed="false" >
        <shape>
            <stroke android:color="@color/divider"
                android:width="1dp"/>
            <corners android:topRightRadius="3dp"
                android:bottomRightRadius="3dp"/>
        </shape>
    </item>
</selector>複製程式碼

dimens中字型大小和邊距

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="addsubutils_btn_width" >30dp</dimen>
    <dimen name="addsubutils_btn_padding">10dp</dimen>
    <dimen name="addsubutils_textsize" >15sp</dimen>
    <dimen name="addsubutils_et_minwidth" >65dp</dimen>
</resources>複製程式碼

分割線divider_horizontal的樣式

<?xml version="1.0" encoding="utf-8"?>
<shape
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">
    <size
        android:width="1dp"/>
    <solid android:color="@color/divider"/>
</shape>複製程式碼

用到的圖片:

addsubutils_ic_plus
addsubutils_ic_plus
addsubutils_ic_minus
addsubutils_ic_minus

3.2 自定義AddSubUtils繼承LinearLayout

/**
 * Created by 賈夢飛 on 2017/8/10 10:55.
 * QQ:821176301
 * 微信:j821176301
 * desc:購物車功能的增加和加減
 */
public class AddSubUtils extends LinearLayout {

    private EditText etInput;
    private ImageView icPlus;
    private ImageView icMinus;

    public AddSubUtils(Context context) {
        this(context, null);
    }

    public AddSubUtils(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AddSubUtils(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs, defStyleAttr);
    }
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void init(Context context, AttributeSet attrs, int defStyleAttr) { 
            // 把佈局和當前類形成整體
            LayoutInflater.from(context).inflate(R.layout.add_sub_layout, this);
            icPlus = (ImageView) findViewById(R.id.ic_plus);
            icMinus = (ImageView) findViewById(R.id.ic_minus);
            etInput = (EditText) findViewById(R.id.et_input);
    }
}複製程式碼

3.3 在init()方法中加入自定義屬性和樣式


    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    private void init(Context context, AttributeSet attrs, int defStyleAttr) {
        //得到屬性
        if (attrs != null) {
            TypedArray typeArray = getContext().obtainStyledAttributes(attrs, R.styleable.AddSubUtils);
            boolean editable = typeArray.getBoolean(R.styleable.AddSubUtils_editable, true);
            String location = typeArray.getString(R.styleable.AddSubUtils_location);
            // 左右兩面的寬度
            int ImageWidth = typeArray.getDimensionPixelSize(R.styleable.AddSubUtils_ImageWidth, -1);
            // 中間內容框的寬度
            int contentWidth = typeArray.getDimensionPixelSize(R.styleable.AddSubUtils_contentWidth, -1);
            // 中間字型的大小
            int contentTextSize = typeArray.getDimensionPixelSize(R.styleable.AddSubUtils_contentTextSize, -1);
            // 中間字型的顏色
            int contentTextColor = typeArray.getColor(R.styleable.AddSubUtils_contentTextColor, 0xff000000);
            // 整個控制元件的background
            Drawable background = typeArray.getDrawable(R.styleable.AddSubUtils_all_background);
            // 左面控制元件的背景
            Drawable leftBackground = typeArray.getDrawable(R.styleable.AddSubUtils_leftBackground);
            // 右面控制元件的背景
            Drawable rightBackground = typeArray.getDrawable(R.styleable.AddSubUtils_rightBackground);
            // 中間控制元件的背景
            Drawable contentBackground = typeArray.getDrawable(R.styleable.AddSubUtils_contentBackground);
            // 左面控制元件的資源
            Drawable leftResources = typeArray.getDrawable(R.styleable.AddSubUtils_leftResources);
            // 右面控制元件的資源
            Drawable rightResources = typeArray.getDrawable(R.styleable.AddSubUtils_rightResources);
            // 資源回收
            typeArray.recycle();

            // 如果是start就說明輸入框在左邊,如果是end說明輸入框在右面,否則預設是在中間
            if("start".equals(location)) {
                //把佈局和當前類形成整體
                LayoutInflater.from(context).inflate(R.layout.add_sub_start_layout, this);
            }else if("end".equals(location)) {
                //把佈局和當前類形成整體
                LayoutInflater.from(context).inflate(R.layout.add_sub_end_layout, this);
            }else {
                //把佈局和當前類形成整體
                LayoutInflater.from(context).inflate(R.layout.add_sub_layout, this);
            }

            icPlus = (ImageView) findViewById(R.id.ic_plus);
            icMinus = (ImageView) findViewById(R.id.ic_minus);
            etInput = (EditText) findViewById(R.id.et_input);

            // 設定EditText是否可點選
            setEditable(editable);
            etInput.setTextColor(contentTextColor);

            // 設定兩邊按鈕的寬度
            if (ImageWidth > 0) {
                LayoutParams textParams = new LayoutParams(ImageWidth, LayoutParams.MATCH_PARENT);
                icPlus.setLayoutParams(textParams);
                icMinus.setLayoutParams(textParams);
            }

            // 設定中間輸入框的寬度
            if (contentWidth > 0) {
                LayoutParams textParams = new LayoutParams(contentWidth, LayoutParams.MATCH_PARENT);
                etInput.setLayoutParams(textParams);
            }
            if (contentTextColor > 0) {
                etInput.setTextSize(contentTextColor);
            }
            if(contentTextSize > 0) {
                etInput.setTextSize(contentTextSize);
            }
            if (background != null) {
                setBackgroundDrawable(background);
            } else {
                setBackgroundResource(R.drawable.addsubutils_add_sub_bg);
            }

            if (contentBackground != null) {
                etInput.setBackground(contentBackground);
            }

            if(leftBackground != null) {
                icMinus.setBackground(leftBackground);
            }

            if (rightBackground != null){
                icPlus.setBackground(rightBackground);
            }
            if (leftResources != null){
                icMinus.setImageDrawable(leftResources);
            }
            if(rightResources != null) {
                icPlus.setImageDrawable(rightResources);
            }
        }
    }

    private void setEditable(boolean editable) {
        if (editable) {
            etInput.setFocusable(true);
            etInput.setKeyListener(new DigitsKeyListener());
        } else {
            etInput.setFocusable(false);
            etInput.setKeyListener(null);
        }
    }複製程式碼

首先使用TypedArray得到自定義屬性值,然後傳給對應的控制元件,這裡註釋寫的很詳細。

3.4 加入點選事件和EditText輸入框的監聽

在init()方法中:

            icPlus.setOnClickListener(this);
            icMinus.setOnClickListener(this);
            etInput.addTextChangedListener(this);複製程式碼

AddSubUtils類實現View.OnClickListener, TextWatcher這兩個介面,並重寫onClick()和其他的方法

public class AddSubUtils extends LinearLayout implements View.OnClickListener, TextWatcher {
    @Override
    public void onClick(View view) {
        int id = view.getId();
        if (id == R.id.ic_plus) {
            // 加
            if (inputValue < Math.min(mBuyMax, inventory)) {
                inputValue += mStep;
                //正常新增
                etInput.setText("" + inputValue);
            } else if (inventory < mBuyMax) {
                //庫存不足
                warningForInventory();
            } else {
                //超過最大購買數
                warningForBuyMax();
            }
        } else if (id == R.id.ic_minus) {
            // 減
            if (inputValue > mBuyMin) {
                inputValue -= mStep;
                etInput.setText(inputValue + "");
            } else {
                // 低於最小購買數
                warningForBuyMin();
            }
        } else if (id == R.id.et_input) {
            // 輸入框
            etInput.setSelection(etInput.getText().toString().length());
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {

    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before, int count) {
        onNumberInput();
    }

    /**
     * 監聽輸入的資料變化
     */
    private void onNumberInput() {
        //當前數量
        int count = getNumber();
        if (count < mBuyMin) {
            //手動輸入
            etInput.setText(mBuyMin + "");
            return;
        }
        int limit = Math.min(mBuyMax, inventory);
        if (count > limit) {
            if (inventory < mBuyMax) {
                //庫存不足
                warningForInventory();
            } else {
                //超過最大購買數
                warningForBuyMax();
            }
        }else{
           inputValue = count;
        }
    }
}複製程式碼

這裡具體邏輯大家一看就明白,我就不再重複了

3.5 定義警告的介面

    public interface OnWarnListener {
        // 不能超過庫存
        void onWarningForInventory(int inventory);
        // 不能超過購買最大數
        void onWarningForBuyMax(int max);
        // 不能低於最小購買數
        void onWarningForBuyMin(int min);
    }複製程式碼

3.6 通過鏈式呼叫,設定需要的資料

    public AddSubUtils setCurrentNumber(int currentNumber) {
        if (currentNumber < mBuyMin){
            inputValue = mBuyMin;
        } else {
            inputValue = Math.min(Math.min(mBuyMax, inventory), currentNumber);
        }
        etInput.setText(inputValue + "");
        return this;
    }

    public int getInventory() {
        return inventory;
    }

    public AddSubUtils setInventory(int inventory) {
        this.inventory = inventory;
        return this;
    }

    public int getBuyMax() {
        return mBuyMax;
    }

    public AddSubUtils setBuyMax(int buyMax) {
        mBuyMax = buyMax;
        return this;
    }
    public AddSubUtils setBuyMin(int buyMin) {
        mBuyMin = buyMin;
        return this;
    }

    public AddSubUtils setOnWarnListener(OnWarnListener onWarnListener) {
        mOnWarnListener = onWarnListener;
        return this;
    }
    public int getStep() {
        return mStep;
    }

    public AddSubUtils setStep(int step) {
        mStep = step;
        return this;
    }複製程式碼

到這裡你已經封裝完成了,具體demo可以去Github下載

4. 如何使用

step 1. 在app的build.gradle中新增依賴

dependencies {
 compile 'com.mengfei:AddSubUtils:1.0.0'
}複製程式碼

或者下載原始碼包,連結:github.com/Jmengfei/Ad… ,並且在build.gradle中新增:

dependencies {
 compile project(':addsubutils')
}複製程式碼

step 2. 在xml程式碼中使用

    <com.mengfei.AddSubUtils
        android:id="@+id/add_sub"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />複製程式碼

你也可以自定義樣式:

 <com.mengfei.AddSubUtils
        android:id="@+id/add_sub_2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        jmf:editable="true"
        jmf:ImageWidth="60dp"
        jmf:contentTextColor="@color/colorText"
        jmf:contentWidth="120dp"
        jmf:contentTextSize="16sp"
        jmf:contentBackground="@color/material_teal_200"
        jmf:leftBackground="@drawable/left_selector"
        jmf:rightBackground="@drawable/right_selector"
        jmf:leftResources="@drawable/minus"
        jmf:rightResources="@drawable/plus"/>複製程式碼

step3. 在Activity或者Fragment中配置AddSubUtils

        AddSubUtils addSubUtils = (AddSubUtils) findViewById(R.id.add_sub);
        addSubUtils.setBuyMax(30)       // 最大購買數,預設為int的最大值
                .setInventory(50)       // 庫存,預設為int的最大值
                .setCurrentNumber(5)    // 設定當前數,預設為1
                .setStep(5)             // 步長,預設為1
                .setBuyMin(2)           // 購買的最小值,預設為1
                .setOnWarnListener(new AddSubUtils.OnWarnListener() {
                    @Override
                    public void onWarningForInventory(int inventory) {
                        Toast.makeText(MainActivity.this, "當前庫存:" + inventory, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onWarningForBuyMax(int max) {
                        Toast.makeText(MainActivity.this, "超過最大購買數:" + max, Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onWarningForBuyMin(int min) {
                        Toast.makeText(MainActivity.this, "低於最小購買數:" + min, Toast.LENGTH_SHORT).show();
                    }
                });複製程式碼

這裡你只需要傳入你關心的值即可。

5.支援 Attributes屬性(addsubutils佈局檔案中呼叫)

Attributes forma describe
editable boolean 是否可以手動輸入
location string 輸入框的位置(在左邊還是右邊),預設中間
ImageWidth dimension 左右2邊+-按鈕的寬度
contentWidth dimension 中間EditText的寬度
contentTextSize dimension 中間EditText的字型大小
contentTextColor color 中間字型的顏色
all_background color/reference 整個控制元件的background
leftBackground color/reference 左面控制元件的背景
rightBackground color/reference 右面控制元件的背景
contentBackground color/reference 中間控制元件的背景
leftResources color/reference 左面控制元件的資源
rightResources color/reference 右面控制元件的資源

以上就是對商城購物車加減控制元件的一些介紹和一個簡單的封裝。需要原始碼的朋友,請看 Github地址:AddSubUtils,如果覺得對你有用的話,歡迎star。

客官別走
在v1.5.0版本中解決了在ListView中由於item的複用導致資料錯亂的問題:
地址: 商城購物車加減控制元件的簡單封裝(續),解決ListView中資料錯亂的問題 如果你覺得對你有用的話,不妨留下你的足跡。

相關文章