2015. 5. 19. 04:03


오늘은 코드보다는 안드로이드 스튜디오에서 사용할 수 있는 유용한 plugin을 하나 소개하려고 한다. 예전에 안드로이드 라이브러리중 ButterKnife를 소개한 적이 있다. 오늘 소개할 plugin은 ButterKnife 라이브러리 사용시 같이 사용하면 코딩을 '두배'는 더 쉽고 편하게 하도록 도와준다. plugin의 이름은 'Android ButterKnife Zelezny'이다.


우선 플러그인을 다운 받기 위해서는, File -> Setting -> plugins 를 선택해도 되고 그냥 shift키를 두번누른 후 검색어에 'plugins'를 입력하면 아랫부분에 선택할 수 있다.plugins를 선택하면 아래와 같은 화면이 나온다.



이 화면에서는 검색해도 나오지 않고 아래쪽에 보이는 버튼 중 가운데 버튼인 'Browse repositories..'를 선택해야 한다.



선택하게되면 위와같은 화면이 나오는데 이때 검색 창에 'ButterKnife'를 입력하면 제일 위에 'Android ButterKnife Zelezny'가 뜨는 것을 확인할 수 있다. 초록색 Install plugin 버튼을 누르고 안드로이드 스튜디오를 재시작 하면 설치과정은 쉽게 끝이난다. 사용법도 간편하다.



위와 같이 레이아웃을 set해줄때, 해당레이아웃에 커서를 옮긴후 generate을 실행시켜주면 된다.(윈도우에서는 alt+insert, 맥에서는 command + n) 그러면 생성할 수 있는 여러 함수들 리스트가 나오는데 그중에 'generate butterKnife injection..'을 선택하면 된다.(버터나이프 마스코트가 왼편에 있다) 



그럼 위와 같은 화면이 뜨고 확인을 누르면,



ButterKnife를 통해 내가 레이아웃에 생성해둔 뷰들이 한번에 inject되는것을 볼 수 있다.


알아둘 점은 레이아웃에 생성한 모든 뷰를 inject하는 것이 아니라 그 중 id값을 추가해 준 뷰들만 inject한다. 또한 adapter에서 레이아웃을 inflate할때, 아래쪽에 있는 'create ViewHolder' 에 체크하면 ViewHolder Pattern에 사용하는 ViewHolder도 한번에 만들어 준다. 꼭 사용해서 조금 더 편한 코딩을 하길 바란다.

Posted by 미뤽
2015. 1. 27. 01:20




[Android pattern 02] 에서의 안정성과 [Android pattern 03] 에서의 가독성을 결합한 새로운 대안이 바로 빌더 패턴이다. 필수 인자를 포함한 빌더객체를 생성하고 선택인자들을 빌더객체에 추가완료한 후에 빌더객체를 통해 원하는 생성자의 객체를 생성한다. 이렇게 하여 만든 생성자의 객체는 변경 불가능(immutable) 객체이다. 설명만 들어서는 이해하기 힘드니 코드를 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
public class NutritionFacts {
    private final int servingSize;                    // 필수
    private final int servings;                         // 필수
    private final int calories;                          // 선택
    private final int fat;                                  // 선택
    private final int sodium;                           // 선택
    private final int carbohydrate;                 // 선택
 
    public static class Builder {
        //    필수 인자
        private final int servingSize;
        private final int servings;    
        //    선택적 인자
        private int calories = 0;                    //기본값으로 초기화
        private int fat = 0;    
        private int sodium = 0;
        private int carbohydrate = 0;
 
        public Builder ( int servingSize, int servings ) {
            this.servingSize = servingSize;
            this.servings = servings;
        }
 
        public Builder calories( int val ) { 
            calories = val;
            return this;
        }
 
        public Builder fat( int val ) { 
            fat = val;
            return this;
        }
 
        public Builder sodium( int val ) { 
            sodium = val;
            return this;
        }
 
        public Builder carbohydrate( int val ) { 
            carbohydrate = val;
            return this;
        }
 
        public NutritionFacts build() {
            return new NutritionFacts( this );
        }
    }
 
    private NutritionFacts( Builder builder ) { 
        servingSize = builder.servingSize;
        servings = builder.servings;
        calories = builder.calories;
        fat = builder.fat;
        sodium = builder.sodium;
        carbohydrate = builder.carbohydrate;
    }
}
cs


NutritionFacts  의 객체는 변경불가능 하고 빌더에 정의되어 있는 setter 메소드들은 빌더 객체 자신을 반환하므로, 설정메소드를 호출하는 코드는 죽 이어서 쓸 수 있다.


1
2
3
4
5
6
7
8
 
NutritionFacts cocaCola = 
        new NutritionFacts().Builder( 2408)
                        .calories( 100 )
                        .sodium ( 35 )
                        .carbohydrate( 27 )
                        .build();
 
cs


빌더패턴은 코드의 가독성도 뛰어나고 사용하기도 쉽다.다만 코드 사용량이 점층적 생성자 패턴 등보다는 많으므로 인자가 충분히 많은 상황에서 이용해야하는게 옳다. 물론 새로운 인자가 추가될 수도 있다는 것을 고려해서 사용하는 방법도 괜찮다.


<참고서적 : Effective Java 2/E>

Posted by 미뤽
2015. 1. 26. 22:19



생성자에 전달하는 인자수가 많은 때 점층적 생성자 패턴과 함께 적용가능한 두번째 패턴은 자바빈 패턴이다.인자가 없는 생성자를 통해 객체를 만든 후, 설정 메서드들을 호출하여 필수, 선택 필드들을 채우는 방법이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class NutritionFacts {
    private int servingSize = -1;              // 필수
    private int servings = -1;                   // 필수
    private int calories = 0;                     // 선택
    private int fat = 0;                             // 선택
    private int sodium = 0;                      // 선택
    private int carbohydrate = 0;             // 선택
 
    public NutritionFacts() { }
 
    //    setter
    public void setServingSize( int val ) { servingSize = val; }
    public void setServings( int val ) { servings = val; }
    public void setCalories( int val ) { calories = val; }
    public void setFat( int val ) { fat = val; }
    public void setSodium( int val ) { sodium = val; }
    public void setCarbohydrate( int val ) { carbohydrate = val; }
}
cs



위 코드를 통해 생성자 객체를 생성하고 인자를 넣는 방법은


1
2
3
4
5
6
NutritionFacts cocaCola = new NutritionFacts();
cocaCola.setServingSize( 240 );
cocaCola.setServings( 8 );
cocaCola.setCalories( 100 );
cocaCola.setSodium( 35 );
cocaCola.setCarbohydrate( 27 );
cs


이 패턴은 점층적 생성자 패턴과는 다르게 읽기도 쉽고 생성하기도 좋다. 하지만 이 패턴에는 두가지 심각한 단점이 있는데, 첫번째는 1회의 함수 호출로 객체 생성을 끝낼 수 없으므로, 객체 일관성(consistency)이 일시적으로 깨질 수 있다는 점이고 두번재는 자바빈 패턴으로는 변경 불가능(immutable)클래스를 만들 수 없다는 것이다.


<참고서적 : Effective Java 2/E>


Posted by 미뤽
2015. 1. 25. 21:02




선택적 인자가 많은 객체를 만들때 '점층적 생성자 패턴'을 사용한다. 필수인자만 받는 생성자 하나를 정의하고, 선택적 인자를 하나 받는 생성자를 추가하고, 선택적 인자를 두개받는 생성자를 하나 추가하는 식으로 점층적으로 인자의 갯수를 늘리는 식의 패턴이다. 예제 코드를 살펴보면,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class NutiritionFacts {
    private final int servingSize;            // 필수
    private final int servings;                    // 필수
    private final int calories;                    // 선택
    private final int fat;                            // 선택
    private final int sodium;                    // 선택
    private final int carbohydrate;            // 선택
 
    public NutiritionFacts ( int servingSize, int servings ) {
        this( servingSize, servings, 0 );
    }
 
    public NutiritionFacts ( int servingSize, int servings, int calories ) {
        this( servingSize, servings, calories, 0 );
    }
 
    public NutiritionFacts ( int servingSize, int servings, int calories, int fat ) {
        this( servingSize, servings, calories, fat, 0 );
    }
 
    public NutiritionFacts ( int servingSize, int servings, int calories, int fat,
                            int sodium) {
        this( servingSize, servings, calories, fat, sodium, 0 );
    }
 
    public NutiritionFacts ( int servingSize, int servings, int calories, int fat,
                            int sodium, int carbohydrate ) {
        this( servingSize, servings, calories, fat, sodium, carbohydrate );
    }
}
cs


필수 항목을 제외하고는 설정하려는 인자를 순서에 맞게 호출하면 된다.

1
2
NutiritionFacts cocaCola = new NutiritionFacts(240810033527);
 
cs


이런 패턴의 문제점이 몇가지 있는데, 설정할 필요가 없는 필드에도 인자를 전달해야 하는 점과(예제에서는 0으로 전달해야한다.) 인자 수가 늘어나면 이 인자가 무슨 인자인지 저 인자가 무슨 인자인지 알 수 없는 경우가 생긴다.


<참고서적 : Effective Java 2/E>

Posted by 미뤽
2015. 1. 17. 21:56




 저번 글에서 ViewHolder에 대해서 구글링 하던중 흥미로운 글들을 몇개 발견해서 ViewHolder 2탄을 준비했다. 2탄의 주제는 'ViewHolder보다 더 효과적인 방법' 이다. 세가지 방법에 대해서 살펴볼 생각인데 '사실 확실히 좋다!'는 확신은 없다는 점을 알아주었으면 한다. ViewHolder패턴이 좋다고 생각한 이유도 기실 'findViewById()가 상당히 부담이 되는 작업이다' 라고 전문가가 말한것을 그대로 수용했기 때문이지 내가 이해했던 것이 아니기 때문이다.(그정도의 깊이가 아직은 없다. 아직은) 그럼 하나씩 살펴보도록 하겠다.


관련 글 링크 : http://www.kmshack.kr/android-%EC%9C%A0%EC%97%B0%EC%84%B1-%EC%9E%88%EB%8A%94-viewholder-pattern/


위 글에서 말하는 첫번째 방법은 ViewHolder에 유연성 있는 코딩을 말한다. 기존 ViewHolder패턴에서는 static class로 만들고 해당 아이템(ListView의 한 줄 )에서 사용할 View들을 ViewHolder의 멤버변수로 저장한다. 이렇게 되면 여러아이템이 다른 View들을 가지고 있다면 여러개의 ViewHolder를 만들어야하므로 이럴때 사용할 수 있는 유연성있는 ViewHolder가 필요하다는 말이다.(이런 경우가 많지는 않을거 같지만.) 방법을 살펴보면,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static class ViewHolder {
    public static <T extends View> T get( View view , int id ) {
        SparseArray<View> viewHolder = ( SparseArray<View> ) view.getTag();
        if( viewHolder == null) {
            viewHolder = new SparseArray<View>();
            view.setTag( viewHolder );
        }
        View childView = viewHolder.get( id );
        if ( childView == null ) {
            childView = view.findViewById( id );
            viewHolder.put( id, childView );
        }
        return (  T ) childView;
}
cs


위 코드에서 보다시피 더이상 ViewHolder에 맴버변수가 없다. 대신 ViewHolder class내부에 viewHolder SparseArray에 사용할 뷰들을 넣고 나중에 해당 뷰들을 꺼내 쓴다. getView()에서 사용방법은 아래코드와 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public View getView( int position, View convertView, ViewGroup parent ) {
    if ( convertView == null ) {
        convertView = LayoutInflater.from( context ).inflate( R.layout.list_item,
             parent, false );
    }
    ImageView imageView = ViewHolder.get( convertView, R.id.imageview );
    TextView textView = ViewHolder.get( convertView, R.id.textview );
 
    ListItem item = getItem( position );
    
    imageView.setImageResource( item.getImage() );
    textView.setTet( item.getText() ) ;
 
    return convertView;
}
cs





관련글 링크 : http://www.jayway.com/2013/11/06/viewholder-vs-holderview/


두번째 글에서 말하는 방법은, ListView의 각각의 Item에서 하는일을 그 뷰가 알아서 하도록 하는 방법이다. 성능적인 면에서의 차이는 잘 모르겠지만 ListItem에 사용할 각각의 Item들을 Adapter의 getView() 메소드에서 선언, 처리 하던 방식을 사용하지 않고, Adapter에서는 기존처럼 findViewById()의 사용만 피하면서 해당 아이템의 holderViewclass 객체를 불러오면 View의 선언이나 사용은 전부 holderView클래스 내부에서 처리하는 방법이다. 사용법을 살펴보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
@Override
public View getView(int i, View convertView, ViewGroup viewGroup) {
    HolderView holderView;
    // Important to not just null check, but rather to a instanceof
    // since we might get any subclass of view here.
    if (convertView instanceof HolderView) {
        holderView = (HolderView) convertView;
    } else {
        holderView = new HolderView(mContext);
    }
    holderView.bind(new Digit(i));
 
    return holderView;
}
cs


Adapter내의 getView()메소드는 정말 간단하다. holderView객체를 찾거나 생성한후 데이터를 보낸다. 객체 내부를 살펴보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 
public class HolderView extends GridLayout {
 
    private TextView mDigitDigit;
    private String mDigitText;
 
    public HolderView(Context context, AttributeSet attrs) {
        super(context, attrs);
        View v = LayoutInflater.from(context).inflate(R.layout.list_detail, this);
        mDigitDigit = (TextView) v.findViewById(R.id.list_detail_digit);
        mDigitText = context.getResources().getString(R.string.list_detail_digit);
    }
 
    public void bind(Digit digit) {
        mDigitDigit.setText(String.format(mDigitText, digit));
    }
}
cs


객체가 생성되는 순가 해당아이템에서 사용할 View들을 찾아 연결해주고 bind()메소드를 통해서 들어오는 값을 HolderView내부의 View와 연결해서 Ui에 나타낸다. 직관적인 코드라서 관리가 더 용이하고 코드를 이해하기도 좋다고 한다(?)




관련글 링크 : http://www.slideshare.net/bs_yagi/potato01-no-more-viewholder


위 링크에서 소개하는 마지막 방법은 두번째 방법과 흡사하다. Adapter내부에서 하던 View를 연결하는 작업을 ViewHolder내부에서 객체 생성시에 한다. 비슷한 방법이지만 소개하는 이유는 몇몇 특징 때문인데 한번 살펴보면


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ViewHolder {
    public final ImageView thumbImage;
    public final TextView titleText;
    public final TextView readText;
    public final TextView userText;
    public final View root;
 
    public ViewHolder( View root ) {
        thumbImage = ( ImageView ) root.findViewById( R.id.thumb_image ) ;
        titleText = ( ImageView ) root.findViewById( R.id.title_text ) ;
        readText = ( ImageView ) root.findViewById( R.id.read_text ) ;
        userText = ( ImageView ) root.findViewById( R.id.user_text ) ;
        this.root = root;
    }
}
cs


1
2
3
4
5
6
7
8
@Override
public View getView( int position, View convertView, ViewGroup parent ) {
    if ( convertView == null ) {
        convertView = LayoutInflater.inflate(R.layout.list_view_item, null );
        ViewHolder holder = new ViewHolder ( convertView );
        convertView.setTag( holder );
    }
}
cs


코드를 보면 다른 코드들과는 다른 2가지를 볼 수 있는데 첫번째가 ViewHolder class가 static class가 아니라는 점과 멤버변수들을 final로 선언했다는 점이다. 기존 사용법에서 ViewHolder를 static으로 선언한 이유는 enclosing instance에 연결되는 경우를 막기위해서 인데 내부클래스로 ViewHolder를 하면 enclosing instance에러는 나지 않는다. 두번째 다른점인 final로 선언한 이유는 사실 아직 정확히 이해하지 못했다...(해당사이트 내용들이 일본말인것도 한 이유...) 우선 final에 대해서 다시 한번 처음부터 공부해봐야겠다.

Posted by 미뤽
2015. 1. 15. 02:21



안드로이드를 개발하면서 가장 많이 사용하는 기능 중 하나가 ListView라고 생각한다. ListView를 사용할 때 잘 모르는 상태에서 무의식 적으로 따라 사용하던 패턴이 있었는데 바로 ViewHolder 패턴이다. (사실 패턴인지도 몰랐었다.) 안드로이드를 처음 접했을때는 Adapter라는 개념도 생소했기 때문에 구현하는 방식의 일부라는 생각에 무의식 적으로 사용해 왔던 것 같다. 이번에 기회가 되서 ViewHolder pattern에 대해 조금 알아보기로 했다. 그러기 위해서는 ListView에 대해서 먼저 알아보자.


ListView는 이름 그대로 내가 보여주고자 하는 데이터들의 리스트를 보여주는 View이다. 하지만 내가 보여주고자 하는 100개, 1000개의 데이터들을 hierarchy에 올려놓고 순서대로 보여주려다가는 느려서 스크롤도 하기 힘들것이다. 이를 해결해 주기 위해서 ListView는 getView()라는 녀석을 사용한다. 정확히는 getView() 메소드에서 사용하는 convertView를 사용한다. convertView는 ListView를 효과적으로 사용하기 위해서 1) adapter에 여러 타입의 data들이 있을때 자동으로 convertView는 해당 타입이 될 것이고 2) 재사용을 할 수 있다. 세가지의 예를 한번 살펴보자.


첫번째는 "느린 방법"이다.

1
2
3
4
5
6
7
8
9
public View getView(int position, View convertView, ViewGroup parent) {
    View item = mInflater.inflate(R.layout.list_item, null);
 
    ((TextView) item.findViewById(R.id.text)).setText(DATA[position]);
    ((ImageView) item.findViewById(R.id.icon)).setImageBitmap(
                (position & 1 ) == 1 ? mIcon1 : mIcon2);
 
    return item;
}
cs


이 방법은 getView()가 리스트의 하나의 아이템을 나타낸다는 것을 알았을때 구현하는 가장 기본적인 방법이다. 이 방법은 getView()가 호출될 때 마다 새로운 View를 생성한다. 또한, 2행에서 XML을 inflating하는 작업은 안드로이드에서 무거운 작업이다. 특히나 위의 코드 예제와 같이 ImageView를 사용할 경우 만약 100개, 1000개의 리스트 아이템을 보여줄 경우 그만큼의 infalting작업과 View 생성 작업을 해야 한다는 단점이 있다.(하나의 view는 Ram에서 1kilobytes,2kilobytes를 차지한다.) 아이템이 많아질 수록 앱의 동작은 느려지거나 끊기게 된다. 이를 고치기 위해서는 convertView를 사용하면 된다.


두번째 "올바른 방법"

1
2
3
4
5
6
7
8
9
10
11
12
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.list_item, parent, false);
    }
 
    ((TextView) convertView.findViewById(R.id.text)).setText(DATA[position]);
    ((ImageView) convertView.findViewById(R.id.icon)).setImageBitmap(
                (position & 1 ) == 1 ? mIcon1 : mIcon2);
 
    return convertView;
}
 
cs


아까 위에서 언급했듯이 convertView는 재사용이 가능하도록 설계되어있기 때문에 infalating작업을 했던 아이템들에 대해서는 null체크를 통해 해당 작업을 생략하는 방법을 사용한다. 이 방법을 사용하면 아이템의 총 갯수에 관계없이 화면에 맞는 갯수의 아이템(view)을 ListView가 할당한다.(1,2개 더 할 수도 있다.) 즉, 100개의 리스트 아이템때문에 느려질 걱정은 하지 않아도 된다. 마지막으로 살펴볼 방법이 오늘 내가 이야기 하고자 하는 ViewHolder를 이용하는 방법이다.


마지막 "빠른 방법"


ViewHolder pattern은 작은 data저장소를 만들어 각각 리스트 아이템들을 static으로 연결하는 것이다. 예제를 보면 두번째 방법을 통해 inflating하는 작업은 줄였지만 TextView와 ImageView를 가져오기 위해 매번 findViewById 작업을 반복하고 있다. ViewHolder pattern을 사용하면 한번 연결한 데이터는 저장소에 저장하여 다시 findViewById를 통해 view를 찾아올 필요가 없다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
 
    if (convertView == null) {
        convertView = mInflater.inflate(R.layout.list_item, parent, false);
        holder = new ViewHolder();
        holder.text = (TextView) convertView.findViewById(R.id.text);
        holder.icon = (ImageView) convertView.findViewById(R.id.icon);
 
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
 
    holder.text.setText(DATA[position]);
    holder.icon.setImageBitmap(
                (position & 1 ) == 1 ? mIcon1 : mIcon2);
 
    return convertView;
}
cs


ViewHold는 필요한 곳에서 생성해 주면 된다.


1
2
3
4
5
static class ViewHolder {
    TextView text;
    ImageView icon;
}
 
cs



참고자료 - Google I/O 2010 - The world of ListView


Posted by 미뤽
2015. 1. 13. 22:33



여러 View 들을 inject할때 도움을 주는 Butter knife 라이브러리


 안드로이드를 공부 할 수록 존경하게 되는 와튼 형님이 만든 라이브러리지만 사실 Butter knife를 사용해야 하는 필요성을 못 느끼고 있었다. 무조건 오픈소스 이용해서 쉽게, 쉽게 코딩하지말고 원리는 알고 이용하라는 선배들의 글을 본 후 더욱 이런 라이브러리는 게으른 사람들만 이용한다고 생각했다. 하지만 어느 순간 내가 Activity 에서 View를 적용할때 마다 불필요한 '노가다 코딩'을 반복한다는 느낌이 들었다. 예를 들어,


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
 
private void setUI() {
    mButtonBack = (Button) findViewById(R.id.btn_back);
    mButtonHome = (Button) findViewById(R.id.btn_home);
    mButtonEnter = (Button) findViewById(R.id.btn_enter);
    mButtonRightArrow = (Button) findViewById(R.id.btn_right_arrow);
    mButtonLeftArrow = (Button) findViewById(R.id.btn_left_arrow);
    mButtonUpArrow = (Button) findViewById(R.id.btn_up_arrow);
    mButtonDownArrow = (Button) findViewById(R.id.btn_down_arrow);
}
 
private void setEventListener() {
    mButtonBack.setOnClickListener(clickListener);
    mButtonHome.setOnClickListener(clickListener);
    mButtonEnter.setOnClickListener(clickListener);
    mButtonRightArrow.setOnClickListener(clickListener);
    mButtonLeftArrow.setOnClickListener(clickListener);
    mButtonUpArrow.setOnClickListener(clickListener);
    mButtonDownArrow.setOnClickListener(clickListener);
}
 
cs

위의 코드와 같이 View(특히 Button)를 사용하려면 멤버변수로 선언하고 setUI()메소드를 통해 Id에 맞는 View들을 연결해주고 다시 setEventListerner()라는 메소드를 이용해서 리스너들과 연결해서 사용해왔다. 멤버변수 선언을 포함하면 내가 하나의 뷰를 사용하기 위해서는 세줄의 코드를 사용하고 있었다. 코드 줄 수가 많아서 무조건 적으로 싫다기 보다는 단순한 코드의 긴 반복이 점점 거슬리기 시작했다. 사족은 이쯤에서 줄이고, 위와 같은 코드를 Butter knife를 사용하면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 
@InjectView(R.id.btn_back) Button mButtonBack; 
@InjectView(R.id.btn_home) Button mButtonHome; 
@InjectView(R.id.btn_enter) Button mButtonEnter; 
@InjectView(R.id.btn_right_arrow) Button mButtonRightArrow; 
@InjectView(R.id.btn_left_arrow) Button mButtonLeftArrow; 
@InjectView(R.id.btn_up_arrow) Button mButtonUpArrow; 
@InjectView(R.id.btn_down_arrow) Button mButtonDownArrow 
 
 
private void setEventListener() {
    mButtonBack.setOnClickListener(clickListener);
    mButtonHome.setOnClickListener(clickListener);
    mButtonEnter.setOnClickListener(clickListener);
    mButtonRightArrow.setOnClickListener(clickListener);
    mButtonLeftArrow.setOnClickListener(clickListener);
    mButtonUpArrow.setOnClickListener(clickListener);
    mButtonDownArrow.setOnClickListener(clickListener);
}
 
cs


위의 코드와 같이 멤버변수를 선언과 동시에 해당 View를 연결할 수 있다. 나아가서 만약 Butter knife에서 제공하는 @OnClick 어노테이션을 사용하면 리스너 injection도 가능한데 이를 이용하면 멤버변수 선언을 포함한 위의 모든 코드 자체를 사용할 필요가 없고


1
2
3
4
5
6
7
8
9
10
11
 
@OnClick({R.id.btn_back,R.id.btn_home,R.id.btn_enter,
        R.id.btn_right_arrow,R.id.btn_left_arrow,
        R.id.btn_up_arrow, R.id.btn_down_arrow})
public void onButtonClick(View view) {
    switch (view.getId()) {            
            case R.id.btn_back:
            ...
    }
}
 
cs

 

위의 코드와 같이 바로 해당 클릭 리스너에 대한 구현만 해주면 된다.이런 방법은 코딩작업의 편의성도 좋지만 가독성 면에서도 나은 방법이라고 생각한다.






그럼 Butter Knife 라이브러리에 대한 사용 방법에 대해서 알아보자. 우선 설치법은, 이클립스 사용자일 경우

 

http://jakewharton.github.io/butterknife/ 


위의 사이트에서 JAR 파일을 다운받은후 적용을 하고 안드로이드 스튜디오 사용자들은 app내에 build.gradle의 dependencies 에


compile 'com.jakewharton:butterknife:6.0.0'

위의 컴파일 주소만 추가해 주면 된다. 라이브러리 설치가 끝났으면 사용법에 대해서 알아보자. 우선 Activity를 상속 받았을 경우 

1
2
3
4
5
6
7
@Override 
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.simple_activity);
    ButterKnife.inject(this);
    // TODO Use "injected" views...
}
cs


위와 같이 onCreate()에서 ButterKnife.inject(this)라는 명령을 통해 inject할 수 있다. 만약 activity가 아닌 fragment 라면


1
2
3
4
5
6
7
8
9
10
 
@Override 
public View onCreateView(LayoutInflater inflater, ViewGroup container, 
            Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fancy_fragment, container, false);
    ButterKnife.inject(this, view);
    // TODO Use "injected" views...    
    return view;
}
 
cs


위와 같이 onCreateView()에서 ButterKnife.inject(this, view)라는 명령어를 통해 inject한다. 또한 adapter에서 ViewHolder pattern 을 사용할때에도


1
2
3
4
5
6
7
8
static class ViewHolder {
    @InjectView(R.id.title) TextView name;
    @InjectView(R.id.job_title) TextView jobTitle;
 
    public ViewHolder(View view) {
      ButterKnife.inject(this, view);
    }
}
cs


처럼 사용할 수 있다.apply()메소드에 대해서는 다음에 기회가 되면 추가적으로 살펴보기로 하고 리스너 injection을 살펴보면 아까 위에서 예제에서 보여준것 처럼 @OnClick()어노테이션으로 injection을 할 때 {}안에 여러개의 id를 동시에 작용하게 할 수도 있지만


1
2
3
4
5
@OnClick(R.id.enter)
public void onButtonClick() {
  // TODO 
}
 
cs


처럼 하나의 id에서만 적용할 수 있다. 여기서 참고할 사항은 메소드의 이름은 내가 정해 줄 수 있지만 만약 메소드에 Parameter값을 넣을 경우 반드시 해당 id가 속한 view 의 parameter가 필요하다. 예를 들어 위의 코드에서 R.id.enter는 버튼의 id인데 메소드 onButtonClick(ImageView img)와 같이 정의하면 런타임시 에러가 발생한다.

Posted by 미뤽
2015. 1. 13. 00:15

안드로이드 앱을 개발하다보면 다중언어(로컬라이징)를 지원해야 하는 경우가 있다. 그럴경우 안드로이드에서 지원하는 방식으로 resource폴더를 관리하면 예상외로 쉽게 개발할 수 있다. (res/values-ja, res/values-en, res/drawable-ko 등등 )


일단 이렇게 개발하고 나면 앱이 실행될때에 핸드폰의 언어설정에 따른 resource를 자동으로 앱이 파악한 후 적용하게 된다.


하지만 만약 내가 개발하고 있는 앱 내부에서 언어를 바꾸고자 할때(예를 들면 앱 내에 있는 설정부분에서) 굳이 핸드폰 자체의 언어설정을 가지 않고 동적으로 바꿔줄 수 있는 방법이 있었다.


1
2
3
4
5
6
7
 
public void changeConfigulation() {
    Locale mLocale = new Locale("language_code")
    Configuration config = new Configuration();
    config.locale = mLocale;
    getResources().updateConfiguration(config, null);
}
cs



위의 코드에서 new Locale("language_code")의 "language_code"에 내가 바꾸고자 하는 나라의 언어코드를 넣으면 적용이 된다. 

Posted by 미뤽