[원문 주소] http://cafe.naver.com/javacircle/104493

⟪Do it! 안드로이드⟫- 개정 3판으로 그 명성을 이어가다!

서평 이벤트!!!!


안녕하세요. 이지스퍼브리싱 출판사 배호종 팀장입니다.

안드로이드 분야에서 베스트셀러 1위를 해오며 5년 넘게 큰 사랑을 받아온 [Do it! 안드로이드 앱 프로그래밍]이 안드로이드 6.0 버전인 ‘마시멜로’와 ‘안드로이드 스튜디오’ 개발 도구를 사용하여 앱을 만들 수 있도록 개정되었습니다.

----------------------------------------------------------------------------------------------------------------------------------

Do it! 안드로이드 앱 프로그래밍- 개정 3판(마시멜로 / 안드로이드 스튜디오 반영판)



 

● 안드로이드 분야 베스트셀러 1위 저자 정재곤 박사님

발행일: 2016년 01월 21일 | 864쪽 | 정가: 40,000원

http://www.yes24.com/24/goods/23991806?scode=029

이벤트 기간: 1월 23일 ~ 1월 28일(일주일간 진행 후 1월 29일 당첨자 발표)

이벤트 추첨: 10

이벤트 방법: 이벤트 기간 동안 이 책이 필요한 이유를 간략하게 작성하신 후 아래의 자료(안드로이드_전면개정3판_책소개이미지.jpg)를 본인의 블로그나 트위터 등에 게시해 주시면 됩니다. 

이메일도 꼭 남겨주시고요. 

물론 SNS에 게시하지 않으셔도 되지만 게시한 곳이나 게시할 곳 등을 함께 댓글로 남겨주시면 당첨 확률이 높아지겠죠. ^^


 

◉ 당첨되신 분들은 책을 배송 받은 후 교보문고, Yes24, 반디, 영풍문고 중에서 2곳에 서평을 남겨주세요. ^^

----------------------------------------------------------------------------------------------------------------------------------


[간단한 책 소개]

안드로이드 분야 1위 도서!

전면 개정 3판(마시멜로 · 안드로이드 스튜디오 반영판)으로 그 명성을 이어간다!

안드로이드 분야에서 큰 사랑을 받아온 《Do it! 안드로이드 앱 프로그래밍》의 세 번째 전면 개정판이 나왔다. 이번 개정 3판에서는 최신 버전인 마시멜로에 맞춰 소스 코드 테스트를 완료했다. 또한 변경된 프래그먼트 사용 방법과 페이스북 연동 방법에 대한 내용을 보강하였으며, 특히 ‘안드로이드 스튜디오’ 사용법에 대한 상세한 해설을 새롭게 추가하여 초보자의 이해를 돕도록 배려했다.

이 책은 최초의 안드로이드 단말 출시 전부터 T아카데미에서 안드로이드 교육 과정을 설계한 명강사, 정재곤 박사가 직접 집필하였다. 안드로이드 분야 최고 전문가이자 현업 프로그래머로서 국내외에서 안드로이드 앱 개발을 설계 및 가이드하고 있는 저자의 명쾌한 설명을 만날 수 있다. 저자가 직접 강의한 무료 강의 동영상(업데이트 중)을 인터넷에 공개하여, 책을 사면 전문 학원에 등록한 것과 비슷한 효과를 볼 수 있다. 초보자용 50일 완성, 중급자용 25일 완성 진도표도 제시되어 있어 효율적 학습이 가능하다.



[개정 3판의 주요 변화]

(1) 첫째 마당

● 개발 환경 구축하기에서 자바의 설치와 안드로이드 스튜디오 설치하기

● 안드로이드 스튜디오를 사용하여 만드는 첫 번째 애플리케이션

● 새로운 프로젝트를 만들 때 다른 화면 종류 선택해보기


(2) 둘째 마당

● 안드로이드 스튜디오 살펴보기

: 새로운 프로젝트를 만드는 과정/ 프로젝트 창의 구성과 기능/ 코드 편집기 살펴보기/ 디자이너 도구 살펴보기 등이 추가로 보강되었습니다.

● 앱을 실행했을 때 권한 부여

: 마시멜로 버전부터 권한(Permission)을 부여하는 방식이 약간 바뀌었습니다. 마시멜로 버전부터는 권한을 일반 권한(Normal Permission)과 위험 권한(Dangerous Permission)으로 나누었으며, 위험 권한의 경우에는 앱이 실행될 때 사용자에게 권한을 부여할 것인지 물어보도록 변경되었습니다.

● 프래그먼트

: 프래그먼트에 대한 이해/ 프래그먼트를 화면에 추가하는 방법 이해하기/ 프래그먼트 만들어 화면에 추가하기 등의 내용이 보강되었습니다.

● 마시멜로에서 화면에 탭을 추가하여 보여주는 방법에 대한 내용이 보강되었습니다.

● 페이스북 연동하는 방법에 대한 내용이 보강되었습니다.


(3) 전체

● 마시멜로/안드로이드 스튜디오 기준으로 전체 프로젝트 소스 및 이에 대한 설명 부분을 업데이트했습니다.



[상세 이미지] 첨부: 안드로이드_전면개정3판_책소개이미지.jpg

 

[저자 소개]

저자 정재곤

e-메일 : mike.jung.global@gmail.com

내외 개발 현장과 강의장을 종횡무진 하는 국내 최고 안드로이드 전문가. 정재곤 박사는 안드로이드 강사들을 가르치는 강사로도 유명하며, 최초 안드로이드 단말 출시 시점부터 T아카데미의 안드로이드 교육과정을 설계한 바로 그 사람이다.

현 (주)유엔에스네트웍스 CTO, 모바일 전문 컨설팅

서울대학교 대학원 박사, GIS 전공

서울디지털대학교 컴퓨터공학과 초빙교수

SK텔레콤 T아카데미 안드로이드 전문 강사

SK텔레콤 T아카데미 안드로이드 프로그래밍 교재 집필

국내/해외 모바일 소프트웨어 컨설팅/설계/개발 경력 10년 이상

모바일 임베디드 데이터베이스, 모바일 서버, 모바일 기기용 자바 VM 설계/개발 등 다수

<자바+안드로이드를 다루는 기술> 집필


[개정 3판 목차]

첫째 마당 | Hello! 안드로이드

01 안드로이드란?

안드로이드에 대한 이해

안드로이드의 특징

안드로이드의 흐름

아이폰! 아이폰! 안드로이드!

안드로이드의 현재

안드로이드의 빠른 진화 과정

02 개발 환경 구축하기

개발 환경 구축을 위한 프로그램

오라클 사이트에서 자바 설치하기

안드로이드 스튜디오 설치하기

03 첫 번째 애플리케이션

첫 프로젝트 만들기

에뮬레이터 만들기

하나씩 바꾸어 보기

XML 레이아웃 파일의 버튼에 onClick 속성값 넣기

자바 파일에 이벤트 처리 메소드 추가하기

여러 개의 버튼에 기능 추가하기

새로운 화면 만들어 띄워주기

새로운 프로젝트를 만들 때 다른 화면 종류 선택해보기

04 안드로이드 프로젝트와 개발 도구

안드로이드 최근 버전의 주요 특징

안드로이드 2.3(진저브레드)의 변화

안드로이드 3.0(허니콤)의 변화

안드로이드 4.0(아이스크림 샌드위치)의 변화

안드로이드 4.2부터 4.3(젤리빈)까지의 변화

안드로이드 4.4(킷캣)의 변화

안드로이드 5.0(롤리팝)의 변화

안드로이드 6.0(마시멜로)의 변화

안드로이드 프로젝트

안드로이드 SDK의 개발 도구

05 안드로이드 앱의 유통과 판매

안드로이드폰의 장터

애플리케이션 판매를 위한 개발자 등록하기

서명된 설치 패키지 만들기

플레이 스토어에 등록하기

유료 판매를 위한 계좌 등록하기

06 실제 단말에 연결하기

PC에 드라이버 설치하기

단말 연결하고 설정 바꾸기


둘째 마당 | 안드로이드 완전 정복

01 기본 위젯과 레이아웃

안드로이드 스튜디오 살펴보기

퀵스타트 화면과 새로운 프로젝트를 만드는 과정

프로젝트 창의 구성과 기능

코드 편집기 살펴보기

디자이너 도구 살펴보기

뷰와 뷰그룹

layout_width, layout_height

background

레이아웃

리니어 레이아웃

방향 설정하기

자바 코드에서 화면 구성하기

정렬 방향 설정하기

여유 공간 설정하기

공간가중치 설정하기

상대 레이아웃

테이블 레이아웃

스크롤뷰

프레임 레이아웃과 뷰의 전환

기본 위젯들

텍스트뷰

버튼

입력상자

이미지뷰

텍스트뷰와 입력상자의 다른 기능들

02 애플리케이션 구성하기

레이아웃 인플레이션

화면 구성과 화면 간 이동

인텐트와 데이터 전달

범주

타입

컴포넌트

부가 데이터

수명주기

서비스

브로드캐스트 수신자

앱을 실행했을 때 권한 부여.

리소스와 매니페스트

매니페스트

리소스의 사용

스타일과 테마

토스트와 대화상자

프래그먼트

프래그먼트에 대해 이해하기

프래그먼트를 화면에 추가하는 방법 이해하기

프래그먼트 만들어 화면에 추가하기

버튼 클릭했을 때 코드에서 프래그먼트 추가하기

프래그먼트 수명주기

두 개의 프래그먼트로 구성된 이미지 뷰어 만들기

03 다양한 위젯과 이벤트 활용하기

이벤트 처리

웹브라우저 사용하기

간단한 애니메이션 사용하기

페이지 슬라이딩 사용하기

뷰플리퍼 사용하기

프로그레스바 사용하기

프로그레스바

시크바

메뉴와 탭 사용하기

화면에 메뉴 기능 넣기

액션바 좀 더 살펴보기

탭으로 보여주기

키패드 설정하기

04 선택 위젯의 사용과 커스텀뷰 만들기

나인패치 이미지

[비트맵] 버튼 만들기

리스트뷰 사용하기

아이템을 위한 XML 레이아웃 정의하기

스피너 사용하기

갤러리 사용하기

그리드뷰 사용하기

복합 위젯 만들기

월별 캘린더 만들기

멀티터치 이미지 뷰어 만들기

05 그래픽

빨간색 사각형 그리기

그래픽 그리기

그리기 객체로 만들어 그리기

비트맵 이미지 사용하기

페인트 보드 만들기

서피스뷰 사용하기

그래픽을 위한 카메라 객체 사용하기

06 스레드와 애니메이션

핸들러 사용하기

자바의 스레드 사용하기

메시지 전송하여 실행하기

Runnable 객체 실행하기

일정 시간 후에 실행하기

스레드로 메시지 전송하기

AsyncTask 사용하기

스레드로 애니메이션 만들기

트윈 애니메이션

위치 이동 액션

회전 액션

스케일 액션

투명도 액션

인터폴레이터.

그래프 애니메이션 만들기

07 네트워킹

네트워킹이란?

소켓 사용하기

웹으로 요청하기

뉴스 정보 가져오기

08 데이터베이스

모바일 데이터베이스

데이터베이스와 테이블 만들기

헬퍼 클래스를 이용해 업그레이드 지원하기

데이터 조회하기

SQL을 메소드 호출로 실행하기

커서 어댑터로 뷰에 보여주기

약품정보 데이터베이스 구성하기

09 멀티미디어

오디오 재생하기

동영상 재생하기

오디오 녹음하여 저장하기

동영상 녹화하기

카메라로 사진 찍어 저장하기

바코드 스캐너 만들기

10 위치기반 서비스

GPS를 이용해 나의 위치 확인하기

에뮬레이터로 가상 위치정보 전송하기

현재 위치의 지도 보여주기

Google Play Service 모듈 설치 여부 확인하기

프로젝트를 만들고 레이아웃에 프래그먼트 추가하기

액티비티 정의하기

매니페스트에 정보 등록하기

구글맵 API 키 발급받기

지도에 아이콘 추가하기

오버레이란?

내 현재 위치 표시를 위한 오버레이 추가하기

은행 위치를 표시하는 오버레이 추가하기

지도 위에 나침반 표시하기

근접 경보 기능 추가하기

인텐트의 액션 정보 정의

인텐트와 펜딩인텐트를 이용한 목표지점 추가

브로드캐스트 수신자의 정의와 등록

주소를 이용하여 위치 알아내기

11 메시징과 소셜 네트워크 서비스

메시징 서비스 이해하기

푸시 서비스 사용하기

푸시 메시지란

GCM을 위한 애플리케이션 등록하기

GCM을 위한 앱 만들기

트위터 연동하기

트위터 글보고 글쓰기

페이스북 연동하기

12 근거리 통신과 센서

근거리 통신 이해하기

NFC 사용하기

NFC 태그 읽기와 쓰기

블루투스 사용하기

블루투스 장치 켜기

다른 블루투스 디바이스 검색하기

다른 블루투스 디바이스 연결하기

데이터 주고받기

센서 이해하기

13 홈 화면

앱위젯 만들기

라이브 배경화면 만들기


셋째 마당 | 구글 플레이 스토어에 올리는 안드로이드 애플리케이션

01 멀티메모 앱

멀티메모의 개요

1단계 - 메인 화면 구성하기

2단계 - 데이터베이스와 메모 입력화면 구성 및 사진 기능 추가하기

데이터베이스 만들기

메모 입력화면 구성하기

사진 기능 추가하기

메모 저장하기

3단계 - 손글씨 기능 추가하기

4단계 - 음성과 동영상 기능 추가하기


온라인 특별 부록 - 퀵나비

상용 앱 소스 전체 공개

최단 경로 내비게이션

•이 앱은 구글 플레이 스토어에서 검색하여 다운 및 설치가 가능합니다.

•관련 소스는 www.easyspub.co.kr과 www.android-town.org에 공개되어 있습니다.


Do it! 안드로이드 앱 프로그래밍

정재곤

이지스퍼블리싱 2016.01.21

 

  기본 기능을 구현한 뒤 app market에 올렸다가 debugging phone에서는 막힘없이 되던 부분에서 app이 멎어버리는 bug를 접했다. logcat을 보니 library에서 class를 읽어들이는데 문제가 있는 모양인데 분명히 gradle에도 해당 .jar 파일들을 추가했고 이미 정상적으로 동작하는 폰도 있는데 왜 bug가 잡힌 것일까? bug가 잡힌 phone은 API 19고 되던 폰은 API 21이긴 한데 말이다.

E/dalvikvm: Could not find class 'org.apache.poi.ss.util.CellRangeAddress', referenced from method org.apache.poi.hssf.usermodel.HSSFWorkbook.setRepeatingRowsAndColumns
W/dalvikvm: VFY: unable to resolve new-instance 7124 (Lorg/apache/poi/ss/util/CellRangeAddress;) in Lorg/apache/poi/hssf/usermodel/HSSFWorkbook;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0009
W/dalvikvm: Unable to resolve superclass of Lorg/apache/poi/ss/util/CellRangeAddress; (7125)
W/dalvikvm: Link of class 'Lorg/apache/poi/ss/util/CellRangeAddress;' failed
E/dalvikvm: Could not find class 'org.apache.poi.ss.util.CellRangeAddress', referenced from method org.apache.poi.hssf.usermodel.HSSFWorkbook.setRepeatingRowsAndColumns
W/dalvikvm: VFY: unable to resolve new-instance 7124 (Lorg/apache/poi/ss/util/CellRangeAddress;) in Lorg/apache/poi/hssf/usermodel/HSSFWorkbook;
D/dalvikvm: VFY: replacing opcode 0x22 at 0x0010

 

  나와 같은 error를 먼저 만나본 사람이 어디없나 검색해 보니 다행히 있긴 있었다. MultiDex 기능이 Lollipop 이전에서는 제대로 동작하지 않을 수도 있기 때문에 아래 code를 class 내에 선언하여야 해당 library의 method들을 활용할 수 있다는 것이다.

@Override
protected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    MultiDex.install(this);
}
[출처] StackOverflow - Android Studio E/dalvikvm﹕ Could not find class '.DatabaseHelper', referenced from method .DatabaseManager
                                  (http://stackoverflow.com/questions/32697460/android-studio-e-dalvikvm-could-not-find-class-databasehelper-referenced-fr

  결과는 성공이었다. 문제를 해결하게 되서 기쁘긴한데 이 해결책을 알아낸 사람들은 도대체 어떻게 찾은건지 ㅎㅎ. 역시 무작정 code만 짜기 보다는 전체적인 동작 원리와 구조를 파악할 줄 아는 것이 진정한 힘인 것 같다.

  아직 완벽하진 않지만 대강 필요한 기능을 구현한 후 내 앱에 Admob Banner를 배치하고자 했다. 처음에는 R symbol을 못찾아 build 자체가 안되어 그 해결책을 찾아보니 xmlns:ads 항목을 다음과 같이 xml layout에 기록하면 된다고 하여 해봤다. 그리고 일단은 build가 되었다. 그런데...

<com.google.android.gms.ads.AdView
    xmlns:ads=http://schemas.android.com/apk/lib/com.google.ads <!-- 이로서 R symbol을 못 찾는 error는 해결하긴 했지만 adSize was missing error가 발생하였다. -->
    android:id="@+id/adViewAdMob03"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:layout_centerHorizontal="true"
    android:layout_alignParentBottom="true"
    ads:adSize="BANNER"
    ads:adUnitId="@string/banner_ad_unit_id_imageview">
</com.google.android.gms.ads.AdView>

  위 사진과 같이 'Required XML attribute "adSize" was missing' error가 발생하면서 banner가 보여야 할 부분이 저렇게 까맣게 처리되어 출력되는 게 아닌가? 다시 구글링을 해보니 중간의 '~/lib/~'를 '~/libs/~'로 고쳐보라고 하는 글이 많아 그대로 해 봤지만 이 역시 동작하지 않았다. 이제 어쩌나 싶었는데, stackOverflow에 한 답변이 눈에 띄었다.

최신 Admob SDK는 다른 namespace를 사용합니다. 
xmlns:ads="http://schemas.android.com/apk/res-auto

[출처] StackOverflow - AdMob in android “AdView missing required XML attribute 'adSize' ”
          (http://stackoverflow.com/questions/7185335/admob-in-android-adview-missing-required-xml-attribute-adsize)

  아무래도 SDK가 update 된 모양이다. 이대로 해보니 이젠 된다. 개발을 하면 할수록 느끼는 거지만 변화를 쫓는데 힘들어하는 사람이라면 (다른 직군도 그렇겠지만 유독 더) 어려운 일이 개발이지 않을까 싶다. 그나저나 새로나온 Gradle 서적 서평도 기록해야 하는데 정초부터 집안에 일이 많아 아직 1/3밖에 읽지 못했다. 지금까지 읽은 내용만 봐도 정리가 잘 되어 좋은 책이다 싶긴 한데 이벤트로 당첨되어 받은 책이라 빨리 끝까지 읽고 서평을 써야겠다.

 

[출처] Stack Overflow - http://stackoverflow.com/questions/4212861/what-is-a-correct-mime-type-for-docx-pptx-etc

  자세히 Intent에 대하여 정리하고 싶지만, 지금은 시간이 없으므로 일단 파일 확장자에 따라 맞는 타 앱을 Intent로 부를 때 필요한 MIME type을 기록해 두려 한다. 기본적으로 Intent intent = new Intent(Intent.ACTION_VIEW); 와 같이 선언한 뒤, intent.setDataAndType("file URI", "아래 MIME Type String"); 로 지정하여 연결한다.

Extension MIME Type
.doc     application/msword
.dot      application/msword

.docx    application/vnd.openxmlformats-officedocument.wordprocessingml.document
.dotx     application/vnd.openxmlformats-officedocument.wordprocessingml.template
.docm   application/vnd.ms-word.document.macroEnabled.12
.dotm    application/vnd.ms-word.template.macroEnabled.12

.xls      application/vnd.ms-excel
.xlt       application/vnd.ms-excel
.xla      application/vnd.ms-excel

.xlsx     application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
.xltx      application/vnd.openxmlformats-officedocument.spreadsheetml.template
.xlsm    application/vnd.ms-excel.sheet.macroEnabled.12
.xltm     application/vnd.ms-excel.template.macroEnabled.12
.xlam    application/vnd.ms-excel.addin.macroEnabled.12
.xlsb     application/vnd.ms-excel.sheet.binary.macroEnabled.12

.ppt      application/vnd.ms-powerpoint
.pot      application/vnd.ms-powerpoint
.pps     application/vnd.ms-powerpoint
.ppa     application/vnd.ms-powerpoint

.pptx      application/vnd.openxmlformats-officedocument.presentationml.presentation
.potx      application/vnd.openxmlformats-officedocument.presentationml.template
.ppsx     application/vnd.openxmlformats-officedocument.presentationml.slideshow
.ppam    application/vnd.ms-powerpoint.addin.macroEnabled.12
.pptm     application/vnd.ms-powerpoint.presentation.macroEnabled.12
.potm     application/vnd.ms-powerpoint.presentation.macroEnabled.12
.ppsm    application/vnd.ms-powerpoint.slideshow.macroEnabled.12

  Custom Gallery를 동작하도록 만들기는 했지만 scroll을 상하로 바꿔서 격자 구조로 사진의 thumbnail을 보는 게 편할 것 같다. 찾아보니 GridView라는 layout이 따로 있었다. Android API reference에 따르면 애초에 Grid View 자체가 2차원으로 item을 보여주는(이 말은 즉, Main이 되는 View위에 다른 View를 여러 개 보여주는) scroll이 가능한 Viewgroup이라고 한다. 이전에 만들었던 앱에서 Gallery를 GridView로 바꾸어 구연해 보자. 

1. 목적 : GridView에 대해 알아보고 이를 이용한 Image Gallery(List)를 만들어보자

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Grid View Example (http://developer.android.com/intl/ko/guide/topics/ui/layout/gridview.html)
 2) AndroidHive - Android GridView Layout Tutorial (http://www.androidhive.info/2012/02/android-gridview-layout-tutorial/)

4. 과정
 1) Main이 되는 Layout(여기서는 activity_main.xml)에 <GridView ...></GridView>를 다음과 같이 선언한다. 자동 완성 scroll 밑으로 <GridLayout>도 볼 수 있으나 minSdkVersion이 높으므로 낮은 사양에서도 사용할 수 있도록 <GridView>를 사용하기로 한다.

<?xml version="1.0" encoding="utf-8"?>
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/gridview"
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"
    android:columnWidth="90dp" <!-- columnWidth(나눠진 칸 안에 있는 View의 가로 길이)에 따라 column(열)의 갯수가 달라진다 -->
    android:numColumns="auto_fit" <!-- 임의로 숫자 지정 가능 -->
    android:verticalSpacing="10dp"
    android:horizontalSpacing="10dp"
    android:stretchMode="columnWidth"
    android:gravity="center">

</GridView>

 

 2) GridView 내부에 ImageView 들을 뿌릴 Adapter를 다음과 같이 정의한다. 이전의 Adapter 부분과 비슷하다.

    // 이상 생략
   
// create a new ImageView for each item referenced by the Adapter
   // 참고자료 내 Android Developers에 게시된 Example 수정

    public View getView(int position, View convertView, ViewGroup parent) {
        ImageView imageView;
        if (convertView == null) {
            // if it's not recycled, initialize some attributes
            imageView = new ImageView(mContext);
        } else {
            imageView = (ImageView) convertView;
        }
        bm = BitmapFactory.decodeFile(mBasePath + File.separator + mImgList[position]);
        Bitmap mThumbnail = ThumbnailUtils.extractThumbnail(bm, 300, 300);
        imageView.setPadding(8, 8, 8, 8);
        imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
        imageView.setLayoutParams(new GridView.LayoutParams(GridView.LayoutParams.MATCH_PARENT, GridView.LayoutParams.MATCH_PARENT));
        imageView.setImageBitmap(mThumbnail);
        return imageView;
    }
    // 이하 생략

 

 3) MainActivity.java 에서 앞서 정의한 GridView layout과 Adapter를 연결하고 Item Click 시 adapter에서 넘어온 해당 ImageView의 주소 값을 Toast message로 보여주도록 다음과 같이 설정한다.


public class
MainActivity extends AppCompatActivity {

    public String basePath = null;
    public GridView mGridView;
    public CustomImageAdapter mCustomImageAdapter;

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

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyCameraApp");

        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");
            }
        }

        basePath = mediaStorageDir.getPath();

        mGridView = (GridView)findViewById(R.id.gridview); // .xml의 GridView와 연결
        mCustomImageAdapter = new CustomImageAdapter(this, basePath); // 앞에서 정의한 Custom Image Adapter와 연결
        mGridView.setAdapter(mCustomImageAdapter); // GridView가 Custom Image Adapter에서 받은 값을 뿌릴 수 있도록 연결
        mGridView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Toast.makeText(getApplicationContext(), mCustomImageAdapter.getItemPath(position), Toast.LENGTH_LONG).show();
            }
        });
    }
}

 

(위 Code의 실행 결과)

  

      

 

  요즘에 만들고 있는 Custom Camera & Gallery App에서 Gallery의 갱신(update)가 잘 되지 않는 문제를 해결하기 위해서 이전까지는 Activity의 생애주기(Life-cycle)를 살펴봤다. 이를 통해서 Camera Intent 실행 후 넘어왔을 때 Gallery의 배열이 update 되도록 하는 문제는 해결되었으나, 파일 삭제를 위해 따로 Intent 만들어 불러오기는 작동은 되나 쓸데없이 군더더기가 많은 듯 하였다. 그래서 gallery update 에 대하여 자료를 찾아봤더니 애초에 adapter class에 있는 notifyDataSetChanged() method를 활용하면 되는 것이었다(이걸 모르고 괜히 헛짓거리를 했네 쿨럭;;). 그런데 이전에 ListView도 그렇고 Adapter를 여기저기 활용하는데, 대체 Adapter란게 무엇인가에 대하여 제대로 살펴봐야 겠다.

1. 목적 : Adapter에 대하여 알아보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Adapter Reference (http://developer.android.com/reference/android/widget/Adapter.html)

4. Adapter에 대한 이해
 1) Adapter란 무엇인가?
   Android APIs' Reference에 따르면 'Adapter' 자체는 하나의 Object(객체)로서, 보여지는 View와 그 View에 올릴 Data를 연결하는 일종의 Bridge라고 한다. 그래서 그런지 ListView Adapter에 대한 예시에서도, GalleryAdapter에 대한 예시에서도 해당 Adapter의 생성자에서 Data를 연결하고 나서 getView() method에서 해당 position에 올라갈 TextView나 ImageView를 setting 해 View로서 뿌려주는 모양이다.

[사진 1] 기본 Adapter를 상속받는 subclass들 목록이다.

 2) Adapter 에 속해 있는 method들

 
[사진 2] Adapter의 기본 public method list

  Adapter class에 속해 있는 기본 method 의 목록은 위 [사진 2]와 같다. getCount()를 통해서 Adapter가 보여주는 item의 갯수값을 int로 받을 수 있고, getItem(int position)을 통해서 position에 위치해 있는 item의 data를 받을 수도 있다. getItemId(int postion)의 경우에는 getItem(int position)과 비슷하게 position에 위치한 값을 반환하기는 하나 그 값이 Item마다 부여된 일종의 ID라고 한다. 그리고 화면에 뿌려주기 위해서 getView(int position, View convertView, ViewGroup parent)를 활용하고, 이 getView()를 통해 생성된 View의 갯수를 getViewTypeCount()로 받을 수 있다고 한다. 이 외에도 hasStableIds()를 통해서 각 Row마다 부여된 ID를 변화에 상관없이 고정할지(stable) 여부를 결정할 수 있으며, 해당 Adapter가 비었는지를 isEmpty()로 판단할 수 있다.

  한편, notifyDataSetChanged()와 같은 기능을 활용하기 위해서는 Adapter에 묶여있는 Item의 변경 여부를 감지해야 하는데 이를 감지하는 것이 말그대로 'DataSetObserver'이다. 따라서, item의 변경이 감지(notify)되도록 하기 위해서는 registerDataSetObserver(DataSetObserver observer)를 통해서 observer를 Adapter에 연결해야 한다.

5. 적용 예시

 1) CustomGalleryAdapter.java 내 registerDataSetObserver() 등의 추가


public class
CustomGalleryAdapter extends BaseAdapter {
    int CustomGalleryItemBg;
    String mBasePath;
    Context mContext;
    String[] mImgs;
    Bitmap bm;
    DataSetObservable mDataSetObservable = new DataSetObservable(); // DataSetObservable(DataSetObserver)의 생성

    public String TAG = "Gallery Adapter Example :: ";

    public CustomGalleryAdapter(Context context, String basepath){
        this.mContext = context;
        this.mBasePath = basepath;

        File file = new File(mBasePath);
        if(!file.exists()){
            if(!file.mkdirs()){
                Log.d(TAG, "failed to create directory");
            }
        }
        mImgs = file.list(); 

        TypedArray array = mContext.obtainStyledAttributes(R.styleable.GalleryTheme);
        CustomGalleryItemBg = array.getResourceId(R.styleable.GalleryTheme_android_galleryItemBackground, 0);
        array.recycle();
    }

    @Override
    public int getCount() {
        File dir = new File(mBasePath);
        mImgs = dir.list();
        return mImgs.length;
    }

    @Override
    public Object getItem(int position) {
        return position;
    }

    // Adapter 내 Item에서 직접 주소를 받아오도록 method 추가.
    // 이전에는 MainActivity와 주소 및 position이 달라 비정상적인 앱의 종료가 발생한 것으로 보인다

    public String getItemPath(int position){ 
        String path = mBasePath + File.separator + mImgs[position];
        return path;
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    // Override this method according to your need
    @Override
    public View getView(int index, View view, ViewGroup viewGroup)

    {
        // TODO Auto-generated method stub
        ImageView i = new ImageView(mContext);

        File dir = new File(mBasePath);
        mImgs = dir.list();
        bm = BitmapFactory.decodeFile(mBasePath+ File.separator +mImgs[index]);

        Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, 300, 300);
        i.setLayoutParams(new Gallery.LayoutParams(300, 300));
        i.setImageBitmap(bm2);
        i.setVisibility(ImageView.VISIBLE);

        i.setBackgroundResource(CustomGalleryItemBg);
        i.setScaleType(ImageView.ScaleType.FIT_CENTER);

        if (bm != null && !bm.isRecycled()) {
            bm.recycle();
        }
        return i;
    }

    @Override
    public void registerDataSetObserver(DataSetObserver observer){ // DataSetObserver의 등록(연결)
        mDataSetObservable.registerObserver(observer);
    }

    @Override
    public void unregisterDataSetObserver(DataSetObserver observer){ // DataSetObserver의 해제
        mDataSetObservable.unregisterObserver(observer);
    }

    @Override
    public void notifyDataSetChanged(){ // 위에서 연결된 DataSetObserver를 통한 변경 확인
        mDataSetObservable.notifyChanged();
    }
}

 

 2) MainActivity.java에 적용

// 파일 삭제 후 notifyDataSetChanged() 적용
        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                        File file = new File(delFilePath);
                        file.delete();
                        File temp = new File(basePath);
                        imgs = temp.list();
                        customGalAdapter.notifyDataSetChanged();
                    }
            });

 

(위 Code의 실행 결과)

     

      

 

  앞선 글에서 Activity 생애주기(Life Cycle)를 고려하여 gallery와 gallery adapter를 onCreate()가 아니라 onStart()에 생성 및 연결을 했을 경우, camera intent 호출 후 새로 찍은 사진이 gallery에 정상 반영됨을 확인했다. 이를 보면서 사진을 삭제한 후에도 onStart()를 불러온다면 문제가 해결되겠다는 생각이 들었는데, 이를 구현하기 위해서 사진을 삭제하는 과정 자체를 새 intent로 불러왔다가 종료하여 MainActivity가 onRestart()가 되도록 해야했다. 그래서 이번에는 App. 내부에 새로운 activity를 추가해 intent로 활용하는 방법을 알아봤다. 

1. 목적 : Intent를 만들어 activity의 작동 구조를 이해하여 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Intent Reference (http://developer.android.com/reference/android/content/Intent.html)
 2) Android Developers - 인텐트 및 인텐트 필터 (http://developer.android.com/intl/ko/guide/components/intents-filters.html)

4. 과정
 일반적인 Intent 생성 및 작동 예제에 내가 필요한 기능을 추가했다.

 1) 새로 추가할 intent의 layout이 될 .xml 파일을 다음과 같이 '/layout' directory 밑에 생성한다. 필요에 따라 layout_main.xml을 활용하듯이 Button, ImageView, TextView 등을 추가하여 새로운 activity를 구성하면 된다.

 

2) Intent가 될 새 Activity를 생성한다. 'extends Activity'한 class java file을 다음과 같이 '/java' 밑에 만들고 MainActivity에서 onCreate() 내부에 내용을 쓰듯이 원하는 동작을 작성하면 된다. 나 같은 경우에는 단순히 AlertDialog를 사용하기 위해 intent를 불러오므로 따로 setContentView(R.layout.'intent layout file 명')하지 않아도 동작은 했었으나 일단 다음과 같이 코딩하였다.

public class FileRearrange extends AppCompatActivity{

    String delFilePath = null; // MainActivity에서 삭제할 file의 경로를 받을 String 변수 선언

    @Override
    protected void onCreate(Bundle savedIntanceState){
        super.onCreate(savedIntanceState);
        setContentView(R.layout.activity_file_rearrange);

        final Intent receiveIntent = getIntent(); // MainActivity에서 intent를 받음
        delFilePath = receiveIntent.getStringExtra("delFilePath"); // MainActivity의 intent에서 추가한 data 참조
        /* 이하 AlertDialog 활용 */
        AlertDialog.Builder builder = new AlertDialog.Builder(FileRearrange.this);
        builder.setTitle("파일을 삭제하시겠습니까?");
        builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss(); // 해당 AlertDialog의 dismiss() 및
                finish(); // receiveIntent의 finish();
            }
        });
        builder.setPositiveButton("예", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                File file = new File(delFilePath);
                file.delete(); // receive 받은 data를 활용해 file 삭제 후
                finish(); // receiveIntent의 finish();
            }
        });
        Dialog dialog = builder.create();
        dialog.show();
    }
}

 

 3) AndroidMainfest.xml 에 (2)에서 만든 Activity를 <application>...</application> 내부에 다음과 같이 <activity>...</activity>하여 선언한다. 이를 통해 App. 내부 Activity같의 communication이 가능해 진다.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.zip.customcameragalleryexample" >

    <uses-feature android:name="android.hardware.camera2" android:required="false" />

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


    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:hardwareAccelerated="false">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <!-- 이하 activity 추가 부분 -->
        <activity android:name=".FileRearrange"> <!-- 위의 activity class명과 동일 -->
            <intent-filter>
                <action android:name="com.example.zip.customcameragalleryexample.filerearrange"/> <!-- intent 생성시 참조 -->
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <!-- 이상 activity 추가 부분 -->
    </application>

</manifest>

 

 4) MainActivity.java 에서 파일을 삭제하고자 할 때 FileRearrange로 넘어가도록 다음과 같이 intent를 생성 및 추가한다. Intent 의 생성 → 필요할 경우 data 추가 → 해당 Intent의 start 순서로 넘어감을 이해하면 된다.

// CustomGallery 예시의 LongClickListener 부분을 다음과 같이 수정하였다.
      customGallery
.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {

                final int tmpPosition = position;
                final String delFilePath = basePath + File.separator + imgs[tmpPosition];
                Intent intent = new Intent(MainActivity.this, FileRearrange.class);
                intent.putExtra("delFilePath", delFilePath);
                startActivity(intent);
                return false;
            }
        });

 

(위 Code의 실행 결과)
  원하는 동작이 오류 없이 실행되긴 했으나, 새로 intent를 불러오니 MainActivity 가 보이지 않았다. 그래서 OnItemLongClick 내부에서 file 삭제 후 다시 gallery를 불러오도록 해당 method를 재선언을 하자니 이 방법은 재귀적이라 app 자체가 비정상적으로 종료해서 쓸 수가 없었다. 아무래도 Adapter에서 배열의 변화를 감지하여 update가 되도록 notifyDataSetChanged() 와 같은 adapter의 method들을 다시 살펴볼 필요가 있어 보인다.

     

  

 

  사실 일전의 Custom Gallery Example에서 Gallery Item Long Click시 해당 사진 파일을 삭제하는 코드를 입력했었다. 실행 결과, 파일은 삭제되기는 했지만 파일 삭제 후의 Gallery Item들의 배열이 정리되지 않았고, 원래 해당 파일이 있던 frame 내부가 null 값으로 까맣게 보이며 click 시 App.이 비정상적으로 종료되었다. 그리고 Camera Intent로 촬영 후 새로 찍은 사진이 Gallery에 넘어오지 않았다. 아무래도 file 배열이 변경된 후 Gallery Adapter를 다시 불러와야 될 것 같은데, 원래 adapter가 생성되어 있던 onCreate()를 통째로 다시 불러들여도 괜찮을지 Activity 생애주기(Life Cycle)에 대해 알아봐야 겠다.

1. 목적 : Android Activity LifeCycle(Activity 생애주기)를 이해하고 기본 구조를 알아보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - Activity Reference (http://developer.android.com/reference/android/app/Activity.html)

4. Activity Life Cycle(Acitivity 생애 주기)의 이론

  Android Studio로 App. project를 생성해 보면 /java/'해당 packaging 명'/MainActivity.java 이 기본으로 만들어져 있고 그 안에 파일명과 같은 class가 'extends Activity' 혹은 'extends AppCompatAcitivity'을 끌고 기본 method인 onCreate(Bundle savedInstanceState)를 @Override한 채로 만들어져 있는 광경을 볼 수 있다. 이 모습을 보다보면 아무래도 C 언어에서 void main(void) 가 생성되며 시작 지점이 자동으로 지정되듯이 onCreate()가 필수적 임을 눈치로 대강 알 수 있을텐데, 실제로 'Android Developers reference'를 찾아보면 다음과 같은 순서도를 통해서 App.의 Activity에 대한 동작 구조를 파악할 수 있다.

  보시다시피 App.이 실행되면서 Activity가 시작되면 처음에 onCreate()를 통해서 Activity가 생성되고 onStart()와 onResume()을 거처 해당 Activity가 실행된다. 그러다가 해당 Activity를 sleep 시킬 때 onPause()와 onStop()을 거치게 되고, onStop() method 시점에서 사용자가 해당 Activity를 다시 실행시키고자 하면 onRestart()를, App.이 memory를 많이 잡아먹어 시스템에 의해 process kill이 됐을 때 사용자가 원할 경우 onCreate()로 넘어간다. 이런 method들을 돌고 돌던 Activity는 끝날 때가 되면 onDestory()를 통해 system에서 사라진다.

  그래서 App.의 activity의 생애주기에 따라 작동해야 할 특정한 기능이 있다면 아래와 같이 Activity class 내에 위 method()를 따로 정의해서 사용한다. 하나의 예로서, 이전에 항상 꺼지지 않는 App.을 만들기 위해서 onStop() 을 @Override하여 onStop() 내에서 onRestart()를 무조건 실행시키게 만들었었다.

public class Activity extends ApplicationContext {
    protected void onCreate(Bundle savedInstanceState);

    protected void onStart();

    protected void onRestart();

    protected void onResume();

    protected void onPause();

    protected void onStop();

    protected void onDestroy();
}

  그럼 이번 Gallery가 새로 찍은 사진이 update되지 않는 문제는 Gallery Adapter의 생성 및 연결을 onStart() method에 선언하여 @Override 해보면 되지 않을까? 아무래도 Camera Intent를 불러오면서 기존 MainActivity는 onStop()에 있어 intent가 종료되고 나면 onStart() 부터 다시 시작할 것 같았기 때문이다.

5. 실험

  이전의 Custom Gallery Example의 Gallery Adapter 선언 부분을 단순히 onStart()로 아래와 같이 옮겨봤다.

public class MainActivity extends AppCompatActivity {

    public static final int MEDIA_TYPE_IMAGE = 1;
    public static final int MEDIA_TYPE_VIDEO = 2;
    public int inSampleSize = 1;

    private static final int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 100;
    private Uri fileUri;
    private static String basePath;

    public float imageViewRotation = 90;
    public String TAG = "Camera Example :: ";

    private Button takePicBtn;
    private ImageView resultView;
    private TextView imgPath;
    private Gallery customGallery;
    private CustomGalleryAdapter customGalAdapter;

    private String[] imgs;

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

        File mediaStorageDir = new File(Environment.getExternalStoragePublicDirectory(
                Environment.DIRECTORY_PICTURES), "MyCameraApp");
        // This location works best if you want the created images to be shared
        // between applications and persist after your app has been uninstalled.

        // Create the storage directory if it does not exist
        if (! mediaStorageDir.exists()){
            if (! mediaStorageDir.mkdirs()){
                Log.d("MyCameraApp", "failed to create directory");

            }
        }

        basePath = mediaStorageDir.getPath();

        imgPath = (TextView)findViewById(R.id.imgpath);
        resultView = (ImageView)findViewById(R.id.resultview);
        takePicBtn = (Button)findViewById(R.id.takepicbtn);
        takePicBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // create Intent to take a picture and return control to the calling application
                Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

                fileUri = getOutputMediaFileUri(MEDIA_TYPE_IMAGE); // create a file to save the image
                intent.putExtra(MediaStore.EXTRA_OUTPUT, fileUri); // set the image file name

                // start the image capture Intent
                startActivityForResult(intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
            }
        });

       
        customGallery = (Gallery)findViewById(R.id.customgallery);


    }

    /* 아래와 같이 onStart()를 Override하여 Gallery Adapter 와 연관된 부분을 그대로 복사해 옮겨놓았다. */
    @Override
    protected void onStart(){
        super.onStart();

        File file = new File(basePath);
        imgs = file.list();
        for(int i=0; i<imgs.length; i++){
            imgPath.setText(imgs[i]);
        }

        customGalAdapter = new CustomGalleryAdapter(getApplicationContext(), basePath);
        customGallery.setAdapter(customGalAdapter);
        customGallery.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Bitmap bm = BitmapFactory.decodeFile(basePath+ File.separator +imgs[position]);
                Bitmap bm2 = ThumbnailUtils.extractThumbnail(bm, bm.getWidth() / inSampleSize, bm.getHeight() / inSampleSize);
                resultView.setImageBitmap(bm2);
                imgPath.setText(basePath+File.separator+imgs[position]);
            }
        });
        customGallery.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() {
            @Override
            public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
                File temp = new File(basePath+File.separator+imgs[position]);
                temp.delete();
                return false;
            }
        });
    }

  위 변경사항을 반영하여 compile/Run을 시켜봤더니 다행히 camera intent 실행 후 새로 찍은 사진이 gallery 에 반영되었다. 이제는 파일 삭제 후 Gallery update가 되도록 다른 method를 살펴봐야겠다.

(위 code의 실행결과)

     

 

  앞의 예시가 기본 AlertDialog의 Method를 사용한 경우라면 이번에는 이전의 ListView와 같이 LayoutInflater를 활용하여 Custom Layout을 AlertDialog에 입혀서 활용해 보자. 실제 App. 개발시에 중요한 부분은 아니겠지만 이왕이면 여러가지 사용법을 알면 좋지 않을까 싶다.

1. 목적 : Custom Layout을 LayoutInflater를 통해 AlertDialog에 반영해 알림창을 만들어 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - AlertDialog Reference (http://developer.android.com/reference/android/app/AlertDialog.html)
 2) 창조적 고찰 - Android 커스텀 다이얼로그 생성 (http://ismydream.tistory.com/107)

4. 과정
  이전의 'AlertDialog를 활용한 알림창 띄우기 01 - 기본'편에 살을 덧붙여 만들어 봤다.

 1) AlertDialog가 받아들일 Custom View에 해당하는 layout 파일을 /layout directory 밑에 생성한다.

<!-- /layout/custom_alert_layout.xml -->
<?
xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="30dp">

    <ImageView
        android:id="@+id/customdialogicon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/customtitle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/customdialogicon"
        android:padding="10dp"
        android:text="Hello World!"/>
</RelativeLayout>

 

 2) AlertDialog를 띄울 activity에 LayoutInflater를 통해 위의 layout file을 연결시키고, 해당 사항을 AlertDialog.Builder의 setView(View view)을 통해 반영한다.

public class MainActivity extends AppCompatActivity {

    public ImageButton imgBtn;
    public TextView returnVal;

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

        imgBtn = (ImageButton)findViewById(R.id.imgbtn); // ImageButton의 activity 내 생성 및 연결
        returnVal = (TextView)findViewById(R.id.returnval);
        imgBtn.setOnClickListener(new View.OnClickListener() { // ImageButton을 Click시 AlertDialog가 생성되도록 아래과 같이 설계
            @Override
            public void onClick(View v) {
                // LayoutInflater를 통해 위의 custom layout을 AlertDialog에 반영. 이 외에는 거의 동일하다.
                LayoutInflater inflater = (LayoutInflater)getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE);
                View view = inflater.inflate(R.layout.custom_alert_layout, null);
                TextView customTitle = (TextView)view.findViewById(R.id.customtitle);
                customTitle.setText("종료하시겠습니까?");
                customTitle.setTextColor(Color.BLACK);
                ImageView customIcon = (ImageView)view.findViewById(R.id.customdialogicon);
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
                builder.setView(view);
                builder.setPositiveButton("네", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish();
                    }
                });
                builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        returnVal.setText("Custom AlertDialog를 통해서 return");
                        dialog.dismiss();
                    }
                });
                AlertDialog dialog = builder.create();
                dialog.show();

            }
        });
    }
}

(위 code의 실행 결과)

  

  

 

  어제는 PopupWindow를 통해 main이 되는 View 에 말풍선처럼 popup을 띄우는 방식을 살펴보았다. 그런데 기존 App.들에서는 안내 메시지 창 자체가 따로 위에 뜨는 느낌이었던 터라 뭐 다른게 있나 싶어 찾아봤더니, AlertDialog를 사용한 것이었다. 이번엔 AlertDialog.builder를 통해 메시지 창을 만들어 동작시켜 보자.

1. 목적 : AlertDialog 의 사용법을 익히고 App.에 적용해 보자.

2. 개발 환경
 - PC : Windows 7, Android Studio 1.4.1(64-bit)
 - Phone : LG G pro Lollipop(API 21)

3. 참고자료
 1) Android Developers - AlertDialog Reference (http://developer.android.com/reference/android/app/AlertDialog.html)
 2) 남시언의 문화지식탐험 - AlertDialog 생성 예제 (http://namsieon.com/313)

4. 과정
 1) 어제와 같이 AlertDialog를 띄울 ImageButton와 return 여부를 확인할 TextView를 activity_main.xml 에 생성한다. AlertDialog에서 돌아왔을 때 return 여부를 보여주기 위해서 TextView의 text는 비워둔다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">

    <ImageButton
        android:id="@+id/imgbtn"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:padding="15dp"
        android:src="@mipmap/ic_launcher"/>
    <TextView
        android:id="@+id/returnval"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/imgbtn"/>
</RelativeLayout>

 

 2) App.의 전체적인 activity를 구성하는 MainActivity.java에 위의 ImageButton과 연결할 변수를 생성 및 연결하고, 해당 Button을 Click하면 AlertDialog 가 나타나도록 다음과 같이 설계한다.

public class MainActivity extends AppCompatActivity {

    public ImageButton imgBtn;
    public TextView returnVal;

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

        imgBtn = (ImageButton)findViewById(R.id.imgbtn); // ImageButton의 activity 내 생성 및 연결
        returnVal = (TextView)findViewById(R.id.returnval);
        imgBtn.setOnClickListener(new View.OnClickListener() { // ImageButton을 Click시 AlertDialog가 생성되도록 아래과 같이 설계
            @Override
            public void onClick(View v) {
                AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this); // AlertDialog를 띄울 activity를 argument로 지정해야 한다.
                builder.setTitle("앱을 종료하시겠습니까?"); // AlertDialog.builder를 통해 Title text를 입력
                builder.setPositiveButton("네", new DialogInterface.OnClickListener() { // AlertDialog.Builder에 Positive Button을 생성
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        finish(); // App.의 종료. Activity 생애 주기 참고
                    }
                });
                builder.setNegativeButton("아니오", new DialogInterface.OnClickListener() { // AlertDialog.Builder에 Negative Button을 생성
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        returnVal.setText("앱을 종료하지 않고 돌아왔음");
                        dialog.dismiss(); // "아니오" button이 받은 DialogInterface를 dismiss 하여 MainView로 돌아감
                    }
                });
                AlertDialog dialog = builder.create(); // 위의 builder를 생성할 AlertDialog 객체 생성
                dialog.show(); // dialog를 화면에 뿌려 줌
            }
        });
    }
}

(위의 code 실행 결과)

   

  

 

+ Recent posts