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

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

效果图:

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

package com.weishitech.qichechangtingyinyue.fragment.lilv; import android.os.Handler; import android.os.Looper; import android.widget.Toast; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager2.widget.ViewPager2; import com.hfd.common.base.BaseFragment; import com.weishitech.qichechangtingyinyue.R; import com.weishitech.qichechangtingyinyue.bean.MusicBean; import com.weishitech.qichechangtingyinyue.bean.MusicCache; import com.weishitech.qichechangtingyinyue.fragment.Adapter.SongPagerAdapter; import com.weishitech.qichechangtingyinyue.utils.MusicPlayerManager; import java.util.ArrayList; import java.util.List; /** * 个人出租屋税率 */ public class LiLvFragment extends BaseFragment { private ViewPager2 vp2_song; private SongPagerAdapter adapter; private List<MusicBean.DataBean> songList; @Override protected int setLayout() { return R.layout.fragment_lilv; } @Override protected void initView() { vp2_song = fvbi(R.id.vp2_song); } @Override protected void initClick() {} @Override protected void initData() { songList = MusicCache.getMainMusicList(); if (songList.isEmpty()) { // 如果缓存为空,监听后续更新(可选) Toast.makeText(requireContext(), "暂无歌曲数据,等待加载...", Toast.LENGTH_LONG).show(); // 可以注册一个事件总线监听“数据加载完成” return; } setupViewPager2(); } private void setupViewPager2() { // 设置垂直滑动 vp2_song.setOrientation(ViewPager2.ORIENTATION_VERTICAL); // 创建适配器 adapter = new SongPagerAdapter(requireContext(), songList); vp2_song.setAdapter(adapter); // 默认播放第一首 playSongAtPosition(0); // 页面切换监听 vp2_song.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() { @Override public void onPageSelected(int position) { super.onPageSelected(position); playSongAtPosition(position); } }); } private void playSongAtPosition(int position) { if (position < 0 || position >= songList.size()) return; MusicBean.DataBean song = songList.get(position); Toast.makeText(requireContext(), "正在播放:" + song.getTitle(), Toast.LENGTH_SHORT).show(); // 调用播放器播放 MusicPlayerManager.getInstance().play(requireContext(), position, song.getMusic()); } // 提供一个方法供外部刷新数据(比如 HomeFragment 加载完后通知它) public void refreshData() { List<MusicBean.DataBean> newList = MusicCache.getMainMusicList(); if (newList.isEmpty()) return; this.songList.clear(); this.songList.addAll(newList); if (adapter != null) { adapter.notifyDataSetChanged(); } // 播放第一首 if (vp2_song != null && adapter != null) { playSongAtPosition(0); } } // 生命周期中尝试刷新(防止进入太早没数据) @Override public void onResume() { super.onResume(); // 再次检查是否可以刷新 if ((songList == null || songList.isEmpty()) && !MusicCache.getMainMusicList().isEmpty()) { songList = new ArrayList<>(MusicCache.getMainMusicList()); if (getView() != null) { setupViewPager2(); // 初始化 } } } private Handler handler = new Handler(Looper.getMainLooper()); private Runnable updateRunnable = new Runnable() { @Override public void run() { if (vp2_song != null && adapter != null) { int currentPosition = vp2_song.getCurrentItem(); long currentTimeMillis = MusicPlayerManager.getInstance().getCurrentPosition(); // 获取当前页面 ViewHolder,进而获取 LrcView RecyclerView recyclerView = (RecyclerView) vp2_song.getChildAt(0); RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(currentPosition); if (holder instanceof SongPagerAdapter.SongViewHolder) { SongPagerAdapter.SongViewHolder vh = (SongPagerAdapter.SongViewHolder) holder; vh.lyricView.setCurrentTime(currentTimeMillis); // 更新歌词时间 } // 每隔 50ms 更新一次(平滑滚动) handler.postDelayed(this, 50); } } }; } Cannot resolve method 'setCurrentTime' in 'LrcView'
最新发布
12-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值