목차
1. 구상
2. 프로젝트 생성
3. 코드 작성(메뉴 - 영화 목록 : 뷰페이저2)
3-1. app_bar_main.xml (툴바+프래그먼트)
3-2. fragment_movie_list.xml ('영화 목록' 레이아웃)
3-3. MovieListFragment.java ('영화 목록' 자바 코드)
4. 코드 작성(바로가기 메뉴)
4-1. activity_main_drawer.xml (메뉴바 화면)
4-2.mobile_navigation.xml (프래그먼트 집합)
4-3. mainActivity.java
5. 실행 결과
6. 발생했던 문제들
7. 참고한 링크
1. 구상
책의 예제는 바로가기 메뉴 + 뷰페이저 + 하단탭 을 합친 앱 화면을 만드는 것이지만 여기서 하단탭을 제외하고 만듦.
대충 아래와 같은 형태로 만들 계획.
(대충 설명)
(양 사이드에 다음 프래그먼트가 살짝 보이는 것은 Carousel을 이용해서 구현해야 하는듯. 예제대로 뷰페이저를 사용하기 위해 저런 형태는 따라하지 않음.
또한 상세 페이지, 메뉴바에 '설정' 메뉴 만드는 것은 일단 패스.
'영화목록' 메뉴를 중점적으로 만들고, 나머지 프래그먼트는 걍 화면에 생성만 시키는 걸로.)
2. 프로젝트 생성
Navigationi Drawer Views Activity로 프로젝트를 생성한다.
3. 코드 작성(매뉴 - 영화 목록 : ViewPager2)
3-1. app_bar_main.xml
오른쪽 하단에 배치된 fab 버튼을 제거했다.(주석처리)
<?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.DoitMissioin101.AppBarOverlay">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/Theme.DoitMissioin101.PopupOverlay" />
</com.google.android.material.appbar.AppBarLayout>
<include layout="@layout/content_main" />
<!--
<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>
3-2. fragment_movie_list.xml : content_main에 들어갈 프래그먼트. '영화 목록' 화면의 레이아웃 구상
프래그먼트 안에 뷰페이저2를 넣어준다. 프래그먼트 안에서 프래그먼트를 전환하는 것이다.
<?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"
android:orientation="vertical"
android:weightSum="15"
tools:context=".Fragment1">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="10"
android:gravity="center"
android:orientation="vertical">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
tools:layout_editor_absoluteX="1dp"
tools:layout_editor_absoluteY="1dp" />
</LinearLayout>
<TextView
android:id="@+id/text_title"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2"
android:gravity="bottom"
android:text="영화제목"
android:textAlignment="center"
android:textSize="48sp" />
<TextView
android:id="@+id/text_info"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:text="영화정보"
android:textAlignment="center"
android:textSize="20sp" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="2">
<Button
android:id="@+id/button"
android:layout_width="180dp"
android:layout_height="60dp"
android:background="@drawable/button_sangsae"
android:text="상세보기"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:textColor="@color/white"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
3-3. MovieListFragment.java
binding을 통해 뷰에 접근하고, FragmentStateAdapter를 사용한다.
package com.example.doitmissioin_10_1.ui.moivelist;
public class MovieListFragment extends Fragment {
ViewPager2 pager2;
private FragmentMovieListBinding binding;
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentMovieListBinding.inflate(inflater, container, false);
View root = binding.getRoot();
pager2 = binding.pager2;
pager2.setOffscreenPageLimit(3);
ArrayList<Fragment> fragments = new ArrayList<>();
Fragment1 fragment1 = new Fragment1();
Fragment2 fragment2 = new Fragment2();
Fragment3 fragment3 = new Fragment3();
fragments.add(fragment1);
fragments.add(fragment2);
fragments.add(fragment3);
FragmentStateAdapter adapter = new MyPagerAdapter(this, fragments);
//페이지가 바뀔 때마다 아래쪽 글자 변경
pager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
updatePageInfo(position);
}
});
pager2.setAdapter(adapter);
//pager2.setSaveEnabled(false); // 상태 유지하는? 이 코드를 작성했더니 에러 해결. 에러 : fragment no longer exists for key f#0
return root;
}
private void updatePageInfo(int position) {
if (binding == null) return; // binding이 null인지 확인
switch (position) {
case 0:
binding.textTitle.setText("라라랜드");
binding.textInfo.setText("예매율 80% | 15세 관람가 | 평점 4.9");
break;
case 1:
binding.textTitle.setText("시카고");
binding.textInfo.setText("예매율 70% | 19세 관람가 | 평점 3.9");
break;
case 2:
binding.textTitle.setText("위키드");
binding.textInfo.setText("예매율 90% | 12세 관람가 | 평점 5.0");
break;
}
}
@Override
public void onDestroyView() {
super.onDestroyView();
binding = null;
}
// 어댑터 클래스를 정의할 때 액티비티에서 정의하는 것이랑 프래그먼트에서 정의하는 차이를 확인하자.
// extends, FragmentManager, 재정의하는 함수가 달라진다.
class MyPagerAdapter extends FragmentStateAdapter {
ArrayList<Fragment> items = new ArrayList<Fragment>();
// 생성자: 프래그먼트 리스트를 전달받음
public MyPagerAdapter(@NonNull MovieListFragment movieListFragment, ArrayList<Fragment> fragments) {
super(movieListFragment);
this.items = fragments;
}
@Override
public int getItemCount() {
return items.size(); // 프래그먼트의 총 개수 반환
}
@NonNull
@Override
public Fragment createFragment(int position) {
return items.get(position); // 특정 위치의 프래그먼트 반환
}
}
}
3-3.(1) onCreateView 기본 생성 코드
public View onCreateView(@NonNull LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
HomeViewModel homeViewModel =
new ViewModelProvider(this).get(HomeViewModel.class);
binding = FragmentMovieListBinding.inflate(inflater, container, false);
View root = binding.getRoot();
HomeViewModel은 기본으로 생성되는 프래그먼트이다. 원래 이 프래그먼트의 이름은 HomeFragment였고, 거기에 짝지어져서 같이 만들어짐 프래그먼트임. (MovieListViewModel로 이름을 고치려고 했지만 귀찮아서 안하고 넘어감)
원래 이 뷰모델에 'This is ~~ Fragment'라는 텍스트가 나타나는 코드가 존재하는데, 없애버림.
이번 프로젝트에선 딱히 중요한 파트는 아님
Fragment@@Binding.inflate 메서드로 binding을 설정해준다.
binding을 사용하면 v.findViewById(ID) 로 뷰를 찾을 필요없이 그냥 binding.ID라고 작성하면 된다.
binding.getRoot 메서드를 사용함. onCreateView의 마지막엔 여기서 할당한 root를 return한다.
3-3.(2) 각각의 영화 목록 프래그먼트 생성
ArrayList<Fragment> fragments = new ArrayList<>();
Fragment1 fragment1 = new Fragment1();
Fragment2 fragment2 = new Fragment2();
Fragment3 fragment3 = new Fragment3();
fragments.add(fragment1);
fragments.add(fragment2);
fragments.add(fragment3);
Fragment1,2,3을 만들고 위 코드를 작성한다.
<Fragment> 타입이 들어가는 ArrayList 배열을 만든다.(배열명:fragments)
add 메서드로 배열에 fragment1,2,3을 넣어준다.
3-3.(3) 어댑터
어댑터 생성
onCreateView 내부
FragmentStateAdapter adapter = new MyPagerAdapter(this, fragments);
FragmentStateAdapter 타입인 어댑터를 생성한다.
여기서 내가 새로 작성하는 MyPagerAdapter 클래스 메서드를 사용하여 생성한다.
어댑터 클래스 작성
MyPagerAdapter 클래스
주의할 점 : FragmentStateAdapter 타입이라는 것. (ViewPager2를 사용하는 경우 FragmentStatePagerAdapter는 호환되지 않는다.)
class MyPagerAdapter extends FragmentStateAdapter {
ArrayList<Fragment> items = new ArrayList<Fragment>();
// 생성자: 프래그먼트 리스트를 전달받음
public MyPagerAdapter(@NonNull MovieListFragment movieListFragment, ArrayList<Fragment> fragments){
super(movieListFragment);
this.items = fragments;
}
@Override
public int getItemCount() {
return items.size(); // 프래그먼트의 총 개수 반환
}
@NonNull
@Override
public Fragment createFragment(int position) {
return items.get(position); // 특정 위치의 프래그먼트 반환
}
}
배열 items를 생성.
클래스 안에 MyPagerAdapter 메서드가 존재한다. 이 메서드를 통해 프래그먼트 리스트를 전달받을 수 있음.
getItemCount, creatFragment 메서드를 재정의하고 return 값을 적절하게 바꿔서 작성한다.
3-3.(4) Pager
onCreateView 내부
pager2 = binding.pager2;
pager2.setOffscreenPageLimit(3);
binding으로 뷰페이저 설정.
setOffscreenPageLimit 메서드로 최대 페이지 수를 설정한다.
//페이지가 바뀔 때마다 아래쪽 글자 변경
pager2.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
@Override
public void onPageSelected(int position) {
super.onPageSelected(position);
updatePageInfo(position);
}
});
pager2.setAdapter(adapter);
//pager2.setSaveEnabled(false);
registerOnPageChangeCallback (나 아직 콜백이 정확히 뭔지 모름. 리스너랑 무슨 차이지)
암튼 페이지가 바뀌면, updatePageInfo 메서드가 실행된다. 이 메서드는 내가 새로 작성한 것이다.
setAdapter로 뷰페이저와 어댑터를 연결한다.
(마지막에 주석처리된 부분은 fragment no longer exists for key f#0 라는 에러가 발생하길래 집어넣었던 코드인데, 계속 이것저것 작성하다보니 없어도 무관했다. 그래도 어떤 메서드인지는 알아두면 좋을 듯 하다..)
updatePageInfo 메서드
페이지가 바뀔 때마다 아랫쪽의 제목과 설명 텍스트가 변경되는 메서드이다.
private void updatePageInfo(int position) {
if (binding == null) return; // binding이 null인지 확인
switch (position) {
case 0:
binding.textTitle.setText("라라랜드");
binding.textInfo.setText("예매율 80% | 15세 관람가 | 평점 4.9");
break;
case 1:
binding.textTitle.setText("시카고");
binding.textInfo.setText("예매율 70% | 19세 관람가 | 평점 3.9");
break;
case 2:
binding.textTitle.setText("위키드");
binding.textInfo.setText("예매율 90% | 12세 관람가 | 평점 5.0");
break;
}
}
4. 코드 작성(바로가기 메뉴)
4-1. activity_main_drawer.xml
메뉴바의 아이콘과 타이틀을 변경해준다.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="navigation_view">
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_movie_list"
android:icon="@android:drawable/ic_dialog_dialer"
android:title="영화 목록" />
<item
android:id="@+id/nav_reservation"
android:icon="@android:drawable/checkbox_on_background"
android:title="영화 예매" />
<item
android:id="@+id/nav_slideshow"
android:icon="@drawable/ic_menu_slideshow"
android:title="@string/menu_slideshow" />
</group>
</menu>
4-2. mobile_navigation.xml
디자인 화면이 아래와 같이 뜨면 된다. 만약 내가 작성한 프래그먼트가 뜨지 않을 경우,
ID 값을 잘 확인하자. (ID 값을 변경할 경우 여기저기서 ID에 맞지 않는 코드가 존재해 에러메시지가 뜰 수 있음. 변경 후엔 제대로 모두 바꿔주기)
startDestination에 들어가는 ID도 잘 들어갔는지 보자.
4-3. MainActivity.java
package com.example.doitmissioin_10_1;
public class MainActivity extends AppCompatActivity {
MovieListFragment movieListFragment;
ReservationFragment reservationFragment;
SlideshowFragment slideshowFragment;
private AppBarConfiguration mAppBarConfiguration;
private ActivityMainBinding binding;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.appBarMain.toolbar);
DrawerLayout drawer = binding.drawerLayout;
NavigationView navigationView = binding.navView;
mAppBarConfiguration = new AppBarConfiguration.Builder(
R.id.nav_movie_list, R.id.nav_reservation, R.id.nav_slideshow)
.setOpenableLayout(drawer)
.build();
// Navigation Component 설정
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);
setSupportActionBar(binding.appBarMain.toolbar);
//navigationView.setNavigationItemSelectedListener(this); 초기 프래그먼트 생성 코드가 있기 때문에, 이걸 쓰면 프래그먼트가 중복 생성됨.
movieListFragment = new MovieListFragment();
reservationFragment = new ReservationFragment();
slideshowFragment = new SlideshowFragment();
// ActionBarDrawerToggle 설정 (GPT)
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, binding.drawerLayout, binding.appBarMain.toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
);
// DrawerLayout에 토글 설정 (GPT)
binding.drawerLayout.addDrawerListener(toggle);
toggle.syncState();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onSupportNavigateUp() {
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment_content_main);
return NavigationUI.navigateUp(navController, mAppBarConfiguration)
|| super.onSupportNavigateUp();
}
}
4-3.(1) 액션바 설정
AndroidMenifest.xml을 열면, NoActionBar 테마인 것을 확인할 수 있다.
setSupportActionBar(binding.appBarMain.toolbar);
그러므로 setSupportActionBar로 액션바를 설정해준다.
그리고 햄버거 모양 토글(메뉴 열기 버튼)이 보이지 않을 수 있다. 이때 아래 코드를 작성하면 다시 토글이 나타난다.
// ActionBarDrawerToggle 설정 (GPT)
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, binding.drawerLayout, binding.appBarMain.toolbar,
R.string.navigation_drawer_open,
R.string.navigation_drawer_close
);
// DrawerLayout에 토글 설정 (GPT)
binding.drawerLayout.addDrawerListener(toggle);
toggle.syncState();
5. 실행결과
다음과 같이 화면을 넘기면 포스터가 바뀌고, 아래에 그에 맞는 제목과 설명이 나타난다.
햄버거 버튼을 눌러서 다른 메뉴를 누를 시 해당 메뉴로 이동한다.
6. 발생했던 문제들
- 뷰페이저를 이용하여 프래그먼트를 전환하는데 아래 텍스트가 바뀌지 않는 문제
원인 : createFragment 메서드에 텍스트 바꾸는 코드 작성함.
해당 메서드는 '생성'만 담당함. 프래그먼트가 바뀌었는지 아닌지는 여기서 알 수 없음. 이 메서드는 프래그먼트 묶음 만들고 끝임.
해결 : updatePageInfo라는 새로운 메서드를 작성함. registerOnPageChangeCallback의 onPageSelected 메서드를 재정의한 다음, updatePageInfo()를 여기에 넣음으로써 페이지 전환에 따라 텍스트가 바뀌도록 만듦.
- 메뉴화면이 겹침
아래와 같이 '영화 목록' 메뉴와 '영화 예매' 메뉴가 겹친 것이 보인다.
원인 : 간단했다. '영화 예매' 프래그먼트의 background 색을 지정해주지 않아서 그런 것.
해결 : '영화 목록' 메뉴 위에 올라가는 것이 되기 때문에, 색을 지정해줘야한다. background color를 하얀색으로 지정해줌
- 영화 목록 내부의 프래그먼트가 이중 생성됨
원인 : 이미 Navigation Component를 설정하는 코드가 있는데(NavigationUI.setup...)
navigationView.setNavigationItemSelectedListener(this); 코드를 코드에 작성함.
해결 : navigationView 설정하는 코드를 제거
7. 참고한 링크
어후.. 이번 거는 꽤 힘들었다. 뷰페이저를 어디다 넣어야하는지도 잘 모르겠고
뷰페이저랑 바로가기메뉴가 합쳐지니까 좀 헷갈렸음. 소스 코드도 이전보다 많아서 원하는 파일을 찾는 것도 바로바로 안되서 좀 현타왔지만.. 그래도 챗지피티 덕분에 막히는 부분에서 나름 빨리 해결할 수 있었다.
'TIL > 안드로이드 스튜디오' 카테고리의 다른 글
6장 - 서비스 (0) | 2025.01.02 |
---|---|
도전!9 - 고객 정보 입력 화면의 구성 (0) | 2024.12.24 |
5장 바로가기 메뉴(NavigationDrawer) 만들기 (0) | 2024.12.23 |
5장 - 뷰페이저 만들기 (ViewPager2) (0) | 2024.12.22 |
5장 상단 탭과 하단 탭 만들기 (0) | 2024.12.20 |