一直用Zaker看新闻,觉得Zaker的设计非常简洁明了。之前一直对Zaker的图片启动页面很喜欢,每次给的图都很漂亮,而且动画的效果也很流畅舒服,所以就找了时间静下心来好好的研究了一番。
最初想了解的时候,在网上搜了一个实现,网上例子只是实现了zaker图片页面的效果,但是实际应该与zaker有很大区别。区别的主要原因是这样的:
1、上面的例子在Layout的子类中用了其他的View,比如ImageView,如果Zaker的实现跟此例相似的话,那么在手机上打开“开发者选项”中的“显示边界布局”,那么就应该能够看到一些子View的边界,但是实际上在zaker的页面上是看不见的,及时是左下角的下载按钮,同样是没有边界的,所以通过这点,我认为Zaker的页面上用到的所有“组件”都不是使用系统的组件,同时这些功能图应该是直接绘制到一个全页面的View上面的。
2、在上面的例子中,动画的部分,我们会发觉例子的动画和zaker的动画有一些差别,特别在顺滑度上,所以我觉得zaker的实现是不同的。
声明:不是认为网上的例子不好,通过这个的例子,我才发现了和zaker实际的不同的地方,然后才考虑换一张方式去实现。感谢例子的作者给我的启发(例子地址附在最下方)。
按照对于Zaker的页面的观察,得到上面两点后,就考虑实际的实现了。首先,已经发现Zaker的实现是通过直接将元素绘制到页面上,那么我们可以创建一个View的子类,用来绘制在这个View中的所有元素,包括左上角的Zaker标志,背景图,左下角的loading图片和下载图片。其次就是动画部分,我们会发现,在实际操作Zaker页面的时候,不同给的操作方式时,显示的动画是有区别的,共有三个不同的动画部分:1、页面划上去部分后释放,自然下落的动画;2、单次点击时的弹跳动画;3、向上快速滑动的时候收起的动画。对于这三个动画,应该是采用不同的加速器,同时,在动画执行过程中,页面上的元素的透明度也是不一样的,鉴于此,我不适用系统自带的动画系统,采用了另外一个开源的动画框架:NineOldAndroid来实现,地址为http://nineoldandroids.com/
首先,我们下载了一张zaker的壁纸图片,发现那张壁纸是960*960的,而在显示的时候,只显示的是中间部分,说明zaker在处理图片的时候,对图片做过矩阵变换,所以我们的View中有Matrix对象来处理图片的矩阵变换。其次,点击图片时,自己发现,会有至少三种动画类型:1、向上快速滑动的收起,2、拖动然后释放,图片自然下落,3、单次点击的操作,那么中和一下,那么我们需要有三种动画类型,然后至少有三个不同的插值器Interpolator。
一,定义动画类型:
/**
* Cover动画类型:默认类型.
*/
private static final int ANIMATOR_TYPE_DEFAULT = 0;
/**
* Cover动画类型:向上收起.
*/
private static final int ANIMATOR_TYPE_FOLD_UP = 1;
/**
* Cover动画类型:普通的向下掉落.
*/
private static final int ANIMATOR_TYPE_NORMAL_FALL = 2;
/**
* Cover动画类型:单击.
*/
private static final int ANIMATOR_TYPE_SINGLE_TOUCH = 3;
二,根据动画类型,设置不同的插值器的动画文件:
private void createCoverAnimator() {
if (mCoverValueAnimator == null) {
float[] defaultPosition = new float[2];
defaultPosition[0] = mMoveDistance;
defaultPosition[1] = 0.0F;
mCoverValueAnimator = ValueAnimator.ofFloat(defaultPosition).setDuration(1000L);
mCoverValueAnimator.addListener(this);
mCoverValueAnimator.addUpdateListener(this);
}
switch (mCoverAnimatorType) {
case ANIMATOR_TYPE_FOLD_UP:// 向上收起的动画
float[] positionsUp = new float[2];
positionsUp[0] = mMoveDistance;
positionsUp[1] = -getHeight();
mCoverValueAnimator.setFloatValues(positionsUp);
mCoverValueAnimator.setDuration(5000L);
mCoverValueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
break;
case ANIMATOR_TYPE_NORMAL_FALL:// 普通下落的动画,采用自定义的弹性加速器
float[] positionsFall = new float[2];
positionsFall[0] = mMoveDistance;
positionsFall[1] = 0.0F;
mCoverValueAnimator.setFloatValues(positionsFall);
mCoverValueAnimator.setDuration(1000L);
mCoverValueAnimator.setInterpolator(new CoverInterpolator());
break;
case ANIMATOR_TYPE_SINGLE_TOUCH:// 单次点击的动画,后续弹起的高度是前一次的一半,最多三次弹起。
float maxHeight = -getHeight() / 15;
float[] positionsSingle = new float[7];
positionsSingle[0] = 0.0F;
positionsSingle[1] = maxHeight;
positionsSingle[2] = 0.0F;
positionsSingle[3] = (maxHeight / 2.0F);
positionsSingle[4] = 0.0F;
positionsSingle[5] = (maxHeight / 4.0F);
positionsSingle[6] = 0.0F;
mCoverValueAnimator.setFloatValues(positionsSingle);
mCoverValueAnimator.setDuration(1000L);
mCoverValueAnimator.setInterpolator(new LinearInterpolator());
default:
break;
}
}
其中的:
private ValueAnimator mCoverValueAnimator;
// 移动的距离.
private float mMoveDistance;
上面代码中用到了一个自定义的CoverInterplator,这个类是参考BounceInterpolator做了一个修改,不过没有达到zaker的效果,但是基本能用,如果要调就可以调整里面的参数,相关代码如下:
@Override
public float getInterpolation(float input) {
float value;
if (input < 0.35) {
value = (float) (8 * input * input);
} else if (input < 0.74) {
value = (float) (0.8 + 7.56 * (input - 0.55D) * (input - 0.55));
} else if (input < 0.9) {
value = (float) (0.94 + 7.56 * (input - 0.812) * (input - 0.82));
} else {
value = (float) (0.98 + 7.56 * (input - 0.95) * (input - 0.95));
}
return value;
}
这样就定义完了动画文件,那么接下来就是上面提到的对图片的矩阵变换了。
PaintFlagsDrawFilter paintFlagsDFilter;
private Drawable mCoverDrawable;
private Matrix mCoverMatrix;
private int mWidth;
private int mHeight;
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
paintFlagsDFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
// 背景图片
mCoverDrawable = getResources().getDrawable(R.drawable.cover_drawable);
mCoverMatrix = getCoverMatrix(mCoverDrawable);
mCoverAnimatorType = ANIMATOR_TYPE_DEFAULT;
}
/**
* 获取图片显示时要做的矩阵变换。
*
* @param drawable
* @return
*/
private Matrix getCoverMatrix(Drawable drawable) {
Matrix matrix;
int intrinsicWidth;
int intrinsicHeight;
float scale;
float transX = 0.0F;
float transY = 0.0F;
matrix = new Matrix();
if (drawable != null) {
// 图片的两个属性一样
intrinsicWidth = drawable.getIntrinsicWidth();
intrinsicHeight = drawable.getIntrinsicHeight();
// 高<=宽
if (intrinsicWidth * mHeight > intrinsicHeight * mWidth) {
scale = (float) mHeight / (float) intrinsicHeight;
transX = 0.5F * (mWidth - scale * intrinsicWidth);
transY = 0.0F;
} else {
scale = (float) mWidth / (float) intrinsicWidth;
transY = 0.5F * (mHeight - scale * intrinsicHeight);
if (intrinsicWidth <= 0) {
drawable.setBounds(0, 0, mWidth, mHeight);
}
}
drawable.setBounds(0, 0, intrinsicWidth, intrinsicHeight);
matrix.setScale(scale, scale);
matrix.postTranslate((int) (transX + 0.5F), (int) (transY + 0.5F));
}
return matrix;
}
对图片做了适当的处理以后,就可以考虑在动画过程中对位置进行处理以实现图片的滑动效果:
@Override
public void onAnimationUpdate(ValueAnimator animation) {
if (animation == mCoverValueAnimator) {
mMoveDistance = ((Float) mCoverValueAnimator.getAnimatedValue()).floatValue();
System.out.println("CoverView distance = " + mMoveDistance);
invalidate();
}
}
@Override
protected void dispatchDraw(Canvas canvas) {
super.dispatchDraw(canvas);
canvas.setDrawFilter(paintFlagsDFilter);
canvas.translate(0.0f, mMoveDistance);
canvas.save();
// 裁剪画布
canvas.clipRect(0, 0, mWidth, mHeight);
if (mCoverDrawable != null) {
// 绘制旧的壁纸
canvas.save();
if (mCoverMatrix != null) {
canvas.concat(mCoverMatrix);
}
mCoverDrawable.draw(canvas);
}
canvas.restore();
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if ((ANIMATOR_TYPE_FOLD_UP == mCoverAnimatorType) || (-1 == mCoverAnimatorType)) {
return true;
}
float x = event.getX();
float y = event.getY();
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
velocityTracker.addMovement(event);
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
if (mCoverValueAnimator != null) {
mCoverValueAnimator.cancel();
mCoverValueAnimator = null;
}
mDownX = x;
mDownY = y;
mLastEventY = y;
break;
case MotionEvent.ACTION_MOVE:
float moverDistance = y - mLastEventY;
if (moverDistance + mMoveDistance <= 0.0F) {// 标明发生了向上移动
mMoveDistance += moverDistance;// 移动的绝对距离,包括上下总和,带符号
}
mLastEventY = y;
invalidate();
break;
case MotionEvent.ACTION_UP:
velocityTracker.computeCurrentVelocity(1000);
if (velocityTracker.getYVelocity() < -800.0F) {
if (mCoverAnimatorType != ANIMATOR_TYPE_FOLD_UP) {
mCoverAnimatorType = ANIMATOR_TYPE_FOLD_UP;
}
if (velocityTracker != null) {
velocityTracker.clear();
velocityTracker.recycle();
velocityTracker = null;
}
} else {
float absX = Math.abs(x - mDownX);
float absY = Math.abs(y - mLastEventY);
if ((absX >= 40.0F) || (absY >= 40.0F) || (mMoveDistance > 0)
|| (mMoveDistance < -40)) {
if (mMoveDistance >= -getHeight() / 3) {
mCoverAnimatorType = ANIMATOR_TYPE_NORMAL_FALL;
} else {
mCoverAnimatorType = ANIMATOR_TYPE_FOLD_UP;
}
} else {
mCoverAnimatorType = ANIMATOR_TYPE_SINGLE_TOUCH;
}
}
startCoverAnimation();
break;
case MotionEvent.ACTION_CANCEL:
break;
default:
break;
}
return true;
}
上面只列出了一些重要的处理代码,其他代码请参考项目源码。
上面代码基本实现了zaker相似的拖动、点击、快速滑动的效果,在一些细节和弹性的地方还是有一些差距。不过,整体思路还是比较明确的。整个例子中,对于其他软件的观察还是花了很长时间,如果单从表面现象其实很难知道对方是怎么实现的,只有自己尝试去做了,抓住关键的地方才是最好的。
最后附上源码:http://pan.baidu.com/s/1o6NXzvk
上面参考的例子地址:http://blog.youkuaiyun.com/manymore13/article/details/12219687