注:該文章也同步發布到稀土掘金:鏈接
前言
ViewPager2是ViewPager的改進版本,提供了一些增強功能:
- 垂直方向的支持
- 可動態修改Fragment集合
- 從右到左支持
當然,這是官方新推的庫,后續會獲得更好的支持~這篇文章主要從基本使用、增強功能使用、viewpager到viewpager2的升級、通過ViewPager2.PageTransformer實作多種切換影片等幾個方面進行介紹,
使用
基本使用
- 引入viewpager2庫
// module的build.gradle檔案
implementation("androidx.viewpager2:viewpager2:1.0.0")
- 在布局中添加viewpager2
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewPager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
- 繼承FragmentStateAdapter創建adapter
class DemoAapdater(activity: FragmentActivity) : FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return 6
}
override fun createFragment(position: Int): Fragment {
val demoFragment = DemoFragment()
demoFragment.arguments = Bundle().apply {
putInt("TEXT", position)
}
return demoFragment
}
}
- 為viewpager2設定adapter
demoAapdater = DemoAapdater(this)
viewPager2.adapter = demoAapdater
通過簡單四步,已經可以正常使用viewpager2啦~
配合TabLayout使用
很多業務場景,需要搭配TabLayout使用的,viewpager2對于tablayout的配合也進行了調整:
tabLayout = findViewById(R.id.tabLayout)
TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
tab.text = "PAGE $position"
}.attach()
增強功能使用
- 切換橫豎屏滾動
cbDirection.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
viewPager2.orientation = ViewPager2.ORIENTATION_HORIZONTAL
} else {
viewPager2.orientation = ViewPager2.ORIENTATION_VERTICAL
}
}
- 是否允許用戶滑動切換
cbScroll.setOnCheckedChangeListener { buttonView, isChecked ->
viewPager2.isUserInputEnabled = isChecked
}
- 通過其他view控制viewpager2的滑動
public boolean fakeDragBy(@SuppressLint("SupportAnnotationUsage") @Px float offsetPxFloat) {
return mFakeDragger.fakeDragBy(offsetPxFloat);
}
直接通過TabLayoutMediator的TabConfigurationStrategy引數即可為tabitem設定title,跟viewpager的用法完全不同,
原始碼分析
viewpager2
public final class ViewPager2 extends ViewGroup
private void initialize(Context context, AttributeSet attrs) {
mAccessibilityProvider = sFeatureEnhancedA11yEnabled
? new PageAwareAccessibilityProvider()
: new BasicAccessibilityProvider();
mRecyclerView = new RecyclerViewImpl(context);
mRecyclerView.setId(ViewCompat.generateViewId());
mRecyclerView.setDescendantFocusability(FOCUS_BEFORE_DESCENDANTS);
mLayoutManager = new LinearLayoutManagerImpl(context);
mRecyclerView.setLayoutManager(mLayoutManager);
mRecyclerView.setScrollingTouchSlop(RecyclerView.TOUCH_SLOP_PAGING);
setOrientation(context, attrs);
mRecyclerView.setLayoutParams(
new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
mRecyclerView.addOnChildAttachStateChangeListener(enforceChildFillListener());
// Create ScrollEventAdapter before attaching PagerSnapHelper to RecyclerView, because the
// attach process calls PagerSnapHelperImpl.findSnapView, which uses the mScrollEventAdapter
mScrollEventAdapter = new ScrollEventAdapter(this);
// Create FakeDrag before attaching PagerSnapHelper, same reason as above
mFakeDragger = new FakeDrag(this, mScrollEventAdapter, mRecyclerView);
mPagerSnapHelper = new PagerSnapHelperImpl();
mPagerSnapHelper.attachToRecyclerView(mRecyclerView);
// Add mScrollEventAdapter after attaching mPagerSnapHelper to mRecyclerView, because we
// don't want to respond on the events sent out during the attach process
mRecyclerView.addOnScrollListener(mScrollEventAdapter);
....
....
attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());
}
首先ViewPager2繼承于Viewgroup,看到最后一句:attachViewToParent(mRecyclerView, 0, mRecyclerView.getLayoutParams());可以發現將RecyclerView添加到ViewPager2的第一個childView,那是不是viewpager2的功能其實就是通過RecyclerView實作的呢?
public void setAdapter(@Nullable @SuppressWarnings("rawtypes") Adapter adapter) {
final Adapter<?> currentAdapter = mRecyclerView.getAdapter();
mAccessibilityProvider.onDetachAdapter(currentAdapter);
unregisterCurrentItemDataSetTracker(currentAdapter);
mRecyclerView.setAdapter(adapter);
mCurrentItem = 0;
restorePendingState();
mAccessibilityProvider.onAttachAdapter(adapter);
registerCurrentItemDataSetTracker(adapter);
}
public void setOrientation(@Orientation int orientation) {
mLayoutManager.setOrientation(orientation);
mAccessibilityProvider.onSetOrientation();
}
上邊抽取了兩個函式,setAdapter最終是設定給了mRecyclerView,setOrientation最終也是給了mLayoutManager,這里再次印證了開始的想法~那通過上邊的分析,FragmentStateAdapter應該也是繼承于RecyclerView.Adapter?
FragmentStateAdapter
public abstract class FragmentStateAdapter extends
RecyclerView.Adapter<FragmentViewHolder> implements StatefulAdapter
查看原始碼可以發現,FragmentStateAdapter確實是繼承于RecyclerView.Adapter,重新實作了RecyclerView.ViewHolder
public final class FragmentViewHolder extends ViewHolder {
private FragmentViewHolder(@NonNull FrameLayout container) {
super(container);
}
@NonNull static FragmentViewHolder create(@NonNull ViewGroup parent) {
FrameLayout container = new FrameLayout(parent.getContext());
container.setLayoutParams(
new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
container.setId(ViewCompat.generateViewId());
container.setSaveEnabled(false);
return new FragmentViewHolder(container);
}
@NonNull FrameLayout getContainer() {
return (FrameLayout) itemView;
}
}
另外,FragmentStateAdapter實作了StatefulAdapter介面:
public interface StatefulAdapter {
/** Saves adapter state */
@NonNull Parcelable saveState();
/** Restores adapter state */
void restoreState(@NonNull Parcelable savedState);
}
該介面主要定義了兩個函式,一個用于保存狀態,一個用于恢復狀態,主要在ViewPager2中被呼叫:
@SuppressWarnings("ConstantConditions")
@Nullable
@Override
protected Parcelable onSaveInstanceState() {
...
if (mPendingAdapterState != null) {
ss.mAdapterState = mPendingAdapterState;
} else {
Adapter<?> adapter = mRecyclerView.getAdapter();
if (adapter instanceof StatefulAdapter) {
ss.mAdapterState = ((StatefulAdapter) adapter).saveState();
}
}
return ss;
}
private void restorePendingState() {
...
if (mPendingAdapterState != null) {
if (adapter instanceof StatefulAdapter) {
((StatefulAdapter) adapter).restoreState(mPendingAdapterState);
}
mPendingAdapterState = null;
}
...
}
ViewPager2會在資源不足回收前保存fragment的狀態,以便后續的狀態恢復
ViewPager到ViewPager2的遷移
關于這塊,官方有詳細的說明:官方說明
- Adapter的變更
- TabLayout關聯的調整:不再通過setupWithViewPager(), 而是使用TabLayoutMediator實體
多種切換影片
Sets a ViewPager2.PageTransformer that will be called for each attached page whenever the scroll position is changed. This allows the application to apply custom property transformations to each page, overriding the default sliding behavior.
Note: setting a ViewPager2.PageTransformer disables data-set change animations to prevent conflicts between the two animation systems. Setting a null transformer will restore data-set change animations.
Params:
transformer – PageTransformer that will modify each page's animation properties
See Also:
MarginPageTransformer, CompositePageTransformer
public void setPageTransformer(@Nullable PageTransformer transformer)
我們可以通過自定義PageTransformer來實作不同的頁面切換效果,官方也給出了兩個具體的例子:官方例子
下邊給出已實作的一些效果圖(參考華為桌面的切換效果),具體實作請看demo






總結
Viewpager2的使用基本介紹到這里,其實官方也提供了一個完整的demo:官方demo,里邊還介紹了viewpager2嵌套滾動視圖的滑動沖突解決方案,有這方面需求的可以看下具體解決思路,
最后,按照慣例,附上該文章對應的demo:gitee-demo
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/377117.html
標籤:其他
