Android2014. 1. 8. 10:11

Smallest-width Qualifier(최소너비 식별자) 사용하기


개발자가 안드로이드 3.2 이전 OS에서 가장 힘들었던 것 중의 하나가 "large" 화면사이즈 폴더였다. 여기에는 Dell Streak(5인치), 최초 갤럭시탭, 7인치 태블릿 등이 해당된다.

많은 앱들은 디바이스에 따라 서로 다른 레이아웃을 구성하기를 원했지만, 5인치든 7인치든 모두 "large" 스크린으로 처리됐다. 이런 문제를 해결하기 위해 안드로이드 3.2부터 "Smallest-width" qualifier(최소너비 식별자)를 추가했다.


최소너비 식별자를 이용하면 특정 가로사이즈(dp)의 화면을 구분지을 수 있다. 예를들어 대부분의 7인치 태블릿은 600dp의 너비값을 갖고 있는데, 한 페이지에 두개의 판(pane)을 동시에 보여주고 싶다면(반면 화면이 작은 디바이스에서는 한개의 판만...), 아래와 같이 두 개의 레이아웃을 구성하면 된다.


  • res/layout/main.xml, single-pane (default) layout:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="match_parent" />
    </LinearLayout>
  • res/layout-large/main.xml, two-pane layout:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="400dp"
                  android:layout_marginRight="10dp"/>
        <fragment android:id="@+id/article"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.ArticleFragment"
                  android:layout_width="fill_parent" />
    </LinearLayout>


그러나, 두 판을 보여주는 레이아웃의 최소너비가 600dp임을  지정하고 싶다면 "large"식별자를 사용하는 대신, "sw600dp" 식별자를 사용하면된다.


  • res/layout/main.xml, single-pane (default) layout:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="match_parent" />
    </LinearLayout>
  • res/layout-sw600dp/main.xml, two-pane layout:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:orientation="horizontal">
        <fragment android:id="@+id/headlines"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.HeadlinesFragment"
                  android:layout_width="400dp"
                  android:layout_marginRight="10dp"/>
        <fragment android:id="@+id/article"
                  android:layout_height="fill_parent"
                  android:name="com.example.android.newsreader.ArticleFragment"
                  android:layout_width="fill_parent" />
    </LinearLayout>


이것은 최소너비가 600dp이거나 그보다 큰 디바이스에서는 layout-sw600dp/main.xml (two-pane) 레이아웃을 사용하고, 600dp보다 작은 디바이스에서는 layout/main.xml (single-pane) 레이아웃을 사용한다는 것을 의미한다.


여기서 주의할 점이 있다. 안드로이드 3.2 미만의 디바이스에서는 "sw600dp"라는 식별자를 인식할 수 없기 때문에 원하는 대로 동작하지 않는다. 이런 이유로 이전에 사용하던 "large"식별자를 계속 유지시켜줘야 한다. 즉, res/layout-sw600dp/main.xml 과 똑같이 res/layout-large/main.xml 을 구성해주면 이 문제를 해결할 수 있다.


다음 섹션에서는 이 방법을 사용했을 때, 레이아웃파일이 중복되지 않게하는 방법에 대해 배울 수 있다.




[원문보기]

(발번역, 오역에 대한 내용이 있다면 댓글로 항의해주세요 ^^)
























Posted by 데브로망스
Android2012. 1. 30. 17:48
기획자가 원하는 대로 액티비티를 띄우고, 이동하고, 점프하는 방법에 대해 알아본다.


[관련글 링크]
1. Activity Jump #1 "홈 액티비티로 이동하기"
2. Activity Jump #2 "액티비티 스택에 없었던 메인액티비티로 이동하기"


[목표]
1번 글에서는 어느 액티비티에서든 홈 액티비티로 이동하는 방법에 대해 정리해봤다.

이번 글에서는 시작 액티비티가 기획자가 요구하는 UI흐름의 중간인 경우. 그래서 시작 액티비티에서 [이전] 키를 눌렀을 때, 안드로이드 홈스크린이 아닌 UI흐름 상의 액티비티를 보여줘야 하는 경우에 대해 알아본다.

그림으로 표현하면 다음과 같다.


안드로이드 홈스크린에서 "위젯" 또는 "실행 아이콘"을 눌렀을 때, 액티비티 B를 보여주는 것. 또한, [이전]의 의미가안드로이드 홈스크린이 아닌, 한 번도 띄운적 없는(액티비티 스택에 존재하지 않는) Activity A인 경우 어떻게 구현해야 할까?


[구현하기]

다음과 같이 액티비티 4개를 준비한다.



우선, AndroidManifest.xml을 수정하여, Activity B를 시작액티비티로 지정해 준다.
이렇게 하면 안드로이드홈에서 아이콘을 클릭하면 제일 먼서 Activity B가 보여진다.
당연히 액티비티 스택에는 Activity B 만 쌓여있는 상태가 된다.


<activity
          android:name=".ActivityJumpDemoActivity"
          android:label="@string/app_name">
</activity>
<activity android:name="ActivityA" 
	android:configChanges="orientation" 
	android:screenOrientation="sensor">
</activity>
<activity android:name="ActivityB" 
	android:configChanges="orientation" 
	android:screenOrientation="sensor">
	<intent-filter>
		<action android:name="android.intent.action.MAIN" />
		<category android:name="android.intent.category.LAUNCHER" />
	</intent-filter>
</activity>
<activity android:name="ActivityC" 
	android:configChanges="orientation" 
	android:screenOrientation="sensor">
</activity>
*맨 아래 링크되어 있는  전체 코드는 이전 글을 기준으로 구현되어 있어서 이 부분이 적용이 되어 있지 않다. 테스트 해보려면 위의 내용을 수정해서 테스트해야 한다.


이제 Activity B에서 [이전]키를 눌렀을 때,
Activity A를 보여주려면 Activity B에 onKeyUp(...)을 구현해준다. 
나머지 액티비티들도 띄워줄 액티비티를 변경/적용해준다.
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_BACK) {
		Intent intent = new Intent(getBaseContext(), ActivityA.class);
		startActivity(intent);
		finish();
		return true;
	}
	return super.onKeyDown(keyCode, event);
}
Line 3
"KeyEvent.KEYCODE_BACK" [이전]키가 눌렸을 때를 처리한다.
Line 4 : [이전]키가 눌렸을 때 보여줄 ActivityA.class를 지정한다.
Line 6 : 현재 창은 종료시켜 액티비티 스택에서 깔끔히 없앤다.

이렇게 구현된 상태에서 "안드로이드홈->B액티비티->[이전]키->A액티비티"의 흐름은 정상적으로 동작한다.


* 안드로이드홈->B액티비티->[이전]키->A액티비티 후 스택 확인

  Running activities (most recent first):

    TaskRecord{300bdd60 #5 A com.juno.activityjump.demo}

      Run #1: HistoryRecord{3012fe80 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{2fe0c3e8 #2 A com.lge.launcher}

      Run #0: HistoryRecord{300d62c0 com.lge.launcher/.Launcher}



그런데, 문제가 있다.
위와 같이 액티비티 마다 onKeyUp(...) 별도로 구현해 놓으면, [이전]키가 눌렸을 때 항상 새롭게 액티비티를 띄우게 된다.
만약 "메인->A->메인"를 반복하면, A액티비티는 finish()로 종료되지만, 스택에 있었던 녀석을 놔두고 새롭게 메인액티비티를 띄우게 된다. 즉, 메인액티비티가 여러 벌이 생기고 메모리에 계속 남게 된다.


* "메인->A->[이전]키->메인"를 두 번 반복했을 때의 스택 확인

  Running activities (most recent first):

    TaskRecord{301628a0 #6 A com.juno.activityjump.demo}

      Run #3: HistoryRecord{30311070 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

      Run #2: HistoryRecord{301e5650 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

      Run #1: HistoryRecord{301c5ea8 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{2fe0c3e8 #2 A com.lge.launcher}

      Run #0: HistoryRecord{300d62c0 com.lge.launcher/.Launcher}


이를 해결하는 방법은
"1. Activity Jump #1 "홈 액티비티로 이동하기" 글에서 소개한 인텐트 플래그를 동일하게 사용하면 해결된다.

@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
	if (keyCode == KeyEvent.KEYCODE_BACK) {
		Intent intent = new Intent(getBaseContext(), ActivityJumpDemoActivity.class);
		intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
		startActivity(intent);
		finish();
		return true;
	}
	return super.onKeyDown(keyCode, event);
}

Line 5 : FLAG_ACTIVITY_CLEAR_TOP 플래그. 혹시 모를 중간의 액티비티를 스택에서 없앤다.
Line 6 : FLAG_ACTIVITY_SINGLE_TOP 플래그. 최상위 스택에 보여줄 액티비티가 존재하는 경우, 이를 재사용한다.

위와 같이 적용한 후에는 액티비티 사이를 이리저리 왔다 갔다 한 후, 메인액티비티로 돌아온다고 해도 스택은 아래와 같이 깔끔해진다.

  Running activities (most recent first):

    TaskRecord{3027dff8 #7 A com.juno.activityjump.demo}

      Run #1: HistoryRecord{301e89e8 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{2fe0c3e8 #2 A com.lge.launcher}

      Run #0: HistoryRecord{300d62c0 com.lge.launcher/.Launcher}



[다운로드]
    전체소스


Posted by 데브로망스
Android2012. 1. 30. 10:12
기획자가 원하는 대로 액티비티를 띄우고, 이동하고, 점프하는 방법에 대해 알아본다.


[관련글 링크] 
1. Activity Jump #1 "홈 액티비티로 이동하기"
2. Activity Jump #2 "액티비티 스택에 없었던 메인액티비티로 이동하기"


[목표]
아래와 같이 메인 액티비티에서 액티비티C까지 4개의 화면이 존재하는 앱을 구현하는데, 갑자기 기획자가 "어떤 페이지에서도 [HOME] 버튼을 누르면 메인 액티비티로 이동이 가능하도록 구현해주세요."라고 한다면.. 

"그냥 띄우면 되겠지~"한다면, 안드로이드 액비티비의 스택구조를 알아볼 필요가 있고, 
"뭔가 좀 꼬일 것 같은데~"한다면 액티비티를 띄울 때, 인텐트에 적용할 플래그(Intent.addFlags(...))에 대해 알아보면 된다.

위의 내용을 정리해서, 구현해본다.



[구현하기]
아래와 같이 4개의 액티비티를 준비한다.
[NEXT PAGE >] 버튼을 누르면 왼쪽에서 오른쪽 순서로 이동하도록 구현한다.
어느 페이지에서든 [Go to MAIN] 버튼을 누르면 무조건 맨 왼쪽의 메인액티비티로 이동한다.



아래는 "B 액티비티"의 구현한 예이다. (다른 페이지도 똑같이 구현한다.)
눈여겨 볼 부분은 Line 19~22 이다.
public class ActivityB extends Activity implements OnClickListener {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_b);
        
        Button btnGoToMain = (Button)findViewById(R.id.btnActB);
        btnGoToMain.setOnClickListener(this);
        Button btnNextPage = (Button)findViewById(R.id.btnActB_Next_Page);
        btnNextPage.setOnClickListener(this);
    }

	@Override
	public void onClick(View v) {
		int id = v.getId();
		
		switch (id) {
		case R.id.btnActB:
			Intent intentHome = new Intent(this, ActivityJumpDemoActivity.class);
			intentHome.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
			intentHome.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
			startActivity(intentHome);
			finish();
			break;
		case R.id.btnActB_Next_Page:
			Intent intent = new Intent(this, ActivityC.class);
			startActivity(intent);
		default:
			break;
		}
	}
}

Line 20 : "FLAG_ACTIVITY_CLEAR_TOP" 플래그. 간단히 현재 액티비티에서 어느 액티비티로 이동하는데, 스택 중간에 있었던 액티비티들을 지우는 역할은 한다고 이해하면 된다. 이 플래그가 없으면, 중간에 액티비티는 스택에 그대로 남아있기 때문에 이동 중간에  화면에 표출되어 UI 흐름을 망친다. 또한 시간이 지나면서 수 많은 액티비티가 쌓이게 되어 메모리 낭비를 초래한다.

FLAG_ACTIVITY_CLEAR_TOP 
If set, and the activity being launched is already running in the current task, then instead of launching a new instance of that activity, all of the other activities on top of it will be closed and this Intent will be delivered to the (now on top) old activity as a new Intent.


Line 21 : "FLAG_ACTIVITY_SINGLE_TOP" 플래그. 띄우려는 액티비티가 스택 맨위에 이미 실행 중이라면 재사용하겠다는 의미로 해석하면 된다.

FLAG_ACTIVITY_SINGLE_TOP
If set, the activity will not be launched if it is already running at the top of the history stack. 


 즉, 이 플래그를 설정하지 않았을 때, 메인액티비티는 새롭게 생성된다.
onCreate(...)와 onResume(...) 함수에 로그를 찍어 테스트 한 결과는 아래와 같다.


"A액티비티->메인" 으로 이동할때, 

1. FLAG_ACTIVITY_SINGLE_TOP 플래그 ON



2. FLAG_ACTIVITY_SINGLE_TOP 플래그 OFF 


결국,  FLAG_ACTIVITY_SINGLE_TOP를 설정하면, 기존에 띄워져있던 액티비티를 재사용하기 때문에 onCreate(...)가 불리지 않는다.


[액티비티 스택으로 확인하기]
(스택을 확인하는 방법은 이전 글을 참고하세요.)

public void onClick(View v) {
	int id = v.getId();
	
	switch (id) {
	case R.id.btnActC:
		Intent intentHome = new Intent(this, ActivityJumpDemoActivity.class);
		intentHome.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
		intentHome.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
		startActivity(intentHome);
		finish();
		break;
	default:
		break;
	}
}


"매인액티비티 -> ... -> 액티비티 C"까지 흘러온 상태에서 [Go to MAIN] 버튼을 눌렀을 때의 스택을 확인해봤다.
위에 코드의 Line 7,8을 ON했을 때와 OFF했을 때를 비교한 결과는 아래와 같다.



MAIN->A->B->C까지 정상적으로 이동했을 때

  Running activities (most recent first):

    TaskRecord{3026a910 #82 A com.juno.activityjump.demo}

      Run #4: HistoryRecord{30186310 com.juno.activityjump.demo/.ActivityC}

      Run #3: HistoryRecord{3021e1e0 com.juno.activityjump.demo/.ActivityB}

      Run #2: HistoryRecord{303b3e08 com.juno.activityjump.demo/.ActivityA}

      Run #1: HistoryRecord{300251c8 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{30064818 #2 A com.lge.launcher}

      Run #0: HistoryRecord{2fcff6e0 com.lge.launcher/.Launcher}



C 에서 -> HOME으로 이동했을 때 (2개의 플래그 ON)

  Running activities (most recent first):

    TaskRecord{3026a910 #82 A com.juno.activityjump.demo}

      Run #1: HistoryRecord{300251c8 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{30064818 #2 A com.lge.launcher}

      Run #0: HistoryRecord{2fcff6e0 com.lge.launcher/.Launcher}



C 에서 -> HOME으로 이동했을 때 (2개의 플래그 OFF)

  Running activities (most recent first):

    TaskRecord{301996d8 #83 A com.juno.activityjump.demo}

      Run #4: HistoryRecord{3035d6c0 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

      Run #3: HistoryRecord{302c4f30 com.juno.activityjump.demo/.ActivityB}

      Run #2: HistoryRecord{3012aa08 com.juno.activityjump.demo/.ActivityA}

      Run #1: HistoryRecord{2ffb9170 com.juno.activityjump.demo/.ActivityJumpDemoActivity}

    TaskRecord{30064818 #2 A com.lge.launcher}

      Run #0: HistoryRecord{2fcff6e0 com.lge.launcher/.Launcher}




[참고자료]
    http://developer.android.com/reference/android/content/Intent.html


[다운로드]
    전체소스





Posted by 데브로망스
Android2012. 1. 19. 17:56

[목표]
안드로이드용 Appy Geek 앱에 있는 리스트에는 갱신될 때마다 리스트 항목이 차례대로 위에서 아래로 흐르는 애니메이션이 적용되어 있다. 이것을 따라 만들어 본다.

 



[리스트 항목 레이아웃 잡기]

리스트 항목의 레이아웃은 아래 그림과 같이 잡는다.

 
이를 /res/layout/list_item_layout.xml 에 구현한다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
	android:layout_height="fill_parent" 
	android:layout_width="fill_parent"
	android:orientation="vertical"
	android:background="#FFFFFF">
	<LinearLayout 
		android:id="@+id/listLayout_main"
		android:layout_height="wrap_content"
		android:layout_width="fill_parent" 
		android:layout_weight="5"
		android:orientation="horizontal">
		<ImageView
			android:id="@+id/main_icon"
			android:layout_height="35dip"
			android:layout_width="35dip"
			android:layout_gravity="center"
			android:layout_marginLeft="2dip"
			android:background="@drawable/icon01"/>
		<LinearLayout 
			android:id="@+id/listLayout02"
			android:layout_height="wrap_content"
			android:layout_width="fill_parent" 
			android:layout_weight="5"
			android:layout_marginLeft="10dip"
			android:orientation="vertical">
			<TextView 
				android:id="@+id/txt_top"
				android:layout_height="30dip"
				android:layout_width="wrap_content"
				android:gravity="center_vertical"
				android:textColor="@color/list_txt_top"
				android:textSize="@dimen/list_top_txt_size"
				android:singleLine="true" 
				android:ellipsize="marquee"
				android:text=""/>
			<TextView 
				android:id="@+id/txt_bottom"
				android:layout_height="20dip"
				android:layout_width="wrap_content"
				android:singleLine="true"
				android:gravity="center_vertical"
				android:textColor="@color/list_txt_bottom"
				android:textSize="@dimen/list_bottom_txt_size"
				android:ellipsize="marquee"
				android:text=""/>
		</LinearLayout>
		<Button 
			android:id="@+id/button01" 
			android:layout_height="35dip"
			android:layout_width="35dip" 
			android:background="@drawable/icon02" 
			android:clickable="true" 
			android:focusable="false" 
			android:focusableInTouchMode="true" 
			android:layout_gravity="center"
			android:layout_marginRight="2dip"/>
		<Button 
			android:id="@+id/button02"
			android:layout_height="35dip"
			android:layout_width="35dip"
			android:layout_gravity="center"
			android:background="@drawable/icon03"
			android:clickable="true" 
			android:focusable="false"
			android:focusableInTouchMode="true"
			android:layout_marginRight="2dip"/>
	</LinearLayout>
</LinearLayout>



[개별 Animation XML 정의하기]

res/anim/anim_alpha_translate_listview.xml 파일에 리스트 항목 한 개에 적용될 기본 애니메이션을 정의한다.
위에서 아래로 이동하고(translate), 페이드인 효과(alpha)를 동시에 표현하기 위해 아래와 같이 구현한다.
<set xmlns:android="http://schemas.android.com/apk/res/android" 
    android:interpolator="@android:anim/accelerate_decelerate_interpolator">
	<translate
		android:fromYDelta="-100%"
		android:toYDelta="0"
		android:duration="500">
	</translate>
	<alpha 
	    android:fromAlpha="0.0"
	    android:toAlpha="1.0"
	    android:duration="500">
	</alpha>
</set>
Line 04~06 : -100%에 해당하는 위치부터 제자리까지 0.5초 안에 이동하도록 설정한다.

Line 09~11 : 투명(0.0) 상태에서 본래 상태(1.0)까지 0.5초 안에 변하도록 설정한다.




[Layout Animation XML 정의하기]

위에서 정의한 애니메이션과 리스트뷰 자체를 연결하려면 제2의 XML 파일이 필요한데, 이를 레이아웃 컨트롤러 XML이라고 부른다. /res/anim/anim_layout_controller.xml 파일을 생성하고, 아래와 같이 구현한다.
<layoutAnimation xmlns:android="http://schemas.android.com/apk/res/android" 
	android:delay="40%"
	android:animationOrder="normal"
	android:animation="@anim/anim_alpha_translate_listview">
</layoutAnimation>
Line 02 : 항목별 애니메이션이 시작되는 지연시간이다. 애니메이션 총 시간의 30%에 해당되는 시간마다 다음 항목의 애니메이션을 시작한다. 만약, 100%를 지정하면, 첫 번째 리스트 항목의 애니메이션이 끝나야만 두 번째 항목의 애니메이션이 시작된다. (사용자가 지루함을 느끼지 않도록 할 자신이 있다면 100%를 사용해도 되지만.. 이 예제는 그럴 자신이 없으므로 40% 설정했다. ^^)

Line 03 : 애니메이션이 적용될 순서다. "normal"은 위에서 아래로 순차적으로 적용된다. 반대로 하려면 "reverse", 랜덤을 원하면 "random"을 적어 넣으면 된다.

Line 04 : 정의해 놓았던 개별 Animation XML 리소스(anim_alpha_translate_listview.xml)를 연결시킨다.




[ListView와 애니메이션 연결짓기]

위에서 준비했던 모든 과정은 바로 여기에서 사용하려고 했던 짓(?)이다.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:background="#FFFFFF">
	<ListView 
		android:id="@android:id/list"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:divider="@color/list_divider" 
		android:dividerHeight="1dip"
		android:smoothScrollbar="true"
		android:fastScrollEnabled="true"
		android:background="#FFFFFF"
		android:persistentDrawingCache="animation|scrolling"
		android:layoutAnimation="@anim/anim_layout_controller"/>
</LinearLayout>

Line 15 : 애니메이션과 스크롤링의 최적화를 위해 persistentDrawingCache 속성을 부여한다.
Line 16 : 레이아웃 컨트롤러를 지정함으로써 드디어 리스트 뷰에 애니메이션이 적용된 것이다.




[결과화면]
(기본적인 리스트뷰 관련 코드는 생략합니다. 맨 아래 전체 코드를 다운받아 참고하시면 됩니다. ^^)




[다운로드]
    전체소스



Posted by 데브로망스
Android2012. 1. 17. 14:07
AndroidManifest.xml에 BroadcastReceiver를 등록하는 방법이 있는데,
여기서는 런타임시에 한 액비티비 내에서 등록 및 해제하는 방법을 정리한다.
메시지가 많지 않고, 잠깐 등록해서 사용하는 경우 이 방법도 편리하다.


[목표]
액티비티에 버튼을 하나 클릭하면, 숫자를 증가시켜 Intent에 담아, 브로드캐시트 메시지를 쏜다.
onReceive()에서 이를 받아, Toast를 띄운다.
간단하게 한 액티비티 상에서 위의 작업을 수행한다.



[구현하기] 
public class BroadcastReceiverDemoActivity extends Activity implements OnClickListener {
	private final String BROADCAST_MESSAGE = "com.juno.broadcastreceiver.INCREASED_NUMBER";
	private BroadcastReceiver mReceiver = null;
	private int mReiceivedNumber = 0;
	
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        ImageButton button = (ImageButton)findViewById(R.id.iv_send_msg);
        button.setOnClickListener(this);
    }
    
    @Override
	public void onResume() {
    	super.onResume();
    	registerReceiver();
	}
    
    @Override
	public void onPause() {
    	super.onPause();
		unregisterReceiver();
	}
    
    @Override
	public void onDestroy() {
    	super.onDestroy();
	}
    
    @Override
	public void onClick(View v) {
		int id = v.getId();
		
		switch (id) {
		case R.id.iv_send_msg:
			Intent intent = new Intent(BROADCAST_MESSAGE);
			intent.putExtra("value", mReiceivedNumber);
			mReiceivedNumber++;
			sendBroadcast(intent);
			break;
		default:
			break;
		}
	}
    
	private void registerReceiver() {
		
		if(mReceiver != null)
			return;

		final IntentFilter theFilter = new IntentFilter();
        theFilter.addAction(BROADCAST_MESSAGE);

        this.mReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
            	
				int strNewPostion = intent.getIntExtra("value", 0);
				if (intent.getAction().equals(BROADCAST_MESSAGE)) {
					Toast.makeText(BroadcastReceiverDemoActivity.this, "number=" + strNewPostion, Toast.LENGTH_SHORT).show();
				}
        	}
        };

        this.registerReceiver(this.mReceiver, theFilter);
	}
	
	private void unregisterReceiver() {
		if(mReceiver != null)
			this.unregisterReceiver(mReceiver);
	}
}
  
Line 02 : 메시지 명칭이다. 메시지를 수신할 때, onReceive(...)에서 받는 여러 메시지들을 분류할 때 사용할 Key값이다.
 

Line 18 : onResume()에서 준비해 놨던 registerReceiver() 함수를 호출한다.
 

Line 24 : onPause()에서 준비해 놨던  unregisterReceiver() 함수를 호출한다. onDestroy()함수가 호출이 되지 않는 경우도 있으니, onPause()가 안전하다. 리시버를 해제하지 않으면 오류가 발생하므로,  unregisterReceiver()의 위치는 상황에 맞춰서 잘 배치해야 한다.
 

Line 38~41 : 송신측 작업 ; "value" 키 값과, 데이터(숫자) 하나를 담아서 보낸다.
 

Line 54,67 : 리시버 등록 작업 ; 메시지 명칭을 필터에 적어논다.
 

Line 67 : 리시버 등록 작업 ;  준비해놓은 리시버와 필터 인스턴스로 현재의 액비티비에 리시버 등록을 완료한다.
 

Line 60 수신측 작업 ; 송신한 쪽에서 보낸, 데이터를 받는다.
 

Line 61 수신측 작업 ; onReceiver()에 들어온 메시지를 메시지명으로 비교해서, 수행할 작업을 구현한다.


[다운로드] 
   전체소스



Posted by 데브로망스
Android2012. 1. 13. 14:43

[목표]


- 메인 액티비티에서 투명 액티비티를 띄운다.

- 투명 액티비티에 버튼을 하나 넣고, 터치하면 간단하게 Toast 메시지를 띄운다.
- 버튼 이외의 투명 공간을 터치하면, 투명 액티비티를 종료한다.



[투명 액티비티를 위한 style 만들기]


/res/values/style.xml 파일을 생성해서, 아래의 코드를 삽입한다.

name으로 지정한, "Theme.Transparent"는 투명 액티비티를 생성할 때, AndroidManifest.xml에서 사용될 이름이다.

<resources>
    <style name="Theme.Transparent" parent="android:Theme">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation</item>
        <item name="android:windowNoTitle">true</item>
        <item name="android:windowContentOverlay">@null</item>
        <item name="android:backgroundDimEnabled">true</item>
    </style>
</resources>

Line 09 : "<item name="android:backgroundDimEnabled">true</item>" 설정은 투명 액티비티의 빈공간의 Dim(어둑한) 처리 여부를 지정한다. true일 경우, 빈 공간이 약간 어두운 톤으로 채워진다. false일 경우 깔끔하게 투명처리된다.


[정의한 투명 Theme, 액티비티에 적용하기]

이제, 아래와 같이 AndroidManifest.xml에 투명액티비티를 새로 정의한다.

<application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        ...
        <activity
            android:label="@string/app_name"
            android:name=".TransparentActivity"
            android:theme="@style/Theme.Transparent">
        </activity>
</application>
Line 08 : 위에서 정의한 style(theme)를 적용한다.

안드로이드에서 제공하는 투명 윈도우 테마를 사용할 수도 있는데,
8번 라인을 아래 코드로 대체하면 되지만, 세밀한 설정을 위해서라면 사용자가 직접 정의해서 사용해야 한다.
android:theme="@android:style/Theme.Translucent.NoTitleBar"


[투명 액티비티 구현하기]

이제, 투명 액티비티 클래스를 만든다.



public class TransparentActivity extends Activity implements OnClickListener {

	private int mDeviceScreenWidth;
	private int mDeviceScreenHeight;
		
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.transparent_activity);
		
		Display display = ((WindowManager)getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
		mDeviceScreenWidth = display.getWidth();
		mDeviceScreenHeight = display.getHeight();
		
		ImageButton btnAuth = (ImageButton) findViewById(R.id.imgbtn_do_sth);
		btnAuth.setOnClickListener(this);
	}
	
	@Override
	public void onClick(View v) {
		int id = v.getId();
		switch (id) {
		case R.id.imgbtn_do_sth:
			Toast.makeText(this, "Button Clicked!!!!", Toast.LENGTH_SHORT).show();
			break;
		default:
			break;
		}
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent event) {
		if (event.getAction() == MotionEvent.ACTION_DOWN) {
        	
			int x = (int)event.getX();
			int y = (int)event.getY();
        	
        	Bitmap bitmapScreen = Bitmap.createBitmap(mDeviceScreenWidth, mDeviceScreenHeight, Bitmap.Config.ARGB_8888);
        	
        	if(x < 0 || y < 0)
        		return false;
        	
        	int ARGB = bitmapScreen.getPixel(x, y);

        	if(Color.alpha(ARGB) == 0) {
        		finish();
        	}

            return true;
        }
        return false;
	}
}

Line 12,13 : 디바이스의 화면 크기를 미리 기억해둔다.
Line 24 : 버튼을 클릭하면 간단히 Toast 메시지를 띄운다.
Line 32 : 버튼을 제외한 투명한 공간을 터치했을 때의 동작을 정의한다. 터치한 위치의 RGB값이 투명한 경우 Activity를 종료(finish())한다.


[다운로드]
    전체코드


[참고자료(이미지)]
 http://trac.pcbsd.org/browser/pcbsd/branches/7.1/system-overlay/PCBSD/local/kde4/share/apps/ksplash/Themes/Galileo/600x400/background.png?rev=3811

http://www.iconarchive.com/show/blawb-icons-by-arrioch/android-icon.html



Posted by 데브로망스
Android2012. 1. 12. 14:08

아래 코드는 안드로이드 프레임워크에서 Activity의 상태에 따라 호출하는 대부분의 함수를 나열했다.
각각 로그를 찍어서 각 함수의 실행 순서를 테스트하여 정리한다.


public class TestAppActivity extends Activity implements OnClickListener {
		
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		Log.d("TestAppActivity", "onCreate");
		setContentView(R.layout.main);

		Button btnAuth = (Button) findViewById(R.id.btn_finish);
		btnAuth.setOnClickListener(this);
		Button btnPopup = (Button) findViewById(R.id.btn_popup);
		btnPopup.setOnClickListener(this);
		Button btnNewAct = (Button) findViewById(R.id.btn_new_activity);
		btnNewAct.setOnClickListener(this);
	}
	
	
	@Override
	protected void onPostCreate(Bundle savedInstanceState) {
		super.onPostCreate(savedInstanceState);
		Log.d("TestAppActivity", "onPostCreate");
	}

	@Override
	protected void onStart() {
		super.onStart();
		Log.d("TestAppActivity", "onStart");
	}

	@Override
	protected void onRestart() {
		super.onRestart();
		Log.d("TestAppActivity", "onRestart");
	}
	
	@Override
	protected void onStop() {
		super.onStop();
		Log.d("TestAppActivity", "onStop");
	}
	
	@Override
	protected void onPostResume() {
		super.onPostResume();	
		Log.d("TestAppActivity", "onPostResume");
	}
	
	@Override
	protected void onPause() {
		super.onPause();
		Log.d("TestAppActivity", "onPause");
	}
	
	@Override
	protected void onResume() {
		super.onResume();
		Log.d("TestAppActivity", "onResume");
	}
	
	@Override
	protected void onDestroy() {
		super.onDestroy();
		Log.d("onPostCreate", "onDestroy");
	}

	@Override
	public void onClick(View v) {
		int id = v.getId();
		switch (id) {
		case R.id.btn_finish:
			finish();
			break;
		case R.id.btn_popup:
			AlertDialog.Builder ab=new AlertDialog.Builder(TestAppActivity.this);
		 	ab.setMessage(Html.fromHtml("<strong><font color=\"#000055\">Title</font></strong><br> Message!!"));
		 	ab.setPositiveButton("OK", null);
		 	ab.show();
			break;
		case R.id.btn_new_activity:
			Intent intent = new Intent(getBaseContext(), NewActivity.class);	
			startActivity(intent);
		default:
			break;
		}
	}
	
	@Override
	protected void onRestoreInstanceState(Bundle savedInstanceState) {
		Log.d("TestAppActivity", "onRestoreInstanceState");
		super.onRestoreInstanceState(savedInstanceState);
	}
	@Override
	protected void onSaveInstanceState(Bundle outState) {
		Log.d("TestAppActivity", "onSaveInstanceState");
		super.onSaveInstanceState(outState);
	}

	@Override
	public void onLowMemory() {
		Log.d("TestAppActivity", "onLowMemory");
		super.onLowMemory();
	}
}




onCreate()
무조건 Activity가 처음 실행될 때 호출된다. [Case1, Case3]
시스템에 의해 호출되면 인자로 받은 Bundle은 null이다. [Case1]
Activity가 실행된 적이 있는데, 어떤 이유로 종료된 후 재시작되면, 종료될 때 호출된 onSaveInstanceState()에서 저장한 내용과 동일한 Bundle을 넘겨준다.
디바이스가 회전되어 가로/세로 전환 등 리소스를 새롭게 갱신되어야 할 때 호출된다. [Case5]

onDestroy()
Actiivity가 종료되기 전 호출된다.
Activity내부에서 finish()를 실행하면 호출된다. [Case7]
시스템 메모리가 부족하면, 안드로이드가 강제로 TestApp을 죽일 때도 호출이 되는데, 메모리 확보가 매우 시급할 때는 호출조차 되지 않는 경우도 있다.
onCreate()와 짝을 이뤄 사용했던 리소스는 이 곳에서 싹~ 치워준다.

onStart()
Activity가 초기 실행 후, 화면의 전면으로 나타날 때 onCreate(), onRestart() 이후에 호출된다. [Case1, Case4]
전화수신, SMS수신 등으로 Background로 갔다가 다시 전면으로 나올때도 호출된다. [Case2, Case3, Case4]

onRestart()
Activity가 정지되었다가 다시 실행될 때 호출되는데, onDestroy()가 호출된 이후가 아닌, onStop()으로 정지된 상태에만 해당된다. 즉, onStop()과 onRestart()는 한 쌍으로 생각하면 된다. [Case2, Case4]

onStop()
하드웨어 HOME버튼을 눌렀을 때와 SMS수신, 전화수신, 다른 App실행할 때 호출된다. [Case2, Case4]

onResume()
Activity가 전면에 나타날 때 대부분의 상황에서 호출된다. 처음 실행했을 때, onCreate() 이후에도 호출된다.
(책에서는 팝업 대화상자가 떳다가 닫히는 경우에도 호출된다고 하지만, AlertDialog로 테스트 했을 때에는 호출되지 않았다.)

onPause()
거의 모든 경우에 onStop(), onDestroy()가 호출되기 이전에 호출된다.
Activity가 사용자의 시선에서 가려지는 경우에 호출된다고 생각하면 된다.
대부분의 상황에서 onStop() 발생하기 이전에 불린다.
*일반적으로 onResume()과 쌍으로 보고, onResume()에서 했던 작업을 onPause()에서 정리, 멈추는 것이 좋다.
예를 들면, onResume()에서 쓰레드를 실행시켰으면, onPause()가 호출될 때, 아직 쓰레드가 실행중이면 정리를 해주면 된다.
*onPause()가 호출되서 App(또는 Activity)이 일시정지된 상태라면 안드로이드 시스템에서 필요에 따라 완전이 죽일 수 있기 때문에 그 이후의 작업을 못할 수도 있다는 점을 유의해야한다.

onSaveInstanceState()
Activity가 전면에서 Background로 숨는 경우 호출된다. [Case2, Case4, Case5, Case8]
현재의 Activity 상태를 저장하려면 이 함수를 구현한다.
호출될 때, Bundle 인스턴스를 넘겨주는데 이를 이용해서 저장하면 되는데, 예를 들어 에디트박스에 입력된 문자열 등을 저장해 두면 된다.

onRestoreInstanceState()
onSaveInstanceState() 함수에서 저장했던 내용은 onCreate()에서 Bundle 인스턴스로 넘겨받는데, onRestoreInstanceState()에서도 같은 내용을 받을 수 있다.
주의할 점은 onRestoreInstanceState()는 정상적인 상황에서는 호출되지 않는다.
테스트 결과 일반적인 상황이 아닌, 디바이스의 화면회전이 발생할 때[Case5] , 강제종료 후 제시작 할 때 [Case6] 발생했다.

onPostCreate() 와 onPostResume()
이 두 함수는 시스템 상에서 마지막 초기화 작업을 목적으로 만들어진 것으로 일반적으로 어플리케이션 작성시에는 구현할 필요가 없다고 한다.
 
onLowMemory()
시스템 메모리가 부족할 때 호출된다고 하나, 테스트로 발생시키기 어려워 생략했다.
안드로이드 Dev 사이트를 참고하여 설명하면, 이 함수가 정확히 호출되는 시점은 명확하지 않고, 다만, Background에서 실행하는 프로세스가 죽임을 했을 때, 호출된다고 한다. 시스템은 현재 Foreground에 있는 App에게 메모리를 좀 확보해 주십사~하고 호출해 주는 것이다. 즉, 이 함수에서는 필요없는 리소스를 최대한 확보하는 코드를 넣어주면 시스템에서 매우 고마워한다는 것이다.
이 함수가 return되는 순간 시스템은 GC를 수행한다고 한다. 
 
[Case 1]
App 초기실행



[Case 2] 
HOME 버튼(하드웨어키)  눌러서 안드로이드 홈으로 이동시


TestApp 아이콘을 선택하여 재시작



[Case 3]
이전버튼(하드웨어키)을 눌러서 안드로이드 홈으로 이동시

 
 다시, TestApp 아이콘을 선택하여 재시작



[Case 4]
TestApp 실행상태에서 SMS수신, 전화수신 등이 되었을 때,
또는, HOME버튼(하드웨어키)를 길게 눌러 최근 사용한 다른 App을 띄워 
TestApp이 Background이동시 


SMS수신, 전화수신, 다른 앱에서 이전버튼(하드웨어키)를 눌러 뒤에 숨어있던 TestApp이 Foreground로 올라올 때



[Case 5] 
디바이스를 회전시켜, 세로에서 가로화면으로 전환 시킬때, 또는 그 반대의 경우



[Case 6]
프로세스 킬러App(마켓에 널려있는..)으로 TestApp을 강제로 죽인 후, 재 실행 시켰을 때


 
[Case 7]
TestApp에 있는 버튼을 눌러 finish()를 강제로 호출할 때



[Case 8]
새로운 Activity를 띄웠을 때


새로 띄웠던 Activity를 닫았을 때



[참고자료]
    안드로이드 프로그래밍2 / 마크 머피 / 에이콘출판사
    http://developer.android.com/reference/android/app/Activity.html 
    http://androidhuman.tistory.com/246  

[전체소스]
    다운로드 
Posted by 데브로망스
Android2012. 1. 11. 10:51

[Screen size]
단말기 스크린의 대각선을 측정한 실제 물리적인 사이즈
안드로이드에서는 수 많은 스크린 사이즈를 아래와 같이 딱 4가지로 분류했다.

스크린 사이즈 : Small, Normal, Large, Extra Large


[Screen density]
스크린 상의, 일정 물리적 영역 안에 들어가는 픽셀의 양이다. 보통 dip로 표기하는데, "dots per inch"의 약자다. (Density는 밀도, 농도라는 의미인데, 정확한 해석은 모르겠다. 여기서는 그냥 덴씨티로 표기했다. ^^)
안드로이드에서는 수 많은 스크린 덴씨티(Density)를 아래와 같이 4개로 분류했다.

스크린 덴씨티 :  Low, Medium, High, Extra High

예를들어, 단말기의 스크린이 Low 덴씨티인 속성를 갖는 경우, 이 스크린은 Normal 또는 High 덴씨티 속성을 갖는 스크린보다 "일정 영역에 들어가는 픽셀의 양이 적다."라고 생각하면 된다.


[Orientation]
"Orientation"이란 말에는 "신입생 오리엔테이션"이란 뜻도 있지만, "방향성"이라는 의미도 있다. ^^
여기서의 "Orientation"은 특히, 사용자가 단말기를 바라보는 관점에서의 "스크린의 방향성"이다.
안드로이드에서는 아래와 같이 두 가지로 분류한다.

방향성 : Landscape, Portrait 

스크린의 가로세로비에 따라 Landscape는 Wide를 의미하고, Portrait는 Tall이라고 해석하면 된다.
안드로이드 단말기에 방향성이 있다는 것은 UI 개발시 주의해야할 점을 내포하고 있다.
하나는 단말기에 따라 디폴트 방향성이 무엇인지를 고려해야 한다는 점이고, 다른 하나는 디폴트 값과 상관없이 런타임 시에도 사용자가 단말기를 회전시킬 때, 방향성이 변경된다는 점이다.
 

[Resolution]
스크린에 존재하는 물리적인 픽셀의 총 개수를 의미한다. 보통 해상도라고 부른다.
안드로이드 App.에서 다양한 스크린을 지원하려고 할 때, 해상도를 직접 이용하지 않는다. 단지, 어플리케이션 단에서는 위에서 분류했던 4개의 "스크린 사이즈"와 4개의 "스크린 덴씨티"만을 고려하면 된다.


[Density-independent pixel (dp or dip)]
아마도 제일 중요한 단위다.
App개발 시, UI Layout을 잡을 때 덴씨티에 독립적인 방법으로 레이아웃의 길이, 크기, 위치를 표현하기 위해 반드시 사용해야하는 "가상의 픽셀 단위"이다.
DIP는 160 dip 스크린 위의 1개의 물리적 픽셀과 같다. 여기서 160은 "Medium" 덴씨티 스크린을 갖는 시스템(디바이스 ; 초창기 HVGA 안드로이드 기기)을 기준을 정하고, 이에 따라 기준으로 부여한 dip 값이다. 런타임 시, 시스템은 필요에 따라 개발하는 스크린의 실제 덴씨티에 기초하여, 어떠한 dip 단위의 크기조정(scaling)을 확실하게 담당해준다.

DIP 단위를 스크린 픽셀 단위로 변경하는 공식은 아래와 같다.

px = dp * (dip / 160)

예를 들면 240dip 속성을 갖는 단말기의 스크린에서는 "1dp * 240dpi / 160dp"로 계산하여, 1dp는 1.5에 해당하는 물리적인 픽셀이라는 것을 알 수 있다. 따라서 50dip는 160dpi화면에서 50px이지만, 240dpi 화면에서는 75px로 그려진다.
그리고, 위의 공식을 적절히 치환하면 아래의 공식을 만들 수 있다.

dp =  px * (160 / dp)

만약, 기존의 작업자가 UI Layout을 잡을 때, XML 속성값으로 px단위를 사용했다면, 위의 공식을 이용하여, 계산한 후, dp값으로 변경해 주는 것이 좋다. 예를 들어, 아래와 같이 ImageView의 오른쪽 여백을 20px이라고 표기되어있고, UI Layout의 기준이 되는 단말기의 스크린 덴씨티가 240dpi이라면, 20px * 160dp / 240dp를 계산하여, 13dp(약 13.33333)로 변경해 주면 된다.

<ImageView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/image_view"
        android:layout_marginRight="20px"  ==> "13dp" 로 변경
        android:scaleType="fitCenter"/>

* 안드로이드 UI개발을 할 때는 시스템이 서로 다른 덴씨티를 갖는 스크린에 UI가 적절히 배치할 수 있도록, 언제나 dp(or dip) 단위를 사용해야 한다.

 
[Scaled Pixel (sp)] 
가변 픽셀이라고 해석하기도 하는데, 안드로이드 시스템에서는 사용자가 지정한 글꼴 크기(System.Settings 설정에 FONT_SCALE로 지정한 값)에 맞춰 sp값의 배율을 정한다. 
보통, 일반적인 이미지나 뷰는 dp(dip)값으로 부여하고, 텍스트 사이즈는 sp값을 사용하기를 강하게 권장하고 있다.



* 참고자료
안드로이드 프로그래밍2 / 마크 머피 / 에이콘출판사
http://developer.android.com/guide/practices/screens_support.html 
http://www.androidcoder.org/blog/compare-difference-textsize-unit-dp-sp-pt-px-in-mm-android-application/







Posted by 데브로망스
Android2012. 1. 9. 13:43
[목표]
Activity 에 ImageView를 추가하고, ImageView를 클릭했을 때, onTouch 이벤트를 받는다.
이벤트는  ACTION_DOWN, ACTION_UP, ACTION_MOVE를 받아야 한다.

ImageButton이 아닌, ImageView 를 하나 만든다.

<linearlayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center"
    android:orientation="vertical" >

    <imageview
        android:id="@+id/iv_facebook"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:background="@drawable/img_facebook"
        android:contentdescription="@string/todo" >
    </imageview>

</linearlayout>


화면에 보여줄 이미지뷰에 onTouch 리스너를 등록하고 각각의 메시지를 받으면 로그를 찍어본다.
public class OnTouchDemoActivity extends Activity implements OnTouchListener {
    @Override
	public void onCreate(Bundle savedInstanceState) {

		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		ImageView ivFacebook = (ImageView) findViewById(R.id.iv_facebook);
		ivFacebook.setOnTouchListener(this);
	}
    
	@Override
	public boolean onTouch(View v, MotionEvent event) {
		
		int action = event.getAction();
		int id = v.getId();
		
		if(action==MotionEvent.ACTION_DOWN) {
			if(id == R.id.iv_facebook) {
				Log.d("TAG", "OnTouch : ACTION_DOWN");
			}
        }
        else if(action==MotionEvent.ACTION_UP){
        	if(id == R.id.iv_facebook) {
        		Log.d("TAG", "OnTouch : ACTION_UP");
    		}
        }
        else if(action==MotionEvent.ACTION_MOVE){
        	if(id == R.id.iv_facebook) {
        		Log.d("TAG", "OnTouch : ACTION_MOVE");
    		}
        }
		return false;
	}
}



[문제점]
이미지 버튼을 아무리 클릭해도, 위와 같이 ACTION_DOWN 메시지만 불려지고, UP, MOVE 메시지는 발생하지 않는다.

[해결책]
ImageView의 속성 중 "clickable"속성을 켠다(On).

ImageView ivFacebook = (ImageView) findViewById(R.id.iv_facebook);
ivFacebook.setOnTouchListener(this);
ivFacebook.setClickable(true);  // If you set this, you can get all the touch-actions
 또는,
<imageview
    android:id="@+id/iv_facebook"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:background="@drawable/img_facebook"
    android:clickable="true"
    android:contentdescription="@string/todo" >
</imageview>


이후에는 DOWN, MOVE, UP 순서로 메시지들이 전달되는 것을 알 수 있다.



[전체소스]
    다운로드 


Posted by 데브로망스
Android2011. 9. 22. 23:28

Activity를 이것 저것 띄우고 닫고 하다보면, 과연 내가 잘 하고 있나? 불안할 때가 있다.
더구나, 인텐트에 액티비티 관련 플래그를 사용하기 시작하면 그 불안감은 고조된다.

실제로 단 3개의 Activity가  destroy()되지 않은 채, 19개가 되는 현상을 겪었다. -0-a
물론, Activity의 생명주기를 로그로 찍어서 확인하는 것도 좋겠지만,
그래도 딱! Activity Stack의 모습을 볼 수 있으면 좋겠다는 생각에 방법을 찾아봤다.


* 방법 : 커멘드 창에서 "adb shell dumpsys activity" 실행

매우 긴 영문들이 뒤섞여 보인다.
처음에는 매우 난감했지만, Activity를 이래저래 띄우면서 테스트 한 결과에 의하면,
"Activity Stack"과 "Running Activities" 부분을 보면 된다.

"Activity Stack"에는 스택 순서대로 Hist #2, Hist #1, Hist #0 순으로 표기되어 있다.
즉, 단말기의 [이전]버튼을 누르면 ActivityA가 닫히고, 한 번 더 누르면 MainActivity가 닫힌다.

작업하는 중간중간에 다시 실행시켜보면, 정확히 화면에 보이는 Activity가 "Activity Stack" 로그의 최상위에 보여지게 된다.

Hist #0는 보통 안드로이드 런처(홈) Activity이다.



C:\Users\Juno>adb shell dumpsys activity


   ... 중략 ...

  

  Activity stack:

  * TaskRecord{461e6d10 #42 A com.juno.activitytest}

    clearOnBackground=false numActivities=2 rootWasReset=false

    affinity=com.juno.activitytest

    intent={act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.juno.activitytest/.MainActivity}

    realActivity=com.juno.activitytest/.MainActivity

    lastActiveTime=6762895 (inactive for 6s)

    * Hist #2: HistoryRecord{46096400 com.juno.activitytest/.ActivityA}

        packageName=com.juno.activitytest processName=com.juno.activitytest

        launchedFromUid=10026 app=ProcessRecord{461537b0 1050:com.juno.adpd.activitytest/10026}

        Intent { flg=0x20000000 cmp=com.juno.activitytest/.ActivityA (has extras) }

        frontOfTask=false task=TaskRecord{461e6d10 #42 A com.juno.livepoint}

        taskAffinity=com.juno.activitytest

        realActivity=com.juno.activitytest/.ActivityA

        base=/data/app/com.juno.activitytest-1.apk/data/app/com.juno.activitytest-1.apk data=/data/data/com.juno.activitytest

        labelRes=0x7f080000 icon=0x7f02000e theme=0x1030006

        stateNotNeeded=false componentSpecified=true isHomeActivity=false

        configuration={ scale=1.0 imsi=0/0 loc=ko_KR touch=3 keys=1/1/2 nav=2/1orien=2 layout=34 uiMode=17 seq=2}

        launchFailed=false haveState=false icicle=null

        state=RESUMED stopped=false delayedResume=false finishing=false

        keysPaused=false inHistory=true persistent=false launchMode=0

        fullscreen=true visible=true frozenBeforeDestroy=false thumbnailNeeded=false idle=true

    * Hist #1: HistoryRecord{46130618 com.juno.activitytest/.MainActivity}

        packageName=com.juno.activitytest processName=com.juno.activitytest

        launchedFromUid=2000 app=ProcessRecord{461537b0 1050:com.juno.activitytest/10026}

        Intent { act=android.intent.action.MAIN cat=[android.intent.category.LAUNCHER] flg=0x10000000 cmp=com.juno.activitytest/.MainActivity }

        frontOfTask=true task=TaskRecord{461e6d10 #42 A com.juno.activitytest}

        taskAffinity=com.juno.activitytest

        realActivity=com.juno.activitytest/.MainActivity

        base=/data/app/com.juno.activitytest-1.apk/data/app/com.juno.activitytest-1.apk data=/data/data/com.juno.activitytest

        labelRes=0x7f080000 icon=0x7f02000e theme=0x1030006

        stateNotNeeded=false componentSpecified=true isHomeActivity=false

        configuration={ scale=1.0 imsi=0/0 loc=ko_KR touch=3 keys=1/1/2 nav=2/1orien=2 layout=34 uiMode=17 seq=2}

        launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=812]

        state=STOPPED stopped=true delayedResume=false finishing=false

        keysPaused=false inHistory=true persistent=false launchMode=0

        fullscreen=true visible=false frozenBeforeDestroy=false thumbnailNeeded=false idle=true

  * TaskRecord{461626c0 #2 A com.android.launcher}

    clearOnBackground=true numActivities=1 rootWasReset=true

    affinity=com.android.launcher

    intent={act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10600000 cmp=com.android.launcher/com.android.launcher2.Launcher}

    realActivity=com.android.launcher/com.android.launcher2.Launcher

    lastActiveTime=6749602 (inactive for 19s)

    * Hist #0: HistoryRecord{4615d870 com.android.launcher/com.android.launcher2.Launcher}

        packageName=com.android.launcher processName=com.android.launcher

        launchedFromUid=0 app=ProcessRecord{46162bb0 2292:com.android.launcher/10012}

        Intent { act=android.intent.action.MAIN cat=[android.intent.category.HOME] flg=0x10000000 cmp=com.android.launcher/com.android.launcher2.Launcher }

        frontOfTask=true task=TaskRecord{461626c0 #2 A com.android.launcher}

        taskAffinity=com.android.launcher

        realActivity=com.android.launcher/com.android.launcher2.Launcher

        base=/system/app/Launcher2.apk/system/app/Launcher2.apk data=/data/data/com.android.launcher

        labelRes=0x7f0c0002 icon=0x7f02005a theme=0x7f0d0000

        stateNotNeeded=true componentSpecified=false isHomeActivity=true

        configuration={ scale=1.0 imsi=0/0 loc=ko_KR touch=3 keys=1/1/2 nav=2/1orien=2 layout=34 uiMode=17 seq=2}

        launchFailed=false haveState=true icicle=Bundle[mParcelledData.dataSize=4480]

        state=STOPPED stopped=true delayedResume=false finishing=false

        keysPaused=false inHistory=true persistent=false launchMode=2

        fullscreen=true visible=false frozenBeforeDestroy=false thumbnailNeeded=false idle=true



  Running activities (most recent first):

    TaskRecord{461e6d10 #42 A com.juno.activitytest}

      Run #2: HistoryRecord{46096400 com.juno.activitytest/.ActivityA}

      Run #1: HistoryRecord{46130618 com.juno.activitytest/.MainActivity}

    TaskRecord{461626c0 #2 A com.android.launcher}

      Run #0: HistoryRecord{4615d870 com.android.launcher/com.android.launcher2.Launcher}


  mPausingActivity: null

  mResumedActivity: HistoryRecord{46096400 com.juno.activitytest/.ActivityA}

  mFocusedActivity: HistoryRecord{46096400 com.juno.activitytest/.ActivityA}

  mLastPausedActivity: HistoryRecord{4614cb90 com.juno.activitytest/.DetailActivity}


  mCurTask: 42


   ... 중략 ...


C:\Users\Juno>



그림으로 그리면, 요런 모습이겠다.



[여담]
이런 로그 메시지를 분석하는 부분은 꽤 자신없는 부분이네요.
혹시나, 잘못된 내용은 가감없이 지적바랍니다.


Update #1

누군가 고맙게도 stack만 추려서 볼 수 있게, 만들어줬네요.

sed 명령어를 사용하려면 GnuWin32를 설치해야합니다.

http://sourceforge.net/projects/getgnuwin32/files/getgnuwin32/


  adb shell dumpsys activity activities | sed -En -e "/Stack #/p " -e "/Running activities/,/Run #0/p"

출처 : http://stackoverflow.com/a/31107447/536078



Posted by 데브로망스