android - onScrolled 在用户不滚动的情况下被调用(回收器 View 无限滚动)

标签 android xml android-recyclerview linearlayoutmanager

尝试了所有相关问题,但没有成功:(

我正在使用 Recycler View 构建无限滚动。

问题:始终会在用户不滚动屏幕的情况下调用 onScrolled 方法。

Android 文档指南对此 onScrolled 方法进行了描述:

Case 1) Callback method to be invoked when the RecyclerView has been scrolled. This will be called after the scroll has completed.

Case 2) This callback will also be called if visible item range changes after a layout calculation. In that case, dx and dy will be 0.

由于情况 2,我的 onScrolled 方法在用户没有滚动的情况下被调用。我在屏幕中记录了 dy,结果发现 onScrolled 是通过 dy=0 调用的。这发生在第二种情况。

如果我使用:

if (dy > 0){
    // The user is scrolling down
}

事实证明,无论我滚动 Recycler View 多少次,我的 onScrolled 方法都不会被调用。

我的代码

RecyclerAdapterSmallVideos.java

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

    private List<VideosBigSize> videosBigSizeList;
    private Context mContext;

    final int VIEW_TYPE_ITEM = 0, VIEW_TYPE_LOADING = 1;
    ILoadMore loadMore;
    boolean isLoading;
    int visibleThreshold = 5;
    int lastVisibleItem, totalItemCount;

    public RecyclerAdapterSmallVideos(RecyclerView recyclerView, Context context, List<VideosBigSize> videosBigSizeList) {
        this.videosBigSizeList = videosBigSizeList;
        this.mContext = context;

        final LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);

                totalItemCount = linearLayoutManager.getItemCount();
                lastVisibleItem = linearLayoutManager.findLastVisibleItemPosition();
                if (!isLoading && totalItemCount <= (lastVisibleItem + visibleThreshold)) {

                    if (loadMore != null) {

                        // This is running without the user scrolling the Recycler View. 
                        loadMore.onLoadMore();
                    }
                    isLoading = true;

                }

            }
        });


    }


    @Override
    public int getItemViewType(int position) {
        return videosBigSizeList.get(position) == null ? VIEW_TYPE_LOADING: VIEW_TYPE_ITEM;
    }


    public void setLoadMore(ILoadMore loadMore) {
        this.loadMore = loadMore;
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {

        if (viewType == VIEW_TYPE_ITEM) {

            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_small_video, parent, false);
            return new ViewHolderSmallVideos(view);
        } else if (viewType == VIEW_TYPE_LOADING) {

            View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_loading, parent, false);
            return new ViewHolderLoading(view);
        }
        return null;
    }


    @Override
    public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {

        /*########### Case 1) The holder is a instance off ViewHolderSmallVideos (Items) ###########*/
        if (holder instanceof ViewHolderSmallVideos) {

            Log.i("Debugging", "onBindViewHolder: Binding instance of ViewHolder SmallVideos: " + holder.getAdapterPosition());

            VideosBigSize singleVideoBigSize = videosBigSizeList.get(position);

            ViewHolderSmallVideos viewHolderSmallVideos = (ViewHolderSmallVideos) holder;

            // Step 1) Images
            GlideApp.with(viewHolderSmallVideos.thumbNail.getContext())
                    .load(singleVideoBigSize.getThumnail_url())
                    .centerCrop()
                    .into(viewHolderSmallVideos.thumbNail); // Video Thumbnail

            // Step 2) Determine the Max Length for each TextView
            setTextMaxLength(viewHolderSmallVideos.videoTitle, viewHolderSmallVideos.channelName, mContext);

            // Step 3) Texts
            viewHolderSmallVideos.videoTitle.setText(singleVideoBigSize.getVideo_title());
            viewHolderSmallVideos.channelName.setText(singleVideoBigSize.getChannel_name());
            viewHolderSmallVideos.videoViews.setText(singleVideoBigSize.getVideo_views());
            viewHolderSmallVideos.videoUploadDate.setText(singleVideoBigSize.getVideo_upload_date());
            viewHolderSmallVideos.videoDuration.setText(singleVideoBigSize.getVideo_duration());

            // Step 4) Handle clicks
            viewHolderSmallVideos.constraintLayoutEachItem.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext, String.valueOf(holder.getAdapterPosition()), Toast.LENGTH_SHORT).show();
                }
            });
        }

        /*########### Case 2) The holder is a instance off ViewHolderLoading (progressBar) ###########*/
        else if (holder instanceof ViewHolderLoading) {

            Log.i("Debugging", "onBindViewHolder: Binding instance of ViewHolder Loading: " + holder.getAdapterPosition());

            ViewHolderLoading viewHolderLoading = (ViewHolderLoading) holder;
            viewHolderLoading.progressBar.setIndeterminate(true);

        }

    }

    /**
     * Get the number of items in the adapter
     * @return the number of items in the adapter
     */
    @Override
    public int getItemCount() { return videosBigSizeList.size(); }


    public void setLoaded() {
        isLoading = false;
    }

    /**
     * Determine a maximum number of characters for the Title and Channel Name
     * If they are too big and the width of the screen is too small, the text
     * will get bigger than the thumbnail and then the layout will be disturbed.
     * @param videoTitle the title of the video
     * @param channelName the name of the channel who uploaded the video
     * @param context the activity context
     */
    private void setTextMaxLength(TextView videoTitle, TextView channelName, Context context) {

        DisplayMetrics displayMetrics = new DisplayMetrics();
        WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

        if (windowManager != null) {

            // Get the current width of the screen in DP
            windowManager.getDefaultDisplay().getMetrics(displayMetrics);
            float dpWidth = displayMetrics.widthPixels / displayMetrics.density;

            // 1) If the device is 420dp or less;
            // 2) Limit the number of characters for the title and channel name
            if (dpWidth <= 420) {
                videoTitle.setFilters(new InputFilter[]{new InputFilter.LengthFilter(35)});
                channelName.setFilters(new InputFilter[]{new InputFilter.LengthFilter(25)});
            }
        }

    }

}

ProfileFragment.java

private void setupRecyclerView(final List<VideosBigSize> userAllVideosList) {

        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(mContext);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mRecyclerView.setNestedScrollingEnabled(false);
        final RecyclerAdapterSmallVideos adapterSmallVideos = new RecyclerAdapterSmallVideos(mRecyclerView, mContext, userAllVideosList);
        mRecyclerView.setAdapter(adapterSmallVideos);

        // Set Load more event
        adapterSmallVideos.setLoadMore(new ILoadMore() {
            @Override
            public void onLoadMore() {
                if (userAllVideosList.size() <= 50) {
                    mRecyclerView.post(new Runnable() {
                        @Override
                        public void run() {
                            userAllVideosList.add(null); 
                            adapterSmallVideos.notifyItemInserted(userAllVideosList.size() - 1);

                        }
                    });
                    new Handler().postDelayed(new Runnable() {
                        @Override
                        public void run() {
                            userAllVideosList.remove(userAllVideosList.size() - 1);
                            adapterSmallVideos.notifyItemRemoved(userAllVideosList.size()); 

                            // Random more data
                            int index = userAllVideosList.size();
                            int end = index + 10;
                            for (int i = index; i < end; i++) {
                                // Add more data to userAllVideosList...
                                adapterSmallVideos.notifyItemInserted(i);

                            }

                            adapterSmallVideos.setLoaded();

                        }
                    }, 2000);
                }
                // Loaded everything already
                else {
                    Toast.makeText(mContext, "Load data completed!", Toast.LENGTH_SHORT).show();
                }
            }
        });

    }

我的 ProfileFragment 布局

fragment_profile.xml

值得注意的是:

1) 我的 Recycler View 位于 NesteScrollView 内部,因为我需要隐藏/显示工具栏。记住我设置了 mRecyclerView.setNestedScrollingEnabled(false) 因为它会触发 NestedScrollView;

2)除了回收器 View 之外,我的布局中还有更多内容。

Profile Screen 1 Profile Screen 2

最佳答案

您应该使用分页库来做到这一点,click to see the best tutorial :

And the Github source code of the tutorial

事实证明,因为分页库是一个新功能,所以我只找到了 Kotlin 教程。然后我发现这可能是用 Java 编写的最好的一个。真的很感谢作者,我为此奋斗了整整一个月。

关于android - onScrolled 在用户不滚动的情况下被调用(回收器 View 无限滚动),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50777702/

相关文章:

java - 无法在抽屉导航 fragment 中包含 RecyclerView

java - 为什么 Dalvik 的 HttpURLConnection 中没有重写 getInputStream() ?

android - sqlite 时间戳

java - 图像布局安卓

Android按钮背景颜色不变

android - Recyclerview 项目的涟漪效应有时并不明显

android-recyclerview - 尝试调用虚拟方法 'void android.support.v7.widget.RecyclerView.setLayoutManager

android - 如何在使用 Retrofit 2 进行单元测试期间创建一个 retrofit.Response 对象

android - 在后台 android 中每 5 分钟运行一次 volley 请求

c# - 从 xsd 生成的代码中复数化属性