구현하려고 했던 것

ViewPager2(이하 뷰페이저)를 이용한 Carousel 뷰를 구현하려고 하였음.

뷰페이저의 어댑터는 RecyclerView.Adapter를 상속받은 클래스를 사용하였음.

양 옆의 뷰가 좌우에서 조금씩 나오게 하기 위해 setPageTransformer로 각 뷰의 translationX를 조정하였음.

 

 

문제 상황

 

뷰페이저의 아이템을 클릭하면 새로운 액티비티를 띄우는데, 해당 액티비티를 종료 후 현재 액티비티로 돌아오면 뷰페이저의 아이템 뷰의 좌우 간격이 이상해지는 현상이 발생함.

 

 

원인

 

리사이클러뷰 어댑터를 사용하며 뷰를 재사용하는 과정에서, 기존 뷰에 설정한 translationX 값이 그대로 남아있었음.

예를 들어 나는 3번째 뷰를 표시한다고 생각했지만 실제로는 뷰를 재사용하면서 0번째 뷰의 translationX 값이 적용된 것.

 

setPageTransformer로 설정했던 것은 onResume 시에는 호출되지 않는다.

 

어떻게 보면 리사이클러뷰 아이템 뷰에서 체크박스 사용할 때 체크가 제대로 안 되는 문제와 동일한 원인이기도 함.

 

 

해결 방법

 

1. 어댑터에 아이템 뷰들의 translationX 값을 저장할 변수(이하 txList) 추가

- txList는 List가 갱신되거나 하면 이에 맞게 초기화되어야 함

 

2. 어댑터의 onBindViewHolder에서, 해당 itemView의 translationX 값을 txList에서 가져와서 적용

holder.itemView.translationX = txList[position]

 

2. 어댑터에 선택된 페이지를 기준으로, 나머지 translationX 값을 계산해서 txList 변경하는 메소드 추가

fun resetViewTranslationXList(center: Int) {
    txList = txList.mapIndexed { index, tx ->
        when(index-center) {
            -1 -> -pageTranslationX //현재 보이는 뷰 바로 앞 포지션
            1 -> pageTranslationX   //현재 보이는 뷰 바로 뒤 포지션
            else -> 0f  //현재 보이는 뷰 또는 화면에 보이지 않는 뷰
        }
    }.toMutableList()
}

 

 

3. 액티비티의 onPause() 메소드 내에 2에서 추가한 메소드를 호출하도록 함.

override fun onPause() {
    super.onPause()
    pagerAdapter.resetViewTranslationXList(requireBinding().pager.currentItem)
}

 

 

---- 또는 아래와 같이도 가능할 듯 ----

 

1. 어댑터에 currentItem 변수 추가 (뷰페이저가 선택한 아이템의 인덱스를 저장)

class BSSMapAdapter: RecyclerView.Adapter<BSSMapHolder>() {
    ...
    var currentItem = 0	//현재 뷰페이저에 표시되는 뷰
    ...
}

 

2. 어댑터의 onBindViewHolder에 현재 position에 따라 translationX 설정하는 코드 추가

when(holder.layoutPosition-currentItem) {
    -1 -> -pageMargin
    1 -> pageMargin
    else -> 0f
}.let {
    holder.itemView.translationX = it
}

 

3. onPause() 시 뷰페이저의 currentItem 값을 어댑터에 저장

pagerAdapter.currentItem = requireBinding().pager.currentItem

 

 

수정 후 결과

 

사용자가 페이지를 넘길 때는 setPageTransformer로 설정한 대로 각 아이템뷰의 translationX가 설정됨.

액티비티가 pause 상태가 될 때 각 뷰에 설정되어야 할 translationX 수치가 어댑터 내부 변수에 저장됨.

 

이후 다른 액티비티를 다녀오면 리사이클러의 onBindViewHolder가 실행되며,

저장했던 translationX 수치가 각 아이템 뷰에 적용됨.

 

* 일단 다른 액티비티를 다녀올 때만 문제가 발생해서 onPause()에 resetViewTransitionXList()를 호출하도록 하였으나,

뷰페이저의 페이지 변경 콜백에서 페이지가 선택된 이후에 호출하게 할 수도 있을 것임. 필요한 경우에 맞게 선택하면 될 듯.

라이브러리를 만들 때 AAR로 만드는 방법이 있고 JAR로 만드는 방법이 있는데,
공식 홈페이지에서는 AAR만 설명되어있고

인터넷에는 전부 옛날 버전으로만 적혀있어서 삽질을 많이 했다.

 

AAR - 리소스 포함 가능 (UI 라이브러리에 사용함)

JAR - 리소스 포함 불가 (유틸 라이브러리에 사용함)

 

1. 프로젝트 우클릭->New->Module (혹은 File-New-NewModule)

 

2. 왼쪽의 Templates 에서 'Java or Kotlin Library' 선택하고 우측에 정보 입력

(Phone&Tablet은 구동 가능한 Application module, Android Library는 AAR 라이브러리)

 

3. 프로젝트를 빌드하면 해당 모듈의 build/libs 안에 jar 파일이 생성된다.

 

task.delete(type: Delete) 
이런 코드 없어도 됨

일단 기본적으로 elevation은 API21에 추가되었고 translationZ는 이전에 추가되었음.

 

뷰의 실질적인 Z 위치는 elevation + translationZ 임.

 

elevation = view의 base z 위치, 부모 뷰에서 해당 뷰의 z 위치를 설정

translationZ = elevation을 기준으로 한 상대적인 z축 위치

 

 

 

 

 

참고 : 
https://stackoverflow.com/a/29850666 
https://developer.android.com/reference/android/R.attr#elevation
https://developer.android.com/reference/android/R.attr#translationZ

일반 Notification을 만들 때  (빨간색 박스)

Notification Group Summary용 notification을 하나 더 만든다. (노란색 박스)

notification을 notify해줄 때 일반 Notification은 id를 다르게 하고, summary용 notification은 id를 같게 한다.

 

이렇게 하면 그룹화되었을 때 누르면 naver로, 그룹을 펼쳐서 각각의 notification을 누르면 google로 이동한다.

 

와! 날먹!

오늘은 아래의 영상처럼 Splash Screen에 애니메이션을 적용해 볼 것이다. 

6초짜리 만드는데 3시간 걸린 거 실화냐? 가슴이 웅장해진다..

 

1. themes.xml에 아래 코드 추가

    <!-- splash screen -->
    <style name="Theme.FortuneWheel.Starting" parent="Theme.SplashScreen" >
        <item name="android:windowSplashScreenBackground" tools:targetApi="s">@color/white</item>
        <item name="android:windowSplashScreenAnimatedIcon" tools:targetApi="s">@drawable/avd_splash</item>
        <item name="windowSplashScreenAnimationDuration">1500</item>
        <item name="postSplashScreenTheme">@style/Theme.FortuneWheel</item>
    </style>

 

2. drawable 폴더 안에 avd_splash.xml 파일을 만들어준다. 이 파일은 AnimatedVectorDrawable이다.

<?xml version="1.0" encoding="utf-8"?>
<animated-vector
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:drawable="@drawable/ic_launcher_splash">
    <target
        android:name="rotationGroup"
        android:animation="@animator/rotation"
        />

</animated-vector>

 

3. drawable 폴더 안에 ic_launcher_splash.xml 파일을 만들어준다.

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="240dp"
    android:height="240dp"
    android:viewportWidth="240"
    android:viewportHeight="240">
    <group
        android:name="rotationGroup"
        android:pivotX="120.0"
        android:pivotY="120.0">
        <path
            android:pathData="M120,120m-80,0a80,80 0,1 1,160 0a80,80 0,1 1,-160 0"
            android:fillColor="#FF648D"/>
        <path
            android:pathData="M120,120m-72.07,0a72.07,72.07 0,1 1,144.15 0a72.07,72.07 0,1 1,-144.15 0"
            android:fillColor="#FFA3BE"/>
        <path
            android:pathData="M188.58,120C188.58,82.12 157.88,51.42 120,51.42C107.96,51.42 96.13,54.59 85.71,60.61L120,120L188.58,120Z"
            android:fillColor="#FFC3D3"/>
        <path
            android:pathData="M85.71,60.61C64.49,72.86 51.42,95.5 51.42,120C51.42,144.5 64.49,167.14 85.71,179.39L120,120L85.71,60.61Z"
            android:fillColor="#FFE7E9"/>
        <path
            android:pathData="M85.71,179.39C96.13,185.41 107.96,188.58 120,188.58C157.88,188.58 188.58,157.88 188.58,120L120,120L85.71,179.39Z"
            android:fillColor="#ffffff"/>
        <path
            android:pathData="M120,120m-11.2,0a11.2,11.2 0,1 1,22.4 0a11.2,11.2 0,1 1,-22.4 0"
            android:fillColor="#FF648D"/>
        <path
            android:pathData="M120,120m-7.33,0a7.33,7.33 0,1 1,14.65 0a7.33,7.33 0,1 1,-14.65 0"
            android:fillColor="#ffffff"/>
    </group>
</vector>

 

 

4. animator 폴더 안에 rotation.xml 파일을 만들어준다.

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:startOffset="300"
    android:duration="1000"
    android:propertyName="rotation"
    android:valueFrom="0"
    android:valueTo="360"
    android:valueType="floatType"
    android:interpolator="@android:anim/anticipate_overshoot_interpolator"/>

 

5. AndroidManifest.xml에서 Application theme (또는 첫 Activity의 Theme)를 @style/Theme.FortuneWheel.Starting 로 변경한다.

걱정하지 마라.

Splash Screen이 끝나고 나면 Theme.FortuneWheel.Starting에서 postSplashScreenTheme로 설정한 테마로 변경된다.

 

 

6. 첫번째 액티비티 (일반적으로는 MainActivity)의 onCreate 메소드에 아래 코드를 넣어준다.

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
		//(필수)setContentView 이전에 설정해줘야 함
        installSplashScreen()
		
        //(선택)splashScreen이 사라질 때의 애니메이션을 주고 싶다면 아래 코드 입력
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            splashScreen.setOnExitAnimationListener { splashScreenView ->
                val anim = ObjectAnimator.ofFloat(splashScreenView, View.ALPHA, 1f, 0f)
                anim.duration = 1000
                anim.doOnEnd { splashScreenView.remove() }
                anim.start()
            }
        }

        setContentView(binding.root)

 

 


아래는 나의 삽질 일기다.

 

1. Splash screen의 icon에 애니메이션이 안된다?

=> 적용한 Theme의 windowSplashScreenAnimatedIcon에 넣어준 drawable이 AnimatedVectorDrawable이나  AnimationDrawable인지 확인하기 (단, AnimationDrawable 넣어줬을 때도 애니메이션이 되는지는 확인 못함)

=> 적용한 Theme이 Theme.SplashScreen을 상속받았는지 확인하기

 

2. Splash screen의 icon이 잘린다?

=> 전체 크기 240dpx240dp에 아이콘은 160dpx160dp가 되도록 이미지를 만들어야 한다.

=> 만약에 오른쪽같은 이미지 밖에 없어요 라고 하면.. vector 파일(ic_launcher_splash.xml)의 group에 scaleX와 scaleY를 설정해서 크기를 조절해준다.

 

3. Splash screen의 icon의 애니메이션이 안 끝났는데 화면이 이동해요

=> Theme.FortuneWheel.Starting의 windowSplashScreenAnimationDuration를 애니메이션 길이와 같거나 길도록 맞춰주세요.

 

4. Splash screen의 icon의 애니메이션이 너무 빨리 시작해요

=> rotation.xml에 startOffset 속성을 주세요

 

 


 

 

참고 자료 :

https://developer.android.com/develop/ui/views/launch/splash-screen

https://developer.android.com/reference/android/graphics/drawable/AnimatedVectorDrawable

+ Recent posts