구현하려고 했던 것

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) 
이런 코드 없어도 됨

스터디를 하면서 백엔드를 찍먹하기로 했다.

이후에 참고용으로 순서만 적어두고, 삽질 기록을 적어본다.

 

1. EC2 인스턴스 생성 (우분투 AMI)

2. 탄력적 IP 연결, 보안그룹 설정

    -> 인바운드 규칙에서 SSH 접근 가능 IP를 내 IP로 제한하니까 EC2 Instance Connect로 연결이 안 되었음. PuTTY로 연결하니 정상적으로 연결 됨

3. 도메인 연결

    -> 가비아에서 산 도메인을 route53에 연결할 때... 호스팅 영역을 만들고 NS레코드에 있는 값들을 복사해서 가비아 홈페이지의 도메인 관리에 네임 서버로 옮겨야 한다. (라우트53에서 만들어진 NS 레코드의 값들이 곧 가비아 해당 도메인의 새로운 네임 서버인 것) 이렇게 하니까 10분만에 됨 (...)

4. PuTTY와 연결

5. 타임존 변경, 호스트네임 변경

6. 도커 설치

7. 도커에 아파치2, MySQL, vsftpd 이미지 생성

  - 도커 파일 작성해서 아파치2 이미지를 생성하던 와중... 명령어를 실행하고 싶어서 CMD 구문을 추가했는데 계속 이미지 생성할 때 알 수 없는 명령어라고 나옴.. 알고보니까 'CMD apachectl -D -FOREGROUND' 그대로 입력해야 했음. (대괄호 ㄴㄴ)

  - 도커 이미지 삭제가 안 될 때, <none>으로 뜰 때는 해당 이미지로 만든 컨테이너가 존재하는지 확인한다. 해당 컨테이너가 실행중이라면 컨테이너를 중지하고(docker stop [컨테이너명]) 컨테이너를 삭제한 뒤(docker rm [컨테이너명]) 이미지를 삭제하면(docker rmi [이미지명 또는 이미지 ID]) 된다. 

  - 도커 파일을 각 이미지별로 하나씩 만들어야 했다.

8. 7에서 받은 이미지를 이용해서 컨테이너로 만들기

  - volumes로 로컬 폴더를 컨테이너의 폴더로 마운트할 때... 파일 경로를 잘못 입력해서 마운트가 안 됐음. (보내는 곳에서 절대 경로로 써 주고, 받는 곳도 /var/www/html/ 로 설정함.. 파일이 마운트가 안 된다면 내 로컬 폴더에 있는 경로인지 꼭 확인할 것!

  - 도커 컴포즈 파일 작성해서 아파치 2 컨테이너를 생성하려고 docker-compose up을 했는데 attaching to... 단계에서 너무 오랜 시간이 걸리는 것임... 근데 그냥 'docker-compose up -d' 를 해 주면 되는 거였다.

  - ports contains an invalid type, it should be a number, or an object 라는 에러를 뱉을 때는

      > 포트 각 항목을 따옴표로 묶는다 (0.0.0.0:20:20이 아닌 '0.0.0.0:20:20')

      > ip 빼고 포트 번호만 입력한다. ('0.0.0.0:20:20'이 아닌 '20:20'으로 입력한다.)

  - vsftpd는 컴포즈 파일로 만들다가 무슨 문제가 나서 그냥 명령어로 컨테이너를 만들었음..

docker run -d -v /var/ftp:/home/vsftpd \
-p 20:20 -p 21:21 -p 50000-50010:50000-50010 \
-e PASV_ADDRESS={아이피주소} -e PASV_MIN_PORT=50000 -e PASV_MAX_PORT=50010 \
-e FILE_OPEN_MODE=0644 -e LOCAL_UMASK=022 \
--name vsftpd2 --restart=always {도커 이미지 이름}

9. 방화벽 설정하기 (ufw allow ...)

10. 각 서비스 별 기본 세팅 하기

  - mysql : 문자셋을 UTF-8로 설정

    > vim이 설치가 안 되어 있는데 bash 안에서 설치하려고 하면 멈춰서 (...) 도커 파일에서 이미지 생성 시 만드도록 함. (RUN microdnf install -y vim)

  - vsftpd :

    > 익명으로도 접속이 안 될 때 : 도커 이미지 만들 때 EXPOSE로 포트 열었는지 확인

    > 익명으로는 접속이 되는데 유저로는 안 될 때 :

      - 해당 유저가 있는지 확인

      - /etc/pam.d/vsftpd 에서 auth required pam_shells.so를 주석(#)처리

      - /etc/passwd 에서 해당 사용자 맨 끝에 /bin/bash를 /sbin/nologin으로 수정

      - /etc/vsftpd/vsftpd.conf 에서 pam_service_name=vsftpd로 되어있는지 확인

11. 각 서비스 실제로 사용 할 프로그램과 연결

  - mysql -> heidiSQL => docker-compose 파일에 포트를 지정 안 해 줘서 연결이 안 되고 자꾸 에러 코드 10061만 내뱉음.. 포트 지정해주니까 정상 연결 됨

 

 

 

 

 

기타 삽질 내용

1. vi 로 새 파일 만들었는데 저장을 못한대 <- sudo vi 써야 함 (파일 편집 단계에서 해결할 수 있는 걸로 암.. 검색 ㄱㄱ)

2. 권한이 없대 <- sudo 붙이면 됨

일단 기본적으로 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로 이동한다.

 

와! 날먹!

+ Recent posts