FlexboxLayout 뜯어보기


부모 레이아웃의 넓이에 따라 자식 뷰의 행을 동적으로 맞출 수 있는 레이아웃을 소개하겠습니다. 태그 목록을 구현할 때 가장 많이 쓰이지 않을까 싶습니다. 크기가 서로 다른 태그를 정해진 넓이에 따라 자동으로 행을 바꿔서 배치를 원하는 경우 안드로이드에서 기본으로 제공되는 레이아웃으로는 불가능합니다.

ConstraintLayout? 불가능.
자식 뷰들 간의 밀접한 관계 구성으로 이미 정해진 자식 뷰의 개수와 넓이인 경우에는 배치가 쉽지만, 새로운 항목이 추가되거나 제거되는 동적인 환경에서는 사용하기가 어렵습니다. 목록이 많아지는 경우 스크롤되지 않으며 자식 뷰의 크기가 줄어들어 적합하지 않습니다.

RelatveLayout? 불가능.
ConstraintLayout와 동일한 구조적인 문제로 적합하지 않습니다.

GridLayout? 확실 구현불가능.
각 행에 표시될 항목의 기본 개수를 지정해야 하기 때문에 자식 뷰의 넓이에 따라 동적으로 행의 개수를 가져야 하는 것과 너무 동떨어 지기 때문에 GridLayout도 적합하지 않습니다.

LinearLayout? 최선의 선택은 아니지만 구현은 가능.
ScrollView와 같이 조합하여 수동으로 구현할 수 있습니다. 부모 레이아웃의 넓이를 가져와 자식 뷰를 하나씩 추가하면서 행을 언제 바꿔야 할지 수동으로 구현하는 방법을 생각할 수 있습니다. 이미 Github에 TagView 등이 이런 방식을 채택하고 있습니다.

FlexboxLayout?

위의 LinearLayout으로 구현하기 위해 사용한 중첩 구조를 완벽하게 피 할 수 있으면서 원하는 구조를 짤 수 있었습니다.

dependencies {
   compile 'com.google.android:flexbox:0.3.0-alpha2'
}

FlexboxLayout을 사용하기 위해 build.gradle에 추가합니다. FlexLayout의 wrap속성을 사용하면 ViewGroup내의 자식 뷰들이 자동으로 다음 줄로 이동합니다. GridLayout의 행에 고정된 항목이 아닌 행 별로 다른 넓이를 가질 수 있도록 항목이 가변적일 수 있음을 의미합니다. FlexboxLayout은 RecyclerView와 함께 사용하여 스크롤을 자동으로 처리할 수 있습니다.

<com.google.android.flexbox.FlexboxLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:flexwrap="wrap”/>

화면에 모두 표시된다면 위의 코드로는 문제가 없습니다. 하지만 아이템이 너무 많아서 스크롤되어야 한다면 ScrollView에서 FlexboxLayout을 구현하는 것보다 RecyclerView를 이용하는 것이 성능이나 메모리면에서 훨씬 더 많은 이점이 있습니다.

RecyclerView에서 FlexboxLayout을 사용하기 위해서는 FlexboxLayoutManager를 사용하면 됩니다.

<android.support.v7.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="com.google.android.flexbox.FlexboxLayout”/>

레이아웃에서 layoutManager속성을 FlexboxLayout을 주면 됩니다. 하지만 코드에서 FlexboxLayout의 flexWrap속성을 wrap값로 변경해야 합니다.

그렇기 때문에 레이아웃에서 layoutManager를 바로 주는 것보다 아래와 같이 코드를 통해 RecyclerView의 layoutManager를 설정하는 게 좋습니다.

FlexboxLayoutManager layoutManager = new FlexboxLayoutManager();
layoutManager.setFlexWrap(FlexWrap.WRAP);
recyclerView.setLayoutManager(layoutManager);

 

flexGrow 속성은 LinearLayout의 weight와 같은 속성으로 작동합니다. 이렇게 하면 각 행의 나머지 공간을 균등한 공간으로 채울 수 있습니다.

FlexboxLayoutManager.LayoutParams flexboxLayoutParams = 
        (FlexboxLayoutManager.LayoutParams) mImageView.getLayoutParams();
flexboxLayoutParams.setFlexGrow(1.0f);

 

이 외에도 alignItem 속성은 아이템의 정렬되는 기준선을 설정 할 수 있으며, justifyContent를 이용하면 컨텐츠 배치를 설정 할 수 있습니다.

layoutManager.setAlignItems(AlignItems.BASELINE);
layoutManager.setJustifyContent(JustifyContent.CENTER);

 

물론 layout 속성에서도 추가 할 수 있습니다. FlexboxLayout을 활용 하면 다음과 같은 멋진 이미지 뷰어도 만들 수 있습니다.

 

참고:
https://blog.devcenter.co/unboxing-the-flexboxlayout-a7cfd125f023#.wyye4dtld




RecyclerView Prefetch – 스크롤을 부 드럽게 하기위한 시도

RecyclerView Prefetch를 번역한 글입니다.

내가 어렸을 때, 어머니께서 지금 방청소를 하면 나중에 할 필요가 없다며 버릇을 고치려고 하였습니다. 하지만 나는 그것에 속지 않았습니다. 나는 가능한 오랫동안 하지 않는 것이 항상 최선이라는 것을 알았습니다. 한 가지 생각해보면, 만약 청소를 지금 당장 하게 된다면 다시 더러워질 것이고 청소를 두 번 해야 됩니다. 만약에 더 오랫동안 미룬다면 어머니는 잊어버릴 수도 있습니다.

미루는 버릇은 항상 나의 일이었습니다. 하지만 내 친구 인 RecyclerView는 일관된 프레임 속도에 대한 문제가 없습니다.

 

문제점

스크롤이나 플링 연산중 RecyclerView는 화면에 새로운 아이템 정보를 표시해야 합니다. 이러한 아이템은 데이터와 바인딩합니다. (캐시에 항목이 없는 경우 새롭게 생성될 수 있습니다.) 그런 다음 배치되며 화면에 그려지게 됩니다. 이 모든 작업이 완료되면, 프레임에서 처리할 작업이 완료될 때까지 UI스레드는 멈춰있게 됩니다. 그러면 렌더링이 진행되고 스크롤은 스무스하게 움직일 수 있습니다… 다음 새로운 아이템이 나타날 때까지

일반적으로 RecyclerView에서 콘텐츠를 스크롤하는 동안 렌더링 하는 단계입니다 (롤리팝 이상에서). UI스레드에서 입력 이벤트, 이벤트 핸들링, 레이아웃을 수행하고 드로잉 연산 작업을 기록합니다. 그런 다음 렌더스레드가 명령을 GPU로 보냅니다.

스크롤하는 동안의 프레임은 처리할 새로운 내용이 없으므로 RecyclerView는 필요한 작업을 수행하는데 아무런 문제가 없습니다. 이 프레임 동안, UI스레드 입력을 처리하고, 애니메이션을 처리하며 레이아웃을 수행하고 드로잉 명령을 기록합니다. 그런 다음 드로잉 정보를 렌더스레드와 동기화합니다. (롤리팝 이상, 이전 버전은 UI 스레드에서 모든 작업을 수행함) 이러한 명령을 GPU로 보냅니다.

새로운 아이템 생성 시 뷰 생성, 바인딩 및 배치될 때 입력 단계가 가장 오래 걸리게 합니다. 결과적으로 프레임 경계 이후에 끝나기 때문에 렌더링 단계가 늦어져 프레임 누락의 원인이 됩니다.

새로운 아이템이 스크린에 나타나면 입력 단계에 더 많은 작업이 필요하며, 적합한 뷰를 생성할 수 있습니다. 이것은 렌더스레드의 후속 작업뿐만 아니라 나머지 UI 스레드를 늦추어 프레임 범위를 벗어나기 때문에 무의미한 프레임이 됩니다.

입력을 받는 동안 새로운 아이템을 보여주기 위해 생성하고 바인딩 하는데 사용할 수 있는 많은 시간을 할당 할 수 있습니다.

새로운 아이템이 나타날 때 입력 단계에서 호출 스택을 검사하면 많은 시간이 뷰 생성 및 바인딩에 소비된다는 것을 알 수 있습니다. 새로운 아이템을 준비 하는 동안 다른 작업을 지연하지 않는 방법이 있으면 좋지 않을까요?

뷰 생성 및 바인딩은 렌더링 되기 전에 완료되어야 하며 필요한 경우 처리하는 동안 UI스레드에서는 많은 시간을 소비합니다. 이전 프레임에서 많은 시간을 보내고 있으면 지연이 발생됩니다.

Chris Craik(안드로이드 UI 툴킷팀, 그래픽 엔지니어)가 RecyclerView의 스크롤을 관측하기 위해 Systraces를 만들었습니다. 특히, 그는 아이템이 필요할 때 준비하는 시간이 오래 걸린다는 것을 알았습니다. 그리고 프레임을 처리는 동안에 UI스레드가 잠자는데 많은 시간을 보내고 있었습니다. 왜냐하면 UI스레드는 작업을 일찍 끝마치기 때문입니다.

 

해결책

생성과 바인드 연산을 이전 프레임에서 허용하면 UI스레드와 렌더스레드가 병렬로 작업을 수행할 수 있게 되며, 렌더스레드의 작업이 완료되기 전 동기적으로 수행하여 나중에 처리할 필요가 없습니다.

분명히 시간을 가지고 놀 시간이 없습니다. 특히 Chris는 기본 RecyclerView 레이아웃에서 일어나는 일을 재배열하여 필요한 View의 아이템을 미리 가져옵니다. 유휴 상태에서 이 작업을 수행하여 결과를 기다리는 것을 피할 수 있습니다.

이제 이 작업은 기본적으로 무료로 가능합니다. 유휴시간을 사용하여 나중에 해야 될 작업을 수행하기 때문에 UI스레드는 프레임들 사이의 갭에서 어떤 일도 하지 않습니다. 어려운 일이 이미 완료되었기 때문에 훤씬 빠른 속도로 프레임을 만들어 갈 수 있습니다.

 

세부 설명

이 시스템은 RecyclerView가 스크롤링 작업을 시작할 때마다 Runnable을 스케쥴링함으로써 작동합니다. 이 Runnable은 레이아웃 매니저와 뷰가 스크롤되는 방향에 따라 즉시 나타나며 아이템의 프리페치를 수행합니다.

프리페치는 단일 아이템에 국한되지 않습니다. GridLayoutManager 아이템의 행이 화면에 오는 등 여러 아이템을 한 번에 검색할 수 있습니다. 버전 25.1에서 프리페치 연산은 개별 생성/바인드 연산으로 분리되어 아이템별 전체 그룹의 연산보다 UI스레드 갭이 더 쉽게 구분됩니다.

프리 페치 방식에 대한 흥미로운 점 중 하나는 시스템에서 작업에 소요되는 시간과 사용 가능한 갭 내에 들어갈 수 있는지 여부를 예측해야 한다는 것입니다. 결국, 프리 페치 작업이 타이밍을 찾지 못하고 놓쳐 프레임을 지연시키게 되면 프리 페치가 없는 것과 동일하게 무의미한 프레임이 될 수 있습니다. 시스템이 이 세부 사항을 처리하는 방식은 View 타입별 평균 생성 및 바인드 지속 기간을 추적하여 향후 생성 및 바인드 연산에 대해 합리적인 예측을 가능하게 합니다.

내부 RecyclerView를 바인딩해도 자식을 할당하지 않기 때문에 충첩된 RecyclerViews(RecyclerView 내부 컨테이너 아이템)에 대해 작업을 수행하는 것이 더 까다롭습니다. RecyclerView는 첨부 및 레이아웃 될 때 자식을 가져옵니다. 프리 페치 시스템은 여전히 RecyclerView 내부에서 자식들을 준비할 수 있지만 얼마나 많은지 알고 있어야 합니다. LinerLayoutMananger의 버전 25.1에 있는 새로운 API인 setInitialItemPrefetchCount()입니다. 이 API는 화면에서 스크롤할 때 RecyclerView를 채우기 위해 미리 가져올 항목의 수를 시스템에 알려줍니다.

 

주의사항

알고 있어야 할 몇 가지 중요한 주의사항:

  • 프리페칭은 필요로 하지 않는 작업을 할 수도 있습니다. View를 프리페칭하고 있기 때문에 적극적으로 일을 할 수 있으며, RecyclerView는 문제 되는 아이템에 도달하지 않을 수도 있습니다. 이것은 프리페치 작업이 낭비될 가능성이 있다는 것을 의미합니다. 동시에 발생했기 때문에 큰 문제는 없습니다. 필요하기 직전에 인출하고 있기 때문에 이것은 매우 드뭅니다. 스크롤이 두 프레임 간 정지나 역전하는 일은 거의 없습니다.
  • 렌더스레드: 렌더스레드는 롤리팝에서 성능 향상을 위해 도입된 기능으로 렌더링을 UI스레드에 영향을 받지 않도록 다른 스레드로 분리하여 불변의 애니메이션(ex. 잔물결 및 원형틀)을 실행하는 등 일부 개선을 허용합니다. 롤리팝 이전 버전은 병렬로 작업을 처리할 수 없기 때문에 최적화의 이점은 얻지 못합니다.

 

무엇을 원하며  — 어디서 얻을 수 있는가?

프리페치 최적화는 서포트 라이브러리 25 버전에 도입되었으며, 25.1.0 버전에서 더욱 향상된 기능이 추가되었습니다. 따라서 첫 번째 단계는 최신의 서포트 라이브러리 버전을 사용하는 것입니다.

RecyclerView와 함께 기본으로 제공되는 레이아웃 매니저를 사용하면 최적화가 자동으로 수행됩니다. 그러나 중첩된 RecyclerView를 사용하거나 직접 만든 레이아웃 매니저의 경우 기능을 활용하려면 코드 변경이 필요합니다.

중첩된 RecyclerView의 경우, 최상의 성능을 얻으려면 내부 레이아웃 매니저에서 LinearLayoutManager의 새로운 메소드인 setInitialItemPrefetchCount()를 호출하면 됩니다. 예를 들어 세로 목록의 행에 최소 3개 이상이 표시되면 setInitialItemPrefetchCount(4)를 호출하면 됩니다.

직접 LayoutManager를 구현한 경우, 프리페치를 활성화하기 위해 LayoutManager.collectAdjacentPrefetchPositions()를 구현해야합니다.
언제나 그렇듯이 가능한 작은 작업으로 생성과 바인드 단계를 최적화하는 것이 좋습니다. 가장 빠른 코드는 프레임워크가 프리 페치를 통해 수행된 작업을 병렬 처리할 수 있을 때 실행될 필요가 없으며, 여전히 시간이 걸리는 비싼 아이템 생성은 비용이 들 수 있습니다. 예를 들어, 뷰를 최소한으로 써가며 복잡한 구조를 만드는 것보다 항상 생성하고 바인딩하는 것이 더욱 저렴합니다. 바인딩은 기본적으로 setter를 호출하는 것처럼 간단하고 빠릅니다. 현재 코드로 프레임 제한 시간까지 잘 맞출 수 있어도 이를 최적화하면 저사양 기기에서 더 잘 작동할 가능성이 커지며 고사양 기기에서는 배터리 소모에 도움이 됩니다.

이미 생성과 바인딩을 빨리 할 수 있더라도 프리페칭은 프레임들 사이의 틈새에서 남은 시간을 당기는 것에 대해 도와주어야 합니다. LayoutManager.setItemPrefetchEnabled()를 토글 하여 결과를 바탕으로 최적화를 비교해볼 수 있습니다. 결과를 시각적으로 볼 수도 있습니다. 정말 중요하며, 특히 생성하고 바인딩하는데 상당한 시간이 걸리는 아이템 있습니다. 프리페치 사용 여부와 상관없이 실시간으로 모니터링하고 싶다면 Systrace를 실행하거나 GPU 프로파일링을 활성화하면 됩니다.

Systrace는 UI스레드의 유휴 시간동안 발생하는 프리페치를 보여줍니다.

 

마치며..

최신 서포트 라이브러리를 확인하고 RecyclerView의 새로운 프리페치를 사용해보세요. 한편, 나는 방을 청소하지 않고 돌아가겠습니다.

 

안드로이드 RecyclerView 성능 개선팁

RecyclerView는 제한된 화면에서 큰 데이터 세트를 제공하기 위한 유연한 View입니다. RecyclerView는 안드로이드 앱 개발에 있어서 가장 중요한 위젯 중 하나인 ListView를 좀 더 발전시킨 버전입니다. 뉴스 피드나 연락처 목록을 구현 시 사용자가 빠르게 스크롤할 때 성능 문제 또는 불필요한 지연을 방지하기 위해 ListView를 사용했습니다. RecyclerView는 ListView의 성능과 지연을 100% 방지 못하는 문제점을 해결한 버전입니다.

이 글은 RecyclerView 사용방법에 관한 글이 아닙니다. RecyclerView를 사용하면서 유용한 정보와 중요한 규칙, 절대 하지 말아야 할 것을 하나씩 살펴보겠습니다.

피할 수 있는 문제

1. 제대로된 View 재사용
: ViewHolder 내부에서 View 애니메이션을 절대 사용하면 안됩니다. (ex. itemView.animate()호출)
ItemAnimator는 View 애니메이션을 처리할 수 있는 유일한 구성 요소입니다. (ex. RecyclerView.setItemAnimator() )

ListView에서는 아이템 View 애니메이션 처리를 위해 getView()에서 작업을 하였습니다. 이는 View의 재사용 문제가 있으며 RecyclerView에서 동일한 패턴으로는 사용해서는 절대 안됩니다.

2. 세분화된 Adapter 업데이트
: 변경이 된 데이터에 대해서만 Adapter 업데이트를 하세요. (ex. notifyItemChanged(4))
NotifyItemRangeChanged()는 필요로 하지 않는 View를 업데이트하기 때문에 불필요하게 사용하지 마세요. (ex. notifyItemRangeChanged(0, getItemsCount() )

DiffUtil을 사용하여 Adapter의 성능을 측정 해 볼 수 있습니다.

3. onBindViewHolder position != final
: 절대 onBindViewHolder 내부에서 View.OnClickListener를 셋하지 마세요. onBindViewHolder는 데이터를 View에 바인딩하기 위해서만 사용해야 합니다.
아래와 같이 사용합니다.

이러한 간단한 규칙 3가지만으로도 RecyclerView의 성능은 보장됩니다.
데이터 변경에 따른 쉬운 애니메이션 처리방법.

  • 위에서 언급한 RecyclerView.setItemAnimator()를 직접 구현해도 되지만 notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted(), notifyItemMoved(), notifyItemRemoved()를 사용하기만 해도 기본적인 애니메이션이 적용됩니다.
  • getItemId(int position)와 함께 setHasStableIds(true)를 사용하면 RecyclerView.notifyDataSetChanged()로 모든 애니메이션을 자동으로 처리할 수 있습니다. (참고: http://wp.me/p4L8WU-MF)

 

 

성능 팁!

부드러운 스크롤을 원한다면 아래의 간단한 규칙을 지키면 됩니다.

  • 프레임 당 모든 작업을 수행하는데 16ms내로 작업하도록 해야 합니다. 개발자 옵션에서 프로필 GPU 렌더링 옵션을 사용하여 성능을 모니터링하세요.
  • 레이아웃 구조를 최적화 및 간단한 구조를 유지하세요.
  • 깊은 레이아웃 계층을 피하기 위해 HierarchyViewer를 사용하세요.
  • 오버 드로우 문제를 피하고 시스템 도구로 모니터링 하세요.
  • TextView에 긴 텍스트를 설정하지 마세요. 텍스트 줄을 계산하기위해 많은 연산이 필요하여 성능을 떨어집니다. 텍스트 끝 말줄임표나 최대 줄수를 설정 해두는 것도 하나의 방법입니다.
  • 렌더링 퍼포먼스를 향상하기위해 LayoutManager.setItemPrefetchEnabled()를 사용하세요.

RecyclerView는 안드로이드의 강력한 위젯이며 모든 결과를 얻을 수 있습니다. 하지만 잘못된 작은것 하나가 앱의 품질을 좌우 합니다.

참고: https://medium.com/master-of-code-global/recyclerview-tips-and-recipes-476410fa12fd#.enkpoi5ir

CoordinatorLayout과 Behavior의 관계

머트리얼 디자인 가이드 라인중 스크롤시 다양한 반응을 위한 테크닉인 Behavior라는 개념이 도입 되었습니다. 기본적으로 액션바를 확장하여 스크롤시 액션바를 줄여들게 하도록 AppBarLayout의 ScrollingViewBehavior와 스크롤시 하단으로 숨기게 하기위해 BottomSheetBehavior를 서포트라이브러리에서 제공하고 있습니다.

  • android.support.design.widget.AppBarLayout$ScrollingViewBehavior
  • android.support.design.widget.BottomSheetBehavior

Behavior를 사용하기 위해서는 CoordinatorLayout을 통해서 사용되는데, CoordinatorLayout은 자식뷰의 스크롤의 변화 상태를 다른 자식뷰들에게 전달 해주는 역할을 합니다. 좀더 쉽게 말해 NestedScrollView나 RecyclerView등에 스크롤의 상태를 판단하여 정의된 반응을 하기위한 View에 Behavior를 등록하면 됩니다.

이해를 돕기위해 안드로이드 서포트 라이브러리에서 제공해주는 Behavior를 한번 보겠습니다. NestedScrollView에 layout_behavior에 AppBarLayout$ScrollingViewBehavior가 정의가 되어있습니다. NestedScrollView의 반응에 따라 AppBarLayout이 반응됩니다.

CoordinatorLayout는 NestedScrollView가 스크롤시 layout_behavior에 정의된 레이아웃으로 스크롤 정보를 전달 하는 역할을 합니다. 그럼 AppBarLayout의 ScrollingViewBehavior가 정보를 받아서 AppBarLayout 자신을 변형하도록 하는 구조입니다.

CoordinatorLayout이 스크롤되는 것은 Behavior에 구현된 NestedScrollingParent를 통해 전달 됩니다. 즉, CoordinatorLayout는 NestedScrollingParent가 구현되어 있으며 스크롤 되는 View들은 NestedScrollingChild가 구현되어 있어야 Behavior가 전달 됩니다. 그렇기 때문에 기존의 ScrollView나 ListView는 NestedScrollingChild가 구현되어 있지 않아 Behavior를 통해 스크롤 정보전달이 되지 않습니다.

이렇게 CoordinatorLayout의 역할과 Behavior의 관계를 알고 있다면 Behavior를 커스텀해서 구현하는데 전혀 문제 없을 것입니다.

Ripple Animation

안드로이드 5.0(API 21)의 머트리얼 디자인에서 물결 터치 효과가 처음 소개 되었습니다. 터치 피드백에 대해 UI요소와 사용자간의 상호작용을 비주얼 하게 머트리얼 디자인에서는 제공합니다. 예를 들어 버튼을 터치하면 즉각 물결 효과가 표시 됩니다. 이것은 안드로이드 5.0에서 기본으로 제공됩니다.

Ripple 애니메이션은 새롭게 생긴 RippleDrawable을 이용합니다. 물결 효과는 뷰 경계에서 끝나거나 경제를 넘어 확장 할 수 있도록 구성 할 수 있습니다. 아래 스크린 샷은 터치한 위치에서 점점 퍼지면서 잔물결 효과가 버튼의 가장자리로 퍼지는 것을 보여 주고 있습니다. 터치를 끝마치게 되면 뷰는 다시 원래 모양으로 돌아갑니다. 이 모든것이 1초 내로 이루어 지지만 애니메이션 시간은 더 길거나 짧게 변경 할 수 있습니다.

 

View 클릭처리

이런 물결 효과는 안드로이드 5.0에서 기본적으로 제공하며, 아래와 같이 정의된 attr를 사용하면 된다.

 

버튼 처리

대부분의 버튼은 Drawable를 사용합니다. 일반적으로 selector에서 몇가지 상태에 맞는 리소스를 설정합니다.

안드로이드 5.0부터는 더 이상 selector를 사용하지 않아도 됩니다. ripple를 사용하면됩니다.

물결을 적용할 색을 선택하기만 하면됩니다. 안드로이드에 정의된 attr/colorControlHighlight의 기본 값이 마음에 들지 않는 경우 직접 다른색으로 변경하는것 보다 테마 스타일을 통해서 색상을 변경하면 됩니다. 테마 스타일을 변경하게 되면 colorControlHighlight가 사용되는 모든 색상들을 한번에 변경 할 수 있습니다.

처음 설명에서 가장자리로 퍼질때 경계 끝이 아닌 경계를 넘어서 확장 할 수 있다고 설명했다. 경계를 넘도록 물결 효과를 적용 하고 싶다면 attr/selectableItemBackgroundBorderless를 사용하면 된다. 큰뷰 내에 일부분의 View에서 더 잘작동 합니다.

 

레이아웃 비동기로 인플레이트하기

안드로이드 서포트 라이브러리 리비전 24부터 레이아웃을 비동기로 인플레이트 할 수 있는 클래스가 추가되었다.

AsyncLayoutInflater

어싱크 레이아웃 인플레이터를 사용하면 무거운 인플레이션이 수행되는 동안 UI스레드에 대한 응답성을 보장할 수 있다. 기존의 레이아웃 인플레이터를 사용하는 경우 UI스레드에서 인플레이션 작업이 수행되어 무거운 레이아웃의 경우 화면이 끊기거나 심한 경우 ANR이 발생된다. 이러한 문제를 보완하기 위해 AsyncLayoutInflater를 제공한다. 별도의 스레드에서 인플레이션을 수행하고 생성된 뷰를 UI스레드로 콜백 받을 수 있다.

사용방법은 레이아웃 인플레이터와 동일하게 AsyncLayoutInflater로 클래스를 생성 후 inflate() 메서드를 호출하면 된다.

AsyncLayoutInflater(@NonNull Context context)

inflate() 메서드 호출 시 인플레이트 대상의 레이아웃 리소스와 인플레이트 된 뷰를 속하게 하고 싶은 뷰 그룹과 콜백 받기 위한 OnInflateFinishedListener의 파라미터가 필요하다.

inflate(@LayoutRes int resid, @Nullable ViewGroup parent, @NonNull OnInflateFinishedListener callback)

생성이 완료되면 OnInflateFinishedListener의 onInflateFinished()를 통해 뷰를 전달된다.

AsyncLayoutInflater은 UI스레드가 아닌 별도의 스레드를 통해서 레이아웃을 인플레이트 하기 때문에 무거운 레이아웃으로 인해 화면의 응답성이 떨어지는 경우 사용할만하다. 액티비티 시작 시 레이아웃이 너무 큰 경우라도 화면의 전환 속도가 느리다면 로딩화면을 심플하게 구현 후 다음 비동기 처리를 통해 뷰를 생성하는 것도 하나의 방법이 될 수 있을 것이다.

구현 예시

빠르고 유연한 ContraintLayout

ContraintLayout?

ContraintLayout은 2016 구글 I/O를 통해 발표된 안드로이드의 새로운 레이아웃이다. 안드로이드 스튜디오(2.2 Preview2 부터)에 내장된 새로운 레이아웃 에디터(Blue Print)와 연동을 통해 이전의 레이아웃보다 쉽게 구성할 수 있다. 뷰 계층의 깊이와 복잡성을 해결하기 위해 ContraintLayout이 만들어졌으며, 앱의 UI렌더링 속도를 높일 수 있을 뿐만아니라 다양한 기기의 해상도에 최적화된 UI를 쉽게 개발 할 수도 있다. 안드로이드 서포트 라이브러리를 통해 사용가능하며 API 레벨 9부터 사용가능하다.

build.gradle에서 해당 라이브러리를 추가한다.

compile 'com.android.support.constraint:constraint-layout:1.0.0-alpha2'

ContraintLayout 속성

  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintLeft_toTopOf
  • layout_constraintLeft_toBottomOf
  • layout_constraintLeft_toLeftOf
  • layout_constraintLeft_toRightOf
  • layout_constraintRight_toTopOf
  • layout_constraintRight_toBottomOf
  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintCenterX_toCenterX
  • layout_constraintCenterY_toCenterY
  • layout_constraintBaseline_toBaselineOf

속성을 보면 알겠지만 많은 속성들로 인해 복잡하고 뷰들 간의 관계에 대한 속성들이 대부분이다. 자식뷰들관의 관계를 연결해주는 것을 보면 RelactiveLayout와 비슷해보이지만 ContraintLayout은 자식들과 부모관의 관계에 대한 정렬및 배치 방식과 좌표가 아닌 비율을 통해 위치가 지정되는등 다양한 기능을 가졌다.

 

레이아웃 에디터

개발자라면 그동안 레이아웃을 XML편집기를 이용하여 텍스트로 작성했을 것이다. 하지만 ContraintLayout은 무수히 많은 속성과 타겟 뷰의 ID를 값으로 주는 만큼 레이아웃구조를 텍스트로 작성하거나 또는 읽을때 예전보다는 확연히 힘들것이다. 이렇게 복잡한 레이아웃을 텍스트가 아닌 새롭게 선보인 디자인 툴(Blue Print)을 이용하면 훨씬 쉽고 간단하게 작성할 수 있다.

레이아웃 에디터는 UI설계를 하는 Design과 뷰들관의 관계를 보여주는 BluePrint 화면 2개가 나타난다. 이 2개 화면을 동시에 보거나 따로보는 방법은 레이아웃 에디터의 상단 아이콘을 이용하면 전환할 수 있다.

be44fb8e685150a5565f19c94e36e954
Design 표시 아이콘: 한번 누르면 디자인 화면으로 전환 되며 또한번 누르면 BluePrint 화면을 동시에 2개가 나타난다.

a72c79e2a3bf45f38413fec30f3dac36
BluePrint 표시 아이콘: 한번 누르면 BluePrint화면으로 전환되며 또한번 누르면 디자인 화면과 동시에 2개가 나타난다.

 

레이아웃 편집시 쓰이는 아이콘

b25f376c52508c900dbab5a8bf5d68bf
BluePrint화면의 뷰들간의 관계에 대한 정보를 숨기고 보일 수 있다.

f6ea28b2afd03030cfeed10d9b3f165a
새로운 뷰를 드래그 하는 경우 다른뷰들간의 관계를 자동으로 연결할 수 있다.

3fd3b60139267dc5bd0e2abce72f6f30
관계를 정보를 모두 삭제 한다.

4d928284744c25094798de88e6cce11d
자동으로 관계에 대한 정보를 연결한다.

7c2b5cfb7453a9614c2e5507998eff27
마진값의 단위를 선택한다. 0, 8, 16dp로 전환이 가능하다.

 

레이아웃 내에서 쓰이는 툴


하나의 뷰를 나타내며 가로/세로 크기와 다른뷰와의 관계에 대해 화살표로 설정할 수 있는데 자세히 알아보자.

99a8ebe4d5ada56e4abacb2dfedc9d50
뷰 크기 변경 컨트롤: 각모서리 가장자리에 있는 네모 모양을 통해 뷰의 크기를 늘리고 줄일 수 있다.

924dedda9eb89c0ab8f6a268c4558e13
관계 설정 컨트롤: 둥글게 생긴 부분으로 다른 뷰들관의 관계를 지정할 수 있다. 가로축의 컨트롤은 다른뷰의 가로축에만 연결되며, 세로축은 다른뷰의 세로축에만 연결된다. 이미 관계가 지정되어 있을때 클릭하면 해제된다.

a6b25ad94ce53ddfedbf2d689a506b78
베이스 라인 컨트롤: 뷰의 기본 라인을 맞춘다. 베이스 라인은 뷰내의 실제 컨텐트가 배치해있는 위치이다. 해당 컨트롤을 선택하기위해서는 커서를 몇초간 위치해있어야 한다.

 

ContraintLayout에서 뷰크기 지정

뷰의 크기는 우리가 알고 있듯이 고정된크기, 뷰의 컨텐츠에 맞게 지정되는 방식, 부모크기를 따라가는 방식이 있다. 이를 UI적으로 표현하여 좀거 쉽게 설정가능하다.

e8124ddde04b2aa76e42f79582123218

7300702340c7018202fbdcf57af5b525
고정된크기: 뷰의 사이즈가 고정되어 있다.

709b28b53490f5fb5f6dcdc5380a6fb3
부모 컨텐츠 크기: MATCH_PARENT방식으로 작동한다.

af2e72fd3b0d4188c61e06da211f9835
뷰 컨텐츠 크기: 뷰의 크기에 따라 크기가 설정된다.

edcfce9f89f19fb900de367212f55c3a
수평및 수직 정렬: 부모 뷰와 자식뷰가 연결되어 있는 경우 정렬을 퍼센테이지로 설정할 수 있다. LinearLayout의 weight와 비슷하다고 생각하면 쉽다.

팁!

처음 ContraintLayout을 접해보면 생각보다 힘든 작업이 될가능성이 높다. 이것저것 버튼을 눌러서 레이아웃을 맞추기에 생각보다 번거롭기 때문이다. 그래서 한가지 팁을 소개해보겠다. 먼저 뷰들간의 관계를 연결하지 말고 배치만으로 작업을 한다. 연결 되어 있다면 모두 끊은 상태에서 배치작업을 한뒤 자동 관계아이콘을 클릭하여 연결을 자동으로 구성 후 보정작업을 하거나 간단한 경우 직접 연결해준다.

 

ContraintLayout은 알파단계로 약간의 버그가존재하며 디자인툴이 생각보다 빨리 움직이지 않고 한번씩 다운되는등의 문제점이 발생된다. 하지만 기존의 너무 깊은 레이아웃을 구조로 앱의 성능에 문제가 생긴점을 해결해줄 속 시원한 레이아웃이 될것 같다. 그리고 BluePrint로 인해 디자이너도 툴을 쉽게 익히고 사용하는데 무리가 없을것 같다. 기존의 계층구조가 복잡한 레이아웃이 있다면 조금씩 바꿔나가는 것에 추천한다.