2주차 및 3주차 적용해본 사항 정리

CmplxN·2020년 8월 8일
0

Constraint Layout의 Guideline 활용

  • Guideline : horizontal 또는 vertical하게 보이지 않는 선을 그어 Constraint Layout 내의 다른 뷰들을 배치하는 도구.
    • layout_constraintGuide_begin : 좌측 또는 상단 기준 고정된 값만큼 떨어져 Guideline 배치
    • layout_constraintGuide_end : 우측 또는 하단 기준 고정된 값만큼 떨어져 Guideline 배치
    • layout_constraintGuide_percent : 전체 너비 또는 높이에서 비율에 따라 Guideline 배치

Transparent Statusbar

  • 처음에는 투명한 Statusbar를 구현하기 위해 강제로 패딩을 주는 방식을 사용했다.
    하지만 Constraint Layout의 GuideLine을 활용하면 성능과 편이성을 모두 챙길 수 있다.
// decorview(최상단 레이아웃)을 전체 화면으로 설정
window.setDecorFitsSystemWindows(false)

// status bar의 배경색을 투명하게 만든다.
window.statusBarColor = ContextCompat.getColor(this, R.color.transparent)

// guideline을 statusbar와 navigationbar 높이에 맞춰 이동시킨다.
// 필요에 따라 guideline의 위치를 조정해주면 된다.
guidelineTop.setGuidelineBegin(statusBarHeight(this@MainActivity))

// setDecorFitsSysyemWindows로 status뿐만 아니라 navigation까지도 확장되므로.
guidelineBottom.setGuidelineEnd(navigationBarHeight(this@MainActivity))

statusBarHeight와 navigationBarHeight는 이전 글의 topHeight을 참조하면 될 것이다.

overlapping views

  • Guideline을 이용하면 아래와 같이 뷰위에 다른 뷰를 걸치는 구조를 만들 수 있다.
    1. GuideLine을 원하는 위치에 배치시킨다.
    2. 아래에 깔릴 뷰를 constraint top을 Guideline으로 잡고 먼저 그린다.
    3. 위에 올릴 뷰의 constrint top과 bottom을 모두 Guideline에 잡고 그린다.
<ImageView
    android:id="@+id/imageViewDown"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:background="@android:color/holo_orange_dark"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/guideline"/>

<androidx.constraintlayout.widget.Guideline
    android:id="@+id/guideline"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:orientation="horizontal"
    app:layout_constraintGuide_percent="0.5" />

<ImageView
    android:id="@+id/imageViewUpper"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:background="@android:color/holo_green_light"
    app:layout_constraintBottom_toBottomOf="@id/guideline"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="@id/guideline" />

비슷한 역할을 할 수 있는 Barrier의 경우, Guideline이 설정된 end와 begin에 위치하는 것과 달리 기준이 될 뷰에 의해 동적으로 위치가 조정된다.

Multi-type RecyclerView

  • RecyclerView를 활용해 다양한 아이템들을 나열해서 표시할 수 있다.
    그러나 불행히도 아래와 같이 리스트에 여러 종류의 뷰를 나타내야하는 경우가 있다.

    그림의 경우 파란색으로 표시된 환영문구, 입장문구, 일반채팅 세 종류의 다른 뷰를 사용하고 있다.

  • 이럴 경우 RecyclerView의 Adapter에서 onBind와 onCreate에서 when으로 경우를 나눠서 처리할 수 있다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
	when (viewType) {
    	1 -> ...
    }
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
	when (list[position].type) {
    	1 -> ...
    }
}

  • 물론 Adapter class에서 모든 경우를 나눠서 처리할 수도 있다.
    하지만 다음과 같이 (생성의)책임을 분리하는 것이 코드 관리 면에서 훨씬 좋다.
    아래와 같은 구조를 한번 고려해보자.
// ViewHolder 생성을 맡기자.
class ChattingViewHolderFactory {
    companion object {
        fun makeViewHolder(type: MessageType, parent: ViewGroup): ViewHolder {
            val resId = when (type) { // 메시지 타입에 따른 레이아웃 선택
                MessageType.NOTICE -> R.layout.layout_notice
                ... (여러 종류)
            }
            val binding: ViewDataBinding =
                DataBindingUtil.inflate(LayoutInflater.from(parent.context), resId, parent, false)
            return when (type) {
                MessageType.NOTICE -> NoticeViewHolder(binding)
                ... (여러 종류)
            }
        }
    }
}

// ViewHolder 종류별로 다른 bind를 구현해주자.
abstract class ViewHolder(val binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) {
    abstract fun bind(chat: Host.Chat) // onBindViewHolder의 동작
}
// Adapter 클래스의 onCreateViewHolder와 onBindViewHolder는 아래와 같게 될 것이다.
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
        ChattingViewHolderFactory.makeViewHolder(MessageType.values()[viewType], parent)
        
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(list[position])

RxJava 활용

RxBinding을 이용한 ui 이벤트 처리

RxJava를 직접 사용해서 ui 이벤트 처리를 할 수도 있지만, RxBinding이라는 잘 알려진 라이브러리가 있다. 링크
프로젝트를 진행하며 사용했던 몇가지 예제를 살펴보자.

  • edittext에 글이 써져 있어야만 버튼이 활성화되는 이벤트(map) :
    textChanges에 text가 빈 문자열인지 map으로 변환해 사용했다.
    compositeDisposable에 add하여 onDestroy()에 clear()하도록 했다.
RxTextView.textChanges(edittextChattingInput)
    .map { it.isNotEmpty() }
    .subscribe {
        btnChattingSend.setClickability(it)
    }.addTo(compositeDisposable)
  • 버튼에 대한 중복 클릭 방지(throttleFirst) :
    click에 throttleFirst를 걸어서 마구 클릭해도 여러번 requestUserInfo(uid)를 호출하지 않도록 했다.
RxView.clicks(btnProfileTest)
    .throttleFirst(1000L, TimeUnit.MILLISECONDS)
    .subscribe {
        requestUserInfo(uid)
    }.addTo(compositeDisposable)

기회가 되면 RxBinding을 직접 뜯어 보는 것도 좋을 것 같다.

Firestore DB접근에 활용

  • DB에 데이터를 insert하는 작업은 성공 / 실패 두가지이므로 Completable을 사용했다. Completable을 순서대로 이어서 할 경우 andThen을 활용했다.
  • DB에 데이터를 요청하는 작업은 데이터 수신 / 실패 두가지이므로 Single을 사용했다.
  • 채팅을 위한 SnapshotListener의 경우 publishSubject에 onNext하는 방식을 사용했다.
  • 아래의 DB에 insert하는 예시를 살펴보자. 나머지 경우도 비슷한 맥락으로 만들면 된다.
// viewmodel
fun postChatting(chat: Host.Chat) {
    broadcastRepository.postChatting(chat, broadcastId) // returns Single
        .timeout(TIMEOUT, TimeUnit.MILLISECONDS)
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(...)
        .addTo(compositeDisposable)
}
// repository
fun postChatting(chat: Chat, broadcastId: String): Completable =
    Completable.create { emitter: CompletableEmitter ->
        firestoreInstance
            ... insert할 경로
            .add(chat)
            .addOnCompleteListener {
                if (!it.isSuccessful) 
                    emitter.onError(...)
                else
                    emitter.onComplete()
            }
    }

마무리

그간 학습 및 적용해봤지만 다음 두가지 주제가 빠졌다.

  1. Dagger2를 이용한 Dependency Injection
  2. CustomView + 안드로이드에서 뷰가 그려지는 과정

괭장히 중요한 주제로 다음에 따로 다뤄볼 예정이다.

profile
Android Developer

0개의 댓글