简体   繁体   English

RecyclerView 的项目在屏幕上的位置在运行时发生变化

[英]RecyclerView's Items Position On The Screen Changes On The Run


I have this RecyclerView where any item can be clicked to open, and when it's opened, it grows (picture 1 is a closed item, picture 2 is an opened item)我有这个RecyclerView可以点击任何项目打开,当它打开时,它会增长(图 1 是关闭的项目,图 2 是打开的项目)


The layout file of an item holds two states - the closed card, and the opened card.项目的布局文件包含两种状态 - 关闭的卡片和打开的卡片。 To switch between them, I change only the visibility of any state.为了在它们之间切换,我只更改任何状态的可见性。 Here are the methods that control an item's opening (expanding) or closing (shrinking) :以下是控制项目打开(扩展)或关闭(收缩)的方法:

    /**
    * handles the expanding functionality.
    */
    public void expand() {
        shrink = true;
        if (expandedItem != -1) {
            notifyItemChanged(expandedItem);
        }
        expandedItem = getLayoutPosition();

        toggleExpandShrinkIcon(true);
        if (!openedFromParent[1]) {
            openedFromParent[1] = true;
        } else {
            openedFromParent[0] = false;
        }
        expandedContainer.setVisibility(View.VISIBLE);
        shrunkProgressBar.setVisibility(View.INVISIBLE);
    }

    /**
     * handles the shrinking functionality.
     */
    public void shrink() {
        toggleExpandShrinkIcon(false);
        expandedContainer.setVisibility(View.GONE);
        shrunkProgressBar.setVisibility(View.VISIBLE);
        shrink = false;
    }

These methods are located in the RecyclerView 's adapter, inside of the ViewHolder 's class, and they are public so I could use them also out of the RecyclerView 's adapter class (not only by clicking), as I did when one item hovers another.这些方法位于RecyclerView的适配器中,在ViewHolder的类内,并且它们是公共的,因此我也可以在RecyclerView的适配器类之外使用它们(不仅仅是通过单击),就像我在一个项目时所做的那样盘旋另一个。


Recently I added drag-to-hover functionality (using this library ) so that I can drag any item on top of any other item, and when one item hovers another item, the lower item gets opened.最近我添加了拖动到悬停功能(使用这个库),这样我就可以将任何项目拖到任何其他项目的顶部,当一个项目悬停在另一个项目上时,下面的项目会被打开。
When an item gets opened, it pushes all the other items below it to be able to expand without hiding the items under it (like in the first video).当一个项目被打开时,它会推动它下面的所有其他项目,以便能够展开而不隐藏它下面的项目(就像在第一个视频中一样)。
When moving from hovering one item to another, say from the second item to the third, when hovering the second item it gets opened and the third item is pushed down, and when moving to the third item the second item gets closed, but the third item won't go back up.当从悬停一个项目移动到另一个项目时,比如说从第二个项目到第三个项目,当悬停第二个项目时它会打开并且第三个项目被按下,当移动到第三个项目时第二个项目关闭,但是第三个项目项目不会恢复。 Then when hovering the third item, it gets opened on the fourth item (see the second video to understand better).然后当悬停第三个项目时,它会在第四个项目上打开(请参阅第二个视频以更好地理解)。

Here's the code in the class that handles the hovering action:这是处理悬停动作的类中的代码:

public class HoveringCallback extends ItemTouchHelper.SimpleCallback {
    //re-used list for selecting a swap target
    private List<RecyclerView.ViewHolder> swapTargets = new ArrayList<>();
    //re used for for sorting swap targets
    private List<Integer> distances = new ArrayList<>();
    private float selectedStartX;
    private float selectedStartY;

    public interface OnDroppedListener {
        void onDroppedOn(ActiveGoalsAdapter.ActiveGoalsViewHolder viewHolder, ActiveGoalsAdapter.ActiveGoalsViewHolder target);
    }

    private List<OnDroppedListener> onDroppedListeners = new ArrayList<>();
    @Nullable
    private RecyclerView recyclerView;
    @Nullable
    ActiveGoalsAdapter.ActiveGoalsViewHolder selected;
    @Nullable
    private ActiveGoalsAdapter.ActiveGoalsViewHolder hovered;

    ItemBackgroundCallback backgroundCallback;

    public HoveringCallback() {
        super(ItemTouchHelper.UP | ItemTouchHelper.DOWN, 0);
    }

    public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
        this.recyclerView = recyclerView;
    }

    public void addOnDropListener(OnDroppedListener listener) {
        onDroppedListeners.add(listener);
    }

    public void removeOnDropListener(OnDroppedListener listener) {
        onDroppedListeners.remove(listener);
    }

    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (viewHolder == null) {
            if (hovered != null) {
                notifyDroppedOnListeners(hovered);
            }
        } else {
            selectedStartX = viewHolder.itemView.getLeft();
            selectedStartY = viewHolder.itemView.getTop();
        }
        this.selected = (ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder;
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE && viewHolder != null) {
            viewHolder.itemView.setBackgroundColor(backgroundCallback.getDraggingBackgroundColor(viewHolder));
        }
    }

    private void notifyDroppedOnListeners(ActiveGoalsAdapter.ActiveGoalsViewHolder holder) {
        for (OnDroppedListener listener : onDroppedListeners) {
            listener.onDroppedOn(selected, (ActiveGoalsAdapter.ActiveGoalsViewHolder) holder);
        }
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        viewHolder.itemView.setBackgroundColor(backgroundCallback.getDefaultBackgroundColor(viewHolder));
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        return false;
    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    }

    @Override
    public void onChildDraw(Canvas canvas, RecyclerView parent, RecyclerView.ViewHolder viewHolder,
                            float dX, float dY, int actionState, boolean isCurrentlyActive) {
        super.onChildDraw(canvas, parent, viewHolder, dX, dY, actionState, isCurrentlyActive);

        if (actionState != ItemTouchHelper.ACTION_STATE_DRAG) {
            return;
        }

        if (recyclerView == null || selected == null) {
            return;
        }

        final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
        final int childCount = lm.getChildCount();
        List<RecyclerView.ViewHolder> swapTargets = findSwapTargets((ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder, dX, dY);
        final int x = (int) (selectedStartX + dX);
        final int y = (int) (selectedStartY + dY);

        hovered = (ActiveGoalsAdapter.ActiveGoalsViewHolder) chooseDropTarget((ActiveGoalsAdapter.ActiveGoalsViewHolder) viewHolder, swapTargets, x, y);
        if (hovered == null) {
            this.swapTargets.clear();
            this.distances.clear();
        }
        for (int i = 0; i < childCount; i++) {
            final View child = lm.getChildAt(i);

            if (viewHolder.itemView == child) {
                continue;
            }

            ActiveGoalsAdapter.ActiveGoalsViewHolder childViewHolder = (ActiveGoalsAdapter.ActiveGoalsViewHolder) parent.findContainingViewHolder(child);

            if (childViewHolder == null || childViewHolder.getAdapterPosition() == RecyclerView.NO_POSITION) {
                continue;
            }

            final int count = canvas.save();
            if (childViewHolder == hovered) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        childViewHolder.expand();
                    }
                }, 500);
            } else {
                if(!childViewHolder.isShrunk()) {
                    childViewHolder.shrink();
                    if(canvas.getSaveCount() != count) {
                        canvas.restoreToCount(count);
                    }
                }
            }
        }
    }

    private List<RecyclerView.ViewHolder> findSwapTargets(ActiveGoalsAdapter.ActiveGoalsViewHolder viewHolder, float dX, float dY) {
        swapTargets.clear();
        distances.clear();
        final int margin = getBoundingBoxMargin();
        final int left = Math.round(selectedStartX + dX) - margin;
        final int top = Math.round(selectedStartY + dY) - margin;
        final int right = left + viewHolder.itemView.getWidth() + 2 * margin;
        final int bottom = top + viewHolder.itemView.getHeight() + 2 * margin;
        final int centerX = (left + right) / 2;
        final int centerY = (top + bottom) / 2;
        final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager();
        final int childCount = lm.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View other = lm.getChildAt(i);
            if (other == viewHolder.itemView) {
                continue; //myself!
            }
            if (other.getBottom() < top || other.getTop() > bottom
                    || other.getRight() < left || other.getLeft() > right) {
                continue;
            }
            final ActiveGoalsAdapter.ActiveGoalsViewHolder otherVh = (ActiveGoalsAdapter.ActiveGoalsViewHolder) recyclerView.getChildViewHolder(other);
            if (canDropOver(recyclerView, selected, otherVh)) {
                // find the index to add
                final int dx = Math.abs(centerX - (other.getLeft() + other.getRight()) / 2);
                final int dy = Math.abs(centerY - (other.getTop() + other.getBottom()) / 2);
                final int dist = dx * dx + dy * dy;

                int pos = 0;
                final int cnt = swapTargets.size();
                for (int j = 0; j < cnt; j++) {
                    if (dist > distances.get(j)) {
                        pos++;
                    } else {
                        break;
                    }
                }
                swapTargets.add(pos, otherVh);
                distances.add(pos, dist);
            }
        }
        return swapTargets;
    }

    @Override
    public float getMoveThreshold(RecyclerView.ViewHolder viewHolder) {
        return 0.05f;
    }

    @Override
    public RecyclerView.ViewHolder chooseDropTarget(RecyclerView.ViewHolder selected,
                                                    List<RecyclerView.ViewHolder> dropTargets,
                                                    int curX, int curY) {
        int right = curX + selected.itemView.getWidth();
        int bottom = curY + selected.itemView.getHeight();
        ActiveGoalsAdapter.ActiveGoalsViewHolder winner = null;
        int winnerScore = -1;
        final int dx = curX - selected.itemView.getLeft();
        final int dy = curY - selected.itemView.getTop();
        final int targetsSize = dropTargets.size();
        for (int i = 0; i < targetsSize; i++) {
            final ActiveGoalsAdapter.ActiveGoalsViewHolder target = (ActiveGoalsAdapter.ActiveGoalsViewHolder) dropTargets.get(i);
            if (dx > 0) {
                int diff = target.itemView.getRight() - right;
                if (diff < 0 && target.itemView.getRight() > selected.itemView.getRight()) {
                    final int score = Math.abs(diff);
                    if (score > winnerScore) {
                        winnerScore = score;
                        winner = target;
                    }
                }
            }
            if (dx < 0) {
                int diff = target.itemView.getLeft() - curX;
                if (diff > 0 && target.itemView.getLeft() < selected.itemView.getLeft()) {
                    final int score = Math.abs(diff);
                    if (score > winnerScore) {
                        winnerScore = score;
                        winner = target;
                    }
                }
            }
            if (dy < 0) {
                int diff = target.itemView.getTop() - curY;
                if (target.itemView.getTop() < selected.itemView.getTop()) {
                    final int score = Math.abs(diff);
                    if (score > winnerScore) {
                        winnerScore = score;
                        winner = target;
                    }
                }
            }

            if (dy > 0) {
                int diff = target.itemView.getBottom() - bottom;
                if (target.itemView.getBottom() > selected.itemView.getBottom()) {
                    final int score = Math.abs(diff);
                    if (score > winnerScore) {
                        winnerScore = score;
                        winner = target;
                    }
                }
            }
        }
        return winner;
    }
}


How can I solve this?我该如何解决这个问题? (make the third item go back up, like when the drag is released, at the end of the second video) (使第三个项目返回,就像在第二个视频结束时释放拖动时一样)
Help would be highly appreciated!帮助将不胜感激! (: (:


First picture (closed item):第一张图(关闭项):
关闭项


Second picture (opened item):第二张图(打开的项目):
打开的项目


First Video (item gets opened & closed in the list):第一个视频(项目在列表中打开和关闭):
项目在列表中打开和关闭


Second video (item dragged):第二个视频(拖动项目):
被错误拖拽的项目

I think the best bet is to set your code that opens each recyclerview using ACTION_UP so that the code is triggered on the lifting of the finger, not on the sensing of it.我认为最好的办法是设置使用 ACTION_UP 打开每个 recyclerview 的代码,以便代码在抬起手指时触发,而不是在感应时触发。

button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { sw If(MotionEvent.ACTION_UP) {Toast.makeText(MainActivity.this, "Up", Toast.LENGTH_SHORT).show(); } button.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View view, MotionEvent motionEvent) { sw If(MotionEvent.ACTION_UP) {Toast.makeText(MainActivity.this, "Up", Toast.LENGTH_SHORT).show (); }

Without studying the code of Library I assume positions are being changed so it is highly likely that .notifyItemChanged(position) is invoked, and in that, expand function invoke because there is no check to isolate the expand function from unhandled events在不研究库代码的情况下,我假设位置正在更改,因此很可能调用.notifyItemChanged(position) ,并且在其中调用 expand 函数,因为没有检查将expand 函数与未处理的事件隔离开来

A simple answer can be to save the state and disable the expansion of items一个简单的答案可以是保存状态并禁用项目的扩展

Set isDragActive to true when where the item is long pressed长按项目时将 isDragActive 设置为 true

private static isDragActive = false
  @Overrides
   public onCreateViewHolder(){
     itemView.setOnItemLongClickListener(new OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> arg0, View arg1,
                int pos, long id) {
            isDragActive = true;
            return true;
        }
    }); 
 }

    public void expand() {
    if(!isDragActive){
        shrink = true;
        if (expandedItem != -1) {
            notifyItemChanged(expandedItem);
        }
        expandedItem = getLayoutPosition();

        toggleExpandShrinkIcon(true);
        if (!openedFromParent[1]) {
            openedFromParent[1] = true;
        } else {
            openedFromParent[0] = false;
        }
        expandedContainer.setVisibility(View.VISIBLE);
        shrunkProgressBar.setVisibility(View.INVISIBLE);
    }
 }

You can also use the library given below instead of above solution您还可以使用下面给出的库代替上述解决方案

https://github.com/mikepenz/FastAdapter https://github.com/mikepenz/FastAdapter

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM