问题
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