You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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,步骤如下:

  1. 修改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);
    }

    // ...其他代码不变
}
  1. 修改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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 15:53:14