티스토리 뷰

1. 안드로이드 간단 전자 액자 앱 만들기 With 코틀린

이번에 만들어본 안드로이드 어플리케이션은 유저가 선택한 사진을 페이드-인,아웃 효과를 적용시켜 보여주는 기능을 가진 앱입니다. 특징은 아래와 같습니다. 해당 앱을 만들어 보면서 익혔던 내용을 기록하고자 포스팅하였습니다.

특징 및 기능

  • 안드로이드 Permission(권한) 사용 하여 엑티비티에서 외부 저장소에 접근
  • 유저가 스마트폰에 저장된 저장소의 사진을 선택 (SAF, Storage Access Framework)
  • 뷰 에니메이터를 사용해서 페이드-인,아웃 처럼 보이도록 설계

1.1. 개발 시 사용한 기술 및 학습내용

  • Layout 이 가로로 표시 되도록 설정 (사진 액자)
  • Android Permission 사용
  • View Animation 사용
  • Activity Lifecycle 을 이해하고 timer를 적정 시기에 소멸
  • Content Provider
    • SAF (Storage Access Framework) 사용

2. Main Activity 레이아웃 특징

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <LinearLayout
        android:id="@+id/firstRowLinearLayout"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:layout_constraintDimensionRatio="H,3:1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <ImageView
            android:id="@+id/imageView11"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:scaleType="centerCrop" />

        <ImageView
            android:id="@+id/imageView12"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:scaleType="centerCrop" />

        <ImageView
            android:id="@+id/imageView13"
            android:layout_width="0dp"
            android:layout_height="match_parent"
            android:layout_weight="1"
            android:scaleType="centerCrop" />

    </LinearLayout>

    <LinearLayout
        .../>
        <ImageView
            ... />
        <ImageView
            ... />
        <ImageView
            ... />
    </LinearLayout>

    <Button
        ... />

    <Button
       .../>
</androidx.constraintlayout.widget.ConstraintLayout>

 

먼저 메인 엑티비티의 경우 유저가 선택한 사진을 보여줄 수 있도록 이미지 뷰 6개를 가로 2줄로 나타내서 한 줄에 3개씩 오도록 구성하였고 app:layout_constraintDimensionRatio="H,3:1" 속성을 사용하여 가로 사이즈 3 : 세로 사이즈 1 비율로 설정 되도록 해서 사이즈 정렬을 시켰습니다.

이때 각각 ImageView 속성인 android:layout_weight="1" 을 적용 시켜 동일한 가중치가 되도록 해서 한 줄 당 3개의 이미지뷰가 알맞게 적용 되도돌 설정할 수 있습니다.

버튼의 경우는 사진을 추가하는 기능을 가진 버튼 한 개와, 전자 액자를 실행하는 버튼 한 개로 구성되어있고 사진을 추가하는 버튼을 누를 시 SAF를 사용하여 유저에게 친숙한 탐색을 제공하도록 하였습니다.

2.1. 안드로이드 저장소 접근 권한 획득하기

해당 어플은 유저의 스마트폰에 저장된 이미지를 사용해서 보여줄 것이기 때문에 저장소 접근 권한이 필요하며 이를 위해서 기본적으로 manifest 세 사용할 권한을 설정해주어야 합니다.

논외로 예전에는 플레이스토어에서 앱 설치 시 접근 권한을 수락하는 과정을 거쳤지만, 최근에는 해당 권한을 사용하는 시점에 요구해서 유저가 동의하도록 변경되었습니다. (나중에 또 변경 될 수 있지만 참고로 알고있으면 좋으니까)

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> 위 코드를 메니페스트에 추가해서 외부 저장소 읽기 권한을 사용하겠다고 명시합니다.

2.2. Android Permission 사용하기

유저가 사진 추가하기를 할 경우 먼저 저장소 접근에 대한 권한이 필요합니다. 이를 위해서 코드에 아래와 같이 추가합니다.

사진 추가 버튼을 누를 경우 when문을 사용해서 순차적으로 검사하는데 권한이 수락된 상태라면 사진을 선택하는 기능을 실행하게 되고 거절되었다면 교육용 팝업을 띄워 왜 이러한 권한이 필요한지를 유저한테 설명하고 권한을 다시 요청하게 됩니다.

ContextCompat.checkSelfPermission 으로 우리가 사용할 권한이 주어졌는 지 확인하고 요청할 권한을 배열로 담아서 requestPermissions 로 요청 후 거절 당했다면 shouldShowRequestPermissionRationale 를 사용해 도움말을 띄웁니다.

교육용 팝업을 띄워야 하는 경우는 showPermissionContextPopup() 함수를 타서 경고메세지를 보여주고 사용자가 동의할 경우에는 다시 한번 권한 요청을 하는 창을 보여주게 됩니다. 이때 requestCode를 1000 으로 설정한 것을 기억해두시기 바랍니다.

권한이 수락 혹은 거절 되면 onRequestPermissionsResult 콜백 함수가 호출되게 되며 우리가 이를 오버라이드(재정의) 하여 권한을 수락, 획득한 경우 사진을 탐색하는 기능이 수행되도록 하였고 거부 시에는 간단한 토스트 메세지를 띄워주도록 하였습니다.

시뮬레이션 해보면 최초에는 else문을 타서 권한을 요청하게 되겠고 이를 만약 거절한다면 교육용 팝업이 필요하므로이를 띄우고 최종적으로 권한 취득이 완료됬다면 우리가 원하는 사진 탐색 기능을 실행하면되는 것 입니다. 추가 지식이 필요하다면 안드로이드 공식문서 안드로이드 권한을 참고하시기 바랍니다.

2.2.1. SAF(Storage Access Framework) 사용

다음은 SAF 기능을 사용해서 사진 uri를 받아오는 navigatePhotos() 부분입니다. 단 3줄만 작성하면 유저가 사진을 선택할 수 있도록 유도할 수 있습니다. SAF를 사용하므로써 개발 시간을 단축시킬 수 있겠네요. (사진 탐색기 따로 안만들어줘도 되므로)

    private fun navigatePhotos() {
        val intent = Intent(Intent.ACTION_GET_CONTENT)
        intent.type = "image/*" // 모든 이미지 타입들만 설정 (필터링)
        startActivityForResult(intent, 2000) // 선택된 컨텐츠를 콜백을 통해 받아오려고 (onActivityResult)
    }

Intent.ACTION_GET_CONTENT 으로 SAF 기능을 실행시켜서 컨텐츠를 가져오는 (안드로이느 내장)엑티비티를 실행 시키고  타입을 모든 이미지 타입들로 설정해서 모든 지원하는 이미지 확장자를 가져오도록 설정합니다.

그런다음 startActivityForResult(intent, 2000) 으로 우리가 가져온 결과를 2000이라는 requestCode로 설정해서 따로 처리를 해주도록 합니다.

2.2.2. onActivityResult

onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) 해당 함수를 오버라이드 하여 사용자가 사진을 선택한 경우 안한 경우 그냥 켄슬한 경우를 모두 처리해주도록 합니다.

이때 onActivityResult를 사용한 이유가 바로 SAF를 통해 사용자가 내놓은 결과(사진선택 등)를 그대로 받아서 처리해주기 위해서인 것입니다. (설정해둔 requestCode 2000을 사용)

현재 앱에서는 선택가능한 이미지를 최대 6개로 제한했기 때문에 이미지를 6개 초과 선택하려는 경우 토스트 메세지를 띄우고 아닌 경우에만 전역으로 선언해둔 image uri 리스트에 추가하도록 했습니다.

이때 ImageView도 마찬가지로 결과로 받은 이미지 Uri로 이미지를 설정해주면 되겠습니다.

3. 사진 액자 엑티비티

다음은 사진 액자 엑티비티로 이미지 뷰 2개를 사용해서 알파값을 주어 투명도를 조정해서 fade in, fade out 처럼 보여지도록 했습니다. 이때 먼저 선언된 이미지뷰가 아래로 가고 나중에 선언한 이미지뷰가 위로 올라가게 됩니다.

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/backgroundPhotoImageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/black"
        android:scaleType="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ImageView
        android:id="@+id/photoImageView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="@color/black"
        android:scaleType="center"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

먼저 메인 엑티비티에서 전자액자 실행하기 버튼의 클릭 리스너 함수를 설정해주도록 합니다. 이때 인텐트를 통해서 현재 이미지 uri 리스트에 있는 이미지 uri를 스트링 형으로 변환해서 담아주도록 하고, 총 사이즈도 추가로 담아줍니다.

3.1. 인텐트에서 ImageUri 꺼내오기

다음으로 PhotoFrameActivity에서 우리가 전달했던 인텐트에서 사진 uri를 꺼내주는 작업입니다. 변경 가능한 Uri 리스트를 하나 전역으로 선언해주고 onCreate가 될 때 이 리스트를 채워주도록 합니다.

넘겨주었던 사진 uri 사이즈로 부터 for문을 돌려서 각각 photo[인덱스]로 지정했던 stringExtra를 가져옵니다. 이때 photolist에 넣어주기 전에 Uri로 파싱해주는 것을 잊지 마시기 바랍니다. 그러면 uri 불러오기가 완료됩니다.

4. 안드로이드 Activity 수명 주기를 활용한 timer 적용

안드로이드 엑티비티의 수명 주기는 아래와 같은데요? 이를 활용하면 어플이 종료, 중단 되거나 다시 재개될 때 적재적소에 알맞은 기능을 넣어줄 수 있습니다.

먼저 엑티비티가 실행되면 onCreate(), onStart(), onResume() 을 거쳐서 돌게되며 사용자가 다른 앱들 키거나 홈으로 갔을 때는 onPause()가 되어 일시정지가되고 화면에서 안보이면 onStop() 그리고 이후에 다시 켜면 onResume()이 되면서 다시 앱이 돌게 되지요.

하지만 게임같은 큰 앱을 도중에 열거나, 메모리 우선순위가 떨어지면 App process killed가 되고 그럼 앱 재실행 시에는 onCreate() 부터 다시 시작되게 됩니다.

그러나 App process가 죽지 않았다면 onRestart()로 onStart() 부터 앱이 실행되게됩니다. 이 같은 내용을 염두에 두고 사진 액자 엑티비티에도 적용(?)해주도록 하겠습니다.

4.1. Timer, animate 사용한 페이드인 페이드아웃 효과 주기

startTimer 가 하는 기능은 간단합니다. 5초마다 작동하는 timer를 하나 생성하고 이를 외부 스레드가 아닌 메인 스레드에서 사용하기 위해 runOnUiThread 를 람다 호출합니다. 메인에 있는 이미지뷰를 사용해야하기 때문이죠.

그런 다음 내부에서는 현재 가지고 있는 이미지 리스트 사이즈 만큼 반복되면서 current, next 로 증가시키며 이미지를 보여주게 됩니다. 이때 이미지 뷰의 animate()를 사용하여 알파 값을 0에서 1.0f로 1초 동안 전환되도록 해서 애니메이션을 실행하면 페이드인, 아웃 하는 것 처럼 보여지게 할 수 있는 것이죠.

4.2. 생명주기에 따른 timer cancel

그런데 위 타이머를 onCreate에서 실행한다면 다른 앱으로 전환할 때도 이 타이머가 꺼지지 않고 잔존하게 되고 이는 오류로 이는 앱이 터지는 치명적 현상으로 이어지게 됩니다.

그렇기 때문에 생명주기에 따라서 onStart 에서 타이머를 켜주고, onStop 및 onDestroy 에서 타이머가 완전히 종료 되도록 설정 해주어 이를 막아 주도록 합니다.

4.2.1. 액자 화면 가로로 보여주기

액자 액티비티의 화면을 무조건 가로로 보여주게 하기 위해서 android:screenOrientation 속성으로 "landscape" 을 주어서 설정하도록 했습니다. 그러면 액자 실행시 무조건 가로로 보여주게 됩니다.

그리고 액션바도 제거를 해주고 싶다면 NoActionBar 를 테마 xml 에 적용시켜 주시면 액션바도 나오지 않으므로 좀 더 깔끔한 UI를 만들 수 있습니다.

해당 프로젝트의 모든 코드는 저의 깃허브 저장소에서 확인하실 수 있습니다.

댓글
최근에 올라온 글
최근에 달린 댓글
네이버 이웃추가
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함