livedata list - 适配器不使用DiffUtil方法更新

时间:2017-10-16 13:47:45

标签: android adapter android-architecture-components

我是Android新手,我正在尝试使用新的架构组件实现条码阅读器方案。

每次读取条形码时,我都想更新ViewModel中的列表,如果条形码不在列表中或者增加数量,则添加新元素。

以下解决方案正在运行,但是为了更新UI,在适配器上调用“notifyDataSetChanged”并不令我满意。这是因为ViewModel列表和适配器内部列表包含对相同对象的引用,因此DiffUtil不会捕获任何更改。

有更好的方法来更新UI吗?除了适配器,我应该考虑处理架构组件的任何改进吗?

视图模型

public class ScannerViewModel extends ViewModel {

    private MutableLiveData<List<ProductScan>> scanListLD;

    public ScannerViewModel() {   
        scanListLD = new MutableLiveData<>();
        scanListLD.setValue(new ArrayList<ProductScan>());
    }

    public LiveData<List<ProductScan>> getScanList() {
        return scanListLD;
    }

    public void addBarcode(String barcode) {
        List<ProductScan> list = scanListLD.getValue();
        ProductScan scan = null;
        for (ProductScan item : list) {
            if (item.barcode.equals(barcode)) {
                scan = item;
                break;
            }
        }
        if (scan == null) {
            scan = new ProductScan();
            scan.barcode = barcode;
            scan.qt = 0;
            list.add(scan);
        }
        scan.qt += 1;

        scanListLD.postValue(list);
    }
}

适配器

public class ScannerAdapter extends RecyclerView.Adapter<ScannerAdapter.ViewHolder> {

    private List<ProductScan> scans;

    public interface OnItemClickListener {
        void onItemClick();
    }

    public ScannerAdapter(List<ProductScan> scans) {
        this.scans = scans;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.template_scan, parent, false);
        ScannerAdapter.ViewHolder viewHolder = new ScannerAdapter.ViewHolder(view);
        return viewHolder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.bindData(scans.get(position), position);
    }

    @Override
    public int getItemCount() {
        return scans != null ? scans.size() : 0;
    }

    //Not used since scans and newScans contain same objects
    public void setScans(final List<ProductScan> newScans) {
        if (scans == null) {
            scans = new ArrayList<ProductScan>();
            scans.addAll(newScans);
            notifyItemRangeInserted(0, newScans.size());
        } else {
            DiffUtil.DiffResult result = DiffUtil.calculateDiff(new DiffUtil.Callback() {
                @Override
                public int getOldListSize() {
                    return scans != null ? scans.size() : 0;
                }

                @Override
                public int getNewListSize() {
                    return newScans != null ? newScans.size() : 0;
                }

                @Override
                public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
                    ProductScan oldScan = scans.get(oldItemPosition);
                    ProductScan newScan = newScans.get(newItemPosition);
                    return Utils.equals(oldScan.barcode,newScan.barcode);
                }

                @Override
                public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
                    ProductScan oldScan = scans.get(oldItemPosition);
                    ProductScan newScan = newScans.get(newItemPosition);
                    return Utils.equals(newScan.barcode, oldScan.barcode)
                            && Utils.equals(newScan.productId, oldScan.productId)
                            && Utils.equals(newScan.productDescription, oldScan.productDescription)
                            && Utils.equals(newScan.qt, oldScan.qt);
                }
            });
            scans.clear();
            scans.addAll(newScans);
            result.dispatchUpdatesTo(this);
        }
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        private TemplateScanBinding binding;

        public ViewHolder(View itemView) {
            super(itemView);
            binding = DataBindingUtil.bind(itemView);
        }

        public void bindData(ProductScan scan, int position) {
            binding.setScan(scan);
            binding.setBtnIncreaseQtListener(() -> {
                scan.qt += 1;
                notifyItemChanged(position);
            });
            binding.setBtnDecreaseQtListener(() -> {
                scan.qt = Math.max(1, scan.qt - 1);
                notifyItemChanged(position);
            });
            binding.edtQt.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                @Override
                public boolean onEditorAction(TextView textView, int i, KeyEvent keyEvent) {
                    if (i == EditorInfo.IME_ACTION_DONE) {
                        binding.edtQt.clearFocus();
                    }
                    return false;
                }
            });
            binding.edtQt.setOnFocusChangeListener(new View.OnFocusChangeListener() {
                @Override
                public void onFocusChange(View view, boolean b) {
                    if (scan.qt == null || scan.qt < 1) {
                        scan.qt = 1;
                        notifyItemChanged(position);
                    }
                    InputMethodManager imm = (InputMethodManager) binding.getRoot().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }
            });
        }
    }
}

活动

public class ScannerActivity extends ScannerBaseActivity {

    @Inject
    ViewModelFactory viewModelFactory;
    private ScannerViewModel viewModel;

    private ActivityScannerBinding binding;
    private ScannerAdapter adapter;

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        ((MyApplication) getApplication()).getComponent().inject(this);
        viewModel = ViewModelProviders.of(this, viewModelFactory).get(ScannerViewModel.class);
        binding = DataBindingUtil.setContentView(this, R.layout.activity_scanner);

        adapter = new ScannerAdapter(viewModel.getScanList().getValue());
        binding.recvScans.setLayoutManager(new LinearLayoutManager(this));
        binding.recvScans.setAdapter(adapter);
        viewModel.getScanList().observe(this, (list) -> {
            adapter.notifyDataSetChanged();
        });

        binding.btnScan.setOnClickListener((v) -> {
             //calls viewModel.addBarcode(String barcode)
              readBarcode(readBarcodeCallback);
        });

    }
}

2 个答案:

答案 0 :(得分:1)

您可以在现有适配器中使用PagedListAdapterHelper,如下所示

class UserAdapter extends RecyclerView.Adapter<UserViewHolder> {
     private final PagedListAdapterHelper<User> mHelper;
     public UserAdapter(PagedListAdapterHelper.Builder<User> builder) {
         mHelper = new PagedListAdapterHelper(this, DIFF_CALLBACK);
     }
     @Override
     public int getItemCount() {
         return mHelper.getItemCount();
     }
     public void setList(PagedList<User> pagedList) {
         mHelper.setList(pagedList);
     }
     @Override
     public void onBindViewHolder(UserViewHolder holder, int position) {
         User user = mHelper.getItem(position);
         if (user != null) {
             holder.bindTo(user);
         } else {
             // Null defines a placeholder item - PagedListAdapterHelper will automatically
             // invalidate this row when the actual object is loaded from the database
             holder.clear();
         }
     }
     public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
          @Override
          public boolean areItemsTheSame(
                  @NonNull User oldUser, @NonNull User newUser) {
              // User properties may have changed if reloaded from the DB, but ID is fixed
              return oldUser.getId() == newUser.getId();
          }
          @Override
          public boolean areContentsTheSame(
                  @NonNull User oldUser, @NonNull User newUser) {
              // NOTE: if you use equals, your object must properly override Object#equals()
              // Incorrectly returning false here will result in too many animations.
              return oldUser.equals(newUser);
          }
      }

OR

用户PageListAdapter

class UserAdapter extends PagedListAdapter<User, UserViewHolder> {
     public UserAdapter() {
         super(DIFF_CALLBACK);
     }
     @Override
     public void onBindViewHolder(UserViewHolder holder, int position) {
         User user = getItem(position);
         if (user != null) {
             holder.bindTo(user);
         } else {
             // Null defines a placeholder item - PagedListAdapter will automatically invalidate
             // this row when the actual object is loaded from the database
             holder.clear();
         }
     }
     public static final DiffCallback<User> DIFF_CALLBACK = new DiffCallback<User>() {
         @Override
         public boolean areItemsTheSame(
                 @NonNull User oldUser, @NonNull User newUser) {
             // User properties may have changed if reloaded from the DB, but ID is fixed
             return oldUser.getId() == newUser.getId();
         }
         @Override
         public boolean areContentsTheSame(
                 @NonNull User oldUser, @NonNull User newUser) {
             // NOTE: if you use equals, your object must properly override Object#equals()
             // Incorrectly returning false here will result in too many animations.
             return oldUser.equals(newUser);
         }
     }

答案 1 :(得分:-1)

尝试在调用适配器之前制作列表的深层副本。那么您可以使用difutil更新回收站。有关深度复制的更多信息,请参考此link