본문 바로가기

Android/Android Issue

ViewPager2에 관한 고찰

이번 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();
        }
    }

이러한 형식으로 연동해줘야한다.

 

 

googlesamples/android-viewpager2

Sample for ViewPager2. Contribute to googlesamples/android-viewpager2 development by creating an account on GitHub.

github.com

(구글샘플깃 ViewPager2에서 TablayoutMediator파일에서 참조함)

 

 

 

의외로 소스가 간단하기는 하지만, 역시 기존 ViewPager에 비해 많이 복잡하다.

곧 지원할 예정인것 같다.

 

현재는 역시나 처음 올라온 릴리즈이다. 그렇기에 부족한점도 많고 KnownIssue부터

여러 보완할점 (TabLayout연동 등)이 많다.

 

하지만 역시 편하고 사용자를 위한다는 느낌이 난다.

예를들면 우리 문화권에선 오른쪽에서 왼쪽으로 슬라이딩하는게 당연하다.

하지만 다른 문화권에선 좌에서 우로 슬라이딩 하는게 당연할 수도 있다.

이러한점을 이번 ViewPager2에서는

 view_pager.orientation = ViewPager2.ORIENTATION_VERTICAL

또는

 view_pager.orientation = ViewPager2.ORIENTATION_HORIZONTAL

같은 기능을 사용하면 슬라이딩 방향을 쉽게 바꿀 수 있다. 

과거 기존 ViewPager은 방향 한번 바꾸려면 커스텀을 해줘야 했지만 이제는 그럴 필요가 없을것 같다.

아직 회사라던지 실서비스중인 대형 프로젝트에 적용하기에는 무리겠지만 자신이 지금 진행하고 있는 개인프로젝트에 적용해보는것도 나쁘지 않을것같다. 

그럼 필자는 이만 가보도록 하겠다.

 

 

 

 

 

HyoGeunGit/ViewPager2_Test

ViewPager2 Can Use Orientation?!?!?!? Contribute to HyoGeunGit/ViewPager2_Test development by creating an account on GitHub.

github.com

이건 필자의 깃이다. ViewPager2를 대충 짜보았다. 어제 새벽에 비몽사몽한 정신으로 짠 소스라 더러울 가능성이 다분하다. 하지만 참고할점 참고해주면 좋겠다!