북극곰의 개발일기

Fragment(프래그먼트) 이해하기





posted by purplebeen on Sat Feb 03 2018 22:29:51 GMT+0900 (KST) in Android


한 5월 이후로 안드로이드는 완전히 놓고 있었다.... 원래 이번달은 디자인패턴만 할려고 했는데 갑자기 여유시간이 좀 생겨서 안드로이드도 같이 하려고 한다.

프래그먼트 : 액티비티 내부에서 독립적으로 애플리케이션의 사용자 인터페이스를 관리하는 객체, 애플리케이션이 실행되는 런타임에서 사용자 인터페이스를 동적으로 변경하기 위해 액티비티에 추가하거나 제거 할 수 있다.

-> 액티비티와 유사한 생명주기를 갖는 기능적인 "부속 액티비티"

코드에서 프래그먼트를 추가하고 관리하기

프래그먼트를 Xml UI로 짜 놓았다면 이제는 이제 이 fragment를 액티비티에 포함시켜야한다.

액티비티에 프래그먼트를 추가하는 방식에는 크게 2가지가 있는데, 자바 코드를 이용해 삽입하는 방법과

MainActivity Layout에 xml형태로 삽입하는 방법이 있다.

xml을 이용해 삽입하는 방법은 액티비티가 실행되고나서 프래그먼트를 추가, 제거, 교체 할 수 없기 때문에 주로 자바코드를 이용해 삽입하는 방법이 이용된다.

1. 프래그먼트 클래스의 인스턴스를 생성한다.
2. 추가적인 인텐트(intent) 인자들을 그 인스턴스에 전달한다.
3. 프래그먼트 매니저(Fragment Manager) 인스턴스의 객체 참조를 얻는다.
4. 그 프래그먼트 매니저 인스턴스의 beginTransaction() 메소드를 호출한다. 그러면 이 메소드에서 프래그먼트 트랜잭션(fragment transaction) 인스턴스를 반환한다.
5. 그 프래그먼트 트랜젝션 인터페이스의 add() 메소드를 호출한다. 이때 프래그먼트를 포함하는 뷰(컨테이너 뷰)의 리소스 ID와 프래그먼트 클래스 인스턴스를 인자로 전달한다.
6. 프래그먼트 트랜잭션의 commit() 메소드를 호출한다.

이 6가지의 과정을 간단하게 단순화해서 나타내면

getFragmentManager().begintTransaction().add(R.id.LinearLayout1, firstFragment)
.commit();

이 될 것이다. 자 이제 그럼 실제로 Fragment를 활용한 앱을 만들어보자.

첫번째 프래그먼트에서 seekbar를 통해 글자의 크기와 내용을 받아오고

버튼을 통해 실행하면 두번째 프래그먼트에 있는 TextView에서 받아온 값에 맞춰서 보여주는 프로그램을 만들자.

먼저 첫번재 Fragment의 UI이다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Change Text"
        android:layout_below="@+id/seekBar1"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="17dp"
        />
    <EditText
        android:id="@+id/editText1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="16dp"
        android:ems="10"
        android:inputType="text">
        <requestFocus/>
    </EditText>

    <SeekBar
        android:id="@+id/seekBar1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@+id/editText1"
        android:layout_marginTop="14dp"/>
</RelativeLayout>

Button이 Seekbar 아래에 있게 하고 가로를 기준으로 가운데에 있으며 위쪽 간격을 17dp로 설정하였다.

editText는 부모 레이아웃(여기서는 Relative Layout)을 기준으로 위쪽에 정렬하며

버튼과 마찬가지로 가로를 기준으로 가운데에 놓고 가장 위쪽의 간격은 16dp로 설정하였다.

또한 input Type를 text로 지정하였고 글자 수를 10으로 제한하였다.

seekbar는 부모뷰의 왼쪽으로 정렬하였고 editText1 아래에 있으며 가장 위쪽 간격을 14dp로 설정하였다.

자 이제 그러면 첫번째 프래그먼트 클래스를 생성하여 보자.

package com.ebookfrenzy.fragmentexample;

import android.app.Activity;
import android.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;


/**
 * Created by baehy on 2016-08-02.
 */
public class ToolbarFragment extends Fragment{
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
        //이 프래그먼트의 레이아웃 인플레이트
        View view = inflater.inflate(R.layout.toolbar_fragment, container, false);
        return view;
    }

LayoutInflater의 인스터스인 inflater의 inflate() 메소드를 이용해 레이아웃으로 정의된 사용자 인터페이스

컴포넌트를 뷰 객체로 만들어주었고 그 객체를 반환하고 있다.

자 이제 그러면 2번째 fragment Layout을 만들어 보자. 아까보다 더 간단하다. 가운데에 TextView 하나만 있다.

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="@string/text_label"
        android:textAppearance="?android:attr/textAppearanceLarge"/>
</RelativeLayout>

TextView를 수직,수평 가운데로 배열하고 있고 textAppearance를 textAppearanceLarge로 선언하여

기본적으로 큰 사이즈로 보이게 해 놓았다. 또한 리소스 이름을 text_label로 선언하였다.

자 그러면 이제 아까처럼 2번째 프래그먼트도 클래스를 만들어주자.

package com.ebookfrenzy.fragmentexample;

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

/**
 * Created by baehy on 2016-08-02.
 */
public class TextFragment extends Fragment {

    private static TextView textview;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, 
Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.text_fragment, container, false);
        textview = (TextView) view.findViewById(R.id.textView1);
        return view;
    }
}

자 이제 그러면 이 두개의 프래그먼트를 xml을 이용해 액티비티에 추가하자.

ToolbarFragment는 centerHorizontal과 alignParentTop 옵션을,

TextFragment는 centerHorizontal과 centerVertical 옵션을 갖도록 한다.

자 이제 그러면 ToolbarFragment 클래스와 TextFragment 클래스를 수정하여 본격적으로 이 두 프래그먼트가 작동될 수 있도록 만들어 보자.

package com.ebookfrenzy.fragmentexample;

import android.app.Activity;
import android.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.EditText;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;

/**
 * Created by baehy on 2016-08-02.
 */
public class ToolbarFragment extends Fragment implements OnSeekBarChangeListener{

    private static int seekvalue = 10;
    private static EditText edittext;

    ToolbarListener activityCallback;

    public interface ToolbarListener {
        public void onButtonClick(int position, String Text);

    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            activityCallback = (ToolbarListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() +
 "must implement Toolbar Listener");
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
 Bundle savedInstanceState) {
        //이 프래그먼트의 레이아웃 인플레이트
        View view = inflater.inflate(R.layout.toolbar_fragment, container, false);
        edittext = (EditText) view.findViewById(R.id.editText1);
        final SeekBar seekBar = (SeekBar) view.findViewById(R.id.seekBar1);
        seekBar.setOnSeekBarChangeListener(this);

        final Button button = (Button) view.findViewById(R.id.button1);
        button.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                buttonClicked(v);
            }
        });
        return view;
    }

    public void buttonClicked (View view) {
        activityCallback.onButtonClick(seekvalue, edittext.getText().toString());
    }
    @Override
    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
        seekvalue = progress;
    }

    @Override
    public void onStartTrackingTouch(SeekBar seekBar) {

    }

    @Override
    public void onStopTrackingTouch(SeekBar seekBar) {

    }
}

프래그먼트의 seekbar와 edittext를 findviewbyid를 이용하여 연결시켰고,

OnSeekBarChhangeListener를 구현하여 seekbar의 값이 변경되었을때 seekvalue가 움직인 값인 progress에 맞춰지도록 하였다.

또한 Activity와 Fragment 사이에 상호작용이 일어나도록 하기 위해서 public 접근 제한자를 사용하는 인터페이스를 만들었다.

이 인터페이스를 액티비티에서 구현하는 방식으로 프래그먼트와 액티비티가 커넥션을 취하게 할 수 있다.

@Override 
public void onAttach(Activity activity) {
	super.onAttach(activity); 
	try { 
		activityCallback = (ToolbarListener) activity; 
	} catch (ClassCastException e) { 
		throw new ClassCastException(activity.toString() + "must implement Toolbar Listener"); 
		} 
}

또한 이 부분에서 Activity가 ToolbarListener를 구현하고 있지 않으면 ClassCastException이 발생하여 이 예외를 처리하도록 하고 있다.

만약 액티비티에서 ToolbarListener를 구현하고 있다면 오류가 없이 정상적으로 activitycallback에 들어가게 될 것이다.

public void buttonClicked (View view) {
        activityCallback.onButtonClick(seekvalue, edittext.getText().toString());
}

또한 버튼 리스너에서는 이 메소드를 실행시키고 있는데, 이 메소드는 상위 액티비티에서 구현된 ToolbarListener의

onButtonClick(int position, String text) 메소드를 실행시켜준다.

이제 이렇게 넘겨준 값을 Fragment2에서 받아올 수 있도록 해주자.

 public void changeTextProperties (int fontsize, String text) { 
	 textview.setTextSize(fontsize); 
	 textview.setText(text); 
}

TextFragment 클래스에 다음과 같은 소스를 추가하였다.

인자값으로 받아온 fontsize와 text로 textview를 설정해준다.

자 이제 그러면 마지막으로 mainActivity의 onButtonClick 메소드에서

프래그먼트 객체를 생성하여 changeTextProperties 메소드를 실행시켜주면 ToolbarFragment에서 전달한 값이 textFragment로 전달될 것이다.

 @Override public void onButtonClick(int fontsize, String text) { 
	 TextFragment textFragment = (TextFragment) getFragmentManager().findFragmentById(R.id.text_fragment);
	 textFragment.changeTextProperties(fontsize, text); 
}

정상 작동되는지 알아보기 위해 실행시켜보면,

seekbar의 변경값과 EditText의 변화된 부분이 정상적으로 적용되는것을 볼 수 있다.