上一篇文章,我们利用Matrix的setPolyToPoly来实现图片的3D旋转,这一次,我们来实现一个漂亮一点的效果,让一张图片像折扇一样可以折叠起来。
具体的效果如下
这个效果是我有一次在DevBytes上看到的一个视频,由Google Android Team的员工介绍的一个效果,不过它们是把这个做成了一个可重复利用的自定义ViewGroup,我当时看了,发现这效果真是太帅了。于是自己就琢磨着应该怎么实现,不过最后,还是跑去GitHub下了它的一份代码,参考着,争取把里面主要的逻辑给理清了,给大家介绍一下。
其实我之所以写前面那篇文章《Android学习小Demo(5)结合Matrix跟Porperty Animatin 实现推拉门效果》,目的只是为了先让大家先熟悉一下matrix的setPolyToPoly方法,因为这个效果的实现就是利用matrix的这个方法的。
下面我们结合一下代码来讲一下思路,然后在最后,大家再下载源代码去学习吧。
在主Activity上,有一个自定义的FoldingView,主要是实现折叠效果的自定义View,下面有一个输入框,用户可以输入数字,表明是要将这张图片分成多少部分,然后点击按钮,开始动画。
1)我们先看一下主Activity中的代码:
public class MainActivity extends Activity { private ValueAnimator valueAnimator = ValueAnimator.ofFloat(0f,1f); private PolyToPolyView polyView; @Override protected void onCreate(Bundle savedInstanceState) { ... valueAnimator.addUpdateListener(new AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator arg0) { float rotateFactor = (Float)arg0.getAnimatedValue(); polyView.setRotateFactor(rotateFactor); } }); ... btnRotate.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { valueAnimator.start(); } }); } ... }
这代码其实跟我们前面一篇文章的代码是一样的:
a)定义一个ValueAnimator,在其AnimatorUpdateListener中设置自定义View的旋转因子,并设置图片折叠的份数
b)点击按钮,开始动画。
2)在自定义View中,
a)我们会从资源中获取一张图片,然后根据Activity中输入框的值,将图片分成等宽的长方形,如下:
代码如下:
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.photo1); bitmapWidth = bitmap.getWidth(); bitmapHeight = bitmap.getHeight(); widthPerFold = Math.round((float)bitmapWidth /(float)folds); heightPerFold = bitmapHeight; for (int i = 0; i < folds; i++) { rects[i] = new Rect(i * widthPerFold, 0, i* widthPerFold + widthPerFold, heightPerFold); } for(int i=0;i<folds;i++){ matrices[i] = new Matrix(); }因为分成的第一份都要实现一个往后推的效果,所以分成多少份,对应的我们也要为各个长方形准备对应的matrix来实现变化,所以在下面也会同时new出一个matrix。
2)分成相同的等份之后,我们就要考虑如何为每一个长方形设置变化的矩阵了。
2.1)分析拆分出来的矩形区域及折叠时候的效果,可以发现,偶数位(从0开始)的矩形是右边的那两个角往后推,而奇数位的矩形则刚好相反,当偶数位的矩形在往后推的时候,奇数位的矩形则相对着其也在往后推,并且在往后推的同时,每个矩形的宽度缩小的比例也是一致的。所以根据这几点,我们可以先算出一些公用的参数变化,比如每个矩形旋转的比例,平移的距离等等,下面看一下代码:
translateFactor = 1 - foldFactor; translateWidth = bitmapWidth * translateFactor; translateWidthPerFold = Math.round(translateWidth / folds); foldDrawWidth = widthPerFold < translateWidthPerFold ? translateWidthPerFold : widthPerFold; foldDrawHeight = heightPerFold; float translateWidthPerfoldsquare = translateWidthPerFold * translateWidthPerFold; float deepth = (float)Math.sqrt(foldDrawWidth * foldDrawWidth - translateWidthPerfoldsquare); scaleFactor = DEPTH_CONSTANT / (DEPTH_CONSTANT + deepth); float scaleWidth = foldDrawWidth * translateFactor; // from 1 to 0, means width becomes small float scaleHeight = foldDrawHeight * scaleFactor; float topScaleHeightPoint = (foldDrawHeight - scaleHeight) / 2.f; float bottomScaleHeightPoint = topScaleHeightPoint + scaleHeight; srcPoly[0] = 0; srcPoly[1] = 0; srcPoly[2] = 0; srcPoly[3] = foldDrawHeight; srcPoly[4] = foldDrawWidth; srcPoly[5] = 0; srcPoly[6] = foldDrawWidth; srcPoly[7] = foldDrawHeight; for (int i = 0; i < folds; i++) { matrices[i].reset(); boolean isEven = (i % 2 == 0); dstPoly[0] = i * scaleWidth; dstPoly[1] = isEven ? 0 : topScaleHeightPoint; dstPoly[2] = dstPoly[0]; dstPoly[3] = isEven ? foldDrawHeight : bottomScaleHeightPoint; dstPoly[4] = (i + 1) * scaleWidth; dstPoly[5] = isEven ? topScaleHeightPoint : 0; dstPoly[6] = dstPoly[4]; dstPoly[7] = isEven ? bottomScaleHeightPoint : foldDrawHeight; if(dstPoly[4] <= dstPoly[0] || dstPoly[6] <= dstPoly[2]){ shouldDraw = false; return; } matrices[i].setPolyToPoly(srcPoly, 0, dstPoly, 0, POLY_POINTS / 2); }
大家如果仔细看一下,会发现前面计算缩放比例及深度变化等,都跟前面的文章是一样的,关键是后面设置坐标数组的时候,会根据奇偶来判断。
2.2)在数组中,前面4位,分别是左上角,左下角的x,y座标,后面下位,则是右上角和右下角的坐标。对于偶数位矩形来说,在变化的过程中,其x坐标会根据平移和缩放的比例慢慢缩小并往左移,而左边的y坐标则是保持不变的,因为它们是这个矩形的轴,而右边的y坐标,则会根据缩放比例变小,而对于奇数位来说,则刚好相反。
3)第三步,分别利用canvas的save和restore函数保存各个矩形自己的matrix变化,利用clipRect剪裁出各个矩形区域,交将图片的对应的部分画到canvas上。
4)加上一些阴影和渐变交果,让其看起来是有纵容变化的感觉。
int alpha = (int) (foldFactor * 255 * SHADOW_APLHA); paintSolid.setColor(Color.argb(alpha, 0, 0, 0)); matrixShadowGradient.setScale(foldDrawWidth, 1); linearGradientShadow.setLocalMatrix(matrixShadowGradient); paintGradientShadow.setAlpha(alpha); ... if (i % 2 == 0) { canvas.drawRect(0, 0, foldDrawWidth, foldDrawHeight, paintSolid); } else { canvas.drawRect(0, 0, foldDrawWidth, foldDrawHeight, paintGradientShadow); }其实总的实现很简单,关键是理解了Matrix的setPolyToPoly方法中对于点映射的变化。