本文介绍了React Native中的RecyclerView:notifyItemInserted()和notifyDataSetChanged()无效的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试将Firebase支持的RecyclerView集成到React Native应用程序中.使用硬编码的数据可以很好地工作,但是在插入动态加载的行并在RecyclerView.Adapter上调用notifyItemInserted()或notifyDataSetChanged()时,RecyclerView本身不会反映该更改.在点击应用程序的刷新按钮之前,这将显示为初始空白视图,其唯一作用是从React Native端重新渲染RecyclerView,或者直到滚动或屏幕方向改变为止.

I am experimenting with integrating a Firebase-backed RecyclerView in a React Native app. With hardcoded data it works well, but upon inserting rows loaded dynamically and calling either notifyItemInserted() or notifyDataSetChanged() on the RecyclerView.Adapter, the RecyclerView itself does not reflect the change. This manifests as an initial blank view until the app's refresh button is tapped, which has the sole effect of re-rendering the RecyclerView from the React Native side, or until scrolling or a screen orientation change.

在阅读了本网站上的十几个或更多问题后,发现不涉及React Native的类似问题,并尝试了大多数常见解决方案后,我怀疑此问题与React Native有关.

After reading through a dozen or more questions on this site about similar issues not involving React Native and having tried most of the common solutions, I suspect this issue is related specifically to React Native.

鉴于React Native的列表视图实现中存在持续的性能问题,找到解决方案可能对许多人有帮助.

Finding a solution to this could be helpful to many, given the ongoing performance issues with React Native's list view implementations.

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不能很好地工作.

The problem is that requestLayout does not work well when the RecyclerView is a native UI component.

以下黑客使所有这些问题都消失了:

The following hack made all those issues go away:

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

I now overwrite the requestLayout method inside my RecyclerView.Then before any notify* method, or even scrollToPosition calls or any method that invokes a re-layout, I allow my custom requestLayout method to force a re-layout.

最终结果如下:

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());
            }
        });
    }
}

这篇关于React Native中的RecyclerView:notifyItemInserted()和notifyDataSetChanged()无效的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

07-17 15:41
查看更多