본문 바로가기
컴퓨터 공학/Android

[Do it 개정6판_안드로이드 스튜디오]둘째마당_05 프래그먼트 이해하기

by hahehohoo 2019. 12. 1.
반응형

- 화면 안에 들어가는 레이아웃이 중복되지 않도록 한 번만 정의하고 재사용할 수 있도록 만든 것이 프래그먼트

05-1 프래그먼트란?

■ 프래그먼트에 대해 이해하기

- 하나의 액티비티 안에 다른 액티비티를 넣는다는 것은 단말의 리소스를 많이 사용하는 비효율적인 방법

- 프래그먼트는 하나의 화면 안에 들어가는 부분 화면과 같아서 하나의 레이아웃처럼 보인다. 

- 프래그먼트의 사용 목적

1. 분할된 화면들을 독립적으로 구성하기 위해 사용함

2. 분할된 화면들의 상태를 관리하기 위해 사용함

 

액티비티 시스템에서 관리하는 화면
프래그먼트 단순히 액티비티 위에 올라가는 화면의 일부 즉 '부분 화면'

- 프래그먼트는 항상 액티비티 위에 올라가 있어야 한다. 

- 따라서 프래그먼트가 제대로 동작하는 시점은 프래그먼트가 메모리에 만들어진 시점이 아니라 액티비티에 올가가는 시점

 

 

- 인텐트는 시스템에서 이해하는 객체이므로 프래그먼트와 액티비티 사이에 전달하게 만드는 것은 X

- 그래서 액티비티와 프래그먼트 간의 데이터 전달은 메서드를 만들어서 메서드를 호출하는 방식을 사용

 

- 액티비티에 하나의 프래그먼트도 둘 수 있다. 

- 화면을 꽉차게 할 수도 있기 때문에 사용자 입장에서 전체 화면처럼 느껴진다. 

- 액티비티보다 훨씬 가볍게 화면 전환 효과를 만들 수 있다. 

 

■ 프래그먼트 만들어 화면에 추가하기

- 프래그먼트도 부분 화면이므로 화면에 뷰들을 배치할 때는 XML레이아웃으로 만든다. 

- 프래그먼트를 위한 자바 소스를 만드는데 Fragment 클래스를 상속하여 만들 수 있다. 

 

- 프래그먼트를 위한 클래스까지 만들었다면 XML 레이아웃 파일의 내용을 소스 파일과 매칭하는 과정이 필요

- 인플레이션 객체인 LayoutInflater를 사용해 인플레이션을 진행(292페이지)

 

< 새로 만든 프래그먼트를 메인 액티비티에 추가하는 방법 > 

- activity_main.xml 파일에 직접 <fragment> 태그를 사용

- 새로 정의한 프래그먼트 클래스의 인스턴스 객체를 new 연산자로 만든 후 Frgment Manager 객체의 add() 메서드를 사용

 

< 프래그먼트의 특성 >

뷰 특성

뷰그룹에 추가되거나 레이아웃의 일부가 될 수 있음

뷰에서 상속받은 것은 아니며 뷰를 담고 있는 일종의 틀임

액티비티 특성

액티비티처럼 Lifecycle 가지고 있음

컨텐스트 객체는 아니며 라이프사이클은 액티비티에 종속됨

- onCreateView() 메서드의 파라미터로 LayoutInflater 객체가 전달되므로 이 객체의 inflate() 메서드를 바로 호출할 수 있음

- inflate() 메서드로 전달되는 첫 번째 파라미터는 XML 레이아웃 파일, 두 번째 파라미터는 XML 레이아웃이 설정 될 뷰그룹 객체가 되는데 이 프래그먼트의 가장 상위 레이아웃이다. 

- 프래그먼트의 이름을 설정할 때는 패키지 이름을 포함한 이름으로 설정하고 프래그먼트의 id 값은 mainFragment로

 

■ 버튼 클릭했을 때 코드에서 프래그먼트 추가하기

- 하나의 프래그먼트에서 다른 프래그먼트를 직접 띄우는 것이 아니라 액티비티를 통해 띄워야 한다

- 프래그먼트는 뷰가 아니라서 Activity 클래스에 있는 findViewById() 메서드로 찾을 수 없고, Fragment 객체의 findFragmentById() 메서드를 사용해서 찾은 후 할당

- onFragmentChanged() 메서드는 프래그먼트에서 호출할 수 있도록 정의한 것 

- 프래그먼트를 변경할 때 오류가 생기면 다시 원래 상태로 돌릴 수 있어야 하므로 트랜잭션 객체를 만들어 실행

 

■ 프래그먼트 수명 주기

- 액티비티처럼 독립적으로 동작하도록 수명주기 메서드를 추가했음

- 상태에 따라 API에서 미리 정해둔 콜백 함수가 호출되므로 그 안에 필요한 기능을 넣을 수 있다. 

- 액티비티의 수명주기에 종속적이지만 프래그먼트만 가질 수 있는 독립적인 상태 정보들이 더 추가되었다. 

 

05-2 프래그먼트로 화면 만들기

- 한 화면에 두 개의 프래그먼트가 들어가도록 만들어 볼 것

 

▶ SampleFragment2 프로젝트

 

▼ ListFragment.java

package com.ogrg.techtown.fragment;

import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;

public class ListFragment extends Fragment {

    public static interface ImageSelectionCallback {
        public void onIamgeSelected(int position);
    }

    public ImageSelectionCallback callback;

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);

        if (context instanceof ImageSelectionCallback) {
            callback = (ImageSelectionCallback) context;
        }
    }

    @Nullable
    public View onCresteView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_list, container, false);
        Button button = rootView.findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (callback != null) {
                    callback.onIamgeSelected(0);
                }
            }
        });

        Button button2 = rootView.findViewById(R.id.button2);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (callback != null) {
                    callback.onIamgeSelected(1);
                }
            }
        });

        Button button3 = rootView.findViewById(R.id.button3);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (callback != null) {
                    callback.onIamgeSelected(2);
                }
            }
        });

        return rootView;
    }
}

- onAttach() 메서드가 호출될 때 callback 변수에 객체가 할당되었는데 그 자료형이 Activity 가 아니라 ImageSelectionCallback이다. 왜 callback 변수의 자료형을 ImageSelectionCallback으로 선언한 것일까?

 

- 화면에서 선택된 버튼에 따라 다른 프래그먼트의 이미지를 바꿔주려면 액티비티 쪽으로 데이터를 전달해야 하므로 액티비티에 onImageSelected() 메서드를 정의한 후 그 메서드를 호출한다. 

- 매번 액티비티마다 다른 이름의 메서드를 만들면 프래그먼트가 올라간 액티비티가 다른 액티비티로 변경되었을 때 해당 액티비티가 무엇인지 매번 확인해야 하는 번거로움이 있기 때문에 인터페이스를 하나 정의한 후 액티비티가 구현현하도록 하는 것이 좋다. 

 

▼ ViewFragment.java

package com.ogrg.techtown.fragment;

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

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

public class ViewerPragment extends Fragment {
    ImageView imageView;

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_viewer, container,false);
        imageView = rootView.findViewById(R.id.imageView);
        return rootView;
    }

    public void setImage(int resId){
        imageView.setImageResource(resId);
    }
}

- onCreateView() 메서드 안에서 인플레이션을 진행하고 나면 이미지뷰를 찾아 imageView라는 이름의 변수에 할당하도록 한다. 

- setImage() 메서드를 만들어 액티비티에서 이 프래그먼트에 있는 이미지뷰에 이미지를 설정

 

=> 두 개의 프래그먼트를 만들었다.

프래그먼트가 두 개이니 새로 만들어진 XML 레이아웃 파일도 두 개, 소스 파일도 두 개이다. 

이제 이 프래그먼트들을 메인 액티비티의 XML 레이아웃에 추가하자. 

 

▼ activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:name="com.ogrg.techtown.fragment.ListFragment"
        android:id="@+id/listFragment"/>

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:name="com.ogrg.techtown.fragment.ViewerPragment"
        android:id="@+id/viewerFragment"/>

        
</androidx.constraintlayout.widget.ConstraintLayout>

 

▼ MainActivity.java

package com.ogrg.techtown.fragment;

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

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

public class MainActivity extends AppCompatActivity {
    ListFragment listFragment;
    ViewerPragment viewerFragment;

    int[] images = {R.drawable.dream01, R.drawable.dream02, R.drawable.dream03};

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        FragmentManager manager = getSupportFragmentManager();
        listFragment = (ListFragment) manager.findFragmentById(R.id.listFragment);
        viewerFragment = (ViewerPragment) manager.findFragmentById(R.id.viewerFragment);
    }

    public void onImageSelected(int position){
        viewerFragment.setImage(images[position]);
    }
}

 

05-3 액션바 사용하기

■ 화면에 메뉴 기능 넣기

- 아이폰과 안드로이드의 차이점은 단말기 아래쪽에 있는 [메뉴] 버튼와 [BACK] 버튼의 유무

- 안드로이드 OS가 업그레이드되며 단말에 하드웨어 [메뉴] 버튼이 없어도 화면 안에 [시스템] 버튼들이 표시될 수 있도록 변경

- 메뉴에 변화를 주는 것을 Option Menu

- 입력 상자를 길게 눌러 나타나는 '복사하기', '붙여넣기' 등의 팝업 형태의 메뉴는 Context Menu 

 

▶ SampleOptionMenu 프로젝트

 

안드로이드 스튜디오는 /app/res/menu 폴더 안에 메뉴를 위한 XML 파일이 만들어진다는 것을 미리 알고 있음

 

▼ MainActivity.java

package com.ogrg.techtown.menu;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int curId = item.getItemId();
        switch (curId){
            case R.id.menu_refresh:
                Toast.makeText(this,"새로고침 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menu_search:
                Toast.makeText(this,"검색 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menu_settings:
                Toast.makeText(this,"설정 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
                default:
                    break;
        }
        return super.onOptionsItemSelected(item);
    }
}

 

▼ menu_main.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_refresh"
        android:title="새로고침"
        android:icon="@drawable/menu_refresh"
        app:showAsAction="ifRoom"
        />

    <item android:id="@+id/menu_search"
        android:title="검색"
        android:icon="@drawable/menu_search"
        app:showAsAction="ifRoom"
        />

    <item android:id="@+id/menu_settings"
        android:title="설정"
        android:icon="@drawable/menu_settings"
        app:showAsAction="ifRoom"
        />

</menu>

 

 

■ 액션바 좀 더 살펴보기

 

- 액션바는 기본적으로 제목을 보여주는 타이틀의 기능을 하므로 앱의 제목을 보여줄 수 있으며 보여주게 할지 말지를 소스 코드로 만들 수 있다. 

 

▶ SampleActionBar1 프로젝트

 

MainActivity.java

package com.ogrg.techtown.actionbar1;

import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
    ActionBar abar;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        abar = getSupportActionBar();

        Button button = findViewById(R.id.button);
        button.setOnClickListener(new View.OnClickListener(){
            @Override
            public void onClick(View v) {
                abar.setLogo(R.drawable.home);
                abar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME|ActionBar.DISPLAY_USE_LOGO);
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        int curId = item.getItemId();
        switch (curId){
            case R.id.menu_refresh:
                Toast.makeText(this,"새로고침 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menu_search:
                Toast.makeText(this,"검색 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
            case R.id.menu_settings:
                Toast.makeText(this,"설정 메뉴가 선택되었습니다. ", Toast.LENGTH_SHORT).show();
                break;
                default:
                    break;
        }
        return super.onOptionsItemSelected(item);
    }
}

- onCreate() 메서드 안에서 getSupportActionBar() 메서드를 이용해 XML 레이아웃에 들어 있는 ActionBar 객체를 참조

- 버튼을 클릭했을 때 액션바가 보이는 모양을 바꾸도록 setDisplayOptions() 메서드를 사용, 파라미터로는 미리 정의된 상수가 전달될 수 있음

 

▶ SampleActionBar2 프로젝트

 

 MainActivity.java

package com.ogrg.techtown.actionbar;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.menu_main, menu);

        View v = menu.findItem(R.id.menu_search).getActionView();
        if(v != null) {
            EditText editText = v.findViewById(R.id.editText);

            if(editText != null) {
                editText.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                    @Override
                    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
                        Toast.makeText(getApplicationContext(), "입력됨", Toast.LENGTH_SHORT).show();
                        return  true;
                    }
                });
            }
        }
        return true;
    }
}

▼ search_layout.xml

<?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">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="검색"
        android:textSize="16sp"
        android:textColor="#ffad8745"/>

    <EditText
        android:id="@+id/editText"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="4dp"
        android:inputType="text"
        android:imeActionId="1337"
        android:imeOptions="actionDone"/>

</LinearLayout>

▼ menu_main.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/menu_refresh"
        android:title="새로고침"
        android:icon="@drawable/menu_refresh"
        app:showAsAction="always"
        />

    <item android:id="@+id/menu_search"
        android:title="검색"
        android:orderInCategory="102"
        android:icon="@drawable/menu_search"
        app:showAsAction="always|withText"
        app:actionLayout="@layout/search_layout"
        />

    <item android:id="@+id/menu_settings"
        android:title="설정"
        android:icon="@drawable/menu_settings"
        app:showAsAction="never"
        />

</menu>

 

05-4 상단 탭과 하단 탭 만들기

■ 상단 탭 보여주기

- 액션바에 탭 기능을 넣어 보여주는 방법

 

SampleTab 프로젝트 <미완성>, 버전 문제

 

- design 라이브러리 추가하는 방법

- design 라이브러리를 추가하면 XML 레이아웃에서 액션바를 직접 정의할 수 있다. 

 

- android(기본 API)나 app(외부 라이브러리)는 속성 앞에 붙는 접두어는 가장 상위 태그이다. 

- 빨간색으로 뜨면 Alt + Enter를 누르자

 

▼ activity_main.xml

 

▼ Fragment1.java

package com.ogrg.techtown.tab;

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

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

public class Fragment1 extends Fragment {

    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.fragment1, container, false);
    }
}

 

▼ MainActivity.java

package com.ogrg.techtown.tab;

import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.fragment.app.Fragment;

import android.os.Bundle;
import android.util.Log;

import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {
    Toolbar toolbar;
    
    Fragment1 fragment1;
    Fragment2 fragment2;
    Fragment3 fragment3;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        ActionBar actionBar = getSupportActionBar();
        actionBar.setDisplayShowTitleEnabled(false);
        
        fragment1 = new Fragment1();
        fragment2 = new Fragment2();
        fragment3 = new Fragment3();
        
        getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment1).commit();

        TabLayout tabs = findViewById(R.id.tabs);
        tabs.addTab(tabs.newTab().setText("통화기록"));
        tabs.addTab(tabs.newTab().setText("스팸기록"));
        tabs.addTab(tabs.newTab().setText("연락처"));

        tabs.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener(){
            @Override
            public void onTabSelected(TabLayout.Tab tab) {
                int position = tab.getPosition();
                Log.d("MainActivity", "선택된 탭 :"+position);

                Fragment selected = null;
                if(position == 0){
                    selected = fragment1;
                }else if(position == 1){
                    selected = fragment2;
                }else if(position == 2){
                    selected = fragment3;
                }

                getSupportFragmentManager().beginTransaction().replace(R.id.container,selected).commit();
            }

            @Override
            public void onTabUnselected(TabLayout.Tab tab) {

            }

            @Override
            public void onTabReselected(TabLayout.Tab tab) {

            }
        });
    }
}

■ 하단 탭 보여주기

- BottomNavigationView 위젯으로 만들기

- 이 위젯은 design 라이브러리를 사용

 

▶ SampleTab2 프로젝트<미완성>, 버전 문제

 

▼ menu_bottom.xml

 

▼ activity_main.xml

 

▼ MainActivity.java

 

05-5 뷰페이저 보여주기

- 손가락으로 좌우 스크롤하여 넘겨볼 수 있는 기능

- 화면 일부분만 차지하고 있어도 그 부분에서만 좌우 스크롤이 동작

- 뷰페이저는 그 안에 프래그먼트를 넣을 수 있고 좌우 스크롤로 프래그먼트를 전환하게 된다. 

- 뷰페이지는 내부에서 어댑터라는 것과 상호작용하기 되어 있는데 이것은 뷰페이지가 여러 개의 아이템 중에 하나를 보여주기 때문에

 

▶ SamplePager 프로젝트

 

▼ MainActivity.java

 

 

▼ activity_main.xml

 

 

05-6 바로가기 메뉴 만들기

- 화면 좌측 상단에 위치한 햄버거 모양 아이콘을 눌렀을 때 나타나는 화면

- 안드로이드에서는 NavigationDrawer라고 부른다. 

 

▶ SampleDrawer 프로젝트

 

 

 

 


Mission10 고객 정보 입력 화면의 구성

Mission11 기본 앱 화면 구성

반응형


댓글