관리 메뉴

코딩 기록 저장소

[안드로이드] 안드로이드 스튜디오 입문 (카메라) 본문

개인 공부/앱

[안드로이드] 안드로이드 스튜디오 입문 (카메라)

KimNang 2023. 4. 11. 18:42

이번시간에는 카메라에 대해 다뤄보겠습니다. 'Empty Activity'로 생성하고 레이아웃으로 가서 화면을 구성해봅시다. 최상위 레이아웃은 'LinearLayout'으로 바꿔주고 'orientation'을 'vertical'로 설정합니다. 그리고 기존에 있던 'TextView'를 지워줍니다. 그럼 빈 화면의 구성은 끝났습니다. 이제 본격적으로 시작해보겠습니다!

activity_main 레이아웃 구성하기

ImageView 배치하기

내부에 'LinearLayout'을 만들어서 레이아웃의 가로와 세로를 'match_parent'로 설정해줍니다. 레이아웃의 무게(weight)는 1로 설정합니다. 이 레이아웃 안에는 'ImageView'를 넣고 id를 설정합니다. 이 ImageView는 나중에 빌드해서 촬영했을 때, 이미지가 이 화면에 뜰것입니다.

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1">

        <ImageView
            android:id="@+id/iv"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>

    </LinearLayout>

 

Button 배치하기

위에서 만든 레이아웃 밑에 하나의 레이아웃을 더 생성해줍니다. 이 레이아웃의 속성으로 'orientation'은 'horizontal'로 설정하고, 'gravity'는 'center'로 설정해줍니다. 여기 레이아웃 안에는 버튼을 넣을겁니다. 이 버튼은 나중에 눌렀을 때 카메라로 이동할 것입니다.

    <LinearLayout
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <Button
            android:id="@+id/btn_capture"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="촬영" />

    </LinearLayout>

 

이렇게하면 레이아웃 구성은 끝났습니다. 구성이 끝난 레이아웃의 코드와 화면 구성 사진입니다.

activity_main.xml 레이아웃 코드 및 디자인 미리보기

 

MainActivity 기능 구현하기

여기는 선언 부분과 '촬영' 버튼을 눌렀을때 동작하는 부분입니다. intent로 기본 카메라를 띄웁니다. intent.putExtra()를 이용하여 화면을 띄우고 startActivityResult.launch()를 이용해 돌아올때 값을 넘겨 받아 이미지 파일을 생성합니다.

코드 보기

더보기
    private static final int REQUEST_IMAGE_CAPTURE = 672;
    private String imageFilePath;
    private Uri photoUri;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

       findViewById(R.id.btn_capture).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
                if( intent.resolveActivity(getPackageManager()) != null) {
                    File photoFile = null;
                    try { // 파일 쓰기를 할때는 항상 try catch 문을 적어야함 !
                        photoFile = createImageFile();
                    } catch (IOException e) {
                    }
                    if(photoFile != null) {
                        photoUri = FileProvider.getUriForFile(getApplicationContext(), getPackageName(),photoFile);
                        intent.putExtra(MediaStore.EXTRA_OUTPUT,photoUri);
                        startActivityResult.launch(intent);
                    }
                }
            }

        });
    }

 

createImageFile()

메소드를 작성안했으면 'photoFile = createImageFile();' 이런식으로 에러가 뜹니다. 아래에 메소드를 생성하여 작성하면 됩니다. timeStampsms 이미지의 이름을 시간단위로 파일이름을 생성하여 중복되지 않게 파일을 저장하는 역할을 합니다.

    private File createImageFile() throws IOException {
        String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
        String imageFileName = "TEST_" + timeStamp + "_";
        File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
        File image = File.createTempFile(
                imageFileName,
                ".jpg",
                storageDir
        );
        imageFilePath = image.getAbsolutePath();
        return image;
    }

 

startActivityResult.launch()

영상에서는 사진의 밑줄 코드가 아닌 아래의 메소드를 사용했습니다. 하지만 제가 했을때 취소선이 있었습니다. 이유는 이 메소드가 Deprecated되었기 때문입니다. 이걸 몰라서 엄청 고생했습니다...ㅠ

startActivityForResult(intent,REQUEST_IMAGE_CAPTURE);

검색을 통해 이것저것 찾아본 결과 해결 방법을 찾았습니다.

위 사진의 밑줄친 코드는 결과를 위한 활동 실행 코드로 위의 코드와 비슷한 역할을 합니다.

onActivityResult에는 콜백 등록을 합니다.

onActivityResult()

코드 보기

더보기
    ActivityResultLauncher<Intent> startActivityResult = registerForActivityResult(
            new ActivityResultContracts.StartActivityForResult(),
            new ActivityResultCallback<ActivityResult>() {
                @Override
                public void onActivityResult(ActivityResult result) {
                    //원하는 기능 작성
                    if (result.getResultCode() == Activity.RESULT_OK) {
                        Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
                        ExifInterface exif = null;

                        try {
                            exif = new ExifInterface(imageFilePath);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                        int exifOrientation;
                        int exifDegree;

                        if (exif != null) {
                            exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
                            exifDegree = exifOrientationToDegress(exifOrientation);
                        } else {
                            exifDegree = 0;
                        }

                        ((ImageView) findViewById(R.id.iv)).setImageBitmap(rotate(bitmap, exifDegree));
                    }
                }
            });

그럼 exiforientationToDegress() 부분과 rotate()부분에서 에러가 뜰것입니다. 두가지 메소드를 작성합니다.

 

exiforientationToDegress() 

이건 카메라에서 사진을 찍을 때 화면을 돌리는 것에 대한 이미지를 회전을 시켜주는 것입니다.

    private int exifOrientationToDegress(int exifOrientation) {
        if(exifOrientation == ExifInterface.ORIENTATION_ROTATE_90){
            return 90;
        } else if (exifOrientation == ExifInterface.ORIENTATION_ROTATE_180){
            return 180;
        } else if ((exifOrientation == ExifInterface.ORIENTATION_ROTATE_270)) {
            return 270;
        }
        return 0;
    }

 

rotate() 
    private Bitmap rotate(Bitmap bitmap, int exifDegree) {
        Matrix matrix = new Matrix();
        matrix.postRotate(exifDegree);
        return Bitmap.createBitmap(bitmap,0,0,bitmap.getWidth(),bitmap.getHeight(),matrix,true);
    }

 

카메라 권한 허용

XML Resource File 생성

먼저 좌측의 Project에서 xml 우클릭 - 'New' - 'XML Resource File'을 선택하여 파일을 하나 만들어줍니다. 만들어지면 'PreferenceScreen'을 지우고 'paths'를 입력합니다. <paths> 태그 내부에 <external-path>태그를 작성하고 속성을 작성합니다. path에는 자신의 패키지 이름을 적어줘야합니다. 전 com.example.practice11이라 저렇게 적었습니다.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path
        name="my_image"
        path="Android/data/com.example.practice11/files/Pictures"/>
</paths>

 

AndroidManifest 작업

카메라 권한을 허용하기 위해 추가적인 작업이 필요합니다. 먼저 좌측의 'Project'탭에서 'app' - 'manifests' - AndroidManifest.xml'에 들어갑니다.  <manifest> 태그 아래에 다음과 같이 작성하시면 됩니다.

    <uses-permission android:name="android.permission.CAMERA"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>

<application> 태그 아래에는 다음과 같이 작성합니다.

        <provider
            android:authorities="com.example.practice11"
            android:name="androidx.core.content.FileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

 

그리고 build.gradle (Module : app) 쪽으로 가서 TedPermission 선언을 해야합니다.  TedPermission이란 안드로이드에서 권한을 관리하는게 번거로운데, TedPermission을 쓰면 아주 편리하게 권한 묻는 창을 만들 수 있다고 합니다.

아래의 링크는 TedPermission라이브러리 개발자님의 깃허브 링크입니다.

https://github.com/ParkSangGwon/TedPermission

 

GitHub - ParkSangGwon/TedPermission: Easy check permission library for Android Marshmallow

Easy check permission library for Android Marshmallow - GitHub - ParkSangGwon/TedPermission: Easy check permission library for Android Marshmallow

github.com

이것 또한 영상과 달라져 계속 에러가 떴습니다. 그래서 여러 글을 찾아본 결과 이렇게 바뀌었다고 합니다. 

implementation 'io.github.ParkSangGwon:tedpermission:2.3.0'

다시 MainActivity.java로 돌아와 권한을 체크할 수 있도록 구현합니다. 권한 설정을 물어보는 코드를 onCreate() 아래쪽에 작성해줬습니다. 작성을 하면 permissionListener부분에 에러가 뜰것입니다. 이것을 구현해야합니다.

 

permissionListener

PermissionListener permissionListener = new PermissionListener() 이렇게 입력하여 엔터를 누르면 아래쪽에 메소드가 뜨게 됩니다. 그 중 위에 있는 메소드는 퍼미션이 허용되었을 때 일어나는 액션을 적어주는 곳이고, 아래는 거부되었을때의 액션을 적어주는 곳입니다. Toast메시지를 넣어줍니다.

 

Manifest 에러

코드를 입력하고 실행하는데 다음과 같은 오류가 떴습니다.

Caused by: java.lang.RuntimeException: Manifest merger failed with multiple errors, see logs

이게 뭔지 몰라서 또 검색을 통해 찾아봤습니다. 이것은 AndroidX로 변경하지 않을 경우 발생하는 에러라고 합니다. 이걸 해결하려면 안드로이드 스튜디오에서 마이그레이션 하여서 변경하거나 AndroidMenifest를 수정하여 회피할 수 있습니다.

 

먼저 안드로이드 스튜디오에서 마이그레이션 해서 변겅하는 방법입니다.

Android Studio -> Refactor Menu -> Migrate to AndroidX

 

두번째로 AndroidMenifest를 수정하여 회피하는 방법입니다.

AndroidMenifest에 들어가 <application> 태그 안에 아래의 코드를 추가합니다.

tools:replace="android:appComponentFactory"
android:appComponentFactory="whateverString"

이걸 적었다면 다른 오류가 뜰것입니다. 이것은 gradle.properties 파일에 들어가 아래의 코드를 추가하면 됩니다.

android.enableJetifier=true

이렇게 하면 작동됩니다.

 

실행 결과

 

참고한 영상은 다음과 같습니다.

 

안드로이드 앱 개발 강의 #11

https://youtu.be/MAB8LEfRIG8