Placing vertically scroll-able view into another vertically scroll-able view is a bit tedious in android. Here we show how to place
ScrollView
inside ScrollView
, ListView inside ScrollView, ListView
inside GridView
, etc without sacrificing the recycling feature of various AbsListView
s. And even we can place ListView
indide ScrollView
which in turn inside another ScrollView
, etc.A project which shows how to add a vertically scroll-able view into another vertically scroll-able view up to any level of depth is published in http://durgadass.github.io/ScrollInsideScroll.
Note Even though you can nest scroll views to any level, please consider the user experience. The project contains a complicated xml layout file
scroll_in_scroll.xml
which has no good user experience.
This post explains a more general issue - "Placing vertically scroll-able view into another vertically scroll-able view". More common use cases of this are "Placing
ListView
inside ScrollView
" and "Placing GridView
inside ExpandableListView
". The workaround given here is same for both these common use cases and others.Step 1
Write a method which determines whether aView
can be scrolled vertically and place the method inside a common utility class as follows. This method is taken from ViewPager.java
and modified to find whether a View
can vertically scroll-able.
public static boolean canScroll(View v, boolean checkV, int dy, int x, int y) { if (v instanceof ViewGroup) { final ViewGroup group = (ViewGroup) v; final int scrollX = v.getScrollX(); final int scrollY = v.getScrollY(); final int count = group.getChildCount(); for (int i = count - 1; i >= 0; i--) { final View child = group.getChildAt(i); if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && canScroll(child, true, dy, x + scrollX - child.getLeft(), y + scrollY - child.getTop())) { return true; } } } return checkV && ViewCompat.canScrollVertically(v, -dy); }
Step 2
Subclass the enclosing vertically scroll-able view, it may beScrollView
or ListView
, or the like and override the onInterceptTouchEvent()
method as follows.
public boolean onInterceptTouchEvent(MotionEvent event) { int action = event.getAction(); float x = event.getX(); float y = event.getY(); float dy = y - mLastMotionY; switch (action) { case MotionEvent.ACTION_DOWN: mLastMotionY = y; break; case MotionEvent.ACTION_MOVE: if (Util.canScroll(this, false, (int) dy, (int) x, (int) y)) { mLastMotionY = y; return false; } break; } return super.onInterceptTouchEvent(event); }
Step 3
Subclass the enclosed vertically scroll-able view, it may beGridView
or ListView
or the like and override the onMeasure()
method as follows. No need to override this method in ScrollView
. Its default implementation behaves in the right way.
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int mode = MeasureSpec.getMode(widthMeasureSpec); if (mode == MeasureSpec.UNSPECIFIED) { int height = getLayoutParams().height; if (height > 0) setMeasuredDimension(getMeasuredWidth(), height); } }
Step 4
Finally create an xml layout file as given below and see how it works. We have to hard code thelayout_height
of CustomListView
. If you create the view hierarchy by java code, then set the height via LayoutParams
.
<com.dass.scroll_inside_scroll.CustomScrollView android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" > <!-- Other Children --> <com.dass.scroll_inside_scroll.CustomListView android:layout_width="match_parent" android:layout_height="300dp" > </com.dass.scroll_inside_scroll.CustomListView> <!-- Other Children --> </LinearLayout> </com.dass.scroll_inside_scroll.CustomScrollView>
CustomListView
, CustomExpandableListView
, CustomGridView
overrides the method onInterceptTouchEvent()
in order to place other vertically scroll-able views inside them. If we don't add vertically scroll-able views inside any of these classes then no need to override onInterceptTouchEvent()
.
This hard coding is not necessary if you use your own measuring strategy rather than one specified in step 3. For example if your list contains 100 items and you decided to show half of your list size 50 then in
onMeasure()
of your ListView
can be changed as follows.protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int mode = MeasureSpec.getMode(widthMeasureSpec); if (mode == MeasureSpec.UNSPECIFIED) { int height = getHeightBasedOnChildCount();//Note the change here if (height > 0) setMeasuredDimension(getMeasuredWidth(), height); } } private int getHeightBasedOnChildCount(){ int height = //Write code to sum up heights of 50 children including the dividers return height; }
Please comment if you find this post useful or it does not works.
No comments:
Post a Comment