记录一下,ViewPager2使用过程中碰到的几个小问题:
1. ViewPager2和RecyclerView滑动冲突。
背景
ViewPager2中使用了水平的RecyclerView。
现象
在网上看,大部分人碰见的都是RecyclerView无法滑动或者是ViewPager2无法滑动。但我碰到的现象是:如果点击RecyclerView后立刻就进行左右滑动,则是ViewPager2被滑动,而非RecyclerView被滑动;如果点击RecyclerView后停顿一下,再滑动,则可以正常滑动RecyclerView。
解决办法
使用Google提供的NestedScrollableHost.kt,包装一下RecyclerView即可。
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.View
import android.view.ViewConfiguration
import android.widget.FrameLayout
import androidx.viewpager2.widget.ViewPager2
import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
import kotlin.math.absoluteValue
import kotlin.math.sign
class NestedScrollableHost : FrameLayout {
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
private var touchSlop = 0
private var initialX = 0f
private var initialY = 0f
private val parentViewPager: ViewPager2?
get() {
var v: View? = parent as? View
while (v != null && v !is ViewPager2) {
v = v.parent as? View
}
return v as? ViewPager2
}
private val child: View? get() = if (childCount > 0) getChildAt(0) else null
init {
touchSlop = ViewConfiguration.get(context).scaledTouchSlop
}
private fun canChildScroll(orientation: Int, delta: Float): Boolean {
val direction = -delta.sign.toInt()
return when (orientation) {
0 -> child?.canScrollHorizontally(direction) ?: false
1 -> child?.canScrollVertically(direction) ?: false
else -> throw IllegalArgumentException()
}
}
override fun onInterceptTouchEvent(e: MotionEvent): Boolean {
handleInterceptTouchEvent(e)
return super.onInterceptTouchEvent(e)
}
private fun handleInterceptTouchEvent(e: MotionEvent) {
val orientation = parentViewPager?.orientation ?: return
if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f)) {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y
parent.requestDisallowInterceptTouchEvent(true)
} else if (e.action == MotionEvent.ACTION_MOVE) {
val dx = e.x - initialX
val dy = e.y - initialY
val isVpHorizontal = orientation == ORIENTATION_HORIZONTAL
val scaledDx = dx.absoluteValue * if (isVpHorizontal) .5f else 1f
val scaledDy = dy.absoluteValue * if (isVpHorizontal) 1f else .5f
if (scaledDx > touchSlop || scaledDy > touchSlop) {
if (isVpHorizontal == (scaledDy > scaledDx)) {
parent.requestDisallowInterceptTouchEvent(false)
} else {
if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
parent.requestDisallowInterceptTouchEvent(true)
} else {
parent.requestDisallowInterceptTouchEvent(false)
}
}
}
}
}
}
使用方法:
<com.example.NestedScrollableHost
android:id="@+id/nsh"
android:layout_width="match_parent"
android:layout_height="72dp"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv"
android:layout_width="match_parent"
android:layout_height="72dp"/>
</com.example.NestedScrollableHost>
2.ViewPager2滑动至边缘阴影取消。
现象
不仅是ViewPager2,类似ScrollView、RecyclerView类的控件,都会存在滑动至第一项或最后一项时,再继续滑动,会出现淡紫色波纹阴影。
解决办法
如果是RecyclerView,则直接设置:
binding.rvRoom.overScrollMode = View.OVER_SCROLL_NEVER
或在xml中设置:
android:overScrollMode="never"
即可。 但ViewPager2这么设置不可以,需用如下办法:
val child = binding.viewPager.getChildAt(0)
if (child is RecyclerView) child.overScrollMode = View.OVER_SCROLL_NEVER
3. TabLayout和ViewPager2滑动效果
背景
当在TabLayout中间隔多个Tab点击时,ViewPager2会连续滑动过多个页面。 需要点击Tab时无滑动动画效果,滑动ViewPager2时依旧有动画效果。(类似Android端微信效果)
解决办法
重写TabLayoutMediator
public class TabLayoutMediators {
private TabLayout tabLayout;
private ViewPager2 viewPager;
private boolean autoRefresh;
private static boolean smoothScroll;
private TabConfigurationStrategy tabConfigurationStrategy;
@Nullable
private RecyclerView.Adapter<?> adapter;
private boolean attached;
@Nullable
private TabLayoutOnPageChangeCallback onPageChangeCallback;
@Nullable
private TabLayout.OnTabSelectedListener onTabSelectedListener;
@Nullable
private RecyclerView.AdapterDataObserver pagerAdapterObserver;
public interface TabConfigurationStrategy {
void onConfigureTab(@NonNull TabLayout.Tab tab, int position);
}
public TabLayoutMediators(
@NonNull TabLayout tabLayout,
@NonNull ViewPager2 viewPager,
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this(tabLayout, viewPager, true, tabConfigurationStrategy);
}
public TabLayoutMediators(
@NonNull TabLayout tabLayout,
@NonNull ViewPager2 viewPager,
boolean autoRefresh,
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this(tabLayout, viewPager, autoRefresh, true, tabConfigurationStrategy);
}
public TabLayoutMediators(
@NonNull TabLayout tabLayout,
@NonNull ViewPager2 viewPager,
boolean autoRefresh,
boolean smoothScroll,
@NonNull TabConfigurationStrategy tabConfigurationStrategy) {
this.tabLayout = tabLayout;
this.viewPager = viewPager;
this.autoRefresh = autoRefresh;
this.smoothScroll = smoothScroll;
this.tabConfigurationStrategy = tabConfigurationStrategy;
}
public void attach() {
if (attached) {
throw new IllegalStateException("TabLayoutMediator is already attached");
}
adapter = viewPager.getAdapter();
if (adapter == null) {
throw new IllegalStateException(
"TabLayoutMediator attached before ViewPager2 has an " + "adapter");
}
attached = true;
onPageChangeCallback = new TabLayoutOnPageChangeCallback(tabLayout);
viewPager.registerOnPageChangeCallback(onPageChangeCallback);
onTabSelectedListener = new ViewPagerOnTabSelectedListener(viewPager, smoothScroll);
tabLayout.addOnTabSelectedListener(onTabSelectedListener);
if (autoRefresh) {
pagerAdapterObserver = new PagerAdapterObserver();
adapter.registerAdapterDataObserver(pagerAdapterObserver);
}
populateTabsFromPagerAdapter();
tabLayout.setScrollPosition(viewPager.getCurrentItem(), 0f, true);
}
public void detach() {
if (autoRefresh && adapter != null) {
adapter.unregisterAdapterDataObserver(pagerAdapterObserver);
pagerAdapterObserver = null;
}
tabLayout.removeOnTabSelectedListener(onTabSelectedListener);
viewPager.unregisterOnPageChangeCallback(onPageChangeCallback);
onTabSelectedListener = null;
onPageChangeCallback = null;
adapter = null;
attached = false;
}
public boolean isAttached() {
return attached;
}
@SuppressWarnings("WeakerAccess")
void populateTabsFromPagerAdapter() {
tabLayout.removeAllTabs();
if (adapter != null) {
int adapterCount = adapter.getItemCount();
for (int i = 0; i < adapterCount; i++) {
TabLayout.Tab tab = tabLayout.newTab();
tabConfigurationStrategy.onConfigureTab(tab, i);
tabLayout.addTab(tab, false);
}
if (adapterCount > 0) {
int lastItem = tabLayout.getTabCount() - 1;
int currItem = Math.min(viewPager.getCurrentItem(), lastItem);
if (currItem != tabLayout.getSelectedTabPosition()) {
tabLayout.selectTab(tabLayout.getTabAt(currItem));
}
}
}
}
private static class TabLayoutOnPageChangeCallback extends ViewPager2.OnPageChangeCallback {
@NonNull
private final WeakReference<TabLayout> tabLayoutRef;
private int previousScrollState;
private int scrollState;
TabLayoutOnPageChangeCallback(TabLayout tabLayout) {
tabLayoutRef = new WeakReference<>(tabLayout);
reset();
}
@Override
public void onPageScrollStateChanged(final int state) {
if (state == SCROLL_STATE_DRAGGING) {
smoothScroll = true;
} else if (state == SCROLL_STATE_IDLE) {
smoothScroll = false;
}
previousScrollState = scrollState;
scrollState = state;
}
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
TabLayout tabLayout = tabLayoutRef.get();
if (tabLayout != null) {
boolean updateText =
scrollState != SCROLL_STATE_SETTLING || previousScrollState == SCROLL_STATE_DRAGGING;
boolean updateIndicator =
!(scrollState == SCROLL_STATE_SETTLING && previousScrollState == SCROLL_STATE_IDLE);
tabLayout.setScrollPosition(position, positionOffset, updateText, updateIndicator);
}
}
@Override
public void onPageSelected(final int position) {
TabLayout tabLayout = tabLayoutRef.get();
if (tabLayout != null
&& tabLayout.getSelectedTabPosition() != position
&& position < tabLayout.getTabCount()) {
boolean updateIndicator =
scrollState == SCROLL_STATE_IDLE
|| (scrollState == SCROLL_STATE_SETTLING
&& previousScrollState == SCROLL_STATE_IDLE);
tabLayout.selectTab(tabLayout.getTabAt(position), updateIndicator);
}
}
void reset() {
previousScrollState = scrollState = SCROLL_STATE_IDLE;
}
}
private static class ViewPagerOnTabSelectedListener implements TabLayout.OnTabSelectedListener {
private final ViewPager2 viewPager;
ViewPagerOnTabSelectedListener(ViewPager2 viewPager, boolean smoothScroll) {
this.viewPager = viewPager;
}
@Override
public void onTabSelected(@NonNull TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition(), smoothScroll);
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
}
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();
}
}
}
使用
注意:使用的是自定义的TabLayoutMediators ,而非TabLayoutMediator
val tabLayoutMediator = TabLayoutMediators(
binding.tabLayout,
binding.viewPager
) { tab, position ->
when (position) {
0 -> tab.text = "智能"
1 -> tab.text = "情景"
2 -> tab.text = " 我 "
}
}
|