效果图:
效果的实现原理主要是自定义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