'리스트뷰'에 해당되는 글 2건

  1. 효율적인 레이아웃 구성하는 기법 (51)
  2. 리스트뷰 백그라운드의 최적화 (5)
효율적인 레이아웃 구성하는 기법

안드로이드 UI 툴은 사용하기 쉽고 편한 관리툴을 제공합니다.
대부분의 경우에는 관리툴이 제공하는 몇몇 기본적인 것들만 이용하면 UI를 간편하게 구성할 수 있습니다.

하지만 기본적인 것들만 이용해서는 효율적인 사용자 UI를 만들지 못하겠죠.
보기 좋은 예가 LinearLayout의 남용을 들 수 있는데 이렇게 남용하게 되면 View 계층에서 View가 넘쳐나게 되는 결과를 야기합니다.
애플리케이션에서 보여지는 모든 뷰와 레이아웃 관리툴은 초기화라는 비용을 수반합니다.
이런 비용은 레이아웃과 그리기 성능을 저하시키죠. 특히 다중 LinearLayout 에서 weight 속성을 여러번 사용하면
weight 속성의 특성상 자식객체를 두번 측정해야하기 때문에 매우 값비싼 비용을 치르게 됩니다.

여기 아주 단순하고 흔한 레이아웃 예제가 있습니다. 리스트 아이템 왼쪽에 아이콘이 있고, 제목이 있고 제목 밑에 설명이 잇습니다.
어떻게 생겼는지 한 번 보죠.


위의 예제를 분석해 봅시다. 
하나의 ImageView 와 두개의 TextView가 각각의 자리를 차지하고 있습니다. HierarchyViewer 를 통해서 캡쳐한 예제의 틀을 한번 살펴봅시다.



LinearLayout을 통하면 이 레이아웃을 단번에 만들수 있습니다.
아이템 자체는 수평 LinearLayout 이고 하나의 ImageView와 두개의 TextView를 포함한 수직 LienarLayout 이 있습니다.
이 레이아웃의 코드를 살펴봅시다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
    
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
                    
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>

</LinearLayout>

이렇게 만들면 동작은 하지만 ListView의 각각의 아이템을 인스턴스화 한다면 매우 소모적입니다.
같은 레이아웃을 RelativeLayout을 통해서 만들수가 있는데, 이렇게 함으로서 하나의 View를 아낄수 있고 View 계층에서 한단계 더 높습니다.
RelativeLayout을 사용한 코드를 한 번 살펴봅시다.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"

        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
                
        android:gravity="center_vertical"
        android:text="My Application" />

</RelativeLayout>

이 구조로 된 레이아웃은 물론 이전의 레이아웃과 똑같이 동작합니다.
단, 리스트아이템을 두 줄로 하고 싶을 때를 제외합니다. 즉, 제목과 설명이 필요하다면 다른 접근이 필요합니다.
주어진 리스트 아이템에 제목말고 설명이 없다면 애플리케이션은 설명에 해당하는 TextView 의 Visibility(보임옵션)을 GONE(보이지도 않고 자리를 차지하지도 않음)으로 처리해버립니다.
이 방식은 LinearLayout에는 완벽하게 동작하지만 RelativeLayout은 동작하지 않습니다.




RelativeLayout에서는 View가 부모객체를 기준으로 정렬을 합니다. 부모가 RelativeLayout 자신이 될 수도 있고, 다른 View가 될 수도 있습니다.
예를 들어, 개발시에 설명 TextView를 RelativeLayout의 아래를 기준으로 정렬하고 제목은 설명위로 정렬하고 부모객체위에 자리를 잡게 설정했다고 한다면 이 상황에서 설명 TextView의 Visibility를 GONE으로 설정시에는 RelativeLayout은 제목의 하단을 어디를 기준으로 정렬해야 할지 모르는 상황이 발생합니다. 이 문제를 해결하기 위해서 아주 특별한 속성값을 사용할 수 있습니다.
layout_alignWithParentIfMissing 입니다.

Boolean 속성을 지니는 이 값이 하는 일은 지정된 타켓이 사라진다면 그에 맞춰 정렬된 다른 객체의 하단을 고정시키는 역할을 합니다.
예를 들어, View를 Visibility 속성이 GONE인 View 옆에 두고 alignParentIfMissing 속성을 true로 놓으면 RelativeLayout은 View를 왼쪽 가장자리에 고정시킵니다.
위의 예에 적용시키면 alignWithParentIfMisiing 속성은 RelativeLayout을 제목의 아래에 정렬시키게됩니다.
결과화면은 다음과 같습니다.





레이아웃의 출력결과는 완벽합니다. 설명이 GONE 이라도 완벽하게 동작합니다. LinearLayout의 weight 옵션도 쓰지 않기 때문에 더 효율적이고 간단합니다.
HierarchyViewer 에서 두 레이아웃의 View 계층도를 확인해보면 더 확실히 구분할 수 있습니다.




다시 말하지만 인스턴스를 위한 리스트뷰에서 각각의 아이템을 나타내기위한 두 레이아웃의 차이점은 매우 중요합니다.
바라건데 UI를 구성하고 최적화하는 방법을 배우는데에 이런 예제들이 도움이 될 수 있었으면 좋겠습니다.
저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License
리스트뷰 백그라운드의 최적화

리스트뷰는 안드로이드의 가장 많이 사용되는 위젯중의 하나입니다.
리스트뷰는 사용하기 쉽고 유연하며 매우 강력합니다.
가끔씩은 이해하기 어려울 때도 있죠.

리스트뷰의 가장 흔한 문제중의 하나는 리스트뷰의 백그라운드(배경)을 커스텀으로 제작할 때 발생합니다.
다른 안드로이드 위젯들처럼 리스트뷰의 백그라운드는 투명으로 아래의 그림에서 기본 배경색인 #FF191919, 
어두운 회색인 윈도우의 백그라운드를 리스트뷰를 통해 들여다 볼 수 있습니다. 추가적으로 리스트뷰는 
기본값으로 아래의 그림에서 볼 수 있듯이 가장자리에 Fading 효과를 주고 있습니다. 
아래의 그림에서 리스트뷰의 첫번째 아이템이 검은색으로 Fade 되는 것을 볼 수 있습니다.
이 기법이 사용됨으로서 리스트뷰의 내용물이 스크롤 되는 것을 보여줍니다.




위의 그림에서 보이는 Fade 효과는 Canvas.SaveLayerAlpha() 메서드와 Porter-duffDestination Out Blending mode.
메서드의 조합으로 실행됩니다.

하지만 안타깝게도 리스트뷰에 커스텀 백그라운드를 넣거나 윈도우의 백그라운드를 바꾸면 화면이 아래의 그림처럼
이상하게 변해버립니다. 아래의 두개의 스크린샷은 윈도우의 백그라운드를 바꾸면 어떤 일이 일어나는지 보여주고 있습니다.
왼쪽 스크린샷은 Default 값으로 설정했을 때 보여지는 화면이고 오른쪽 스크린샷은 리스트뷰를 스크롤했을때 보여지는 화면입니다.




이렇게 렌더링(화면이 출력되는 것)되는 이유는 안드로이드 프레임워크가 자동적으로 리스트뷰의 모든 인스턴스에 최적화를 실행하기 때문입니다.
앞에서 리스트뷰의 Fade 효과는 Porter-Duff blending mode를 사용해서 실행된다고 했는데 사실 이 메서드는 잘 동작하지만 실행하는데
리스트뷰가 Offscreen Bitmap(forescreen 말고 background의 비트맵 렌더링)의 일부분을 캡쳐해야하고 이 작업이 끝나면 추가적으로
Extra Blending(이 작업이 메모리로부터의 재호출을 필요로 합니다.)을 필요로 하기 때문에 비용이 높은 측면이 있습니다.

리스트뷰는 대개 단색을 배경으로 하기 때문에 이런 복잡한 과정을 거칠 이유가 없습니다.
이런 이유로 "Cache Color Hint"라는 최적화기법을 소개해 드립니다.
Cache color hint 기법은 윈도우의 배경색을 RGB컬러셋 기본값으로 정해놓은 것입니다.
안드로이드의 #191919색(어두운 회색)이 그런것입니다. RGB 컬러값이 정해지면 리스트뷰는(View 클래스의 파생)윈도우의 백그라운드를 단색으로 
설정할 것이라는 것을 알고 비용이 높은 saveLayerAlpha)()/Poretr-duff 메서드 대신에 단순한 그래디언트를 사용해 화면을 그립니다.
이 그래디언트는 완전 투명색부터 Cache color hint 값까지 지원합니다. 위의 그림에서 볼 수 있는 바와같이 리스트뷰의 마지막 리스트의
어두운 색의 그래디언트까지 지원합니다. 하지만 아직도 리스트뷰의 리스트가 스크롤이 될 때 왜 전체 리스트뷰가 검은색으로 변하는지 설명이 안됩니다.

이미 언급했듯이 리스트뷰는 다른 기본 위젯들과 마찬가지로 기본값으로 완전투명과 반투명속성을 가지고 있습니다.
이 말은 리스트뷰가 자식리스트(Children)를 다시 그릴때 자식리스트를 윈도우의 백그라운드의 색상과 혼합해야 한다는 뜻입니다.
다시 말하지만 다시 그리는 작업은 메모리로부터 재호출을 필요로 하므로 비용이 아주 높은 작업입니다.

스크롤 작업시에 그리기 성능을 끌어올리기 위해서 안드로이드 프레임워크는 Cache color hint 값을 재이용합니다.
이 값을 다시 이용함으로서 안드로이드는 각각의 자식(Child)에 hint값의 비트맵색상을 채워넣습니다. 리스트뷰는 이 비트맵이 투명하면서 혼합이 필요하지 않기 때문에
분할해서 채워넣습니다. 또한 기본값으로 백그라운드가 #191919 로 정해져 있기 때문에 리스트뷰를 스크롤 할 때 각각의 아이템의 백그라운드가 검은색으로 보이는 것입니다.

이 문제를 해결하려면 Cache color hint 최적화 옵션을 사용하지 않으면 됩니다.
백그라운드 배경색을 단색이 아닌 색을 넣고 싶다거나 hint 값을 적절한 단색값으로 넣고 싶을때 그렇게 합니다.
코드에서 할 수 있고 XML 속성으로도 설정할 수 있는데 코드로 설정하려면 setCacheColorHint(int) 메서드를 사용합니다.
XML 속성을 사용하려면 android:cacheColorHint 속성을 이용합니다. 최적화 옵션을 끄려면 투명색상인 #00000000 값을 이용하도록 합니다.
아래의 그림은 XML 레이아웃에서 android:cacheColorHint="#00000000" 속성을 이용한 화면입니다.




위 그림에서 볼 수 있듯이 윈도우의 백그라운드 색인 커스텀 나무 색상에 Fade 가 완벽하게 작동되는것을 볼 수 있습니다.
Cache color hint 기능의 특징은 이처럼 흥미로운데 왜냐하면 때때로 최적화 작업이 어떤 상황에서는 개발을 더 힘들게 할 수도 있기 때문입니다.
하지만 이번의 경우에는 기본적인 값의 수정이 복잡한 기능의 추가보다 더 낫다는 걸 보여줍니다.
저작자 표시 비영리 동일 조건 변경 허락
신고
크리에이티브 커먼즈 라이선스
Creative Commons License