- 화면 안에 들어가는 레이아웃이 중복되지 않도록 한 번만 정의하고 재사용할 수 있도록 만든 것이 프래그먼트
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 프로젝트
▼ 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 기본 앱 화면 구성
'컴퓨터 공학 > Android' 카테고리의 다른 글
[Do it 개정6판_안드로이드 스튜디오]둘째마당_11 단말에 데이터베이스와 내용 제공자 만들기 (0) | 2019.12.03 |
---|---|
[Do it 개정6판_안드로이드 스튜디오]둘째마당_10 서버에 데이터 요청하고 응답받기 (0) | 2019.12.03 |
[Do it 개정6판_안드로이드 스튜디오]둘째마당_04 여러 화면 간 전환하기 (0) | 2019.11.29 |
[Do it 개정6판_안드로이드 스튜디오]둘째마당_03 기본 위젯과 드로어블 사용하기 (0) | 2019.11.28 |
[Do it 개정6판_안드로이드 스튜디오]둘째마당_02 레이아웃 익히기 (0) | 2019.11.27 |
댓글