React Native中RecyclerView触发receivedTouches事件Emitter缺失问题
React Native原生RecyclerView组件触摸交互异常问题
问题背景
开发了一个包含RecyclerView的React Native原生UI组件,用于展示可滚动卡片列表,组件及管理器代码如下:
ListView组件代码
public class ListView extends LinearLayout { CustomRecyclerView recyclerView; SwipeRefreshLayout swipeRefreshLayout; RecyclerViewAdapter adapter; Product[] products; public ListView(Context context) { super(context); inflate(context, R.layout.list, this); recyclerView = findViewById(R.id.listView); recyclerView.setLayoutManager(new LinearLayoutManager(context)); swipeRefreshLayout = findViewById(R.id.refresh_layout); adapter = new RecyclerViewAdapter(); recyclerView.setAdapter(adapter); } @SuppressLint("NotifyDataSetChanged") public void setData(@Nullable ReadableArray data) { post(() -> { Gson gson = new Gson(); assert data != null; products = gson.fromJson(data.toString(), Product[].class); adapter.clearProducts(); adapter.addProducts(Arrays.asList(products)); adapter.notifyDataSetChanged(); }); } }
ListViewManager代码
public class ListViewManager extends SimpleViewManager<ListView> { private final String REACT_CLASS = "nativeList"; @NonNull @Override public String getName() { return REACT_CLASS; } @NonNull @Override protected ListView createViewInstance(@NonNull ThemedReactContext reactContext) { return new ListView(reactContext); } @ReactProp(name = "data") public void setData(ListView view, ReadableArray data) { view.setData(data); } @ReactProp(name = "refreshing") public void setRefreshing(ListView view, boolean refreshing) { view.setRefreshing(refreshing); } }
列表可正常显示,但交互卡片时频繁出现如下错误:
E Unhandled SoftException com.facebook.react.bridge.ReactNoCrashSoftException: Cannot find EventEmitter for receivedTouches: ReactTag[26] UIManagerType[2] EventName[topTouchMove] at com.facebook.react.uimanager.events.ReactEventEmitter.receiveTouches(ReactEventEmitter.java:103) at com.facebook.react.uimanager.events.TouchEvent.dispatchModern(TouchEvent.java:192) at com.facebook.react.uimanager.events.EventDispatcherImpl$DispatchEventsRunnable.run(EventDispatcherImpl.java:370) at android.os.Handler.handleCallback(Handler.java:958) at android.os.Handler.dispatchMessage(Handler.java:99) at com.facebook.react.bridge.queue.MessageQueueThreadHandler.dispatchMessage(MessageQueueThreadHandler.java:29) at android.os.Looper.loopOnce(Looper.java:205) at android.os.Looper.loop(Looper.java:294) at com.facebook.react.bridge.queue.MessageQueueThreadImpl$4.run(MessageQueueThreadImpl.java:234)
推测原因是React Native未识别RecyclerView内部视图,导致交互时无法找到关联事件处理器,甚至触发无关视图的事件引发异常。
解决方案
方案1:拦截触摸事件,不让RN处理(推荐)
直接让原生侧完全接管RecyclerView的触摸逻辑,不把事件传递给RN的事件系统,从根源避免找不到EventEmitter的问题。
方式A:给RecyclerView整体设置触摸拦截
在ListView的构造方法中添加触摸监听:
public ListView(Context context) { super(context); inflate(context, R.layout.list, this); recyclerView = findViewById(R.id.listView); recyclerView.setLayoutManager(new LinearLayoutManager(context)); // 添加触摸拦截 recyclerView.setOnTouchListener((v, event) -> { // 让RecyclerView自身处理触摸事件,返回true表示不向上传递给RN recyclerView.onTouchEvent(event); return true; }); swipeRefreshLayout = findViewById(R.id.refresh_layout); adapter = new RecyclerViewAdapter(); recyclerView.setAdapter(adapter); }
方式B:给单个ItemView设置触摸拦截
如果需要更精细的控制,在RecyclerViewAdapter的onCreateViewHolder中处理:
@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card, parent, false); // 拦截Item的触摸事件 itemView.setOnTouchListener((v, event) -> { // 消费触摸事件,不传递给RN return v.onTouchEvent(event); }); return new ItemViewHolder(itemView); }
方案2:让RN识别RecyclerView内部视图(适合需要RN处理Item事件的场景)
如果必须让RN处理Item的交互事件,需要给每个动态创建的ItemView注册ReactTag到RN的UIManager,步骤如下:
- 修改
ListView构造方法,保存ThemedReactContext:
public class ListView extends LinearLayout { CustomRecyclerView recyclerView; SwipeRefreshLayout swipeRefreshLayout; RecyclerViewAdapter adapter; Product[] products; private ThemedReactContext reactContext; // 添加成员变量 public ListView(ThemedReactContext context) { super(context); this.reactContext = context; // 保存上下文 inflate(context, R.layout.list, this); recyclerView = findViewById(R.id.listView); recyclerView.setLayoutManager(new LinearLayoutManager(context)); swipeRefreshLayout = findViewById(R.id.refresh_layout); adapter = new RecyclerViewAdapter(reactContext); // 传递给Adapter recyclerView.setAdapter(adapter); } // ...其他代码不变 }
- 修改
RecyclerViewAdapter,给每个ItemView分配并注册ReactTag,同时在回收时注销:
public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ItemViewHolder> { private ThemedReactContext reactContext; private List<Product> productList = new ArrayList<>(); public RecyclerViewAdapter(ThemedReactContext reactContext) { this.reactContext = reactContext; } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card, parent, false); // 生成唯一ReactTag int reactTag = UIManagerHelper.nextReactTag(); // 注册视图到RN的UIManager UIManager.registerViewForReactTag(reactContext, itemView, reactTag); // 把ReactTag存储到View的tag中,方便后续注销 itemView.setTag(reactTag); return new ItemViewHolder(itemView); } @Override public void onViewRecycled(@NonNull ItemViewHolder holder) { super.onViewRecycled(holder); // 从View的tag中取出ReactTag并注销 int reactTag = (int) holder.itemView.getTag(); UIManager.unregisterViewFromReactTag(reactContext, reactTag); } // ...其他Adapter逻辑不变 }
注意:这种方式需要处理ReactTag的生命周期,避免内存泄漏,复杂度较高,非必要不推荐使用。
内容的提问来源于stack exchange,提问作者Thomas Berthaud
相关产品推荐
相关产品推荐

