每次向左滑动时,右边的页4个View中,右下角的View速度最慢,视差最明显。
每次向右滑动时,左边的页4个View中,左上角的View速度最慢,视差最明显。
每次滑动时,View中的标题和View右上角的红色标注,都是随着滑动有属于自己的纬度的视差跟透明度的变化。
需要获取到ViewPager中手势滑动的距离,根据具体,根据子View的位置做变化,子View中的成员也要以子View为标准再次变化,产生两层错落的视觉效果。
ViewPager.PageTransformer
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.
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.
public abstract void transformPage (View page, float position)
Apply a property transformation to the given page.
Parameters
page Apply the transformation to this page
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.
要展示不同于默认滑屏效果的动画,实现 ViewPager.PageTransformer 接口,然后把它补充到 view pager 里就行了。这个接口只暴露了一个方法, transformPage() 。每次界面切换,这个方法都会为每个可见page(通常只有一个页面可见)和其即将消失的相邻page面调用一次。例如,第三页可见而且用户向第四页拖动, transformPage() 在操作的各个阶段为第二,三,四页分别调用。
在你 transformPage() 的实现中,基于当前界面上page的 position(position 由 transformPage() 方法的参数给出)决定哪些page需要被动画转换,这样你就能创建自己的动画。
position 参数表示特定page相对于屏幕中的page的位置。它的值在用户滑动页面过程中动态变化。当page填充屏幕,它的值为 0。当page刚从屏幕右边拖走,它的值为 1。如果用户在page 1和page 2间滑动到一半,那么page 1的 position 为 -0.5 并且page 2的 position 为 0.5。根据屏幕上页面的 position,你可以通过 setAlpha() , setTranslationX() 或 setScaleY() 这些方法设定页面属性来自定义滑动动画。
有了符合的API,那剩下的就是实现了。
省略了ViewPager跟Indicator的代码部分了,专注来看下我们的PageTransformer部分跟自定义的VIew部分。
public class StickyPagerTransformer implements PageTransformer {
/*
* 继承PageTransformer
* 重写transformPage方法
* 通过transformPage方法中的v参数,得到滑动的变化值
* 获取transformPage方法中view的childView,设置变化
* */
private RecyclerView recyclerView;
private float speed = 0.3f;
/*在一次手势滑动中,关系到两页的变化
* 向左滑动,关系到未滑动时处于左边的那一页和未滑动时显示的当前页
* 向右滑动,关系到未滑动时处于右边的那一页和未滑动时显示的当前页
* 两个页的变化都会回调到transformPage方法中
* */
@SuppressLint("NewApi")
@Override
public void transformPage(View pager, float v) {
/*
* -1
/*position = 0,pager处于正中心,没有位移*/
recyclerView = (RecyclerView) pager.findViewById(R.id.my_recycler_view);
for (int i = 0; i < recyclerView.getChildCount(); i++) {
ViewTestItem itemView = (ViewTestItem) recyclerView.getChildAt(i);
float weight;
if (v > 0) {
weight = speed * i;
} else {
weight = speed * Math.abs(4 - i);
}
trans(itemView, v, weight);
}
}
private void trans(ViewTestItem viewTestItem, float position, float offset) {
float value = 0;
if (viewTestItem != null) {
float width = viewTestItem.getWidth();
if (position != 0) {
/*当position不为0时,不用区分position>0还是position<0
* 因为每次滑动时,向右滑动位移为正,向左滑动位移为负
* 想让item向右位移,
* 那向左滑动时,偏移量就应该也为负值,只是比位移量绝对值小,才能出现粘滞视差的效果
* 如果向左滑动,偏移量却为正值,那就不是粘滞的效果,而且相反方向的运动了
* 那向右滑动时,偏移量就应该也为正值,只是比位移量绝对值小,才能出现粘滞视差的效果
* 想让item向左位移
* 那向左滑动时,偏移量就应该也为负值,只是比位移量绝对值大,才能出现粘滞视差的效果
* 那向右滑动时,偏移量就应该也为正值,只是比位移量绝对值大,才能出现粘滞视差的效果
* */
value = (position * width * offset);
/*设置item在X方向上位移*/
viewTestItem.setTranslationX(value);
viewTestItem.setOffset(position * offset);
} else if (position == 0) {
viewTestItem.setTranslationX(0);
viewTestItem.setOffset(0);
}
}
}
}
注释已经写的很清楚了,这里就不啰嗦了。
public class ViewTestItem extends LinearLayout {
private TextView textView;
private View rootView;
private TextView sub_title;
private TextView title;
public ViewTestItem(Context context) {
super(context);
initView(context);
}
public ViewTestItem(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
private void initView(Context context){
LayoutInflater inflater = LayoutInflater.from(context);
inflater.inflate(R.layout.view_text,this,true);
textView = (TextView) findViewById(R.id.textView);
rootView = findViewById(R.id.rootView);
sub_title = (TextView) findViewById(R.id.sub_title);
title = (TextView) findViewById(R.id.title);
}
public void setString(String sta){
textView.setText(sta);
title.setText(sta);
sub_title.setText(sta+sta+sta);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
rootView.measure(widthMeasureSpec,widthMeasureSpec);
textView.measure(widthMeasureSpec-200,widthMeasureSpec-200);
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
}
/*设置一定的范围值,超出范围后,使得标题彻底隐藏*/
private float div = 0.2f;
/*水平方向上标题位移的速度*/
private float horiziSpeed = 1f;
/*竖直方向上标题位移的速度*/
private float verSpeed = 0.3f;
public void setOffset(float position) {
if (position > 0) {
if (position > div) {
/*超过范围值,彻底隐藏标题*/
sub_title.setAlpha(0);
title.setAlpha(0);
} else if (position < div && position > 0) {
title.setAlpha((div - position) / div);
/*一级标题偏移速度比为1*/
title.setTranslationX(position * title.getWidth() * horiziSpeed * 1);
/*竖直方向上的偏移*/
title.setTranslationY(-position * title.getHeight() * verSpeed);
sub_title.setAlpha((div - position) / div);
/*二级标题偏移速度比为2,与一级标题偏移量不同,造成错落的感觉*/
sub_title.setTranslationX(position * sub_title.getWidth() * horiziSpeed * 2);
/*竖直方向上的偏移*/
sub_title.setTranslationY(-position * sub_title.getHeight() * verSpeed);
}
} else if (position < 0) {
if (position < -div) {
/*超过范围值,彻底隐藏标题*/
sub_title.setAlpha(0);
title.setAlpha(0);
} else if (position > -div && position < 0) {
title.setAlpha((-div - position) / -div);
title.setTranslationX(position * title.getWidth() * horiziSpeed * 1);
title.setTranslationY(position * title.getWidth() * verSpeed);
sub_title.setAlpha((-div - position) / -div);
sub_title.setTranslationX(position * sub_title.getWidth() * horiziSpeed * 2);
sub_title.setTranslationY(position * sub_title.getWidth() * verSpeed);
}
} else if (position == 0) {
/*当前页完全显示在中心,完全显示标题,偏移量为0*/
sub_title.setAlpha(1);
sub_title.setTranslationX(0);
title.setAlpha(1);
title.setTranslationX(0);
}
}
自定义View中,onMeasure部分代码先不做解释了,只是为了完成圆形的绘制而已,在不同分辨率下能够适配,下一篇文章会详细讲一个自定义ViewGroup的demo。
github地址
希望能帮到你。