2013년 10월 10일 목요일

ScrollView 내의 삽입된 ListView의 height가 조절되지 않는 현상

이전 블로그에서 이전 함 (원본 글 2013/10/10 작성)

예전에 경험하고 그냥 넘어갔었는데..
오늘 다시 같은 것을 가지고 삽질 했음. 정리의 필요성!!!을 새삼 느낌. ㅜㅜ

<ScrollView>
...
   <ListView>
</ScrollView>

와 같은 경우 ListView의 크기가 하나의 item 크기로 고정되어 버리거나 의도한 대로 보이지 않는 현상이 발생

Android에서는 ScrollView를 설명할 때 다음과 같은 언급을 하고 있다.

 You should never use a ScrollView with a ListView, because ListView takes care of its own vertical scrolling. Most importantly, doing this defeats all of the important optimizations in ListView for dealing with large lists, since it effectively forces the ListView to display its entire list of items to fill up the infinite container supplied by ScrollView.

ListView와 ScrollView는 각자 vertical scrolling을 관리하므로 서로 간섭을 일으켜서 원치 않는 결과가 발생된다. 사실 내가 본건 scrollview가 listview안에 들어가 있는게 아니라 반대의 경우라서 아마도 하나의 item 크기로 listview가 고정된게 아닌가 생각됨..

만약 위의 상황에서 ScrollView에 android:fillViewport="true" 속성을 주게 되면 ScrollView의 height가 parent view에 맞춰지게 되어 ListView도 view에 맞춰 나오긴 하지만 정작 scroll이 안된다. 
해당 option은 ScrollView 내의 view들의 height가 parent의 height에 도달하지 않을 때 강제로 늘리는 방법이지만 view hierachy상에 ListView가 들어 있는 상황에서는 ListView의 scrolling 계산에 영향을 주어 ScrollView의 height는 바로 변경되더라도 의도한 대로 ListView가 동작하지 않는 것으로 추측..


이를 회피하는 방법으로는 다음과 같은 방법들이 있다.
 1. scrollview의 내용들을 listview의 header로 설정하는 방법
 2. scrollview와 listview를 분리해서 사용하는 방법
 3. scrollview의 height를 재 계산해서 업데이트 해주는 방법.

개인적으로는 2번 추천...

1, 2번 방법


I would recommend you to put the ScrollView Content as HeaderView in the ListView or do you explicitly want to have two separated scrollable areas on screen?
Example for putting the content of the scroll view in the list view as header (one single scrollable area):
public void onCreate(Bundle s){
    setContentView(R.id.yourLayout);

    ListView listView = (ListView) findViewById(R.id.listView);

    // Set the adapter before the header, because older 
    // Android version may have problems if not

    listView.setAdapter(new YourAdapter());

    // Add the header
    View header = inflater.inflate(
            R.layout.you_layout_that_was_in_scrollview_before, null, false); 

    listView.addHeaderView(header);

}
The layout of the activity would look like that:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/frameLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ListView  android:id="@+id/listView" 
    android:layout_height="match_parent" 
    android:layout_width="match_parent"
    android:background="#FFF"/>
</LinearLayout>
If you want two scrollable areas, you should work with layout weight:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ScrollView 
  android:id="@+id/marketDetailScrollView"
  android:layout_width="match_parent"
  android:layout_height="0dp"
  android:layout_weight="1" >
  <!-- ScrollViewContent -->
</ScrollView>
<ListView  android:id="@android:id/list" 
android:layout_height="0dp" 
android:layout_width="match_parent"
android:background="#FFF"
android:layout_weight="1"/>
</LinearLayout>
share|improve this answer

[안드로이드/Android ListView Header, Footer 설정 하기 ~! by 아라비안왕자]
 : 2번의 예제로는 잘 되어 있는듯.


* 만약 header, footer 설정 후 실행 시 ClassCaseException 발생 시
ListView에서 getAdapter() 함수가 존재하는지 확인하고 이를 제거 해줘야 한다.

01-01 14:41:27.467: E/AndroidRuntime(19481): FATAL EXCEPTION: main
01-01 14:41:27.467: E/AndroidRuntime(19481): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.xxx.yyy/com.xxx.yyy.createsession.ZzzActivity}: java.lang.ClassCastException: android.widget.HeaderViewListAdapter cannot be cast to android.widget.BaseAdapter
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2247)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2297)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.app.ActivityThread.access$700(ActivityThread.java:152)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1282)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.os.Handler.dispatchMessage(Handler.java:99)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.os.Looper.loop(Looper.java:137)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at android.app.ActivityThread.main(ActivityThread.java:5329)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at java.lang.reflect.Method.invokeNative(Native Method)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at java.lang.reflect.Method.invoke(Method.java:511)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1102)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:869)
01-01 14:41:27.467: E/AndroidRuntime(19481):  at dalvik.system.NativeStart.main(Native Method)
01-01 14:41:27.467: E/AndroidRuntime(19481): Caused by: java.lang.ClassCastException: android.widget.HeaderViewListAdapter cannot be cast to android.widget.BaseAdapter




3번 방법, 사실 해보지는 않았다.

Android ListView in ScrollView Problem

This is the method to re-calculate listview height to fix the problem.
public static void setListViewHeightBasedOnChildren(ListView listView) {
  ListAdapter listAdapter = listView.getAdapter();
  if (listAdapter == null) {
   // pre-condition
   return;
  }
 
  int totalHeight = 0;
  int desiredWidth = MeasureSpec.makeMeasureSpec(listView.getWidth(), MeasureSpec.AT_MOST);
  for (int i = 0; i < listAdapter.getCount(); i++) {
   View listItem = listAdapter.getView(i, null, listView);
   listItem.measure(desiredWidth, MeasureSpec.UNSPECIFIED);
   totalHeight += listItem.getMeasuredHeight();
  }
 
  ViewGroup.LayoutParams params = listView.getLayoutParams();
  params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
  listView.setLayoutParams(params);
  listView.requestLayout();
  


댓글 1개:

  1. 와 진짜 감사합니다!! 저는 1번방법으로 했는데 잘되네요 ㅎㅎㅎ

    답글삭제