项目中要求实现一个可以选择类型以及编辑的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还有好多要学习。