【问题标题】:How to use addOnScrollListener with different lists in one activity如何在一个活动中使用带有不同列表的 addOnScrollListener
【发布时间】:2019-07-13 05:32:32
【问题描述】:

在我的应用程序中,有两种方法getDatagetItemsByLabel。每个都通过改造回调方法获得不同的列表,我使用导航抽屉方法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's 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. 在每个方法getDatagetItemsByLabel 中实现ScrollListener
  2. 为抽屉式导航菜单上的每个项目创建一个片段并在其上实现ScrollListener
  3. 最后,我在onScrolled 方法上放置了for 循环,以检测抽屉菜单上的哪个项目被选中以获取它自己的列表。但不幸的是,没有一个有效。

【问题讨论】:

  • 我无法理解这个问题...有多少个列表?何时调用 addOnScrollListener()? RecyclerViews 的创建在哪里?
  • @luca-murra MainActivity 类太大,我没有全部附加,以免分散代码阅读器的注意力,但是,如果这有帮助,我将编辑代码并附加它
  • 现在好多了,但我想我不明白是什么问题
  • 方法 getData 获取主列表并查看它,在导航抽屉菜单上有七个类别,当用户单击特定类别/项目时,方法 getItemsByLabel(String label); 被调用并根据给它的字符串参数查看列表,(这意味着除了 getData 方法生成的主列表之外还有七个不同的列表)这里的问题是当向下滚动发生在主列表和主列表与所选类别/项目的列表之间的显示列表中重叠的项目
  • 这是因为 ScrollListener 会检测滚动行为,而与所选菜单无关。我希望我已经澄清了问题并为延长道歉

标签: java android android-recyclerview retrofit2 onscrolllistener


【解决方案1】:

我认为将常见的addOnScrollListener 添加到您的RecyclerView 不会出现问题。我认为这个问题的发生是由于控制流的实现和线程之间的竞争条件,同时使用 API 调用获取列表。让我简要描述一下我的想法。

getDatagetItemsByLabel 函数中调用的 API 都是异步的,因此您无法确定 API 调用何时返回数据。因此,让我们考虑以下场景:

  1. getItemsByLabel 在您单击导航抽屉中的项目时调用。
  2. 与此同时,onScrolled 函数以某种方式被调用,getData 的 API 立即触发。
  3. getItemsByLabel 函数返回数据,清除 items 列表并开始向 items 插入新数据。
  4. items 列表中的数据被getItemsByLabel 清除并开始在items 中添加元素后,getData API 同时返回。
  5. 因此items 列表包含来自getDatagetItemsByLabel 的元素。
  6. ArrayList 实现不是线程安全的,因此很容易混合来自两个 API 响应的数据。

我希望这能解释您遇到的问题。为避免此问题,您可能会考虑使用 boolean 变量,例如 getItemsByLabelCalled,默认情况下可能具有值 false。在导航抽屉中单击项目时,设置getItemsByLabelCalled = true 的值。然后在以下情况下将元素添加到items 列表之前检查此值。

  1. getData 函数内部。 if(getItemsByLabelCalled) return
  2. getItemsByLabel 函数内部,设置和重置getItemsByLabelCalled 的值。请检查更新部分中的以下代码。

希望对您有所帮助!

更新

为您提供一个示例,说明您应该进行哪些更改:

使用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;
        }
    });
}

我认为不需要进行更多更改,但我不确定。让我知道这是否有效。

【讨论】:

  • 感谢分析代码,虽然理论上看起来合乎逻辑但不幸的是没有和我一起工作,一开始你说“在onScrolled函数内部。如果(getItemsByLabelCalled)
  • if (getItemsByLabelCalled) return; 这是我的建议。你想做什么?循环从何而来?
  • 我调用了 getItemsByLabel 并从抽屉菜单的标题中传递了字符串值。在第二步我按照你说的做,if(getItemsByLabelCalled){ return; } 3. 在getItemsByLabel 上设置getItemsByLabelCalled = false
  • 您不必在getData 函数中调用getItemsByLabel。让我为您更新一个示例演示的答案。
  • 我希望我能让您理解为什么会出现重叠问题。如果没有完整的代码,很难解决实际问题。但是,StackOverflow 并不适合共享整个代码,它鼓励发布需要特定答案的特定问题。我希望,我正确地回答了这个问题,即使它没有解决您遇到的整体问题。如果您需要任何进一步的帮助,请告诉我。我很乐意提供帮助。 :)
【解决方案2】:

我认为问题的出现是因为recyclerView.swapAdapter(adapter, false);

swapAdapter 用于将适配器与新适配器交换。你传递了同一个适配器,所以这个调用是没用的。即使你想设置一个新的适配器,布尔参数也应该为真,让 RecyclerView 回收所有现有的视图。

【讨论】:

  • 使用布尔参数true没有任何影响,问题是在我调用getData时的onScrolled方法导致当前项目之间的重叠列表和所选类别/项目的列表。
  • 删除那行 'swapAdapter' 怎么样?
【解决方案3】:

在recycleview中设置列表时可以使用flag,根据条件设置监听器

    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】:

    您必须...清除每个类别的主列表单击并恢复该列表以用于特定类别。

    【讨论】:

    • 为什么会有三个recyclerview?它只是应用程序中的一个recyclerView,我们还需要onScrollStateChangedonScrolled这两个方法
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-11-17
    • 1970-01-01
    • 2019-05-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-24
    相关资源
    最近更新 更多