발견록_04

김재현·2023년 5월 3일
0

안드로이드

목록 보기
6/12

1. 상태표시줄은 없애지만 하단의 네비게이션바는 남기는 방법

이전에 위와 같이 사용하여서 상태표시줄을 없앴다. 이것은 전체화면을 사용하겠다는 의미이므로 하단의 네비게이션 바가 겹쳐지는 문제가 나타난다.

상태표시줄의 높이를 구했던 것과 마찬가지로 네비게이션바의 높이또한 구할 수 가 있는데 이를 통해서 맨 밑 View의 bottomMargin을 네비게이션바의 높이만큼 주어서 띄우는 것이다.


2. BottomNavigationView

위와 같이 xml에서 material에 있는 BottomNavigationView를 만들 수 있다. 이를 통해 menu를 직접만들어서 Navigation바를 만들어 줄수 있다.
메뉴에는 어떻게 들어갈것인지를 쓰는데 나는 지금 글씨는 없고 아이콘만 존재하는 NavigationBar를 만들고자 했기 때문에 title에 ""를 주었다.
icon에 들어갈 부분에는 selector를 포함한 drawable을 넣어준다. BottomNavigation의 기능으로 이것이 선택되었는지 아닌지를 인식하는 기능이 있기 때문.state_selected가 true면 선택되었을때, false는 선택되지 않았을때이다. android:drawable에 이미지 아이콘을 넣어준다.

이렇게만 적는다면 아이콘의 색이나 배경이 앱의 기본 색에 따라간다. (primary_500인가..?) 이를 방지하기 위해서는 MainActivity에서 추가적인 속성을 준다. 이렇게 해주면 내가 원했던 원래 색상이 나오게 된다.

지금 이 BottomNavigation은 기본 NavigationBar의 높이만큼 아래의 마진을 줘서 띄어주고 있기 때문에 아래가 비었을때 나타나는 elevate현상이 나오게 된다. 이런 elevate는 아래에 검정 그림자가 지게 되는데 이를 없애주기 위해서 xml에서 android:outlineProvider=none를 지정한것이다.


3. View아래 터치 막기

코드의 Layout의 대부분을 ConstraintLayout을 사용했기 때문에 아래에 있는 블럭일 수록 윗층에 쌓이게 된다. 이때 윗층의 bakcground를 @null로 주었을 경우 배경색이 투명하게 되는데 View아래에 버튼이 있으면 눌린다는 것을 발견하였다.

이러한 문제는 background의 색상이 지정되어있어도 나타나는데 View아래의 버튼을 누르는 것을 막아주기 위해서는 다음과 같은 코드를 써서 막아줄 수 있다.


4. Retrofit2 사용법

Retrofit2를 사용하여 간단하게 url 주소를 통해서 데이터를 가져올 수 있다.
build.gradle에 build할 내용을 적어준다.gson과 관련된 내용은 json으로 받아온 파일을 gson으로 변경하여 쉽게 코드를 작성할 수 있기 때문에 받아온다.

첫번째로는 어떤식의 데이터를 가져올것인지 Data파일을 만든다.
데이터의 형태에 따라 다르겠지만 이 경우에는

처음으로 가져올수 있는 데이터는(최상위데이터는) StarList로 []리스트 안에 {...},{...},{...}...5개의 배열이 또존재하고 그 배열 안에는 또 profImg라는 이름이 배열이 존재한다.

정보를 가져오기 위해서는 {}종류당 하나씩 Data를 만들어 주어야 하는 것 같았기에 StarList에서 가져오는 데이터로는 FollowingData 클래스를 리스트로 같고있는 데이터이고, FollowingData는 가져올 데이터에 추가로 profImgData클래스를 가지고있는 데이터가 있다.

데이터가 준비되었으면 Service를 만들어서 어떤 방식으로 가져올 것인지를 지정해준다.
Get방식인지 Post방식인지, baseUrl에 추가로 들어가는 정보는 무엇인지, Post방식이면 @Body로 무슨 값을 넣어주어서 요청할 것인지 적어준다.
가져오는 데이터는 최상위 데이터를 통째로 가져오므로 최 상위 데이터인 매개변수를 제네릭에 넣어준다.

그 다음으로는 RetrofitManager 클래스를 만들어준다.

이제 MainActivity에서 service, manager, data등을 연결해주면 된다.
로 service를 지연초기화 해놓고 onCreate()함수 내부에서Manager와 Service를 연결해준다.

그 후 아래처럼 데이터가 들어갈 배열을 미리 만들어놓은 후에 함수를 만들어서 enqueue를 실행한다.서비스에서 만들어 놓은 함수를 불러와서 object 익명함수를 요청하면 자동완성으로 override해야하는게 만들어진다. onResponse에서 통신에 성공했을때 데이터를 넣어주는데 확인을 위해 response가 제대로 왔는지 검사 후에 넣어주는 것이 좋다.

데이터는 reponse.body()안에 있으며 reponse.body()가 null인지 확인 후에 아니라면 실행한다. arrayList의 addAll이라는 함수를 통해서 가져온 데이터의 배열을 우리가 사용할 ArrayList안에 넣어준다. 그 후 데이터를 원하는 곳에서 사용하면된다.

주의점, service.함수이름().enqueue는 비동기적으로 시행된다. onCreate()에서 이 함수를 사용한 후에 데이터가 전역변수로 선언한 ArrayList안에 들어가 있다고 생각하고 그 리스트의 길이를 바로 구하려고 하면 0으로 값이 나올것이다.


5. ViewPager2 자동 스크롤

이전에 뷰페이저2를 무한 스크롤이 가능하도록 만들었다. 거기에 추가로 내가 스크롤을 하지 않아도 페이지가 넘어가는 효과를 내주고싶었다. 여러 곳을 찾아봤을때 handler()를 사용하던데 handler()보다 좋은 coroutine()이 나왔기 때문에 코루틴으로 만들어보고자 했다.

참고사이트 -

lifecylce를 쉽게 관리할 수 있게 build해준다.

ViewPager2의 OnPageChangeCallback()의 onPageScrollStateChanged 메소드에 들어오는 state 값을 이용하면 뷰 페이저의 상태를 알 수 있다.
이 상태에 따라 자동 스크롤을 시작할지, 중지할지 정해준다.

만약 가만히 있고 코루틴의 job이 active가 아니라면(코루틴은 자동 스크롤에 대한 내용이 들어가있다.) 자동 스크롤이 되어야하고, 내가 드래그를 하고있다면 job을 취소시켜서 자동스크롤이 안되게 막아야한다.
lifecycleScope.launch로 코루틴을 만들어서 LifecycleOwner 컴포넌트의 수명 주기에 맞춰 자동으로 코루틴을 실행하고, 수명 주기가 끝나면 자동으로 코루틴을 취소하게 한다. 액티비티가 백그라운드에 들어가거나 소멸될 때 자동으로 작업을 취소할 수 있다.


6. Sticky ScrollView 만들기

NestedScrollView를 상속받아서 CustomScrollView를 만든다.
ScrollView속성상 ScrollView안에 RecylcerView가 들어갈 경우, 스크롤이 겹치는 문제가 일어나기 때문에 어쩔 수 없이 NestedScrollView를 상속받아서 만든다. 참고사이트

코드의 주요 내용은 CustomScrollView를 만들어서 내가 원하는 특정 지점에 도달했을때 layout의 translationY값을 바꿔주는 것이다. 특정 지점보다 더 높이 올라갔다면 그 만큼 내린값을 layout에 주는 식으로 말이다.

내가 CustomScrollView를 만들어서 xml에서 처럼 사용한다면 어떻게 될까?

일단 CustomScrollView 클래스를 만들어야한다. 그 클래스에 ScrollView나 NestedScrollView를 상속받으면 된다. 그런데 내가 하고싶은것은 붙이려고하는 뷰 (참고사이트에서는 header)가 상태 표시줄이나 맨 위가 아닌 특정 layout의 아래에 붙어야 한다는 것이다.

이를 위해서 특정 layout의 특징을 파악한 결과, layout의 높이를 추가로 알아낼 수 있어야 했다.
tabLayoutSection은 header에 해당하는 부분이고 actionBarW는 actionBarW의 binding한 값을 넣어준다. 이러한 값들은 CustomScrollView의 생성자나 set에 들어가는 부분이다. stickListner는 제대로 실행되나 아니나는 보기 위한 값으로 필요없다면 지워도 된다.

본격적인 CustomScrollView 클래스를 보자.

다음과 같이 생성자와 초기화 블럭이 존재한다. ViewTreeObserver를 통해서 화면이 전부 그려지고 난 후에 현재 존재하는 header, 즉 내가 붙이고자 하는 tabLayout의 y높이값을 불러오고 어디 위치에 붙이고자하는 actionBarW의 Height값을 찾아낸다. init을 통해 addOnGlobalLayoutListener를 초기화해주지 않으면 실행되지 않기 때문에 꼭 실행해준다.
tabLayout이 붙었는지 아닌지 판별하여 붙었거나 안붙었을때 한번만 실행되게 한다. 붙었을때 한번만 실행하고 싶은 코드가 있으면 사용하는데 그런게 아니라면 딱히 없어도 된다.
tabLayoutInitPosition와 actionbarWHeight를 통하여 스크롤 경계점을 계산해야 한다. onGlobalLayout()을 통해 값을 가져오고 remove를 통해 계속 반복실행하지 않도록 없앤다.

onScrollChanged메서드를 사용하여 tabLayout의 y값을 경계기준으로 초기화를 시켜준다. 이럼 Sticky Scroll View가 끝난다.

현재 MainActivity에는 View.OnScrollChangeListener를 상속받아서 scrollView를 관찰하고 있다. 그럼 이때는 onScrollChange 메서드를 override하는데 onScrollChange는 ScrollView 뿐만아니라 스크롤 되는 모든 것 즉, Viewpager2, RecylcerView도 같이 정보를 가져오게 된다. 따라서 scrollY를 구하고자 할때 ScrollView의 문제상 RecylcerView가 같이 스크롤이 되기 때문에 서로 다른 값이 계속 호출되는 문제가 생기기 때문에 CustomScrollView를 만들었다.

이렇게만 해도 원하는 곳에 붙고 스크롤을 해도 정상적으로 붙어서 동작하는 것처럼 보인다.

추가 해야할 사항
NestedScrollView를 쓰면 RecylcerView의 장점이 사라지는데 어떻게 해야할까? 여러 View로 나눠서 관리하는 방법이 있다는 한다. 하지만 이미 여러 constraintlayout으로 나눠져 있는데 어떻게 하면 되는지 모르겠다.

NestedScrollView를 상속받자 라는 것을 마지막에 결정했기 때문에 만약 CustomScrollView를 만들지 않고 ScrollView만 썼을때, MainActivity에서 onScrollChange내부에서 translationY의 값을 바꾸면 정상적으로 작동하는지 여부에 대해서는 실험해보지 못했다.

지금 와서 드는 생각
어짜피 필요한 값은 TabLayout의 첫 y시작 값과 actionBarW의 높이 Height인데 이걸 MainActivity에서 계산 했다면 CustomScrollView에서 ViewTreeObserver를 안써도 그냥 사용할 수 있지 않았을까

이렇게 NestedScrollView를 그냥 사용해서 onScrollChange 내부에 로직을 넣었는데 동작을 안했다. 뭐가 다른것일까?


7. TabLayout 속성

android material을 통해서 TabLayout을 구현한다고 했을때 두가지 방법이 있다. 하나는 xml에 여러 item들을 같이 만드는 방법이고 다른 하나는 TabLayout만 만들어두고 MainActivity에서 만드는 방법이다.

첫번째 방법으로 여러 item을 꾸밀 수 있는 줄 알았는데 생각보다 꾸미는 방법을 찾기가 어려웠다. 그래서 두번째 방법으로 그냥 item만 추가해주었다. 이런 느낌의 TabLayout을 만든다고 했을때 한 방법이다.

  • app:tabRippleColor=transparent를 통해서 버튼을 눌렀을때 검정색 물방울이 퍼지는 그런, ripple효과를 없애준다.

  • tabIndicatorColor는 바닥의 분홍색부분으로 현재 어디를 보고있는지 알려주는 Indicator의 색상이다.

  • tabIndicator는 인디케이터의 모양을 설정할 수 있고 drawable에서 테두리가 둥그렇게 만들어주었다. 여기서 Indicator의 가로길이 또한 설정해 줄 수 있다.
    <item>이 한개밖에 없지만 layer-list를 사용하였다. 그냥 shape를 통해 모양을 만들면 size인 width가 적용이 되지 않았기 때문이다...

  • tabINdicatorHeight를 통해서 indicator의 높이를 설정해줄 수 있다.

  • tabSelctedTextColor를 통해서 내가 선택했을때 텍스트의 색을 지정해줄수있다.

  • tabTextAppearance를 통해서 보통 어떤 모습의 텍스트를 쓸건지, 즉 선택되지 않는 텍스트 등의 설정을 할 수 있다. 위와 같은 경우는 아래와 같이 설정되어 있다.


8. ViewPager2 자동 스크롤 느리게 하기(feat. ValueAnimator)

위에서 했던 방법으로 ViewPager2의 요소를 넘기게 되면 금방 스크롤이 되버린다. 참고 사이트의 밑을 보면 자동 스크롤을 느리게 하는 방법으로 ValueAnimator를 정의해줬는데 이에 대한 내용을 적어보고자 한다.

전체 코드는 위와 같은데 ViewPager2의 확장함수로써 구현하고 해당 함수의 동작 과정은 다음과 같다.

  1. 현재 ViewPager2의 아이템과 지정된 아이템 간의 거리를 계산한다.
    이때 item은 다음으로 내가 주는 지정된 아이템이고 currentItem은 확장함수의 기능으로 ViewPager2의 현재 아이템이다.

  2. ValueAnimator를 사용하여 ViewPager2를 일정한 속도로 가속시키면서 스크롤을 시작한다. 비율은 0~1사이의 값으로 진행도를 나타내며 interpolator를 통해 보간을 한 비율을 나타낸다.

  1. ViewPager2를 드래그하는 것과 같은 형식으로 애니메이션을 수행하기 위해 beginFakeDrag()를 호출한다.

  2. 애니메이션이 완료될 때 endFakeDrag()를 호출하여 드래그를 끝낸다.

이 방법은 결국 ValueAnimator를 사용하여 애니메이션을 처리해주는 것이다. ValueAnimator는 시작값과 끝값을 지정하고 그 사이의 값을 쉽게 계산할 수 있도록 도와주는 안드로이드 클래스이다. ValueAnimator의 사용법은 다음과 같다.

  1. ValueAnimator 객체 생성
    먼저 ValueAnimator 객체를 생성한다. 이때, 시작값과 끝값을 지정한다. 이때 시작값과 끝 값은 애니메이션을 적용할 대상의 프로퍼티 값에 해당한다.

  2. 애니메이션 리스너 설정
    ValueAnimator에서 값이 변경될 때마다 호출될 애니메이션 리스너를 설정한다. 아래 코드같은 경우에는 addUpdateListener를 통해서 애니메이션 중에 값이 업데이트 될 때마다 호출된다.valueAnimator.animatedValue는 현재 애니메이션 실행 중인 값이 된다. 이 값을 currentValue 변수에 캐스팅 하여 저장하고, 애니메이션 실행 전과의 차이인 currentPxToDrag 변수를 계산한다. 그리고 'fakeDragBy'메소드를 사용하여 ViewPager2를 가상으로 드래그하고 previousValue를 갱신한다. 이러한 과정을 애니메이션이 끝날 때까지 반복한다.

  1. 애니메이션 속성 설정
    ValueAnimator의 duration, repeateCount, repeatMode등을 설정한다. 나는 시간과 보간방법을 주었다.(duration, interpolator)

  2. 애니메이션 시작
    ValueAnimator를 시작한다.

더 참고할 사항은 속성 애니메이션 개요 - 안드로이드 공식문서에서 찾아볼 수 있다.


profile
배운거 정리하기

0개의 댓글