onItemClickListeners misbehaving after RecyclerView resize

安稳与你 提交于 2020-01-01 17:11:15

问题


I am working on a pinch-to-zoom RecyclerView and have the pinch-to-zoom functionality working, but once zoomed in, the onItemClickListeners are in the wrong locations. Below is the code for the modified RecyclerView. I also made a simple demo that is hosted on GitHub. When trying the demo, when you click on an item, you should get a Toast that says the number of the icon you click on. Try it without zooming, then zoom in and try again to see the issue. I believe that I need to notify the RecyclerView that the onItemClickListener locations need to be updated, but cannot find the correct method to override. Thanks for any help!

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

No Zoom, Clicked #34, Working

Zoomed In, Clicked #34, Not Working


回答1:


Update I thought better of spreading the modifications throughout several of your classes. Here is a link to just PinchRecyclerView.java with all the changes that are needed. I will leave the entire project linked below in case there is some other use for it.


You are changing just the display on the canvas and not the underlying views as azizbekian stated in his comment. If you don't want to go with a custom GridLayoutManager and continue with the approach you have embarked upon, you will need to map screen touches back to your underlying views.

Here is your updated project on GitHub that does the mapping. Here are the basic changes:

  • Add a touch listener to the PinchRecyclerView.

  • Translate touch coordinates on the PinchRecyclerView to the corresponding position within your underlying layout.

  • Identify through searching the intended item view and do your click processing.

If you try this updated app and, as you can see in the video, the touch listener is also triggered on a pinch, so that is a bug.




回答2:


Here is an architecture of how the recyclerview works

Adapter supplies data to > ViewHolder

ViewHolder stores the data items findviewbyid's in memory/saved location to prevent finding views every time you scroll for performance issues. Next...its

ViewHolder > LayoutManager

Layout manager stores the position of each item and does the job of updating their positions on pinch to zoom, scroll etc.

I think the way to solve this issue is to try and call

adapter.notifyDataSetChanged();

Inside a method that returns true when zoom activity is detected. Do not do this in adaper class do it in zoom activity class.

This should update the LayoutManager/ViewHolder and reassign ids.

Or

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

To tell that item range has been changed

You can see more on this How to update RecyclerView Adapter Data?

How do I make RecyclerView update its layout?

Both Answered




回答3:


Why not just add the click listeners in your RecyclerView.ViewHolder:

itemView.setOnClickListener(...);

And as azizbekian already stated, use your own LayoutManager for the layout.



来源:https://stackoverflow.com/questions/44435543/onitemclicklisteners-misbehaving-after-recyclerview-resize

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!