안드로이드 앱에서 사진을 촬영하는 방법은 크게 두가지가 있다.
가장 간단한 방법은 Intent 를 이용하여 단말에 설치된 카메라 어플을 실행하는 것이고, 다른 방법은 surfaceview 를 이용해 카메라 미리보기 화면을 직접 집어넣어 구현하는 방법이 있다. (카메라 미리보기는 서피스뷰를 사용하므로 서피스뷰 사용 패턴을 그대로 사용한다. 뭔소린지 모르겠다)
<우선 첫번째, 인텐트로 카메라 어플 실행하기>
0. manifest 에 권한을 추가한다. (위험 권한이므로 별도의 메시지를 띄워 권한을 받아야한다. http://one-delay.tistory.com/10?category=766130 참고)
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
1. 간단한 화면 구성 (xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="사진촬영" />
<ImageView
android:id="@+id/imageView"
android:src="@mipmap/ic_launcher"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentBottom="true"/>
</RelativeLayout>
2. 메인 액티비티
public class MainActivity extends AppCompatActivity {
private ImageView imageView;
private File file;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
checkPermission();
File sdcard = Environment.getExternalStorageDirectory();
String imageFileName = "capture.jpg";
file = new File(sdcard, imageFileName);
imageView = findViewById(R.id.imageView);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
capture();
}
});
}
private void capture() {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
// Uri uri = Uri.fromFile(file);
Uri uri = FileProvider.getUriForFile(getBaseContext(), "com.onedelay.chap7.fileprovider", file);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
// resolveActivity 메서드를 이용해 카메라 앱이 있는지 확인 가능 (하지만 이 메서드는 없었다고 한다)
startActivityForResult(intent, 101);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
// 인텐트에 정보가 포함되어 넘어오게 됨.
if(requestCode == 101 && resultCode == Activity.RESULT_OK){
/* 이미지 파일의 용량이 너무 커서 그대로 앱에 띄울 경우
* 메모리 부족으로 비정상 종료될 수 있으므로 크기를 줄여 비트맵으로 로딩한 후 설정 */
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8; // 1/8 로 크기를 줄임
Bitmap bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
imageView.setImageBitmap(bitmap);
}
}
}
+ 추가 : if (intent.resolveActivity(getPackageManager()) != null) 을 통해 카메라 앱이 있는지 검사할 수 있다.
MediaStore.ACTION_IMAGECAPTURE 로 인텐트를 생성하여 시작하면, Manifest 에 카메라 기능이 등록된 앱들을 필터링하여 실행시켜준다. (따라서 저렇게 실행하면 카메라 어플들을 선택하여 실행할 수 있다)
MediaStore.EXTRA_OUTPUT 은 요청된 이미지 또는 비디오를 저장하는 데 사용될 콘텐츠 확인자 Uri 정보로, "output" 이라는 String 상수가 들어있다.
capture 메서드에 주석처리된 Uri 를 가져오는 부분은, Nougat (API 24) 버전부터 정책이 바뀌어 그대로 사용하면 오류가 발생한다. (http://one-delay.tistory.com/39?category=778698 참고)
그리고 비트맵 객체로 변환하여 이미지 사이즈를 줄여주는 부분이 중요하다. 요즘 카메라의 성능이 너무 좋아져서 이미지의 용량이 엄청나게 커졌는데 그대로 안드로이드 앱에 띄울 경우 메모리 부족으로 비정상 종료될 수 있다. (졸업작품 앱을 제작할 때 많이 겪어서 이미지 로드 라이브러리를 사용했었는데, 이렇게 사이즈를 줄일 수 있는줄은 몰랐다. 역시 공부를 많이 해야한다...)
<두번째, surfaceView 로 카메라 미리보기 화면을 구성하여 촬영하기>
0. manifest 에 권한 추가
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
1. surfaceView 클래스 정의
/**
* 카메라 미리보기 화면을 구현하기 위한 것
*/
public class CameraSurfaceView extends SurfaceView implements SurfaceHolder.Callback{
private SurfaceHolder mHolder;
private Camera mCamera = null;
// 필수 생성자
public CameraSurfaceView(Context context) {
super(context);
init(context);
}
// 필수 생성자
public CameraSurfaceView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context);
}
// 초기화를 위한 메서드
private void init(Context context) {
mHolder = getHolder(); // 서피스뷰 내에 있는 SurfaceHolder 라고 하는 객체를 참조할 수 있다.
mHolder.addCallback(this); // holder
}
// 서피스뷰가 메모리에 만들어지는 시점에 호출됨
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
mCamera = Camera.open(); // 카메라 객체를 참조하여 변수에 할당
mCamera.setDisplayOrientation(90); // 이게 없으면 미리보기 화면이 회전되어 나온다.
try {
mCamera.setPreviewDisplay(mHolder); // Camera 객체에 이 서피스뷰를 미리보기로 하도록 설정
} catch (IOException e) {
e.printStackTrace();
}
}
/* 서피스뷰가 크기와 같은 것이 변경되는 시점에 호출
* 화면에 보여지기 전 크기가 결정되는 시점 */
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
// 미리보기 화면에 픽셀로 뿌리기 시작! 렌즈로부터 들어온 영상을 뿌려줌.
mCamera.startPreview();
}
// 없어질 때 호출
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
mCamera.stopPreview(); // 미리보기 중지. 많은 리소스를 사용하기 때문에
// 여러 프로그램에서 동시에 쓸 때 한쪽에서 lock 을 걸어 사용할 수 없는 상태가 될 수 있기 때문에, release 를 꼭 해주어야함
mCamera.release(); // 리소스 해제
mCamera = null;
}
// 서피스뷰에서 사진을 찍도록 하는 메서드
public boolean capture(Camera.PictureCallback callback){
if (mCamera != null){
mCamera.takePicture(null, null, callback);
return true;
} else {
return false;
}
}
}
각 설명은 주석으로 추가했다. (그래서 지저분...ㅠㅠ)
2. 간단한 화면 구성 (xml)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:text="사진촬영" />
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_toEndOf="@+id/button"
android:layout_height="80dp"
android:src="@mipmap/ic_launcher" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentStart="true"
android:layout_below="@+id/imageView">
<com.onedelay.chap7.CameraSurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
</RelativeLayout>
3. 메인 액티비티
private void capture() {
surfaceView.capture(new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = 8;
Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
imageView.setImageBitmap(bitmap);
// 사진을 찍게 되면 미리보기가 중지된다. 다시 미리보기를 시작하려면...
camera.startPreview();
}
});
}
위에 설명한 첫번째 방법의 메인액티비티와 동일한 내용. (추가적으로 surfaceView 만 findViewById 해주면 된다)
사진 촬영 버튼을 눌렀을 경우 capture 메서드가 호출되며, 이 메서드에서는 surfaceView 의 capture 메서드를 호출하도록 했다.
사진이 촬영되면 onPictureTaken 메서드가 자동 실행되며, 다음과 같이 이미지뷰에 띄워주게 된다.
이렇게 직접 카메라 화면을 구현하면 카메라에 대한 모든 기능을 제어할 수 있는데 소리도 안나고, 필터도 바꿀 수 있는 등의 여러가지 기능이 있다.
아무튼, 지난번 졸업작품 제작 시 카메라 화면을 커스텀하고싶었지만 할 줄도 모르고 이런게 있는지도 몰랐어서 그냥 라이브러리를 가져다썼었다. 지금이라도 방법을 알게 되어 너무 좋다 ㅎㅎ
사진 촬영 버튼을 누르면, 버튼 옆에있는 imageView 에 촬영된 사진이 띄워진다.
- 출처 : 부스트코스 강의
- 참고할 사이트
https://developer.android.com/guide/topics/media/camera.html
https://developer.android.com/training/camera/cameradirect.html
'Android > Basic' 카테고리의 다른 글
[Android] 동영상 재생하기 (0) | 2018.08.28 |
---|---|
[Android] 음악 재생하기 (0) | 2018.08.28 |
[Android] 인터넷 연결상태 체크하기 (0) | 2018.07.31 |
[Android] SQLiteOpenHelper (0) | 2018.07.31 |
[Android] 웹에서 Image 다운로드 (0) | 2018.07.09 |