A list,

Name: ViewPager2

Birthday: November 20, 2019

Address: androidx. Viewpager2. Widget

Background:

On November 20, 2019, Google made a big announcement! The long-awaited release of ViewPager2 has excited many Android developers. ViewPager2 is an updated version of ViewPager, so many scenarios can be used in the same way as ViewPager. ViewPager2 advanced processing is the internal use of RecyclerView implementation, to solve a lot of problems in the process of using ViewPager but also increase some of its own characteristics ~ next let’s come to know him ~

Second, the basic

1. Difference comparison

Google describes ViewPager2 in the source code

ViewPager2 replaces ViewPager, addressing most of its predecessor’s pain-points, including right-to-left layout support, vertical orientation, modifiable Fragment collections, etc.

Copy the code

Use the following table to show the comparison clearly:

ViewPager ViewPager2
PagerAdapter RecyclerView.Adapter
FragmentStatePagerAdapter FragmentStateAdapter
addPageChangeListener registerOnPageChangeCallback
Does not support Support RTL
Does not support Support for vertical sliding
Does not support You can stop user operations

My favorite change is the internal RecyclerView implementation and support for vertical scrolling

2. Basic usage

ViewPager2 to implement the most basic function of ViewPager [image banner] as an example:

The premise is to ensure that the project code support androidx yo ~ refer to the official website

2.1 Adding a Dependency (Dependency ~ must be added separately)

dependencies {
    implementation "androidx.viewpager2:viewpager2:version"
}
Copy the code

2.3 Layout File Usage

When used in XML, there are no special points, just like common controls. Set the width, height, ID, position, and so on

    <androidx.viewpager2.widget.ViewPager2
            android:id="@+id/view_pager2"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            app:layout_constraintDimensionRatio="2:3"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
Copy the code

2.4 set up the Adapter

Because of internal RecycleRiew implementation, so the Adapter of ViewPager2 needs to be set as the Adapter of RecyclerView

Class VpAdapter: recyclerview.adapter < vpAdapter.vpViewholder >() {// Adapter private var data: MutableList<HouseItem> = mutableListOf() fun setData(list: MutableList<HouseItem>) { data.clear() data.addAll(list) notifyDataSetChanged() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VpViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_rv2_basic_view, parent, false) return VpViewHolder(view) } override fun onBindViewHolder(holder: VpViewHolder, position: Int) { holder.render(data[position]) } override fun getItemCount() = data.size class VpViewHolder(_itemView: View) : RecyclerView.ViewHolder(_itemView) { private var pvImage: SimpleDraweeView = itemView.findViewById(R.id.pv_image) private var tvTitle: TextView = itemView.findViewById(R.id.tv_title) fun render(bean: HouseItem) { tvTitle.text = bean.houseTypeName pvImage.setImageURI(bean.houseTypePic) } } }Copy the code

The layout file is also very simple, simply placing a SimpleDraweeView+ a TextView to display an image + a title; As you can see, this is no different from the Adapter we wrote when we used RecyclerView. So there will be no obstacles in use

SimpleDraweeView (SimpleDraweeViewSimpleDraweeView)

2.5 Configuring Attributes

Similar to RecyclerView~ initialize ViewPager2, initialize Adapter, set the data source, set Adapter to ViewPager2, you can achieve similar ViewPager Banner effect ~

        adapter = VpAdapter()
        adapter.setData(list)
        viewPager2.adapter = adapter
Copy the code

The effect is shown in the picture: Video

ViewPager2 supports vertical scrolling by default, so I want to implement vertical scrolling Banner, how to deal with?

It’s really easy, just set the scroll direction property of ViewPager2

Orientation = ViewPager2.orientation = viewPager2.orientation_verticalCopy the code

At this point, the basic use of ViewPager2 is covered

Third, the advanced

1. Page switching monitoring

ViewPager2 page switching to monitor, provides registerOnPageChangeCallback method, pass an abstract object OnPageChangeCallback, benefit is listed three methods don’t need a one-off, use which write which.

    public abstract static class OnPageChangeCallback {
       
        public void onPageScrolled(int position, float positionOffset,
                @Px int positionOffsetPixels) {
        }

        public void onPageSelected(int position) {
        }

        public void onPageScrollStateChanged(@ScrollState int state) {
        }
    }
Copy the code

OnPageSelected (int Position) method onPageSelected(int Position) method onPageSelected(int Position)

/ / page to switch to monitor viewPager2. RegisterOnPageChangeCallback (object: ViewPager2. OnPageChangeCallback () {/ / callback, selected core used in dealing with the selected index value of logic to override fun onPageSelected (position: Int) { super.onPageSelected(position) Log.e(TAG, "onPageSelected: position = $position") } })Copy the code

2. Multiple pages per screen

ViewPager2 if you want to achieve one screen with multiple pages, you need to use offscreenPageLimit and setPadding

Viewpager2. apply {offscreenPageLimit = 1 val recyclerView = this.getChildAt(0) as recyclerView RecyclerView recyclerView.apply { val padding = 40.dp setPadding(padding, 0, padding, 0) clipToPadding = false } }Copy the code
val recyclerView = this.getChildAt(0) as RecyclerView
Copy the code

MRecyclerView is added to the ViewGroup by default. The index value is fixed at 0!

attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
Copy the code

3. OffscreenPageLimit preloading

In ViewPager, one page is loaded by default, so lazy loading can be troublesome. In ViewPager2, this restriction is removed by default, so only one page is loaded by default

public void setOffscreenPageLimit(@OffscreenPageLimit int limit) { if (limit < 1 && limit ! = OFFSCREEN_PAGE_LIMIT_DEFAULT) { throw new IllegalArgumentException( "Offscreen page limit must be OFFSCREEN_PAGE_LIMIT_DEFAULT or a number > 0"); } mOffscreenPageLimit = limit; // Trigger layout so prefetch happens through getExtraLayoutSize() mRecyclerView.requestLayout(); }Copy the code

The setOffscreenPageLimit setting in the source code needs to handle one parameter, and you can see that there is a comment OffscreenPageLimit indicating that the default value needs to start at 1.

Next, I load the Fragment with ViewPager2, using the Fragment’s lifecycle log as a reference by default

3.1 offscreenPageLimit Not set

E/debug_VpFragment: onCreate:  mCurrentPosition = 0
E/debug_VpFragment: onPause:  onCreateView = 0
E/debug_VpFragment: onResume:  mCurrentPosition = 0
Copy the code

As you can see, by default, this method does nothing but initialize the first Fragment.

3.2 offscreenPageLimit = 1

E/debug_VpFragment: onCreate:  mCurrentPosition = 0
E/debug_VpFragment: onPause:  onCreateView = 0
E/debug_VpFragment: onResume:  mCurrentPosition = 0
E/debug_VpFragment: onCreate:  mCurrentPosition = 1
E/debug_VpFragment: onPause:  onCreateView = 1
Copy the code

Setting offscreenPageLimit = 1 initializes the first two fragments.

3.3 offscreenPageLimit = 2

E/debug_VpFragment: onCreate:  mCurrentPosition = 0
E/debug_VpFragment: onPause:  onCreateView = 0
E/debug_VpFragment: onResume:  mCurrentPosition = 0
E/debug_VpFragment: onCreate:  mCurrentPosition = 1
E/debug_VpFragment: onPause:  onCreateView = 1
E/debug_VpFragment: onCreate:  mCurrentPosition = 2
E/debug_VpFragment: onPause:  onCreateView = 2
Copy the code

Setting offscreenPageLimit = 2 initializes the first three fragments.

4. Combining TabLayout

TabLayout and ViewPager are also commonly used, but ViewPager2 requires a TabLayoutMediator

TabLayoutMediator takes three arguments:

Parameter 1: TabLayout

Parameter 2: ViewPager2

Parameter 3: TabConfigurationStrategy (Interface)

The third parameter, onConfigureTab(@nonNULL tabLayout. Tab Tab, int Position), needs to be overridden, so if we want to implement some custom Tab style, we can handle it directly in this method, and we can get the index value for special processing.

        new TabLayoutMediator(mTabLayout, mViewPager2, new TabLayoutMediator.TabConfigurationStrategy() {
            @Override
            public void onConfigureTab(@NonNull TabLayout.Tab tab, int position) {
                TextView textView = new TextView(mFragmentActivity);
                textView.setText(mVpBeans.get(position).getTitle());
                tab.setCustomView(textView);
            }
        }).attach();
Copy the code

The project of actual combat

Freely combine, belonging to an important member of the comfortable family, is a kind of more young, social centralized apartments, with a variety of social activities, and other functions, so freely combine the housekeeper will daily events organized by some of the wonderful record video, pictures, and uploaded to the “fun” TAB of the data, and display in the form of cascade flow. Based on user experience and interaction form, we consider making an effect of sliding up and down to play videos and horizontally sliding to show pictures for these videos and pictures. Considering the above ViewPager2 characteristics, we try to implement it

General train of thought

Page container, using ViewPager2 to carry each switch Page; Page is implemented as Fragment; Fragment Specifies the video type and image type, and uses the video player and image banner respectively

Page structure

Given ViewPager2’s vertical scrolling, don’t hesitate to try it all at once; The structure of the Page is simplified. In the corresponding Page, the overall structure is ViewPager2+TopView(some custom styles are displayed, which is ignored here), and then VpFragmeng is used as the host of each Page, including player controls or wheel controls (to display slide-through images), as shown in the following figure:

The content in the Activity is relatively simple, mainly dealing with the ViewPager2 and Fragment bindings.

private fun initView() { mAdapter = PageAdapter(this, mModelList) mAdapter? .setOnAdapterDataChangeListener(object : PageAdapter.OnAdapterDataChangeListener { override fun onPageChangeListener(selectedPosition: Int) { mTvCurrentPage? .text = String.format("%d", selectedPosition + 1) } override fun onSupportClick(bean: PlayEvaluationListModel.RowsBean? , position: Int, status: Boolean) {override fun onImageClick() {finish()}}) mViewPager2? .orientation = ViewPager2.ORIENTATION_VERTICAL mViewPager2? .adapter = mAdapter mViewPager2? .registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) {super.onPageselected (position) // Handle the top indicator data // The current item data val model = mModelList[position] // Ready to load more data if (position == mmodellist.size -2) {// -2; mCanLoadMore) { return } mPresenter? .fillData() } } }) initTitlePosition() }Copy the code

The Adapter code will not be attached, as in the demo, binding Fragment line ~

PageFragment, as the host of each Page, is used in the same way as ordinary fragments.

Override Fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mFragmentActivity = activity if (arguments ! = null) { mRowsBean = arguments? .getSerializable("rowsBean") as PlayEvaluationListModel.RowsBean mCurrentPosition = arguments? .getInt("position") ? : 0}}Copy the code
Fun onCreateView(Inflater: LayoutInflater, container: ViewGroup? , savedInstanceState: Bundle?) : View? { val view = inflater.inflate(R.layout.item_play_evaluation_list, container, false) initView(view) return view }Copy the code

// This is the specific display logic, depending on the type of video or image, display the video player or image banner control respectively;

private fun render() { val model = mRowsBean ? : return if (model.isVideo) { mZvvVideo? .visible = true mCpPicList? .visible = false val pic = if (null ! = model.picList && model.picList.size >= 1) model.picList[0].picUrl else "" mZvvVideo? .setPlaceImg(pic) mZvvVideo? .setVideoPath(model.videoUrl) mZvvVideo? .setOnClickListener { _: View? -> if (mZvvVideo? .videoView ! = null && mZvvVideo? .videoView? .isPlaying == true) { mZvvVideo? .pause() } else { mZvvVideo? .startVideo()}} var imgWidth = 720.0 var imgHeight = 1280.0 = null && model.picList.isNotEmpty()) { val picListBean = model.picList[0] if (picListBean ! ImgWidth = piclistbean.width imgHeight = piclistbean.height}} val lp: ViewGroup.LayoutParams = mZvvVideo? .layoutParams ? : ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup. LayoutParams. MATCH_PARENT) lp. Width = mScreenWidth var newH = imgHeight / / vertical screen video if (imgWidth > 0) {newH = mScreenWidth * imgHeight / imgWidth } lp.height = newH.toInt() mZvvVideo? .videoView? .setOnVideoSizeChangedListener { _: Int, _: Int -> mZvvVideo? .videoView? .displayAspectRatio = PLVideoView.ASPECT_RATIO_FIT_PARENT } } else { mZvvVideo? .visible = false mCpPicList? .visible = true mCpPicList? .isCanLoop = false mCpPicList? .setPages({ val detailPlayPicHolder = DetailPlayPicHolder() detailPlayPicHolder.setOnAdapterDataChangeListener(object : OnAdapterDataChangeListener { override fun onPageChangeListener(selectedPosition: Int) {} override fun onSupportClick(bean: PlayEvaluationListModel.RowsBean? , position: Int, status: Boolean) {} override fun onImageClick() { mOnAdapterDataChangeListener? .onImageClick() } }) detailPlayPicHolder }, model.picList) mCpPicList? .onPageChangeListener = object : ViewPager.OnPageChangeListener { override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {} override fun onPageSelected(page: Int) { mOnAdapterDataChangeListener? .onPageChangeListener(page) } override fun onPageScrollStateChanged(state: Int) {} } } // .... Omit... }Copy the code

Considering the video playback state to combine with user operations Page state, so switch to the Page out, and the corresponding video player to pause or stop the operation (specific what action to take as the case may be), we used here is suspended, when a user manual switch back to a first switching out the Page, You can continue to play the last video that has not been played.

override fun onResume() { super.onResume() mZvvVideo? .start() } override fun onPause() { super.onPause() mZvvVideo? .pause() }Copy the code

So far, it has realized the function of sliding up and down to switch videos/pictures and left and right to switch atlas. Above, only for my actual combat experience, if there is a better posture, welcome to give advice ~

Author: Qiu Banfeng, Research and development Center of Free Big front end