簡體   English   中英

RecyclerView 的項目在屏幕上的位置在運行時發生變化

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


我有這個RecyclerView可以點擊任何項目打開,當它打開時,它會增長(圖 1 是關閉的項目,圖 2 是打開的項目)


項目的布局文件包含兩種狀態 - 關閉的卡片和打開的卡片。 為了在它們之間切換,我只更改任何狀態的可見性。 以下是控制項目打開(擴展)或關閉(收縮)的方法:

    /**
    * 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;
    }

這些方法位於RecyclerView的適配器中,在ViewHolder的類內,並且它們是公共的,因此我也可以在RecyclerView的適配器類之外使用它們(不僅僅是通過單擊),就像我在一個項目時所做的那樣盤旋另一個。


最近我添加了拖動到懸停功能(使用這個庫),這樣我就可以將任何項目拖到任何其他項目的頂部,當一個項目懸停在另一個項目上時,下面的項目會被打開。
當一個項目被打開時,它會推動它下面的所有其他項目,以便能夠展開而不隱藏它下面的項目(就像在第一個視頻中一樣)。
當從懸停一個項目移動到另一個項目時,比如說從第二個項目到第三個項目,當懸停第二個項目時它會打開並且第三個項目被按下,當移動到第三個項目時第二個項目關閉,但是第三個項目項目不會恢復。 然后當懸停第三個項目時,它會在第四個項目上打開(請參閱第二個視頻以更好地理解)。

這是處理懸停動作的類中的代碼:

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;
    }
}


我該如何解決這個問題? (使第三個項目返回,就像在第二個視頻結束時釋放拖動時一樣)
幫助將不勝感激! (:


第一張圖(關閉項):
關閉項


第二張圖(打開的項目):
打開的項目


第一個視頻(項目在列表中打開和關閉):
項目在列表中打開和關閉


第二個視頻(拖動項目):
被錯誤拖拽的項目

我認為最好的辦法是設置使用 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 (); }

在不研究庫代碼的情況下,我假設位置正在更改,因此很可能調用.notifyItemChanged(position) ,並且在其中調用 expand 函數,因為沒有檢查將expand 函數與未處理的事件隔離開來

一個簡單的答案可以是保存狀態並禁用項目的擴展

長按項目時將 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);
    }
 }

您還可以使用下面給出的庫代替上述解決方案

https://github.com/mikepenz/FastAdapter

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM