http://blog.csdn.net/coder_nice/article/details/45311715
可能很多人不知道parallax的概念,那就先上一张动态图来看看效果。
有了这张图,大家就应该明白了parallax视差的概念,google paly有这个效果。
相信很多人在看到效果图之后,就想立刻得到链接,好人做到底。
github地址
最初先接到设计师要求的这个效果的时候,我自己先仔细的想了一下,我原本打算用一个FramLayout中包含两层view,下层就是随着滑动出现视差效果的parallax view(其实是滑动速度比较慢),上层就是普通的view层,随着手势的滑动而滑动(跟手势滑动速度一致),然后把上层的滑动速度传递给下层的parallax view,然后把速度值改小一些就好了。可能这也是大多人的想法。
在接触到github上这个开源项目之后,我看了看代码发现不是这样的,远远比这要简单。
这里只做最常用的ParallaxScrollView 具有视差效果的 ScrollView的解析,先来看代码。
ParallaxScrollView 源码
public class ParallaxScrollView extends ScrollView {
private static final int DEFAULT_PARALLAX_VIEWS = 1;
private static final float DEFAULT_INNER_PARALLAX_FACTOR = 1.9F;
private static final float DEFAULT_PARALLAX_FACTOR = 1.9F;
private static final float DEFAULT_ALPHA_FACTOR = -1F;
private int numOfParallaxViews = DEFAULT_PARALLAX_VIEWS;
private float innerParallaxFactor = DEFAULT_PARALLAX_FACTOR;
private float parallaxFactor = DEFAULT_PARALLAX_FACTOR;
private float alphaFactor = DEFAULT_ALPHA_FACTOR;
private ArrayList<ParallaxedView> parallaxedViews = new ArrayList<ParallaxedView>();
public ParallaxScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
public ParallaxScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
public ParallaxScrollView(Context context) {
super(context);
}
protected void init(Context context, AttributeSet attrs) {
TypedArray typeArray = context.obtainStyledAttributes(attrs, R.styleable.ParallaxScroll);
this.parallaxFactor = typeArray.getFloat(R.styleable.ParallaxScroll_parallax_factor, DEFAULT_PARALLAX_FACTOR);
this.alphaFactor = typeArray.getFloat(R.styleable.ParallaxScroll_alpha_factor, DEFAULT_ALPHA_FACTOR);
this.innerParallaxFactor = typeArray.getFloat(R.styleable.ParallaxScroll_inner_parallax_factor, DEFAULT_INNER_PARALLAX_FACTOR);
this.numOfParallaxViews = typeArray.getInt(R.styleable.ParallaxScroll_parallax_views_num, DEFAULT_PARALLAX_VIEWS);
typeArray.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
makeViewsParallax();
}
private void makeViewsParallax() {
if (getChildCount() > 0 && getChildAt(0) instanceof ViewGroup) {
ViewGroup viewsHolder = (ViewGroup) getChildAt(0);
int numOfParallaxViews = Math.min(this.numOfParallaxViews, viewsHolder.getChildCount());
for (int i = 0; i < numOfParallaxViews; i++) {
ParallaxedView parallaxedView = new ScrollViewParallaxedItem(viewsHolder.getChildAt(i));
parallaxedViews.add(parallaxedView);
}
}
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float parallax = parallaxFactor;
float alpha = alphaFactor;
for (ParallaxedView parallaxedView : parallaxedViews) {
parallaxedView.setOffset((float)t / parallax);
parallax *= innerParallaxFactor;
if (alpha != DEFAULT_ALPHA_FACTOR) {
float fixedAlpha = (t <= 0) ? 1 : (100 / ((float)t * alpha));
parallaxedView.setAlpha(fixedAlpha);
alpha /= alphaFactor;
}
parallaxedView.animateNow();
}
}
protected class ScrollViewParallaxedItem extends ParallaxedView {
public ScrollViewParallaxedItem(View view) {
super(view);
}
@Override
protected void translatePreICS(View view, float offset) {
view.offsetTopAndBottom((int)offset - lastOffset);
lastOffset = (int)offset;
}
}
}
ParallaxedView源码
public abstract class ParallaxedView {
static public boolean isAPI11 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB;
protected WeakReference<View> view;
protected int lastOffset;
protected List<Animation> animations;
abstract protected void translatePreICS(View view, float offset);
public ParallaxedView(View view) {
this.lastOffset = 0;
this.animations = new ArrayList<Animation>();
this.view = new WeakReference<View>(view);
}
public boolean is(View v) {
return (v != null && view != null && view.get() != null && view.get().equals(v));
}
@SuppressLint("NewApi")
public void setOffset(float offset) {
View view = this.view.get();
if (view != null)
if (isAPI11) {
view.setTranslationY(offset);
} else {
translatePreICS(view, offset);
}
}
public void setAlpha(float alpha) {
View view = this.view.get();
if (view != null)
if (isAPI11) {
view.setAlpha(alpha);
} else {
alphaPreICS(view, alpha);
}
}
protected synchronized void addAnimation(Animation animation) {
animations.add(animation);
}
protected void alphaPreICS(View view, float alpha) {
addAnimation(new AlphaAnimation(alpha, alpha));
}
protected synchronized void animateNow() {
View view = this.view.get();
if (view != null) {
AnimationSet set = new AnimationSet(true);
for (Animation animation : animations)
if (animation != null)
set.addAnimation(animation);
set.setDuration(0);
set.setFillAfter(true);
view.setAnimation(set);
set.start();
animations.clear();
}
}
public void setView(View view) {
this.view = new WeakReference<View>(view);
}
ParallaxScrollView源码还是很简单,我相信大部分代码不用解释就可以看懂,大概流程就是创建ParallaxScrollView之后,先加载TypedArray中的几个参数(最初的速度衰减率、颜色渐变率、视差中的速度衰减率、视差效果view个数),然后把需要有视差效果的view(使用numOfParallaxViews参数通过getChildAt方法获取的)作为参数创建一个ScrollViewParallaxedItem(具有视差效果方法的view,只是简单封装而已),然后都加到parallaxedViews列表当中,然后在onScrollChanged()方法中循环parallaxedViews列表,把每个需要视差的view都设置这一次手势滑动的视差效果,然后就ok了。
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
float parallax = parallaxFactor;
float alpha = alphaFactor;
for (ParallaxedView parallaxedView : parallaxedViews) {
parallaxedView.setOffset((float)t / parallax);
parallax *= innerParallaxFactor;
if (alpha != DEFAULT_ALPHA_FACTOR) {
float fixedAlpha = (t <= 0) ? 1 : (100 / ((float)t * alpha));
parallaxedView.setAlpha(fixedAlpha);
alpha /= alphaFactor;
}
parallaxedView.animateNow();
}
}
for循环是在有多个视差view时用到的,alpha是渐变用到的,
起到视差作用的是这句话:
parallaxedView.setOffset((float)t / parallax);
然后追着看看这句话的方法体中到底做了什么:
@SuppressLint("NewApi")
public void setOffset(float offset) {
View view = this.view.get();
if (view != null)
if (isAPI11) {
view.setTranslationY(offset);
} else {
translatePreICS(view, offset);
}
}
API大于11时用的是这个方法view.setTranslationY(offset),小于11时用的view.offsetTopAndBottom((int)offset - lastOffset);这个方法,我们只看高版本的view.setTranslationY(offset)方法吧。
然后看看API中是怎么解释view的这个方法:
Sets the vertical location of this view relative to its top position. This effectively positions the object post-layout, in addition to wherever the object's layout placed it.
Related XML Attributes
android:translationY
Parameters
translationY The vertical position of this view relative to its top position, in pixels.
设置竖直方向的相对于view顶部的位置,这个位置在对象调用layout()方法后都会生效的,只有在这个对象的layout()方法中重新放置了它自己时不生效。
作者是想在用户每次滑动屏幕时,在ScrollView 的onScrollChanged()中设置一次视差view距离顶部的位置(如果不设置距离顶部的位置,那就跟普通的ScrollView是一样的了,两个view都跟手势滑动的速度一致),这样就出现了视差view滑动速度慢,普通view滑动快的效果了,根本不需要传递速度一类的数值,只是简单的设置距离顶部的位置,确实简化了问题。
本人只讲了比较简单的ParallaxScrollView的源码,而其他几个稍稍复杂一点的多个视差View的ParallaxListView、ParallaxExpandableListView也是大同小异,理解问题当然应该从简单入手。