Android之实现RTL的ViewPager
1 问题
如何实现RTL的ViewPager,就是滑动方向和我们之前滑动的方向相反,比如一般,我们用ViewPager滑动4个图片,依次顺序是
1 2 3 4 ,我们在页面1的时候,我们一般都是习惯向左滑动到2,现在需要实现手指向右滑动到2.
2 解决办法
1)我们可以使用ViewPager2,这个是可以支持的,不熟悉可以去官网看介绍。
https://developer.android.google.cn/jetpack/androidx/releases/viewpager2
2) 如果我们使用普通的viewPager,我们可以把viewPager和page旋转180度。
viewPager.setRotationY(180); viewPager.setPageTransformer(false, new ViewPager.PageTransformer() { @Override public void transformPage(@NonNull View page, float position) { page.setRotationY(180); }
优点:简单适合适配Android9.0以上的手机这个方法非常好
缺点:就是在Android9.0以上的手机上滑动效果不是很敏感,高版本手机适配还是不要采取了。
3)我们使用第三方开源项目(rtl-viewpager)
地址:https://github.com/duolingo/rtl-viewpager
优点:左滑右滑都可以,而且安卓手机版本兼容性也比较好。
缺点:但是就是点击图片预览的放大和缩小不怎么好使,效果不过。
我们可以直接把这2个类拷贝到我们项目也行,可以方便改代码
RtlViewPager.java 文件,onRtlPropertiesChanged函数有判断是否是RTL布局,如果觉得判断不准确,可以用的我的这篇博客
修改来判断Android之如何判断当前是阿拉伯布局的方法
package com.duolingo.open.rtlviewpager;import android.content.Context;import android.os.Parcel;import android.os.Parcelable;import android.util.AttributeSet;import android.view.View;import android.view.ViewGroup;import androidx.annotation.NonNull;import androidx.core.view.ViewCompat;import androidx.viewpager.widget.PagerAdapter;import androidx.viewpager.widget.ViewPager;import java.util.HashMap;public class RtlViewPager extends ViewPager { private final HashMap mPageChangeListeners = new HashMap<>(); private int mLayoutDirection = ViewCompat.LAYOUT_DIRECTION_LTR; public RtlViewPager(Context context) { super(context); } public RtlViewPager(Context context, AttributeSet attrs) { super(context, attrs); } @Override public void onRtlPropertiesChanged(int layoutDirection) { super.onRtlPropertiesChanged(layoutDirection); int viewCompatLayoutDirection = layoutDirection == View.LAYOUT_DIRECTION_RTL ? ViewCompat.LAYOUT_DIRECTION_RTL : ViewCompat.LAYOUT_DIRECTION_LTR; if (viewCompatLayoutDirection != mLayoutDirection) { PagerAdapter adapter = super.getAdapter(); int position = 0; if (adapter != null) { position = getCurrentItem(); } mLayoutDirection = viewCompatLayoutDirection; if (adapter != null) { adapter.notifyDataSetChanged(); setCurrentItem(position); } } } @Override public void setAdapter(PagerAdapter adapter) { if (adapter != null) { adapter = new ReversingAdapter(adapter); } super.setAdapter(adapter); setCurrentItem(0); } @Override public PagerAdapter getAdapter() { ReversingAdapter adapter = (ReversingAdapter) super.getAdapter(); return adapter == null ? null : adapter.getDelegate(); } private boolean isRtl() { return mLayoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL; } @Override public int getCurrentItem() { int item = super.getCurrentItem(); PagerAdapter adapter = super.getAdapter(); if (adapter != null && isRtl()) { item = adapter.getCount() - item - 1; } return item; } @Override public void setCurrentItem(int position, boolean smoothScroll) { PagerAdapter adapter = super.getAdapter(); if (adapter != null && isRtl()) { position = adapter.getCount() - position - 1; } super.setCurrentItem(position, smoothScroll); } @Override public void setCurrentItem(int position) { PagerAdapter adapter = super.getAdapter(); if (adapter != null && isRtl()) { position = adapter.getCount() - position - 1; } super.setCurrentItem(position); } @Deprecated @Override public void setOnPageChangeListener(@NonNull ViewPager.OnPageChangeListener listener) { super.setOnPageChangeListener(new ReversingOnPageChangeListener(listener)); } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); return new SavedState(superState, mLayoutDirection); } @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { super.onRestoreInstanceState(state); return; } SavedState ss = (SavedState) state; mLayoutDirection = ss.mLayoutDirection; super.onRestoreInstanceState(ss.mViewPagerSavedState); } public static class SavedState implements Parcelable { private final Parcelable mViewPagerSavedState; private final int mLayoutDirection; private SavedState(Parcelable viewPagerSavedState, int layoutDirection) { mViewPagerSavedState = viewPagerSavedState; mLayoutDirection = layoutDirection; } private SavedState(Parcel in, ClassLoader loader) { if (loader == null) { loader = getClass().getClassLoader(); } mViewPagerSavedState = in.readParcelable(loader); mLayoutDirection = in.readInt(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeParcelable(mViewPagerSavedState, flags); out.writeInt(mLayoutDirection); } // The `CREATOR` field is used to create the parcelable from a parcel, even though it is never referenced directly. public static final Parcelable.ClassLoaderCreator CREATOR = new Parcelable.ClassLoaderCreator() { @Override public SavedState createFromParcel(Parcel source) { return createFromParcel(source, null); } @Override public SavedState createFromParcel(Parcel source, ClassLoader loader) { return new SavedState(source, loader); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } @Override public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) { ReversingOnPageChangeListener reversingListener = new ReversingOnPageChangeListener(listener); mPageChangeListeners.put(listener, reversingListener); super.addOnPageChangeListener(reversingListener); } @Override public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) { ReversingOnPageChangeListener reverseListener = mPageChangeListeners.remove(listener); if (reverseListener != null) { super.removeOnPageChangeListener(reverseListener); } } @Override public void clearOnPageChangeListeners() { super.clearOnPageChangeListeners(); mPageChangeListeners.clear(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.UNSPECIFIED) { int height = 0; for (int i = 0; i < getChildCount(); i++) { View child = getChildAt(i); child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); int h = child.getMeasuredHeight(); if (h > height) { height = h; } } heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } private class ReversingOnPageChangeListener implements OnPageChangeListener { private final OnPageChangeListener mListener; ReversingOnPageChangeListener(OnPageChangeListener listener) { mListener = listener; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { // The documentation says that `getPageWidth(...)` returns the fraction of the _measured_ width that that page takes up. However, the code seems to // use the width so we will here too. final int width = getWidth(); PagerAdapter adapter = RtlViewPager.super.getAdapter(); if (isRtl() && adapter != null) { int count = adapter.getCount(); int remainingWidth = (int) (width * (1 - adapter.getPageWidth(position))) + positionOffsetPixels; while (position < count && remainingWidth > 0) { position += 1; remainingWidth -= (int) (width * adapter.getPageWidth(position)); } position = count - position - 1; positionOffsetPixels = -remainingWidth; positionOffset = positionOffsetPixels / (width * adapter.getPageWidth(position)); } mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } @Override public void onPageSelected(int position) { PagerAdapter adapter = RtlViewPager.super.getAdapter(); if (isRtl() && adapter != null) { position = adapter.getCount() - position - 1; } mListener.onPageSelected(position); } @Override public void onPageScrollStateChanged(int state) { mListener.onPageScrollStateChanged(state); } } private class ReversingAdapter extends DelegatingPagerAdapter { ReversingAdapter(@NonNull PagerAdapter adapter) { super(adapter); } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { if (isRtl()) { position = getCount() - position - 1; } super.destroyItem(container, position, object); } @Deprecated @Override public void destroyItem(@NonNull View container, int position, @NonNull Object object) { if (isRtl()) { position = getCount() - position - 1; } super.destroyItem(container, position, object); } @Override public int getItemPosition(@NonNull Object object) { int position = super.getItemPosition(object); if (isRtl()) { if (position == POSITION_UNCHANGED || position == POSITION_NONE) { // We can't accept POSITION_UNCHANGED when in RTL mode because adding items to the end of the collection adds them to the beginning of the // ViewPager. Items whose positions do not change from the perspective of the wrapped adapter actually do change from the perspective of // the ViewPager. position = POSITION_NONE; } else { position = getCount() - position - 1; } } return position; } @Override public CharSequence getPageTitle(int position) { if (isRtl()) { position = getCount() - position - 1; } return super.getPageTitle(position); } @Override public float getPageWidth(int position) { if (isRtl()) { position = getCount() - position - 1; } return super.getPageWidth(position); } @Override public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) { if (isRtl()) { position = getCount() - position - 1; } return super.instantiateItem(container, position); } @Deprecated @Override public @NonNull Object instantiateItem(@NonNull View container, int position) { if (isRtl()) { position = getCount() - position - 1; } return super.instantiateItem(container, position); } @Override public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { if (isRtl()) { position = getCount() - position - 1; } super.setPrimaryItem(container, position, object); } @Deprecated @Override public void setPrimaryItem(@NonNull View container, int position, @NonNull Object object) { if (isRtl()) { position = getCount() - position - 1; } super.setPrimaryItem(container, position, object); } }}
DelegatingPagerAdapter.java文件
package com.duolingo.open.rtlviewpager;import android.database.DataSetObserver;import android.os.Parcelable;import android.view.View;import android.view.ViewGroup;import androidx.annotation.NonNull;import androidx.viewpager.widget.PagerAdapter;public class DelegatingPagerAdapter extends PagerAdapter { private final PagerAdapter mDelegate; DelegatingPagerAdapter(@NonNull final PagerAdapter delegate) { this.mDelegate = delegate; delegate.registerDataSetObserver(new MyDataSetObserver(this)); } PagerAdapter getDelegate() { return mDelegate; } @Override public int getCount() { return mDelegate.getCount(); } @Override public void startUpdate(@NonNull ViewGroup container) { mDelegate.startUpdate(container); } @Override public @NonNull Object instantiateItem(@NonNull ViewGroup container, int position) { return mDelegate.instantiateItem(container, position); } @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { mDelegate.destroyItem(container, position, object); } @Override public void setPrimaryItem(@NonNull ViewGroup container, int position, @NonNull Object object) { mDelegate.setPrimaryItem(container, position, object); } @Override public void finishUpdate(@NonNull ViewGroup container) { mDelegate.finishUpdate(container); } @Deprecated @Override public void startUpdate(@NonNull View container) { mDelegate.startUpdate(container); } @Deprecated @Override public @NonNull Object instantiateItem(@NonNull View container, int position) { return mDelegate.instantiateItem(container, position); } @Deprecated @Override public void destroyItem(@NonNull View container, int position, @NonNull Object object) { mDelegate.destroyItem(container, position, object); } @Deprecated @Override public void setPrimaryItem(@NonNull View container, int position, @NonNull Object object) { mDelegate.setPrimaryItem(container, position, object); } @Deprecated @Override public void finishUpdate(@NonNull View container) { mDelegate.finishUpdate(container); } @Override public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { return mDelegate.isViewFromObject(view, object); } @Override public Parcelable saveState() { return mDelegate.saveState(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { mDelegate.restoreState(state, loader); } @Override public int getItemPosition(@NonNull Object object) { return mDelegate.getItemPosition(object); } @Override public void notifyDataSetChanged() { mDelegate.notifyDataSetChanged(); } @Override public void registerDataSetObserver(@NonNull DataSetObserver observer) { mDelegate.registerDataSetObserver(observer); } @Override public void unregisterDataSetObserver(@NonNull DataSetObserver observer) { mDelegate.unregisterDataSetObserver(observer); } @Override public CharSequence getPageTitle(int position) { return mDelegate.getPageTitle(position); } @Override public float getPageWidth(int position) { return mDelegate.getPageWidth(position); } private void superNotifyDataSetChanged() { super.notifyDataSetChanged(); } private static class MyDataSetObserver extends DataSetObserver { final DelegatingPagerAdapter mParent; private MyDataSetObserver(DelegatingPagerAdapter mParent) { this.mParent = mParent; } @Override public void onChanged() { if (mParent != null) { mParent.superNotifyDataSetChanged(); } } @Override public void onInvalidated() { onChanged(); } }}
4)我们使用第三方开源项目(RtlViewPager)
https://github.com/diego-gomez-olvera/RtlViewPager
优点:代码简单,左滑右滑都可以,而且安卓手机版本兼容性也比较好,点击图片放缩效果也非常棒,暂时用这个最优。
我们可以直接把这2个类拷贝到我们项目也行,可以方便改代码
RtlViewPager.java 文件,isRtl()函数判断是否是RTL布局,如果觉得判断不准确,可以用的我的这篇博客
修改来判断Android之如何判断当前是阿拉伯布局的方法
RtlViewPager.java
package com.booking.rtlviewpager;import android.content.Context;import android.database.DataSetObserver;import android.os.Parcel;import android.os.Parcelable;import android.support.annotation.NonNull;import android.support.annotation.Nullable;import android.support.v4.text.TextUtilsCompat;import android.support.v4.util.ArrayMap;import android.support.v4.view.PagerAdapter;import android.support.v4.view.ViewCompat;import android.support.v4.view.ViewPager;import android.util.AttributeSet;import android.view.ViewGroup;import java.util.Map;/** * ViewPager that reverses the items order in RTL locales. */public class RtlViewPager extends ViewPager { @NonNull private final Map reverseOnPageChangeListeners; @Nullable private DataSetObserver dataSetObserver; private boolean suppressOnPageChangeListeners; public RtlViewPager(Context context) { super(context); reverseOnPageChangeListeners = new ArrayMap<>(1); } public RtlViewPager(Context context, AttributeSet attrs) { super(context, attrs); reverseOnPageChangeListeners = new ArrayMap<>(1); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); registerRtlDataSetObserver(super.getAdapter()); } @Override protected void onDetachedFromWindow() { unregisterRtlDataSetObserver(); super.onDetachedFromWindow(); } private void registerRtlDataSetObserver(PagerAdapter adapter) { if (adapter instanceof ReverseAdapter && dataSetObserver == null) { dataSetObserver = new RevalidateIndicesOnContentChange((ReverseAdapter) adapter); adapter.registerDataSetObserver(dataSetObserver); ((ReverseAdapter) adapter).revalidateIndices(); } } private void unregisterRtlDataSetObserver() { final PagerAdapter adapter = super.getAdapter(); if (adapter instanceof ReverseAdapter && dataSetObserver != null) { adapter.unregisterDataSetObserver(dataSetObserver); dataSetObserver = null; } } @Override public void setCurrentItem(int item, boolean smoothScroll) { super.setCurrentItem(convert(item), smoothScroll); } @Override public void setCurrentItem(int item) { super.setCurrentItem(convert(item)); } @Override public int getCurrentItem() { return convert(super.getCurrentItem()); } private int convert(int position) { if (position >= 0 && isRtl()) { return getAdapter() == null ? 0 : getAdapter().getCount() - position - 1; } else { return position; } } @Nullable @Override public PagerAdapter getAdapter() { final PagerAdapter adapter = super.getAdapter(); return adapter instanceof ReverseAdapter ? ((ReverseAdapter) adapter).getInnerAdapter() : adapter; } @Override public void fakeDragBy(float xOffset) { super.fakeDragBy(isRtl() ? xOffset : -xOffset); } @Override public void setAdapter(@Nullable PagerAdapter adapter) { unregisterRtlDataSetObserver(); final boolean rtlReady = adapter != null && isRtl(); if (rtlReady) { adapter = new ReverseAdapter(adapter); registerRtlDataSetObserver(adapter); } super.setAdapter(adapter); if (rtlReady) { setCurrentItemWithoutNotification(0); } } private void setCurrentItemWithoutNotification(int index) { suppressOnPageChangeListeners = true; setCurrentItem(index, false); suppressOnPageChangeListeners = false; } protected boolean isRtl() { return TextUtilsCompat.getLayoutDirectionFromLocale( getContext().getResources().getConfiguration().locale) == ViewCompat.LAYOUT_DIRECTION_RTL; } @Override public void addOnPageChangeListener(@NonNull OnPageChangeListener listener) { if (isRtl()) { final ReverseOnPageChangeListener reverseListener = new ReverseOnPageChangeListener(listener); reverseOnPageChangeListeners.put(listener, reverseListener); listener = reverseListener; } super.addOnPageChangeListener(listener); } @Override public void removeOnPageChangeListener(@NonNull OnPageChangeListener listener) { if (isRtl()) { listener = reverseOnPageChangeListeners.remove(listener); } super.removeOnPageChangeListener(listener); } @Override public Parcelable onSaveInstanceState() { return new SavedState(super.onSaveInstanceState(), getCurrentItem(), isRtl()); } @Override public void onRestoreInstanceState(Parcelable state) { SavedState ss = (SavedState) state; super.onRestoreInstanceState(ss.superState); if (ss.isRTL != isRtl()) setCurrentItem(ss.position, false); } private class ReverseAdapter extends PagerAdapterWrapper { private int lastCount; public ReverseAdapter(@NonNull PagerAdapter adapter) { super(adapter); lastCount = adapter.getCount(); } @Override public CharSequence getPageTitle(int position) { return super.getPageTitle(reverse(position)); } @Override public float getPageWidth(int position) { return super.getPageWidth(reverse(position)); } @Override public int getItemPosition(Object object) { final int itemPosition = super.getItemPosition(object); return itemPosition < 0 ? itemPosition : reverse(itemPosition); } @Override public Object instantiateItem(ViewGroup container, int position) { return super.instantiateItem(container, reverse(position)); } @Override public void destroyItem(ViewGroup container, int position, Object object) { super.destroyItem(container, reverse(position), object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { super.setPrimaryItem(container, lastCount - position - 1, object); } private int reverse(int position) { return getCount() - position - 1; } private void revalidateIndices() { final int newCount = getCount(); if (newCount != lastCount) { setCurrentItemWithoutNotification(Math.max(0, lastCount - 1)); lastCount = newCount; } } } private static class RevalidateIndicesOnContentChange extends DataSetObserver { @NonNull private final ReverseAdapter adapter; private RevalidateIndicesOnContentChange(@NonNull ReverseAdapter adapter) { this.adapter = adapter; } @Override public void onChanged() { super.onChanged(); adapter.revalidateIndices(); } } private class ReverseOnPageChangeListener implements OnPageChangeListener { @NonNull private final OnPageChangeListener original; private int pagerPosition; private ReverseOnPageChangeListener(@NonNull OnPageChangeListener original) { this.original = original; pagerPosition = -1; } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (!suppressOnPageChangeListeners) { if (positionOffset == 0f && positionOffsetPixels == 0) { pagerPosition = reverse(position); } else { pagerPosition = reverse(position + 1); } original.onPageScrolled(pagerPosition, positionOffset > 0 ? 1f - positionOffset : positionOffset, positionOffsetPixels); } } @Override public void onPageSelected(int position) { if (!suppressOnPageChangeListeners) { original.onPageSelected(reverse(position)); } } @Override public void onPageScrollStateChanged(int state) { if (!suppressOnPageChangeListeners) { original.onPageScrollStateChanged(state); } } private int reverse(int position) { final PagerAdapter adapter = getAdapter(); return adapter == null ? position : adapter.getCount() - position - 1; } } public static class SavedState implements Parcelable { Parcelable superState; int position; boolean isRTL; public SavedState(Parcelable superState, int position, boolean isRTL) { super(); this.superState = superState; this.position = position; this.isRTL = isRTL; } SavedState(Parcel in, ClassLoader loader) { if (loader == null) { loader = getClass().getClassLoader(); } superState = in.readParcelable(loader); position = in.readInt(); isRTL = in.readByte() != 0; } @Override public void writeToParcel(Parcel out, int flags) { out.writeParcelable(superState, flags); out.writeInt(position); out.writeByte(isRTL ? (byte) 1 : (byte) 0); } @Override public int describeContents() { return 0; } public static final ClassLoaderCreator CREATOR = new ClassLoaderCreator() { @Override public SavedState createFromParcel(Parcel source, ClassLoader loader) { return new SavedState(source, loader); } @Override public SavedState createFromParcel(Parcel source) { return new SavedState(source, null); } public SavedState[] newArray(int size) { return new SavedState[size]; } }; }}
PagerAdapterWrapper.java
package com.booking.rtlviewpager;import android.database.DataSetObserver;import android.os.Parcelable;import android.support.annotation.NonNull;import android.support.v4.view.PagerAdapter;import android.view.View;import android.view.ViewGroup;/** * PagerAdapter decorator. */class PagerAdapterWrapper extends PagerAdapter { @NonNull private final PagerAdapter adapter; protected PagerAdapterWrapper(@NonNull PagerAdapter adapter) { this.adapter = adapter; } @NonNull public PagerAdapter getInnerAdapter() { return adapter; } @Override public int getCount() { return adapter.getCount(); } @Override public boolean isViewFromObject(View view, Object object) { return adapter.isViewFromObject(view, object); } @Override public CharSequence getPageTitle(int position) { return adapter.getPageTitle(position); } @Override public float getPageWidth(int position) { return adapter.getPageWidth(position); } @Override public int getItemPosition(Object object) { return adapter.getItemPosition(object); } @Override public Object instantiateItem(ViewGroup container, int position) { return adapter.instantiateItem(container, position); } @Override public void destroyItem(ViewGroup container, int position, Object object) { adapter.destroyItem(container, position, object); } @Override public void setPrimaryItem(ViewGroup container, int position, Object object) { adapter.setPrimaryItem(container, position, object); } @Override public void notifyDataSetChanged() { adapter.notifyDataSetChanged(); } @Override public void registerDataSetObserver(DataSetObserver observer) { adapter.registerDataSetObserver(observer); } @Override public void unregisterDataSetObserver(DataSetObserver observer) { adapter.unregisterDataSetObserver(observer); } @Override public Parcelable saveState() { return adapter.saveState(); } @Override public void restoreState(Parcelable state, ClassLoader loader) { adapter.restoreState(state, loader); } @Override public void startUpdate(ViewGroup container) { adapter.startUpdate(container); } @Override public void finishUpdate(ViewGroup container) { adapter.finishUpdate(container); }}
更多相关文章
- 没有一行代码,「2020 新冠肺炎记忆」这个项目却登上了 GitHub 中
- 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
- android开发中实现个性化ListView的一些概念和思路
- Android06之RecyclerView详解
- Android布局管理器详解
- 一步步实现 仿制Android(安卓)LOL多玩盒子(四) 自定义AlertDialo
- React Native运行问题:Warning: License for package Android(安
- Android(安卓)UI开发之——使用Fragment构建灵活的桌面
- [置顶] 去掉listview的分割线和分割线的颜色,高度的设置