Как использовать 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);
}
});
}
Методы, которые я пытался решить эту проблему:
- Реализация
ScrollListener
внутри каждого метода getData
и getItemsByLabel
- Создайте фрагмент для каждого элемента в меню навигационной панели и внедрите в него
ScrollListener
.
- И наконец, я включил цикл для метода
onScrolled
, чтобы определить, какой пункт в меню ящика проверен, чтобы получить собственный список. Но, к сожалению, никто не работает.
Ответы
Ответ 1
Я не думаю, что возникает проблема добавления общего addOnScrollListener
к вашему RecyclerView
. Я думаю, что эта проблема возникает из-за реализации потока управления и состояния гонки между потоками при получении списков с помощью вызовов API. Позвольте мне кратко описать мою идею.
Оба API, вызываемые в функциях getData
и getItemsByLabel
, являются асинхронными, и, следовательно, вы не можете гарантировать, когда вызов API вернется с данными. Поэтому давайте подумаем о следующем сценарии:
getItemsByLabel
вызывается, когда вы щелкаете по элементу в панели навигации.
- Между тем, функция
onScrolled
была вызвана каким-то образом, и API для getData
сработал немедленно.
- Функция
getItemsByLabel
вернулась с данными, очистила список items
и начала вставлять новые данные в items
.
- API
getData
возвратился в то же самое время, сразу после того, как данные в списке items
были очищены getItemsByLabel
и началось добавление элементов в items
.
- Таким образом, список
items
содержит элементы из getData
и getItemsByLabel
.
- Реализация
ArrayList
не является поточно-ориентированной, поэтому она склонна к смешиванию данных из обоих ответов API.
Я надеюсь, что это объясняет проблему, с которой вы столкнулись. Чтобы избежать этой проблемы, вы можете рассмотреть возможность использования переменной boolean
, например getItemsByLabelCalled
, которая по умолчанию может иметь значение false
. Когда по элементу навигации щелкают по элементу, установите значение getItemsByLabelCalled = true
. Затем проверьте это значение перед добавлением элементов в список items
в следующих случаях.
- Внутри функции
getData
. if(getItemsByLabelCalled) return
- Внутри функции
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
Вы должны... очистить основной список по каждой категории, щелкнуть и восстановить этот список для категории твердых частиц.