android - React Native 中的 RecyclerView : notifyItemInserted() and notifyDataSetChanged() have no effect

标签 android reactjs react-native firebase-realtime-database android-recyclerview

我正在尝试将 Firebase 支持的 RecyclerView 集成到 React Native 应用程序中。对于硬编码数据,它运行良好,但是在插入动态加载的行并在 RecyclerView.Adapter 上调用 notifyItemInserted() 或 notifyDataSetChanged() 时,RecyclerView 本身不会反射(reflect)更改。这表现为一个初始的空白 View ,直到应用程序的刷新按钮被点击,这具有从 React Native 端重新渲染 RecyclerView 的唯一效果,或者直到滚动或屏幕方向改变。

在阅读了本网站上关于不涉及 React Native 的类似问题的十几个或更多问题并尝试了大多数常见解决方案后,我怀疑这个问题与 React Native 特别相关。

鉴于 React Native 的 ListView 实现的持续性能问题,找到解决方案可能对许多人有所帮助。

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

    ArrayList<Post> mDataset;

    public PostsAdapter(ArrayList<Post> list){
        mDataset = list;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public LinearLayout mPostView;
        public ViewHolder(LinearLayout v) {
            super(v);
            mPostView = v;
        }
    }

    @Override
    public PostsAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        LinearLayout v = (LinearLayout) LayoutInflater.from(parent.getContext())
                .inflate(R.layout.post, parent, false);
        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        TextView tv = (TextView) holder.mPostView.findViewById(R.id.title);
        tv.setText(mDataset.get(position).title);
    }

    @Override
    public int getItemCount() {
        return mDataset.size();
    }

}

 public class RNPostsViewManager extends SimpleViewManager{
    private ArrayList<Post> mDataset = new ArrayList<>();
    public static final String REACT_CLASS = "AndroidPostsView";
    private RecyclerView mRecyclerView;
    private PostsAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private DatabaseReference mDatabase;
    private Query initialDataQuery;
    private ChildEventListener initialDataListener;

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

    @UiThread
    public void addPost (Post p){
        mDataset.add(p);
        mAdapter.notifyItemInserted(mDataset.size()-1);
    }

    @Override
    public RecyclerView createViewInstance( ThemedReactContext context) {

        mAdapter = new PostsAdapter(mDataset);

        mRecyclerView = new RecyclerView(context){
            @Override
            protected void onAttachedToWindow() {
                super.onAttachedToWindow();
                initialDataQuery.addChildEventListener(initialDataListener);
            }
        };

        mLayoutManager = new LinearLayoutManager(context.getCurrentActivity(), LinearLayoutManager.VERTICAL, false);
        DividerItemDecoration mDecoration = new DividerItemDecoration(context, 1);
        mDecoration.setDrawable(ContextCompat.getDrawable(context.getCurrentActivity(), R.drawable.sep));
        mRecyclerView.setLayoutManager(mLayoutManager);
        mRecyclerView.addItemDecoration(mDecoration);
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
        mRecyclerView.setAdapter(mAdapter);

        mDatabase = FirebaseDatabase.getInstance().getReference();
        initialDataQuery = mDatabase.child("wp-posts").orderByChild("unixPostDate").limitToFirst(100);
        initialDataListener = new ChildEventListener() {
            @Override
            public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) {
                Post p = dataSnapshot.getValue(Post.class);
                addPost(p);
            }

            @Override
            public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) {
            }
            @Override
            public void onChildRemoved(DataSnapshot dataSnapshot) {
            }
            @Override
            public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) {
            }
            @Override
            public void onCancelled(DatabaseError databaseError) {
            }
        };

        return mRecyclerView;
    }

}

// layout file post.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    android:background="@android:color/background_light"
    android:clickable="true"
    android:focusable="true"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:padding="10dp"
    android:weightSum="1">

    <Button
        android:id="@+id/button"
        style="@style/Widget.AppCompat.Button.Small"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginRight="20dp"
        android:background="@android:drawable/ic_menu_more"
        android:gravity="center_vertical" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="fill_vertical"
        android:orientation="horizontal"
        android:weightSum="1">

        <TextView
            android:id="@+id/title"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_gravity="fill_vertical"
            android:layout_weight="1"
            android:gravity="top"
            android:text="TextView"
            android:textColor="@color/title"
            android:textStyle="bold" />
    </LinearLayout>

</LinearLayout>

最佳答案

问题是当 RecyclerView 是原生 UI 组件时,requestLayout 无法正常工作。

以下 hack 解决了所有这些问题:

我现在覆盖 RecyclerView 中的 requestLayout 方法。 然后在任何 notify* 方法,甚至 scrollToPosition 调用或任何调用重新布局的方法之前,我允许我的自定义 requestLayout 方法强制重新布局。

最终结果是这样的:

private boolean mRequestedLayout = false;


public void aMethodThatUpdatesStuff(int indexToUpdate, ReadableMap updatedChild) {
    final SPAdapter adapter = (SPAdapter) getAdapter();
    mRequestedLayout = false;
    adapter.updateDataAtIndex(indexToUpdate, updatedChild); // <-- this runs notifyItemChanged inside
}

@Override
public void requestLayout() {
    super.requestLayout();
    // We need to intercept this method because if we don't our children will never update
    // Check https://stackoverflow.com/questions/49371866/recyclerview-wont-update-child-until-i-scroll
    if (!mRequestedLayout) {
        mRequestedLayout = true;
        this.post(new Runnable() {
            @SuppressLint("WrongCall")
            @Override
            public void run() {
                mRequestedLayout = false;
                layout(getLeft(), getTop(), getRight(), getBottom());
                onLayout(false, getLeft(), getTop(), getRight(), getBottom());
            }
        });
    }
}

关于android - React Native 中的 RecyclerView : notifyItemInserted() and notifyDataSetChanged() have no effect,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/44868899/

相关文章:

android - 改变声音/点击软键盘 - android

java - 如何一键对齐两个文本

javascript - 三元运算符?在 Javascript 中从预期向后解析

reactjs - 使用 React、Typescript 和 ES6 进行 Jest 测试

reactjs - Prop 未定义,但我可以看到它控制台记录 Prop

ios - 在 React Native 中打开 iOS 中的某个 HealthKit 屏幕

Android服务不显示日志?

android - Android 4.3 上的系统跟踪

reactjs - react 导航 route.params typescript

javascript - 切换导航器和选项卡导航器冲突