recycleView中實現item動態添加、刪除以及item中嵌套editText

項目中要求實現一個可以選擇類型以及編輯的item,還要可以根據選擇的類型能夠動態的添加、刪除一行。實現的效果下圖:



大體要求如下:點擊左側下拉菜單彈出一系列選項可以選擇類別、點擊右側editText可以編輯金額、點擊圓形+號動態添加一行(-號刪除一行)。

從這個代碼的實現意識到兩點:1、當你喜歡用別人封裝好的框架時,別人的框架比如自己寫的adapter有很多好處,可以讓代碼簡練等但同時也限制了你自己的思考,是呢,你總是想方設法的往框架裏面套用,不去思考是否真的適合,所以在實現這個效果我試了不少的框架都不能正常的實現,最後只有逼着自己去用最基本的、一步步的去實現效果;2、不能總copy啊,還是要真正的去理解,多看看源碼,加油。  

代碼解析如下:
public class MainActivity extends AppCompatActivity implements CallBackListener, View.OnClickListener {
    private RecyclerView rvPayMoney;
    List<PayMoneyReasonItem> payList = new ArrayList<>();
    private WriteOnePayMoneyItemAdapter writeOnePayMoneyItemAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        rvPayMoney = (RecyclerView) findViewById(R.id.rv_pay_money);
        findViewById(R.id.act_save).setOnClickListener(this);
        initPayMoneyRv();
    }
    /**
     * 初始化recycleView
     */
    private void initPayMoneyRv() {
        PayMoneyReasonItem payMoneyReasonItem = new PayMoneyReasonItem();
        payList.add(payMoneyReasonItem);
        writeOnePayMoneyItemAdapter = new WriteOnePayMoneyItemAdapter(MainActivity.this, payList, this);
        //2.排列方式
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        rvPayMoney.setLayoutManager(linearLayoutManager);
        rvPayMoney.setAdapter(writeOnePayMoneyItemAdapter);
    }
    @Override
    public void CallBack(int code, Object object) {
        switch (code) {
            case Constants.CallBack.PAY_ADD_ITEM:
                int position = (int) object;
                PayMoneyReasonItem payMoneyReasonItem = new PayMoneyReasonItem();
                payMoneyReasonItem.setIsAdded(true);
                payList.add(payMoneyReasonItem);
                writeOnePayMoneyItemAdapter.notifyItemInserted(position);
                break;
            case Constants.CallBack.PAY_DETELE_ITEM:
                position = (int) object;
                //爲什麼position的值不變呢 一直都是默認的最後一位 那對於list來說remove的時候肯定數組越界啊
                //writeOnePayMoneyItemAdapter.notifyItemRemoved(position);
                payList.remove(position);
                writeOnePayMoneyItemAdapter.notifyItemRemoved(position);
                break;
        }
    }
    /**
     * 點擊保存時 對應的操作 看具體需求組織自己的數據格式
     * 1.上傳數據到後臺  2.通過sqllite或者sp保存到本地
     *
     * @param v
     */
    @Override
    public void onClick(View v) {
        StringBuilder sbStr = new StringBuilder();
        if (payList != null && payList.size() != 0) {
            for (PayMoneyReasonItem payMoneyReasonItem : payList) {
                sbStr.append("+++" + "保存的類型:" + payMoneyReasonItem.getName() + " 對應的金額" + payMoneyReasonItem.getMoney())
                        .append("\n");
            }
        }
        Toast.makeText(MainActivity.this, sbStr.toString(), Toast.LENGTH_LONG).show();
    }
}

佈局文件:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.yezhu.rvabout.MainActivity">
    <!--標題-->
    <RelativeLayout style="@style/act_titles">
        <RelativeLayout
            android:id="@+id/rl_back"
            android:layout_width="50dp"
            android:layout_height="match_parent">
            <ImageView
                style="@style/act_title_left"
                android:layout_width="40dp"
                android:layout_height="40dp"
                android:padding="10dp"
                android:src="@mipmap/back"
                android:visibility="gone" />
        </RelativeLayout>
        <TextView
            style="@style/act_title_center"
            android:text="測試" />
        <TextView
            android:id="@+id/act_save"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="15sp"
            android:text="保存"
            android:visibility="visible" />
    </RelativeLayout>
    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_pay_money"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:nestedScrollingEnabled="false" />
    <TextView
        android:id="@+id/tv_show"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

回調函數類:

public interface CallBackListener {
    void CallBack(int code, Object object);
}
適配器adapter以及ViewHolder:
public class WriteOnePayMoneyItemAdapter extends RecyclerView.Adapter<WriteOnePayMoneyItemAdapter.PayMoneyHolder> {
    private final List<PayMoneyReasonItem> mPayList;
    private final Context mContext;
    private final CallBackListener mCallBackListener;//點擊加減時回調
    private final List<String> spItems;
    private PayMoneyReasonItem payMoneyReasonItem;
    private int position;
    public WriteOnePayMoneyItemAdapter(Context context, List<PayMoneyReasonItem> payList, CallBackListener callBackListener) {
        this.mContext = context;
        this.mPayList = payList;
        this.mCallBackListener = callBackListener;
        spItems = new ArrayList<>();
        spItems.add("定金支付金額");
        spItems.add("尾款支付金額");
        spItems.add("全額支付");
    }
    @Override
    public PayMoneyHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(mContext).inflate(R.layout.pay_money_item, parent, false);
        return new PayMoneyHolder(view);
    }
    @Override
    public void onBindViewHolder(final PayMoneyHolder holder, final int position) {
        this.position = position;
        payMoneyReasonItem = mPayList.get(position);
        if (payMoneyReasonItem.getIsAdded()) {
            holder.addImageView.setVisibility(View.GONE);
            holder.deleteImageView.setVisibility(View.VISIBLE);
        } else {
            holder.addImageView.setVisibility(View.VISIBLE);
            holder.deleteImageView.setVisibility(View.GONE);
        }
        int location = spItems.indexOf(payMoneyReasonItem.getName());
        holder.spChooseReason.setSelection(location);
        if (payMoneyReasonItem.getMoney() != 0) {
            holder.etPayMoney.setText(payMoneyReasonItem.getMoney() + "");
        }
        holder.tvPayReason.setText(payMoneyReasonItem.getName());
    }
    @Override
    public int getItemCount() {
        return mPayList.size();
    }
    public class PayMoneyHolder extends RecyclerView.ViewHolder implements View.OnClickListener, AdapterView.OnItemSelectedListener {
        private final ImageView addImageView;
        private final ImageView noAddImageView;
        private final ImageView deleteImageView;
        private ArrayAdapter<String> stringArrayAdapter;
        Spinner spChooseReason;
        TextView tvPayReason;
        EditText etPayMoney;
        public PayMoneyHolder(View itemView) {
            super(itemView);
            itemView.setOnClickListener(this);
            spChooseReason = (Spinner) itemView.findViewById(R.id.sp_choose_reason);
            tvPayReason = (TextView) itemView.findViewById(R.id.tv_pay_reason);
            etPayMoney = (EditText) itemView.findViewById(R.id.et_pay_money);
            addImageView = (ImageView) itemView.findViewById(R.id.iv_add_pay);
            noAddImageView = (ImageView) itemView.findViewById(R.id.iv_add_pay_no);
            deleteImageView = (ImageView) itemView.findViewById(R.id.iv_delete_pay);
            addImageView.setOnClickListener(this);
            deleteImageView.setOnClickListener(this);
            stringArrayAdapter = new ArrayAdapter<>(mContext, R.layout.my_spinner, spItems);
            stringArrayAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
            spChooseReason.setAdapter(stringArrayAdapter);
            spChooseReason.setOnItemSelectedListener(this);
            //默認是定金支付金額
//            int tempLocation = spItems.indexOf("全額支付");
            spChooseReason.setSelection(2);
            etPayMoney.addTextChangedListener(new TextWatcher() {
                @Override
                public void beforeTextChanged(CharSequence s, int start, int count, int after) {
                }
                @Override
                public void onTextChanged(CharSequence s, int start, int before, int count) {
                }
                @Override
                public void afterTextChanged(Editable s) {
                    String s1 = s.toString();
                    try {
                        Double money = Double.parseDouble(s1);
                        mPayList.get(getLayoutPosition()).setMoney(money);//調用getLayoutPosition()方法
                    } catch (Exception e) {
                        if (!s.toString().equals("輸入金額") && !s.toString().equals("")) {
                            Toast.makeText(mContext, "請不要輸入文字", Toast.LENGTH_SHORT).show();
                        }
                        return;
                    }
                }
            });
        }
        @Override
        public void onClick(View v) {
            int id = v.getId();
            switch (id) {
                case R.id.iv_add_pay:
                    mCallBackListener.CallBack(Constants.CallBack.PAY_ADD_ITEM, position + 1);
                    break;
                case R.id.iv_delete_pay:
                    mCallBackListener.CallBack(Constants.CallBack.PAY_DETELE_ITEM, getLayoutPosition());
                    break;
            }
        }
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            String str = spItems.get(position);
            mPayList.get(getLayoutPosition()).setName(str);
        }
        @Override
        public void onNothingSelected(AdapterView<?> parent) {
        }
    }
}

adapter中的item佈局:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="41dp"
    android:orientation="vertical">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:orientation="horizontal">
        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:orientation="horizontal">
            <TextView
                android:id="@+id/tv_pay_reason"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginLeft="14dp"
                android:text="定金支付金額"
                android:textSize="14sp"
                android:visibility="gone" />
            <Spinner
                android:id="@+id/sp_choose_reason"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center" />
        </LinearLayout>
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="right"
            android:orientation="horizontal">
            <EditText
                android:id="@+id/et_pay_money"
                android:layout_width="130dp"
                android:layout_height="wrap_content"
                android:layout_gravity="center|right"
                android:layout_marginRight="8dp"
                android:background="@null"
                android:gravity="right"
                android:hint="輸入金額"
                android:inputType="number"
                android:textSize="14sp" />
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="center"
                android:layout_marginRight="10dp"
                android:text="元"
                android:textSize="14sp" />
            <ImageView
                android:id="@+id/iv_add_pay"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_gravity="center"
                android:layout_marginRight="15dp"
                android:src="@drawable/add_pay" />
            <ImageView
                android:id="@+id/iv_add_pay_no"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_gravity="center"
                android:layout_marginRight="15dp"
                android:src="@drawable/add_pay_no"
                android:visibility="gone" />
            <ImageView
                android:id="@+id/iv_delete_pay"
                android:layout_width="20dp"
                android:layout_height="20dp"
                android:layout_gravity="center"
                android:layout_marginRight="15dp"
                android:src="@drawable/delete_pay"
                android:visibility="gone" />
        </LinearLayout>
    </LinearLayout>
    <View style="@style/act_vip_line" />
</LinearLayout>

spinner佈局文件:

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@android:id/text1"
    style="@style/act_left_tv"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="left"
    android:paddingLeft="14dp"
    android:singleLine="true" />

model類:isAdded 字段是 點擊添加一行item時標定此item是否可刪除

public class PayMoneyReasonItem {
    private Long id;
    /**
     * 自定義remarkId用於和AccountDetailModel對應
     */
    private Long remarkId;
    public String name = "";
    public double money;
    public boolean isAdded = false; //是不是點擊添加的 添加的可以刪除
    public boolean getIsAdded() {
        return isAdded;
    }
    public PayMoneyReasonItem() {
    }
    public PayMoneyReasonItem(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public double getMoney() {
        return money;
    }
    public void setMoney(double money) {
        this.money = money;
    }
    public Long getRemarkId() {
        return this.remarkId;
    }
    public void setRemarkId(Long remarkId) {
        this.remarkId = remarkId;
    }
    public void setIsAdded(boolean isAdded) {
        this.isAdded = isAdded;
    }
    public Long getId() {
        return this.id;
    }
    public void setId(Long id) {
        this.id = id;
    }
}

以上代碼就能實現效果了。

有一個細節點這次使用體會比較深,即ViewHolder中的方法getLayoutPosition();以下源碼列舉了常用方法:

  public static abstract class ViewHolder {
        public final View itemView;
        WeakReference<RecyclerView> mNestedRecyclerView;
        int mPosition = NO_POSITION;
        int mOldPosition = NO_POSITION;
        long mItemId = NO_ID;
        int mItemViewType = INVALID_TYPE;
        int mPreLayoutPosition = NO_POSITION;
      
       ..................很多方法 省略不計................
        public ViewHolder(View itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }
      
        ................以下是ViewHolder中常用的幾個方法...............
        /**
         * @deprecated This method is deprecated because its meaning is ambiguous due to the async
         * handling of adapter updates. Please use {@link #getLayoutPosition()} or
         * {@link #getAdapterPosition()} depending on your use case.
         *
         * @see #getLayoutPosition()
         * @see #getAdapterPosition()
         */
        @Deprecated
        public final int getPosition() {
            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
        }
        /**
         * Returns the position of the ViewHolder in terms of the latest layout pass.
         * <p>
         * This position is mostly used by RecyclerView components to be consistent while
         * RecyclerView lazily processes adapter updates.
         * <p>
         * For performance and animation reasons, RecyclerView batches all adapter updates until the
         * next layout pass. This may cause mismatches between the Adapter position of the item and
         * the position it had in the latest layout calculations.
         * <p>
         * LayoutManagers should always call this method while doing calculations based on item
         * positions. All methods in {@link RecyclerView.LayoutManager}, {@link RecyclerView.State},
         * {@link RecyclerView.Recycler} that receive a position expect it to be the layout position
         * of the item.
         * <p>
         * If LayoutManager needs to call an external method that requires the adapter position of
         * the item, it can use {@link #getAdapterPosition()} or
         * {@link RecyclerView.Recycler#convertPreLayoutPositionToPostLayout(int)}.
         *
         * @return Returns the adapter position of the ViewHolder in the latest layout pass.
         * @see #getAdapterPosition()
         */
        public final int getLayoutPosition() {
            return mPreLayoutPosition == NO_POSITION ? mPosition : mPreLayoutPosition;
        }
        /**
         * Returns the Adapter position of the item represented by this ViewHolder.
         * <p>
         * Note that this might be different than the {@link #getLayoutPosition()} if there are
         * pending adapter updates but a new layout pass has not happened yet.
         * <p>
         * RecyclerView does not handle any adapter updates until the next layout traversal. This
         * may create temporary inconsistencies between what user sees on the screen and what
         * adapter contents have. This inconsistency is not important since it will be less than
         * 16ms but it might be a problem if you want to use ViewHolder position to access the
         * adapter. Sometimes, you may need to get the exact adapter position to do
         * some actions in response to user events. In that case, you should use this method which
         * will calculate the Adapter position of the ViewHolder.
         * <p>
         * Note that if you've called {@link RecyclerView.Adapter#notifyDataSetChanged()}, until the
         * next layout pass, the return value of this method will be {@link #NO_POSITION}.
         *
         * @return The adapter position of the item if it still exists in the adapter.
         * {@link RecyclerView#NO_POSITION} if item has been removed from the adapter,
         * {@link RecyclerView.Adapter#notifyDataSetChanged()} has been called after the last
         * layout pass or the ViewHolder has already been recycled.
         */
        public final int getAdapterPosition() {
            if (mOwnerRecyclerView == null) {
                return NO_POSITION;
            }
            return mOwnerRecyclerView.getAdapterPositionFor(this);
        }
        /**
         * When LayoutManager supports animations, RecyclerView tracks 3 positions for ViewHolders
         * to perform animations.
         * <p>
         * If a ViewHolder was laid out in the previous onLayout call, old position will keep its
         * adapter index in the previous layout.
         *
         * @return The previous adapter index of the Item represented by this ViewHolder or
         * {@link #NO_POSITION} if old position does not exists or cleared (pre-layout is
         * complete).
         */
        public final int getOldPosition() {
            return mOldPosition;
        }
        /**
         * Returns The itemId represented by this ViewHolder.
         *
         * @return The item's id if adapter has stable ids, {@link RecyclerView#NO_ID}
         * otherwise
         */
        public final long getItemId() {
            return mItemId;
        }
        /**
         * @return The view type of this ViewHolder.
         */
        public final int getItemViewType() {
            return mItemViewType;
        }
    }

        這裏有問題值得思考:首先,通過ViewHolder的構造中我們可以拿到要填充的itemview佈局,通過itemview我們可以拿到所有的子控件,也就是說在item的子控件findViewById 過程交給了ViewHolder的構造方法(其他方法也可以),它的本質是在onCreateViewHolder方法裏生成ViewHolder的時候執行的;其次,常規來講我們拿到子控件findViewById 之後就可以設置其listener事件,比如etPayMoney.addTextChangedListener(this)或者btn.setOnClickListener(this)等等這裏有一個小細節,因爲在onBindViewHolder中的holder.etPayMoney.addTextChangedListener(this)同樣可以操作,這種事件聲明寫在哪裏好,onBindViewHolder還是ViewHolder的構造函數中,以前模模糊糊現在比較明確了,緣由是在編輯的時候要獲取當前編輯的model,通過dataList.get(索引)方法,這裏“索引”方法是getLayoutPosition() 能通過點擊準確得獲取當前的位置索引,而上面的源碼所示getLayoutPosition() 是ViewHolder 的方法,可以直接調用

        打開recycleView類一看源碼1萬多行的代碼,各式各樣的方法,仰慕研發開發人員的同時也時刻提醒自己應該多看看源碼,好多東西都可以自己去潛心學習然後實現,三方庫不是萬能的,希望能早點去除這樣的依賴心理。關於recycleView還有好多要學習。


下載鏈接:點擊打開鏈接下載資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章