項目中要求實現一個可以選擇類型以及編輯的item,還要可以根據選擇的類型能夠動態的添加、刪除一行。實現的效果下圖:
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還有好多要學習。
下載鏈接:點擊打開鏈接下載資源