국비교육(22-23)

71일차(2)/Android App(16) : Fragment(1)

서리/Seori 2023. 1. 18. 00:04

71일차(2)/Android App(16) : Fragment

 

- 새 모듈 생성- step07fragment

MainActivity

package com.example.step07fragment;

import android.os.Bundle;
import android.view.View;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentManager;

public class MainActivity extends AppCompatActivity implements MyFragment.MyFragmentListener {
    MyFragment mf1, mf2;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //전개된 view에는 MyFragment 객체가 2개 있다. 만일 해당 객체의 참조값이 액티비티에서 필요하다면?

        //FragmentManager 객체의 참조값을 얻어내서
        FragmentManager fm=getSupportFragmentManager();
        //해당 객체의 메소드를 활용해서 프래그먼트의 참조값을 얻어낸다.
        mf1=(MyFragment) fm.findFragmentById(R.id.fragment1);
        mf2=(MyFragment) fm.findFragmentById(R.id.fragment2);
    }

    @Override
    public void sendMsg(String msg) {
        Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
    }

    public void resetFragment(View v){
        mf1.reset();
        mf2.reset();
    }
}

 

- 나중에는 여기서도 fragment 를 만들 수 있다.

- 일단 지금은 커스텀으로 만들어보기!

 

- new java class생성

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);
        //만든 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);
    }
}

 

- import 할 때 이렇게 두가지라면 'x' 가 붙은 것으로 import 하는 게 좋다.

- 이쪽이 더 최신 버전이다.

 

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

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

 

 

 

- 한 화면이 액티비티로 구성된다면, 화면의 일부를 프래그먼트로 구성할 수 있다.

- 각각의 프래그먼트는 자기 자신만의 고유한 UI를 가진다. 분업화된 것이라고 볼 수 있다.

 

- ★재활용★한다는 개념이 중요하다! fragment를 만드는 이유.

- 한 액티비티의 프래그먼트가 다른 액티비티에서도 활용될 수 있다.

- 보통은 코드를 복사해서 똑같이 동작하도록 만들어야 하는데,

  fragment가 있으면 그 fragment 를 import 해서 사용하면 된다.

- 액티비티를 조각내서 각각의 fragment가 제어하게 한다는 개념!

 


 

- res 폴더에 새 레이아웃 리소스 파일 / LinearLayout 으로 생성

 

fragment_my

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="0"
        android:textSize="50sp"
        android:gravity="center"
        android:id="@+id/textView"
        android:background="#11FF00"/>

</LinearLayout>

 

- 0 이라는 텍스트가 들어가 있는 textView를 하나 만들고 id 부여하기

 

- MyFragment 클래스에서 onCreateView를 override 해준다.

 

- 이 메소드에서는 View를 리턴한다. 

- 이 프래그먼트가 Activity의 일부 공간을 차지할 것이다.

- 그 공간에서 사용할 view를 리턴해주면 된다.

 

- onCreateView 메소드에는 레이아웃 전개자(LayoutInflater) 객체가 전달된다.

 

@NonNull / @Nullable

- 이 annotation은 null이 들어갈 수 있음, 없음에 대해서 설명해주는 역할을 한다.

- java에서 좀 더 엄격한 문법으로 사용하는 것

 

- 두번째 인자로 View Group을 넣어준다.

 

- 이 view를 가져와주는 것이 중요한 작업!

 


 

activity_main.xml

<?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">
    <fragment
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:name="com.example.step07fragment.MyFragment"
        android:id="@+id/fragment1"/>
    <fragment
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_margin="10dp"
        android:name="com.example.step07fragment.MyFragment"
        android:id="@+id/fragment2"/>
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:onClick="resetFragment"
        android:text="리셋"
        android:id="@+id/Btn"/>

</LinearLayout>

 

- fragment 의 name 속성에 myFragment 넣어주기!!

 

- Gradle Script 폴더 안에서 targerSdk를 32->33 으로 수정

- run 했을때 나오는 오류메세지에서 수정하라고해서 33으로 바꾸어 봤더니 잘 실행됨!

 

- 그래들 설정을 변경하고 나면 Sync 한번 맞춰주기!

 

 

- 위와 같이 프래그먼트 2개로 화면을 구성했다.

 

- 박스를 클릭할 때마다 숫자를 증가시킬 수 있도록 할 것!

 

- this 는 MyFragment를 가리킨다. 여기에 구현한다! 프래그먼트가 리스너역할을 할 수 있도록.

- override한 OnClick 메소드 안에서 화면의 숫자가 올라가도록 하는 코드를 작성

 

- 그럼 textView안의 텍스트를 onClick() 메소드 안에서 바꿔주어 한다.

→ textView의 참조값이 필요하므로 textView를 필드로 만들어야 한다.

 

- 필드로 만들어서 어디에서나 참조할 수 있도록 수정

 

 

- 이제 클릭하면 숫자가 하나씩 늘어난다.

 

- 이런 프래그먼트를 만들어서 여기저기에서 재활용하기 위해서는

  특정 액티비티에 의존성이 없어야 한다!

 

- 액티비티에 의존성이 없다는 것은, 

  여기에 특정 activity에서 가져온 import 값이 없어야 한다는 것을 의미한다.

 

 

- 만약 숫자가 10의 배수가 될 때마다 알림을 띄우고자 한다면?

- 이렇게 메시지를 전달하는 메소드를 작성한다.

- 이 메소드에 프래그먼트로부터 정보가 들어가야한다.

 

- myfragment에서 보면 .getActivity() 라는 메소드가 있다. fragment를 리턴한다.

 

- 프래그먼트 파일에서 getActivity 하면

 제어하고 있는 액티비티의 참조값이 부모타입으로 리턴된다.

- 이것을 MainActivity 타입으로 받으려면 캐스팅하면 된다!

 

 

- 액티비티의 참조값을 알아내서 특정 조건일 때 액티비티에 어떤 정보를 전달하도록 했다.

 

 

- 10이 되면 이렇게 토스트 메시지가 뜨도록 했다.

 

MainActivity ma=(MainActivity) getActivity();
    if(count%10==0){
        ma.sendMsg(count+" 입니다!");
    }
}

- 현재 myFragment는 이 부분 때문에 오직 MainActivity 에서만 동작할 수 있다.

- 이렇게 작성한 프래그먼트는 재활용이 안된다!

 

- 여기서 의존성을 없애는 방법은?

 

- 프래그먼트에 MyFragmentListener 인터페이스를 구현하고,

 액티비티가 이것을 구현하도록 한다.

 

MyFragmentListener ma=(MyFragmentListener) getActivity();
    if(count%10==0){
        ma.sendMsg(count+" 입니다!");
    }

- 변수를 인터페이스 타입으로 선언하고, getActivity()를 인터페이스 타입으로 캐스팅한다

 

- 구현하고, sendMsg 메소드 오버라이드하기.

 

 

- 이렇게 인터페이스로 구현하면 어떤 액티비티에서든 가져다가 사용할 수 있다.

 

instanceof 타입명

- 객체의 타입을 확인해서 해당 타입으로 형변환이 가능하면 T, 불가능하면 F를 반환한다.

 

- 항상 캐스팅하는것이 아니라 if로 한번 확인해서 캐스팅할 수 있으면 더 좋다.

 

- 의존성을 삭제하고도 똑같이 잘 작동한다.


 

- fragment는 fragment일 뿐이다. view가 아니다!

 

 

- main에서 생성된 fragment는 이렇게 2개이다.

- 하지만 간접적으로 전개된 것이다.우리가 직접 전개해준 것은 아니다.

- 직접 전개해서 사용하려면 참조값을 얻어와서 사용해야 한다.

- view가 아니므로 findViewById() 로 참조값을 알아낼 수 없다.

 

getSupportFragmentManager() 

- fragment의 참조값을 얻어오는 데 사용한다.

 

- 이 안에 findFragmentById() 메소드가 있다.

- 부모타입으로 리턴된다. 원래 타입으로 캐스팅해서 받으면 된다!

 

 

- MyFragment에 메소드 추가

- 이 메소드가 호출되면 count가 0으로 바뀌도록 할 것

 

- 버튼 요소를 하나 추가하고 onclick 속성 하나 추가!

 

- fragment의 참조값이 필요하다. fragment의 참조값을 담을 곳을 지역변수가 아닌 필드로 만들어주기!

 

- 액티비티/프래그먼트가 어떻게 소통하는가를 익히면 된다.

- fragment는 미니 컨트롤러의 연할을 한다.

- 원래 액티비티가 하던 일을 fargment가 일부 분담한다.

 

- MainActivity 에서는 getSupportFragmentManager 에서 참조값을 얻어내는 것이다.

- 액티비티는 특정 프래그먼트에 의존한다.

 

 

- 반면 프래그먼트는 재활용성을 고려했으므로 특정 Activity에 의존하면 안 된다.

- 그래서 ① 인터페이스를 하나 정의해놓고,

  ② 나를 제어하는 액티비티의 참조값을 찾아와서,

  ③ 해당 인터페이스로 구현한 후에

  ④ 그 인터페이스타입을 활용해서 메소드를 호출하면서

  ⑤ 어떤 정보를 전달하는 등으로 사용하면 된다.

 

 

- 프래그먼트는 매우 많이 쓰인다!

- 안드로이드의 코드 자유도는 일반 웹 java 코딩에 비해 매우 높다.