java - 当到达 RecyclerView 底部时从 API 接收下一页电影?

标签 java android android-recyclerview

我目前正在开发一个项目,其中我们必须开发一个从电影数据库 API ( https://www.themoviedb.org/ ) 检索电影的 Android 应用程序。该应用程序的功能之一是从 API 检索热门电影并将其显示在 RecyclerView 中,如下所示:

enter image description here

从此 URL 检索电影:https://api.themoviedb.org/3/movie/popular?api_key=4c422ac80f2c83f42b8f905d4303959d&language=en&page=1

如您所见,有 500 页。在 RecyclerView 中显示数据之前加载 Asynctask 中的所有 500 个页面并不是一个解决方案,因为加载所有内容需要很长时间。相反,我希望 RecyclerView 能够检测何时到达列表末尾,然后从 API 加载下一页。我不知道如何解决这个问题。

我非常接近一个解决方案,即向 RecyclerView 添加 OnScrollListener 并在 RecyclerView 无法垂直滚动 2 个项目时检索下一页,但我还没有得到正确的逻辑。使用我当前的代码,一旦接近第一页的末尾,它将立即加载其他 499 页,这使得新加载的电影无法单击,因为任务尚未完成。我需要让它工作,以便在加载第二个页面后,它再次检查 RecyclerView 是否无法垂直滚动 2 个项目。

在我的 UrlBuilder 类中构建 URL 的方法:

    public static URL buildPopularMovieListUrl(int page) {
        Log.d(TAG, "Method called: buildPopularMovieListUrl");

        // Paths and parameters are appended to the base URL.
        Uri builtUri = Uri.parse(BASE_URL_TMDB).buildUpon()
                .appendPath(MOVIE_PATH)
                .appendPath(POPULAR_PATH)
                .appendQueryParameter(PARAM_API_KEY, API_KEY)
                .appendQueryParameter(PARAM_LANGUAGE, LANGUAGE)
                .appendQueryParameter(PARAM_PAGE, String.valueOf(page))
                .build();

        URL url = null;
        try {
            url = new URL(builtUri.toString());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        Log.d(TAG, "Built URL: " + url);

        return url;
    }

MainActivityFragment.java

package nl.avans.cinetopia.presentation;

import android.os.Bundle;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import java.util.ArrayList;

import nl.avans.cinetopia.R;
import nl.avans.cinetopia.adapters.PopularMoviesRecyclerViewAdapter;
import nl.avans.cinetopia.data_access.UrlBuilder;
import nl.avans.cinetopia.data_access.get_requests.GenresGetRequest;
import nl.avans.cinetopia.data_access.get_requests.PopularMovieGetRequest;
import nl.avans.cinetopia.data_access.utilities.JsonUtils;
import nl.avans.cinetopia.domain.Movie;

public class MainActivityFragment extends Fragment implements PopularMoviesRecyclerViewAdapter.OnItemClickListener {
    private static final String TAG = MainActivityFragment.class.getSimpleName();

    // RecyclerView attributes
    private RecyclerView mRecyclerView;
    private PopularMoviesRecyclerViewAdapter mAdapter;
    private RecyclerView.LayoutManager mLayoutManager;
    private ArrayList<Movie> mMovies = new ArrayList<>();

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.activity_main_fragment, container, false);

        retrieveLatestGenresFromApi();
        retrievePopularMoviesFromApi(1);

        // Obtain a handle to the object.
        mRecyclerView = view.findViewById(R.id.activity_main_recyclerView);
        // Use a linear layout manager.
        mLayoutManager = new LinearLayoutManager(getActivity());
        // Connect the RecyclerView to the layout manager.
        mRecyclerView.setLayoutManager(mLayoutManager);

        // Specify an adapter.
        mAdapter = new PopularMoviesRecyclerViewAdapter(getActivity(), mMovies);
        // Connect the RecyclerView to the adapter.
        mRecyclerView.setAdapter(mAdapter);
        // Set OnItemClickListener.
        mAdapter.setOnItemClickListener(this);

        /* Add a divider to the RecyclerView. */
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(getActivity(), DividerItemDecoration.VERTICAL);
        dividerItemDecoration.setDrawable(getResources().getDrawable(R.drawable.rv_divider));
        mRecyclerView.addItemDecoration(dividerItemDecoration);

        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
                super.onScrollStateChanged(recyclerView, newState);

                for (int i = 2; i < 500; i++) {
                    if (!recyclerView.canScrollVertically(2)) {
                        retrievePopularMoviesFromApi(i);
                        mAdapter.notifyDataSetChanged();
                    }
                }
            }
        });

        return view;
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
    }


    private void retrievePopularMoviesFromApi(int page) {
        PopularMovieGetRequest task = new PopularMovieGetRequest(new PopularMovieApiListener());
        task.execute(UrlBuilder.buildPopularMovieListUrl(page));
    }

    private void retrieveLatestGenresFromApi() {
        GenresGetRequest task = new GenresGetRequest(new JsonUtils.GenresApiListener());
        task.execute(UrlBuilder.buildGenreUrl());
    }


    /**
     * Listener class for the PopularMovieGetRequest.
     */
    class PopularMovieApiListener implements PopularMovieGetRequest.PopularMovieApiListener {
        /**
         * Fills our global ArrayList with the retrieved movies and notifies the adapter that the
         * dataset has changed.
         *
         * @param movies The list of movies retrieved by our PopularMovieGetRequest.
         */
        @Override
        public void handleMovieResult(ArrayList<Movie> movies) {
            Log.d(TAG, "Method called: handleMovieResult");

            // Add all movies to our ArrayList and notify the adapter that the dataset has changed.
            mMovies.addAll(movies);
            mAdapter.notifyDataSetChanged();
        }
    }

    @Override
    public void onItemClick(int position) {
        getActivity().getSupportFragmentManager().beginTransaction()
                .replace(R.id.activity_main_frameLayout, new MovieDetailsActivity(mMovies.get(position).getId()))
                .addToBackStack(null).commit();
    }
}

最佳答案

为什么使用原始代码而不是 Retrofit 库从服务器获取数据? Retrofit 非常容易从服务器获取数据。只需使用对象进行 API 调用即可。您可以轻松地使用库进行分页。请参阅此处如何实现它 https://github.com/square/retrofit

关于java - 当到达 RecyclerView 底部时从 API 接收下一页电影?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60934614/

相关文章:

java - 阻止 RecyclerView 加载所有数据

android - 我可以在缩放时将 RecyclerView 中的项目固定到中心吗

java - MyBatis 3.1.1,如何直接执行自定义SQL代码

java - Socket.io 无需加入即可收听房间

java - JTextArea 行中的字符限制

java - 使用 Chudnovsky 算法计算圆周率时出错 - Java

android - 约束布局 : z-index issue

android - 在 Android 上存储传感器数据和转储事件文件

java - WebView - 如果存在后退历史记录,则仅显示后退按钮

java - 应用程序在访问 RecyclerView 时崩溃