오늘의 강좌는 미리 공지한대로 TabActivity 내에서 ActivityGroup을 사용할 경우의 Navigation 처리에 대한 내용입니다.

 1.     TabActivity의 구성

먼저 TabActivity가 동작하는 간단한 원리를 정리해 보면 다음과 같습니다.

1) TabActivity에 생성되어 있는 TabHost를 얻는다.

getTabHost();

2) TabHost에 통해 TabSpec을 생성하면서 Indicator Intent를 지정한다.

Intent intent = new Intent().setClass(this,

com.mk.counsel.group.ViewCounselGroup.class);

spec = tabHost.newTabSpec("tab01").setIndicator(

                new TabView(this, R.drawable.tab1_selector, "tab01")

                ).setContent(intent);

3) TabHost TabSpec을 등록한다.

tabHost.addTab(spec);

 

위의 과정에서 TabSpec에 적용되는 Intent가 하나의 Activity 만을 처리하는 형태와 여러 Activity가 하나의 Tab 내에서 관리되는 경우(그림 참조)로 구성될 수 있는데 후자의 경우에 문제가 발생할 수 있습니다.

단순히 Activity를 변경하기 위해서 startActivity() 함수를 사용할 경우 TabActivity 새로운 Activity로 변경되어 버려서 기존의 Tab 화면을 유지할 수 없다는 문제가 생기는데 이런 문제를 해결해 주기 위해서 등장한 것이 ActivityGroup 입니다.

 

2.     ActivityGroup

ActivityGroup은 철저하게 Activity수행에 대한 관리와 화면처리(View)를 분리하여 관리하기 위한 용도로 생각하시면 됩니다. Activity 수행에 대한 관리는 전적으로 LocalActivityManager에 위임하고 각종 Event 처리에 대해서는 Activity 자체에 맡기고 생성된 최종 화면(View)만 본인이 소유하고 있도록 구성되어 있습니다.(그림참조)

문제는 전형적인 ActivityGroup은 모든 Activity의 관리 권한을 LocalActivityManager에 위임한 상태이고 View만을 제공 받으므로 자체적으로 Navigation 처리를 할 수 없다는 것입니다.바로 여기서 Navigation을 관리할 대상이 정해집니다. 바로 ActivityGroup이 유일하게 소유할 수 있는 View가 바로 그것입니다. View들을 ActivityGroup이 관리함으로 Navigation이 가능진다는 것입니다.

 

3.     실제 적용

다음의 코드를 보시기 바랍니다.

public class NavigationGroupActivity extends ActivityGroup {
        ArrayList<View> history; // View들을 관리하기 위한 List
       NavigationGroupActivity group; // Activity들이 접근하기 위한 Group
        @Override
       protected void onCreate(Bundle savedInstanceState) {
             // TODO Auto-generated method stub
             super.onCreate(savedInstanceState);
             history = new ArrayList<View>();
             group = this;
       }
        public void changeView(View v)  { // 동일한 Level의 Activity를 다른 Activity로 변경하는 경우

             history.remove(history.size()-1);
             history.add(v);
             setContentView(v);
       }
       public void replaceView(View v) {   // 새로운 Level의 Activity를 추가하는 경우
             Log.d("MK","REPLACE VIEW...");
             history.add(v);   
             setContentView(v); 
       }   
       public void back() { // Back Key가 눌려졌을 경우에 대한 처리
             if(history.size() > 1) {   
                 history.remove(history.size()-1);   
                 setContentView(history.get(history.size()-1)); 
             }
            else {   
                 finish(); // 최상위 Level의 경우 TabActvity를 종료해야 한다.
             }   
       }  
       @Override  
       public void onBackPressed() { // Back Key에 대한 Event Handler  
             group.back();   
             return;
       }
}

위에서 언급한 내용대로 ActivityGroup이 관리할 수 있는 유일한 자원이 View 이므로 View를 저장할 List를 하나 생성합니다. 기존에는 Activity가 변경될 때마다 LocalActivityManager가 넘겨주는 View ActivityGroup이 받아서 화면 처리만 하던 것을 조금 능동적으로 View를 직접 관리하는 것으로 바뀌었습니다. Back Key에 대한 Event가 발생하면 이를 처리할 Handler에서 ActivityGroup에게 View List에서 화면 전환 처리를 해 주라고 직접 요청하는 것입니다.

그러면 Back Key 처리는 알겠는데 새로운 Activity를 추가할 경우는 어디서 누가 호출을 해 주는지가 궁금할 것입니다. 다음의 코드를 보시기 바랍니다.

public class NavigationActivity extends Activity {
        public void goNextHistory(String id,Intent intent)  { //앞으로 가기 처리
                NavigationGroupActivity parent = ((NavigationGroupActivity)getParent());
                View view = parent.group.getLocalActivityManager()
                     .startActivity(id,intent)   
                     .getDecorView();   
                  parent.group.replaceView(view);
       }
        
        @Override
       public void onBackPressed() { //뒤로가기 처리
                  NavigationGroupActivity parent = ((NavigationGroupActivity)getParent());
                  parent.back();
       }
}

위의 코드를 보시면 해당 Activity를 소유하고 있는 ActivityGroup getParent() 함수로 구한 다음에 실제 Activity의 실행을 LocalActivityManager에 맡기고 있습니다.

LocalActivityManager에서 실행된(startActivity() 함수 호출) Activity View getDecoderView() 함수를 통해 얻은 다음 이 View ActivityGroup view에 적용하는 모습을 볼 수 있습니다. 반대로 Back Key가 발생하면 Parent back 함수를 호출하여ActivityGroup내의 View를 조정하여 Navigation처리를 하도록 요청합니다.

자 그럼 이제 두 클래스를 상속받은 실제 ActivityGroup Activity가 어떻게 적용되는지를 살펴 보겠습니다. 
아래의 코드를 보시기 바랍니다.

public class ViewCounselGroup extends NavigationGroupActivity {
       @Override
       protected void onCreate(Bundle savedInstanceState) {
             // TODO Auto-generated method stub
             super.onCreate(savedInstanceState);
             Intent intent = new Intent(this,ViewCounselMainActivity.class);
             intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_SINGLE_TOP);
             View view = getLocalActivityManager().startActivity("CounselMainActivity",intent)
                .getDecorView();   
            replaceView(view);   
       }
 
       @Override
       public void onBackPressed() { // Back Key에 대한 처리 요청
             super.onBackPressed();
       }
}

ViewCounselGroup이라는 ActivityGroup에서는 LocalActivityManager를 통해 초기에 실행할 Activity ViewCounselMainActivity라는 클래스를 Intent로 실행합니다. 이때 Intent에 적절한 Flag를 설정해 주지 않으면 LocalActivityManager가 관리하는 Stack ActivityView의 저장 구조와 다른 형태로 쌓이게 되므로 Navigation이 동기화 되지 못합니다.따라서 반드시 위의 Intent Flag를 설정해 주셔야 Activity View의 순서가 동기화 됩니다. 또한 Back Key에 대한 처리는 모든 Activity에서 처리가 되는 것이 아니라 ActivityGroup에서 실행한 맨처음 Activity에서만 Event를 받을 수 있다는 점을 꼭~~~ 기억 하셔야 합니다.

참고로 Intent에 대한 Flag 설명은 다음 주소를 참고하시기 바랍니다.

http://chihun80.springnote.com/pages/6423199

 Intent가 설정되면 LocalActivityManager를 통해 Activity를 실행하고 이때 생성된 ViewActivityGroup에 적용해 주어야 하는데 그냥 적용하면 안되고 replace()라는 함수를 통해 View를 관리하는 List에 등록한 후 화면 적용을 해 주셔야 합니다.

 

public class ViewCounselMainActivity extends NavigationActivity 
implements OnItemClickListener{
 
        ……중략……
       @Override
       protected void onCreate(Bundle savedInstanceState) {
             // TODO Auto-generated method stub
             super.onCreate(savedInstanceState);
             setContentView(R.layout.view_counsel_list);
             adapter = new CounselAdapter(this,items);
             ListView view = (ListView)findViewById(R.id.counsel_list);
             view.setAdapter(adapter);
             view.setOnItemClickListener(this);
       }
 
       @Override
       public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
             Intent intent = new Intent(ViewCounselMainActivity.this,
                                             com.mk.counsel.ViewCounselDetailActivity.class);
             intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP |Intent.FLAG_ACTIVITY_SINGLE_TOP);
             intent.putExtra("id", items.get(position).id);
             goNextHistory("ViewCounselMainActivity",intent);
       }
}

동작에 대한 Event 처리 등은 실행된 Activity 내에서 관리가 된다고 말씀 드렸듯이 GroupActivity에서 처음 실행된 ViewCounselMainActivity 내에서 특정 아이템이 선택된 경우에 ItemClick 핸들러를 처리하고 다음 Level Activity 이동을 하려고 한다면 goNextHistory() 통해 이동하고자 하는 Intent 넘겨주면 GroupActivity 내부에서 LocalActivityManager 통해 Intent 수행하고 View GroupACtivity ViewList 추가하고 화면을 전환하는 작업을 줍니다.

 

4.  마무리

TabSpec Intent를 설정할 때 GroupActivity를 먼저 등록하고 GroupActivity 내에서 처음 호출할 ActivityLocalActivityManager를 통해 실행하고 View를 얻는 과정이 핵심입니다.

 이걸로 TabActivity ActivityGroup간의 관계 정리와 ActivityGroup에서 LocalActivityManager를 통해 Activity View를 관리하는 메커니즘을 살펴 보았습니다. 먼저 그림을 한번 보시고 전체 흐름을 이해하신 후에 해당 소스를 보시면 훨씬 이해가 쉬우실 것으로 생각됩니다.

 다음 글에는 TabActivity 위에 TitleBar를 각 탭별로 Custom하게 만들어서 TitleBar의 메뉴와 TabActivity의 각 Tab이 상호 연동되어 구동되는 예제를 가지고 설명을 드리겠습니다.

완벽하게 돌아가는 예제를 만들어 전체 소스를 올리고 싶지만 예제를 따로 만들만한 개인적인 시간이 조금 부족한 관계로 핵심 코드만을 보여 드렸습니다. 다음 번 강좌를 기대해 주세요. ^^