티스토리 뷰

Android

Fragment

PeonyF 2020. 4. 11. 01:21
반응형

Fragment: 액티비티 처럼 이용할 수 있는 뷰

재사용 할 수 있는 컴포넌트나 하위 액티비티(다른 액티비티에 재사용 가능)

Fragment를 왜 사용할까?

  • Fragment로 런타임시 화면 일부를 제어 하거나 여러 화면에 재사용할 수 있다.(UI  유연성)
  • Activity에 여러개의 fragments를 재사용 또는 사용하여 창이 여러개인 UI 빌드 가능

 

Fragment 동작방법

  1. Fragment를 Activity layout에 추가
  2. 해당 Fragment는 Acvitiy의 view계층내에 있는 container에 들어간다.
  3. 이 container에서 자체적인 view layout을 정의한다.
  4. Activity layout file에서 <fragement>로 선언 혹은 기본 container에 추가하는 방법으로 사용

Fragment 특징

  • 프래그먼트는 항상 액티비티 내에서 호스팅되어야 한다.
  • 프래그먼트의 lifecycle은 host activity의 lifecycle에 직접적인 영향을 받는다

Activity와 Fragment의 차이점

  • Activity는 UI 유연성이 없어 Activity의 view는 런타임시에 변경 될 수 있다.(따로 코드 관리 필요)
  • Fragment 클래스는 manifests에 추가하지 않는다.(Activity class만 한다)
  • Fragment 클래스는 Activity 클래스를 상속받지 않아 몇몇 메소드는 Fragment에서 사용할 수 없다.
  • ex1. Context 클래스 구현X -> 대신 fragment는 자신의 부모 activity등 다른 객체의 context를 통해 접근한다.
  • ex2. Fragment 클래스에서는 findViewById() 사용 불가 -> getView() fragment root view의 참조값을 얻은 다음 root view의 자식을 찾는다.
  • Fragment는 view를 상속받지 않아서 page가 안보이는 상태에서 수행 가능하다.
  • 주의! Fragment는 view를 담는것이지 view 자체가 보여지는 것이 아니다.
 public void onStart() {
        super.onStart();
//Fragment lifecycle 메서드 구현시, 항상 상위 클래스를 호출해야 한다.
 
        View view = getView();
        if (view != null) {
            TextView title = (TextView) view.findViewById(R.id.textTitle);
            Workout workout = Workout.workouts[(int) workoutId];
            title.setText(workout.getName());
        }
    }

 


만들어보자

Step 1. 사용자 인터페이스에 추가 

  • Fragment class에 onCreateView() 콜백 메서드 구현  (예외 ListFragment)
  • Activity의 onCreate()와 동일한 역할을 한다.
public static class ExampleFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.example_fragment, container, false);
    }
}


Step 2. UI Fragment Hosting 하는 방법

정적으로 추가 (레이아웃 프레그먼트)

  • fragment를 액티비티의 레이아웃 파일 안에서 선언
  • 액티비티 생애동안 fragment를 교체할 수 없다.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <fragment android:name="com.example.news.ArticleListFragment"
            android:id="@+id/list"
            android:layout_weight="1"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
    <fragment android:name="com.example.news.ArticleReaderFragment"
            android:id="@+id/viewer"
            android:layout_weight="2"
            android:layout_width="0dp"
            android:layout_height="match_parent" />
</LinearLayout>

주의! <fragment> 안의 android:name 속성에 fragment의 전체 경로를 포함한 이름을 할당해 fragment를 지정한다.

 

이유)

  1. 안드로이드가 Activity layout 생성
  2.  <fragment>를 인스턴스화
  3. 각각에 대해 onCreateView() 메소드 호출
  4. 각 fragment의 layout 검색
  5. 이후 fragment가 반환한 view(inflate 함수로 return 값)을 <fragement> name에 곧바로 삽입하기 위해서

출처) https://developer.android.com/guide/components/fragments

동적으로 추가 (액티비티에 코드 추가)

  • 프로그래밍 방식으로 프래그먼트를 기존의 ViewGroup에 추가
  • Fragment 교체 및 추가, 컨트롤 가능
  • LinearLayout 자리에 Fragment를 넣는다. (container역할을 한다.)
  • LinearLayout 인 이유: 프래그먼트를 붙이고 싶으면 부모가 될수있는 뷰여야 한다.

부모가 될 수 있는 LinearLayoutRelativeLayout와 같은것을 view componet = view container 이라고 불린다.

 

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent">   
    
    <LinearLayout 
    android:id="@+id/container"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"/>
    
</LinearLayout>
 MyFragment fragment  = new MyFragment();
 FragmentManager fragmentManager = getSupportFragmentManager();
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
 fragmentTransaction.replace(R.id.container,fragment);
 fragmentTransaction.commit();

출처: https://developer.android.com/reference/android/view/ViewGroup


Step 3. 관리 : FragmentManager

  • Activity가 Fragment와 상호작용하려면 Fragment의 참조값를 얻어야 하므로 FragmentManager를 사용하여 Activity가 사용하는 fragment를 추적한다.
  • Fragment를 관리하고 그것의 view activity의 view 계층에 추가하는 책임을 갖는다.
  • 컨테이너뷰의 리소스ID로 UI Fragment를 식별한다
  • 액티비티 레이아웃 내에서 UI를 제공하는 프래그먼트의 경우 액티비티 내에 존재하는 fragment를 findFragmentById()로 가져온다. 
  • Activity class에 아래와 같은 코드를 입력하여 Fragment를 관리한다.
 MyFragment fragment  = new MyFragment();
 FragmentManager fragmentManager = getSupportFragmentManager();
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
 fragmentTransaction.add(R.id.fragment_container,fragment);
 fragmentTransaction.commit();
 fragment = (MyFragment) getSupportFragmentManager().findFragmentById(R.id.fragment_container);
 if(fragment != null){
     // you are good to go, do your logic
 }

 

  • UI를 제공하거나 하지 않는 fragment의 경우 : findFragmentByTag()로 가져온다.
 MyFragment fragment  = new MyFragment();
 FragmentManager fragmentManager = getSupportFragmentManager();
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
 fragmentTransaction.add(R.id.place_holder,fragment,"myFragmentTag");
 fragmentTransaction.commit();
 fragment = (MyFragment) getSupportFragmentManager().findFragmentByTag("myFragmentTag");
 if(fragment != null){
     // ok, we got the fragment instance, but should we manipulate its view?
 }

(출처: https://stackoverflow.com/questions/43520688/findfragmentbyid-and-findfragmentbytag )

 

 

그외 FragmentManager 역할..


Step 4. 수행: FragmentTransaction

  • Fragment Transaction 이란 동시에 fragment에 적용하려는 모든 변경사항의 집합
  • Fragment를 사용해서 런타임시에 화면을 구성/재구성하는 방법

Step 4-1. 트랜잭션 시작하기

  • 트랜잭션에 기록하려는 여러 변경이 시작됨을 안드로이드에 알린다.
 FragmentManager fragmentManager = getSupportFragmentManager();
 //지원 라이브러리에서 fragment를 처리하는 FragmentManager를 반환한다.
 FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
 //Fragment 트랜잭션이 시작된다.
  • 참고) FragmentTransaction을 구성하는 메소드들이 void 대신 FragmentTransaction를 반환하기 때문에 그 메소드들을 연속적으로 연결하여 호출할 수 있다.(https://developer.android.com/reference/androidx/fragment/app/FragmentTransaction)                               단, 같은 컨테이너에 여러개의 fragment를 추가하는 경우, 추가하는 순서에 때라 view 계층에 나타는 순서가 결정된다.
  public class CrimeActivity extends FragmentActivity {
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_crime);
            FragmentManager fm = getSupportFragmentManager();
            Fragment fragment = fm.findFragmentById(R.id.fragment_container);
            if (fragment == null) {
                fragment = new CrimeFragment();
                fm.beginTransaction()
                        .add(R.id.fragment_container, fragment)
                        .commit();
            }
        }
    }

출저) 실무에 바로 적용하는 안드로이드 프로그래밍 

Step 4-2. 변경사항 지정하기

트랜잭션으로 그룹화할 모든 액션을 지정(add,remove,replace,addToBackStack 등)

 transaction.add(R.id.fragment_container, newFragment);
 transaction.replace(R.id.fragment_container, newFragment);
 transaction.remove(newFragment);
 transaction.addToBackStack(null);

만약 Back 버튼을 눌렀을떄 이전 Fragment 상태로 돌아가려면?

 => commit 전에 addToBackStack()을 호출해야한다. (트랜잭션을 fragement 트랜잭션의 백스택에 추가 할 수 있고,            이 백스택은 액티비티가 관리하기 때문) 

Step 4-2-1. add와 replace의 차이

  • add 는 기존 fragments를 보존하고 back button을 누르면 기존 fragment (FragmentA) 를 호출하지 않기 때문에     "onPause"상태가 되지 않고 새로운 fragment(FragmentB) 를 추가한다.
  • replace 는 기존 fragment (FragmentA) 를 제거하고 새 fragment (FragmentB)를 추가하는 방식                                                         : back button 누르면 교체된 fragment (FragmentB)의 "onCreateView"가 호출된다.

결론 : "onResume, onCreateView 및 기타 lifecycle은 replace때만 호출된다.

 

특이사항) replace -> addToBackStack 할 경우

  1. newFragment가 현재 레이아웃 컨테이너에 식별된 프래그먼트(있는 경우)를 R.id.fragment_container ID로 교체 
  2. addToBackStack()를 호출하면 교체 트랜잭션이 백 스택에 저장됨                                                           
  3. 사용자가 Back 버튼을 누르면 FragmentActivity가 onBackPressed()를 통해 백 스택에 프래그먼트를 자동 검색 
  4. 트랜잭션을 되돌리고 이전 프래그먼트를 다시 가져올 수 있다.
 // Create new fragment and transaction
 Fragment newFragment = new ExampleFragment();
 FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();

 // Replace whatever is in the fragment_container view with this fragment,
 // and add the transaction to the back stack
 transaction.replace(R.id.fragment_container, newFragment);
 transaction.addToBackStack(null);

 // Commit the transaction
 transaction.commit();

Step 4-3. 트랜잭션 커밋하기

  • 트랜잭션을 마치고 모든 변경사항을 적용한다. (반드시 commit은 마지막에 호출한다.)
  • 주의) commit()을 사용할 수 있는것은 activity가 그 상태를 저장하기 전(사용자가 액티비티를 떠날 때)만 가능하다.
 transaction.commit();

Activity -> Fragment : data전달

Step 5. Fragment와 data

Step 5-0. Bundle을 사용하여 data를 주고받는 이유

 

Step 5-1. Fragment에 data 넣기(Bundle,setArguments())

  • 모든 fragment Instance는 첨부된 Bundle 객체(키와 값이 한쌍으로 된 데이터) 를 가질 수 있다.
  • 이 Bundle을 객체를 생성후 이 번들을 setArguments()를 사용해 fragment에 첨부한다.
  • 단, setArguments() 는 Fragment가 생성된 후( DetailsFragment f), Activity에 Fragment가 추가되기 전에 (public View onCreateView) 생성한다.

 

public static class CountingFragment extends Fragment {
    int mNum;
    static CountingFragment newInstance(int num) {
    
        CountingFragment f = new CountingFragment();
        Bundle args = new Bundle();
        args.putInt("num", num);
        f.setArguments(args);

        return f;
    }

 

Step 5-2. newInstance()를 사용하는 이유

  • Fragment에서 data 추가시 "newInstance() 이름의 static 메서드를 Fragment 클래스에 추가하는 것이 좋다.           
  • 이유1) 안드로이드 fragment는 파라미터가 없는 constructor만 가지고 있어 constructor을 사용해 초기화 불가능                                                 출처 : https://seungjun-lee.tistory.com/1
  • 이유2) newInstance() 에 필요한 객체들을 모두 선언하면, 호출시 newInstance() 메소드만 호출하면 되기 때문

Step 5-3. Fragment에 data 가져오기(getArgmuents())

  • getArgmuents()를 호출한 후 Bundle의 get메소드들 중 하나를 호출한다. (타입에 맞는 메소드 호출)
  public int getShownIndex() {
        return getArguments().getInt("index", 0);
    }

    출처) https://developer.android.com/reference/android/app/Fragment


Fragment-> Activity : data전달

Step 6. Activity에 대한 Event Callback 생성

  • Activity -> Fragment로 데이터 전달시 bundle과 argument를 사용하였지만, 반대의 경우 직접 구현 필요
  • 가장 보편적인 방법은 Fragment에 Callback Interface를 정의, Activity에서 이를 구현한다.
  • Activity가 Interface를 통해 Callback 수신시, 필요에 따라 정보를 layout내의 다른 fragments와 고유할 수 있다.

Step 6-1. CrimeListFragment에 Callback Interface를 정의

  • CrimeListFragment 내부에 interface를 추가한다.
  • interface명이 Callbacks 이므로, CrimeListFragment.Callbacks 으로 불린다.
public class CrimeListFragment extends Fragment {  

    private Callbacks callbacks;
    
    public interface Callbacks {
        void onCrimeSelected(Crime crime);
    }
}

Step 6-2. CrimeListActivity에서 Callback 구현

  • Activity에서 반드시 CrimeListFragment.Callbacks Interface가 구현되어야 한다.                                               
  • 이유 : Activity가 그 interface의 서브타입이 되어야만 그것의 객체가 그 interface의 타입이 될 수 있기 때문
public class CrimeListActivity extends SingleFragmentActivity
        implements CrimeListFragment.Callbacks {
  
    @Override
    public void onCrimeSelected(Crime crime) {
        if (findViewById(R.id.detail_fragment_container) == null) {
            Intent intent = CrimePagerActivity.newIntent(this, crime.getId());
            startActivity(intent);
        } else {
            Fragment newDetail = CrimeFragment.newInstance(crime.getId());

            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.detail_fragment_container, newDetail)
                    .commit();
        }
    }
}

 

Step 6-3. Fragement가 여러개라면?

  • 6-1과 6-2 와 동일한 방식으로 추가한다.
public class CrimeFragment extends Fragment {
    				...
    private Callbacks callbacks;
    
    public interface Callbacks {
        void onCrimeUpdated(Crime crime);
    }

    public static CrimeFragment newInstance(UUID crimeId) {
        Bundle args = new Bundle();
        args.putSerializable(ARG_CRIME_ID, crimeId);

        CrimeFragment fragment = new CrimeFragment();
        fragment.setArguments(args);
        return fragment;
    }
    				...
}
public class CrimeListActivity extends SingleFragmentActivity
        implements CrimeListFragment.Callbacks, CrimeFragment.Callbacks {

					...
    @Override
    public void onCrimeSelected(Crime crime) {
        if (findViewById(R.id.detail_fragment_container) == null) {
            Intent intent = CrimePagerActivity.newIntent(this, crime.getId());
            startActivity(intent);
        } else {
            Fragment newDetail = CrimeFragment.newInstance(crime.getId());

            getSupportFragmentManager().beginTransaction()
                    .replace(R.id.detail_fragment_container, newDetail)
                    .commit();
        }
    }
    
    @Override
    public void onCrimeUpdated(Crime crime) {
        CrimeListFragment listFragment = (CrimeListFragment)
                getSupportFragmentManager()
                        .findFragmentById(R.id.fragment_container);
        listFragment.updateUI();
    }
}

 

출처) 실무에 바로 적용하는 안드로이드 프로그래밍 ch.17

 

참고) https://stackoverflow.com/questions/18634207/difference-between-add-replace-and-addtobackstack?answertab=votes#tab-top


추가로 알아두면 좋은 개념들

  •  중복 프래그먼트
  • fragment lifecycle
  • bundle과 Intent

 

 

https://peonyf.tistory.com/86

 

[Fragment] Fragment Support Library VS Fragment Standard Library

Fragment Support Library VS Fragment Standard Library Support Library의 장점 해마다 Support Library의 업데이트 버전이 나와서 이 버전을 사용할 수 있다. 또한, Standard Library가 업데이트 시 API에 추가..

peonyf.tistory.com

불러오는 중입니다...

 

반응형

'Android' 카테고리의 다른 글

[Fragment] Fragment Support Library VS Fragment Standard Library  (0) 2020.04.10
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함