Как использовать addOnScrollListener с разными списками в одном упражнении

В моем приложении есть два метода getData и getItemsByLabel. Каждый получает разные списки с помощью модифицированного метода обратного вызова, и я использовал метод навигационного ящика onNavigationItemSelected, чтобы каждый раз, когда пользователь нажимал на определенный элемент, в RecyclerView отображался другой список.

Проблема в том, что я использую метод addOnScrollListener для обнаружения поведения прокрутки из любого списка в RecyclerView, который вызывает наложение элементов в отображаемом списке.

Следовательно, проблема заключается в том, что при прокрутке элементы основного списка и списка выбранной категории/элемента перекрываются.

Вот мой код.

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Fabric.with(this, new Crashlytics());

    // Crashlytics.logException(new Exception("My first Android non-fatal error"));
    // I'm also creating a log message, which we'll look at in more detail later
    // Crashlytics.log("MainActivity started");

    swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout);
    recyclerView = findViewById(R.id.recyclerView);
    manager = new LinearLayoutManager(this);
    emptyView = (TextView) findViewById(R.id.empty_view);
    progressBar = findViewById(R.id.spin_kit);
    adapter = new PostAdapter(this, items);
    recyclerView.setLayoutManager(manager);
    recyclerView.setAdapter(adapter);
    toolbar = findViewById(R.id.toolbar);
    setSupportActionBar(toolbar);
    getSupportActionBar().setDisplayShowTitleEnabled(false);
    toolbar.setTitle(R.string.home);

    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
            this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
    drawer.addDrawerListener(toggle);
    toggle.syncState();
    NavigationView navigationView = findViewById(R.id.nav_view);
    navigationView.getMenu().getItem(0).setChecked(true);
    navigationView.setNavigationItemSelectedListener(this);
    swipeRefreshLayout.setColorSchemeColors(getResources().getColor(R.color.colorPrimaryGreen));
    swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
        @Override
        public void onRefresh() {
            if (navigationView.getMenu().getItem(0).isChecked()) {
                if (Utils.hasNetworkAccess(MainActivity.this)) {
                    getData();
                } else {
                    Toast.makeText
                            (MainActivity.this, "You must connect to the Internet to update the list"
                                    , Toast.LENGTH_LONG).show();
                }
            } else {
                for (int i = 1; i < 7; i++) {
                    if (navigationView.getMenu().getItem(i).isChecked()) {
                        getItemsByLabel(navigationView.getMenu().getItem(i).getTitle().toString());
                    }
                }
            }

            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    swipeRefreshLayout.setRefreshing(false);
                }
            }, 3000);
        }

    });

    recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
            if (newState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
                isScrolling = true;
                if (!recyclerView.canScrollVertically(1)) {
                    progressBar.setVisibility(View.GONE);
                }
            }
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            if (dy > 0) {
                currentItems = manager.getChildCount();
                totalItems = manager.getItemCount();
                scrollOutItems = manager.findFirstVisibleItemPosition();
                if (isScrolling && (currentItems + scrollOutItems == totalItems)) {
                    isScrolling = false;
                    getData(); // This is where I call getData <--
                }
            }
        }
    });

    if (Utils.hasNetworkAccess(this)) {
        getData();
    } else {
        if (runtimeExceptionDaoItems == null || runtimeExceptionDaoItems.queryForAll().isEmpty()) {
            Toast.makeText(this, "There no data", Toast.LENGTH_LONG).show();
        } else {
            items.addAll(runtimeExceptionDaoItems.queryForAll());
            Toast.makeText(this, "From Database", Toast.LENGTH_LONG).show();
        }
    }
}

long lastPress;
Toast backpressToast;

@Override
public void onBackPressed() {
    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    if (drawer.isDrawerOpen(GravityCompat.START)) {
        drawer.closeDrawer(GravityCompat.START);
    } else {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastPress > 5000) {
            backpressToast = Toast.makeText(getBaseContext(), "Press back again to exit", Toast.LENGTH_LONG);
            backpressToast.show();
            lastPress = currentTime;
        } else {
            if (backpressToast != null) backpressToast.cancel();
            super.onBackPressed();
        }
    }
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
    // Inflate the menu; this adds items to the action bar if it is present.
    getMenuInflater().inflate(R.menu.main, menu);
    SearchManager searchManager = (SearchManager) getSystemService(SEARCH_SERVICE);
    SearchView searchView = (SearchView) menu.findItem(R.id.app_bar_search).getActionView();
    searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
    searchView.setQueryHint(getResources().getString(R.string.searchForPosts));
    searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
        @Override
        public boolean onQueryTextSubmit(String keyword) {
            getItemsBySearch(keyword);
            return false;
        }

        @Override
        public boolean onQueryTextChange(String keyword) {
            return false;
        }
    });

    searchView.setOnCloseListener(() -> {
        emptyView.setVisibility(View.GONE);
        recyclerView.setVisibility(View.VISIBLE);
        getData();
        return false;
    });

    return true;
}

@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    // Handle navigation view item clicks here.

    switch (item.getItemId()) {
        case R.id.home:
            getData();
            break;
        case R.id.accessory:
            toolbar.setTitle(R.string.accessory);
            getItemsByLabel("Accessory");
            break;
        case R.id.arcade:
            toolbar.setTitle(R.string.arcade);
            getItemsByLabel("Arcade");
            break;
        case R.id.fashion:
            toolbar.setTitle(R.string.fashion);
            getItemsByLabel("Fashion");
            break;
        case R.id.food:
            toolbar.setTitle(R.string.food);
            getItemsByLabel("Food");
            break;
        case R.id.heath:
            toolbar.setTitle(R.string.heath);
            getItemsByLabel("Heath");
            break;
        case R.id.lifeStyle:
            toolbar.setTitle(R.string.lifestyle);
            getItemsByLabel("Lifestyle");
            break;
        case R.id.sports:
            toolbar.setTitle(R.string.sports);
            getItemsByLabel("Sports");
            break;
        case R.id.settings:
            break;
    }

    DrawerLayout drawer = findViewById(R.id.drawer_layout);
    drawer.closeDrawer(GravityCompat.START);
    return true;
}

private void getData() {
    progressBar.setVisibility(View.VISIBLE);
    String url = BloggerAPI.BASE_URL + "?key=" + BloggerAPI.KEY;

    if (token != "") {
        url = url + "&pageToken=" + token;
    }
    if (token == null) {
        return;
    }

    final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
    postList.enqueue(new Callback<PostList>() {
        @Override
        public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
            if (response.isSuccessful()) {
                progressBar.setVisibility(View.GONE);
                PostList list = response.body();
                if (list != null) {
                    token = list.getNextPageToken();
                    items.addAll(list.getItems());
                    adapter.notifyDataSetChanged();
            } else {
                progressBar.setVisibility(View.GONE);
                recyclerView.setVisibility(View.GONE);
                emptyView.setVisibility(View.VISIBLE);

                int sc = response.code();
                switch (sc) {
                    case 400:
                        Log.e("Error 400", "Bad Request");
                        break;
                    case 404:
                        Log.e("Error 404", "Not Found");
                        break;
                    default:
                        Log.e("Error", "Generic Error");
                }
            }
        }

        @Override
        public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
            Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: " + t.toString());
            Log.e(TAG, "onFailure: " + t.getCause());
            progressBar.setVisibility(View.GONE);
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.VISIBLE);
        }
    });
}

//=============================================================================================

public void getItemsByLabel(String label) {
    progressBar.setVisibility(View.VISIBLE);
    String url = BloggerAPI.BASE_URL + "search?q=label:" + label + "&key=" + BloggerAPI.KEY;

    Log.e("Label :", url);

    if (token != "") {
        url = url + "&pageToken=" + token;
    }
    if (token == null) {
        return;
    }

    final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
    postList.enqueue(new Callback<PostList>() {
        @Override
        public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
            if (response.isSuccessful()) {
                progressBar.setVisibility(View.GONE);
                items.clear();
                recyclerView.swapAdapter(adapter, false);
                PostList list = response.body();
                if (list != null) {
                    token = list.getNextPageToken();
                    items.addAll(list.getItems());
                    adapter.notifyDataSetChanged();
                }
            } else {
                progressBar.setVisibility(View.GONE);
                recyclerView.setVisibility(View.GONE);
                emptyView.setVisibility(View.VISIBLE);
                int sc = response.code();
                switch (sc) {
                    case 400:
                        Log.e("Error 400", "Bad Request");
                        break;
                    case 404:
                        Log.e("Error 404", "Not Found");
                        break;
                    default:
                        Log.e("Error", "Generic Error");
                }
            }
        }

        @Override
        public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
            Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: " + t.toString());
            Log.e(TAG, "onFailure: " + t.getCause());
            progressBar.setVisibility(View.GONE);
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.VISIBLE);
        }
    });
}

Методы, которые я пытался решить эту проблему:

  1. Реализация ScrollListener внутри каждого метода getData и getItemsByLabel
  2. Создайте фрагмент для каждого элемента в меню навигационной панели и внедрите в него ScrollListener.
  3. И наконец, я включил цикл для метода onScrolled, чтобы определить, какой пункт в меню ящика проверен, чтобы получить собственный список. Но, к сожалению, никто не работает.

Ответы

Ответ 1

Я не думаю, что возникает проблема добавления общего addOnScrollListener к вашему RecyclerView. Я думаю, что эта проблема возникает из-за реализации потока управления и состояния гонки между потоками при получении списков с помощью вызовов API. Позвольте мне кратко описать мою идею.

Оба API, вызываемые в функциях getData и getItemsByLabel, являются асинхронными, и, следовательно, вы не можете гарантировать, когда вызов API вернется с данными. Поэтому давайте подумаем о следующем сценарии:

  1. getItemsByLabel вызывается, когда вы щелкаете по элементу в панели навигации.
  2. Между тем, функция onScrolled была вызвана каким-то образом, и API для getData сработал немедленно.
  3. Функция getItemsByLabel вернулась с данными, очистила список items и начала вставлять новые данные в items.
  4. API getData возвратился в то же самое время, сразу после того, как данные в списке items были очищены getItemsByLabel и началось добавление элементов в items.
  5. Таким образом, список items содержит элементы из getData и getItemsByLabel.
  6. Реализация ArrayList не является поточно-ориентированной, поэтому она склонна к смешиванию данных из обоих ответов API.

Я надеюсь, что это объясняет проблему, с которой вы столкнулись. Чтобы избежать этой проблемы, вы можете рассмотреть возможность использования переменной boolean, например getItemsByLabelCalled, которая по умолчанию может иметь значение false. Когда по элементу навигации щелкают по элементу, установите значение getItemsByLabelCalled = true. Затем проверьте это значение перед добавлением элементов в список items в следующих случаях.

  1. Внутри функции getData. if(getItemsByLabelCalled) return
  2. Внутри функции getItemsByLabel установите и сбросьте значение getItemsByLabelCalled. Пожалуйста, проверьте следующий код в разделе ОБНОВЛЕНИЕ.

Надеюсь, это поможет!

UPDATE

Чтобы дать вам демонстрацию того, какие изменения вы должны сделать:

Возьмите boolean для отслеживания при вызове функции.

private boolean getItemsByLabelCalled = false; 

Теперь измените функцию getData следующим образом.

private void getData() {
    if(getItemsByLabelCalled) return;

    // Other statements are the same as before
}

Теперь измените функцию getItemsByLabel, чтобы установить переменную getItemsByLabelCalled.

public void getItemsByLabel(String label) {

    // Here is the change
    if(getItemsByLabelCalled) return; 
    else getItemsByLabelCalled = true;

    progressBar.setVisibility(View.VISIBLE);
    String url = BloggerAPI.BASE_URL + "search?q=label:" + label + "&key=" + BloggerAPI.KEY;

    Log.e("Label :", url);

    if (token != "") {
        url = url + "&pageToken=" + token;
    }
    if (token == null) {
        return;
    }

    final Call<PostList> postList = BloggerAPI.getService().getPostList(url);
    postList.enqueue(new Callback<PostList>() {
        @Override
        public void onResponse(@NonNull Call<PostList> call, @NonNull Response<PostList> response) {
            if (response.isSuccessful()) {
                progressBar.setVisibility(View.GONE);
                items.clear();
                recyclerView.swapAdapter(adapter, false);
                PostList list = response.body();
                if (list != null) {
                    token = list.getNextPageToken();
                    items.addAll(list.getItems());
                    adapter.notifyDataSetChanged();
                }

                // Reset again here
                getItemsByLabelCalled = false;
            } else {
                // Reset again here
                getItemsByLabelCalled = false;

                progressBar.setVisibility(View.GONE);
                recyclerView.setVisibility(View.GONE);
                emptyView.setVisibility(View.VISIBLE);
                int sc = response.code();
                switch (sc) {
                    case 400:
                        Log.e("Error 400", "Bad Request");
                        break;
                    case 404:
                        Log.e("Error 404", "Not Found");
                        break;
                    default:
                        Log.e("Error", "Generic Error");
                }
            }
        }

        @Override
        public void onFailure(@NonNull Call<PostList> call, @NonNull Throwable t) {
            Toast.makeText(MainActivity.this, "Error occured", Toast.LENGTH_LONG).show();
            Log.e(TAG, "onFailure: " + t.toString());
            Log.e(TAG, "onFailure: " + t.getCause());
            progressBar.setVisibility(View.GONE);
            recyclerView.setVisibility(View.GONE);
            emptyView.setVisibility(View.VISIBLE);

            // Reset again here
            getItemsByLabelCalled = false;
        }
    });
}

Я думаю, что больше никаких изменений не требуется, хотя я не уверен. Дай мне знать, если это работает.

Ответ 2

Я думаю, что проблема возникает из-за recyclerView.swapAdapter(adapter, false);

swapAdapter используется для замены адаптера на новый. Вы передаете тот же адаптер, поэтому этот вызов бесполезен. Даже если вы хотите установить новый адаптер, логический параметр должен иметь значение true, чтобы позволить RecyclerView перезапускать все существующие представления.

Ответ 3

Вы можете использовать флаг, когда настраиваете список в recycleview и настраиваете прослушиватель в соответствии с условиями

    protected void getRefreshView() {
    int childs = getChildCount();
    if (childs > 0) {
        View childView = getChildAt(0);
        if (childView instanceof RecyclerView) {
            mRecyclerView = (RecyclerView) childView;
            mRecyclerView.setOnScrollListener(mScrollListener);
            mRecyclerView.setOnScrollListener(new OnScrollListener() {
            });
        }
    }
}

Ответ 4

Вы должны... очистить основной список по каждой категории, щелкнуть и восстановить этот список для категории твердых частиц.