Android SDK Tool 20 버전 이상부터 Library Project의 AndroidManifest.xml 의 내용을 애플리케이션 프로젝트의 Manifest에 병합할 수 있게 되었다 (관련 링크 : http://tools.android.com/download/adt-20-preview)


  • Build System
    • Automatic merging of library project manifest files into the including project's manifest. Enable with the manifestmerger.enabled property.


이전에는 Library Project의 매니페스트에 서비스나 액티비티, 퍼미션 등을 등록하더라도 해당 Library Project를 사용하는 애플리케이션 프로젝트에는 아무런 적용이 되지 않았는데, ADT 20 부터는 이를 활용할 수 있게 된것이다.


지금 소마에서 개발하고 있는 Salina의 안드로이드 SDK에서 퍼미션이나, 서비스 사용을 위해서 애플리케이션 프로젝트에서 이래저래 설정해줘야 할 것들이 많았는데, 이것으로 SDK의 적용이 아주 쉬워졌다. 라이브러리를 개발하는 입장에서는 아주 큰 이점이 아닌가 싶다.


라이브러리 프로젝트와 애플리케이션 프로젝트의 Manifest의 내용이 중복되는 경우의 충돌은 확인해 봐야할 듯 하다.


적용방법은 간단하다.  프로젝트의 설정파일(project.preference)에서 manifestmerger.enabled=true 만 추가해주면 적용된다.

Salina Project에서 RatingBar가 아래 같은 모습으로 들어가야 하는데 기본 크기는 너무 크다






그렇다고 그냥 RatingBar의 layout_width, layout_height의 크기를 줄여버리면 이런 모습이 되어버린다.


ㅡㅡ...




검색 결과 중 대부분이 RatingBar를 커스터마이징 하기 위해서 style xml을 만들어내는 것이었는데,

(http://blog.naver.com/PostView.nhn?blogId=telsome&logNo=90094111681)

RatingBar를 커스터마이징 할 수도 있는 거지만 크기만 변경하는 되는 문제여서 더 간단한 것을 찾다 보니 

StackOverFlow에서 답을 찾을 수 있었다.


>> StackOverFlow : Decrease the size of Rating Bar in android 2.1


RatingBar의 xml에 속성에 style="?android:attr/ratingBarStyleSmall" 를 추가하면 보다 작은 크기로 적용이 된다.


<RatingBar
	    android:layout_width="wrap_content"
	    android:layout_height="wrap_content"
	    style="?android:attr/ratingBarStyleSmall"/>


다만 정확히 원하는 크기로 변경하기 위해서는 커스터마이징을 해야한다.

이미지뷰를 화면에 꽉 채우기에 앞서 뷰의 영역에 대해 먼저 설명하겠습니다.



그림이 다소 난잡하지만, 하나의 뷰는 크게 세 가지 영역으로 볼 수 있습니다.

뷰가 실제로 보이는 크기와는 상관이 없지만 다른 뷰와의 거리를 조절하는 외부 여백 margin

눈에 보이는 크기에 포함되는 내부 여백 padding,

전체 뷰 크기에서 위의 두 여백을 제외하고 내용이 들어가는 부분으로 나뉘어집니다.


margin과 padding은 둘다 여백이지만 뷰에 배경색상을 지정했을 때 margin 영역에는

배경색상이 지정되지 않고 공간만을 차지하고, padding 부분에는 배경색상이 적용됩니다.


왜 이 이야길 꺼냈느냐 하면, 액티비티에 이미지뷰를 삽입하려면 이미지뷰를 둘러싸고 있는 레이아웃이

padding의 값이 0이 아닌 경우가 있기 때문에 이미지뷰를 아무리 키운다고 한들 여백이 생기기 때문입니다.


IDE (Eclipse나 IntelliJ 기반의 Android Studio)에서 새로운 레이아웃 XML을 자동으로 생성하면

기본 레이아웃은 RelativeLayout이고 요런 속성이 주렁주렁 달려서 자동으로 xml 문서가 생성됩니다.

android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"


저렇게 자동으로 생성된 xml 레이아웃 파일의 경우 저 부분을 제거해주어야 원하는 대로 화면에 꽉 찬 이미지뷰를 만들 수 있습니다.



여백에 관한 얘기는 여기까지 하고, 이미지뷰에서 이미지를 채우기 관련 속성인 scaleType에 대해 알아보겠습니다.

scaleType 속성이 가질 수 있는 값은



위의 그림과 같이 matrix, fitXY, fitStart, fitCenter, fitEnd, center, centerCrop, centerInside 여덟가지입니다.


위 속성에 관해 녹두장군님의 포스트(http://mainia.tistory.com/473)에서 설명이 잘 되어있어 링크로 대체하도록 하고, 화면에 이미지를 맞추기 위해서는 scaleType의 값으로 fitXY를 사용하면됩니다.


위의 내용대로 한다면 xml의 내용은 아래와 같이 나오게됩니다.


<LinearLayout 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"
    tools:context=".IntroActivity" >

    <ImageView 
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:src="@drawable/salina_community_intro"
        android:scaleType="fitXY"/>

</LinearLayout>


추가적으로 Manifest에서 Activity의 theme 속성 수정을 통해서 타이틀바의 유무, 상태표시바 유무(풀스크린)를 수정할 수 있습니다. 어찌보면 별 내용도 없는 것을 횡설수설 괜히 길게 썼네요. 도움이 되셨길 바랍니다~!  :)

현재 진행 중인 Salina 프로젝트에서 나름 국제화를 적용하겠다고 상수 스트링을 values 리소스로 빼두고

final 키워드를 제거 후 멤버 변수 위치 그대로 두고 getString(int resourceId)를 호출하니 NullPointerException 발생


프로그래밍계의 지식인  StackOverflow느님께서 역시 해결책을 주셨다.


원인은 액티비티가 초기화되기 전에 리소스를 사용하려고 하면 NullPointerException이 발생할 수 있다고 한다.


즉 액티비티에서 사용하는 getString()은 Resources 객체의 getString()을 래핑한 메서드이고.

Resources는 Context 객체로부터 얻을 수 있는데, getString()을 풀어보면

getContext().getResources().getString()이 되는 셈이다.


따라서 해결책은 멤버 필드에서 바로 초기화를 하는 대신 onCreate 이 후 시점에 해야한다.

그냥 onCreate 안에서 하면 된다.

혹은 Application 클래스를 상속한 커스텀 Application을 만들고 거기에 static 메서드를 이용해서 컨텍스트를 통해 리소스를 가져오는 방법이다.

액티비티는 해당 액티비티가 onCreate 되어서야 초기화 후에 context를 갖지만 애플리케이션 단위에서는 처음 앱이 실행될 때 정보를 가지고 있기 때문에 가능하다.

자바 웹 프로그래밍을 하면서 ORM을 당연하게 사용해왔는데,

안드로이드에서 SQLite DB 프로그래밍을 하려니.. ORM 쓰던게 습관이 되어있어서인지

귀찮은 점이 한두가지가 아니네요.


먼저 ORM에 대해 간략하게 소개하자면 Object Relational Mapping으로 객체와 릴레이션(RDB의 테이블)간에 매핑을 해 주는 역할을 합니다. 쉽게 말하자면 Book 이라는 객체가 있다면 이 객체를 ORM을 통해 RDB에 넣거나(Insert), 가져오거나(Select), 지우거나(Delete), 고칠 수(Update)도 있고, 기본적인 CRUD 외에도 스키마를 다루거나 조인 연산등 RDB의 기능을 객체지향적으로 다룰 수 있도록 도와줍니다.



ORM

이미지 출처 : 4 Benefits of Object-Relational Mapping (ORM)


SQLite를 대상으로 하는 안드로이드 기반 ORM 라이브러리를 몇개 찾아보았습니다. 아래와 같은 것들이 있네요.

  1. ActiveAndroid(https://www.activeandroid.com/)
  2. SqliteORM(https://github.com/kremerk/SqliteORM/wiki)
  3. ORMLite(http://ormlite.com)
  4. Storm(https://code.google.com/p/storm-gen/)
  5. Green Dao(http://greendao-orm.com)
  6. Sugar ORM(https://github.com/satyan/sugar)


글 제목에서도 알 수 있지만 Sugar ORM이 간단하게 쓰기에는 쉽게 쓸 수 있도록 되어있네요. 모두 사용해 볼 수는 없어서 각 라이브러리들의 Getting Start에서 사용 방법을 눈으로만 살펴봤는데, DB 핸들링 관련 코드를 만들기 위한 Generater와 사용을 위한 라이브러리가 따로 있는가 하면, 빌드 패스에 이것 저것 추가하고 설정도 하는 것도 있고, 이래 저래 사용하기 복잡해서 좀 귀찮아도 그냥 Helper 구현해서 쓰고 말지 하는 생각이 드는 것들이 대부분이었네요. (사실 새로운 걸 익힌다는 것에 대한 귀찮음이 맞지만요 :D )


어쨌거나 Sugar ORM이 개중에서는 가장 쓰기 쉽도록 되어있네요.

간략하게 사용 순서를 풀어보면

1. 안드로이드 프로젝트 libs 폴더에 라이브러리를 추가한다.

2. 매니페스트에 Application 클래스를 설정한다(보통은 설정되어있지 않고 디폴트로 사용합니다.)

3. 매니페스트에 meta-data 태그를 이용하여 4개의 프로퍼티를 추가한다.

4. 쓴다.






코드로 써 봐야 아는 것에 구구절절 글을 늘어놓는 것 보다 백문이 불여일견 백견이 불여일행이라 하였으니 해보도록 하겠습니다. 개발 환경은 Windows 7, Eclipse Indigo + Android SDK Plug-in입니다.


가장 먼저 Sugar ORM GitHub 다운로드 페이지에서 라이브러리 파일을 다운로드 받습니다.

https://github.com/satyan/sugar/blob/master/dist/sugar-1.1.jar

(글을 쓰는 13.03.04 기준 7개월 전에 업데이트 되었네요)


그런 다음 이클립스를 켜고 새 안드로이드 애플리케이션 프로젝트를 생성해 줍니다.



프로젝트 이름이나 패키지는 본인의 입맛에 맞게 설정하시면 되겠습니다.

다만 Package Name은 기억해 두도록 하세요. 저는 nnoco.example.sugar 로 기본 패키지 경로를 정했습니다.

나중에 Manifest.xml 파일에 속성값으로 쓸 일이 있습니다.


Next -> Next -> ... Finish를 하면 프로젝트가 짠! 하고 만들어졌습니다.


ADK 플러그인 버전에 따라서 libs 폴더를 자동으로 생성해주기도 하고, 없는 경우도 있는데요, libs 폴더는 기본적으로 안드로이드 패키지에서 라이브러리가 있는 클래스패스로 인식을 합니다. 외부 라이브러리 파일의 경우 이곳에 복사하시면 클래스패스에 따로 추가하지 않으셔도 자동으로 잡히게 됩니다.

따라서 프로젝트 루트에 libs가 없는 분은 libs 폴더를 하나 추가해주시면 되겠습니다. 그럼 아래와 같은 프로젝트 구조가 보이게 됩니다. (환경에 따라 조금씩 다를 수 있지만 여기서는 libs 폴더만 있으면 됩니다.)



그럼 저 libs 폴더 안에 프로젝트를 만들기 전에 다운로드 받은 sugar-1.1.jar 파일을 복사해서 넣어줍니다.




여기까지 되었다면 이제 Manifest.xml 파일을 편집해 보겠습니다.

해야할 것은 Custom Appication 클래스를 지정하는 것과, meta-data의 추가입니다.


<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="nnoco.example.sugar"

    android:versionCode="1"

    android:versionName="1.0" >


    <uses-sdk

        android:minSdkVersion="8"

        android:targetSdkVersion="15" />


<!-- Custom Application 지정 -->

    <application

        android:name="com.orm.SugarApp"

        android:icon="@drawable/ic_launcher"

        android:label="@string/app_name"

        android:theme="@style/AppTheme" >

        <activity

            android:name=".MainActivity"

            android:label="@string/title_activity_main" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />


                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

        <!-- 메타 데이터 추가 -->

        <meta-data android:name="DATABASE" android:value="sugar_example.db"/>

        <meta-data android:name="VERSION" android:value="2"/>

        <meta-data android:name="QUERY_LOG" android:value="true"/>

        <meta-data android:name="DOMAIN_PACKAGE_NAME" 

                      android:value="nnoco.example.sugar"/> <!-- 본인의 패키지로 설정-->

    </application>


</manifest>



저렇게 두 부분만 (라인으로는 5줄!) 추가하면 사용할 준비는 끝입니다.

이제 사용 해보도록 하겠습니다. 만들고자 하는 엔티티는 다음과 같습니다.



간단합니다. 책 엔티티로 제목(title)과 저자(author)를 가지고 있습니다.

여기에 맞는 모델 클래스를 만들어야겠죠? 원하는 위치에 Book 클래스를 하나 만들어보도록 하겠습니다.

모델 클래스를 만들 때 Sugar ORM을 사용하면 하나의 제약사항이 있습니다. SugarRecord를 상속해서 구현해야 한다는 점과, Context를 매개변수로 받는 생성자가 필요합니다. 사실 SugarRecord를 상속 받는 건 제약이라기 보다 편의쪽에 가깝다고 봐야겠네요.




public class Book extends SugarRecord {

private String title;

private String author;


public Book(Context context) {

super(context);

}


public Book(Context context, String title, String author) {

super(context);

this.title = title;

this.author = author;

}


public String getTitle() {

return title;

}


public void setTitle(String title) {

this.title = title;

}


public String getAuthor() {

return author;

}


public void setAuthor(String author) {

this.author = author;

}

}


Context 객체를 받는 생성자와 title, author를 포함해서 받는 생성자, getter/setter를 만들어준 모습입니다.



여기까지 되었으면 메인 액티비티로 이동해서 onCreate 메서드에서 간단하게 테스트를 해보도록 하겠습니다.

// Book 인스턴스 생성

Book book = new Book(this, "논어", "공자");

        

// DB에 저장

book.save();

        

// 수정

book.setTitle("論語");

book.save();

        

// DB로부터 ID 가져오기

long id = book.getId();

        

// ID를 이용해서 객체 가져오기

Book foundBook = Book.findById(Book.class, id);

        

// 로그로 확인

Log.d("Found Book Info", foundBook.getTitle() + ", " + foundBook.getAuthor());

        

// 삭제

book.delete();


api는 참 쉽죠? save()는 없다면 생성하고, 있다면 수정합니다. delete()는 삭제구요.

static 메서드에는 find(...), 나 findById(...)과 같이 select의 기능을 하는 메서드가 있고,

listAll(...) 테이블의 모든 레코드를 가져와 리스트로 반환하는 메서드,

deleteAll(...) 과 같이 테이블의 모들 레코드를 삭제하는 메서드가 있습니다.


이외에도 1:1 관계의 표현, 1:N관계의 표현등에 대한 내용이 있지만 간단하게 소개하는 글이므로 여기까지 쓰도록 하고

더 자세한 내용은 Sugar ORM의 위키를 통해 확인하실 수 있습니다.(http://satyan.github.com/sugar/getting-started.html)


레이아웃을 어느정도 만들어서 EditText 뷰에 속성값을 넣고 추가하고 리스트뷰에서 보여주고 하는 식으로 하려다가 단순한 기능 때문에 오히려 다른 부분(리스트뷰 어댑터 만들고, 이벤트 추가하는 등의)의 작업이 많아지면 의미없는 일이라고 생각하여 그 부분은 제외하였습니다. 

리스트 뷰를 사용하여 DB내용을 확인하는 예제는 첨부하여 올리니 확인하실 분들은 다운로드 받으셔서 확인하시면 됩니다.


Android ORM Test.zip





이클립스 안드로이드 레이아웃에 EditText 뷰만 넣으면 아래와 같은 예외가 발생하면서

java.util.LinkedHashMap.eldest()Ljava/util/Map$Entry;

Exception details are logged in Window > Show View > Error Log

GUI 화면에서는 제대로 레이아웃이 보이지 않는 문제가 발생했다.


모놀로그님의 글을 통해 1차적인 문제는 해결했으나, EditText에 멀티라인이 적용되지 않는 것이다.


scrollHorizontally="false" 속성을 추가해 보기도 하고

singleLine="false" 로 해보기도 하고

lines="5" 와 같이 line 수를 하드코딩도 해보았으나 모두 적용이 되지 않는다.


1차적인 해결 방법은 EditText에 inputType="textNoSuggestions" 속성을 추가하는 것이었고, 그로 인해 파싱 오류는 없어졌다. 허나 textNoSuggestion 속성값은 IME의 엔터키에 엔터를 완료로 바꾸는 것이라(좀 더 정확히는 사전 기반의 단어 추천을 하지 않게 하는 기능이나 외부적으로는 IME에 완료 버튼을 나타나게 함) EditText 뷰 자체가 싱글라인으로 인식이 된다.


혹시나 싶어 inputType 속성의 값을 textMultiline으로 하니 원래 지정한 대로 scrollHorizontally 속성으로 수평 방향 스크롤도 없어졌으며 Gson에서 pretty print로 얻은 JSON 스트링도 정상적으로 줄바꿈이 되어 표시가 된다.


그러나 파싱 오류는 다시 발생해서 레이아웃 확인을 Graphical Layout에서 확인할 수는 없게 되었다.

textNoSuggestions 말고 근본적인 파싱 오류의 원인을 찾아봐야겠다.





참고 : inputType 속성 값에 대한 특성은 서동규님의 블로그 글에서 확인할 수 있음

다른 분들이 쓰신 유용한 개발 팁을 링크해 둔 글입니다. 계속해서 업데이트 됩니다.



XML 레이아웃을 작성하다 보니 중복되는 내용도 많고, 한 파일에 너무 많은 코드가 들어가니 가독성도 떨어져서

XML 코드를 재사용할 수 있는 방법을 찾아봤다.


Include 태그를 활용하거나, styles.xml, themes.xml 파일을 활용하는 방법이 있다.


1. Include 태그 활용

Include 태그는 다른 XML 파일을 읽어와 레이아웃에 포함합니다. 자주 쓰는 XML 코드를 파일로 분리해두고

Include 태그를 이용하여 불러와 재사용할 수 있습니다.

제 경우에는 모든 액티비티의 상단에 타이틀 바를 두었는데, 이를 파일로 분리하고 불러와서 사용했습니다.

자세한 내용은 아래 링크를 참조하세요.

http://croute.me/435



2. Styles & Themes

values 폴더의 styles.xml파일과 themes.xml 파일에 스타일이나 테마를 추가하여 애트리뷰트에 대한 값을 지정하여 이를 스타일로 묶어 불러와 사용하는 방식입니다.

CSS에서 클래스를 정의해서 사용하는 것과 유사하다고 보시면 됩니다.

아래 링크는 안드로이드 개발자 사이트의 가이드입니다.

http://developer.android.com/guide/topics/ui/themes.html

안드로이드에서 EditText와 같은 뷰를 가진 액티비티가 시작되면 소프트 키보드가 항상 보이는 채로 시작된다.

키보드가 보이지 않는 채로 액티비티를 시작하고 싶다면

Activity를 상속받은 클래스에서 onResume 메서드를 아래와 같이 오버라이딩 한다.

@Override protected void onResume(){     super.onResume();     getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); }


만약 특정 순간(이벤트 처럼) 후에 키보드를 감추거나 보이게 할 때는 아래와 같이 하면 된다.

// InputMethodManager를 가져옴 InputMethodManager imm =     (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE); // 감출 때 imm.hideSoftInputFromWindow(ViewName.getWindowToken(), 0); // 보이게 할 때 imm.showSoftInput(ViewName, 0);



+ 2012.06.10 추가

애초에 포커스를 EditText로 주지 않는 방법도 있다.

해당 액티비티의 레이아웃 파일에서 레이아웃에 focusable, focusableInTouchMode 애트리뷰트를 추가하고

값을 true로 주고 requestFocus 태그를 추가한다.


<LinearLayout

        ... 다른 속성들

        android:focusable="true"

        android:focusableInTouchMode="true">

        <requestFocus/>


       ...

</LinearLayout>


이는 포커스를 가질 수 없는 레이아웃에 강제로 포커스를 가지게 하고 포커스를 줌으로써

EditText가 포커스를 가지지 않게하여 소프트 키보드를 보이지 않게 하는 방법이다.

+ Recent posts