Android修行手册-ViewPager定制页面切换以及实现原理剖析
Unity3D特效百例 | 案例项目实战源码 | Android-Unity实战问题汇总 |
---|---|---|
游戏脚本-辅助自动化 | Android控件全解手册 | 再战Android系列 |
Scratch编程案例 | 软考全系列 | Unity3D学习专栏 |
蓝桥系列 | ChatGPT和AIGC |
👉关于作者
专注于Android/Unity和各种游戏开发技巧,以及各种资源分享(网站、工具、素材、源码、游戏等)
有什么需要欢迎底部卡片私我,交流让学习不再孤单。
👉实践过程
😜简述及原理
你是不是觉得 ViewPager 默认的切换效果有些平淡?其实,我们可以定制 ViewPager 的页面切换效果。定制 ViewPager 的页面切换效果,只需用到 ViewPager 的一个方法setPageTransformer(boolean reverseDrawingOrder, @Nullable PageTransformer transformer)
,实现一个接口 PageTransformer
。
包含 ViewPager2 ,大概看了下源码,动画是由ViewPager2.PageTransformer,其实跟ViewPager.PageTransformer接口一致。
这个接口主要为:
public interface PageTransformer {void transformPage(@NonNull View page, float position);
}
其中存在两个参数,比较不好理解,第一个参数page我们可以理解为我们即将要转换的对象,而对于position,我刚开始的理解为page的的当前的位置index,当看到postion是float的时候,我想我猜错了。
虽然我不知道这个position是什么意思,给的解释也是模棱两可的,然后我就打log记录这个position值,大致得出这样的结论:
大致的viewpager效果如下图:
那么在滑动的过程中:
前一个view的position变化 | 当前view的position变化 | 后一个view的position变化 | |
---|---|---|---|
当前view右滑时 | -1 ----> 0 | 0-------->1 | 1 ----> +∞ |
当前view左滑时 | -∞ ----> -1 | 0 -----> -1 | 1 ------->0 |
我们用动图模拟一下此时的三个view的position的动态变化:
当我们向右移动时:
当我们向左移动时:
我们模拟viewpager的滑动,此时可以看到三个position的趋势与上表是一致的,因此对于这个position我可以这样解释:
当前我们的viewpager存在一个currentItem,就是当前的current position位置,我们记录此时的坐标轴为0,那么向右移动时,前一个view的position也是像右移动的,只是它的坐标是由-1慢慢变大到0的,这种position的值是一个相对值,是相对于当前curerntItem的坐标位置的相对值;同理右边的view也会向右移动,只是它的相对值由1慢慢变得无限大。
同理,我们往左滑动时,这个position也是一个有方向的相对值。
还记得我们比较喜欢设置viewpager.setOffscreenPageLimit
,它的意思就是屏幕之外的view保留几个,我们也称之为缓存view,其实这个limit的个数limitN
与viewpager应该保持view的Count
的关系为:
Count = limitN * 2 + 1
即需要viewPager保存(limitN * 2 + 1)个缓存状态view。为什么扯到这个东西呢?很简单,如果我们将setOffscreenPageLimit
设置为2,那么
void transformPage(@NonNull View page, float position);
这个方法中将会有5中不同的数据回调,分别是:
我们做个测试,将view加上id:
@NonNull@Overridepublic Object instantiateItem(@NonNull ViewGroup container, int position) {ImageView iv = new ImageView(getApplicationContext());iv.setScaleType(ImageView.ScaleType.FIT_XY);// 将id设置为 10000 + 当前的positioniv.setId(10000 + position);ImageUtils.loadImage(imageList.get(position),iv);container.addView(iv);return iv;}
然后我们在滑动的时候,打印一下日志:
@Overridepublic void transformPage(@NonNull View page, float position) {Log.e("TAG", "page:" + page.getId() + "," + position);}
我们看到的确存在5个类型的page值,说明我们的推断是正确的。
😜clipChildren属性
clipChildren属性表示是否限制子控件在该容器所在的范围内,clipChildren属性配合layout_gravity属性,可以用来设置多余部分的显示位置,我这里举一个简单的例子,比如喜马拉雅FM这个应用的首页:
大家注意看这个应用底部导航栏中中间一个是要比另外四个高的,这种效果很多人就会想到使用一个RelativeLayout布局来实现,其实不用那么麻烦,这种效果一个clipChildren属性就能实现,示例Demo如下:
代码:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:clipChildren="false" tools:context="org.lenve.clipchildren.MainActivity"> <LinearLayout android:layout_width="match_parent" android:layout_height="48dp" android:layout_alignParentBottom="true" android:background="#03b9fc" android:orientation="horizontal"> <ImageView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:src="@mipmap/ic_launcher"/> <ImageView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:src="@mipmap/ic_launcher"/> <ImageView android:layout_width="0dp" android:layout_height="72dp" android:layout_gravity="bottom" android:layout_weight="1" android:src="@mipmap/ic_launcher"/> <ImageView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:src="@mipmap/ic_launcher"/> <ImageView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="1" android:src="@mipmap/ic_launcher"/> </LinearLayout></RelativeLayout>
大家看只需要在根节点添加clipChildren属性,然后在第三个ImageView上添加layout_gravity属性即可,layout_gravity属性值为bottom表示控件大小超出后控件底部对齐。效果如下:
OK,上面是对clipChildren属性一个简单介绍,算是一个铺垫,接下来我们来看看ViewPager。
😜ViewPager结合CardView
那么在这之前,我想先介绍一个属性,那就是clipToPadding,这个属性是什么意思呢?它表示是否允许ViewGroup在ViewGroup的padding中进行绘制,默认情况下该属性的值为true,即不允许在ViewGroup的padding中进行绘制。那如果我设置了false呢?我们来看看:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="org.lenve.myviewpagercards2.MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="200dp" android:clipToPadding="false" android:paddingBottom="24dp" android:paddingLeft="48dp" android:paddingRight="48dp" android:paddingTop="24dp"></android.support.v4.view.ViewPager></RelativeLayout>
ViewPager的Adapter如下:
public class MyAdapter extends PagerAdapter { private List<Integer> list; private Context context; public MyAdapter(Context context, List<Integer> list) { this.context = context; this.list = list; } @Override public int getCount() { return list.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { ImageView iv = new ImageView(context); iv.setImageResource(list.get(position)); container.addView(iv); return iv; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); }}
Activity中的代码:
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); List<Integer> list = new ArrayList<>(); list.add(R.drawable.p001); list.add(R.drawable.p002); list.add(R.drawable.p003); list.add(R.drawable.p004); list.add(R.drawable.p005); MyAdapter adapter = new MyAdapter(this, list); viewPager.setAdapter(adapter); viewPager.setPageMargin(20);
显示效果如下:
OK,那这个clipToPadding属性是我们在一个页面中显示多个ViewPager item的第二种方式。这个CardView式的ViewPager我们就使用这种方式来实现。先来看看效果图:
整体思路和上文其实是一致的,我们来看看activity的布局:
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="org.lenve.myviewpagercards2.MainActivity"> <android.support.v4.view.ViewPager android:id="@+id/viewpager" android:layout_width="match_parent" android:layout_height="300dp" android:clipToPadding="false" android:paddingBottom="24dp" android:paddingLeft="80dp" android:paddingRight="80dp" android:paddingTop="24dp"></android.support.v4.view.ViewPager></RelativeLayout>
ViewPager中每一个item的布局:
<?xml version="1.0" encoding="utf-8"?><android.support.v7.widget.CardView android:id="@+id/cardview" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" app:cardCornerRadius="10dp"> <RelativeLayout android:layout_width="match_parent" android:layout_height="300dp"> <TextView android:id="@+id/tv" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_centerInParent="true" android:gravity="center" android:text="我是一个TextView"/> <Button android:layout_width="96dp" android:layout_height="36dp" android:textColor="#ffffff" android:layout_below="@id/tv" android:layout_centerHorizontal="true" android:layout_marginTop="12dp" android:background="@color/colorAccent" android:text="我是一个按钮"/> </RelativeLayout></android.support.v7.widget.CardView>
Adapter:
public class MyAdapter extends PagerAdapter { private List<Integer> list; private Context context; private LayoutInflater inflater; public MyAdapter(Context context, List<Integer> list) { this.context = context; this.list = list; inflater = LayoutInflater.from(context); } @Override public int getCount() { return list.size(); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } @Override public Object instantiateItem(ViewGroup container, int position) { View view = inflater.inflate(R.layout.vp_item, container, false); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); }}
Activity中的代码:
ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager); List<Integer> list = new ArrayList<>(); list.add(R.drawable.p001); list.add(R.drawable.p002); list.add(R.drawable.p003); list.add(R.drawable.p004); list.add(R.drawable.p005); MyAdapter adapter = new MyAdapter(this, list); viewPager.setAdapter(adapter); viewPager.setPageMargin((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics())); viewPager.setPageTransformer(false, new ScaleTransformer(this));
最后再来看看我们定义的PageTransformer:
public class ScaleTransformer implements ViewPager.PageTransformer { private Context context; private float elevation; public ScaleTransformer(Context context) { this.context = context; elevation = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics()); } @Override public void transformPage(View page, float position) { if (position < -1 || position > 1) { } else { if (position < 0) { ((CardView) page).setCardElevation((1 + position) * elevation); } else { ((CardView) page).setCardElevation((1 - position) * elevation); } } }}
很简单,我只是对CardView的阴影做了处理 ,其他属性都没改,这样就有了我们刚才看到的效果。
😜例子和效果
1、手风琴效果
其实就是水平缩放效果。在页面滑动时,
左边页面 position < 0,右边页面 position > 0;
左边页面以页面右边缘为缩放中心,右边页面以左边缘为缩放中心。
代码如下所示:
/*** 手风琴效果(水平方向缩放)*/
public class AccordionTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {if (position < 0f) {page.setPivotX(page.getWidth());page.setScaleX(1f + position * 0.5f);} else if (position < 1f) {page.setPivotX(0f);page.setScaleX(1f - position * 0.5f);}}
}
2、下弧形效果
实现此效果需以页面下边缘某一点为旋转中心旋转:
- position < -1 时,旋转到最大角度,旋转中心为右下角;
- -1 < position < 0 时,position 越靠近 0 ,旋转角度越小,旋转中心向下边缘中心靠拢;
- 0 <= position <= 1 时,position 越靠近 0 ,旋转角度越小,旋转中心向下边缘中心靠拢;
- position > 1 时,旋转到最大角度,旋转中心为左下角。
代码如下所示:
/*** 下弧形切换效果*/
public class ArcDownTransformer implements ViewPager.PageTransformer {private static final float DEF_MAX_ROTATE = 12.0f;private float mMaxRotate = DEF_MAX_ROTATE;@Overridepublic void transformPage(@NonNull View page, float position) {page.setPivotY( page.getHeight());if (position < -1f) {//[-Infinity, -1)page.setRotation(-mMaxRotate);page.setPivotX(page.getWidth());} else if (position <= 1f) {//[-1, 1]if (position < 0f) {//[-1, 0)page.setRotation(mMaxRotate * position);page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));} else { //[0, 1]page.setRotation(mMaxRotate * position);page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));}} else {//(1, +Infinity]page.setRotation(mMaxRotate);page.setPivotX(0f);}}
}
3、上弧形效果
与下弧形相反,旋转中心以上边缘某一点:
- position < -1 时,旋转到最大角度,旋转中心为右下角;
- -1 < position < 0 时,position 越靠近 0 ,旋转角度越小,旋转中心向上边缘中心靠拢;
- 0 <= position <= 1 时,position 越靠近 0 ,旋转角度越小,旋转中心向上边缘中心靠拢;
- position > 1 时,旋转到最大角度,旋转中心为左下角。
代码如下:
/*** 上弧形切换效果*/
public class ArcUpTransformer implements ViewPager.PageTransformer {private static final float DEF_MAX_ROTATE = 12.0f;private float mMaxRotate = DEF_MAX_ROTATE;@Overridepublic void transformPage(@NonNull View page, float position) {page.setPivotY(0f);if (position < -1f) {//[-Infinity, -1)page.setRotation(mMaxRotate);page.setPivotX(page.getWidth());} else if (position <= 1f) {//[-1, 1]if (position < 0f) {//[-1, 0)page.setRotation(-mMaxRotate * position);page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));} else { //[0, 1]page.setRotation(-mMaxRotate * position);page.setPivotX(page.getWidth() * (0.5f - 0.5f * position));}} else {//(1, +Infinity]page.setRotation(-mMaxRotate);page.setPivotX(0f);}}
}
4、立方翻转-外
其实是绕 Y 轴旋转,再加上缩放效果。绕 Y 轴旋转,用到了 View 的 setRotationY(float)
方法,此方法可以设置绕 Y 轴的旋转角度。
- position < -1,逆时针旋转到最大角度,旋转中心为页面右边缘;
- -1 <= position < 0,旋转中心为页面右边缘,position 越靠近 0,旋转角度越小,页面先缩小后放大,缩放值是关于 position 开口向上 对称线为 position = -0.5 的抛物线;
- 0 <= position <= 1,旋转中心为左边缘,position 越靠近 0,旋转角度越小,页面先缩小后放大,缩放值是关于 position 开口向上 对称线为 position = 0.5 的抛物线;
- position > 1,顺时针旋转到最大角度,旋转中心为左边缘。
引入抛物线计算缩放值,是为了让页面在滑动到一半(position 为 -0.5 和 0.5)时,缩放到最小。
需要注意 的是镜头距离,即图像与屏幕距离,距离较小时,旋转时会有较大的失真,效果很差,需要设置一下镜头距离(Camera Distance,参考 View#setCameraDistance(float))。
代码如下:
/*** 立方体翻转效果*/
public class CubicOverturnTransformer implements ViewPager.PageTransformer {public static final float DEFAULT_MAX_ROTATION = 60f;public static final float DEF_MIN_SCALE = 0.86f;/*** 最大旋转角度*/private float mMaxRotation = DEFAULT_MAX_ROTATION;/*** 最小缩放*/private float mMinScale = DEF_MIN_SCALE;public CubicOverturnTransformer() {this(DEFAULT_MAX_ROTATION);}public CubicOverturnTransformer(float maxRotation) {this(maxRotation, DEF_MIN_SCALE);}public CubicOverturnTransformer(float maxRotation, float minScale) {mMaxRotation = maxRotation;this.mMinScale = minScale;}@TargetApi(Build.VERSION_CODES.HONEYCOMB)@Overridepublic void transformPage(@NonNull View page, float position) {page.setPivotY(page.getHeight() / 2f);float distance = getCameraDistance();page.setCameraDistance(distance);//设置 View 的镜头距离,可以防止旋转大角度时出现图像失真或不显示。if (position < -1) { // [-Infinity,-1)page.setRotationY(-mMaxRotation);page.setPivotX(page.getWidth());} else if (position <= 1) { // [-1,1]page.setRotationY(position * mMaxRotation);if (position < 0) {//[0,-1]page.setPivotX(page.getWidth());float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position + 0.5f) * (position + 0.5f);page.setScaleX(scale);page.setScaleY(scale);} else {//[1,0]page.setPivotX(0);float scale = DEF_MIN_SCALE + 4f * (1f - DEF_MIN_SCALE) * (position - 0.5f) * (position - 0.5f);page.setScaleX(scale);page.setScaleY(scale);}} else { // (1,+Infinity]page.setRotationY(mMaxRotation);page.setPivotX(0);}}/*** 获得镜头距离(图像与屏幕距离)。参考{@link View#setCameraDistance(float)},小距离表示小视角,* 大距离表示大视角。这个距离较小时,在 3D 变换(如围绕X和Y轴的旋转)时,会导致更大的失真。* 如果改变 rotationX 或 rotationY 属性,使得此 View 很大 (超过屏幕尺寸的一半),则建议始终使用* 大于此时图高度 (X 轴旋转)或 宽度(Y 轴旋转)的镜头距离。* @return 镜头距离 distance** @see {@link View#setCameraDistance(float)}*/private float getCameraDistance() {DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();float density = displayMetrics.density;int widthPixels = displayMetrics.widthPixels;int heightPixels = displayMetrics.heightPixels;return 1.5f*Math.max(widthPixels, heightPixels)*density;}
}
使用:
mPageTransformer = new CubicOverturnTransformer(90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);
最大旋转角度 90 度,最小缩放到 0.6 。
5、立方翻转-内
与上一个效果是同一套代码,绕 Y 轴旋转方向相反,使用时,让 最大旋转角度小于 0 即可,如下代码所示,最大旋转角度改为 -90f 就是内部翻转:
使用:
mPageTransformer = new CubicOverturnTransformer(-90f, 0.6f);
mVpImgs.setPageTransformer(reverseDrawingOrder, mPageTransformer);
最大旋转角度 90 度,最小缩放到 0.6 。
6、下沉效果
特殊的缩放效果,有最小缩放值,页面缩到最小值不再缩小,同时有透明度的变化(可以去掉透明度变化)。
旋转中心位置的调整主要是为了调整页面间隙。
/*** 下沉效果*/
public class DipInTransformer implements ViewPager.PageTransformer {private static final float MIN_SCALE = 0.85f;private float mMinScale = MIN_SCALE;private static final float MIN_ALPHA = 0.5f;private float mMinAlpha = MIN_ALPHA;@Overridepublic void transformPage(@NonNull View page, float position) {Log.i("DipInTransformer", "transformPage: id = " + page.getId() + ", position = " + position);int pageWidth = page.getWidth();int pageHeight = page.getHeight();
// page.setPivotX(pageWidth * 0.5f);page.setPivotY(pageHeight * 0.5f);if (position < -1f) {//(-Infinity, -1]page.setAlpha(mMinAlpha);page.setScaleX(mMinScale);page.setScaleY(mMinScale);page.setPivotX(pageWidth*1f);} else if (position <= 1f) {//(-1, 1)float scaleFactor = Math.max(mMinScale, 1 - Math.abs(position));page.setScaleX(scaleFactor);page.setScaleY(scaleFactor);if (position < 0) {page.setPivotX(pageWidth * (0.5f + 0.5f * scaleFactor));} else {page.setPivotX(pageWidth * (0.5f - 0.5f * scaleFactor));}page.setAlpha(mMinAlpha + (scaleFactor - mMinScale) / (1f - mMinScale) * (1f - mMinAlpha));} else {//(1, +Infinity)page.setAlpha(mMinAlpha);page.setScaleX(mMinScale);page.setScaleY(mMinScale);page.setPivotX(pageWidth * 0f);}}
}
7、淡入淡出效果(透明度)
不多作解释,就是透明度属性动画。代码如下:
/*** 淡入淡出*/
public class FadeInOutTransformer implements ViewPager.PageTransformer {private static final float DEF_MIN_ALPHA =0.5f;private float mMinAlpha = DEF_MIN_ALPHA;@Overridepublic void transformPage(@NonNull View page, float position) {if (position < -1f) {//[-Infinity, -1)page.setAlpha(mMinAlpha);} else if (position <= 1f) {//[-1, 1]if (position < 0f) {//[-1, 0)page.setAlpha(1f + (1f - mMinAlpha) * position);} else { //[0, 1]page.setAlpha(1f - (1f - mMinAlpha) * position);}} else {//(1, +Infinity]page.setAlpha(mMinAlpha);}}
}
8、水平翻转效果(左右翻转)
即以水平中心线为旋转中心,绕 Y 轴旋转。代码如下所示,同样要注意镜头距离:
/*** 水平翻转效果*/
public class FlipHorizontalTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {page.setCameraDistance(getCameraDistance());page.setTranslationX(-page.getWidth() * position);float rotation = 180f * position;page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);page.setPivotX(page.getWidth() * 0.5f);page.setPivotY(page.getHeight() * 0.5f);page.setRotationY(rotation);if (position > -0.5f && position < 0.5f) {page.setVisibility(View.VISIBLE);} else {page.setVisibility(View.INVISIBLE);}}private float getCameraDistance() {DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();float density = displayMetrics.density;int widthPixels = displayMetrics.widthPixels;int heightPixels = displayMetrics.heightPixels;return 1.5f * Math.max(widthPixels, heightPixels) * density;}
}
9、竖直翻转效果(上下翻转)
即以竖直中心线为旋转中心,绕 X 轴旋转。代码如下所示,同样要注意镜头距离:
/*** 竖直翻转效果*/
public class FlipVerticalTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {page.setCameraDistance(getCameraDistance());page.setTranslationX(-page.getWidth() * position);float rotation = 180f * position;page.setAlpha(rotation > 90f || rotation < -90f ? 0f : 1f);page.setPivotX(page.getWidth() * 0.5f);page.setPivotY(page.getHeight() * 0.5f);page.setRotationX(rotation);if (position > -0.5f && position < 0.5f) {page.setVisibility(View.VISIBLE);} else {page.setVisibility(View.INVISIBLE);}}private float getCameraDistance() {DisplayMetrics displayMetrics = VpsApplication.getAppContext().getResources().getDisplayMetrics();float density = displayMetrics.density;int widthPixels = displayMetrics.widthPixels;int heightPixels = displayMetrics.heightPixels;return 1.5f * Math.max(widthPixels, heightPixels) * density;}
}
10、浮出效果
让所有右边页面都移动到 正中位置,从右向左滑动切换页面时,左边从右向左滑出,右边页面放大淡入。
/*** 浮出效果*/
public class RiseInTransformer implements ViewPager.PageTransformer {private static final float DEF_MIN_SCALE = 0.72f;private float mMinScale = DEF_MIN_SCALE;private static final float DEF_MIN_ALPHA = 0.5f;public RiseInTransformer() {}public RiseInTransformer(float minScale) {this.mMinScale = minScale;}public float getMinScale() {return mMinScale;}public void setMinScale(float minScale) {this.mMinScale = minScale;}@Overridepublic void transformPage(@NonNull View page, float position) {if (position < 0f) {page.setTranslationX(0f);} else if (position <= 1) {page.setTranslationX(-position * page.getWidth());page.setScaleX(1f - (1f - mMinScale) * position);page.setScaleY(1f - (1f - mMinScale) * position);page.setAlpha(1f - (1f - DEF_MIN_ALPHA) * position);} else {page.setTranslationX(-position * page.getWidth());page.setScaleX(mMinScale);page.setScaleY(mMinScale);page.setAlpha(DEF_MIN_ALPHA);}}
}
调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
11、下潜效果
让所有左边页面都移动到 正中位置,切换页面时,左边页面缩小淡出,右面页面从右向左滑入正中。
/*** 下潜效果*/
public class DiveOutTransformer implements ViewPager.PageTransformer {private static final float DEF_MIN_SCALE = 0.72f;private float mMinScale = DEF_MIN_SCALE;private static final float DEF_MIN_ALPHA = 0.5f;public DiveOutTransformer() {}public DiveOutTransformer(float minScale) {this.mMinScale = minScale;}public float getMinScale() {return mMinScale;}public void setMinScale(float minScale) {this.mMinScale = minScale;}@Overridepublic void transformPage(@NonNull View page, float position) {if (position < -1f) {page.setScaleX(mMinScale);page.setScaleY(mMinScale);page.setAlpha(DEF_MIN_ALPHA);page.setTranslationX(-position * page.getWidth());} else if (position <= 0) {page.setTranslationX(-position * page.getWidth());page.setScaleX(1f + (1f - mMinScale) * position);page.setScaleY(1f + (1f - mMinScale) * position);page.setAlpha(1f + (1f - DEF_MIN_ALPHA) * position);} else {page.setTranslationX(0f);}}}
调用时不需要反转绘制顺序,即 reverseDrawingOrder = false,使右边页面先绘制,左边页面后绘制,否则效果无法实现。
int reverseDrawingOrder = false;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
12、堆叠效果
所有右边页面移动到正中位置,即 0 位置,滑动时,把最上面页面滑掉,代码如下所示:
/*** 堆叠效果*/
public class StackTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {page.setTranslationX(position < 0 ? 0f : -page.getWidth() * position);}
}
调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
13、缩放效果
比较简单,与淡入淡出效果类似,不多作解释:
/*** 缩放效果*/
public class ZoomInOutTransformer implements ViewPager.PageTransformer {private static final float DEF_MIN_SCALE = 0.9f;private float mMinScale = DEF_MIN_SCALE;@Overridepublic void transformPage(@NonNull View page, float position) {if (position < -1f) {//[-Infinity, -1)page.setScaleX(mMinScale);page.setScaleY(mMinScale);} else if (position <= 1f) {//[-1, 1]if (position < 0f) {//[-1, 0)page.setScaleX(1f + (1f - mMinScale) * position);page.setScaleY(1f + (1f - mMinScale) * position);} else { //[0, 1]page.setScaleX(1f - (1f - mMinScale) * position);page.setScaleY(1f - (1f - mMinScale) * position);}} else {//(1, +Infinity]page.setScaleX(mMinScale);page.setScaleY(mMinScale);}}
}
14、并行覆盖效果
通过调用 View 的 setScrollX() 方法,使页面内容随着 position 移动。
public class ParallaxTransformer implements ViewPager.PageTransformer {@Overridepublic void transformPage(@NonNull View page, float position) {int width = page.getWidth();if (position <= -1f) {page.setScrollX(0);} else if (position < 1f) {if (position < 0f) {page.setScrollX((int) (width * 0.75f * position));} else {page.setScrollX((int) (width * 0.75f * position));}} else {page.setScrollX(0);}}
}
调用时需要反转绘制顺序,即 reverseDrawingOrder = true,使左边页面先绘制,右边页面后绘制,否则效果无法实现。
int reverseDrawingOrder = true;
mVpImgs.setPageTransformer(reverseDrawingOrder , mPageTransformer);
👉其他
📢作者:小空和小芝中的小空
📢转载说明-务必注明来源:https://zhima.blog.csdn.net/
📢这位道友请留步☁️,我观你气度不凡,谈吐间隐隐有王者霸气💚,日后定有一番大作为📝!!!旁边有点赞👍收藏🌟今日传你,点了吧,未来你成功☀️,我分文不取,若不成功⚡️,也好回来找我。
温馨提示:点击下方卡片获取更多意想不到的资源。
相关文章:

Android修行手册-ViewPager定制页面切换以及实现原理剖析
Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列ChatGPT和AIGC 👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资源分…...

Mycat实现读写分离
Mycat实现读写分离 Mycat支持MySQL主从复制状态绑定的读写分离机制。这里实现的也是基于MySQL主从复制的读写分离。 MySQL主从复制配置 首先要配置MySQL的主从复制,这里配置的是一主一次从。可以参考下面的文章。 https://blog.csdn.net/wsb_2526/article/detail…...

Ceph----CephFS文件系统的使用:详细实践过程实战版
CephFS 介绍 是一个基于 ceph 集群 且兼容 POSIX 标准的文件系统。 创建 cephfs 文件系统时 需要在 ceph 集群中添加 mds 服务,该服务 负责处理 POSIX 文件系统中的 metadata 部分, 实际的数据部分交由 ceph 集群中的 OSD 处理。 cephfs 支持以内核模块…...
python tkinter 使用(七)
python tkinter 使用(七) 本篇文章主要讲下tkinter 中的message 控件. Message控件可以用于在窗口中显示一段文本消息. 以下是个简单的例子: #!/usr/bin/python3 # -*- coding: UTF-8 -*- """Author: zhTime 2023/11/24 上午11:38 .Email:Describe: "…...

17. Python 数据库操作之MySQL和SQLite实例
目录 1. 简介2. 使用PyMySQL2. 使用SQLite 1. 简介 数据库种类繁多,每种数据库的对外接口实现各不相同,为了方便对数据库进行统一的操作,大部分编程语言都提供了标准化的数据库接口,用户不需要了解每种数据的接口实现细节&#x…...

Kafka-TopicPartition
Kafka主题与分区 主题与分区 topic & partition,是Kafka两个核心的概念,也是Kafka的基本组织单元。 主题作为消息的归类,可以再细分为一个或多个分区,分区也可以看作对消息的二次归类。 分区的划分为kafka提供了可伸缩性、水…...

英特尔工作站:助力专业用户实现高效创作
原创 | 文 BFT机器人 英特尔工作站是由全球知名的英特尔公司设计和开发的一款计算平台。英特尔在工作站处理器领域将其产品分为性能型和移动型两类,它的诞生旨在满足专业用户在科学、工程、设计等领域对高性能计算的需求。英特尔工作站配备了最新的英特尔处理器、大…...

软件工程期末复习(选择+填空+判断)
文章目录 软件工程期末复习一、 选择题 软件工程期末复习 一、 选择题 1.“软件危机”的表现不包括:(c) A、软件产品不能按期交付 B、用户对“已完成的”软件产品时常不满意 C、程序员越来越供不应求 D、软件项目难以管理,维护困…...

群晖NAS基础设置
群晖NAS基础设置 最近一直在玩群晖NAS系统,有一些基础的配置跟大家分享一下 开启ssh登录 1.开启方法 控制面板—>终端和SNMP—>终端机 2.使用ssh软件登录 这里我用SecureCRT登录 进入ssh 3.进入root用户 starstar-nas:~$ sudo su -l root Password: ro…...
2023亚太杯数学建模A题B题C题选题建议,思路分析,模型代码
目录 ABC题思路模型代码:获取见文末名片,第一时间更新 视频连接讲解如上 A题思路:采果机器人的图像识别技术思路模型代码 B题思路:玻璃温室中的微气候法规 C题思路:我国新能源电动汽车的发展趋势 ABC题思路模型代…...

OpenGL的学习之路 -5
1.视景体 正交投影 人眼看世界,有一个可见范围。范围内可见,范围外不可见。视景体就是这么一个概念。 (上图仅学习记录用) 在OGL中,有两种投影方式,对应两种视景体。第一种,正交投影…...

【linux】服务器CPU占用50%,top/htop/ps却看不到异常进程?使用unhide可以查看!
问题描述 htop发现前32个核全被占满了,但是却找不到对应进程号 查杀 治标:杀死隐藏进程 1、unhide 安装unhide apt-get install unhideunhide使用 unhide proc果然发现了隐藏进程 kill -9 kill -9 [pid]这么多pid号,我这边杀了其中…...

JSP EL表达式获取list/Map集合与java Bean对象
上文 JSP EL表达式基本使用 中 我们对EL表达式做了一个基本的了解 也做了基础的字符串数据使用 那么 我们可以来看一下我们的集合 首先 list 这个比较简单 我们直接这样写代码 <% page import"java.util.ArrayList" %> <% page import"java.util.Lis…...

汇编程序:查找数组中最大最小值
实验内容 1. 从数据段DS中包含9个字节的数组数据VALUE中分别找出最大值(存到max中)、最小值(存到min中)。 2. 能够单步执行程序,认真观察、判断每条指令执行的结果是否正确,对错误结果,能够做出…...
ElasticSearch之禁用交换分区
操作系统将进程加载至内存中执行时,对于当前未使用到的内存页,可能会将相关内存页交换至硬盘上,即swap。 对于性能敏感、时延敏感的应用程序比如ElasticSearch,swap特性会明显影响性能和稳定性,因此最好禁用swap特性。…...

【Linux】第二十一站:文件(一)
文章目录 一、共识原理二、C系列文件接口三、从C过渡到系统:文件系统调用四、访问文件的本质 一、共识原理 文件 内容 属性 文件分为打开的文件 和 没打开的文件 打开的文件:是谁打开的?是进程!----所以研究打开的文件本质是研…...
centos7 docker开启认证的远程端口2376配置
docker开启2375会存在安全漏洞 暴露了2375端口的Docker主机。因为没有任何加密和认证过程,知道了主机IP以后,,任何人都可以管理这台主机上的容器和镜像,以前贪图方便,只开启了没有认证的docker2375端口,后…...

Java王者荣耀小游戏
Background类 package LX;import java.awt.*; //背景类 public class Background extends GameObject{public Background(GameFrame gameFrame) {super(gameFrame);}Image bg Toolkit.getDefaultToolkit().getImage("C:\\Users\\ASUS\\Desktop\\王者荣耀图片\\Map.jpg&…...

谈谈Redis的几种经典集群模式
目录 前言 主从复制 哨兵模式 分片集群 前言 Redis集群是一种通过将多个Redis节点连接在一起以实现高可用性、数据分片和负载均衡的技术。它允许Redis在不同节点上同时提供服务,提高整体性能和可靠性。在Redis中提供集群方案总共有三种:主从复制、…...

【腾讯云 HAI域探秘】基于高性能应用服务器HAI部署的 ChatGLM2-6B模型,我开发了AI办公助手,公司行政小姐姐用了都说好!
目录 前言 一、腾讯云HAI介绍: 1、即插即用 轻松上手 2、横向对比 青出于蓝 3、多种高性能应用部署场景 二、腾讯云HAI一键部署并使用ChatGLM2-6B快速实现开发者所需的相关API服务 1、登录 高性能应用服务 HAI 控制台 2、点击 新建 选择 AI模型,…...

【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

Swift 协议扩展精进之路:解决 CoreData 托管实体子类的类型不匹配问题(下)
概述 在 Swift 开发语言中,各位秃头小码农们可以充分利用语法本身所带来的便利去劈荆斩棘。我们还可以恣意利用泛型、协议关联类型和协议扩展来进一步简化和优化我们复杂的代码需求。 不过,在涉及到多个子类派生于基类进行多态模拟的场景下,…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...
1688商品列表API与其他数据源的对接思路
将1688商品列表API与其他数据源对接时,需结合业务场景设计数据流转链路,重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点: 一、核心对接场景与目标 商品数据同步 场景:将1688商品信息…...

sipsak:SIP瑞士军刀!全参数详细教程!Kali Linux教程!
简介 sipsak 是一个面向会话初始协议 (SIP) 应用程序开发人员和管理员的小型命令行工具。它可以用于对 SIP 应用程序和设备进行一些简单的测试。 sipsak 是一款 SIP 压力和诊断实用程序。它通过 sip-uri 向服务器发送 SIP 请求,并检查收到的响应。它以以下模式之一…...
MinIO Docker 部署:仅开放一个端口
MinIO Docker 部署:仅开放一个端口 在实际的服务器部署中,出于安全和管理的考虑,我们可能只能开放一个端口。MinIO 是一个高性能的对象存储服务,支持 Docker 部署,但默认情况下它需要两个端口:一个是 API 端口(用于存储和访问数据),另一个是控制台端口(用于管理界面…...
为什么要创建 Vue 实例
核心原因:Vue 需要一个「控制中心」来驱动整个应用 你可以把 Vue 实例想象成你应用的**「大脑」或「引擎」。它负责协调模板、数据、逻辑和行为,将它们变成一个活的、可交互的应用**。没有这个实例,你的代码只是一堆静态的 HTML、JavaScript 变量和函数,无法「活」起来。 …...
关于uniapp展示PDF的解决方案
在 UniApp 的 H5 环境中使用 pdf-vue3 组件可以实现完整的 PDF 预览功能。以下是详细实现步骤和注意事项: 一、安装依赖 安装 pdf-vue3 和 PDF.js 核心库: npm install pdf-vue3 pdfjs-dist二、基本使用示例 <template><view class"con…...
LCTF液晶可调谐滤波器在多光谱相机捕捉无人机目标检测中的作用
中达瑞和自2005年成立以来,一直在光谱成像领域深度钻研和发展,始终致力于研发高性能、高可靠性的光谱成像相机,为科研院校提供更优的产品和服务。在《低空背景下无人机目标的光谱特征研究及目标检测应用》这篇论文中提到中达瑞和 LCTF 作为多…...

消防一体化安全管控平台:构建消防“一张图”和APP统一管理
在城市的某个角落,一场突如其来的火灾打破了平静。熊熊烈火迅速蔓延,滚滚浓烟弥漫开来,周围群众的生命财产安全受到严重威胁。就在这千钧一发之际,消防救援队伍迅速行动,而豪越科技消防一体化安全管控平台构建的消防“…...