월별 글 목록: 2014년 11월월

이클립스(4.4)에서 안드로이드 v7 Material Design 위젯을 사용해 보자.

안드로이드 롤리팝 버전은 기본 UI로 머터리얼 디자인(Material Design)이라는 멋진 디자인을 사용한다. 그리고 하위 버전도 이 UI를 적용한 위젯을 사용할 수 있도록 외부 라이브러리의 형태로 v7 라이브러리를 배포하고 있다. 하위 버전에서 머터리얼 디자인을 적용하는 많은 예제가 안드로이드 스튜디오(Android Studio)에서 Gradle 스크립트를 사용하도록 가이드를 하고 있다. 하지만 아직도 많은 개발자들의 안드로이드 개발에 이클립스(Eclipse)가 사용되고 있어서, 이클립스에서 머터리얼 디자인을 사용하는 방법을 살펴봤다.

v7 라이브러리( Support Library 일부 )에는 다음과 같은 하위 라이브러리 프로젝트들이 존재하고 있다.
+ v7 Support Libraries
– v7 appcompat library
– v7 cardview library
– v7 gridlayout library
– v7 mediarouter library
– v7 palette library
– v7 recyclerview library

이 예제에서는 v7 appcompat library만을 사용하는 예제이다.
이클립스 프로젝트에서 아래의 순서대로 진행을 한다.

1. v7 appcompat library를 추가한다.
v7 appcompat library 추가는 이클립스에서 File -> Import를 사용해서 다음과 같이 추가를 시작한다.

2. v7 appcompat library를 선택하고 추가한다.
위치는 Root Directory에 보는 것과 같은 위치에서 찾을 수 있다.

3. 위에서 추가한 라이브러리를 적용할 프로젝트의 Library로 추가한다.

3.1 선택을 완료하면 다음의 에러가 발생할 수도 있다.

[2014-11-19 09:34:24 – android-support-v7-appcompat] C:\dev\android-sdk\extras\android\support\v7\appcompat\res\values-v21\themes_base.xml:193: error: Error: No resource found that matches the given name: attr ‘android:colorControlNormal’.
[2014-11-19 09:34:24 – android-support-v7-appcompat]
[2014-11-19 09:34:24 – android-support-v7-appcompat] C:\dev\android-sdk\extras\android\support\v7\appcompat\res\values-v21\themes_base.xml:190: error: Error: No resource found that matches the given name: attr ‘android:colorPrimary’.
[2014-11-19 09:34:24 – android-support-v7-appcompat]
[2014-11-19 09:34:24 – android-support-v7-appcompat] C:\dev\android-sdk\extras\android\support\v7\appcompat\res\values-v21\themes_base.xml:191: error: Error: No resource found that matches the given name: attr ‘android:colorPrimaryDark’.

이 경우에는 안드로이드 SDK의 support 라이브러리등의 각종 개발환경을 최신버전으로 업데이트 한다. 만약, 계속 에러 상황인 경우에는 이클립스를 다시 실행시키면 된다.
그리고, android-support-v7-appcompat 라이브러리 프로젝트의 Manifest.xml 파일은 다음과 같다.

따라서 2.1 버전도 지원하는 것을 알 수 있다.

3.2 support v4 라이브러리 일치 문제가 발생할 수 있다.

[2014-11-19 09:56:33 – MaterialExample] Found 2 versions of android-support-v4.jar in the dependency list,
[2014-11-19 09:56:33 – MaterialExample] but not all the versions are identical (check is based on SHA-1 only at this time).
[2014-11-19 09:56:33 – MaterialExample] All versions of the libraries must be the same at this time.
[2014-11-19 09:56:33 – MaterialExample] Versions found are:
[2014-11-19 09:56:33 – MaterialExample] Path: C:\dev\android-sdk\extras\android\support\v7\appcompat\libs\android-support-v4.jar
[2014-11-19 09:56:33 – MaterialExample] Length: 995386
[2014-11-19 09:56:33 – MaterialExample] SHA-1: a13f8fe2c278737e2f0b6fcf00f6b2ae4034aacf
[2014-11-19 09:56:33 – MaterialExample] Path: C:\eclipse 4.4\works\MaterialExample\libs\android-support-v4.jar
[2014-11-19 09:56:33 – MaterialExample] Length: 987314
[2014-11-19 09:56:33 – MaterialExample] SHA-1: 9b6a9a9078af571732159b904ad423b03b7cc786
[2014-11-19 09:56:33 – MaterialExample] Jar mismatch! Fix your dependencies

이 경우에는 android-support-v7-appcompat 프로젝트의 /libs 폴더의 android-support-v4.jar를 프로젝트에 복사를 한다. 이제 다음과 같이 정상 화면을 볼 수 있다.

아래는 첨부파일을 실행한 예제이다.

이제 이클립스에서도 v7 패키지를 추가해서 머터리얼 디자인을 앱을 개발할 수 있다. 하지만 IDE 자체가 가지는 불편함도 있고 하니 안드로이드 스튜디오로 개발하는 것이 좋겠다.

* 예제 프로젝트 : MaterialExample.zip
이 프로젝트의 소스는 커니님의 소스를 약간 수정한 버전입니다.

Android Dialog를 안전하고 간단하게 종료하기..

안드로이드 애플리케이션을 개발하다 보면, Dialog 부류의 위젯을 많이 사용하게 된다. 그리고 이 위젯을 화면에 보여주고 완료하면 dismiss()를 호출해서 종료를 시킨다. 이 과정에서 java.lang.NullPointerException이 발생할 수 있다. 그리고 Activty가 Dialog보다 먼저 종료(finish() 호출되는 등)가 되는 상황에서는 아래의 예외를 발생시킨다. java.lang.IllegalArgumentException: View not attached to window manager.

아래는 API 문서의 구조로, Dialog 부류의 최상위는 DialogInterface라는 것을 알 수 있다.

위 구조에서 dismiss()를 가지고 있는 클래스는 Dialog로, 이 클래스의 구조도는 다음과 같다.

ProgressDialog를 사용하면서 dismiss()를 하는 일반적이 코드는 다음과 같다.

if(progressdialog != null && progressdialog.isShowing())
{
   progressdialog.dismiss();
}

이 코드는 대체로 동작을 하지만, 위에서 언급한 예외가 발생할 수 있기에, 위의 코드를 try ~ catch로 감싸는 코드를 종종 보게 된다. 그래서 dismiss()를 안전하고 verbose하지 않게 처리하기 위해서 아래와 같은 간단한 유틸 클래스를 사용하면 좋다.

public class DialogDismisser {
	public static void dismiss(DialogInterface d) {
		if(d == null)
			return;
		
		try {
			if(d instanceof AlertDialog) {
				if(((AlertDialog) d).isShowing())
					((AlertDialog)d).dismiss();
				
				return;
			}
					
			if(d instanceof ProgressDialog) {
				if(((ProgressDialog) d).isShowing())
					((ProgressDialog)d).dismiss();
				
				return;
			}
			
			if(d instanceof Dialog) {
				if(((Dialog) d).isShowing())
					((Dialog)d).dismiss();
				
				return;
			}
		} catch(Exception e) {
			Log.e("dissmiss error", e);
		}
	}

	public static void dismiss(DialogInterface d1, DialogInterface d2) {
		dismiss(d1);
		dismiss(d2);
	}
}

이 클래스를 사용해서 각종 Dialog의 dismiss()를 안전하게 종료시킬 수 있다. 혹시 cancel()을 사용한다면 이 클래스와 동일한 형태로 작성해서 사용하면 된다.