Android Studio - ViewPager2 & DotsIndicator

Minjae Lee·2024년 12월 21일

Android Studio

목록 보기
10/12

0. workflow

필자는 viewpager2dotsindicator를 사용하여 좌우로, 한페이지 단위로 스와이프 됨과 동시에 진행도를 확인할 수 있는 fragment 형식을 구현하고자 한다.

0-1. 외부 모듈 implementation

0-2. 화면 구성

0-3. Adapter 구성 및 MainActivity 구성

0-4. Fragment의 데이터 통합하여 MainActivity에 저장

1. 외부 모듈 implementation

보다 쉬운 개발을 위해, viewpager2에 attach할 수 있는 dotsindicator모듈을 사용한다.

  • build.gradle (Module :app)
dependencies {
    implementation 'androidx.viewpager2:viewpager2:1.1.0'
    implementation 'com.tbuonomo:dotsindicator:5.1.0'
}

2. 화면 구성

아래의 두 가지 요소를 xml 파일에 추가한다.

  • viewPager2 : fragment를 화면에 띄우기 위한 container
  • viewpagerdotsindicator : viewPager2와 연동되어 상태를 나타내는 요소

추가로, dotsindicator 색상 및 모양을 바꾸려면 각 dotsindicator의 속성 dotsColor, selectedDotColor, dotsSize, dotsSpacing를 수정한다. 이외의 속성을 바꾸고 싶으면 dotsindicator 사이트를 방문하여 attribute의 설명을 읽고 수정한다.

  • 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:id="@+id/linearLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    tools:context=".MainActivity">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/view_pager"
        android:layout_width="0dp"
        android:layout_height="600dp"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toTopOf="@+id/dot1"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.65999997" />

    <com.tbuonomo.viewpagerdotsindicator.DotsIndicator
        android:id="@+id/dot1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="10dp"
        app:dotsColor="@android:color/holo_green_light"
        app:dotsSize="15dp"
        app:dotsSpacing="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/view_pager"
        app:progressMode="true"
        app:selectedDotColor="@android:color/holo_green_dark" />

    <com.tbuonomo.viewpagerdotsindicator.SpringDotsIndicator
        android:id="@+id/dot2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        app:dotsColor="@android:color/holo_orange_light"
        app:dotsSize="15dp"
        app:dotsSpacing="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/dot1"
        app:selectedDotColor="@android:color/holo_orange_dark"
        app:stiffness="300" />

    <com.tbuonomo.viewpagerdotsindicator.WormDotsIndicator
        android:id="@+id/dot3"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        app:dotsColor="@android:color/holo_blue_light"
        app:dotsSize="15dp"
        app:dotsSpacing="5dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/dot2"
        app:selectedDotColor="@android:color/holo_blue_dark" />

</androidx.constraintlayout.widget.ConstraintLayout>

3. Java 코드

우선 viewPager에 fragment를 띄우기 위해 FragmentStateAdapter를 상속받는 Adapter를 생성한다. 구현해야 할 요소는 아래와 같다.

  • mFragment : 띄울 fragment를 Adapter 내에서 ArrayList 형태로 저장한다.

  • ViewAdapter : 생성자이며, Viewpager2를 띄울 Activity와 Fragment ArrayList를 입력받고, Class 내부 변수에 저장한다.

  • createFragment : 입력한 Fragment ArrayList에서 스와이프를 통해 몇 번째 fragment를 띄울 지 결정한다. 사용자가 호출하지 않는 함수이다.

  • getItemCount : Fragment ArrayList의 길이를 반환하는 함수이며, dotsindicator의 개수를 구할 때 사용한다. 사용자가 호출하지 않는 함수이다.

  • ViewAdapter.java

public class ViewAdapter extends FragmentStateAdapter {
    private ArrayList<Fragment> mFragments;

    public ViewAdapter(@NonNull FragmentActivity fragmentActivity, ArrayList list) {
        super(fragmentActivity);
        this.mFragments = list;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        return mFragments.get(position);
    }

    @Override
    public int getItemCount() {
        return mFragments.size();
    }
}

MainActivity에서는 아래의 작업을 수행한다.

  • 띄울 Fragment들을 ArrayList 형태로 저장
  • ViewPager와 Adapter 연결
  • DotsIndicator와 ViewPager 연결 (필자는 3가지 종류의 dotsindicator를 보여주기 위해 코드를 작성하였지만, 마음에 드는 dotsindicator만 사용해야 한다. 종류에 따라 스와이프 되는 모션이 다르다.)

아래는 MainActivity 코드이다.

  • MainActivity.java
public class MainActivity extends AppCompatActivity {
    ViewPager2 viewPager;
    DotsIndicator dotsIndicator;
    SpringDotsIndicator springDotsIndicator;
    WormDotsIndicator wormDotsIndicator;
    ViewAdapter viewAdapter;
    
    Fragment1 fragment1 = new Fragment1();
    Fragment2 fragment2 = new Fragment2();
    Fragment3 fragment3 = new Fragment3();
    Fragment4 fragment4 = new Fragment4();
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        viewPager = findViewById(R.id.view_pager);
        dotsIndicator = findViewById(R.id.dot1);
        springDotsIndicator = findViewById(R.id.dot2);
        wormDotsIndicator = findViewById(R.id.dot3);

        ArrayList<Fragment> fragments = new ArrayList<>();
        fragments.add(fragment1);
        fragments.add(fragment2);
        fragments.add(fragment3);
        fragments.add(fragment4);

        viewAdapter = new ViewAdapter(this, fragments);
        viewPager.setAdapter(viewAdapter);
        dotsIndicator.attachTo(viewPager);
        springDotsIndicator.attachTo(viewPager);
        wormDotsIndicator.attachTo(viewPager);
    }
}

만약 사용자가 임의로 스크롤하지 못하게 막고, 버튼을 통해 스크롤을 하고 싶으면 아래의 코드를 추가한다.

viewPager.setUserInputEnabled(false);
dotsIndicator.dotsClickable = false

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View view) {
        int position = viewPager.getCurrentItem();
        if (position < viewAdapter.getItemCount() - 1) {
            viewPager.setCurrentItem(position + 1);
        } else {
            Toast.makeText(getApplicationContext(), "End of the slides", Toast.LENGTH_SHORT).show();
        }
    }
});

4. fragment의 데이터 통합하여 MainActivity에 저장

각 fragment에 데이터를 한번에 저장하기 위해 우선 아래와 같은 interface를 MainActivity에 구현한다.

  • MainActivity.java
public class MainActivity extends AppCompatActivity {
	...
    
    public interface SaveableFragment {
        void saveData();
        Object getData();
    }
}

다음으로, 각 fragment별 구현 내용을 아래의 형식에 맞추어 추가한다.

  • Fragment1.java
public class Fragment1 extends Fragment implements MainActivity.SaveableFragment {
	...
    
    HashMap<String, String> data = new HashMap<String, String>();

    @Override
    public void saveData() {
        data.put("key", "저장할 데이터(ex. editText의 데이터)");
    }

    @Override
    public HashMap<String, String> getData() {
        return data;
    }
}

마지막으로, 아래와 같은 코드를 통해 모든 fragment별 구현 내용을 하나로 합칠 수 있다. 내용들은 HashMap 형태로 저장되며, 저장된 데이터의 예시는 아래와 같다. (버튼은 서버나 다른 Activity에 전송 등의 작업에 사용)

{fragment1={key=value}, fragment3={key=value}, fragment2={key=value}, fragment4={key=value}}
  • MainActivity.java
public class MainActivity extends AppCompatActivity {
	...
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    	...
        
    	button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                int position = viewPager.getCurrentItem();
                Fragment currentFragment = fragmentManager.findFragmentByTag("f" + position);

                if (currentFragment instanceof SaveableFragment) {
                    SaveableFragment saveableFragment = (SaveableFragment) currentFragment;
                    saveableFragment.saveData();
                    data.put("fragment" + (position + 1), saveableFragment.getData());
                }

                if (position < viewAdapter.getItemCount() - 1) {
                    viewPager.setCurrentItem(position + 1);
                } else {
                    System.out.println(data);
                }
            }
        });
    }
}

5. 구현 결과

0개의 댓글