用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好用,起码不用你到处贴代码!!!!