본문 바로가기

Android/UI

ViewPager2로 슬라이드 프래그먼트(Fragment) · 안드로이드 캐러셀 슬라이드 만들기 ― FragmentStateAdapter와 Indicator 사용하기

728x90
반응형

ViewPager2와 Fragment


ViewPager를 이용하여 슬라이딩 가능한 예제들은 많이 보았는데요.

ViewPager2를 이용하여 래그먼트 간 슬라이드 하기 위한 예제가 많이 없어 만들어보았습니다.

ViewPager2는 수직방향 슬라이드와 right-to-left(RTL) 슬라이딩(좌에서 우로)도 지원하고 있습니다.

그리고 notifyDatasetChanged()가 버그 없이 잘 작동한다고 합니다.

기존의 ViewPager의 매서드들과도 차이가 좀 있습니다.

예를들면 Fragment를 사용하기 위해 FragmentPagerAdapterFragmentStatePagerAdapter대신 FragmentStateAdapter를 사용해야합니다.

 

ViewPager에서 ViewPager2로 이전하기 위한 변경

  • getCount() → getItemCount()
  • getItem() → createFragment()
  • FragmentPagerAdapter, FragmentStatePagerAdapter → FragmentStateAdapter

 

작년에 ViewPager2가 릴리즈 됬는데도 불구하고 ViewPager2에서 Fragment를 이용한 예제가 많이 없어 조금 헤맸습니다.

많은 소스들이 Fragment없이 그냥 단순히 화면 색만 바꾼다거나 글씨만 바꿀 뿐이어서,

Fragment를 만들어 잘 운용할수 있는 ViewPager를 캐러셀(carousel) 형식으로 만들어 보았습니다.

Indicator도 사용하기 위해서 여러가지 소스를 합치고 수정하였습니다.

 

코드 미리보기


 

 

 

Gradle


dependencies { 
   ...
   implementation 'androidx.viewpager2:viewpager2:1.0.0'
   implementation 'me.relex:circleindicator:2.1.6'
   ...
}

ViewPager2Indicator를 사용하기위해 추가해줍니다.

gradle(Mobile:app) → dependencies 추가

 


 

values 파일 추가


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <dimen name="pageMargin">20dp</dimen>
    <dimen name="offset">20dp</dimen>
    <dimen name="pageMarginAndOffset">40dp</dimen>
    <dimen name="frameSize">320dp</dimen>
</resources>

layout 을 위한 dimens.xml 파일입니다.

 


 

activity_main.xml


<?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"
    tools:context=".MainActivity">

    <FrameLayout
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="@dimen/frameSize">
        <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/viewpager"
            android:clipToPadding="false"
            android:clipChildren="false"
            android:layout_width="match_parent"
            android:layout_height="@dimen/frameSize" />

        <me.relex.circleindicator.CircleIndicator3
            android:id="@+id/indicator"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:layout_gravity="bottom"/>

    </FrameLayout>

    <TextView
        android:id="@+id/text"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="뷰어 밑에 위치"
        android:gravity="center"/>

</LinearLayout>

ViewPager2를 이용한 FrameLayout을 원하는 위치에 추가하였습니다.

인디케이터도 FrameLayout에 넣어 ViewPager 위에 띄우도록 하였습니다.


 

fragment.xml 추가


프래그먼트 layout fragment_1p.xml, fragment_2.pxml, fragment_3p.xml, fragment_4p.xml을 추가합니다.

fragment_1p.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_marginLeft="@dimen/pageMarginAndOffset"
        android:layout_marginRight="@dimen/pageMarginAndOffset"
        android:layout_height="@dimen/frameSize"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imgBanner1"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#ffcccc"
            android:contentDescription="@string/app_name"/>

        <TextView
            android:id="@+id/tvName1"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_centerInParent="true"
            android:textColor="@android:color/white"
            android:textSize="20sp"
            android:textStyle="bold"
            android:text="Hello This is Page 1"/>

    </RelativeLayout>


</FrameLayout>

 

fragment_2p.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_marginLeft="@dimen/pageMarginAndOffset"
        android:layout_marginRight="@dimen/pageMarginAndOffset"
        android:layout_height="@dimen/frameSize"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imgBanner2"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#afe3ff"
            android:contentDescription="@string/app_name"/>

        <TextView
            android:id="@+id/tvName2"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_centerInParent="true"
            android:textColor="@android:color/white"
            android:textSize="20sp"
            android:textStyle="bold"
            android:text="Hello This is Page 2"/>

    </RelativeLayout>


</FrameLayout>

 

fragment_3p.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_marginLeft="@dimen/pageMarginAndOffset"
        android:layout_marginRight="@dimen/pageMarginAndOffset"
        android:layout_height="@dimen/frameSize"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imgBanner3"
            android:layout_centerInParent="true"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#aaff88"
            android:contentDescription="@string/app_name"/>

        <TextView
            android:id="@+id/tvName3"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_centerInParent="true"
            android:textColor="@android:color/white"
            android:textSize="20sp"
            android:textStyle="bold"
            android:text="Hello This is Page 3"/>

    </RelativeLayout>


</FrameLayout>

 

fragment_4p.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_marginLeft="@dimen/pageMarginAndOffset"
        android:layout_marginRight="@dimen/pageMarginAndOffset"
        android:layout_height="@dimen/frameSize"
        android:orientation="vertical">

        <ImageView
            android:id="@+id/imgBanner4"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#999999"
            android:contentDescription="@string/app_name"/>

        <TextView
            android:id="@+id/tvName4"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:gravity="center"
            android:layout_centerInParent="true"
            android:textColor="@android:color/white"
            android:textSize="20sp"
            android:textStyle="bold"
            android:text="Hello This is Page 4"/>

    </RelativeLayout>


</FrameLayout>

 

Fragment1


 

 

Fragment.java


프래그먼트 java파일 FragFirst.java, FragSecond.java, FragThird.java, FragFourth.java를 만들어줍니다.

FragmentFirst.java

public class FragFirst extends Fragment {
  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
                           Bundle savedInstanceState) {
      ViewGroup rootView = (ViewGroup) inflater.inflate(
              R.layout.frame_1p, container, false);

      return rootView;
  }
}

FragmentSecond.java

public class FragSecond extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.frame_2p, container, false);

        return rootView;
    }
}

 

FragmentThird.java

public class FragThird extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.frame_3p, container, false);

        return rootView;
    }

}

 

FragmentFourth.java

public class FragFourth extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.frame_4p, container, false);

        return rootView;
    }

}

각각 Fragment를 만들고 레이아웃이랑 연결하였습니다.

 

Fragment에 아래 코드 처럼 newInstance등을 추가하거나 Bundle을 이용하여 Int나 String을 저장해 볼 수 있습니다.

활용에 참고해보세요.

public class FragSecond extends Fragment {
    // Store instance variables
    private int frag_num;
    TextView data_t;

    public FragSecond(){

    }

    // newInstance constructor for creating fragment with arguments
    public static FragSecond newInstance(int num){
        FragSecond fragment = new FragSecond();
        Bundle args = new Bundle();
        args.putInt("num",num);
        fragment.setArguments(args);
        return fragment;
    }

    // Store instance variables ba
    // sed on arguments passed
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        frag_num = getArguments().getInt("num",0);
    }

    // Inflate the view for the fragment based on layout XML
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.frame_2p,container,false);
        return view;
    }
    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstancdState){
        super.onViewCreated(view,savedInstancdState);
        data_t = (TextView) view.findViewById(R.id.tvName2);
        data_t.setText("Page " + frag_num);
    }

    @Override
    public void onStop() {
        super.onStop();
    }
}

 

 

 


 

MyAdapter.java


ViewPager2 Adapter 파일을 만들어줍니다.

FragmentStateAdapter를 사용하였습니다.

public class MyAdapter extends FragmentStateAdapter {

    public int mCount;

    public MyAdapter(FragmentActivity fa, int count) {
        super(fa);
        mCount = count;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        int index = getRealPosition(position);

        if(index==0) return new FragFirst();
        else if(index==1) return new FragSecond();
        else if(index==2) return new FragThird();
        else return new FragFourth();

    }

    @Override
    public int getItemCount() {
        return 2000;
    }

    public int getRealPosition(int position) { return position % mCount; }

}

ViewPager 이전버전과 매서드 이름이 다른것을 확인하세요.

프래그먼트를 무한으로 슬라이딩하기위해 getItemCount()는 2000으로 임의로 정하였습니다. (2000번 슬라이딩.. 진짜무한은 아니죠.)

mCount는 생성할 프래그먼트 갯수가 들어옵니다. 

position에서 프래그먼트 갯수로 나눈 나머지값이 진짜 position입니다. (예. 슬라이드 4개이므로 0, 1, 2, 3이 반복되어야함.)

createFragment(position)에서 진짜 position마다 원하는 Fragment를 연결해주면 됩니다.

 

만약 Fragment에 newInstance()를 추가하였다면 createFragment에서 return FragFirst.newInstance(); 로 하면 됩니다.(아래 코드 참조. 또한 switch문을 사용하였음)

 @NonNull
    @Override
    public Fragment createFragment(int position) {
        int index = getRealPosition(position);

        switch(index){
            case 0 :
                return FragFirst.newInstance(index+1);
            case 1:
                return FragSecond.newInstance(index+1);
            case 2:
                return FragThird.newInstance(index+1);
            case 3:
                return FragFourth.newInstance(index+1);
            default:
                return null;
        }

    }

 


 

MainActivity.java


MainActivity에선 ViewPager2, FragmentStateAdapter, Indicator(CircleIndicator3 사용)를 잘 정의하고 사용해주면 됩니다.

public class MainActivity extends FragmentActivity {

    private ViewPager2 mPager;
    private FragmentStateAdapter pagerAdapter;
    private int num_page = 4;
    private CircleIndicator3 mIndicator;

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

        //ViewPager2
        mPager = findViewById(R.id.viewpager);
        //Adapter
        pagerAdapter = new MyAdapter(this, num_page);
        mPager.setAdapter(pagerAdapter);
        //Indicator
        mIndicator = findViewById(R.id.indicator);
        mIndicator.setViewPager(mPager);
        mIndicator.createIndicators(num_page,0);
        //ViewPager Setting
        mPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);
        mPager.setCurrentItem(1000);
        mPager.setOffscreenPageLimit(3);

        mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (positionOffsetPixels == 0) {
                    mPager.setCurrentItem(position);
                }
            }

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                mIndicator.animatePageSelected(position%num_page);
            }

        });


        final float pageMargin= getResources().getDimensionPixelOffset(R.dimen.pageMargin);
        final float pageOffset = getResources().getDimensionPixelOffset(R.dimen.offset);

        mPager.setPageTransformer(new ViewPager2.PageTransformer() {
            @Override
            public void transformPage(@NonNull View page, float position) {
                float myOffset = position * -(2 * pageOffset + pageMargin);
                if (mPager.getOrientation() == ViewPager2.ORIENTATION_HORIZONTAL) {
                    if (ViewCompat.getLayoutDirection(mPager) == ViewCompat.LAYOUT_DIRECTION_RTL) {
                        page.setTranslationX(-myOffset);
                    } else {
                        page.setTranslationX(myOffset);
                    }
                } else {
                    page.setTranslationY(myOffset);
                }
            }
        });

    }

}

 

ViewPager2 Item을 2000개 만들었으니 현재 위치를 1000으로setCurrentItem(1000) 하여 좌우로 슬라이딩 가능하도록 하였습니다.

 

ViewPager2의 registerOnPageChangeCallback 추상클래스로 필요한 onPageSelected, onPageScrolled 메서드를 재정의 하였습니다. 이는 인디케이터를 페이지에 맞게 잘 사용하기 위해서 정의했습니다.

setPageTransformer를 통해 프래그먼트간 애니메이션 맞춤설정도 가능합니다.

 


이 페이지 참고 문서

 

ViewPager2와 Fragment, 또 Indicator까지 사용하는 기본적인 예제를 만들어보았습니다.

github에 소스를 올려놓았으니 위의 링크를 눌러 파일 확인이 가능합니다.

이제 이를 이용해서 각각의 Fragment도 운영해 볼 수 있겠네요~^^

 

 

 

 

728x90
반응형