ViewPager几种堆叠效果及刷新问题处理,缓存处理和Adapter base封装

本文详细介绍自定义ViewPager的PageTransformer实现多种动画效果,包括缩放、平移和旋转,并探讨了滑动动画时间的设置与缓存优化策略。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

效果图:

gif录制的不带好,等个十秒钟
效果的实现原理主要是自定义ViewPager的PageTransformer来对每一个item进行缩放,平移(X,Y,Z轴 ),当然还可以旋转角度。
第一个效果,是最基础的,主要是布局,viewPager本身和父布局加上 android:clipChildren="false"属性,允许绘制的内容超过本身显示。

 <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="@color/oldlace"
        android:clipChildren="false">

        <cjx.liyueyun.viewpagerdemo.viewpager.ViewPagerSkip
            android:id="@+id/viewPager01"
            android:layout_width="200dp"
            android:layout_height="match_parent"
            android:layout_centerInParent="true"
            android:clipChildren="false" />

    </RelativeLayout>

代码:

private void initPager01() {
        MyPagerAdapter pagerAdapter = new MyPagerAdapter(colorList, this);
        viewPager01.setOffscreenPageLimit(3);
        viewPager01.setPageMargin(20);
        viewPager01.setPageTransformer(false, new ScaleTransformer());
        viewPager01.setAdapter(pagerAdapter);
    }
public class ScaleTransformer implements ViewPager.PageTransformer {
    private static final float MAX_SCALE = 1.00f;
    private static final float MIN_SCALE = 0.7f;

    @Override
    public void transformPage(View page, float position) {
        if (position < -1) {
            position = -1;
        } else if (position > 1) {
            position = 1;
        }
        float tempScale = position < 0 ? 1 + position : 1 - position;
        float slope = (MAX_SCALE - MIN_SCALE) / 1;
        float scaleValue = MIN_SCALE + tempScale * slope;
        page.setScaleX(scaleValue);
        page.setScaleY(scaleValue);
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
            page.getParent().requestLayout();
        }
    }
}

第二三中效果是一样的,只是transformer逻辑略有不同

public class SingleAddPageTransformer implements ViewPager.PageTransformer {
    private float mOffset = 0;
    private String TAG = this.getClass().getSimpleName();
    private float scall = 1;
    private static final float CENTER_PAGE_SCALE = 0.8f;
    private int offscreenPageLimit;//vp 缓存个数
    private boolean addToRight = true;

    public SingleAddPageTransformer(int offscreenPageLimit, float mOffset, boolean addToRight) {
        this.mOffset = mOffset;
        this.offscreenPageLimit = offscreenPageLimit;
        this.addToRight = addToRight;
    }

    private int pagerWidth;
    private float horizontalOffsetBase;

    @Override
    public void transformPage(@NonNull View page, float position) {
        if (pagerWidth == 0)
            pagerWidth = page.getWidth();//vp width
        if (horizontalOffsetBase == 0)
            horizontalOffsetBase = (pagerWidth - pagerWidth * CENTER_PAGE_SCALE) / 2 / offscreenPageLimit + mOffset;

        if (addToRight) {
            if (position >= offscreenPageLimit || position <= -1) {//向左边滑去的
//            logUtil.d_2(TAG, "向左边滑去了");
                page.setVisibility(View.GONE);
            } else {
                page.setVisibility(View.VISIBLE);
            }

            //setTranslation
            if (position >= 0) {
//            page.setTranslationX((-pagerWidth * position) + horizontalOffsetBase * position);
                page.setTranslationX((horizontalOffsetBase - pagerWidth) * position);
            } else {
                page.setTranslationX(0);
            }

            //setScale
            if (position == 0) {
                page.setScaleX(CENTER_PAGE_SCALE);
                page.setScaleY(CENTER_PAGE_SCALE);
            } else {
                float scaleFactor = Math.min(CENTER_PAGE_SCALE - position * 0.1f, CENTER_PAGE_SCALE);
                page.setScaleX(scaleFactor);
                page.setScaleY(scaleFactor);
            }

        } else {
            if ( position >= 1) {
                page.setVisibility(View.GONE);
            } else {
                page.setVisibility(View.VISIBLE);
            }

            //setTranslation
            if (position < 0) {
//            page.setTranslationX((-pagerWidth * position) + horizontalOffsetBase * position);
                page.setTranslationX((horizontalOffsetBase - pagerWidth) * position);
            } else {
                page.setTranslationX(0);
            }

            //setScale
            if (position == 0) {
                page.setScaleX(CENTER_PAGE_SCALE);
                page.setScaleY(CENTER_PAGE_SCALE);
            } else {
                float scaleFactor = Math.min(CENTER_PAGE_SCALE - Math.abs(position) * 0.1f, CENTER_PAGE_SCALE);
                page.setScaleX(scaleFactor);
                page.setScaleY(scaleFactor);
            }
        }
    }
}

第四个效果:
两边item叠加的代码,,有一个api的限制,必须得安卓5.0及以上才能用,原因是
setTranslationZ()方法的限制,view的绘制要么从前向后绘制,要么从后向前,后绘制的view会覆盖前面的view,getChildDrawingOrder方法也可以改变View的绘制顺序,,但同时改变多个,且有规矩改变,尝试了一下,没有规律可言,一般情况下view的Z都是0,setTranslationZ()设一个复制,就会显示在当前Z为0的后面,刚好符合我们的要求。重写的transformPage中参数position变化的规律为:
当前 page显示时position=0
向左滑
currentPage pos 0 到-1
currentPage左边一个Page pos -1到-2
currentPage右边一个Page pos 1到 0
向右滑
currentPage pos 0 到1
currentPage左边一个Page pos -1到0
currentPage右边一个Page pos 1到 2

public class DoubleAddPageTransformer implements ViewPager.PageTransformer {
    private float mOffset = 0;
    private String TAG = this.getClass().getSimpleName();
    private float SCALE = 0.8f;
    private int offscreenPageLimit;//vp 缓存个数

    public DoubleAddPageTransformer(int offscreenPageLimit, float mOffset) {
        this.mOffset = mOffset;
        this.offscreenPageLimit = offscreenPageLimit;
    }

    @Override
    public void transformPage(@NonNull View page, float position) {
        animWay1(page, position);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            page.setTranslationZ(-Math.abs(position));
        }
    }

    private int pagerWidth;
    private float horizontalOffsetBase;
    private void animWay1(View page, float position) {
        if (pagerWidth == 0)
            pagerWidth = page.getWidth();//vp width
        if (horizontalOffsetBase == 0)
            horizontalOffsetBase = (pagerWidth - pagerWidth * SCALE) / 2 / offscreenPageLimit + mOffset;
        //setTranslation
//        page.setTranslationX((-page.getWidth() * position) + horizontalOffsetBase * position);
        page.setTranslationX((horizontalOffsetBase - pagerWidth) * position);
        //setScale
        float scaleFactor = Math.min(SCALE - Math.abs(position) * 0.1f, SCALE);
        page.setScaleX(scaleFactor);
        page.setScaleY(scaleFactor);
    }
}

第五个效果只是,item平移的方向在竖直方向

public class AddYTransformer implements ViewPager.PageTransformer {
    private int mOffset = 10;
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    public void transformPage(View page, float position) {
        if (position <= 0) {
            page.setTranslationX(-position * page.getWidth());
            //缩放卡片并调整位置
            float scale = (page.getWidth() + mOffset * position) / page.getWidth();
            page.setScaleX(scale);
            page.setScaleY(scale);
           //移动Y轴坐标
            page.setTranslationY(-position * mOffset);
            page.setTranslationZ(position);
        }
    }
}

自定义的viewpager可以设置滑动动画执行的时间(网上有很多类似的)

public class ViewPagerScroller extends Scroller {
    private int mDuration = 2000;/*default duration time*/
    /**
     * Set custom duration time.
     * @param duration duration
     */
    public void setScrollDuration(int duration){
        mDuration = duration;
    }

    /**
     * Get duration time.
     * @return duration
     */
    public int getmDuration() {
        return mDuration;
    }

    public ViewPagerScroller(Context context) {
        super(context);
    }

    public ViewPagerScroller(Context context, Interpolator interpolator) {
        super(context, interpolator);
    }

    public ViewPagerScroller(Context context, Interpolator interpolator, boolean flywheel) {
        super(context, interpolator, flywheel);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy) {
        super.startScroll(startX, startY, dx, dy,mDuration);
    }

    @Override
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        //此处必须重写,网上有些资料里只重写了上面那个,不知道他们的是怎么工作的,我实际测试时行不通的。
        super.startScroll(startX, startY, dx, dy, mDuration);
    }

    public void initViewPagerScroll(ViewPager pager){
        try{
            Field field = ViewPager.class.getDeclaredField("mScroller");
            field.setAccessible(true);
            field.set(pager,this);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
public class ViewPagerSkip extends ViewPager {
    private ViewPagerScroller scroller;

    public ViewPagerSkip(Context context) {
        this(context, null);
    }

    public ViewPagerSkip(Context context, AttributeSet attrs) {
        super(context, attrs);
        if (scroller == null)
            scroller = new ViewPagerScroller(context);
        scroller.initViewPagerScroll(this);
    }


    @Override
    public void setCurrentItem(int item) {
        setCurrentItem(item, true);
    }

    @Override
    public void setCurrentItem(int item, boolean smoothScroll) {
        if (Math.abs(getCurrentItem() - item) > 1) {
            int duration = scroller.getmDuration();
            scroller.setScrollDuration(0);
            super.setCurrentItem(item, smoothScroll);
            scroller.setScrollDuration(duration);
        } else {
            super.setCurrentItem(item, smoothScroll);
        }
    }

    /**
     * 设置翻页的时间
     */
    public void setScrollDuration(int dur) {
        scroller.setScrollDuration(dur);
    }

    @Override
    protected int getChildDrawingOrder(int childCount, int i) {
        try {
            return super.getChildDrawingOrder(childCount, i);
        } catch (Exception e) {
//            Toast.makeText(getContext(), "异常:" + e.getMessage(), Toast.LENGTH_SHORT).show();
            return i;
        }
    }
}

BasePagerAdapter的封装,,

public abstract class BasePagerAdapter<T, H extends BasePagerAdapter.BaseHolder> extends PagerAdapter {
    protected List<T> dataList;
    protected Context mContext;//可以用软引用

    public BasePagerAdapter(List<T> dataList, Context mContext) {
        this.dataList = dataList;
        this.mContext = mContext;
    }

    @Override
    public Object instantiateItem(ViewGroup container, int position) {
        H holder;
        T data;
        holder = createHolder(mContext, position);
        data = dataList.get(position);
        container.addView(holder.itemView);
        onBindView(holder, data, position);
        return holder.itemView;
    }

    @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }

    @Override
    public int getCount() {
        return dataList == null ? 0 : dataList.size();
    }

    @Override
    public boolean isViewFromObject(View view, Object object) {
        return view == object;
    }

    @Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        View removeView = (View) object;
        container.removeView(removeView);
        onReleasePagerHolder((H) removeView.getTag(), position);
    }

    @NonNull
    protected abstract H createHolder(Context context, int position);

    protected abstract void onBindView(H holder, T data, int position);

    /**
     * 用于释放页面destroyItem调用且移除时调用
     */
    protected void onReleasePagerHolder(H holder, int position) {
    }

    public static class BaseHolder {
        public final View itemView;

        public BaseHolder(View itemView) {
            itemView.setTag(this);
            this.itemView = itemView;
        }
    }

}

BasePagerAdapter用法及缓存的优化:

public class MyPagerAdapter extends BasePagerAdapter<Integer, MyPagerAdapter.MyHolder> {
    private String TAG = "MyPagerAdapter";
    private HashMap<Integer, MyHolder> allHolder;//缓存所有的holder
    
    public MyPagerAdapter(List<Integer> dataList, Context mContext) {
        super(dataList, mContext);
        allHolder = new HashMap<>();
        createAllItemView(dataList);
    }

    //此刷新有些Viewpager 效果会出现问题  当数据有增减,重新new adapter就好
    public void refreshAll(List<Integer> colors) {
        createAllItemView(colors);
        notifyDataSetChanged();
    }
    
    //item数量不变刷新单个item内View
    public void referOnItem(List<Integer> newList, int position) {
        MyHolder holder = allHolder.get(position);
        if (holder != null) {
            onBindView(holder, newList.get(position), position);
        }
    }

    /**
     * 缓存所有的holder是为了,使ViewPager加载大图时滑动不卡顿,,适用于少量大图模式
     * 正常情况下,,createHolder每次去创建就好,,destroyItem回收
     * @param dataList
     */
    private void createAllItemView(List<Integer> dataList) {
        allHolder.clear();
        for (int i = 0; i < dataList.size(); i++) {
            MyHolder itemH = createHolder(mContext, i);
            allHolder.put(i, itemH);
        }
    }

    @NonNull
    @Override
    protected MyHolder createHolder(Context context, int position) {
        MyHolder holder = allHolder.get(position);
        if (holder == null) {
            logUtil.d_2(TAG, "应该不会出现这种情况吧");
            holder = new MyHolder(View.inflate(context, R.layout.item_vp, null));
            allHolder.put(position, holder);
        }
        return holder;
    }

    @Override
    protected void onBindView(MyHolder holder, Integer data, int position) {
        holder.tvLeft.setText(String.valueOf(position));
        holder.tvRight.setText(String.valueOf(position));
        holder.itemView.setBackgroundResource(data);
    }

    static class MyHolder extends BasePagerAdapter.BaseHolder {
        private final TextView tvLeft;
        private final TextView tvRight;
        public MyHolder(View itemView) {
            super(itemView);
            tvLeft = (TextView) itemView.findViewById(R.id.tv_left);
            tvRight = (TextView) itemView.findViewById(R.id.tv_right);
        }
    }
}

此处我是缓存了item个数的holder,,目的是为了解决加载大图滑动时再去创建View带来的卡顿,我是一次创建完,,滑动时去取填充数据就好,我这个场景是少量item,大图加载,当然最重要的是每个item的还进行了缩放,平移。
还有一个方法缓存,用两个list,showList和removeList分别存储当前显示的item(),,和Viewpager回收的Item即destroyItem中remove的View的holder

holder是静态的,持有itemView

 @Override
    public Object instantiateItem(ViewGroup container, int position) {
        H holder;
        T data;
        holder = createHolder(mContext, position);//当前显示的holder
        data = dataList.get(position);
        container.addView(holder.itemView);
        onBindView(holder, data, position);
        return holder.itemView;
    }
@Override
    public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
        View removeView = (View) object;
        container.removeView(removeView);
        onReleasePagerHolder((H) removeView.getTag(), position);
        //removeView.getTag() remove的View的holder
    }

这两个List怎么用呢,一旦viewPager需要创建view,优先从removeList中取,没有才去创建,这两个list的size和最大就是所有item的数量,达到合理利用资源的效果。这种情况适用于所有itemVIew布局,大小等是一样的,,如果有缩放,,复用的item大小会错乱。

viewPager刷新问题,,相距多个item setCurrentItem时item没有刷新,空白,自定义的viewpager就解决了,见上面的ViewPagerSkip。
adapter不刷新的问题,adapter中重写下面这个方法,返回这个值就好,见上面
MyPagerAdapter,BasePagerAdapter

  @Override
    public int getItemPosition(Object object) {
        return PagerAdapter.POSITION_NONE;
    }

就这样啦,喜欢就给个start吧!
GitHub:https://github.com/caijianxiong/ViewPagerDemo.git

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值