【问题标题】:onItemClickListeners misbehaving after RecyclerView resize在 RecyclerView 调整大小后 onItemClickListeners 行为不端
【发布时间】:2017-06-08 12:16:12
【问题描述】:

我正在开发一个捏拉缩放 RecyclerView,并且捏拉缩放功能可以正常工作,但是一旦放大,onItemClickListeners 就位于错误的位置。下面是修改后的 RecyclerView 的代码。我还做了一个simple demo that is hosted on GitHub。在尝试演示时,当你点击一个项目时,你应该会得到一个 Toast,上面写着你点击的图标的编号。尝试不放大,然后放大并再次尝试查看问题。我相信我需要通知 RecyclerView onItemClickListener 位置需要更新,但找不到正确的方法来覆盖。感谢您的帮助!

public class PinchRecyclerView extends RecyclerView {
    private static final int INVALID_POINTER_ID = -1;
    private int mActivePointerId = INVALID_POINTER_ID;
    private ScaleGestureDetector mScaleDetector;
    private float mScaleFactor = 1.f;
    private float maxWidth = 0.0f;
    private float maxHeight = 0.0f;
    private float mLastTouchX;
    private float mLastTouchY;
    private float mPosX;
    private float mPosY;
    private float width;
    private float height;


    public PinchRecyclerView(Context context) {
        super(context);
        if (!isInEditMode())
            mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    public PinchRecyclerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (!isInEditMode())
            mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    public PinchRecyclerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        if (!isInEditMode())
            mScaleDetector = new ScaleGestureDetector(getContext(), new ScaleListener());
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        width = View.MeasureSpec.getSize(widthMeasureSpec);
        height = View.MeasureSpec.getSize(heightMeasureSpec);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException ex) {
            ex.printStackTrace();
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(@NonNull MotionEvent ev) {
        super.onTouchEvent(ev);
        final int action = ev.getAction();
        mScaleDetector.onTouchEvent(ev);
        switch (action & MotionEvent.ACTION_MASK) {
            case MotionEvent.ACTION_DOWN: {
                final float x = ev.getX();
                final float y = ev.getY();
                mLastTouchX = x;
                mLastTouchY = y;
                mActivePointerId = ev.getPointerId(0);
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK)
                        >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final float x = ev.getX(pointerIndex);
                final float y = ev.getY(pointerIndex);
                final float dx = x - mLastTouchX;
                final float dy = y - mLastTouchY;

                mPosX += dx;
                mPosY += dy;

                if (mPosX > 0.0f)
                    mPosX = 0.0f;
                else if (mPosX < maxWidth)
                    mPosX = maxWidth;

                if (mPosY > 0.0f)
                    mPosY = 0.0f;
                else if (mPosY < maxHeight)
                    mPosY = maxHeight;

                mLastTouchX = x;
                mLastTouchY = y;

                invalidate();
                break;
            }

            case MotionEvent.ACTION_UP: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }

            case MotionEvent.ACTION_CANCEL: {
                mActivePointerId = INVALID_POINTER_ID;
                break;
            }

            case MotionEvent.ACTION_POINTER_UP: {
                final int pointerIndex = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
                final int pointerId = ev.getPointerId(pointerIndex);
                if (pointerId == mActivePointerId) {
                    final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
                    mLastTouchX = ev.getX(newPointerIndex);
                    mLastTouchY = ev.getY(newPointerIndex);
                    mActivePointerId = ev.getPointerId(newPointerIndex);
                }
                break;
            }
        }

        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        canvas.translate(mPosX, mPosY);
        canvas.scale(mScaleFactor, mScaleFactor);
        canvas.restore();
    }

    @Override
    protected void dispatchDraw(@NonNull Canvas canvas) {
        canvas.save(Canvas.MATRIX_SAVE_FLAG);
        if (mScaleFactor == 1.0f) {
            mPosX = 0.0f;
            mPosY = 0.0f;
        }
        canvas.translate(mPosX, mPosY);
        canvas.scale(mScaleFactor, mScaleFactor);
        super.dispatchDraw(canvas);
        canvas.restore();
        invalidate();
    }

    private class ScaleListener extends ScaleGestureDetector.SimpleOnScaleGestureListener {
        @Override
        public boolean onScale(ScaleGestureDetector detector) {
            mScaleFactor *= detector.getScaleFactor();
            mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, 3.0f));
            maxWidth = width - (width * mScaleFactor);
            maxHeight = height - (height * mScaleFactor);
            invalidate();
            return true;
        }
    }
}

没有缩放,点击#34,正在工作

放大,点击#34,不工作

【问题讨论】:

  • 您正在使用Canvas 进行操作。相反,您必须使用实际的布局管理器,从而管理布局过程。目前你只是通过对画布进行更改来伪装,但它下面的布局完全没有被触及。 Imo,你必须继承 GridLayoutManager 并在那里执行繁重的工作。
  • 你使用 recyclerView.setHasFixedSize(true) 吗?如果是,则删除该行并尝试
  • 查看我的更新答案以获得更简洁的实现。

标签: android android-recyclerview


【解决方案1】:

更新 我认为最好将修改分散到您的几个课程中。 Here 是指向 PinchRecyclerView.java 的链接,其中包含所需的所有更改。我将把整个项目链接在下面,以防它有其他用途。


您只是更改了画布上的显示,而不是 azizbekian 在评论中所说的基础视图。如果您不想使用自定义 GridLayoutManager 并继续采用您已经开始的方法,则需要将屏幕触摸映射回您的底层视图。

这是您在 GitHub 上的 updated project,用于进行映射。以下是基本变化:

  • PinchRecyclerView添加一个触摸监听器。

  • PinchRecyclerView 上的触摸坐标转换为底层布局中的相应位置。

  • 通过搜索预期的项目视图进行识别并进行点击处理。

如果您尝试这个更新的应用程序,并且正如您在视频中看到的那样,触摸监听器也会在捏合时触发,所以这是一个错误。

【讨论】:

  • 谢谢@Cheticamp!似乎关键是在适配器中添加了一个 onTouchListener。您的修改完美无缺。
  • 抱歉提出这个问题,@Cheticamp,我尝试了你的代码,但我想知道为什么它不断将所选项目移至下一个项目?比如我点击了38,然后toast消息显示我点击了39?我在 recyclerview 的左侧和右侧有两个额外的视图。请帮忙。
  • 感谢@Cheticamp,它对项目点击工作正常,但在 row_item 子点击监听器的情况下它会失败,如果我们为 row_item 的任何孩子设置了任何 onClickListener 然后滚动或放大/out,它不起作用,当我们第一次显示屏幕时,它按预期工作,但是在滚动和/或放大/缩小之后,它不起作用,请问有什么解决方案吗?
  • 另外,当我们缩放回收站时,我们设置的文本看起来很模糊,有什么解决方案吗?
  • 您好@Cheticamp,如果您有解决上述两个错误的方法将非常感谢。
【解决方案2】:

这里是 recyclerview 工作原理的架构

适配器向 > ViewHolder 提供数据

ViewHolder 将 findviewbyid 的数据项存储在内存/已保存位置,以防止每次滚动时都因性能问题而查找视图。接下来...它的

ViewHolder > 布局管理器

布局管理器存储每个项目的位置,并在缩放、滚动等时更新它们的位置。

我认为解决这个问题的方法是尝试调用

adapter.notifyDataSetChanged();

在检测到缩放活动时返回 true 的方法内部。不要在 adaper 类中执行此操作,请在缩放活动类中执行此操作。

这应该会更新 LayoutManager/ViewHolder 并重新分配 ID。

或者

adapter.notifyItemRangeChanged(position, list.size());

告知项目范围已更改

您可以在此查看更多信息 How to update RecyclerView Adapter Data?

How do I make RecyclerView update its layout?

都回答了

【讨论】:

  • 不幸的是,这不起作用。 NotifyDataSetChanged() 指的是适配器用来填充视图但不以任何方式影响布局的数据集。可以肯定的是,我尝试了它,但它没有按预期工作。不过还是谢谢。
  • 如果我理解你的话,你的意思是 onclick 监听器只在正常大小没有缩放的情况下正确获取项目 ID,但在使用缩放时返回错误的项目 ID/对象?我现在无法使用我的电脑,请您发布问题的屏幕截图
  • 没错。请用截图查看我编辑的问题。
【解决方案3】:

为什么不在RecyclerView.ViewHolder 中添加点击监听器:

itemView.setOnClickListener(...);

正如 azizbekian 所说,使用您自己的 LayoutManager 进行布局。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-10-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多