孤独不是一种脾性,而是一种无奈。 —— 余秋雨 《文化苦旅》
在开始本文之前我们得知道什么是专场特效,有什么效果。
例1
今天我们就来实现这个效果并且在做两个练习。
对于ViewPager基本使用比较简单这里不再说明了。
开始讲解我们今天的主题。
翻看源码:
我们发现接口PageTransformer。顾名思义实现该接口可以控制ViewPager中item view的滑动效果。
/**
* A PageTransformer is invoked whenever a visible/attached page is scrolled.
* This offers an opportunity for the application to apply a custom transformation
* to the page views using animation properties.
*
* <p>As property animation is only supported as of Android 3.0 and forward,
* setting a PageTransformer on a ViewPager on earlier platform versions will
* be ignored.</p>
*/
public interface PageTransformer {
/**
* Apply a property transformation to the given page.
*
* @param page Apply the transformation to this page
* @param position Position of page relative to the current front-and-center
* position of the pager. 0 is front and center. 1 is one full
* page position to the right, and -1 is one page position to the left.
*/
void transformPage(@NonNull View page, float position);
}
方法也比较简单。
也就是说当ViewPager中的View滑动时,会触发PageTransformer调用transformPage方法。PageTransformer 支持用户通过动画属性自定义页面滑动效果。transformPage(View page, float position)方法中两个参数分别是当前的View和当前Page的“坐标位置”,对于position也是重点不好理解,接下来我将用例子来说明position是什么。
我们来重写ViewPager调用setPageTransformer来时先刚才的效果图。
实现原理:
在左滑过程中当前Page1正常左移,下一页Page2以0.5到1的比例缩放透明度变化直至恢复透明度和缩放比例恢复原状。
向右滑动过程中当前Page2以1到0.5的比例缩放且透明度变化并且位移至ViewPager最左端。上一页Page1正常右移直至覆盖恢复原状。
代码如下(填充ViewPager比较简单省略):
/**
* @description:水平切换 上下切换
* @Author MRyan
* @Date 2020/3/20 14:53
* @Version 1.0
*/
public class CustomReaderViewPager extends ViewPager {
public CustomReaderViewPager(Context context) {
this(context, null);
}
public CustomReaderViewPager(Context context, AttributeSet attrs) {
super(context, attrs);
setReadEffect();
}
@SuppressLint("ClickableViewAccessibility")
private void setReadEffect() {
setPageTransformer(true, new PageTransformer() {//reverseDrawingOrder 返回True前面的视图可以遮住后面的视图
private float MIN_SCALE = 0.5f;//初始
@Override
public void transformPage(View page, float position) {
Log.e("position",page.toString()+position+"");
int pageWidth = page.getWidth();
if (position <= 0) { //不缩放不移动 左滑时当前的View position=(0->-1] ||右滑时前一个View position=(0->-1]
page.setTranslationX(0);
page.setScaleX(1);
page.setScaleY(1);
} else if (position <= 1) { // (0,1] 左滑时后一个View position=[1->0) ||右滑时当前View position=(0->1]
float SCALE = 0.5f - position / 2; //左滑时SCALE从0变为0.5 右滑时从0.5变为0
page.setScaleX(MIN_SCALE + SCALE);
page.setScaleY(MIN_SCALE + SCALE);
page.setAlpha(MIN_SCALE + SCALE);
page.setTranslationX(pageWidth *-position); //左移
/* Log.e("pageWidth",pageWidth *-position+"");*/
} else {//右滑时后一个View position=[1->+无穷) || 左滑时前一个View position=(-无穷->-1]
page.setTranslationX(pageWidth);//移出右端到屏幕外侧
}
}
});
}
}
相信你看到这里已经懵了,position到底是什么代表什么意思。别急我们打印下Log信息来仔细研究。
我们设第一个界面为Page1 ID:255b1d6
设第二个界面为Page2 ID: b68c344
设第三个界面Page3 ID: b4f84e0
当我们左滑ViewPager第一次时 打印Log:
根据ID我们可知Page1的postion变化趋势,我们发现postion的值由0逐渐减少变为-1 记做(0->-1] 不等于0可以等于-1。
根据ID我们可知Page2的postion变化趋势,我们发现postion的值由1逐渐变为0 记做[1,0)
左滑 第一次:
ID: 255b1d6 0->-1 当前View(Page1)
ID: b68c344 1->0 后一个View (Page2)
结束显示Page2
我们左滑一次的过程实际上就是Page1(当前页面)向左滑动直至消失,Page2(下一页)出现直至变为当前页。
再次左滑ViewPager 打印Log:
根据ID我们可知Page3的postion变化趋势,我们发现postion的值由1逐渐变为0 记做[1,0)
根据ID我们可知Page2的postion变化趋势,我们发现postion的值由0逐渐变为-1 记做(0,-1]
根据ID我们可知Page1的postion变化趋势,我们发现postion的值由-1逐渐变为-2 记做[-1,-2]
左滑 第二次:
ID: 255b1d6 -1->-2 前一个View(Page1)
ID: b68c344 0->-1 当前View(Page2)
ID: b4f84e0 1->0 后一个View(Page3)
结束显示Page3
我们再次左滑的过程其实就是Page2作为当前页面向左滑动直至消失,Page3出现直至成为当前页。
同理我们也可以推导出第三次左滑postion的变化趋势:
ID: b68c344 -1->-2 前一个View(Page2)
ID: b4f84e0 0->-1 当前View(Page3)
ID: ? 1->0 后一个View(Page4)
因此我们得到如下结论:
前一个view的position变化 当前view的position变化 后一个view的position变化
-1 ----> 0 0-------->1 1 ----> +∞ (当前view右滑时)
- 1---->-∞ 0 -----> -1 1 ------->0 (当前view左滑时)
由此我们就可以更好的理解postion了。
我们趁热打铁继续做练习。
例2
实现原理:
一屏多个视图滚动,且当前页面缩放比例正常且透明度正常(正常为1),两侧缩放且透明度减少。在滑动的过程中缩放比例和透明度也是变化的。
实现:
首先我们需要在自定义ViewPager和自身布局中添加如下代码:
android:clipChildren="false"
设置屏多个视图滚动 在控件和根ViewGroup一起设置
在Activity中设置左右宽距和预加载数
mViewPage2.setCurrentItem(1); //设置当前显示哪个子元素
mViewPage2.setPageMargin(10);//设置左右相距
mViewPage2.setOffscreenPageLimit(3);//设置预加载的页数 默认为1
接着使我们的自定义ViewPager
代码如下:
/**
* @description:水平切换一屏多个视图滚动
* @Author MRyan
* @Date 2020/3/20 14:53
* @Version 1.0
*/
public class CustomReaderViewPager2 extends ViewPager {
public CustomReaderViewPager2(Context context) {
this(context, null);
}
public CustomReaderViewPager2(Context context, AttributeSet attrs) {
super(context, attrs);
setReadEffect();
}
@SuppressLint("ClickableViewAccessibility")
private void setReadEffect() {
setPageTransformer(true, new PageTransformer() {//reverseDrawingOrder 返回True前面的视图可以遮住后面的视图
private float MIN_SCALE = 0.8f;//初始
private float MIN_ALPHA = 0.3f;
@Override
public void transformPage(View page, float position) {
float scaleFactor = Math.max(MIN_ALPHA, 1 - Math.abs(position));//[1->0.3] or [0.3->1]
if (position < -1 || position > 1) {//前一个或者后一个 大小缩小0.5并且透明度减少0.5
page.setScaleX(MIN_SCALE);
page.setScaleY(MIN_SCALE);
page.setAlpha(scaleFactor);
} else if (position <= 1) {
if (position < 0) {//右滑时前一个View position=[-1->0) ||左滑时当前View position=(0->1]
float scaleX = 1 + 0.2f * position;// 右滑时前一个View scaleX=0.8->1 || 左滑当前View scaleX=1->0.8
page.setScaleX(scaleX);
page.setScaleY(scaleX);
} else { //右滑当前View position=(0->1]
float scaleX = 1 - 0.2f * position;
page.setScaleX(scaleX);
page.setScaleY(scaleX);
}
page.setAlpha(scaleFactor);
}
}
});
}
}
代码中有详细解释,这里就不在说明了。
例3
实现原理:
page.setPivotX(float pivotX); 设置View的X轴支点,影响视图的旋转和缩放效果。
page.setPivotY(float pivotY); 设置View的Y轴支点,影响视图的旋转和缩放效果。
page.setRotationY(float rotationY); 设置View绕Y轴旋转的角度。
代码如下:
/**
* @description:立体旋转
* @Author MRyan
* @Date 2020/3/20 14:53
* @Version 1.0
*/
public class CustomReaderViewPager3 extends ViewPager {
public CustomReaderViewPager3(Context context) {
this(context, null);
}
public CustomReaderViewPager3(Context context, AttributeSet attrs) {
super(context, attrs);
setReadEffect();
}
@SuppressLint("ClickableViewAccessibility")
private void setReadEffect() {
setPageTransformer(true, new PageTransformer() {//reverseDrawingOrder 返回True前面的视图可以遮住后面的视图
private float maxRotate = 90f; // 最大旋转角度
@Override
public void transformPage(View page, float position) {
if (position < -1){
page.setPivotX(page.getWidth());
page.setPivotY(page.getHeight() / 2);
page.setRotationY(-maxRotate);
}else if(position < 0){
page.setPivotX(page.getWidth() * (0.5f + 0.5f * (- position))); // X轴支点坐标变化范围[page.getWidth()/2, page.getWidth()]
page.setPivotY(page.getHeight() / 2);
page.setRotationY(maxRotate * position);
}else if(position <= 1){
page.setPivotX(page.getWidth() * 0.5f * (1 - position)); // // X轴支点坐标变化范围[0, page.getWidth()/2]
page.setPivotY(page.getHeight() / 2);
page.setRotationY(maxRotate * position);
}else{
page.setPivotX(0);
page.setPivotY(page.getHeight() / 2);
page.setRotationY(maxRotate);
}
}
});
}
}
到这里本文就结束了,可以自行扩展实现更多有趣的动画。
附上项目链接:
项目链接