项目中要求实现一个可以选择类型以及编辑的item,还要可以根据选择的类型能够动态的添加、删除一行。实现的效果下图:



Android NestedScrollView中添加recyclerview recyclerview添加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还有好多要学习。