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 데브로망스