本文记录react-native封装Android RecycleView的代码

用react-native封装自定义View,比较少,所以想来尝试下
效果如下:


ScreenShot_2025-12-29_164711_755.png

废话不多少,贴代码:
原生代码
CustomRecycleView类

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

/**
 * author by sunny
 * created at 2025/4/14 12:25
 * 屏蔽左右Touch手势事件,禁止滑动
 */
public class CustomRecycleView extends RecyclerView {

    public CustomRecycleView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public CustomRecycleView(@NonNull Context context) {
        super(context);
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        return false;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        return false;
    }
}

RecycleViewAdapter类

import android.content.Context;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;

public class RecycleViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    private List<String> mData;
    private Context mContext;
    private OnItemClickLitener mOnItemClickLitener;

    public RecycleViewAdapter(Context context, List<String> mData) {
        this.mContext = context;
        this.mData = mData;
    }

    @Override
    public int getItemViewType(int position) {
        return position;
    }

    /**
     * 设置数据
     * @param mData
     */
    public void setDatas(List<String> mData){
        this.mData = mData;
    }

    @Override
    public int getItemCount() {
        return Integer.MAX_VALUE;
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        String item = mData.get(position % mData.size());
        if(!TextUtils.isEmpty(item)){
            ((ViewHolder)holder).textView.setText(item);
//            Glide.with(mContext).load(url).into(((ViewHolder)holder).imageView);
        }
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View itemView = LayoutInflater.from(mContext).inflate(R.layout.recycleview_item, parent, false);
        ViewHolder holder = new ViewHolder(itemView);
        return holder;
    }

    public class ViewHolder extends RecyclerView.ViewHolder {
        TextView textView;

        public ViewHolder(View itemView) {
            super(itemView);
            textView = itemView.findViewById(R.id.iv_text);
        }
    }

    public interface OnItemClickLitener {
        void onItemClick(View view, int position);
    }

    public void setOnItemClickLitener(OnItemClickLitener mOnItemClickLitener) {
        this.mOnItemClickLitener = mOnItemClickLitener;
    }
}

ScollLinearLayoutManager类

import android.content.Context;
import android.graphics.PointF;
import android.util.DisplayMetrics;

import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.LinearSmoothScroller;
import androidx.recyclerview.widget.RecyclerView;

public class ScollLinearLayoutManager extends LinearLayoutManager {
    private float MILLISECONDS_PER_INCH = 25f;  //修改可以改变数据,越大速度越慢

    private Context contxt;

    public ScollLinearLayoutManager(Context context) {
        super(context);
        this.contxt = context;
    }

    @Override
    public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) {
        LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) {
                    @Override
                    public PointF computeScrollVectorForPosition(int targetPosition) {
                        return ScollLinearLayoutManager.this.computeScrollVectorForPosition(targetPosition);
                    }

                    @Override
                    protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
                        return MILLISECONDS_PER_INCH / displayMetrics.density; //返回滑动一个pixel需要多少毫秒
                    }

                };
        linearSmoothScroller.setTargetPosition(position);
        startSmoothScroll(linearSmoothScroller);
    }

    //可以用来设置速度
    public void setSpeedSlow(float x) {
        MILLISECONDS_PER_INCH = contxt.getResources().getDisplayMetrics().density * 0.3f + (x);
    }
}

LoopViewManager类

import android.app.Activity;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.LinearLayoutManager;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.SimpleViewManager;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import com.facebook.react.uimanager.UIManagerHelper; // <-- Import this
import com.facebook.react.uimanager.events.EventDispatcher; // <-- Import this
import com.shop.mova.marquee.InitChangeEvent; // <-- We will create this event class


/**
* author by sunny
* created at 2025/12/24 16:54
*/
public class LoopViewManager extends SimpleViewManager<CustomRecycleView> {
   ArrayList<String> dataList = new ArrayList<>();
  static final String REACT_CLASS = "LoopRecycleView";
  public final int COMMAND_NOTIFY_DATASET_CHANGED = 1;

   @Override
   public String getName() {
       return REACT_CLASS;
   }

   public LoopViewManager(ReactApplicationContext context){
   }

   @NonNull
   @Override
   public CustomRecycleView createViewInstance(@NonNull ThemedReactContext reactContext) {
        return new CustomRecycleView(reactContext);
   }

   @ReactProp(name = "dataList")
   public void setDataList(CustomRecycleView customRecycleView, ReadableArray list) {
       ArrayList<Object> viewList = list.toArrayList();
       ArrayList<String> newList = new ArrayList<>();
       viewList.forEach(item->{
           newList.add((String) item);
       });
       this.dataList = newList;
       System.out.println("=====dataList:"+dataList);
   }


   public void initAdapater(CustomRecycleView recycleView,ArrayList<String> list){
       // Get the context directly from the view itself.
       ThemedReactContext context = (ThemedReactContext) recycleView.getContext();
       Activity activity = context.getCurrentActivity();

       // It's crucial to check if the activity is null, as it can be during transitions.
       if (activity == null) {
           System.out.println("===== Activity is null, cannot init adapter =====");
           return;
       }

       ScollLinearLayoutManager scollLinearLayoutManager = new ScollLinearLayoutManager(activity);
       scollLinearLayoutManager.setSpeedSlow(25f);
       scollLinearLayoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
       recycleView.setHasFixedSize(false);
       recycleView.setLayoutManager(scollLinearLayoutManager);
       ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);
       recycleView.setLayoutParams(layoutParams);

       RecycleViewAdapter adapter = new RecycleViewAdapter(activity,list);
       recycleView.setAdapter(adapter);
       recycleView.smoothScrollToPosition(Integer.MAX_VALUE - 1);
   }

   @Nullable
   @Override
   public Map<String, Integer> getCommandsMap() {
       Map<String,Integer> map = new HashMap<>();
       map.put("notifyDataSetChanged", COMMAND_NOTIFY_DATASET_CHANGED);
       return map;
   }

       @Override
   public void receiveCommand(@NonNull CustomRecycleView recycleView, String commandId, ReadableArray args) {
       super.receiveCommand(recycleView, commandId, args);
       System.out.println("====== receiveCommand ====commandId: "+commandId);

//        val reactNativeViewId = requireNotNull(args).getInt(0)

           this.onReceiveNativeEvent(recycleView);

           switch (commandId){
               case "notifyDataSetChanged":
                   ReadableArray argsArray = args.getArray(0);
                   ArrayList<String> newList = new ArrayList<>();
                   assert argsArray != null;
                   ArrayList<Object> viewList = argsArray.toArrayList();
                   viewList.forEach(item->{
                       newList.add((String) item);
                   });
                   System.out.println("====== receiveCommand 2222===="+newList);
                   this.initAdapater(recycleView,newList);
                   break;
           }
   }

   //==========这种方式不能从原生发送事件给js =========
//    public void onReceiveNativeEvent(CustomRecycleView recycleView) {
//        // Get the context directly from the view itself.
//        ThemedReactContext context = (ThemedReactContext) recycleView.getContext();
////        Activity activity = context.getCurrentActivity();
////
////        // It's crucial to check if the activity is null, as it can be during transitions.
////        if (activity == null) {
////            System.out.println("===== Activity is null, cannot init adapter =====");
////            return;
////        }
//        WritableMap writableMap = new WritableNativeMap();
//        writableMap.putString("message", "MyMessage");
//        context.getJSModule(RCTModernEventEmitter.class)
//                .receiveEvent(3,10,"initChange",writableMap);
//    }

// ================ 从原生发送事件给js =============
public void onReceiveNativeEvent(CustomRecycleView recycleView) {
   ThemedReactContext context = (ThemedReactContext) recycleView.getContext();

   // 1. Get the EventDispatcher for the curÏrent UI context
   EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, recycleView.getId());

   if (eventDispatcher != null) {
       // 2. Create the event payload
       WritableMap writableMap = new WritableNativeMap();
       writableMap.putString("message", "MyMessage from native event");

       // 3. Dispatch a custom event. The event name "initChange" must match
       //    what you exported in getExportedCustomBubblingEventTypeConstants.
       eventDispatcher.dispatchEvent(
               new InitChangeEvent(
                       UIManagerHelper.getSurfaceId(context), // Surface ID
                       recycleView.getId(),                   // View ID (React Tag)
                       writableMap                            // Event Data
               )
       );
   }
}

   @Nullable
   @Override
   public Map getExportedCustomBubblingEventTypeConstants() {
       return MapBuilder.builder().put(
               "initChange",
               MapBuilder.of(
                       "phasedRegistrationNames",
                       MapBuilder.of("bubbled", "initEvent")
               )
       ).build();
   }
}

最后到js的封装调用,LoopView 类

import React, { Component, } from 'react'
import {
  requireNativeComponent,
  UIManager,
  findNodeHandle,
} from 'react-native'

const NativeLoopView = requireNativeComponent('LoopRecycleView');

export default class LoopView extends Component {

  ref = React.createRef();

  setNativeProps(props) {
    this.ref.current.setNativeProps(props)
  }

  notifyDataSetChanged(list) {
    console.log("==== notifyDataSetChanged ===", list)
    const viewId = findNodeHandle(this.ref.current);
    console.log("==== notifyDataSetChanged viewId===", viewId)
    UIManager.dispatchViewManagerCommand(
      viewId,
      "notifyDataSetChanged",
      [list]
    );
  }

  render() {
    return (
      <NativeLoopView {...this.props}
        ref={this.ref}
        style={{
          position: 'absolute',
          top: 0,
          left: 0,
          bottom: 0,
          right: 0,
          backgroundColor: 'blue'
        }}
        initEvent={(event)=>{//js接收原生发送过来的事件
          console.log("===== initEvent ===",event.nativeEvent)
        }} />
    );
  }

}

使用的时候,

nativeLoopViewRef = React.createRef();
<LoopView
            ref={this.nativeLoopViewRef}
            style={{ flex: 1 }}
            dataList={["11User1234 Dapatkan Rp100.000",
              "11User1234 Dapatkan Rp100.000",
              "11User1234 Dapatkan Rp100.000"
            ]}
             />

if (this.nativeLoopViewRef?.current) {
        console.log("===== nativeLoopViewRef =====")
        const list = ["11User1234 Dapatkan Rp100.000",
              "11User1234 Dapatkan Rp100.000",
              "11User1234 Dapatkan Rp100.000"
            ];
        this.nativeLoopViewRef.current.notifyDataSetChanged(list);
      }

网上看官方的文档,
image.png

这一行误导我很久,才知道是不对,应该写:

const viewId = findNodeHandle(this.ref.current);
 UIManager.dispatchViewManagerCommand(
      viewId,
      "notifyDataSetChanged",
      [list]
    );

上面是js主动调用原生的方法 , 如果是原生主动调用js的方法,参考代码如下:

// ================ 从原生发送事件给js =============
public void onReceiveNativeEvent(CustomRecycleView recycleView) {
   ThemedReactContext context = (ThemedReactContext) recycleView.getContext();

   // 1. Get the EventDispatcher for the curÏrent UI context
   EventDispatcher eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(context, recycleView.getId());

   if (eventDispatcher != null) {
       // 2. Create the event payload
       WritableMap writableMap = new WritableNativeMap();
       writableMap.putString("message", "MyMessage from native event");

       // 3. Dispatch a custom event. The event name "initChange" must match
       //    what you exported in getExportedCustomBubblingEventTypeConstants.
       eventDispatcher.dispatchEvent(
               new InitChangeEvent(
                       UIManagerHelper.getSurfaceId(context), // Surface ID
                       recycleView.getId(),                   // View ID (React Tag)
                       writableMap                            // Event Data
               )
       );
   }
}

   @Nullable
   @Override
   public Map getExportedCustomBubblingEventTypeConstants() {
       return MapBuilder.builder().put(
               "initChange",
               MapBuilder.of(
                       "phasedRegistrationNames",
                       MapBuilder.of("bubbled", "initEvent")
               )
       ).build();
   }
}

最后说下,Android stuido自带AI真好用, 把error复制过去,他自己就会结合你项目代码,给你搜索解决方法, 比网页上AI好用,起码不用你到处贴代码!!!!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容