前两年互联网行业火热的时候,大多数移动端的项目以电商为主,而电商中添加商品又是一个不可缺少的环节,前段时间做了一个小型的电商项目,其中有一个添加物品规格的需求,可以看下面的展示图。
nullpoint | Exception |
---|---|
|
|
限于图片上传大小,尺寸略小,并只能展示添加和删除的部分逻辑,感兴趣的同学可以下载项目运行看下效果;
github地址:https://github.com/LzyChonger/CreateGoodsSpec
业务需求分析
业务需求是在界面上输入一个规格名称,下面再出现规格的具体值【举个例子:输入: 颜色 — 红色,黄色等】,并继续在下面展示出此规格的价钱和库存(ps:或者其他的任何参数),规格可以删除,并且对应的价格库存也要删除,点击保存将数据回传到前一个商品添加界面,再次点击进来数据要回显,点击返回按钮将数据清空,最后服务器要求将数据以json的形式传递过去。demo中便于展示,省略了商品添加界面,只做了规格添加,并把数据转换成json传到前一个界面,放在TextView中展示。
技术分析
我的思路首先是想到,需要用两个RecylerView,上面添加商品的一个,下面展示组合的是一个,需要将上面手动输入的数据拿到,通过排列组合去重(技术有限只能想到有遍历的方式,大佬们有优秀的算法烦请指点[手动膜拜大佬]),展示到下面,一些对应删除操作和添加操作需要用到接口回调。
代码展示
限于篇幅这里只展示主要代码逻辑
首先MainActivity.java,没有复杂的东西,都是常规代码,JSON用的是fastjson,布局文件就不贴了,一个Button,一个TextView;
public class MainActivity extends AppCompatActivity {
private static final int SELECT_SPEC = 102;
private Button mBtn;
private TextView mTv_goods_spec;
private List mGuiges;
private List mGood_spec;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mBtn = (Button) findViewById(R.id.btn);
mTv_goods_spec = (TextView) findViewById(R.id.tv_goods_spec);
mBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, GoodsSpecActicity.class);
//做非空判断,回显数据
if (mGood_spec != null) {
intent.putExtra("good_spec", (Serializable) mGood_spec);
}
if(mGuiges !=null){
intent.putExtra("good_guige", (Serializable) mGuiges);
}
startActivityForResult(intent, SELECT_SPEC);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case SELECT_SPEC:
mGood_spec = (List) data.getSerializableExtra("good_spec");
mGuiges = (List) data.getSerializableExtra("good_guige");
if (mGood_spec == null || mGood_spec.size() == 0) {
mTv_goods_spec.setHint("未填写");
} else {
mTv_goods_spec.setText("Guiges:" + JSON.toJSONString(mGuiges)
+ "\nGood_spec:" + JSON.toJSONString(mGood_spec));
}
break;
default:
}
}
}
然后是实体类,实体结构非常重要,所以贴一下代码,偷懒用了Serializable ,项目的话建议用Parcelable
public class StoreManagerListEntity implements Serializable {
public static class SkuListEntity implements Serializable {
/*
"sku_id": "7",
"spec": "绿色:10寸",
"sku_name": "颜色,尺码",
"price": "10.00",
"stock": "0"库存
*/
public String sku_id;
public String spec;
public String sku_name;
public String price;
public String stock;
}
public static class GuigesEntity implements Serializable {
/*
"title": "颜色",
"guigeArray": ["绿色", "红色"]
*/
public String title;
public List guigeArray= new ArrayList<>();
}
}
添加商品规格的主界面,交互了两个RecylerView的数据,布局文件顺便也贴出来,一些资源文件就不贴了。
public class GoodsSpecActicity extends AppCompatActivity implements View.OnClickListener {
private RelativeLayout mRlBack;
private TextView mTvTitle;
private TextView mTvRight;
private RelativeLayout mRlRight;
private EditText mEtName;
private TextView mTvAdd;
private View mView01;
private RecyclerView mRvSpec;
private RecyclerView mRvPrice;
private List mSpecNameList = new ArrayList<>();
private List mSpecPriceList = new ArrayList<>();
private GoodsSpecTypeAdapter mSpecTypeAdapter;
private GoodsSpecTypeNumberAdapter mNumberAdapter;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goods_spec);
initView();
}
private void initView() {
mRlBack = (RelativeLayout) findViewById(R.id.rl_back);
mTvTitle = (TextView) findViewById(R.id.tv_title);
mTvRight = (TextView) findViewById(R.id.tv_right);
mRlRight = (RelativeLayout) findViewById(R.id.rl_right);
mEtName = (EditText) findViewById(R.id.et_name);
mTvAdd = (TextView) findViewById(R.id.tv_add);
mView01 = (View) findViewById(R.id.view01);
mRvSpec = (RecyclerView) findViewById(R.id.rv_spec);
mRvPrice = (RecyclerView) findViewById(R.id.rv_price);
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
mRvSpec.setLayoutManager(layoutManager);
mRvSpec.setHasFixedSize(true);
LinearLayoutManager layoutManager1 = new LinearLayoutManager(this);
mRvPrice.setLayoutManager(layoutManager1);
mRvPrice.setHasFixedSize(true);
mRlBack.setOnClickListener(this);
mTvAdd.setOnClickListener(this);
mRlRight.setOnClickListener(this);
mTvTitle.setText("商品规格");
mRlRight.setVisibility(View.VISIBLE);
mTvRight.setText("完成");
mSpecTypeAdapter = new GoodsSpecTypeAdapter(this, mSpecNameList);
mRvSpec.setAdapter(mSpecTypeAdapter);
mNumberAdapter = new GoodsSpecTypeNumberAdapter(this, mSpecPriceList);
mRvPrice.setAdapter(mNumberAdapter);
mRvPrice.setNestedScrollingEnabled(false);
mSpecTypeAdapter.setAddItem(new RecylerViewAddItemListener() {
@Override
public void onAddItemListener(List entity, int position) {
mSpecPriceList.clear();
String sku_name = "";
for (int i = 0; i < mSpecNameList.size(); i++) {
if (i < mSpecNameList.size() - 1) {
sku_name = sku_name + mSpecNameList.get(i).title + ",";
} else {
sku_name = sku_name + mSpecNameList.get(i).title;
}
}
if (entity != null) {
for (int i = 0; i < entity.size(); i++) {
StoreManagerListEntity.SkuListEntity serverEntity = new StoreManagerListEntity.SkuListEntity();
serverEntity.spec = entity.get(i);
serverEntity.sku_name = sku_name;
mSpecPriceList.add(serverEntity);
}
}
mNumberAdapter.notifyDataSetChanged();
}
});
List good_guige = (List) getIntent().getSerializableExtra("good_guige");
if (good_guige != null) {
mSpecNameList.addAll(good_guige);
}
List good_spec = (List) getIntent().getSerializableExtra("good_spec");
if (good_spec != null) {
mSpecPriceList.addAll(good_spec);
}
}
private void toComplete() {
for (int i = 0; i < mSpecPriceList.size(); i++) {
if (TextUtils.isEmpty(mSpecPriceList.get(i).price)) {
UIUtil.toastShort(this, "请输入价格");
return;
}
if (TextUtils.isEmpty(mSpecPriceList.get(i).stock)) {
UIUtil.toastShort(this, "请输入库存");
return;
}
}
Intent intent = new Intent();
intent.putExtra("good_spec", (Serializable) mSpecPriceList);
intent.putExtra("good_guige", (Serializable) mSpecNameList);
setResult(RESULT_OK, intent);
finish();
}
private void addList() {
String name = mEtName.getText().toString().trim();
if (!TextUtils.isEmpty(name)) {
StoreManagerListEntity.GuigesEntity entity = new StoreManagerListEntity.GuigesEntity();
entity.title = name;
mSpecNameList.add(entity);
mSpecTypeAdapter.notifyDataSetChanged();
mEtName.setText("");
((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).hideSoftInputFromWindow(
this.getCurrentFocus().getWindowToken(),
InputMethodManager.HIDE_NOT_ALWAYS);
} else {
UIUtil.toastShort(this, "请输入规格名称");
}
}
public void saveEditData(int position, String type, String str) {
try {
if ("1".equals(type)) {
mSpecPriceList.get((position - 1) / 100).price = str;
} else if ("2".equals(type)) {
mSpecPriceList.get((position - 2) / 100).stock = str;
}
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.rl_back:
UIUtil.showConfirm(this, "返回将清空数据,是否确定?", new View.OnClickListener() {
@Override
public void onClick(View v) {
mSpecPriceList.clear();
mSpecNameList.clear();
Intent intent = new Intent();
intent.putExtra("good_spec", (Serializable) mSpecPriceList);
intent.putExtra("good_guige", (Serializable) mSpecNameList);
setResult(RESULT_OK, intent);
finish();
}
});
break;
case R.id.tv_add:
addList();
break;
case R.id.rl_right:
toComplete();
break;
default:
break;
}
}
}
后面重头戏来了,首先是上面的RecylerView的适配器
public class GoodsSpecTypeAdapter extends BaseAdapter {
private RecylerViewAddItemListener mAddItemListener;
public GoodsSpecTypeInfoAdapter mInfoAdapter;
public GoodsSpecTypeAdapter(Context context, List list) {
super(context, list);
}
@Override
public GoodsSpecTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new GoodsSpecTypeViewHolder(mInflater.inflate(R.layout.item_goods_spec_type, null));
}
@Override
public void onBindViewHolder(final GoodsSpecTypeViewHolder holder, final int position) {
final StoreManagerListEntity.GuigesEntity item = getItem(position);
holder.mTv_type_name.setText(item.title);
mInfoAdapter = new GoodsSpecTypeInfoAdapter(mContext, item.guigeArray, position);
holder.mRv_spec_size.setAdapter(mInfoAdapter);
mInfoAdapter.setOnItemDeleteListener(new ImageRecylerReduceItemListener() {
@Override
public void onReduceItemListener(int position) {
List list = bindAnotherRecyler1();
mAddItemListener.onAddItemListener(list, position);
}
});
holder.mEt_size.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
String size = holder.mEt_size.getText().toString().trim();
if (!TextUtils.isEmpty(size)) {
mInfoAdapter = new GoodsSpecTypeInfoAdapter(mContext, item.guigeArray /*mTypeList*/, position);
item.guigeArray.add(size);
List list = bindAnotherRecyler1();
holder.mRv_spec_size.setAdapter(mInfoAdapter);
mAddItemListener.onAddItemListener(list, position);
holder.mEt_size.setText("");
mInfoAdapter.setOnItemDeleteListener(new ImageRecylerReduceItemListener() {
@Override
public void onReduceItemListener(int position) {
List list = bindAnotherRecyler1();
mAddItemListener.onAddItemListener(list, position);
}
});
} else {
UIUtil.toastShort(mContext, "请输入规格参数");
}
}
return false;
}
});
}
private List bindAnotherRecyler1() {
int b = 0;
if (mItems.size() > 0) {
List copylist = new ArrayList<>();
for (int a = 0; a < mItems.size(); a++) {
if (mItems.get(a).guigeArray.size() != 0) {
copylist.addAll(mItems.get(a).guigeArray);
b = a;
break;
}
}
if (copylist.size() > 0) {
List L0 = new ArrayList<>();
L0.addAll(copylist);
for (int i = b + 1; i < mItems.size(); i++) {
List L1 = mItems.get(i).guigeArray;
List list = new ArrayList<>();
for (int j = 0; j < L0.size(); j++) {
for (int z = 0; z < L1.size(); z++) {
String s = L0.get(j) + ":" + L1.get(z);
list.add(s);
}
}
if (list.size() != 0) {
L0 = list;
}
}
return L0;
}
}
return null;
}
class GoodsSpecTypeViewHolder extends RecyclerView.ViewHolder {
private final RecyclerView mRv_spec_size;
private final EditText mEt_size;
private final TextView mTv_type_name;
public GoodsSpecTypeViewHolder(View itemView) {
super(itemView);
mTv_type_name = itemView.findViewById(R.id.tv_type_name);
mEt_size = itemView.findViewById(R.id.et_size);
mRv_spec_size = itemView.findViewById(R.id.rv_spec_size);
GridLayoutManager gridLayoutManager = new GridLayoutManager(mContext, 3);
gridLayoutManager.setOrientation(LinearLayout.VERTICAL);
mRv_spec_size.setLayoutManager(gridLayoutManager);
mRv_spec_size.setHasFixedSize(true);
}
}
public void setAddItem(RecylerViewAddItemListener addItemListener) {
mAddItemListener = addItemListener;
}
重点就在于bindAnotherRecyler1()这个方法,在数据输入后,通过EditText的监听或者点击事件来组装数据,因为在这个适配器中,是数据的来源,所以在这里拿到数据并组装数据,再传到另一个适配器中可以直接显示,首先做一个判断增加程序的健壮性,第一次遍历中,新建一个copylist,遍历此适配器的规格数组,同事不影响适配器的数据展示;在用copylist的数据进行双重循环遍历组装数据,并做一些空数据处理,":"是为了后台存储数据做的规则。
private List bindAnotherRecyler1() {
int b = 0;
if (mItems.size() > 0) {
List copylist = new ArrayList<>();
for (int a = 0; a < mItems.size(); a++) {
if (mItems.get(a).guigeArray.size() != 0) {
copylist.addAll(mItems.get(a).guigeArray);
b = a;
break;
}
}
if (copylist.size() > 0) {
List L0 = new ArrayList<>();
L0.addAll(copylist);
for (int i = b + 1; i < mItems.size(); i++) {
List L1 = mItems.get(i).guigeArray;
List list = new ArrayList<>();
for (int j = 0; j < L0.size(); j++) {
for (int z = 0; z < L1.size(); z++) {
String s = L0.get(j) + ":" + L1.get(z);
list.add(s);
}
}
if (list.size() != 0) {
L0 = list;
}
}
return L0;
}
}
return null;
}
在这个适配器里面嵌套了一个新的适配器,是便于规格删除操作和展示用的,以前遇到这种问题都是用这种方式来写~~~(>_<)~~~,如果众大佬有更好的方式,希望能帮我指出来去改正。
public class GoodsSpecTypeInfoAdapter extends RecyclerView.Adapter {
private final Context mContext;
private final List getItem;
protected LayoutInflater mInflater;
private int mPosition;
private ImageRecylerReduceItemListener mItemDeleteListener;
public GoodsSpecTypeInfoAdapter(Context context,List list, int position) {
mContext = context;
getItem = list;
mInflater = LayoutInflater.from(context);
mPosition = position;
}
@Override
public GoodsSpecTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new GoodsSpecTypeViewHolder(mInflater.inflate(R.layout.item_goods_spec_info, parent, false));
}
@Override
public void onBindViewHolder(GoodsSpecTypeViewHolder holder, final int position) {
holder.mRl_del.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
getItem.remove(position);
notifyDataSetChanged();
mItemDeleteListener.onReduceItemListener(position);
}
});
String item = getItem.get(position);
if (!TextUtils.isEmpty(item)) {
holder.mTv_size.setVisibility(View.VISIBLE);
holder.mTv_size.setText(item);
} else {
holder.mTv_size.setVisibility(View.GONE);
}
}
@Override
public int getItemCount() {
try {
if (getItem != null && getItem.size() > 0) {
return getItem.size();
} else {
return 0;
}
} catch (Exception e) {
e.printStackTrace();
return 0;
}
}
class GoodsSpecTypeViewHolder extends RecyclerView.ViewHolder {
private final TextView mTv_size;
private final RelativeLayout mRl_del;
private final ImageView mIv_del;
public GoodsSpecTypeViewHolder(View itemView) {
super(itemView);
mTv_size = itemView.findViewById(R.id.tv_size);
mRl_del = itemView.findViewById(R.id.rl_del);
mIv_del = itemView.findViewById(R.id.iv_del);
}
}
public void setOnItemDeleteListener(ImageRecylerReduceItemListener itemDeleteListener) {
mItemDeleteListener = itemDeleteListener;
}
}
然后就是主界面第二个RecylerView的适配器了,主要记录了输入的数据并保证数据可以正确的携带回去
public class GoodsSpecTypeNumberAdapter extends BaseAdapter {
public GoodsSpecTypeNumberAdapter(Context context, List list) {
super(context, list);
}
@Override
public GoodsSpecTypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return new GoodsSpecTypeViewHolder(mInflater.inflate(R.layout.item_goods_spec_number, parent, false));
}
@Override
public void onBindViewHolder(GoodsSpecTypeViewHolder holder, int position) {
StoreManagerListEntity.SkuListEntity item = getItem(position);
holder.mEt_number_price.setTag(position * 100 + 1);
holder.mEt_number_content.setTag(position * 100 + 2);
holder.mTv_number_name.setText(item.spec);
if (!TextUtils.isEmpty(item.stock)) {
holder.mEt_number_content.setText(item.stock);
}
if (!TextUtils.isEmpty(item.price)) {
holder.mEt_number_price.setText(item.price);
}
holder.mEt_number_price.addTextChangedListener(new TextSwitcher(holder, "1"));
holder.mEt_number_content.addTextChangedListener(new TextSwitcher(holder, "2"));
}
class GoodsSpecTypeViewHolder extends RecyclerView.ViewHolder {
private final TextView mTv_number_name;
private final EditText mEt_number_price;
private final EditText mEt_number_content;
GoodsSpecTypeViewHolder(View itemView) {
super(itemView);
mTv_number_name = itemView.findViewById(R.id.tv_number_name);
mEt_number_price = itemView.findViewById(R.id.et_number_price);
mEt_number_content = itemView.findViewById(R.id.et_number_content);
}
}
class TextSwitcher implements TextWatcher {
private GoodsSpecTypeViewHolder mHolder;
private String mType;
TextSwitcher(GoodsSpecTypeViewHolder mHolder, String type) {
this.mHolder = mHolder;
mType = type;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable s) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if ("1".equals(mType)) {
int position = (Integer) mHolder.mEt_number_price.getTag();
((GoodsSpecActicity) mContext).saveEditData(position, mType, s.toString());
} else if ("2".equals(mType)) {
int position = (Integer) mHolder.mEt_number_content.getTag();
((GoodsSpecActicity) mContext).saveEditData(position, mType, s.toString());
}
}
}
}
最后贴出来两个挂件,自定义两个接口,主要为了讲清楚思路和逻辑,工具类和基类就不贴在这里了,有兴趣的同学可以下载项目看下。
public interface ImageRecylerReduceItemListener {
void onReduceItemListener(int position);
}
public interface RecylerViewAddItemListener {
void onAddItemListener(List entity, int position);
}
文章就到这里,欢迎指出错误或者修改优化意见。