태터데스크 관리자

도움말
닫기
적용하기   첫페이지 만들기

태터데스크 메시지

저장하였습니다.

뷰에 표시되는 내용을 캡쳐하려면?

2011.01.24 02:52

애플리케이션을 제작하다 보면 뷰에 표시되는 내용을 캡쳐해야 하는 경우가 종종 있습니다. 가장 간단한 예로는 그림판 애플리케이션에서 사용자가 그린 그림을 이미지 파일로 저장하는 경우가 있겠죠.

자, 그럼 어떻게하면 뷰에 표시되는 내용을 이미지로 얻을 수 있을까요? - 뷰의 Drawing cache를 사용하면 됩니다.

Drawing cache는 뷰에 표시되는 내용을 BItmap 형태로 캐싱한 것으로, 해당 뷰의 XML 속성 중 drawing cache enabled가 true로  설정되어 있거나, 코드에서 View.setDrawingCache() 메서드를 사용하여 Drawing cache의 사용 여부를 설정할 수 있습니다.

Drawing cache를 사용하도록 설정하면 뷰의 내용이 변경(Invalidate) 될 때마다 Drawing cache에 새로운 뷰 정보가 저장됩니다. 따라서 내용이 자주 변경되는 뷰에 Drawing cache를 사용하도록 설정하면 성능 저하를 가져올 수 있습니다. 그렇다면, 성능 저하를 최대한 막으면서 뷰에 표시되는 내용도 캡쳐하고 싶을 땐 어떻게 해야 할까요?

이럴 때는 View.buildDrawingCache()를 사용하면 됩니다. buildDrawingCache()는 뷰의 Drawing cache 활성화 여부와는 상관없이 메서드를 호출할 때 그 순간의 뷰 정보를 가지는 Drawing cache를 생성하며, 이렇게 생성된 뷰의 이미지는 getDrawingCache() 를 사용하여 얻을 수 있습니다.

API
public void View.setDrawingCacheEnabled(boolean enable)

뷰가 업데이트 될 때마다 그 때의 뷰 이미지를 Drawing cache에 저장할지 여부를 결정합니다.

public void View.buildDrawingCache()
뷰 이미지를 Drawing cache에 저장합니다. Drawing cache enabled 속성이 활성화되어 있다면 이 메서드를 호출하지 않아도 자동으로 Drawing cache에 뷰의 이미지가 저장됩니다.

public Bitmap View.getDrawingCache()
Drawing cache에 저장된 뷰의 이미지를 Bitmap 형태로 반환합니다.

예제를 통해 간단히 사용 방법을 알아보겠습니다.

[어플리케이션 정보]

액티비티
  • Main (Main.java)

레이아웃
  • main.xml (Main)

권한 (uses-permission)
  • android.permission.WRITE_EXTERNAL_STORAGE

API Level
  • Android 2.2 (API Level 8)


프로젝트를 생성한 후, 뷰의 내용을 적절히 구성합니다. 저는 진저드로이드(Gingerdroid)의 이미지와 약간은 도발적인(?) 텍스트를 표시하는 뷰를 구성하였습니다.

[main.xml]
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
	xmlns:android="http://schemas.android.com/apk/res/android"
	android:orientation="vertical"
	android:layout_width="fill_parent"
	android:layout_height="fill_parent"
	android:gravity="center"
	android:id="@+id/main_container"
>
	<ImageView
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:src="@drawable/gingerdroid" />
		
	<TextView
		android:layout_height="wrap_content"
		android:layout_width="wrap_content"
		android:textSize="20sp"
		android:textStyle="bold"
		android:text="CAPTURE me if you can :)" />
		
	<Button android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:id="@+id/main_capture"
		android:text="Capture" />
		
</LinearLayout>

구성한 레이아웃의 모습은 다음과 같습니다.

레이아웃의 모습


진저드로이드가 '캡쳐해 볼 테면 해보시지' 라고 도발하고 있군요 '_'... 
단호하게 'Capture' 버튼을 눌러 건방진 모습(?)을 캡쳐해보도록 합시다.

Main 액티비티를 작성하도록 하겠습니다. 레이아웃에서 ImageView와 TextView, Button을 모두 포함하고 있는 LinearLayout을 캡쳐해야 전체 뷰의 모습을 캡쳐할 수 있기에 LinearLayout의 인스턴스가 필요하고, 버튼 이벤트를 처리하기 위해 Button의 인스턴스 및 OnClickListener가 필요합니다. 각각의 인스턴스를 받고, 버튼의 OnClickListener를 등록합니다.

[Main.java]
public class Main extends Activity implements OnClickListener{
    private LinearLayout container;
    private Button captureButton;
    
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        container = (LinearLayout)findViewById(R.id.main_container);
        captureButton = (Button)findViewById(R.id.main_capture);
        captureButton.setOnClickListener(this);
    }

버튼의 OnClickListener에서 뷰의 모습을 이미지로 캡쳐하고, 캡쳐한 이미지를 그림 파일로 저장하는 작업을 구현합니다. 전체 요소를 감싸고 있는 LinearLayout의 drawing cache가 활성화 되어있지 않은 상태이므로 이미지를 얻기 위해 buildDrawingCache()를 먼저 호출한 후 getDrawingCache() 메서드를 호출합니다. 

얻은 뷰의 이미지를 확인하기 위해 예제에서는 이미지를 SD카드 루트 디렉터리에 image.jpeg 파일로 저장하도록 구현하였습니다. SD카드에 데이터를 저장하기 위해 WRITE_EXTERNAL_STORAGE 권한이 필요하니 이를 선언해 주는 것을 잊지 마세요.

@Override
public void onClick(View v) {
	container.buildDrawingCache();
	Bitmap captureView = container.getDrawingCache();
	FileOutputStream fos;
	try {
		fos = new FileOutputStream(Environment.getExternalStorageDirectory().toString()+"/capture.jpeg");
		captureView.compress(Bitmap.CompressFormat.JPEG, 100, fos);
	} catch (FileNotFoundException e) {
		e.printStackTrace();
	}
	Toast.makeText(getApplicationContext(), "Captured!", Toast.LENGTH_LONG).show();
}

예제를 실행한 후, 뷰가 제대로 캡쳐되는지 확인해 볼 차례입니다. 건방진 진저드로이드 아래의 Capture 버튼을 살포시 눌러줍니다. 뷰가 캡쳐되었다는 메시지가 표시됩니다.

뷰가 캡쳐되었습니다.


과연 뷰가 잘 캡쳐되었을까요? 이를 확인하기 위해 SD카드의 루트 디렉터리를 확인해 보겠습니다. 캡쳐된 뷰의 이미지가 잘 저장된 것을 확인할 수 있습니다. 버튼이 클릭되 때 뷰의 상태를 캡쳐했으므로 버튼이 눌린 상태까지 그대로 캡쳐된 것을 확인할 수 있습니다. :)







저작자 표시 비영리 변경 금지
신고

커니 안드로이드 개발 팁 , , , , , , ,

  1. Blog Icon
    헬프미

    안녕하세요 구글링을 하다보니 여기까지 들어왔습니다. ^^

    위에 올리신 내용을 보고 적용을 하는 부분에서 java.lang.NullPointerException 이 발생하여 문의를 드립니다.


    제가 연습하고 있는 것은 화면에 꽉찬 이미지위에 작은 이미지르 올린후 화면캡쳐와 같이 저장을 하는 것입니다.
    SurfaceView를 상속받아 View를 만들어서 그것을 setContentView(new MySurfaceView(this)); 하였습니다.

    그리고
    private View container;

    이렇게 한후 올려주신 소스를 그대로 사용을 했습니다.
    container.buildDrawingCache();
    Bitmap captureView = container.getDrawingCache();
    .....
    Toast.makeText(.........);

    조언 부탁드립니다.

  2. SurfaceView는 위와 같은 방법으로 캡쳐가 안됩니다. 그냥 까만 화면으로 캡쳐가 될텐데, NullPointerException이 발생하는건 다른 부분에서 오류가 있는 듯 하네요. container의 인스턴스는 생성하셨나요?

  3. Blog Icon
    한드로이드

    캡쳐를 계속하면 자동으로 캡쳐파일이 계속 저장 되는 법은 없나요???

  4. 안녕하세요~
    커니님 블로그에서 항상 많은 도움 받고 있는 학생입니다...ㅎ
    다름이 아니고 위와 같은 방법으로 비디오뷰 역시 캡쳐가 가능한지 궁금해서 여쭙니다.
    비디오뷰부분만 따로 linearLayout을 만들어 그안에 videoView만 위치시켜놨습니다.
    그리고 혹시 저장되는 sd카드 내에서의 위치도 바꿀 수 있는지 궁금하네요..
    꼭 답변좀 부탁드립니다~

  5. 비디오뷰는 SurfaceView를 상속받기에 위의 방법으로는 캽쳐가 불가능한것으로 알고있습니다. SurfaceView를 캡쳐하는 방법에 대해 구글링하면 정보가 나오긴 했던 것 같은데.. 너무 오래되어서 잘 기억이 나지는 않네요 ㅠㅠ

    그리고, SD카드에 캡쳐된 파일이 저장되는 경로를 바꾸는 것은 물론 가능합니다. FileOutputSteam에서 파일 경로를 바꿔주면 다른 경로에 저장하는 것이 가능합니다.

  6. 답변감사드립니다~

  7. Blog Icon
    hello~

    잘보고 갑니다~ 도움 만땅 되네요~

  8. Blog Icon
    kimst

    안녕하세요 첨부하신 소스를 갤럭시 탭 10.1에서 동작 시켜 봤는데요

    안드로이드 3.1 에서는 잘 동작을 하는데 3.2로 업그레이드를 하고 난후에는 첨부된 Source가 Portrait에서는 null 값이 넘어 옵니다. 물론 LandScape에서는 잘 동작 하고 있습니다. 혹시 해결책이 있나 해서 여쭤봅니다.

  9. 현재 화면의 캡쳐가 아니라..
    스크룰 밑으로 안보이는 부분까지 전체화면을 캡쳐하는 방법은
    어떻게 해야하는지요? 혹~ 알고 계시면 조언 부탁드립니다.

  10. Blog Icon
    ryanonit

    안녕하세요. 좋은 강좌 자료 정말 감사드립니다.
    저 캡쳐버튼 누른후 파일네임을 다르게 세이브하고 싶을 경우에는 어떻게 해야 하나요? 찍을때마다 자동으로 다른 이름으로 세이브하게 하고싶을 경우요. 그리고 만약 자기가 원하는 이름으로 세이브하고싶을 경우하구요.
    답변주시면 정말 감사하겠습니다. :)

  11. 원하는 이름으로 저장하는 것에는 여러 방법이 있지만, 사용자가 원하는 이름으로 받게끔 한다면 다이얼로그로 파일 이름을 받는 방법이 있겠지요.
    파일 이름을 받은 후 파일 생성한 후, 캡쳐한 이미지 데이터를 파일에 쓰는 것은 구글링하면 금방 나오니 쉽게 하실 수 있을 겁니다~

  12. Blog Icon
    hello

    좋은 강좌 감사합니다!

    혹시, 이 화면 뿐만아니라 홈화면이나 다른 화면들을
    캡쳐할 수 있는 방법이 있을까요?

  13. 홈 화면을 비롯한 다른 화면은 루팅을 하거나, adb를 통해 캡쳐해야 합니다.

  14. Blog Icon
    박병국

    안녕하세요 연습을하다가
    레이아웃을 캡쳐하려고 짠 소스인데요

    layout1.buildDrawingCache();
    Bitmap bitmap = layout1.getDrawingCache();
    arrayBitmap.add(new BitmapDTO(bitmap,"" ));
    layout1.setBackgroundResource(R.drawable.a1 + count++);
    대략 버튼을 누를때마나 변화돼는 화면을 캡쳐하여 리스트에 담아서 나중에 한번에 저장하려고 만든 소스인데요 저장된 파일을 확인해보니 첫번째 화면만 캡쳐되고 나머지는 다같은 화면으로 캡쳐가 되더라구요 혹시 그전 데이터가 날라가지 않아서인지 해결방법이있는지 알고싶습니다.

    맨윗줄에 destroyDrawingCache() 를 해보아도 안되더라구요 ㅜㅜ

  15. http://stackoverflow.com/questions/10793191/deletion-of-drawing-cache
    요걸 한번 참고해보세요.

  16. Blog Icon
    도모

    안녕하세요 스크린 캡쳐 공부를 하다 커니님의 블로그까지 찾아오게 되었네요

    음.. 제가 궁금한건 setDrawingCacheEnabled(boolean) 과 buildDrawingCache() 의 차이점을 정확하게 모르겠네요

    Drawing Cache가 setDrawingCacheEnabled(boolean) 은 자동적으로 계속 쌓여서 자주 업데이트 되는 view경우에는 성능 저하가 올 수 있고

    buildDrawingCache() 는 업데이트 되는 view에 한해서만 Drawing Cache가 생성되었다가 다른 업데이트가 발생하면 전에 있던 Drawing Cache가 사라진다는 말씀이신건지요?

  17. Blog Icon
    aaaaa

    클릭 없이 화면을 키자마자 스크린샷 할수는 없나여?

  18. onResume() 이후 Handler 등을 이용하면 가능할 것 같습니다.

  19. Blog Icon
    bluemarble

    캡쳐후에 저장이아니라 바로 화면에 띄울수있는방법은 없을까요?

  20. Bitmap 형식으로 저장되므로, ImageView 등을 사용하면 바로 표시할 수 있습니다.

  21. Blog Icon
    bluemarble

    imageview를 쓰려면 drawable에 이미지 파일이 있어야하지않나요?

  22. http://developer.android.com/reference/android/widget/ImageView.html#setImageBitmap(android.graphics.Bitmap)

    참고하세요~

  23. 헐이게뭐람...죄송해욬ㅋㅋㅋ..궁금한게잇는데요!! 지금 제가 만든뷰 캡쳐가 아니라 흔히 저희가 홈키랑 잠금키 눌러서 캡쳐하듯이 최상위뷰 캡쳐를 하고싶은데 안드로이드에서 서비스 막아놧다고 들어서요 ㅠㅠ다른 방법 뭐 없을까용..

  24. 알고 계신 대로 보안상 문제로 막혀있고, 공식적인 방법으론 API 21부터 지원되는 MediaProjection(http://developer.android.com/reference/android/media/projection/MediaProjection.html) 을 사용하면 화면 데이터를 받아올 수 있습니다.

  25. 감사합니다 잘보았습니다!

    보안상 SD카드없을경우에

    앱 내부 폴더에 저장하는 방법은 따로 없나요?

    예를들어 가상에뮬레이터로 실행시켰을때
    가상에뮬레이터 갤러리에서 사진을보려고하는데 없네요ㅠㅠㅠㅠ
    이게 다 SD카드가 없어서 생기는 오류가아닌가요?ㅠㅠ

  26. Blog Icon
    파란기린

    지나가다 리동경님의 글에 답변을 달자면 어머 벌써 4달이나... Preference에다가 이미지를 String으로 인코딩해서 저장하는 방법이있습니다.

    http://stackoverflow.com/questions/9768611/encode-and-decode-bitmap-object-in-base64-string-in-android

    http://developer.android.com/reference/android/preference/Preference.html

    그리고 저는 scrollView에서 이미지를 이동한다음에 사진을 저장하는 방법을 사용했는데 찾아보니 많은 자료가 없더군요 그래서 전 레아이웃을 생성시키고 그 뒤에 Scrollview를 생성하여 레이아웃을 캡쳐하는 방법을 사용했는데 혹시 저와 같은것으로 찾고있다면 ㅎㅎ 이런것도 있다는것을 알아두시면 편하실거같네요

  27. Blog Icon

    비밀댓글입니다

  28. Blog Icon
    행인28

    안녕하세요 좋은 글 감사합니다!!

    근데 웹뷰를 잘르면서 저장할 수는 없나요??

    예를들어 뷰의 높이가 1만이면
    3천, 3천, 1천 이렇게 자르고 싶습니다.ㅎ

  29. Blog Icon
    yoon

    저.. 한번만 되고 다음 부터는 이미지가 바껴도 예전이미지가 저장되는데 왜그럴까요?