在recyclerView中检测滑动手势时出现问题,MotionEvent e1为null?

4

我有一个包括recyclerViewtabLayout的界面,当用户切换标签时,recyclerView会加载新的条目。现在我想添加滑动功能,当用户在recyclerView上向左或向右滑动时,应用程序可以在标签之间进行切换(并重新加载recyclerView中的新条目)。我一直很难有效地检测到在recyclerView上向左或向右滑动手势。我不想使用ViewPager,因为我想保持我的应用程序轻巧快速。此外,我只需要滑动这一功能。以下是我最后使用的方法:

OnSwipeTouchListener onSwipeTouchListener = new OnSwipeTouchListener(getActivity())
    {
        public void onSwipeRight() {
            //update recyclerview with new data
        }

        public void onSwipeLeft(){
            //update recyclerview with new data
        }
    };

b.rcvItems.setOnTouchListener(onSwipeTouchListener);



b.rcvItems.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 0) {
                int threshold = 20;
                int count = adapter.getItemCount();
                LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
                int lastVisible = layoutManager.findLastVisibleItemPosition();
                if (lastVisible >= count - threshold) { 
                    //load more items
                }
            }
        }
    });

这里是类OnSwipeTouchListener,其中包含类GestureListener

public class OnSwipeTouchListener implements View.OnTouchListener {
private final GestureDetector gestureDetector;

protected OnSwipeTouchListener(Context ctx) {
    gestureDetector = new GestureDetector(ctx, new GestureListener());
}

@Override
public boolean onTouch(View v, MotionEvent event) {
    return gestureDetector.onTouchEvent(event);
}

public void onSwipeRight() {
}

public void onSwipeLeft() {
}

public void onSwipeTop() {
}

public void onSwipeBottom() {
}





private final class GestureListener extends GestureDetector.SimpleOnGestureListener {
    private static final int SWIPE_THRESHOLD = 23;
    private static final int SWIPE_VELOCITY_THRESHOLD = 0;

    @Override
    public boolean onDown(MotionEvent e) {
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        boolean result = false;
        try {
            float diffY = e2.getY() - e1.getY();
            float diffX = e2.getX() - e1.getX();
            if (Math.abs(diffX) > Math.abs(diffY)) {
                if (Math.abs(diffX) > SWIPE_THRESHOLD && Math.abs(velocityX) > SWIPE_VELOCITY_THRESHOLD) {
                    if (diffX > 0) {
                        onSwipeRight();
                    } else {
                        onSwipeLeft();
                    }
                    result = true;
                }
            }
        } catch (Exception exception) {
            exception.printStackTrace();
        }
        return result;
    }
}
}

现在问题是当加载Fragment时,首先,滑动功能无法使用。但是在我滚动recyclerView或者有时在尝试多次滑动后,它开始检测到滑动动作!然后就变得流畅了。我检查发现,在方法onFling()中的第一个参数MotionEvent e1在我第一次滚动recyclerView之前或尝试多次滑动后为null。仍然在某些启动中,滑动功能即使在最初也能正常工作!(这很少见,但确实会发生)。这太复杂了,我甚至无法确定正确的行为方式。我该如何解决这个问题?
附注:有时应用程序会冻结,然后崩溃。我获取以下崩溃日志:
java.lang.IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter positionMyViewHolder{779d2db position=27 id=-1, oldPos=-1, pLpos:-1 no parent} androidx.recyclerview.widget.RecyclerView{2415051 VFED..... ........ 0,0-1080,2049 #7f09016d app:id/rcvItems}, adapter:ui.adapter.ItemsAdapter@bee10b6, layout:androidx.recyclerview.widget.LinearLayoutManager@38c1b7, context:ui.MainActivity@577a61c
    at androidx.recyclerview.widget.RecyclerView$Recycler.validateViewHolderForOffsetPosition(RecyclerView.java:5972)
    at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6156)
    at androidx.recyclerview.widget.GapWorker.prefetchPositionWithDeadline(GapWorker.java:288)
    at androidx.recyclerview.widget.GapWorker.flushTaskWithDeadline(GapWorker.java:345)
    at androidx.recyclerview.widget.GapWorker.flushTasksWithDeadline(GapWorker.java:361)
    at androidx.recyclerview.widget.GapWorker.prefetch(GapWorker.java:368)
    at androidx.recyclerview.widget.GapWorker.run(GapWorker.java:399)
    at android.os.Handler.handleCallback(Handler.java:883)
    at android.os.Handler.dispatchMessage(Handler.java:100)
    at android.os.Looper.loop(Looper.java:237)
    at android.app.ActivityThread.main(ActivityThread.java:8167)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:496)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1100)
3个回答

2
主要原因是e1在初始移动时可能为空,因此将其放置在try/catch块中。对于try/catch,检查是否为空并返回。请参阅下面的代码片段。

if (e1 == null || e2 == null) 
  return false;
try {
...
} catch (Exception e) {}
return false;


2

编写 OnSwipeTouchListener.kt 代码:

import android.content.Context
import android.view.GestureDetector
import android.view.GestureDetector.SimpleOnGestureListener
import android.view.MotionEvent
import android.view.View
import android.view.View.OnTouchListener
import kotlin.math.abs
internal open class OnSwipeTouchListener(c: Context?) :
OnTouchListener {
   private val gestureDetector: GestureDetector
   override fun onTouch(view: View, motionEvent: MotionEvent): Boolean {
      return gestureDetector.onTouchEvent(motionEvent)
   }
   private inner class GestureListener : SimpleOnGestureListener() {
      private val SWIPE_THRESHOLD: Int = 100
      private val SWIPE_VELOCITY_THRESHOLD: Int = 100
      override fun onDown(e: MotionEvent): Boolean {
         return true
      }
      override fun onSingleTapUp(e: MotionEvent): Boolean {
         onClick()
         return super.onSingleTapUp(e)
      }
      override fun onDoubleTap(e: MotionEvent): Boolean {
         onDoubleClick()
         return super.onDoubleTap(e)
      }
      override fun onLongPress(e: MotionEvent) {
         onLongClick()
         super.onLongPress(e)
      }
      override fun onFling(
         e1: MotionEvent,
         e2: MotionEvent,
         velocityX: Float,
         velocityY: Float
      ): Boolean {
         try {
            val diffY = e2.y - e1.y
            val diffX = e2.x - e1.x
            if (abs(diffX) > abs(diffY)) {
               if (abs(diffX) > SWIPE_THRESHOLD && abs(
                  velocityX
               ) > SWIPE_VELOCITY_THRESHOLD
            ) {
               if (diffX > 0) {
                  onSwipeRight()
               }
               else {
                  onSwipeLeft()
               }
            }
         }
          else {
            if (abs(diffY) > SWIPE_THRESHOLD && abs(
               velocityY
            ) > SWIPE_VELOCITY_THRESHOLD
         ) {
            if (diffY < 0) {
               onSwipeUp()
            }
            else {
                  onSwipeDown()
                  }
               }
               }
         } catch (exception: Exception) {
         exception.printStackTrace()
      }
      return false
      }
   }
   open fun onSwipeRight() {}
   open fun onSwipeLeft() {}
   open fun onSwipeUp() {}
   open fun onSwipeDown() {}
   private fun onClick() {}
   private fun onDoubleClick() {}
   private fun onLongClick() {}
   init {
      gestureDetector = GestureDetector(c, GestureListener())
   }
}

在 MainActivity.kt 中

val recyclerView = findViewById(R.id.recyclerView)
recyclerView.setOnTouchListener(object : OnSwipeTouchListener(this@MainActivity) {
         override fun onSwipeLeft() {
            super.onSwipeLeft()
            Toast.makeText(this@MainActivity, "Swipe Left gesture detected",
            Toast.LENGTH_SHORT)
            .show()
         }
         override fun onSwipeRight() {
            super.onSwipeRight()
            Toast.makeText(
               this@MainActivity,
               "Swipe Right gesture detected",
               Toast.LENGTH_SHORT
            ).show()
         }
         override fun onSwipeUp() {
            super.onSwipeUp()
            Toast.makeText(this@MainActivity, "Swipe up gesture detected", Toast.LENGTH_SHORT)
            .show()
         }
         override fun onSwipeDown() {
            super.onSwipeDown()
            Toast.makeText(this@MainActivity, "Swipe down gesture detected", Toast.LENGTH_SHORT)
            .show()
         }
      })
   }

0
根据上面的提示(https://dev59.com/Rr7pa4cB1Zd3GeqP8usX#68815311),我成功地使用Java的Nullable注解覆盖了Kotlin的非空运动事件。

MyGestureDetector.java

public class MyGestureDetector implements GestureDetector.OnGestureListener {

private final MyGestureDetectorCallBack onScrollDetectAction;

public MyGestureDetector(MyGestureDetectorCallBack onScrollDetectAction) {
    this.onScrollDetectAction = onScrollDetectAction;
}

@Override
public boolean onDown(@NonNull MotionEvent e) { return false; }
@Override
public void onShowPress(@NonNull MotionEvent e) {}
@Override
public boolean onSingleTapUp(@NonNull MotionEvent e) { return false; }

@Override
public boolean onScroll(@Nullable MotionEvent e1, @Nullable MotionEvent e2, float distanceX, float distanceY) {
    if (onScrollDetectAction != null) {
        try {
            onScrollDetectAction.onScrollNullable(e1, e2);
        } catch (Exception ignored) {}
    }
    return false;
}

@Override
public void onLongPress(@NonNull MotionEvent e) {}
@Override
public boolean onFling(@NonNull MotionEvent e1, @NonNull MotionEvent e2, float velocityX, float velocityY) { return false; }}

在Java中,我们可以将运动事件操作为@Nullable。 在Kotlin中重写onScroll是严格的@NotNull,因此会显示编译错误。

MyGestureDetectorCallBack.kt

interface PSINTGestureDetectorCallBack {
    fun onScrollNullable(e1: MotionEvent?, e2: MotionEvent?)
}

然后我们只需要在 SwipeLayout.kt 上定义手势检测器(扩展 FrameLayout):
private val gestureDetector = GestureDetectorCompat(context, gestureDetectorCallBack)

private val gestureDetectorCallBack = MyGestureDetector(
    object : MyGestureDetectorCallBack {
        override fun onScrollNullable(e1: MotionEvent?, e2: MotionEvent?) {
            //run your code
        }
    }
)

override fun onTouchEvent(event: MotionEvent): Boolean {
    gestureDetector.onTouchEvent(event)
    return false //depending on the requirements
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接