마스터Q&A 안드로이드는 안드로이드 개발자들의 질문과 답변을 위한 지식 커뮤니티 사이트입니다. 안드로이드펍에서 운영하고 있습니다. [사용법, 운영진]

NavigationDrawer 에 Viewpager 프래그먼트 구현 질문드립니다

0 추천

 

 

MovieList.java

public class MovieList extends AppCompatActivity {
    ViewPager pager;
    private AppBarConfiguration mAppBarConfiguration;
    ListPagerAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.movie_list);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        pager = findViewById(R.id.pager);
        adapter = new ListPagerAdapter(getSupportFragmentManager());

        adapter.addItem(createMovieListPage(1));
        adapter.addItem(createMovieListPage(2));
        adapter.addItem(createMovieListPage(3));
        adapter.addItem(createMovieListPage(4));


        pager.setAdapter(adapter);
        pager.setOffscreenPageLimit(adapter.getCount()); // 페이지 개수 6개

        DrawerLayout drawer = findViewById(R.id.drawer_layout);
        NavigationView navigationView = findViewById(R.id.nav_view);
        
        mAppBarConfiguration = new AppBarConfiguration.Builder(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow)
                .setDrawerLayout(drawer)
                .build();
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
        NavigationUI.setupWithNavController(navigationView, navController);
    }

    // 우측상단 점세개 메뉴 만드는 화면인듯
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
//        getMenuInflater().inflate(R.menu.movie_list, menu);
        return true;
    }

    @Override
    public boolean onSupportNavigateUp() {
        NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
        return NavigationUI.navigateUp(navController, mAppBarConfiguration)
                || super.onSupportNavigateUp();
//        return true;
    }
// 뷰페이저 어댑터
    class ListPagerAdapter extends FragmentStatePagerAdapter {
        ArrayList<Fragment> items = new ArrayList<>();

        public ListPagerAdapter(FragmentManager fm) {
            super(fm);
        }

        public void addItem(Fragment item) {
            items.add(item);
        }

        @NonNull
        @Override
        public Fragment getItem(int position) {
            return items.get(position);
        }

        @Override
        public int getCount() {
            return items.size();
        }
    }

    // 페이지 내용 설정할 데이터 보내기 -> MovieFragment.java
    public MovieFragment createMovieListPage(int index) {
        MovieFragment fragment = new MovieFragment();
        Bundle bundle = new Bundle(); // 데이터 넣을 객체

        bundle.putInt("size", adapter.getCount()); // 총 페이지 수 정보
        bundle.putInt("index", index); // 페이지 번호만 넣기
        fragment.setArguments(bundle); // 데이터 보내기
        return fragment;
    }
}

 

MovieFragment.java

public class MovieFragment extends Fragment {
    TextView title; // 영화제목
    TextView rv_rating; // 예매 비율
    ImageView movie_post; // 영화 포스트
    TypedArray typedArray; // 배경화면 배열을 받기위한 TyedArray 객체
    Button detail_btn;

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(R.layout.fragment_movie, container, false);

        movie_post = rootView.findViewById(R.id.movie_post);
        title = rootView.findViewById(R.id.title);
        rv_rating = rootView.findViewById(R.id.reserve_rating);
        typedArray = getResources().obtainTypedArray(R.array.movie_background); // 배경화면 배열 참조
        detail_btn = rootView.findViewById(R.id.view_detail);

        String[] titleArr = getResources().getStringArray(R.array.movie_list); // 타이틀 배열 참조
        String[] ratingArr = getResources().getStringArray(R.array.reserve_rating); // 예매율 배열 참조

        Bundle bundle = getArguments(); // MovieList.java 에서 보낸 Bundle 받기
        int pageSize = bundle.getInt("size"); // 총 페이지수 받기
        int index = bundle.getInt("index"); // 해당 페이지 인덱스 받기


        // 데이터 설정하기
        for(int i=0; i<pageSize; i++) {
            movie_post.setImageResource(typedArray.getResourceId(index-1,-1));
            title.setText(index + ". " + titleArr[index-1]);
            rv_rating.setText("예매율 " + ratingArr[index-1]);
        }

        detail_btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(getActivity(), MainActivity.class);
                startActivity(intent);
            }
        });

        return rootView;
    }
}

 

mobile_navigation.xml

<navigation 
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="com.example.bstcproject.MovieFragment"
        android:label="@string/menu_movielist"
        tools:layout="@layout/fragment_movie" />

    <fragment
        android:id="@+id/nav_gallery"
        android:name="com.example.bstcproject.ui.gallery.GalleryFragment"
        android:label="@string/menu_movieapi"
        tools:layout="@layout/fragment_gallery" />

    <fragment
        android:id="@+id/nav_slideshow"
        android:name="com.example.bstcproject.ui.slideshow.SlideshowFragment"
        android:label="@string/menu_reservation"
        tools:layout="@layout/fragment_slideshow" />
</navigation>

에러 내용(navigation graph인 mobile_navigation.xml 의 homefragment를 MovieFragment로 변경시)

java.lang.NullPointerException: Attempt to invoke virtual method 'int android.os.Bundle.getInt(java.lang.String)' on a null object reference
        at com.example.bstcproject.MovieFragment.onCreateView(MovieFragment.java:38)

MovieFragment.java의 getInt부분 에러

 

codeslave (3,940 포인트) 님이 2020년 10월 2일 질문
codeslave님이 2020년 10월 4일 수정
뷰페이저에서 MovieFragment를 createMovieListPage() 통해서 생성하고 있나요?
createMovieListPage() 메소드가 작성된 MovieList.java 파일 사진도 추가적으로 올렸습니다..저렇게 구현되어 있어요..
그리고 뷰페이저 어댑터를 사용할 때는 데이터만 전달하는 게 일반적인데, 직접 프레그먼트를 생성하고 계시네요. 다음처럼요.

data class Movie(
   @DrawableRes val imgRes: Int,
    val title: String,
    val description: String
)

fun getMovies(): List<Movies> {

}


val movies = getMovies()
adapter.setitems(movies)

1개의 답변

0 추천
음. 먼저 구글 검색하셔서 안드로이드 네이게이션 컴포넌트와 뷰페이저 관련 문서를 정독하시기를 강력히 권해요. 질문 올리신 걸 보니 네이게이션 컴포넌트 기본적인 사용법을 모르시고 쓰시고 계시는 걸로 보이네요.

네이게이션 컴포넌트의 역할은 네이게이션 기능입니다. 따라서 네비게이션 그래프에 설정된 프레그먼트로 이동을 하게 해줍니다.

뷰페이저의 역할은프레그먼트로 여러페이지를 구성하게 해줍니다. 네이게이션과는 다르고 아직 네비게이션 컴포넌트가 뷰페이저를 지원하지는 않습니다.

그러므로, 네이게이션 컴포넌트와 뷰페이져가 각각 어떤 녀석과 연관이 있어야 하는지를 생각해 보시길 바랍니다.

Navigation Component -> HomeFragment
ViewPager -> MovieFragment
따라서 네이게이션 그래프는 그냥 놔두시는 게 맞을 거 같구요, 뷰페이저가 MovieFragment를 생성하는 부분을 확인하셔야 합니다. viwepager2를 사용하고 계시고 있다고 가정하면 FragmentStateAdapter를 사용한다고 가정하면, createFragment() 함수에서 createMoveListPage를 호출하고 있어야 할 것으로 보여지네요. ViewPager가 사용하는 어댑터의 프레그먼트 생성 부분을 확인하셔야 해요. 올려주신 소스에서는 adapter.addItem 부분으로 보여지는데, 이 부분을 먼저 확인해 보세요. 디버거를 사용하셔서 MovieFragment를 생성할 때 해당 값이 잘 세팅되어서 전달되는지 확인해 보세요.
spark (226,420 포인트) 님이 2020년 10월 3일 답변
Navigation Drawer가 올해 뭔가가 많이 바뀐것 같더라구요..그래서 구글링으로 이 함수가 뭘 의미하는지 막 찾아보고 이해하려고 했는데.. 좀 어렵더라구요..
대강 무슨 의미다 하는건 미세하게 알겠는데 정확하게는 역할은 이해 못했습니다..
createFragment 가 무슨 말씀인지 몰라 찾아보니 viewpager의 getItem 함수가
viewpager2의 createFragment 함수로 변경되었다는걸 찾았습니다..
그렇다면 선생님 답변대로라면 제 코드에서 getItem에서 createMovieListPage 함수를 호출하라는 말씀이신데 현재도 사진속 코드에서 보이다시피
addIte()함수에서 createMovieListPage 함수를 통해 리턴되는 프래그먼트를
어댑터 클래스에 있는 ArraList에 저장하고 있는데 어떠한 차이인지 잘모르겠습니다..ㅠㅠ

또한, navhost가 보여줄 네비게이션 그래프를 연결하는데 이 네비게이션 그래프는 아시다시피 프래그먼트로 되어있잖아요..? 근데 저는 이걸 시작부터
이 화면을 보여줘야하는 것인데 왜 그대로 두라고 하시는건지 잘이해가 안갑니다 ㅠㅠ..
HomeFragment 안에 ViewPager가 있는 거 맞으시죠? 네이게이션 컴포넌트는 HomeFragment로 화면을 이동시키는 거구요. HomeFragment안에 있는 ViewPager 가 MovieFragment를 보여주는 거구요. 이게 맞다면, MovieFragment는 네이게이션 컴포넌트와 아무 상관이 없어요. 그건 순전히 ViewPager와 관련된 부분이예요. 네비게이션 컴포넌트에 뷰페이저가 관리하는 프레그먼트를 세팅한다면, 그건 뷰페이저와는 전혀 별개의 프레그먼트가 되는 거예요.
만약 원하시는게 NavigationDrawer에 있는 Navigation Item을 클릭할 때 뷰페이저에서 있는 해당  MovieFragment로 이동하시는 거면, NavigationDrawer와 NavigationComponent를 직접 연결하는 부분을 수정하셔야 하구요.
Navigation Item을 눌렀을 때, 뷰페이저와 상관없이 MovieFragment를 띄우셔야 하는거면 navigation graph안에 MovieFragment를 세팅하시는게 맞는데, 이럴 때는 MovieFragment의 argument도 xml에 세팅해 주시거나 코드를 통해서 제어를 해주셔야 해요. 질문의 내용으로는 원하시는 부분이 어떤 건지 정확하게 이해하기 힘드네요.
본문 전면 수정했습니다! NavigationDrawer 액티비티인 MovieList.java코드와
영화리스트인 MovieFragment.java 코드입니다.
---------------------------------------------------------------------

아뇨아뇨 선생님..뷰페이저가 HomeFragment 안에 있는게 아니라 MovieList.java액티비티 안에 있습니다.
MovieList.java 액티비티에 현재 NavigationDrawer 액티비티 생성시 기본으로 있는 Home,Gallery,SlideShow 프래그먼트가 기본적으로 햄버거 메뉴에 연결되어있습니다..
제가 하려는 것은 이 3개 다 말고 이중 Home Fragment만 제가 만든
영화 목록 프래그먼트(MovieFragment)로 바꾸는 것이에요.

그런데!! 지금 영화목록 화면, 즉 뷰페이저에 영화 리스트를 띄우는 것은 성공했지만, 지금 HomeFragment 대신에 MovieFragment를 띄우는데 실패했어요.
앱을 실행하면, 햄버거메뉴의 영화목록 메뉴 즉 제가 말한 뷰페이저로 구현된 영화 프래그먼트가 떠야하는데.. 뜨기는 뜨는데
그위에 기본 프래그먼트 들이 뜹니다. 첫번째, 두번째 사진을 자세히보시면
사진에 빨간색부분에 텍스트가 적혀져있는데 이게 기본으로 생성되어있던 프래그먼트들입니다..

제가 하려는 것은 시작시에 햄버거메뉴에서 HomeFragment 대신에 제가 만든 프래그먼트를 만들려던 것이므로
말씀하신대로 navigation graph가 있는 mobile_navigation.xml에 가서
HomeFragment와 fragment_home.xml 대신에 MovieFragment와 fragment_movie로 바꿔주니..에러가 납니다.

여기까지가 제가하려던것이고 ㅠㅜ이해되셨을려나 모르겠네요..
요약하면 햄버거메뉴..선생님이 말씀하시는 Navigation Itme이 그것일려나요..
그것을 클릭시 또는 앱시작시에도 뷰페이저로 구현된 MovieFragment를
띄우는 것입니다..

지금 본문 사진을
추가적으로 프래그먼트 구현은..첫번째 두번째 사진을 보시면 아시겠지만
영화 포스터나 타이틀 같은것 빼면 기본적인 UI는 동일하므로
프래그먼트 액티비티를 여러개 만들지않고 하나만 만들어서
MovieList.java(createMovieListPage()) 에서 인덱스(페이지정보)만 MovieFragment 로 보내 해당 인덱스 정보에 맞게 데이터를 설정하는 방식을
사용했습니다... 그런데 에러내용을 보면 그냥하면 잘 나오는데
내비게이션 그래프에서 MovieFragment로 연결을 설정하면 MovieFragment.java의인덱스 받는 부분.. getInt부분에서 에러가 나는군요 ㅠㅠ
먼저 MovieList.java를 MovieListActivity.java로 이름을 바꾸세요. MovieList는 List<Movie> 로 이해되기 때문에 코드를 읽는 사람이 오해하기가 쉽습니다.
1.  NavigationDrawer의 아이템을 클릭해 MovieFragment로 이동하기 위해서는 Navigation graph를 수정하셔야 합니다.
mobile_navigation.xml
 <fragment
        android:id="@+id/nav_movie"
        android:name="<your_package>.MovieFragment"
        android:label="@string/menu_movie"
        tools:layout="@layout/fragment_movie">

2. activity_main_drawer.xml 에 다음과 같이 세팅이 되어야 합니다.
 <item
            android:id="@+id/nav_movie"
            android:icon="@drawable/ic_movie"
            android:title="@string/menu_movie" />
이 때 중요한 부분은 mobile_navigation.xml의 nav_movie와 activity_main_drawer.xml의 nav_movie가 같아야 합니다.

3. MainActivity.java
appBarConfiguation 세팅하는 부분에 조금 전에 추가한 nav_movie의 id를 추가해 주셔야 합니다.
private val appBarConfiguration: AppBarConfiguration by lazy {
        AppBarConfiguration(
            setOf(
                R.id.nav_home, R.id.nav_gallery, R.id.nav_slideshow, R.id.nav_movie
            ), drawerLayout
        )
    }

그리고 추가로 MovieFragment에 데이터를 넘겨주어야 한다면, 그건 살짝 복잡해 집니다. 기존에 navigation drawer에 setNavigationItemSelectedListener 하셔서 기존 동작을 변경시켜야 하기 때문에 추가 작업이 발생합니다.
navView.setNavigationItemSelectedListener(
  new NavigationView.OnNavigationItemSelectedListener() {
      @Override
       public void onNavigationItemSelected(MenuItem item) {
             drawerLayout.close();
             if (item.getItemId() == R.id.nav_movie) {  
                  Bundle bundle = Bundle();
                  bundle. putShort("size", <your_value>);
                  bundle. putShort("index", <your_value>);
                  navController.navigate(item.getItemId(), bundle)
             }
   
             // TODO : 다른 메뉴 아이템에 대한 처리도 해주셔야 합니다.
             return true;
       }
   }
);
추가로 ViewPager 초기화 하는 코드는 뭔가 이상합니다.

        adapter = new ListPagerAdapter(getSupportFragmentManager());

        adapter.addItem(createMovieListPage(1));
        adapter.addItem(createMovieListPage(2));
        adapter.addItem(createMovieListPage(3));
        adapter.addItem(createMovieListPage(4));
        pager.setAdapter(adapter);


이 부분은
List<Movie> movies = Arrays.asList(Movie(1), Movie(2), Movie(3), Movie(4));
adapter = new MoviePagerAdapter(getSupportFragmentManager(), movies);
형태가 되는 게 맞는 거 같고 Fragment의 생성은 adapter안에서 이루어지는 게 맞아 보입니다.
그리고 님의 코드를 보면 뷰에서 모든 게 다 처리되고 있는데, 이건 절대 권장하지 않는 코드스타일입니다. 뷰를 컨트롤 하는 로직은 별도의 클래스에 있어야 합니다. 뷰는 데이터를 받아서 화면에 나타내 주는 게 자신의 역할입니다. 그 이상을 초과하면 코드가 굉장히 어려워 집니다. MVP나 MVVM 패턴을 사용하세요. 그래야 변경이 생겨도 영향이 적은 코드가 되고 테스트를 만들기도 쉬워집니다.
...