Android:如何复制RecyclerView列表以供ListAdapter使用?

问题描述 投票:0回答:1

如何复制现有的 ListAdapter 列表,以便可以使用它来移动项目,然后通过将新复制的列表与原始列表进行比较来更新 UI?

也许Android开发人员在设计用于RecyclerView列表的ListAdapter的源代码时只想到了CRUD操作。如果是这样,他们就忽略了其他重要的 recyclerView 操作,例如过滤、排序和移动列表项。这些操作唯一改变的是排序顺序(排序和移动项目)或从原始列表中删除一些项目(用于过滤),但这现在需要 CustomAdapter?此外,对于非常大的 RecyclerView 列表,需要将列表复制到内存中,以便 ListAdapter“看到”新列表,这似乎违背了 ListAdapter 在后台线程上提供效率提升的最初目的。

我欣然接受将现有项目转换为 ListAdapter,因为我认为后台线程的更新对于稳定性和 UI 效率非常有价值。我的 CRUD 操作运行良好,但现在艰巨的任务是弄清楚如何进行其他过滤、排序和移动列表项。

我尝试移动 RecyclerView 列表中的项目,但拖放不起作用。以下是我迄今为止尝试过的不同方法的各种组合。与此同时,我阅读了 stackoverflow 上的所有其他 ListAdapter 问题...不幸的是我对 Kotlin 的了解为零,所以我正在寻找 Java 解决方案。我在这里缺少什么?

MainActivity 方法#1:

ItemTouchHelper.SimpleCallback itemTouchHelperCallback1 = new ItemTouchHelper.SimpleCallback(
   (ItemTouchHelper.UP | ItemTouchHelper.DOWN),0) {

    int dragFrom = -1;
    int dragTo = -1;    

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {

        int fromPos = viewHolder.getBindingAdapterPosition();
        int toPos = target.getBindingAdapterPosition();

        if (dragFrom == -1) {
            dragFrom = fromPos;
        }
        dragTo = toPos;

        cardsAdapter.moveItem(fromPos, toPos);                        
        return true;
    }  
};        
new ItemTouchHelper(itemTouchHelperCallback1).attachToRecyclerView(recyclerView); 

MainActivity 方法#2,针对 onMove() 方法:

List newList = adapter.getCurrentList();
Quickcard quickcardItem = newList.get(fromPos);
newList.remove(fromPos);
newList.add(toPos, quickcardItem);
cardsAdapter.moveItem(fromPos, toPos);
cardsAdapter.notifyItemMoved(fromPos, toPos);
cardsAdapter.submitList(newList);
return true;

ListAdapter 方法#1:

public class CardsAdapter extends ListAdapter<Quickcard, CardsAdapter.ItemHolder> {

    private final LayoutInflater layoutInflater;

    protected CardsAdapter(@NonNull LayoutInflater layoutInflater, DiffUtil.ItemCallback<Quickcard> diffCallback) {
        super(diffCallback);
        this.layoutInflater = layoutInflater;
    }

    ... // ViewHolder code 
     
    public void moveItem(int fromOldPos, int toNewPos) {

        // Copy the ListAdapter's existing list
        ArrayList<Quickcard> currList = new ArrayList<>(getCurrentList()); 
    
        if (fromOldPos == toNewPos) {
            return;
        }
        Collections.swap(currList, toNewPos, fromOldPos);
        notifyItemMoved(fromOldPos, toNewPos);
        submitList(currList);
    }     
}

ListAdapter 方法#2,用于 moveItem() 方法:

List<Quickcard> currList = getCurrentList();
List<Quickcard> copiedList = new ArrayList<>(currList);

if (fromOldPos == toNewPos) {
    return;
}
if (fromOldPos < toNewPos) { // the q. is being moved down the q.list
    for (int i = fromOldPos; i < toNewPos; i++) {
    Collections.swap(copiedList, i,i + 1);
    }
} else { // the q. is being moved up the q.list
    for (int i = fromOldPos; i > toNewPos; i--) {
        Collections.swap(copiedList, i,i - 1);
    }
}
notifyItemMoved(fromOldPos, toNewPos);
submitList(copiedList);
android android-recyclerview listadapter
1个回答
0
投票

不幸的是,

ListAdapter
本身在拖动/滑动操作方面存在问题,但
submitList
方法的大多数优秀功能都可以在常规适配器中使用
DiffUtil
方法来复制。您可以通过在其他地方(例如在 ViewModel 中)过滤列表并使用
submitList
发布它来处理过滤。通过这样做,您可以访问显示的列表,并可以轻松修改它以进行拖动/滑动事件,同时在发布新列表或重新排序的列表时仍然获得
ListAdapter
的一些好处。

这里有一个示例,说明如何执行此操作,并通过使用

submitList
工具的
DiffUtil
获得“类似 ListAdapter”行为的有效拖动/滑动行为。您确实丢失了 diff 回调的后台线程。这可以稍微困难一点地添加回
submitList
(或者您可以使用 Kotlin 和协程更轻松地添加它)。

适配器

由于适配器可以访问显示的列表,因此当项目被移动/刷开时,它可以在其中移动项目或将其删除。当使用

submitList
发布新列表时,它使用 DiffUtil 方法将新列表与当前显示的列表进行比较,更新显示的列表,并将更改分派到要动画化的回收器视图。

如果您需要将订单更改/刷回 ViewModel 或其他层,请将接口传递给适配器并根据需要在

onItemMove
onItemDismiss
中调用它,让后端知道项目位置已更改或项目已被删除。此类更改不应导致后端向
submitList
提交新列表,因为显示的列表已更新。

public class RecyclerViewAdapter 
    extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> 
    implements ItemTouchHelperAdapter 
{
    // Define a diff callback, similar to what is used in ListAdapter,
    // but with a couple other methods.
    private static class DiffCallback extends DiffUtil.Callback {
        DiffCallback(List<RecyclerViewData> old) {
            oldList = old;
        }

        @Override
        public int getOldListSize() {
            return oldList.size();
        }

        @Override
        public int getNewListSize() {
            return newList.size();
        }

        // Each entry in the example has a UUID, this compares only
        // the UUID, not the name or birthday, to determine if an 
        // item moved
        @Override
        public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
            return oldList.get(oldItemPosition).isSameEntry(newList.get(newItemPosition));
        }

        // Compare the displayed data (name and birthday) to decide
        // if it should be redrawn/rebound
        @Override
        public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
            return oldList.get(oldItemPosition).isSameAs(newList.get(newItemPosition));
        }

        List<RecyclerViewData> newList = new ArrayList<>();
        private final List<RecyclerViewData> oldList;
    }

    // Basic ViewHolder
    public static class ViewHolder extends RecyclerView.ViewHolder {

        TextView name;
        TextView birthday;

        public ViewHolder(@NonNull View itemView) {
            super(itemView);
            name = itemView.findViewById(R.id.name);
            birthday = itemView.findViewById(R.id.birthday);
        }
    }

    private final LayoutInflater inflater;
    private final ArrayList<RecyclerViewData> displayedList = new ArrayList<>();
    private final DiffCallback diffCallback = new DiffCallback(displayedList);

    public RecyclerViewAdapter (Context ctx) {
        this.inflater = LayoutInflater.from(ctx);
    }

    // Implement methods from ItemTouchHelperAdapter interface, to be called
    // from the ItemTouchHelper.SimpleCallback
    @Override
    public boolean onItemMove(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to) {
        int fromP = from.getAdapterPosition();
        int toP = to.getAdapterPosition();

        if( fromP == toP ) {
            return false;
        }
        else {
            Collections.swap(displayedList, fromP, toP);
            notifyItemMoved(fromP, toP);
            return true;
        }
    }

    @Override
    public void onItemDismiss(int position, int direction) {
        displayedList.remove(position);
        notifyItemRemoved(position);
    }

    // Mimic the ListAdapter "submitList" method, but without the benefit
    // of a background thread. Could be added if needed, but adds complexity.
    public void submitList(List<RecyclerViewData> newList) {
        diffCallback.newList = newList;
        DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
        displayedList.clear();
        displayedList.addAll(newList);
        diffResult.dispatchUpdatesTo(this);
    }

    // Basic RecyclerView stuff
    @NonNull
    @Override
    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View view = inflater.inflate(R.layout.profile_row,parent,false);
        return new RecyclerViewAdapter.ViewHolder(view);
    }

    @SuppressLint("ClickableViewAccessibility")
    @Override
    public void onBindViewHolder(@NonNull RecyclerViewAdapter.ViewHolder holder, int position) {
        RecyclerViewData data = displayedList.get(position);
        holder.name.setText(data.getName());
        holder.birthday.setText(data.getBirthday());
    }

    @Override
    public int getItemCount() {
        return displayedList.size();
    }
}

活动

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    RecyclerView recyclerView = findViewById(R.id.profile_list);

    RecyclerViewAdapter adapter = new RecyclerViewAdapter(this);
    recyclerView.setAdapter(adapter);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));

    ItemTouchHelper ith = new ItemTouchHelper(new SimpleItemTouchHelper(adapter));
    ith.attachToRecyclerView(recyclerView);
    
    // this should come from a ViewModel, just here for simplicity
    ArrayList<RecyclerViewData> data = new ArrayList<>();
    data.add(new RecyclerViewData("Bob", "Jan 12"));
    data.add(new RecyclerViewData("Frank", "Feb 12"));
    data.add(new RecyclerViewData("Herbert", "Mar 22"));
    data.add(new RecyclerViewData("Joe", "Apr 15"));
    data.add(new RecyclerViewData("Bob", "Jan 12"));
    
    // when the ViewModel gets data, or filters the list to show it in a new order,
    // it calls "submitList" and the adapter figures out what to show/move
    adapter.submitList(data);
}

物品触摸界面

(不是绝对必要的,但很好地将适配器与项目触摸助手分离)

public interface ItemTouchHelperAdapter {
    boolean onItemMove(RecyclerView.ViewHolder from, RecyclerView.ViewHolder to);
    void onItemDismiss(int position, int direction);
}

item触摸回调

简单的回调,只需调用适配器上的

onItemMove
onItemDismiss

public class SimpleItemTouchHelper extends ItemTouchHelper.SimpleCallback {

    private final ItemTouchHelperAdapter adapter;
    SimpleItemTouchHelper(ItemTouchHelperAdapter adapter) {
        super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, ItemTouchHelper.RIGHT);
        this.adapter = adapter;
    }
    
    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        return adapter.onItemMove(viewHolder, target);
    }

    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
        adapter.onItemDismiss(viewHolder.getAdapterPosition(), direction);
    }
}

数据类

public class RecyclerViewData {
    private final String uuid;
    private final String name;
    private final String birthday;

    public RecyclerViewData(String name, String birthday) {
        this.uuid = UUID.randomUUID().toString();
        this.name = name;
        this.birthday = birthday;
    }

    public boolean isSameEntry(RecyclerViewData other) {
        return this.uuid.equals(other.uuid);
    }

    public boolean isSameAs(RecyclerViewData other) {
        return this.name.equals(other.name) && 
               this.birthday.equals(other.birthday);
    }

    public String getName() {
        return name;
    }

    public String getBirthday () {
        return birthday;
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.