이번 2/7 에 ViewPager2가 신규 릴리즈 되었다.
기존 ViewPager와는 너무나도 다른 사용법에 신선한 충격? 을 받았다.
아무튼 각설하고 내용을 진행하도록 하겠다.
일단 ViewPager2는 AndroidX용으로 릴리즈되었기에 사용하기 위해서는 AndroidX로 버전을 바꿔야한다.
implementation 'androidx.viewpager2:viewpager2:1.0.0-alpha01'
그리고 implementation을 해준다
살짝 귀찮긴 하지만 ㅎㅎ...
이번 ViewPager2의 가장 크게 바뀐점이라고 하면
역시 첫번째로는
ViewPager2에서는 리사이클러뷰를 사용한다.
인것같다.
원래 ViewPager은 PagerAdapter기반이다.
class CustomPagerAdapter extends PagerAdapter {
Context mContext;
LayoutInflater mLayoutInflater;
public CustomPagerAdapter(Context context) {
mContext = context;
mLayoutInflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public int getCount() {
return mResources.length;
}
@Override
public boolean isViewFromObject(View view, Object object) {
return view == ((LinearLayout) object);
}
@Override
public Object instantiateItem(ViewGroup container, int position) {
View itemView = mLayoutInflater.inflate(R.layout.pager_item, container, false);
ImageView imageView = (ImageView) itemView.findViewById(R.id.imageView);
imageView.setImageResource(mResources[position]);
container.addView(itemView);
return itemView;
}
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
container.removeView((LinearLayout) object);
}
}
이렇게 말이다.
하지만 ViewPager2에서는 리사이클러뷰 기반이기에 굳이 이렇게 PagerAdapter를 커스텀할 필요가 없다.
매우 쉽고 당신이 알고있는 그 방법 그대로
class ViewPager2Adapter (private val views: Array<String>): RecyclerView.Adapter<ViewPager2Adapter.viewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): viewHolder =
viewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_2test, parent, false))
override fun getItemCount(): Int = views.size
override fun onBindViewHolder(holder: viewHolder, position: Int) {
holder.viewName.text = views[position]
}
class viewHolder(view: View): RecyclerView.ViewHolder(view) {
val viewName: TextView = view.findViewById(R.id.view_name)
}
}
이렇게만 해주면 된다.
두번째로는
FragmentStateAdapter를 이용하여 프레그먼트를 짠다.
이다.
기존 ViewPager의 FragmentStatePagerAdapter는
public class FragmentStatePagerSupport extends Activity {
static final int NUM_ITEMS = 10;
MyAdapter mAdapter;
ViewPager mPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_pager);
mAdapter = new MyAdapter(getFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
Button button = (Button)findViewById(R.id.goto_first);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPager.setCurrentItem(0);
}
});
button = (Button)findViewById(R.id.goto_last);
button.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
mPager.setCurrentItem(NUM_ITEMS-1);
}
});
}
public static class MyAdapter extends FragmentStatePagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
return ArrayListFragment.newInstance(position);
}
}
public static class ArrayListFragment extends ListFragment {
int mNum;
static ArrayListFragment newInstance(int num) {
ArrayListFragment f = new ArrayListFragment();
Bundle args = new Bundle();
args.putInt("num", num);
f.setArguments(args);
return f;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mNum = getArguments() != null ? getArguments().getInt("num") : 1;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_pager_list, container, false);
View tv = v.findViewById(R.id.text);
((TextView)tv).setText("Fragment #" + mNum);
return v;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, Cheeses.sCheeseStrings));
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
Log.i("FragmentList", "Item clicked: " + id);
}
}
}
이러한 형식으로 FragmentStatePagerAdapter 를 사용한다.
하지만 ViewPager2는FragmentStateAdapter를 사용해 대체한다.
pager.adapter = PagerFragmentStateAdapter(bgColors, supportFragmentManager)
pager.orientation = ViewPager2.ORIENTATION_VERTICAL
이렇게 할수도 있다는 말이다.
OnPageChangeCallback
이 점도 크게 바뀌었다.
기존 ViewPager의 addPageChangeListener는 인터페이스여서 메서드를 모두 재정의해야 했다.
이런식으로 말이다.
vp = (ViewPager)findViewById(R.id.vp);
vp.addOnPageChangeListener(new ViewPager.OnPageChangeListener()
{
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
}
@Override
public void onPageSelected(int position)
{
int i = 0;
while(i<3)
{
if(position==i)
{
ll.findViewWithTag(i).setSelected(true);
}
else
{
ll.findViewWithTag(i).setSelected(false);
}
i++;
}
}
@Override
public void onPageScrollStateChanged(int state)
{
}
});
하지만 ViewPager2의 OnPageChangeCallback은 추상 클래스이기 때문에 필요한 메서드만 재정의하면 된다.
이렇게 말이다.
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
}
})
ViewPager2에 TabLayout연동하기
ViewPager2는 아직까진 TabLayout과의 연동을 공식적으로 지원하지 않는다.
기존 ViewPager에서는
public class MainActivity extends AppCompatActivity {
private TabLayout tabLayout;
private ViewPager viewPager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
tabLayout = (TabLayout) findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setText("1째 탭"));
tabLayout.addTab(tabLayout.newTab().setText("2째 탭"));
tabLayout.addTab(tabLayout.newTab().setText("3째 탭"));
tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
viewPager = (ViewPager) findViewById(R.id.pager);
TabPagerAdapter pagerAdapter = new TabPagerAdapter(getSupportFragmentManager(), tabLayout.getTabCount());
viewPager.setAdapter(pagerAdapter);
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
}
}
이러한 형식으로 연동을 해준다.
하지만 ViewPager2에선
public TabLayoutMediator(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager,
@NonNull OnConfigureTabCallback onConfigureTabCallback) {
this(tabLayout, viewPager, true, onConfigureTabCallback);
}
public TabLayoutMediator(@NonNull TabLayout tabLayout, @NonNull ViewPager2 viewPager,
boolean autoRefresh, @NonNull OnConfigureTabCallback onConfigureTabCallback) {
mTabLayout = tabLayout;
mViewPager = viewPager;
mAutoRefresh = autoRefresh;
mOnConfigureTabCallback = onConfigureTabCallback;
}
public void attach() {
if (mAttached) {
throw new IllegalStateException("TabLayoutMediator is already attached");
}
mAdapter = mViewPager.getAdapter();
if (mAdapter == null) {
throw new IllegalStateException("TabLayoutMediator attached before ViewPager2 has an "
+ "adapter");
}
mAttached = true;
mOnPageChangeCallback = new TabLayoutOnPageChangeCallback(mTabLayout);
mViewPager.registerOnPageChangeCallback(mOnPageChangeCallback);
mOnTabSelectedListener = new ViewPagerOnTabSelectedListener(mViewPager);
mTabLayout.addOnTabSelectedListener(mOnTabSelectedListener);
if (mAutoRefresh) {
// Register our observer on the new adapter
mPagerAdapterObserver = new PagerAdapterObserver();
mAdapter.registerAdapterDataObserver(mPagerAdapterObserver);
}
populateTabsFromPagerAdapter();
mTabLayout.setScrollPosition(mViewPager.getCurrentItem(), 0f, true);
}
private static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
private final ViewPager2 mViewPager;
ViewPagerOnTabSelectedListener(ViewPager2 viewPager) {
this.mViewPager = viewPager;
}
@Override
public void onTabSelected(TabLayout.Tab tab) {
mViewPager.setCurrentItem(tab.getPosition(), true);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
// No-op
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
// No-op
}
}
private class PagerAdapterObserver extends RecyclerView.AdapterDataObserver {
PagerAdapterObserver() {}
@Override
public void onChanged() {
populateTabsFromPagerAdapter();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
populateTabsFromPagerAdapter();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) {
populateTabsFromPagerAdapter();
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
populateTabsFromPagerAdapter();
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
populateTabsFromPagerAdapter();
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
populateTabsFromPagerAdapter();
}
}
이러한 형식으로 연동해줘야한다.
(구글샘플깃 ViewPager2에서 TablayoutMediator파일에서 참조함)
의외로 소스가 간단하기는 하지만, 역시 기존 ViewPager에 비해 많이 복잡하다.
곧 지원할 예정인것 같다.
현재는 역시나 처음 올라온 릴리즈이다. 그렇기에 부족한점도 많고 KnownIssue부터
여러 보완할점 (TabLayout연동 등)이 많다.
하지만 역시 편하고 사용자를 위한다는 느낌이 난다.
예를들면 우리 문화권에선 오른쪽에서 왼쪽으로 슬라이딩하는게 당연하다.
하지만 다른 문화권에선 좌에서 우로 슬라이딩 하는게 당연할 수도 있다.
이러한점을 이번 ViewPager2에서는
view_pager.orientation = ViewPager2.ORIENTATION_VERTICAL
또는
view_pager.orientation = ViewPager2.ORIENTATION_HORIZONTAL
같은 기능을 사용하면 슬라이딩 방향을 쉽게 바꿀 수 있다.
과거 기존 ViewPager은 방향 한번 바꾸려면 커스텀을 해줘야 했지만 이제는 그럴 필요가 없을것 같다.
아직 회사라던지 실서비스중인 대형 프로젝트에 적용하기에는 무리겠지만 자신이 지금 진행하고 있는 개인프로젝트에 적용해보는것도 나쁘지 않을것같다.
그럼 필자는 이만 가보도록 하겠다.
이건 필자의 깃이다. ViewPager2를 대충 짜보았다. 어제 새벽에 비몽사몽한 정신으로 짠 소스라 더러울 가능성이 다분하다. 하지만 참고할점 참고해주면 좋겠다!
'Android > Android Issue' 카테고리의 다른 글
Shak IT! - 쉨잇 애플리케이션 개발기 (0) | 2019.09.12 |
---|---|
객체지향 5원칙 SOLID 1. 단일 책임 원칙 (0) | 2019.07.05 |