필자는 viewpager2와 dotsindicator를 사용하여 좌우로, 한페이지 단위로 스와이프 됨과 동시에 진행도를 확인할 수 있는 fragment 형식을 구현하고자 한다.
보다 쉬운 개발을 위해, viewpager2에 attach할 수 있는 dotsindicator모듈을 사용한다.
dependencies {
implementation 'androidx.viewpager2:viewpager2:1.1.0'
implementation 'com.tbuonomo:dotsindicator:5.1.0'
}
아래의 두 가지 요소를 xml 파일에 추가한다.
추가로, dotsindicator 색상 및 모양을 바꾸려면 각 dotsindicator의 속성 dotsColor, selectedDotColor, dotsSize, dotsSpacing를 수정한다. 이외의 속성을 바꾸고 싶으면 dotsindicator 사이트를 방문하여 attribute의 설명을 읽고 수정한다.
<?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>
우선 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에서는 아래의 작업을 수행한다.
아래는 MainActivity 코드이다.
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();
}
}
});
각 fragment에 데이터를 한번에 저장하기 위해 우선 아래와 같은 interface를 MainActivity에 구현한다.
public class MainActivity extends AppCompatActivity {
...
public interface SaveableFragment {
void saveData();
Object getData();
}
}
다음으로, 각 fragment별 구현 내용을 아래의 형식에 맞추어 추가한다.
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}}
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);
}
}
});
}
}
