【问题标题】:RecyclerView in React Native: notifyItemInserted() and notifyDataSetChanged() have no effectReact Native 中的 RecyclerView:notifyItemInserted() 和 notifyDataSetChanged() 没有效果
【发布时间】:2017-12-05 17:43:13
【问题描述】:

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

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

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

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>

【问题讨论】:

  • 可以在addPost(p)方法中插入日志吗?
  • 那里应该登录什么?
  • 刚刚添加了一个 RecyclerView.AdapterDataObserver 并从中记录。永远不会调用 onChanged() 方法。
  • 是的,我也想知道——数据是否被插入到 mDataset 中。
  • 数据肯定被插入,因为 getItemCount() 方法在滚动或方向改变后返回正确的值。但是 getItemCount() 在调用 addPost() 并因此 notifyItemInserted() 之后永远不会被调用。

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


【解决方案1】:

问题是requestLayoutRecyclerView 是本机UI 组件时无法正常工作。

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

【讨论】:

    【解决方案2】:

    ReactNative 中的根视图是 ReactRootView,onlayout 是一个空方法。

    在 RecyclerView 中调用 notifyDatasetChanged 时,实际上是请求 layout 重新布局其子级。并且布局方法会调用super.layout首先遍历整个视图树。所以当根视图是 ReactRootView 时,这不是问题。

    您可以手动调用RecyclerView.onlayout(boolean changed, int l, int t, int r, int b) 来触发其子重新布局以使 notifyDatasetChanged 工作。

    【讨论】:

    • 但是当 notify* 运行时,onLayout 甚至不会在 ReactRootView 内部被调用,我认为这不是问题所在。 ://
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-04-07
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-07
    相关资源
    最近更新 更多