This article is the blogger’s original article, reproduced please indicate the source.

preface

The development process will always encounter Scrollview nested ListView requirements, nested 1 ListView there are a lot of mature solutions, such as ListView item set height, ListView height adaptive, re-measure listView height and so on. However, each of the above methods has a disadvantage that it cannot take advantage of listView reuse, because during the calculation process of the above method, all items will be executed through the getView method.

In addition, if you need to nest two listViews in a Scrollview, what about three? How to handle sliding conflicts? This article provides a solution.

It is worth mentioning that this article is purely exploring the solution of nesting two ListViews in a Scrollview. Some friends will say that it is completely possible to use RecycleView, or custom LinearLayout to achieve this requirement, but it is another problem, and this article has nothing to do.

Results show





Problems faced

Nested two ListViews in a Scrollview face the following problems

  • When listView1 slides to the bottom, how do I get ListView2 to slide up as well?
  • How do you solve the reuse problem, when does listview1 load, when does listview2 load?
  • Does a swipe event conflict with a list click event?

Sliding conflicts between two ListViews and scrollViews

The solution

First we need to define a concept: mixed state, where two ListViews appear in a Scrollview at the same time.

So we can see that there are three total states: mixed state, ListView1 is full of ScrollView (if the data is large enough), and ListView2 is full of ScrollView (if the data is large enough). So the solution itself revolves closely around these three states. The core idea of resolving slide conflicts is to allow only one of the ListView and ScrollView to slide at the same time, while blocking the other. Let’s assume that ListView1 and ListView2 are both large enough. The solution is as follows:

  • ListView1 needs to listen for the slide to the bottom of the ListView1 event. If the slide to the bottom of the ListView1 event is blocked, the ScrollView slide event is enabled
mLv1.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: // This code cannot be left out, otherwise, after lv1 is rolled to the bottom, sv can not be started. It can no longer be loaded lv2 if (the getLastVisiblePosition () = = (the getCount () - 1)) {setMixStatus (true); } break; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // Empty } });Copy the code
  • ScrollView is always shielded from other ListViews while sliding
mSv.setScrollViewListener(new MultiScrollView.ScrollViewListener() { @Override public void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy) { ... // Mask the listView msv.forbidChildScroll (); }});Copy the code
  • During ScrollView sliding, if ListView2 is detected to fill the screen, mask ScrollView sliding and enable ListView2 sliding
mSv.setScrollViewListener(new MultiScrollView.ScrollViewListener() { @Override public void onScrollChanged(ScrollView scrollView, int x, int y, int oldx, int oldy) { int[] svLocation = new int[2]; mSv.getLocationOnScreen(svLocation); . int[] lv2Location = new int[2]; mLv2.getLocationOnScreen(lv2Location); if (lv2Location[1] == svLocation[1]) { mLv2.forbidParentScroll(); mSv.allowChildScroll(); mLv2.setMix(false); return; }... }});Copy the code
  • The reverse slide (from ListView2 to ListView1) requires listening for the ListView2 slide to the top. If ListView2 slides to the top, block ListView2 slide and enable ScrollView slide
mLv2.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { switch (scrollState) { case AbsListView.OnScrollListener.SCROLL_STATE_IDLE: / / lv2 scroll to the top, rolling shielding sv, open lv2 rolling the if (the getFirstVisiblePosition () = = 0) {mLv2. AllowParentScroll (); mSv.forbidChildScroll(); } break; } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, Int totalItemCount (visibleItemCount) {if (visibleItemCount == totalItemCount) {// The height of listView2 is smaller than that of the parent control mLv2.setMaxHeight(setListViewHeightBasedOnChildren(mLv2, 0)); mLv2.setMax(true); }}});Copy the code

Analysis of other cases

If ListView1 is small enough to fill the parent control, or ListView2 is small enough to fill the parent control. Or maybe both listViews have a small amount of data?

ListView1 has a small amount of data, ListView2 has a large amount of data

In this case, the first state must be the mixed state, so the question is, when do you set the mixed state? As you can see from the above solution, the mixed state is set when ListView1 slides to the bottom, but in the mixed state, ListView sliding is disabled. So, we need to complete the state setting when ListView1 is first loaded.

root.post(new Runnable() { @Override public void run() { int lv1Height = setListViewHeightBasedOnChildren(mLv1, root.getHeight()); if (lv1Height < root.getHeight()) { ... // Set the mixstatus setMixStatus(true); }}});Copy the code

ListView1 and ListView2 are both small

And the above situation is almost the same, do not repeat

ListView1 is large enough, ListView2 is small

In this case, because the ListView2 data volume is small, so after the ListView2 slide to the bottom, the reverse slide, has been in a mixed state, so there is no problem in the above scheme, no need for special treatment.

ListView display incomplete problem

This problem is well known, and the solutions are well established. Most of them require specifying the height of the ListView, or remeasuring the height of the ListView, and so on. In each of these scenarios, there is a definite failure to use ListView’s reuse feature. All items are loaded at once. How do you solve the reuse problem? Look at the analysis below

ListView reuse problem

If the amount of data is small, or if the operations in the getView method are simple enough, this problem can be ignored. If ListView1 and ListView2 are loaded at the same time, it is not acceptable to load all items of ListView1 and ListView2 at once. The core idea to solve this problem is to load as much as you display as you fetch. Details as follows:

ListView1

There are two scenarios: ListView1 is large enough to hold the parent control; The other is small enough not to hold all the parent controls. You can see that the maximum display boundary of ListView1 is the height of the parent control and the minimum display boundary is the actual size of ListView1 itself. It can be seen from here that obtaining the height of the parent control needs to be completed first, otherwise the subsequent judgment can not be carried out. The code to get the height of the parent control is as follows:

root.post(new Runnable() { @Override public void run() { ... root.getHeight(); . }});Copy the code

Then you need to re-measure ListView1, which involves the ListView’s method of measuring the height

ListView measurement method improvement

The traditional method of measurement is to iterate over all items and then perform the calculation. We make a small change here. The method accepts a value for the maximum height, and if the maximum height is reached, the traversal is stopped. This ensures that the remaining item items are not preempted by the getView method

public static int setListViewHeightBasedOnChildren(ListView listView, int maxHeight) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
        return 0;
    }

    int totalHeight = 0;
    int dividerHeight = listView.getDividerHeight();
    for (int i = 0; i < listAdapter.getCount(); i++) {
        View listItem = listAdapter.getView(i, null, listView);
        listItem.measure(0, 0);
        totalHeight = totalHeight + listItem.getMeasuredHeight() + dividerHeight;

        if (totalHeight > maxHeight && maxHeight > 0) {
            ViewGroup.LayoutParams params = listView.getLayoutParams();
            params.height = maxHeight;
            listView.setLayoutParams(params);
            return maxHeight;
        }
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight;
    listView.setLayoutParams(params);

    return totalHeight;
}
Copy the code

ListView2

The solution is to dynamically change the height of ListView2 based on ScrollView sliding. Two questions arise here. One is what if ListView2 is too small to hold all the parent controls? We capture ListView2 at maximum height as follows:

mLv2.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { ... } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, Int totalItemCount (visibleItemCount) {if (visibleItemCount == totalItemCount) {// The height of listView2 is smaller than that of the parent control mLv2.setMaxHeight(setListViewHeightBasedOnChildren(mLv2, 0)); mLv2.setMax(true); }}});Copy the code

Second, if the height of ListView2 is too high, we need to limit the height of ListView2 to the height of the parent control. Otherwise, the reuse problem of redundant items will occur again. 3, dynamically set the height of ListView2, sometimes make the last item display incomplete, at this time we need to do a “correct” operation, that is, when ScrollView sliding, if found that ListView2 has reached the maximum state, but the actual height is not the maximum height, reset the height.

The code for ScrollView is as follows

mSv.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: mLastY = (int) event.getRawY(); break; case MotionEvent.ACTION_MOVE: int dy = (int) event.getRawY() - mLastY; If (mMix) {if (mlv2.ismax ()) {if (mlv2.getheight () < mlv2.getMaxheight ()) ViewGroup.LayoutParams layoutParams = mLv2.getLayoutParams(); layoutParams.height = mLv2.getMaxHeight(); mLv2.setLayoutParams(layoutParams); }} else if (dy < 0) {viewgroup.layoutParams LayoutParams = mlv2.getLayOutParams ();}} else if (dy < 0) {viewgroup.layoutParams = mlv2.getLayOutParams (); int dex = mLv2.getHeight() - dy; if (dex > root.getHeight()) { dex = root.getHeight(); } layoutParams.height = dex; mLv2.setLayoutParams(layoutParams); } } mLastY = (int) event.getRawY(); break; . } return false; }});Copy the code

Click event failure in mixed state

If the code stops at this point, sliding conflicts and reuse problems have all been solved. However, after repeated testing, there is a serious click event failure problem: When in the sliding state, because the sliding of ScrollView is enabled, the sliding of ListView is shielded, so that the clicking event of ListView is intercepted by ScrollView, and the items of ListView cannot be clicked.

The solution is to restore the ListView click event in the ACTION_UP action of the ScrollView, but at the same time, the mixed ListView only handles click events and ignores movement events.

ScrollView key code

mSv.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { ... case MotionEvent.ACTION_UP: mSv.allowChildScroll(); break; } return false; }});Copy the code

ListView key code

@Override public boolean dispatchTouchEvent(MotionEvent event) { if (mForbidParentScroll) { switch (event.getAction()) {  case MotionEvent.ACTION_DOWN: mScrollView.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_UP: mScrollView.requestDisallowInterceptTouchEvent(false); break; } } else { mScrollView.requestDisallowInterceptTouchEvent(false); if (mMix) { if (event.getAction() == MotionEvent.ACTION_MOVE) { return true; } } } return super.dispatchTouchEvent(event); }Copy the code

conclusion

So far, many problems of ScrollView nesting two ListViews have been solved, and the implementation ideas of two or more ListViews are similar. Finally, add a sentence, this paper is only “on the case”, pure analysis of ScrollView nested ListView, as to whether the demand itself needs to use this way, whether this way itself fits the demand itself, is not within the scope of this paper.

GitHub

The project address

Welcome star and Issue

The resources

  • www.jianshu.com/p/c1fc19795…
  • Blog.csdn.net/chaihuasong…
  • Blog.csdn.net/zhang_duo/a…
  • Blog. Qiji. Tech/archives / 12…
  • Blog.csdn.net/u010687392/…
  • Stackoverflow.com/questions/7…
  • www.eoeandroid.com/blog-108811…