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

자, 그럼 어떻게하면 뷰에 표시되는 내용을 이미지로 얻을 수 있을까요? - 뷰의 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]
01.<?xml version="1.0" encoding="utf-8"?>
02.<LinearLayout
04.android:orientation="vertical"
05.android:layout_width="fill_parent"
06.android:layout_height="fill_parent"
07.android:gravity="center"
08.android:id="@+id/main_container"
09.>
10.<ImageView
11.android:layout_width="wrap_content"
12.android:layout_height="wrap_content"
13.android:src="@drawable/gingerdroid" />
14. 
15.<TextView
16.android:layout_height="wrap_content"
17.android:layout_width="wrap_content"
18.android:textSize="20sp"
19.android:textStyle="bold"
20.android:text="CAPTURE me if you can :)" />
21. 
22.<Button android:layout_width="wrap_content"
23.android:layout_height="wrap_content"
24.android:id="@+id/main_capture"
25.android:text="Capture" />
26. 
27.</LinearLayout>

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

레이아웃의 모습


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

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

[Main.java]
01.public class Main extends Activity implements OnClickListener{
02.private LinearLayout container;
03.private Button captureButton;
04. 
05.@Override
06.public void onCreate(Bundle savedInstanceState) {
07.super.onCreate(savedInstanceState);
08.setContentView(R.layout.main);
09.container = (LinearLayout)findViewById(R.id.main_container);
10.captureButton = (Button)findViewById(R.id.main_capture);
11.captureButton.setOnClickListener(this);
12.}

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

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

01.@Override
02.public void onClick(View v) {
03.container.buildDrawingCache();
04.Bitmap captureView = container.getDrawingCache();
05.FileOutputStream fos;
06.try {
07.fos = new FileOutputStream(Environment.getExternalStorageDirectory().toString()+"/capture.jpeg");
08.captureView.compress(Bitmap.CompressFormat.JPEG, 100, fos);
09.catch (FileNotFoundException e) {
10.e.printStackTrace();
11.}
12.Toast.makeText(getApplicationContext(), "Captured!", Toast.LENGTH_LONG).show();
13.}

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

뷰가 캡쳐되었습니다.


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