74일차(3)/Android App(27) : ViewPager 예제

- 원래 한 프로젝트에는 모듈 1~2개 정도만 만들어야 한다.
- 모듈 하나라도 문제가 있으면 다른 파일에도 영향을 미치기 때문에!

File-Project Structure
- 프로젝트 구조를 설정할 수 있는 환경설정

- 이 각각의 모듈은 서로 연관성이 있다.
- 하나라도 오류가 있으면 다른 것들이 build가 되지 않는다.
- test 모듈을 생성해서 지우고싶은데, 우클릭메뉴에 delete가 없다면?

- 위쪽의 - 버튼으로 에러나거나 필요없는 모듈을 삭제할 수 있다.

- 프로젝트에서 삭제하지만 어떤 파일도 디스크에서 삭제되지는 않는다는 뜻!
- 의존성만 제거하는 것이다. 실제 파일 시스템에서 삭제한 것은 아니다.

- Android 이것은 안드로이드 앱을 만들 때 최적화된 탭이다. 다른 탭으로도 볼 수 있다.
- Android폴더에서는 필요한 것만 있었는데, Project 화면에 들어가보면 있는 폴더를 다 볼수있다.

- test01모듈이 여전히 존재한다.
- 깨끗이 지우고 싶으면 여기서 마저 지우면 된다.

- delete 가 이제는 있다. 하지만 항상 있는것은 아니다.
- 의존성을 삭제하고 나면 delete할 수 있는 버튼이 생긴다.
새 모듈 생성 - step10viewpager

- Tabbed Activity로 생성!

- Launcher Activity 체크
- 앱이 처음 시작했을 때 인덱스 역할을 해줄 수 있다는 뜻이다.
- 이것을 인덱스 액티비티로 만들것인가 여부를 결정하는 것이라고 보면 된다!
- run 해보면 탭이 있는 것을 볼수있다.

- 이 각각의 탭이 프래그먼트이다!
MainActivity
package com.example.step10viewpager;
import android.os.Bundle;
import android.view.View;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;
import com.example.step10viewpager.databinding.ActivityMainBinding;
import com.example.step10viewpager.ui.main.SectionsPagerAdapter;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.tabs.TabLayout;
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//view binding을 이용해서 화면 구성하기
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// ViewPager에 연결할 Adapter 객체 생성
SectionsPagerAdapter sectionsPagerAdapter = new SectionsPagerAdapter(this, getSupportFragmentManager());
// ViewPager의 참조값을 얻어와서
ViewPager viewPager = binding.viewPager;
// Adapter 연결하기
viewPager.setAdapter(sectionsPagerAdapter);
//상단 탭의 참조값 얻어와서
TabLayout tabs = binding.tabs;
//ViewPager와 연동되도록 연결하기
tabs.setupWithViewPager(viewPager);
//떠있는 Action Button의 참조값 얻어와서
FloatingActionButton fab = binding.fab;
//해당 버튼을 눌렀는지 감시할 리스너 등록
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//하단에서 잠시 올라왔다가 사라지는 Snackbar 띄우기
Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
}
- binding을 사용하고 있다.
- ActivityMainBinding 클래스가 자동으로 만들어져 있다.

- build.gradle에 들어가보면 buildfeatures가 자동으로 들어가 있는 것을 볼 수 있다
- tab으로 선택해서 만들었기 때문에!
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/Theme.MyAndroid.AppBarOverlay">
<TextView
android:id="@+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:minHeight="?actionBarSize"
android:padding="@dimen/appbar_padding"
android:text="@string/app_name"
android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title" />
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager.widget.ViewPager
android:id="@+id/view_pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_marginEnd="@dimen/fab_margin"
android:layout_marginBottom="16dp"
app:srcCompat="@android:drawable/ic_dialog_email" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>
- 코디네이터 레이아웃이 있다.
- 이것을 쓰는 이유는 위의 탭을 나타내기 위해서이다.
- 꼭 이것을 써야하는건 아님!

- 그리고 뭔가 위에 떠있는 버튼이 하나 있다. 이것은 FloatingActionButton 이다.

- 그리고 화면을 꽉 채우고 있는 Viewpager가 있다.
- 화면을 바꿔가는 기능이다. 좌우로 스와이프하며 컨텐츠를 바꿔볼 수 있다.
- 레이아웃 분석

- 각각의 코드 확인하기!
- ViewPager 안에 프래그먼트
- 각각의 탭에 해당되는 프래그먼트(총2개)
- 프로팅 액션 버튼은 우하단에 마진을 부여해서 붙어있다.

- ui.main 패키지가 만들어져 있다.
PlaceholderFragment 클래스
package com.example.step10viewpager.ui.main;
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.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import com.example.step10viewpager.databinding.FragmentMainBinding;
/**
* A placeholder fragment containing a simple view.
*/
public class PlaceholderFragment extends Fragment {
//private static final String ARG_SECTION_NUMBER = "section_number";
private PageViewModel pageViewModel;
private FragmentMainBinding binding;
//인자로 전달하는 인덱스에 해당하는 새 Fragment(PlaceholderFragment) 객체를 리턴하는 메소드
public static PlaceholderFragment newInstance(String ownerName) {
//fragment 객체를 생성하고
PlaceholderFragment fragment = new PlaceholderFragment();
//Bundle 객체를 생성해서
Bundle bundle = new Bundle();
//"ownerName"이라는 키값으로 전달된 이름을 담고
bundle.putString("ownerName", ownerName);
//Fragment 에 전달하고
fragment.setArguments(bundle);
//해당 fragment 객체를 리턴해준다.
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//PageViewModel 을 사용할 준비하기
pageViewModel = new ViewModelProvider(this).get(PageViewModel.class);
//Fragment에 전달받은 인자(Bundle)을 얻어낸다.
Bundle bundle=getArguments();
//Bundle 객체에 "ownerName"이라는 키값으로 담겨있는 이름을 얻어낸다.
String ownerName=bundle.getString("ownerName");
//MutableLiveData를 수정한다.
pageViewModel.setOwnerName(ownerName);
}
@Override
public View onCreateView(
@NonNull LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentMainBinding.inflate(inflater, container, false);
//fragment_main.xml 문서를 전개해서 만든 view의 참조값
View root = binding.getRoot();
//textview의 참조값을 얻어와서
final TextView textView = binding.sectionLabel;
//PageView 모델이 가지고 있는 데이터를 관찰하고 있다가 혹시 변경이 되면 UI를 업데이트할 옵저버 등록
//단, 이 뷰의 주인(프래그먼트 혹은 액티비티)가 활성화된 상태에서만 동작하겠다는 의미
pageViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
//가공된 문자열이 들어온다.
@Override
public void onChanged(@Nullable String s) {
//textView 문자열을 출력하기
textView.setText(s);
}
});
//버튼을 눌렀을 때 동작할 리스너 등록
binding.changeBtn.setOnClickListener(view -> {
//입력한 이름을 읽어와서
String newName=binding.inputName.getText().toString();
//PageViewModel 이 가지고 있는 라이브 데이터를 업데이트한다.
pageViewModel.setOwnerName(newName);
});
return root;
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
}
- fragment 하나를 리턴하는 메소드가 들어있다.
- 인자로 전달하는 인덱스에 해당하는 새 Fragment(PlaceholderFragment) 객체를 리턴하는 메소드

- 스태틱 메소드로, 전달받은 인덱스에 해당하는 fragment를 리턴한다.

- static 메소드. newInstance를 사용해서 객체를 생성한다.
- 아래는 프래그먼트의 생명주기 메소드들이다.
- frament_main.xml은 textView하나만 들어있다.

- onCreateView에서 그 레이아웃을 전개해서 View를 리턴해준다.
- view는 이 바인딩으로부터 얻어낸다.

- View는 결국 이 root를 리턴해주는 것이다.
- textview의 참조값을 얻어와서 문자열을 출력하기(setText)
- 이 예제에 들어있는 주요기능 및 익혀야 할 것
1) Fragment 사용법
2) ViewPage사용법
3) ViewModel + LiveData의 사용법
-> 검색해서 공부해보면 좋다. 실제 모델을 수정하면 view UI가 자동으로 업데이트되도록 해볼 것

- 그냥 textview에 출력하면 되는데 좀 특이한 방법으로 출력하고 있다.
- 이게 무엇인지 배울 예정~
- onCreateView에서는 바인딩으로 xml문서를 전개해서 View를 리턴하고 있는 것이다!
- onDestroyView는 해제 코드가 들어있다.
SectionsPagerAdapter 클래스
package com.example.step10viewpager.ui.main;
import android.content.Context;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
public class SectionsPagerAdapter extends FragmentPagerAdapter {
String[] roomNames={"첫번째방","두번째방"};
private final Context mContext;
public SectionsPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
@Override
public Fragment getItem(int position) {
return PlaceholderFragment.newInstance("주인 없음");
}
@Nullable
@Override
public CharSequence getPageTitle(int position) {
return roomNames[position];
}
@Override
public int getCount() {
// Show 2 total pages.
return 2;
}
}
- 이전에 ListView 예제에서, listview를 를 바로 보여주지 않고 아답타를 연결했다.
- 뷰페이저에 데이터를 출력할때도 데이터를 바로 연결하지 않고, 아답타를 통해서 연결한다.
- SectionsPagerAdapter의 작동 방식

- 이런 구조이다.
- Adapter는 ListView에서 사용할 셀 뷰를 공급한다.
- 아래에서는 ViewPager 에게 page fragment를 공급한다. 페이지 하나하나가 있다.
- 아답타는 이 여러개의 프래그먼트를 바꿔가며 보여주는 것!

- 여기서 사용하는 이 데이터를 LiveData 로 사용한다.
- 사용법은 좀 불편하다.
(Angular js 의 모델-뷰 연결. 모델이 바뀌면 뷰도 바로 자동으로 업데이트됨. 이것을 본따서 만든 것!)
- 여기서도 라이브데이터를 변경하면 자동으로 업데이트되게 했다.
- 만든 Adapter는 pagerAdapter를 상속받아서 만든 것이고, 추상메소드들 override해서 만들었다.

- Viewpager가 알아서 호출하는 메소드(fragment를 공급하는 메소드)
- 0번째 프래그먼트 내놔! → 만들어서 리턴해주어야 한다. custom adapter에서 뷰를 리턴해주듯이!
- static 메소드를 호출해서 프래그먼트를 만들어서 리턴해주고있다.
- pageTitle : 만들어진 페이지 타이틀을 전달
- getCount : 전체 페이지 수 (현재 2개)
PageViewModel 클래스
package com.example.step10viewpager.ui.main;
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.Transformations;
import androidx.lifecycle.ViewModel;
public class PageViewModel extends ViewModel {
//수정 가능한 라이브 데이터
private MutableLiveData<String> ownerName = new MutableLiveData<>();
//읽기 전용 라이브 데이터
private LiveData<String> mText = Transformations.map(ownerName, new Function<String, String>() {
@Override
public String apply(String input) {
//input을 가공해서 새로운 정보를 얻어낸다.
String info="이 구역의 주인은 : "+input;
return info;
}
});
//라이브 데이터를 변경하는 메소드
public void setOwnerName(String newOwner) {
//인자로 전달받은 이름을 MutableliveData 객체의 setValue() 메소드의 인자로 전달해서 변경한다.
ownerName.setValue(newOwner);
}
//읽기 전용 라이브 데이터를 리턴하는 메소드
public LiveData<String> getText() {
return mText;
}
}
- ViewModel 을 상속받아서 만든 클래스이다.
- MutableLiveData<> : 수정 가능한 라이브 데이터
- LiveData<> : 읽기 전용 라이브 데이터
- setOwnerName 메소드를 사용해서 수정하고,
아래 getText 메소드를 사용해서 가져간다.

- 인터페이스의 참조값을 익명 이너클래스를 사용해서 전달해주는 것이다.

- Function<> 인터페이스는 간단하다.
- i 타입을 받아서 o 타입을 반환해준다! input type / output type
- <> 제너릭 타입 2개를 가지고 있는 interface 이다.
- 정수를 받아서 가공해서 리턴해주는 것이라고 생각하면 된다.

- 여기서 출력되는 메소드가 이 위치에 출력되는 것이다.
- 읽기 전용 라이브데이터가 뷰페이저에 하나하나 출력된다.
- 이 읽기전용 라이브 데이터는 어디서 사용하는지?
- PlaceholderFragment 클래스의 onCreate 메소드에서 사용한다.
- pageviewmodel 필드도 있다.

- 만들어서 준비된 것을 가져와서 onCreateView() 에서 사용한다.
- onChanged가 자동으로 호출되면서 문자열이 출력된다.
- 복잡하다... 근데 데이터가 수정되면 자동으로 업데이트되게 하려고 이렇게 만든 것..
- MainActivity에서 adapter 객체 생성-> view참조값 얻어와서
- 뷰페이저는 아답타로부터 프래그먼트를 공급받아야 한다.

- 이 탭 코드가 없으면 상단탭이 출력이 안된다.

- 플로팅버튼을 클릭하면 snackbar가 호출된다.
- snackbar에서 뭔가 동작을 하고싶다면 저 아래 null 자리에 listener를 등록하면 된다.

- adapter는 프래그먼트를 공급하는 공급자!
- 아답타에 가보면 3개의 메소드가 override 되어 있다.
- viewPager가 호출할 예정인 메소드이다.

- 인덱스 프래그먼트가 필요하면 getItem에 0을 전달하면 placeholder 메소드에서 가져와준다.
- PlaceHolder 클래스에가면 번들에담겨서 putInt에서 전달되고,

- 전달된 것을 getArgument로 읽어준다.
- 이 index가 아래 onCreateView 에서 textView로 출력된다.
- 프래그먼트 레이아웃 수정

- 지금 constraint 로 되어있는데, textView에 걸린 모든 제약조건을 해제하고 이렇게 맞춰준다.
- 텍스트를 넣고 배경을 연두색으로 수정
- plaintext, button 추가해주고 id 부여

- 이런 레이아웃으로 2개의 프래그먼트가 만들어진다.
- 사용자가 입력한 이름을 읽어와서 초록색 박스에 출력하도록 할 것!

- putString 메소드 사용
- activity와 레이아웃과의 관계, ViewBinding 등을 보면 된다.

- 이름을 넣어서 번들에 담겨서 결국 fragment 로 전달되게 한다 -> 최종적으로 프래그먼트가 리턴된다.

- 여기서 리턴한 프래그먼트가 언젠가 초기화되면 이 부분의 메소드가 호출된다.

- 이 번들이 여기에 전달되는데(빨간색), ownerName으로 저장된 이름이 들어있을것
- 페이지 구조를 문자열을 전달받는 구조로 바꿀 것이다.

- 여기서 생성하고 담긴 번들 객체가 읽어져서 bundle.getString 에서 사용된다.
- onCreate 되었을 때 bundle에 전달된 내용은 남아있는다.
- pageViewModel 수정
- ownerName 문자열을 관리

- apply 메소드를 override하기
- String 타입을 받아서 String 타입을 리턴하는 메소드로 바꿔준다.
- 이 input을 가공해서 String 타입을 만들어낸다.

- 읽기전용 라이브데이터를 만드는데, 혹시 가공이 필요하다면 transfermations.map을 사용해서 새로운 정보를 얻어내기
- 메소드 입력데이터, 출력데이터를 상황에 맞게 만들어서 사용하면 된다.

- setValue 되면 여기 이름이 자동으로 갱신되도록 함
- setIndex를 setOwnerName으로 수정해줌
//PageView 모델이 가지고 있는 데이터를 관찰하고 있다가 혹시 변경이 되면 UI를 업데이트할 옵저버 등록
pageViewModel.getText().observe(getViewLifecycleOwner(), new Observer<String>() {
- 화면 UI를 업데이트할 Observer(관찰자) 객체를 생성해준다.

- 이 장점 때문에 이런 구조를 사용한다고 보면 된다.
- 프래그먼트, 액티비티가 죽어도 View, Model은 살아있다.
- 그래서 다른 액티비티에서도 똑같은 모델을 사용해도 그 모양으로 유지된다.
- getViewLifeCycleOwner 가 프래그먼트가 된다

- 데이터가 바뀌면 옵저버가 바뀐다.
- 새로운 문자열이 들어온다. 이것을 textView에 넣어주면 출력한다.

- 문자열 전달

- 현재까지 만든 걸로는 이렇게 작성된다.
- 버튼의 동작 정의하기
- binding 사용해서 참조값 얻어내기

- 새 이름을 받아와서 넣어주도록 한다.
- 이 코드에는 textview를 업데이트하는 코드가 없다. 하지만 잘 업데이트된다!

- 옵저버가 관찰하고 있다가 setText() 로 텍스트뷰에 들어올 수 있도록 넣어준다.

- 사용자로부터 입력받은 값으로 textView를 수정할 수 있게 된다.
'국비교육(22-23)' 카테고리의 다른 글
74일차(5)/Android App(29) : Broadcast Receiver (0) | 2023.01.24 |
---|---|
74일차(4)/Android App(28) : Bottom Navigation 예제 (0) | 2023.01.24 |
74일차(2)/Android App(26) : View 로 미니 슈팅게임 만들기(4) (0) | 2023.01.20 |
74일차(1)/Android App(25) : Kotlin 연산자 / apply / when (0) | 2023.01.20 |
73일차(3)/Android App(24) : View 로 미니 슈팅게임 만들기(3) (0) | 2023.01.19 |