반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리

[CookBook] Fragments 이해하기 -5-

2013. 9. 28. 23:28 | Posted by 솔웅


반응형

Creating Fragments Dynamically at Runtime



layout xml 파일에 fragment 가 <fragment> element를 사용해서 정의 되었어도 runtime 시 자바코딩에 의해서 이 fragment를 재정의 할 수 있습니다. 액티비티에 fragment를 dynamic 하게 생성,추가,replace 하기 위해 FragmentManager를 사용하실 수 있습니다.



이와 관련한 예제를 다룰건데요. FragmentsByCodeApp이라는 안드로이드 프로젝트를 생성하겠습니다. 이 앱에서는 두개의 fragment를 dynamic하게 생성할 겁니다. Fragment1과 Fragment2인데요. Fragment1은 선택 위젯인 ListView를 가지게 되고 여기에 product들이 display 될 겁니다. Fragment2는 TextView를 가지고 있고 Fragment1의 리스트뷰에서 선택된 제품이 display 될 겁니다.



이 두 Fragment들을 표시하기 위해 적당한 layout xml을 만들어야 합니다.



activity_fragments_by_code_app.xml



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <LinearLayout
        android:id="@+id/frag1_container"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:orientation="horizontal"/>
    <LinearLayout
        android:id="@+id/frag2_container"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_weight="0"
        android:orientation="horizontal"/>
</LinearLayout>




여기 보시면 두개의 LinearLayout가 있습니다. 각각 id를 가지고 있죠. 하나는 frag1_container이고 다른 하나는 frag2_container 입니다. 자바 코드에서 이 아이디를 가지고 동적으로 fragment들을 생성하고 다룰 겁니다. 전체 틀을 구성하는 LinearLayout은 horizontal 로 돼 있죠. 그러니까 두 fragment들은 좌우로 배치 될 겁니다. 첫번째 fragment에는 제품을 표시할 리스트뷰가 들어갈 것이고 두번째 fragment에는 이 제품을 표시할 텍스트 뷰가 들어갈 겁니다. 이 두 fragment들에 view를 정의하기 위해 두개의 xml 파일을 만들겠습니다. fragment1.xml과 fragment2.xml 인데요. res/layout 폴더 밑에 만들어 넣습니다.
첫번째 fragment에 리스트뷰를 넣기 위해 아래와 같이 만듭니다.



fragment1.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:background="#0000FF"  >
    <ListView
        android:id="@+id/products_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:drawSelectorOnTop="false" />
</LinearLayout>



안에 리스트 뷰가 있죠? id 는 products_list 로 했습니다. 두개의 fragment를 구별하기 쉽게 첫번째는 배경색을 파란색으로 했습니다.
두번째 fragment에 텍스트뷰를 정의하기 위해 아래와 같은 xml 파일을 만듭니다.


fragment2.xml



<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >   
    <TextView 
        android:id="@+id/selectedopt"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text=" Please select a product "
        android:textSize="@dimen/text_size"
        android:textStyle="bold"  />  
</LinearLayout>



selectedopt라는 아이디를 가진 텍스트뷰가 있습니다. 그리고 text가 이미 정해져 있는데요. "Please select a product" 라고 돼 있습니다. 텍스트 사이즈는 dimention 리소스 안에 있는 사이즈를 불러다 쓰구요.
디바이스의 화면 크게에 맞게 리스트 아이템들을 표시하기 위해 아래 xml 하일을 하나 더 만들겠습니다.



list_item.xml



<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="6dp"
      android:textSize="@dimen/text_size"
      android:textStyle="bold" />
     

여기를 보면 각 아이템은 리스트 뷰 내에서 6dp 만큼 패딩을 가질 겁니다. 그리고 bold체로 설정을 했고 폰트 싸이즈는 dimention 리소스 안에 있는 사이즈를 불러다 씁니다.
이제 fragment1.xml과 fragment2.xml 에 설정된 UI를 로드할 자바코드를 만들 차례입니다. 두개의 자바코드가 만들어 져야겠죠. 하나는 Fragment1Activity.java 이고 나머지 하나는 Fragment2Activity.java 입니다.


이 자바 코드는 다음 글에서 다뤄 보겠습니다.

반응형

[CookBook] Fragments 이해하기 -4-

2013. 9. 27. 22:06 | Posted by 솔웅


반응형

Understanding the Role of FragmentManager and FragmentTransaction in Handling Fragments




FragmentManager는 이름 그대로 액티비티 내에서 fragments를 관리하는데 사용됩니다. 액티비티에서 사용 가능한 fragment에 접근할 수 있게 해 주고 fragments를 추가하거나 제거하거나 재배치할 필요가 있을 때 FragmentTransaction을 사용합니다. FragmentManager에 접근하려면 getFragmentManager() 메소드를 사용합니다.



FragmentManager fragmentManager = getFragmentManager();



fragment transaction을 사용하려면 FragmentTransaction의 인스턴스를 사용합니다.


FragmentTransaction fragmentTransaction = fragmentMaanger.beginTransaction();




새로운 FragmentTransaction은 FragmentManager의 beginTransaction() 메소드를 사용해서 생성합니다.



private static final String TAG1 = "1";
FragmentManager fragmentManager = getFragmentManager()
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment1Activity fragment = new FragmentActivity();
fragmentTransaction.add(R.id.fragment_container, fragment, TAG1);
fragmentTransaction.commit();


여기 보시면 Fragment1Activity는 fragment의 자바 클래스입니다. 이것은 XML 파일로부터 fragment의 UI를 로드해서 사용하죠. 밑에서 두번째 줄을 보시면 fragment_container가 id라는 걸 알 수 있을 겁니다. 아마 이 id는 LinearLaout이나 FrameLayout 일 거라고 예상할 수 있을 겁니다. 그리고 commit()메소드가 보이는데요. 이것은 변화된 사항을 적용하기 위해 사용하는 메소드 입니다. 이 commit()은 즉시 발효되는 것은 아니고 쓰레드가 준비 됐을 때 발효가 될 겁니다. 그러니까 방금 생성한 fragment에 대한 reference를 곧바로 get하는 것은 문제가 생길 수 있습니다.



Note
fragment를 dynamic 하게 추가하려면 container view는 fragment 가 display될 view 가 있는 layout에 존재해야 합니다.



fragment를 추가하기 전에 이것이 존재하는지 확인할 필요가 있을 겁니다. 아래처럼 하시면 됩니다.



private static final String TAG1 = "1";
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
if(null == fragmentManager.findFragmentByTag(TAG1)){
    Fragment1Activity fragment = new Fragment1Activity();
    fragmentTransaction.add(R.id.fragment_container, fragment, TAG1);
}
fragmentTransaction.commit();



FragmentManager의 findFragmentByTag() 메소드가 주어진 tag의 fragment가 있는지 여부를 체크 하고 있습니다. 이 findFragmentById() 메소드는 activity layout 에 추가된 fragment를 identify 합니다. 아니면 findFragmentByTag() 메소드를 사용할 수도 있습니다. 위에 있는 Fragment1Activity는 fragment의 layout 파일에서 정의된 view들이 로드되는 자바 클래스 입니다.



fragment나 content를 replace하기위해 FragmentTransaction의 replace()메소드를 사용합니다.



private static final String TAG2 = "2";
fragmentTransaction.replace(R.id.fragment_container, fragment2, TAG2);



여기 보시면 fragment2의 view는 액티비티 layout의 fragment_container안에있는 view를 다시 replace할 겁니다. fragment를 없애려면 findFragmentById()나 findFragmentByTag()메소드를 사용해서 그 fragment 구분해 내야 합니다. 아래 코드는 findFragmentById()메소드를 사용해서 fragment를 정의하고 그 fragment를 remove 하는 과정을 보여줍니다.


FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragment = fragmentManager.findFragmentById(R.id.fragment);
fragmentTransaction.remove(fragment);
fragmentTransaction.commit();



위에 보시면 없애고자 하는 fragment를 정의하기 위해 findFragmentById()를 사용하신 걸 보실 수 있습니다. 이것을 findFragmentByTag()를 사용하시려면 아래와 같이 사용하실 수 있습니다.
Fragment fragment = fragmentManager.findFragmentByTag(TAG1);

반응형

[CookBook] Fragments 이해하기 -3-

2013. 9. 27. 11:15 | Posted by 솔웅


반응형

Adding and Removing Fragments with Device Orientation



fragments를 사용할 때 가장 큰 잇점 중의 하나는 디바이스를 가로 세로 방향으로 돌려서 볼 때 쉽게 fragment를 넣고 뺄 수 있어서 적당한 layout의 view를 사용자에게 보여질 수 있도록 하는 것입니다. 그럼 이렇게 만들기 위해 이전 글에서 다뤘던 소스들을 약간 수정해 보겠습니다. (안드로이드 전화기에서 세웠을 때는 한개의 fragment만 보여지도록요.)


activity_foreground_fragment.xml 에서 두번째 fragment 부분을 주석 처리 합니다.



<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >
    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment1Activity"
        android:id="@+id/fragment1"
        android:layout_weight="1"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" />
 <!--    <fragment
        android:name="com.androidtablet.foregroundfragmentapp.Fragment2Activity"
        android:id="@+id/fragment2"
        android:layout_weight="0"
        android:layout_width="wrap_content"
        android:layout_height="match_parent" /> -->
</LinearLayout>




이렇게 하는 이유는 오직 1개의 fragment만 화면에 표시하기 위해서죠. 이 경우는 안드로이드 전화기가 세워졌을 때 뿐입니다. 눕혀졌을 때나 태블릿 일 경우에는 두개의 fragments들이 좌우로 배치되어야 하겠죠.
그러면 이런 경우를 위해서 어떤 작업을 해야 합니다. 우선 res 폴더 아래에 layout-land 폴더를 생성하세요. 그리고 그 폴더에 activity_foreground_fragment.app 을 복사해 넣으세요.



전화기가 세워졌다가 옆으로 뉘여졌을 때 이 activity는 layout-land 에 있는 layout xml 파일을 사용해서 화면에 표시해 줄 겁니다. 전화기가 다시 세워지면 layout 폴더 안에 있는 xml 을 사용해서 화면을 갱신시킬거구요.



layout_land 폴더에 있는 activity_foreground_fragment.xml 파일에서는 위에 있는 주석을 풀어 주시면 됩니다.



그 다음에는 Fragment1Activity.java 파일을 수정하시면 됩니다.



public class Fragment1Activity extends Fragment {
    OnOptionSelectedListener  myListener;
    boolean large, xlarge;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Context c = getActivity().getApplicationContext();
        View vw = inflater.inflate(R.layout.fragment1, container, false);
        String[] products={"Camera", "Laptop", "Watch",  "Smartphone", "Television"};
        large = ((getResources().getConfiguration().screenLayout  & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_LARGE);             
        xlarge =((getResources().getConfiguration().screenLayout  & Configuration.SCREENLAYOUT_SIZE_MASK) == Configuration.SCREENLAYOUT_SIZE_XLARGE);      
        ListView productsList = (ListView) vw.findViewById(R.id.products_list);
        ArrayAdapter<String> arrayAdpt= new ArrayAdapter<String> (c, R.layout.list_item, products);
        productsList.setAdapter(arrayAdpt);        
        productsList.setOnItemClickListener(new OnItemClickListener(){
            @Override
            public void onItemClick(AdapterView<?> parent, View v, int position, long id){                 
                if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE || large || xlarge){                                      
                    myListener.onOptionSelected(((TextView)  v).getText().toString());  
                 } else {
                    Intent intent = new Intent(getActivity().getApplicationContext(),  DisplayItemActivity.class);
                    intent.putExtra("item", ((TextView) v).getText().toString());
                    startActivity(intent);
                 }

            }
        });
        return vw;
    }
   
    public interface OnOptionSelectedListener {
        public void onOptionSelected(String message);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            myListener = (OnOptionSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + "  must implement OnItemClickListener");
        }
    }   
}



위의 굵은 글씨체가 추가된 부분입니다. 리스트뷰의 아이템이 클릭 됐을 때 onItemClick() 메소드를 보면  우선 device가 landscape 이거나 large 혹은 xlarge 즉 태블릿인 경우를 체크합니다. 만약에 그렇다면 fragment2를 보여줍니다. 즉 그럴 경우 onOptionSelected() 메소드가 호출되고 이때 선택된 아이템의 이름이 파라미터로 전달 되는 겁니다. 그러면 Fragment2에서 이 파라미터를 받아서 화면에 출력하겠죠.



위 경우가 아니라면 즉 디바이스가 전화기이고 세워진 상태라면 (Portrait orientation) fragment2는 호출 되지 않을 겁니다. 리스트뷰에 있는 아이템이 선택 됐으면 그 이름이 다른 화면에 표시 되어야 합니다. 다른 화면에 표시된다는 얘기는 다른 activity가 필요하다는 얘기입니다. 그리고 다른 activity로 옮길 경우에는 Intent를 사용해야 되구요. 이 경우 현재의 Context를 같이 보내 주어야 합니다. getActivity().getApplicationContext(). 그리고 불려질 Activity를 정해주고 (DisplayItemActivity.class) 그리고 추가적으로 데이터를 넣어서 이것을 전달합니다. 마지막으로 startActivity() 를 하면 다음 화면이 불려 오겠죠.



그러면 이제 할 일을 DisplayItemActivity.java 를 만들어야 겠네요.
소스 코드는 아래와 같습니다.



public class DisplayItemActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) {
            finish();
            return;
        }
        setContentView(R.layout.fragment2);
        Bundle extras = getIntent().getExtras();
        if (extras != null) {
            String selectedItem = extras.getString("item");
            TextView selectedOpt = (TextView) findViewById(R.id.selectedopt);
            selectedOpt.setText("You have selected "+selectedItem);
        }
    }
}



onCreate() 만 있죠? 맨 처음에 이 기계가 Landscape 모드이면 그냥 끝내고 return 을 해 버립니다. 그리고 난 후 fragment2.xml 을 view로 세팅하죠.
그리고 나서 이전 파일에서 전달해 준 데이터를 체크합니다. getExtra()가 그 일을 합니다. 그리고 item이란 key 값을 이용해서 그 value 값을 selectedItem에 담구요. 그 값을 텍스트뷰에 넣어 줍니다.


이렇게 하면 완성된 겁니다.
그런데 한가지 더 해야 될 게 있는데요.
DisplayItemActivity 라는 Activity가 새로 생겼으면 이것을 AndroidManifest.xml 에 신고를 해야 합니다.
<activity android:name=".DisplayItemActivity" android:label="@string/app_name" /> 을 추가해야 하는데요.
혹시 Manifest 파일에 액티비티 추가하는 방법을 모르시면 관련 글을 보시고 참고하시기 바랍니다.



그리고 한가지 더 해야 할 일이 있는데요.
현재 layout과 layout-land 폴더에만 xml layout 파일이 있습니다. 이건 전화기에만 해당되는데요. 태블릿에도 적용시키기 위해서 두개의 폴더를 더 만듭니다. layout-sw600dp 와 layout-sw720dp 폴더를요. 그리고 이 앱에서는 전화기의 landscape 인 경우와 7인치 10인치 태블릿의 landscape, portrait orientation 모두 그 구성이 같습니다. 그러니까 lauout-land 에 있는 activity_foreground_fragment_app.xml 파일을 복사해서 위 두 폴더 밑에 넣습니다.



이제 이 앱을 돌리면 안드로이드 전화기인 경우에는 Portrait orientation인 경우 첫화면에는 리스트뷰만 표시되고 그 리스트 뷰의 아이템을 클릭했을 때 그 이름이 두번째 화면에서 표시되게 됩니다. landscape orientation으로 바뀌게 되면 태블릿과 마찬가지로 두개의 fragment들이 좌우로 배치되고 좌측의 리스트뷰 아이템을 클릭하면 그 이름이 우측의 텍스트 뷰에 출력되게 됩니다.

반응형