티스토리 뷰
Fragment: 액티비티 처럼 이용할 수 있는 뷰
재사용 할 수 있는 컴포넌트나 하위 액티비티(다른 액티비티에 재사용 가능)
Fragment를 왜 사용할까?
- Fragment로 런타임시 화면 일부를 제어 하거나 여러 화면에 재사용할 수 있다.(UI 유연성)
- Activity에 여러개의 fragments를 재사용 또는 사용하여 창이 여러개인 UI 빌드 가능
Fragment 동작방법
- Fragment를 Activity layout에 추가
- 해당 Fragment는 Acvitiy의 view계층내에 있는 container에 들어간다.
- 이 container에서 자체적인 view layout을 정의한다.
- 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를 지정한다.
이유)
- 안드로이드가 Activity layout 생성
- 각 <fragment>를 인스턴스화
- 각각에 대해 onCreateView() 메소드 호출
- 각 fragment의 layout 검색
- 이후 fragment가 반환한 view(inflate 함수로 return 값)을 <fragement> name에 곧바로 삽입하기 위해서
출처) https://developer.android.com/guide/components/fragments
동적으로 추가 (액티비티에 코드 추가)
- 프로그래밍 방식으로 프래그먼트를 기존의 ViewGroup에 추가
- Fragment 교체 및 추가, 컨트롤 가능
- LinearLayout 자리에 Fragment를 넣는다. (container역할을 한다.)
- LinearLayout 인 이유: 프래그먼트를 붙이고 싶으면 부모가 될수있는 뷰여야 한다.
부모가 될 수 있는 LinearLayout, RelativeLayout와 같은것을 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 역할..
- 프래그먼트를 백 스택에서 꺼낼 경우 (사용자가 Back 명령시) : popBackStack()
- 백 스택에 변경 내용이 있는지 알아볼 경우 : addOnBackStackChangedListener()
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 할 경우
- newFragment가 현재 레이아웃 컨테이너에 식별된 프래그먼트(있는 경우)를 R.id.fragment_container ID로 교체
- addToBackStack()를 호출하면 교체 트랜잭션이 백 스택에 저장됨
- 사용자가 Back 버튼을 누르면 FragmentActivity가 onBackPressed()를 통해 백 스택에 프래그먼트를 자동 검색
- 트랜잭션을 되돌리고 이전 프래그먼트를 다시 가져올 수 있다.
// 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
추가로 알아두면 좋은 개념들
- 중복 프래그먼트
- fragment lifecycle
- bundle과 Intent
[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 |
---|