구현하려고 했던 것

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 붙이면 됨

심심해서 써 보는 비전공자 출신 개발자의 공부 방법(이라고 쓰고 걸어온 길이라고 읽는다)

컴공 전공자는 3번부터 보면 됨.

100% 옳다고는 볼 수 없다. 되려 야매에 가깝다. 반박시 님 말이 맞음 ㅇㅇ

 

1. 학원이나 책 등으로 기본 공부

10년 전이긴 했지만.. 학원에서 딱히 뭔가를 잘 가르쳐주지 않았음.

더군다나 안드로이드는 주가 아닌 부의 느낌이 낭낭했기에.. (C 2개월, 자바 2개월, 안드 1개월)

개인적인 생각으로는 책 보고 혼자 독학해도 된다고 생각함.

 

2. 정보처리기사 공부 (자격증을 꼭 안 따도 됨)

자격증을 무조건 따라는 얘긴 아님. 물론 따놓으면 좋긴 함 (검증할 수 있는 수단이 생기기에..)

자격증 공부를 한번 해 보면 해당 분야의 전반적인 지식을 얕게나마 맛보기할 수 있음.

CS는 정처기 공부할 때 한번 팍 하고, 나머지는 실무 하면서 그때그때 필요한 공부 해 나가도 된다.

물론 미리 알아둬도 나쁠 건 없다. 아는 만큼 보인다는 말도 있으니...

 

3. 앱 하나 카피해보기

일단 카피라고는 했지만 뭐 혼자서 기획하고 디자인해서 만들어도 된다.

 

그럼에도 불구하고 카피하라는 이유는...

① 본인이 디자이너가 아니고 디자인을 정하지 않고 만들면 결국 '개발하기 쉬운 디자인'으로 타협하게 된다. 디자인이 아닌 기획도 마찬가지... '아 이거 좀 어려운데...'하면서 쉬운 기획으로 바꾸고 됨.

② 시장에 나왔다 + 인지도가 있다 = 검증된 UI/UX, 어쨌든 구현해낼 수 있다는 가능성

 

참고로 안드로이드 개발은 '프론트엔드' 개발이다. 곧 '보여주는' 개발이다.

소개팅 나갔을 때 내면이 아무리 예뻐도 외면이 엉망이면 인정받지 못하는 것처럼

개발자들이나 코드 예쁜 거 알아주지,

소비자는 코드 보지도 못하니까 겉으로 보이는 레이아웃, 속도 등으로만 판단할 수 밖에 없음.

 

....그렇다고 겉모습에만 치중해서 코드를 개판으로 짜라는 말은 아니다.

아무리 예뻐도 성격이 지X이면 아웃이듯이

코드를 개판으로 짜서 소비자한테 체감이 될 정도로 영향을 주면.. 그건 그냥 아웃이다.

 

뭐 그래도 이 단계에서는 그저 만들기만 해도 실력이 늘기 때문에...

일단 만들기 ㄱㄱ

어차피 이 단계에서는 자기 코드가 예쁜지 개판인지 모른다.

그저 돌아가는 게 기쁜 단계라서... 지금은 그 기쁨을 충분히 만끽하면 된다.

 

4. 코드 리팩토링

3번에서 반복해서 만들었다면, 어느정도 문법도 익숙해지고 자신만의 코드 스타일도 만들어졌을 것이다.

이제 코드를 다시 되돌아보면서 다듬는다.

책을 읽어도 좋고, 코드 스타일 등을 다룬 포스팅을 보면서 공부해도 좋다.

사람이 처음부터 완벽할 수는 없다. 사람이 만든 코드도 마찬가지다.

완벽한 코드는 없다. 그냥 계속 수정해가는 것이다.

 

5. 디자인 패턴, 아키텍쳐 공부

디자인 패턴이나 아키텍쳐를 공부해서 좀 더 알기 쉬운 코드로 만들어간다.

이에 필요한 다른 개념들도 함께 공부함.

 

6. 면접 질문, 안드로이드 로드맵 보면서 부족한 부분 채우기

면접 질문이나 안드로이드 로드맵을 보면서 부족한 부분을 채우면 된다.

 

 

 

아침갬성으로 적어본당...

 

뷰에 위치 애니메이션을 주는 방법은 여러가지가 있는데

첫번째로 Animation을 extends한 TranslateAnimation 등의 클래스를 사용하는 방법이 있고

두번째로 ObjectAnimator를 사용하는 방법이 있음.

 

전자는 애니메이션이 끝나면 다시 제자리로 돌아가고 (fillAfter 속성을 true로 주면 되긴 함)

후자는 끝난 자리에 그대로 있음

 

<< translate animation 적용 시 >>

( 참고 ) anim = TranslateAnimation / anim2 = ObjectAnimator

 

anim start! x > 545.0 translationX > 0.0
anim end! x > 545.0 translationX > 0.0

anim2 start! x > 545.0 translationX > 0.0
anim2 end! x > 645.0 translationX > 100.0

 

 

근데 또 ScaleAnimation이랑 ObjectAnimator이랑은 width 차이가 없다.

실시간으로 변경해주려면 layoutParam의 width를 수정하는 애니메이션을 직접 만들라고..

 

왜지...

+ Recent posts