Kotlin으로 네이버웹툰 클론 코딩해보기

게시일:

OverView

자주 사용하는 한 어플리케이션의 기능들을 비슷하게 구현해봄으로써 몰랐던 것들을 알 수 있고, 실제로 사용중인 여러 기능들에 대한 여러 고민을 할 수 있었다. 사실 안드로이드 개발 공부를 하면서 개인 어플을 만들다보면 UI 적인 문제를 직면하고 매번 비슷한 어플이 결과로 만들어진다는 고민을 가지고 있었다. 가장 처음에 클론을 했었던 어플이 인스타그램이었는데 잘 만들어진 어플의 인터널을 분석하면서 고민을 하다보면 모르던 개념들이 등장하게 되고 해결을 하면 스스로 성장할 수 있는 한계치가 늘어나는 것을 느꼈다. 이번 프로젝트도 결국 내가 어떤 것을 정확히 알고 있지 않은가에 대한 많은 고민을 할 수 있었다.

Target

네이버웹툰은 자주 사용하는 어플 중 하나였고, Main 페이지의 UI 부분을 스스로 구현하고 싶은 도전 욕구를 불러 일으킨 어플이었다. 또한 많은 사용자가 사용하는만큼, 배울 점 또한 많다는 것이 나의 의견이다. 사실 개발 또는 해킹을 하다 보면 파싱을 해야 되는 경우가 종종 있는데, 목록과 같은 정보들을 파싱해서 앱의 리사이클러뷰에 전달하는 과정을 해보고 싶은 것도 이유중 하나였다. 물론 graphQL이나 REST로 서버에서부터 정보를 가져올 수도 있지만 웹툰과 관련된 여러 이미지 정보들을 서버에 가져갔을 때 발생할 수 있는 문제도 있을 뿐더러 온전히 앱에서만 고민을 할 수 있다는 장점이 컸던 것 같다.

Duration

약 2주 정도를 소요한 것 같다. 하면서 느낀 것은 로직적인 부분보다는 UI 부분에서 많은 어려움을 겪었다. 확실히 Google Docs를 한 번 정독한 것이 접근하는데 상당히 많은 도움을 주었다. 대부분의 시간은 로직을 구현하기 보다는 UI를 따서 만드는 과정에서 소요된 것 같다.

Progress

대부분의 앱들이 화면에 보여지는 순서를 생각하며 개발을 하였던 것 같다. 물론 어떻게 보면 실질적으로 웹툰의 모든 내용을 보여주지는 않지만 구현하는 방법을 알거나 시간을 들이면 충분히 가능한 부분들은 과감히 제외하였다. 그렇게 많은 부분이 제외되다 보니 Activity의 관점에서는 5개 내외의 소규모 프로젝트가 된 듯하다. 앱을 출시하는 것이 목적이 아니라 잘 만들어진 어플이 어떤식으로 구현되었을까를 내 입장에서 고민을 해보는 것이 목적이었기 때문에 이 부분에 대해서는 인스타그램과 같이 거의 모든 파트를 구현해볼 필요가 없다고 감히 판단하였다.

MainActivity

가장 고민을 많이한 부분이었다. 이 부분에서 로직적인 문제로 고민을 했다기 보단 UI를 따라하는 것이 어려웠다. 아래의 이미지에서 왼쪽은 원본 어플의 MainActivity 부분이고 오른쪽은 따라 만들어본 MimicWebtoon의 MainActivity이다.

original main

메인 화면을 보면 크게 2부분으로 나눌 수 있다고 판단하였다. 첫 파트는 배너를 나누는 부분이고 두 번째 파트는 리스트를 뿌려주는 Grid 부분이었다. Grid 부분의 경우 월,화,수 등의 요일에 따라 표시되는 웹툰의 종류가 다르며 한 줄에는 3개의 웹툰 정보가 들어가게 된다. 그리고 특징으로는 사용자가 손가락으로 Swap을 하면 월->화 또는 화->월로 요일 정보가 변경이 되며 리스트가 바뀐다는 사실이다. 여기서 생각난 것이 Fragment와 거의 비슷한 기능을 수행하는 ViewPager였다. ViewPager로 각각의 요일에 대한 View를 생성한 뒤 Swap Transform 이벤트를 추가해주면 처리를 할 수 있었다.

여기서 예상치 못한 문제가 발생하였는데 Fragment와 달리 ViewPager는 wrap_content에 대한 처리가 자동으로 되지 않았다는 것이다. 요일에 대한 웹툰의 개수는 상이할 수 있기 때문에 ConstraintLayout으로 정의를 할 때 wrap_content또는 0dp의 값을 주고 싶었지만 ViewPager에서는 해당 기능이 잘 동작하지 않았다. 이를 해결하기 위해 많은 시간을 소요하였는데 viewPager의 onMeasure를 오버라이딩하여 Height를 갱신하는 방식을 사용하여 해결할 수 있었다.

UI부분에서 또 하나의 문제점이 있었는데, ViewPager 내부에 RecyclerView를 배치하면 자동으로 Scroll이 가능하지만 실제 어플은 위의 배너와 RecyclerView의 스크롤 이벤트가 동기화 되어 있었다. 이를 위해서 Nested ScrollView를 추가하여 같이 움직일 수 있도록 만들어 주었다.

배너를 나누는 부분도 구현하는데 상당히 까다로웠다. 앱의 toolbar 부분은 다음과 같이 구현되어 있다.

original toolbar

보통 Toolbar의 경우 Coordinate Layout을 활용하여 pin을 하는 Collapsing Layout 기법을 사용하게 되면 scroll이 위로 올라가게 되면 toolbar가 사라지게 된다. 하지만 해당 어플은 그 반대로 사용자가 scroll-down 이벤트를 발생시키면 toolbar와 하단의 광고배너가 노출이 되는 방식이었다. 처리를 위해 Y의 높이를 계산하여 두 view가 보였다가 사라지도록 코드를 수정하였다.

또한 scroll 이벤트가 발생하더라도 요일을 선택하는 bar는 Toolbar아래에 고정이 된다. 이 부분에서도 많은 고민을 하였는데 결국 Rect로 View가 화면에 표시되고 있는지를 비교하여 AppbarLayout에 동일한 모양의 bar의 visibility를 변경하는 방식으로 구현할 수 있었다.

RecyclerView에서 웹툰에 대한 내용은 gsoup을 활용하여 사이트로부터 html 파싱을 하여 필요한 부분을 추출하였고, 이미지의 경우 RecyclerView의 Adapter의 bindHolder 부분을 오버라이딩 하여 glide로 서버로부터 가져오는 방식을 사용하였다.

하단의 bar는 NavigationBottomView를 사용하면 편리하게 구현할 수 있기 때문에 item을 설정하는 방식으로 구현하였고, 기본 이미지 필터를 비활성화함으로써 checked상태와 반대 상태의 이미지를 resource로부터 가져와서 사용할 수 있도록 하였다.

EpisodeActivity

Main에서 GridLayout으로 구현된 RecyclerView의 항목을 클릭하면 이동되는 액티비티이다.

original detail

이 부분에서는 MainActivity로부터 클릭 시 웹툰의 고유번호를 넘겨주어 Episode에 대한 부분을 파싱처리하도록 하였다. 웹 html 소스를 기준으로 페이지별로 표시되는 회차의 한도가 있었기 때문에 1번 회차를 파싱할 때까지 page 번호를 변경하며 가져오는 방식을 사용하였고 미리보기 리스트 부분의 경우 FilteredList를 RecyclerView에 추가함으로써 구현할 수 잇다는 것을 알기 때문에 굳이 적용하지 않았다.

MoreActivity

더보기 부분의 경우 GridLayout을 활용하여 imageButton에 대한 클릭 이벤트를 등록하는 방식으로 구현하였다.

mimic more

해당 부분에서 구현을 한 파트는 공지사항과 설정부분이다.

NoticeActivity

안드로이드 개발을 하면서 WebView를 써본 적이 없기 때문에 한 번 구현해보는 것도 괜찮다는 생각을 하였고 구현을 해보았다. 먼저 원본은 다음과 같다.

original notice

Toolbar의 색상이 검은색으로 바꼈으며 overflow menu에서 클립보드로 복사, 링크 복사, 링크 공유를 선택할 수 있었다. 그리고 아래에는 웹사이트의 정보를 그대로 가져와 보여주기 때문에 INTERNET 퍼미션을 Manifest에 등록한 뒤 loadUrl을 하여 뿌려주면 된다. 이 부분에서 webview에서 링크를 클릭할 경우 브라우저 앱이 실행되는 경우가 있는데 방지를 위해 webViewClient를 webview에 추가해주면 된다. 링크 공유의 경우 암시적 인텐트인 ACTION_SEND를 plain/text로 정의하여 url을 넘겨주는 방식으로 활용하였다.

SettingsActivity

세팅부분의 경우 개인 프로젝트를 하면서 자주 사용하는 SettingPreference를 활용하여 구현하였다.

original setting

Preference 라이브러리를 추가하게 되면 세팅과 같이 사용자에 의해 변경될 수 있는 여러 항목들에 대한 정보를 Preference로 간편하게 관리할 수 있다. 또한 Fragment를 생성하여 PreferenceScreen을 뷰로 뿌려주는 것이 가능하다. 이 액티비티의 내부 설정의 경우 앱의 모방 상태에서는 구현의 의미가 없었기에 형태만 비슷하게 적용하였고 동영상 자동 재생과 같이 클릭을 하여 세부 액티비티에서 설정이 필요한 부분을 추가로 구현하였다.

Result

총 6개의 액티비티가 Manifest에 등록되었고 구현한 기능들에 대해서는 상당히 비슷한 모습을 갖추었다고 생각을 한다. 물론 모든 기능을 구현한 것이 아니기 때문에 얼마나 비슷한가에 대한 질문에 대한 답변을 하기에는 모자라지만 UI 부분에서 정말 많은 고민을 할 수 있는 계기가 되었다(물론 크기와 간격 등과 같은 UI 측면의 완성도는 많이 떨어지긴 하지만..) 그리고 역시 완성도가 높은 어플을 만들기 위해서는 기본 기능들을 얼마나 잘 활용하고 커스터마이징 할 수 있는지가 중요한 것 같다.