Friday, August 31, 2012

Android: failed to find style 'mapviewstyle' in current theme ?

It seems that the default style for MapView is specified with 'mapViewStyle', and the style 'mapViewStyle'  is defined  in Android public.xml file. But Android style 'mapViewStyle' is not defined in android styles.xml file. So I guess that the problem occurred.

So, I tried to solve that problem, as follows...

public.xml and styles.xml : You can find them in 'android-sdk/platforms/<android-api-level>/data/res/values folder.


1.GO-TO : android-sdk/platforms/ <android-api-level> /data/res/values 
2.In public.xml,  check the mapViewStyle's definition and id value.











3. In styles.xml,  insert 'mapViewStyle' item element as shown below.
(Note : 'mapViewStyle's id values' and insert point )



***  If you find anything that you think is wrong or unclear, please let me know by dropping a line in the comments section.



Sunday, August 26, 2012

Android Location Startegies



Location Strategies

이 문서는 안드로이드 개발자 노트의 일부를 내맘대로 번역한 것임을 밝힙니다.
출처:http://developer.android.com/guide/topics/location/strategies.html


휴대 단말기 사용자가 자신이 어디에 있는지를 알게하면 너님이 만든 애플리케이션이 더 똑똑해 보이기도 하고 사용자에게 더 나은 정보를 전달할 수도 있지. 위치 정보를 다루는 애플리케이션을 안드로이드에서개발할 때 GPS나 네트워크 위치 제공자(Android's Network Location Provider)를 이용할 수가 있는데, GPS가 더 정확하지만 GPS는 밖에 나가야만 사용할 수가 있어.  게다가 GPS는 배터리를 무쟈게 소모시킬 뿐더러 사용자가 원하는 만큼 재빠르게 위치 정보를 넘겨주지도 않아.

그에 반해, Android's Network Location Provider는 무선 통신 타워나 Wi-Fi 신호를 이용해서 위치를 잡기 때문에 건물 내부에서나 외부에서나 모두 사용할 수가 있고, 반응 속도도 빠르고, 배터리 소모도 적어.

아무튼 사용자의 위치정보를 얻기위해서 위에 언급한 두 가지를 모두 사용할 수도 있고, 그냥 하나만 선택해서 사용할 수도 있다는 건 기억해두자.


Challenges in Determining User Location


휴대 단말기로부터 사용자의 위치정보를 얻어오는건 어찌보면 복잡한 고민이 따르는 일이라고 봐. 왜냐면, 개발자인 너님을 고민에 빠지게 하는 원인들이 많거든.  정보를 받지 못할 수도 있고 기껏 받아온 게 정확하지 않은 정보라면 난감하지 않겠어?

그럼 그런 상황을 초래 하는 이유가 뭐냐. 아래와 같이 정리해 볼 수 있지.

위치 정보를 제공하는 소스가 많다 : GPS, Cell-ID, Wi-Fi가 각각 사용자 위치를 가늠할 수 있는 정보를 주는데 어떤 정보를 사용할지 또 어떤 것이 믿을 만한 정보인지를 결정하는 건  정확도, 속도 배터리에는 어떤 영향을 주는지 사이에서 고민한 후에 결정할 것이라서 그래.

사용자는 계속해서 움직인다 :  사용자는 계속 해서 움직이기 때문에, 개발자인 너님은 이런 상황을 가정하고, 계속해서 반복적으로 자주 다시 계산해야하거든.

정확함이라는 게 뭔지 : 각각의 위치정보 제공자에게 받아온 위치 정보들이라는 게 말이야. 그 제공자들 각자가 원래 가지고 있는 정확도라는게 모두 달라. 일 분 전에 A라는 소스에서 받아온 정보가 방금 B라는 곳에서 받아온 정보보다 더 정확할 수 있다는 거야.

이러한 이유로 사용자에게 신뢰를 줄만한 위치 정보를 얻는다는게 아주 힘들지. 이 문서에서는 그러한 고민을 해결하기 위한 것이야. '어떻게 해야 그나마 신뢰할만한 위치정보를 얻을 수 있을까'에 대한 답이랄까? 너님이 개발하는 앱의 사용자에게 좀 더 정확한 위치정보를 재빠르고 정확하게 제공하려고 하는 너님의 고민에 좋은 아이디어가 되길 바라.


Requesting Location Updates


위에 언급한 문제들을 해결하기 전에 위치정보를 획득하는 방법에 대해서 먼저 알아야겠지? 안다면 다음 소제목으로 패스.

안드로이드에서 사용자 위치정보를 얻는 건 콜백(callback)에 의해 이루어져. LocationManager 클래스의 requestLocationUpdates() 메소드를 호출하면서 '나 위치정보를 업데이트 하고 싶어요'라고 알릴 수 있고, 그럼 업데이트된 위치정보를 LocationListener 에게 전달하지. 이 LocationListener는 너님이 구현해줘야하는 부분인데, 이 인터페이스에는 미리 정의된 몇 개의 콜백 메소드가 있어서 사용자 위치가 변화했을 때나 서비스 상태가 변화할 때 마다 Location Manager가 그 메소드들을 호출하거든.

아래 예제 코드를 봐.
   // 시스템의 위치관리자(Location Manager)에 대한 레퍼런스를 획득한다
   LocationManager locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);

   // 위치정보 업데이트시 반응할 리스너를 정의한다
   LocationListener locationListener = new LocationListener() 
   {
        public void onLocationChanged(Location location) {
             // 네트워크 위치정보 제공자가 새로운 위치정보를 찾게되면 호출되는 메소드임
              makeUseOfNewLocation(location);
        }
        public void onStatusChanged(String provider, int status, Bundle extras) {}
        public void onProviderEnabled(String provider) {}
        public void onProviderDisabled(String provider) {}
   };

   // Location Manager에게 리스너를 등록해준다
   locationManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, locationListener);
requestLocationUpdate() 메소드의 첫 번째 파라메터는 위치 제공자의 타입이야. 여기서는 무선 통신 타워나 Wi-Fi 기반의 위치 정보를 위한 Network Location Provider로 제공자를 설정한 거지. 두 번째와 세 번째 파라메터는 위치 정보 전달을 위한 최소시간과 최소 거리를 의미해. 여기서는 모두 0으로 설정했으니 가능한 자주 알려달라는 거지. 마지막으로 네 번째 파라메터는 너님이 구현한 Listener 객체를 알려달라는 거고.

GPS 위치 정보 제공자로부터 받고 싶다면 NETWORK_PROVIDER를 GPS_PROVIDER로 바꾸면 되고, 양쪽 모두에서 받고 싶다면 requestLocationUpdates()를 각각에 대해 총 두 번 호출해주면 되지. (물론 LoationManager의 getAllProviders()메소드를 이용해서 모든 provider 정보를 탐색한 후에 몇 몇 조건을 따져서 그 중 가장 나은 하나를 선택할 수도 있어. 이 부분은 이 블로그의 다른 포스팅을 참고해바바바)

Requesting User Permissions


NETWORK_PROVIDER든 GPS_PROVIDER든 위치 정보를 업데이트 받고 싶다면, ACCESS_COARSE_LOCATION이나 ACCESS_FINE_LOCATION 권한을 안드로이드 메니페스트 파일에 추가해 줘야해. 권한이 없으면 위치 정보 업데이트를 요청할 때 멈추게 될끄야.

<manifest ... >
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    ...
</manifest>
그리고, NETWORK_PROVIDER와  GPS_PROVIDER 모두를 사용하려 한다면 ACCESS_FINE_LOCATION 권한 하나만 추가해줘도 돼. 왜냐면, 요게 두 종류 PROVIDER에 대한 권한 모두를 포함하고 있거든, ACCESS_COARSE_LOCATION은 NETWORK_PROVIDER에 대한 권한만을 가지고 있고.


Defining a Model for the Best Performance


위치 기반 애플리케이션이라는 게 이제 뭐 흔하디 흔하지만, " 떨어지는 정확도, 사용자의 잦은 이동, 너무도 다양한 위치 정보 소스, 그 놈의 배터리"  이런 문제거리들을 모두 해결하면서 애플리케이션을 작성하는 게 그리 만만치는 않지. 배터리 소모를 최소화 하면서 사용자가 만족할만한 위치정보를 얻어오려면 먼저 너님은 '위치 정보를 얻는 방법에 대한 효과적인 모델'을 먼저 정의해야 해.  너가 작성한 LocationListener가 허구헌 날 계속 대기모드로 업데이트를 기다린다고 생각해봐. 소개팅한 여자를 미리 수소문해서 예약해 놓은 맛집에 데려가기도 전에 너님의 스마트폰은 방전되어 버릴테고 결국 근처 김밥헤븐에서 다 말라비틀어진 김밥을 먹은 후 그 소개팅녀의 스맛폰에서는 너님의 전화번호가 지워져 버릴거거든. 무섭지?

그래서, 그 모델에는 언제 업데이트에 대한 리스닝을 시작할지, 언제 끝낼지, 언제 수집된 위치정보를 사용할지에 대한 정의도 포함되어야 하지.



Flow for obtaining user location

자 이제 사용자 위치 정보를 획득하는 가장 일반적인 흐름을 볼까?

  1. 애플리케이션을 시작한다
  2. 잠시 뒤, 위치 정보 제공자들로 부터 업데이트 정보를 수신하도록 리스닝을 시작한다
  3. 새로운 위치 정보가 잡히더라도 덜 정확한 정보를 걸러내면서 최상의 위치 장보를 유지한다
  4. 위치 정보 업데이트에 대한 리스닝을 멈춘다
  5. 최상의 위치 정보로 추정되는 정보를 이용한다


아래 그림은 시간대별로, 애플리케이션이 업데이트에 귀기울이는  과정에서 일어나는 일들을 표현해 본 것이야. 자세히 보면 너님이 너님의 앱에서 위치 정보를 사용함에 있어  너님 스스로 결정해야할 시점이 다양하게 존재한다는 걸 볼 수가 있지.



















Deciding when to start listening for updates

애플리케이션이 시작하자마자 리스닝을 시작하게 하거나 아니면 사용자 선택한 특정 시점에만 시작하게 구현하고 싶을텐데, 하나 기억할 것은 위치 정보를 결정하기 꽤 긴 과정을 거치게 되면 그만큼 배터리 수명이 떨어져 버린다는 거다. 그렇다고 짧게 잡으면 좋으냐 그것도 아니지.. 정확한 위치 정보를 잡기가 힘들어 지거든.(도대체 어쩌라구)


Getting a fast fix with the last known location

그래서!! 맨 처음 위치 정보를 잡을 때까지의 시간이 꽤 길어질 수가 있으니까,  더 정확한 위치 정보가 리스너에 전달되기 전까지는 최근에 저장되어 있는 쓰면 어떠냐는 거지. 그런 정보도 getLastKnownLocation(String) 메소드로 알아낼 수가 있거든. 인자로 location provider를 알려주면 해당 제공자의 최근 위치를 가져오는 거지 ( API를 보면 알겠지만, 이 메소드는 단말기기 꺼진 상태에서 전혀 다른 장소로 이동한 상태라도, 꺼지기 이전에 저장된 정보를 알려주게 되므로 받은 정보가 전혀 쓸모 없는 정보일 수도 있다는 거야)

String locationProvider = LocationManager.NETWORK_PROVIDER;
// Or use LocationManager.GPS_PROVIDER

Location lastKnownLocation = locationManager.getLastKnownLocation(locationProvider);


Deciding when to stop listening for updates

획득한 위치 정보가 더 이상 필요없어 질 시점을 결정하는 로직도 역시 애플리케이션에 따라 아주 간단하기도 하고, 아주 복잡해지기도 하지. 중요한 건 위치 정보를 획득하는 시점과 그것이 사용되는 시점 간의 갭이 아주 작아야 추정한 위치 정보의 정확도도 올라간다는 거야.  오랫동안 리스닝을 하게 되면 배터리 소모가 심하다는 건 항상 기억하고 있지? 그래서 너님이 필요하다 생각했던 위치 정보를 얻었다면 리스닝하는 작업을 바로 끝내줘야 해. removeUpdates(PendingIntent) 메소드를 호출해서 말이야.

// Remove the listener you previously added
locationManager.removeUpdates(locationListener);


Maintaining a current best estimate

가장 최근에 얻어진 위치 정보가 가장 정확한 값이라고 판단하기가 쉽지만, 사실은 그렇지 않아. 수집된 위치 정보마다 그 정확도가 아주 다양하거든. 그렇기 때문에 가장 정확한 위치 정보를 선별하기 위한 로직을 가져야만 하지. 그런 선별에도 일반화된 기준이 있어야 하겠지? 물론, 애플리케이션에 따라 다르고 실제 필드에서 어떻게 테스트하느냐에 따라 달라지겠지만 말이야.

하지만, 아래와 같은 질문들을 해보며 위치정보의 정확도를 체크해보면 어때?

  • 가져온 위치 정보가 이전에 획득한 추정 위치값들에 비해  뚜렷하게 새로운 정보인가?
  • 위치 정보의 정확도가  이전에  작성된 값들에 비해서는 더 나은가? 아니면 떨어지는가?
  • 새로운 위치 정보를 보내온 제공자는 누구고 이게 이전것들보다 더 믿을만 한가?


위의 체크 사항들을 하나하나 로직으로 작성해 보면 아마도 아래의 소스코드와 같을 거야.

private static final int TWO_MINUTES = 1000 * 60 * 2;

/** 어떤 Location 정보가 현재 Location fix보다 더 나은 값인지를 결정하는 메소드
  * @param location  너님이 평가하고 싶어하는 위치 정보
  * @param currentBestLocation  현재의 Location fix, 새로운 값을 이 값과 비교할 것임
  */
protected boolean isBetterLocation(Location location, Location currentBestLocation) {
    if (currentBestLocation == null) {
        // 위치 정보가 없는 것보다는 있는 게 더 나으므로
        return true;
    }

    // 새로 들어온 위치 정보가 현재의 fix값보다 새것인지 오래된 것인지부터 검사
    long timeDelta = location.getTime() - currentBestLocation.getTime();
    boolean isSignificantlyNewer = timeDelta > TWO_MINUTES;
    boolean isSignificantlyOlder = timeDelta < -TWO_MINUTES;
    boolean isNewer = timeDelta > 0;

    // 새로 들어온 위치 정보가 현재 정보보다 2분 이상 더 새로운 것이라면 그냥 그걸 쓰자
    // 2분이면, 우사인 볼트의 경우 달나라까지 갔다올 시간이잖아.
    if (isSignificantlyNewer) {
        return true;
    // 위와 같은 이유로, 현재 정보보다 2분이나 더 오래된 정보라면 무시해도 될거 같아.
    } else if (isSignificantlyOlder) {
        return false;
    }

    // 이제는 새로운 정보가 현재의 정확도보다 더 나은건지 아닌지를 검사해보자
    int accuracyDelta = (int) (location.getAccuracy() - currentBestLocation.getAccuracy());
    boolean isLessAccurate = accuracyDelta > 0;
    boolean isMoreAccurate = accuracyDelta < 0;
    boolean isSignificantlyLessAccurate = accuracyDelta > 200;

    // 혹시, 동일한 Provider에서 제공한 정보인지도 알면 판단하기가 더 쉽겠지?
    boolean isFromSameProvider = isSameProvider(location.getProvider(),
            currentBestLocation.getProvider());

    // 두 정보를 시간과 정확도를 함께 비교하면서 판단해본다
    if (isMoreAccurate) {
        return true;
    } else if (isNewer && !isLessAccurate) {
        return true;
    } else if (isNewer && !isSignificantlyLessAccurate && isFromSameProvider) {
        return true;
    }
    return false;
}

/** 이 메소드는 인자로 넘겨진 두 개의 provider가 동일한 것인지를 판단한다  */
private boolean isSameProvider(String provider1, String provider2) {
    if (provider1 == null) {
      return provider2 == null;
    }
    return provider1.equals(provider2);
}


Adjusting the model to save battery and data exchange

간단한 애플리케이션을 만들어서 테스트 해보면, 위의 코드만으로는 뭔가 좀 부족하다 싶을 거야. 약간의 조정이 필요하지. 아래에 적어놓은 두 가지 상황을 비교해 가면서 두 요건들 사이에서 밸런스를 찾아가야 할거야.

위치 정보를 받아들일 범위를 좁힌다.
신호를 받아들이는 범위를 좁혀버리면 이전보다 더 적은 수의 GPS와 네트워크 위치 서비스 하고만 교신하면 될 뿐더러 배터리 수명도 유지하기가 편할거야. 하지만,  그만큼 좋은 정보를 찾기위한 리소스가 부족해지기도 하지

위치 제공자한테 더 적은 빈도로 업데이트 정보를 보내라 한다.
새로운 업데이트를 보내는 빈도를 줄여버리면 배터리 효율을 개선할 수가  있지만, 정확도 측면에서는 손해를 볼 수 밖에 없지. 배터리 효율이냐 정확도 사이에서의 판단은, 어떤 앱에서 어떻게 사용되느냐에 따라 많이 달라지겠네 그치? 업데이트 빈도를 줄이려면 requestLocationUpdates() 메소드의 interval time, minimun distance 값을 올려주면 돼.

Provider의 종류를 제한해버린다.
앱을 사용하는 환경에 따라 정확도를 요구하는 수준도 달라지잖아. 그런 상황에 맞춰서,  네트워크 위치 제공자나 GPS 둘 중 하나만을 쓸 수가 있을거야. 그런 서비스들 중 하나만 사용함으로써 (정확도 감소라는 비용을 지불해야할 상황이 있을 수도 있지만 - 반드시 수반되는 비용은 아니라는 의미로 이해해) 배터리 사용을 감소시킬 수가 있지.



Common application cases

너님이, 너님의 앱에서 위치 정보가 필요한 이유야  내 월급이 한달에 1억은 되어야하는 이유 만큼이나 많겠지만, 아래의 시나리오를 잘 참고한다면 아마도 왠만큼은 너님의 앱이 좀 더 나은 모습을 변화하도록 위치 정보를 잘 활용할 수 있을 거라 생각해. 각각의 시나리오는 너님이 리스닝을 언제 시작하고 끝내야 할지에 결정하는데 좋은 예가 될테고, 질 좋은 위치 정보를 획득하면서도 배터리 사용은 더 효율적으로 하는 앱을 위한 길라잡이가 될 수도 있겠지.


사용자가 만드는 컨텐츠 내에 위치 정보가 태그로 삽입되는 경우

여행 정보를 만든달지, 음식점에 대한 리뷰를 포스팅한달지 이런 경우의 시나리오야. 이런 경우에는 사용자가 컨텐츠를 작성하는 동안(트위터를 작성하는 등의)에 위치 정보를 결정하면 되겠지? 컨텐츠 작성을 시작할 때나 해당 앱이 시작했을 때 리스닝을 시작하면 될테고, 글을 저장하거나 포스팅하면 리스닝을 끝내야겠지.  물론, 그 작업이 이루어지는 평균적인 시간에 대한 예측, 그리고 그 사이에 리스닝을 지속한다면 초래될 여러 조건들도 너님이 판단해야하는 건 당연한 거고...

주변의 레스토랑이나 쇼핑센터를 찾고자 한다면?

추천 항목을 제공하면서도 사용자가 이동을 하면 추천 항목도 당연히 변화해야 하겠지?
리스닝을 시작한 이후로 조금 더 정확한 위치 정보를 획득할 때마다, 거기에 맞는 추천 항목리스트도 업데이트 해줘야 할거야. 그러다, 추천 항목이 안정화되면 리스닝도 멈춰 버리면 되겠지
그런 앱에서 사용자가 관심 있는건 주변의 추천 항목들이지, 자기 자신의 위치가 아니잖아.




















원문에는 Mock 위치 정보를 사용하는 법이 이후로도 설명되어 있지만, 그 정도는 금방 알 수 있는 부분이라서 여기서 난 끝내고자 한다. 도움이 되었기를.......끝!









Wednesday, August 22, 2012

Android Layout Tricks #1

효율적인 레이아웃 구성에 대한 이야기



다음 글은 아래의 글을 내맘대로 번역한 것임을 밝힙니다.
원본출처: http://android-developers.blogspot.kr/2009/02/android-layout-tricks-1.html

안드로이드 UI 툴킷은 사용하기 편한 몇 개의 레이아웃 매니저를 제공하며, 대부분의 경우에는 이 레이아웃 매니저의 기본적인 특성들만을 이용해서 UI를 개발하게 되지. 하지만, 그런 기본적인 특성을 고집하면 효과적으로 UI를 구성할 수가 없어. 대표적인 예가 LinearLayout을 과도하게 사용하는 것인데, 뷰 계층내에 쓸데 없이 많은 뷰가 존재하게 하지.

모든 뷰는 그만큼의 댓가를 요구해. 초기화를 해야하잖아. 레이아웃을 구성하고 그리고 하는 작업이 꽤나 느려지는 건 당연하지 않겠어?  weight 파라메터를 사용하는 LinearLayout을 몇 개 사용하게 되면 ( 자식 노드(child)들의 크기가 다시 한번 계산되어야 하기 때문에) 더더욱 그렇게 되겠지.

레이아웃과 관련된 아주 간단하면서도 일반적인 예를 들어 설명해볼께. 아래 그림 처럼 만들어볼 건데, 왼쪽에는 아이콘이, 오른쪽 위에는 제목을 아래에는 추가적인 설명을 붙이려고 해


위의 모습처럼 나타나게 하려면 하나의 ImageView와 두 개의 TextView로 레이아웃을 구성해야 한다는 정도는 알지? HierachyViewer로 보면 아래와 같은 모습일거야.



LinearLayout 을 이용한 레이아웃 구성

이 정도의 레이아웃은 LinearLayout을 이용해서 아주 쉽게 구현할 수가 있어. Horzontal LinearLayout 내부에 ImageView와 Vertical LinearLayout을 포함시키고, 내부에 포함된 Vertical LinearLayout은 다시 두 개의 TextView를 포함하면 되지.  코드를 볼까?

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <LinearLayout
        android:orientation="vertical"
    
        android:layout_width="0dip"
        android:layout_weight="1"
        android:layout_height="fill_parent">

        <TextView
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1"
                    
            android:gravity="center_vertical"
            android:text="My Application" />
            
        <TextView  
            android:layout_width="fill_parent"
            android:layout_height="0dip"
            android:layout_weight="1" 
            
            android:singleLine="true"
            android:ellipsize="marquee"
            android:text="Simple application that shows how to use RelativeLayout" />
            
    </LinearLayout>
</LinearLayout>

이 레이아웃은 아주 잘 동작할거야 하지만, 이것을 ListView의 리스트 아이템으로 사용한다면 자원 낭비가 심할 수도 있다는 거지. 이 레이아웃을 RelativeLayout을 이용해서 다시 작성하면 이전 구성보다 (리스트 아이템 하나당)하나의 View를 절약할 수 있고 그만큼 뷰 계층도 복잡도가 덜 해져.  구현 소스코드도 아주 심플해지는 건 덤?

RelativeLayout을 이용한 레이아웃 구성


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="?android:attr/listPreferredItemHeight"
    
    android:padding="6dip">
    
    <ImageView
        android:id="@+id/icon"
        
        android:layout_width="wrap_content"
        android:layout_height="fill_parent"
        
        android:layout_alignParentTop="true"
        android:layout_alignParentBottom="true"
        android:layout_marginRight="6dip"
        
        android:src="@drawable/icon" />

    <TextView  
        android:id="@+id/secondLine"

        android:layout_width="fill_parent"
        android:layout_height="26dip" 
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        
        android:singleLine="true"
        android:ellipsize="marquee"
        android:text="Simple application that shows how to use RelativeLayout" />

    <TextView
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        
        android:layout_toRightOf="@id/icon"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:layout_above="@id/secondLine"
        android:layout_alignWithParentIfMissing="true"
                
        android:gravity="center_vertical"
        android:text="My Application" />
</RelativeLayout>


RelativeLayout을 이용해서 구현한 것이나 이전 것이나 동일하지만 다른 점이 하나 있어.
첫 번째나 두번째나 두 개의 TextView을 가지고 있잖아? 만약에 추가 설명이 리스트 아이템에 주어지지 않아서 제목만 출력해야한다면 어떻게 할 수 있을까? 이러한 상황이라면 두 번째 TextView의 Visibiility 속성을 Gone으로 설정해서 해당 항목을 제외시킬 수가 있을거야.

여기서 중요한 건, 이게 LinearLayout에서는 완벽하게 동작하지만 RelativeLayout을 사용하면 그렇지 않다는 거지.




RelativeLayout, alignWithParentIfMissing

RelativeLayout에서는  부모 뷰나 RelativeLayout 자신, 혹은 다른 뷰를 기반으로 해서 정렬이 되잖아? 앞의 예시 코드에만 봐도 추가 설명은 RelativeLayout의 아래(bottom)에, 제목은 추가설명위, 부모 뷰의 윗쪽에 고정되도록 구성했지. 근데 여기서, 추가 설명을 GONE으로 해버리면 RelativeLayout은  제목에 해당하는 TextView의 bottom 부분을 어디에 맞춰야 할지를 모르게 되어 버린다는 거야.

아주 다행스럽게도 이러한 문제는  alignWithParentIfMissing 이라는 아주 특별한 속성을 사용해서 해결할 수가 있어. 이 파라메터 값는 boolean 값으로 지정할 수가 있는데, RelativeLayout에게 '혹시라도 네 모습을 결정할 근거가 되는 객체가 없을 경우엔  네 자신(RelativeLayout)을 기준으로 삼아라'라고 말해주는 거라 생각하면 돼.

예를 들어 설명해볼까? 앞의 예제에서는 제목 TextView를 선언하면서 '아이콘 그림의 오른쪽에, 부모 뷰의 오른쪽 위에, secondLine(추가설명)의 위에' 위치한다고 정의했잖아? 근데 제목의 bottom값으로 정해야할, 아래에 당연히 있어야 할 추가설명 TextView가 없으니, 자기 자신(Relativelayout)의 바닥(bottom)을 제목의 bottom으로 지정해버리는 거지.






이제 우리가 작성한 레이아웃이 아주 잘 동작할거야. 일부 예외 적인 상황에서도 말이야. 게다가 뷰 계층도 아주 심플해졌어. LinearLayout의 weight도 사용하지 않았으니 더 효율적이고 말이야. 두 가지 구현 방법의 차이점은 아래 뷰 계층도를 보면 더 분명해질거야.

다시한번 말하지만,  이 두 구현방법의 차이점은 '(예로) ListView의 리스트 아이템으로 사용할 레이아웃을 구성해야 할 때'와 같은 상황에서라면 아주 중요하게 고려할 부분이라는 거야. 레이아웃을 구성하는 방법을 보여주는 아주 간단한 예제였지만 이것을 통해서 사용자 인터페이스를 효과적으로 구성하는 방법을 배웠길 바라. 끝!


Tuesday, August 14, 2012

사용자 정의 뷰와 R.styleable

사용자 정의 뷰에서 사용할 attribute를 정의하고 사용하기



1.사용자 정의 뷰에서 사용할 attribute를 attrs.xml에 정의하기


   <resources>
      <declare-styleable name="FramedImageView">
         <attr format="reference" name="placeHolder" />
      </declare-styleable>
   </resources>

위의 코드 처럼 작성을 하면 R.java 파일에는 다음과 같은 레퍼런스가 작성이 된다.

   public static final class styleable {
      public static final int[] FramedImageView = { ... };
      public static final int FramedImageView_placeHolder = 0;
   }

객체 타입을 reference로 format을 정하는 것처럼 그 외의 타입에 대한 attribute 추가고 가능하다.

   <attr format="integer" name="testInteger" />
   <attr format="boolean" name="testBoolean" />

을 추가하면 당연히 R.java 파일에도 아래와 같은 레퍼런스가 각각 추가되겠지.

   public static final int FramedImageView_testInteger = 1;
   public static final int FramedImageView_testBoolean = 2;



2.레이아웃 xml 파일에서 attribute 값을 지정하고


다음으로 레이아웃 xml파일에서 각각의 attribute 값에 원하는 값을 지정해 줄 수가 있어. 아래 코드에서 붉게 표시된 부분을 주의 깊게 볼 것.


   <?xml version="1.0" encoding="utf-8"?>
   <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
       xmlns: how2develop="http://schemas.android.com/apk/res/com.how2develop"
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:orientation="vertical" >

       <com.how2develop.widget.FramedImageView
           android:id="@+id/frameImageView"
           android:layout_width="200dp"
           android:layout_height="200dp"
           android:layout_centerVertical="true"
           android:layout_gravity="center"
           android:layout_margin="10dp"
           how2develop:placeHolder="@drawable/blankImage" />


3.뷰 클래스의 생성자에서 해당 값을 읽어 오면 된다


사용자 정의 뷰 클래스에서 이제 해당 attribute를 읽어오면 되는데, 아래의 코드는 ImageView를 상속하는 사용자 정의 뷰 클래스를 작성하면서 위 1,2 과정을 거치면서 정의하고 세팅한 attribute 값을, 뷰 클래스의 생성자에서 읽어오는 코드이다.


          public FramedImageView(Context context, AttributeSet attrs) {
   super(context, attrs);

   TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FramedImageView, 0, 0);
   placeHolder = a.getDrawable(R.styleable.FramedImageView_placeHolder);

   a.recycle();
   }


obtainStyledAttributes()로 획득되는 TypedArray 타입에는 위에서 호출된 getDrawable()외에도 attribute값을 읽는 (타입에 따른) 다양한 메소드가 제공되니 꼭 API을 보고 눈으로 익히는 게 좋을 듯 하다.

그리고, 불러온 후에는 recycle() 메소드를 호출해 주는 것을 잊지 말자. 그래야 할당되어 있던 메모리를 풀(pool)에 즉시 돌려줘서 garbage collection이 될 때까지 기다릴 필요가 없게 되니까.