국비교육(22-23)

72일차(2)/Android App(18) : Fragment(2), Fragment Lifecycle

서리/Seori 2023. 1. 18. 18:14

72일차(2)/Android App(18) : Fragment (2), Fragment Lifecycle

 

step07fragment2

Myfragment

package com.example.step07fragment;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;

/*
    [Fragment]
    - 액티비티의 제어 하에 존재하는 미니 Controller
    - 재활용을 염두에 두고 만드는 경우가 많다
    - 재활용이라는 것은 여러개의 액티비티에서 활용된다는 의미

    [ Fragment 만드는 방법 ]
    1. Fragment 클래스를 상속받는다.
    2. Fragment 의 layout xml 문서를 만든다.
    3. onCreateView() 메소드를 오버라이딩 한다.
 */

public class MyFragment extends Fragment implements View.OnClickListener {
    //필드
    TextView textView;
    int count=0;

    // layout 으로 사용할 View를 만들어서 리턴해주어야 한다.
    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        //1. fragment_my.xml 문서를 전개해서 View를 만든 다음
        View view=inflater.inflate(R.layout.fragment_my, container, false);
        //만든 View에서 TextView의 참조값을 알아낸다.
        textView=view.findViewById(R.id.textView);
        textView.setOnClickListener(this);
        //2. 해당 View 를 리턴해 준다.
        return view;
    }

    @Override
    public void onClick(View v) {
        //카운트 값을 1 증가시키고
        count++;
        //정수를 문자열로 만들어서 TextView에 출력하기
        textView.setText(Integer.toString(count));

        /*
            만일 count 값이 10의 배수이면 이 fragment를 제어하고 있는 액티비티에 해당 정보를 알려주기!
            - 액티비티의 특정 메소드를 호출하면서 문자열 전달하기
            - 단 특정 액티비티의 의존성은 없어야 된다.
         */

        //이 fragment 를 제어하고 있는 액티비티의 참조값 알아내기.
        FragmentActivity fa = getActivity();

        //혹시 액티비티가 MyFragmentListener 인터페이스를 구현하지않았을 수도 있기 때문에 type을 확인해서 캐스팅한다.
        if(count%10==0 && fa instanceof MyFragmentListener){
            MyFragmentListener ma=(MyFragmentListener)fa;
                    ma.sendMsg(count+" 입니다!");
        }


    }

    //액티비티에서 특정시점에 호출할 예정인 메소드
    public void reset(){
        count=0;
        textView.setText("0");
    }

    //이 fragment 에서 전달하는 메세지를 받을 액티비티에서 구현할 인터페이스를 클래스 안에 정의하기
    public interface MyFragmentListener{
        public void sendMsg(String msg);
    }
}

 

 

- layout으로 사용할 view를 리턴해주는 것이 제일 중요하다!

 

- 이 부분 수정하기! , false 인자를 추가해준다.

 

- 부모 뷰가 준비되지 않았을 때에는 에러가 날 수 있으므로 false를 옵션으로 전달해야 한다.

- 이 fragment를 나중에 부모 뷰가 다 준비되면 붙이겠다는 뜻

 

- inflater 객체를 사용해서 레이아웃, 전달된 뷰 그룹, false까지 전달해서 레이아웃을 전개하는 것이라고 생각하면 된다.

 

- onCreateView 에서 리턴된 View 가 레이아웃의 이 위치에 들어온다.

- 리턴한 View는 1개인데, 프래그먼트 객체 2개를 생성해서 똑같은 뷰가 2개 들어간 것!

 


 

activity_main.xml을 복붙해서 생성

activity_main2.xml 수정 (FragmentContainerView 사용)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment1"
        android:name="com.example.step07fragment.MyFragment"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"/>
    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/fragment2"
        android:name="com.example.step07fragment.MyFragment"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"/>

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="resetFragment"
        android:text="리셋"
        android:id="@+id/Btn"/>

</LinearLayout>

 

- 프래그먼트 요소를 레이아웃 구성할 때 직접 작성했는데 

 최근에 Android Studio가 업데이트되면서 모양을 좀 바꿔두었다.

- fragment 요소를 View로 둘러싸서 넣는 방법이 있다.

 

- FragmentContainerView 를 사용한다.

 

- 디자인탭에도 이렇게 있다. 끌어다 놓으면 된다!

 

- 이 FragmentContainerView에 어떤 프래그먼트를 사용할 것이냐고 나온다. MyFragment 선택.

 

- 코드에 보면 이 요소가 자동으로 들어와있다

- 프래그먼트를 내부적으로 가지고 사용하는 view 라고 생각하면 된다. 사용법은 fragment와 거의 같다.

 

- 새 요소를 만들어주고 id를 이쪽에 부여한다.

- 원하는 프래그먼트를 집어넣을 때 FragmentContainerView 를 사용해서 집어넣으라는 뜻이다.

 그냥 fragment 요소 대신!

 

 

- 이것도 view이므로 이렇게 참조값을 얻어올 수 있다.

 

- 위 코드(분홍)는 프래그먼트의 id를 사용해서 참조값을 찾아온 것이다.

 (똑같은 아이디를 사용한다. 단 프래그먼트는 FragmentManager 사용)

- 아래(파랑)는 View의 참조값을 얻어온 것이다.

 

- FragmentContainerView 는 프레임 레이아웃을 상속받아서 만든 일종의 View이다.

 


 

새 모듈 생성-Empty Activity

step07fragment2

 

 

- 있는 fragment 양식으로 만들어보기

 

BlankFragment

package com.example.step07fragment2;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

public class BlankFragment extends Fragment {


    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";
    private String mParam1;
    private String mParam2;

    //생성자
    public BlankFragment() {

    }

    //자신의 객체(Fragment) 를 생성해서 리턴해주는 static 메소드
    public static BlankFragment newInstance(String param1, String param2) {
        //객체 생성
        BlankFragment fragment = new BlankFragment();
        //생성된 객체에 인자를 전달할 꾸러미(Bundle) 객체 생성
        Bundle args = new Bundle();
        //꾸러미 객체에 전달할 값을 담고
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        //꾸러미 객체를 Fragment 객체에 인자로 전달한 다음
        fragment.setArguments(args);
        //Fragment 객체를 리턴해준다.
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_blank, container, false);
    }
}

 

- 객체 생성시 직접 new하지 말고 이 생성자를 호출해서 받아가라는 것이다. (static)

- 인자가 있으면 전달하고, 꾸러미 객체에 그 인자를 받아서 fragment 객체를 리턴하는 데에 활용.

 

- 이 fragment 객체의 참조값이 필요하다면 new 하는것이 아니라 

 생성자에 값을 전달해서 프래그먼트의 참조값을 얻어오도록 하기!

 

- 인자로 무엇을 전달할지, 전달할 값은 그때그때 다르지만

 하지만 newInstance() 메소드를 사용할 것 !

 

- getArguments() 를 통해서 꾸러미 객체를 얻어낼 수 있다.

 

- set했던것을 아래에서 get으로 얻어와서 사용하는 것.

 

- 이 꾸러미에 담긴 데이터를 getString() 메소드로 읽어내기

 

- 저 args가 여기에 리턴된다고 보면 된다. 

 

- 그러고 나서 아래의 onCreate 에서 View를 리턴해주면 된다.

- 이 fragment 구조가 안드로이드에 기본으로 만들어져있는 프래그먼트의 샘플이다!

 

- 이전에 공부한 Activity 생명주기의 많은 메소드와 비슷하게

  프래그먼트도 고유한 생명주기를 가진다. 훨씬 더 많은 메소드가 있다.

 

- ctrl+o 하면 오버라이드할수있는 메소드들이 엄청나게나온다.

 

- on 해보면 많은 메소드가 있다.

- 여기서는 이 중 2가지 onCreate(), onCreateView() 를 override 한 것

 

** Fragment LifeCycle : 링크

 

 

- Fragment의 생명 주기!!

 

- 위의 2개를 주로 많이 override 한다.

- onCreate() : 준비 작업을 하는 메소드

- onCreateView() : 준비 작업단계에서 뷰를 만들어서 리턴하는 메소드

 

- onStop() : 마무리 작업을 할 게 있으면 여기서 하면 된다.

 

 

- 사실 지금 같은 fragment를 만드는 상태에서는 생명주기가 그렇제 중요하지는 않다.

 

 

- 하지만 이런 어플이라면? 스와이프하면 사진을 여러장 보여주는 어플이라고 하자.

- 이런 slider를 프래그먼트로 많이 만든다. 각각의 사진 하나하나가 fragment이다.

- 레이아웃은 같은데 안에 출력된 사진과 내용은 다른 구조이다!

 

- 사진을 좌우로 쭉 넘기다보면 쓸데없는 프래그먼트는 비활성화시킬 수 있도록 관리해야 한다.

- 저 프래그먼트에 어떤 내용/사진을 출력할 것인지, 각각의 프래그먼트 객체에 전달해주어야 한다.

 

- 그럴 때 newinstance() 메소드에서 데이터를 전달해주고,

 onCreate() 에서 전달받은 값을 얻어내서,

 onCreateView() 에서 출력할 준비를 하는 것이다.