Android Folding View(折叠视图、控件)

时间:2022-09-12 12:53:02

版本号:1.0

日期:2014.4.21
版权:© 2014 kince 转载注明出处

非常早之前看过有人求助以下这个效果是怎样实现的,

Android Folding View(折叠视图、控件)

  也就是側滑菜单的一个折叠效果,事实上关于这个效果的实现,谷歌的一名project师已经完毕。并开放源代码到devbytes上面了。

如以下所看到的:

Android Folding View(折叠视图、控件)
  地址是: https://android.googlesource.com/platform/development/+/master/samples/devbytes/graphics/。还有对应的视频讲解:DevBytes: Folding Layout - YouTube,这个想看的话须要fq。执行效果例如以下:
Android Folding View(折叠视图、控件)

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ2ppbnl1NTAx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

  那后来就有人在此基础之上加入了側滑菜单效果,包含DrawerLayout和PaneLayout两种,此项目的github地址是Folding-Android,执行效果例如以下:
1、DrawerLayout
Android Folding View(折叠视图、控件)
  
  2、PaneLayout

Android Folding View(折叠视图、控件)

  以下主要环绕实现原理来展开,以谷歌的devbytes为例。看一下它的自己定义ViewGroup:FoldingLayout。代码例如以下,
/*
* Copyright (C) 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/ package com.example.android.foldinglayout; import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.LinearGradient;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Rect;
import android.graphics.Shader.TileMode;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup; /**
* The folding layout where the number of folds, the anchor point and the
* orientation of the fold can be specified. Each of these parameters can
* be modified individually and updates and resets the fold to a default
* (unfolded) state. The fold factor varies between 0 (completely unfolded
* flat image) to 1.0 (completely folded, non-visible image).
*
* This layout throws an exception if there is more than one child added to the view.
* For more complicated view hierarchy's inside the folding layout, the views should all
* be nested inside 1 parent layout.
*
* This layout folds the contents of its child in real time. By applying matrix
* transformations when drawing to canvas, the contents of the child may change as
* the fold takes place. It is important to note that there are jagged edges about
* the perimeter of the layout as a result of applying transformations to a rectangle.
* This can be avoided by having the child of this layout wrap its content inside a
* 1 pixel transparent border. This will cause an anti-aliasing like effect and smoothen
* out the edges.
*
*/
public class FoldingLayout extends ViewGroup { public static enum Orientation {
VERTICAL,
HORIZONTAL
} private final String FOLDING_VIEW_EXCEPTION_MESSAGE = "Folding Layout can only 1 child at " +
"most"; private final float SHADING_ALPHA = 0.8f;
private final float SHADING_FACTOR = 0.5f;
private final int DEPTH_CONSTANT = 1500;
private final int NUM_OF_POLY_POINTS = 8; private Rect[] mFoldRectArray; private Matrix [] mMatrix; private Orientation mOrientation = Orientation.HORIZONTAL; private float mAnchorFactor = 0;
private float mFoldFactor = 0; private int mNumberOfFolds = 2; private boolean mIsHorizontal = true; private int mOriginalWidth = 0;
private int mOriginalHeight = 0; private float mFoldMaxWidth = 0;
private float mFoldMaxHeight = 0;
private float mFoldDrawWidth = 0;
private float mFoldDrawHeight = 0; private boolean mIsFoldPrepared = false;
private boolean mShouldDraw = true; private Paint mSolidShadow;
private Paint mGradientShadow;
private LinearGradient mShadowLinearGradient;
private Matrix mShadowGradientMatrix; private float [] mSrc;
private float [] mDst; private OnFoldListener mFoldListener; private float mPreviousFoldFactor = 0; private Bitmap mFullBitmap;
private Rect mDstRect; public FoldingLayout(Context context) {
super(context);
} public FoldingLayout(Context context, AttributeSet attrs) {
super(context, attrs);
} public FoldingLayout(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
} @Override
protected boolean addViewInLayout(View child, int index, LayoutParams params,
boolean preventRequestLayout) {
throwCustomException(getChildCount());
boolean returnValue = super.addViewInLayout(child, index, params, preventRequestLayout);
return returnValue;
} @Override
public void addView(View child, int index, LayoutParams params) {
throwCustomException(getChildCount());
super.addView(child, index, params);
} @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
View child = getChildAt(0);
measureChild(child,widthMeasureSpec, heightMeasureSpec);
setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
} @Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
View child = getChildAt(0);
child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
updateFold();
} /**
* The custom exception to be thrown so as to limit the number of views in this
* layout to at most one.
*/
private class NumberOfFoldingLayoutChildrenException extends RuntimeException {
public NumberOfFoldingLayoutChildrenException(String message) {
super(message);
}
} /** Throws an exception if the number of views added to this layout exceeds one.*/
private void throwCustomException (int numOfChildViews) {
if (numOfChildViews == 1) {
throw new NumberOfFoldingLayoutChildrenException(FOLDING_VIEW_EXCEPTION_MESSAGE);
}
} public void setFoldListener(OnFoldListener foldListener) {
mFoldListener = foldListener;
} /**
* Sets the fold factor of the folding view and updates all the corresponding
* matrices and values to account for the new fold factor. Once that is complete,
* it redraws itself with the new fold. */
public void setFoldFactor(float foldFactor) {
if (foldFactor != mFoldFactor) {
mFoldFactor = foldFactor;
calculateMatrices();
invalidate();
}
} public void setOrientation(Orientation orientation) {
if (orientation != mOrientation) {
mOrientation = orientation;
updateFold();
}
} public void setAnchorFactor(float anchorFactor) {
if (anchorFactor != mAnchorFactor) {
mAnchorFactor = anchorFactor;
updateFold();
}
} public void setNumberOfFolds(int numberOfFolds) {
if (numberOfFolds != mNumberOfFolds) {
mNumberOfFolds = numberOfFolds;
updateFold();
}
} public float getAnchorFactor() {
return mAnchorFactor;
} public Orientation getOrientation() {
return mOrientation;
} public float getFoldFactor() {
return mFoldFactor;
} public int getNumberOfFolds() {
return mNumberOfFolds;
} private void updateFold() {
prepareFold(mOrientation, mAnchorFactor, mNumberOfFolds);
calculateMatrices();
invalidate();
} /**
* This method is called in order to update the fold's orientation, anchor
* point and number of folds. This creates the necessary setup in order to
* prepare the layout for a fold with the specified parameters. Some of the
* dimensions required for the folding transformation are also acquired here.
*
* After this method is called, it will be in a completely unfolded state by default.
*/
private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) { mSrc = new float[NUM_OF_POLY_POINTS];
mDst = new float[NUM_OF_POLY_POINTS]; mDstRect = new Rect(); mFoldFactor = 0;
mPreviousFoldFactor = 0; mIsFoldPrepared = false; mSolidShadow = new Paint();
mGradientShadow = new Paint(); mOrientation = orientation;
mIsHorizontal = (orientation == Orientation.HORIZONTAL); if (mIsHorizontal) {
mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.BLACK,
Color.TRANSPARENT, TileMode.CLAMP);
} else {
mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK,
Color.TRANSPARENT, TileMode.CLAMP);
} mGradientShadow.setStyle(Style.FILL);
mGradientShadow.setShader(mShadowLinearGradient);
mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor;
mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth();
mOriginalHeight = getMeasuredHeight(); mFoldRectArray = new Rect[mNumberOfFolds];
mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) {
mMatrix[x] = new Matrix();
} int h = mOriginalHeight;
int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) {
mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mFullBitmap);
getChildAt(0).draw(canvas);
} int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) :
((float) h) /((float) mNumberOfFolds)); /* Loops through the number of folds and segments the full layout into a number
* of smaller equal components. If the number of folds is odd, then one of the
* components will be smaller than all the rest. Note that deltap below handles
* the calculation for an odd number of folds.*/
for (int x = 0; x < mNumberOfFolds; x++) {
if (mIsHorizontal) {
int deltap = (x + 1) * delta > w ? w - x * delta : delta;
mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h);
} else {
int deltap = (x + 1) * delta > h ? h - x * delta : delta;
mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap);
}
} if (mIsHorizontal) {
mFoldMaxHeight = h;
mFoldMaxWidth = delta;
} else {
mFoldMaxHeight = delta;
mFoldMaxWidth = w;
} mIsFoldPrepared = true;
} /*
* Calculates the transformation matrices used to draw each of the separate folding
* segments from this view.
*/
private void calculateMatrices() { mShouldDraw = true; if (!mIsFoldPrepared) {
return;
} /** If the fold factor is 1 than the folding view should not be seen
* and the canvas can be left completely empty. */
if (mFoldFactor == 1) {
mShouldDraw = false;
return;
} if (mFoldFactor == 0 && mPreviousFoldFactor > 0) {
mFoldListener.onEndFold();
} if (mPreviousFoldFactor == 0 && mFoldFactor > 0) {
mFoldListener.onStartFold();
} mPreviousFoldFactor = mFoldFactor; /* Reset all the transformation matrices back to identity before computing
* the new transformation */
for (int x = 0; x < mNumberOfFolds; x++) {
mMatrix[x].reset();
} float cTranslationFactor = 1 - mFoldFactor; float translatedDistance = mIsHorizontal ? mOriginalWidth * cTranslationFactor :
mOriginalHeight * cTranslationFactor; float translatedDistancePerFold = Math.round(translatedDistance / mNumberOfFolds); /* For an odd number of folds, the rounding error may cause the
* translatedDistancePerFold to be grater than the max fold width or height. */
mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold : mFoldMaxWidth;
mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ?
translatedDistancePerFold : mFoldMaxHeight; float translatedDistanceFoldSquared = translatedDistancePerFold * translatedDistancePerFold; /* Calculate the depth of the fold into the screen using pythagorean theorem. */
float depth = mIsHorizontal ? (float)Math.sqrt((double)(mFoldDrawWidth * mFoldDrawWidth -
translatedDistanceFoldSquared)) :
(float)Math.sqrt((double)(mFoldDrawHeight * mFoldDrawHeight -
translatedDistanceFoldSquared)); /* The size of some object is always inversely proportional to the distance
* it is away from the viewpoint. The constant can be varied to to affect the
* amount of perspective. */
float scaleFactor = DEPTH_CONSTANT / (DEPTH_CONSTANT + depth); float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint,
leftScaledPoint; if (mIsHorizontal) {
scaledWidth = mFoldDrawWidth * cTranslationFactor;
scaledHeight = mFoldDrawHeight * scaleFactor;
} else {
scaledWidth = mFoldDrawWidth * scaleFactor;
scaledHeight = mFoldDrawHeight * cTranslationFactor;
} topScaledPoint = (mFoldDrawHeight - scaledHeight) / 2.0f;
bottomScaledPoint = topScaledPoint + scaledHeight; leftScaledPoint = (mFoldDrawWidth - scaledWidth) / 2.0f;
rightScaledPoint = leftScaledPoint + scaledWidth; float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth :
mAnchorFactor * mOriginalHeight; /* The fold along which the anchor point is located. */
float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth) : anchorPoint /
mFoldDrawHeight; mSrc[0] = 0;
mSrc[1] = 0;
mSrc[2] = 0;
mSrc[3] = mFoldDrawHeight;
mSrc[4] = mFoldDrawWidth;
mSrc[5] = 0;
mSrc[6] = mFoldDrawWidth;
mSrc[7] = mFoldDrawHeight; /* Computes the transformation matrix for each fold using the values calculated above. */
for (int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if (mIsHorizontal) {
mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint + (x - midFold) *
scaledWidth : anchorPoint - (midFold - x) * scaledWidth;
mDst[1] = isEven ? 0 : topScaledPoint;
mDst[2] = mDst[0];
mDst[3] = isEven ? mFoldDrawHeight: bottomScaledPoint;
mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint + (x + 1 - midFold)
* scaledWidth : anchorPoint - (midFold - x - 1) * scaledWidth;
mDst[5] = isEven ? topScaledPoint : 0;
mDst[6] = mDst[4];
mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight; } else {
mDst[0] = isEven ? 0 : leftScaledPoint;
mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint + (x - midFold) *
scaledHeight : anchorPoint - (midFold - x) * scaledHeight;
mDst[2] = isEven ? leftScaledPoint: 0;
mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint + (x + 1 -
midFold) * scaledHeight : anchorPoint - (midFold - x - 1) * scaledHeight;
mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint;
mDst[5] = mDst[1];
mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth;
mDst[7] = mDst[3];
} /* Pixel fractions are present for odd number of folds which need to be
* rounded off here.*/
for (int y = 0; y < 8; y ++) {
mDst[y] = Math.round(mDst[y]);
} /* If it so happens that any of the folds have reached a point where
* the width or height of that fold is 0, then nothing needs to be
* drawn onto the canvas because the view is essentially completely
* folded.*/
if (mIsHorizontal) {
if (mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) {
mShouldDraw = false;
return;
}
} else {
if (mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) {
mShouldDraw = false;
return;
}
} /* Sets the shadow and bitmap transformation matrices.*/
mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2);
}
/* The shadows on the folds are split into two parts: Solid shadows and gradients.
* Every other fold has a solid shadow which overlays the whole fold. Similarly,
* the folds in between these alternating folds also have an overlaying shadow.
* However, it is a gradient that takes up part of the fold as opposed to a solid
* shadow overlaying the whole fold.*/ /* Solid shadow paint object. */
int alpha = (int) (mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color.argb(alpha, 0, 0, 0)); if (mIsHorizontal) {
mShadowGradientMatrix.setScale(mFoldDrawWidth, 1);
mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix);
} else {
mShadowGradientMatrix.setScale(1, mFoldDrawHeight);
mShadowLinearGradient.setLocalMatrix(mShadowGradientMatrix);
} mGradientShadow.setAlpha(alpha);
} @Override
protected void dispatchDraw(Canvas canvas) {
/** If prepareFold has not been called or if preparation has not completed yet,
* then no custom drawing will take place so only need to invoke super's
* onDraw and return. */
if (!mIsFoldPrepared || mFoldFactor == 0) {
super.dispatchDraw(canvas);
return;
} if (!mShouldDraw) {
return;
} Rect src;
/* Draws the bitmaps and shadows on the canvas with the appropriate transformations. */
for (int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x];
/* The canvas is saved and restored for every individual fold*/
canvas.save(); /* Concatenates the canvas with the transformation matrix for the
* the segment of the view corresponding to the actual image being
* displayed. */
canvas.concat(mMatrix[x]);
if (FoldingLayoutActivity.IS_JBMR2) {
mDstRect.set(0, 0, src.width(), src.height());
canvas.drawBitmap(mFullBitmap, src, mDstRect, null);
} else {
/* The same transformation matrix is used for both the shadow and the image
* segment. The canvas is clipped to account for the size of each fold and
* is translated so they are drawn in the right place. The shadow is then drawn on
* top of the different folds using the sametransformation matrix.*/
canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top); if (mIsHorizontal) {
canvas.translate(-src.left, 0);
} else {
canvas.translate(0, -src.top);
} super.dispatchDraw(canvas); if (mIsHorizontal) {
canvas.translate(src.left, 0);
} else {
canvas.translate(0, src.top);
}
}
/* Draws the shadows corresponding to this specific fold. */
if (x % 2 == 0) {
canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mSolidShadow);
} else {
canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight, mGradientShadow);
}
canvas.restore();
}
} }

 在变量中。看到使用了Rect、Matrix、LinearGradient、Bitmap等graphics类,主要是负责画图。当然还有最基础的canvas和paint。在FoldingLayout中。大体能够分为三个部分,分别以三个方法为代表:
1、prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds)
     初始化准备。为变量赋值
2、calculateMatrices()
     计算转换矩阵,用于绘制每个单独的折叠部分
3、dispatchDraw(Canvas canvas)
      重载ViewGroup的方法,绘制图形
  然后进入各个方法。在calculateMatrices()方法的最后,有一个以下的方法: 
  /* Sets the shadow and bitmap transformation matrices.*/

mMatrix[x].setPolyToPoly(mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2);

  这种方法是Matrix类里面的,那它是做什么用的呢?首先肯定是对矩阵进行变换,那会是什么效果呢?结合折叠的效果来说一下。在api中,这个类是这样描写叙述的,

Android Folding View(折叠视图、控件)

  这种方法就是设置指定的矩阵的src点映射到指定的dst点。这些点代表一个float数组,每一点由两个float值组成。比方一个矩形,用数组能够这样表示:[x0,y0,x1,y1,x2,y2,x3,y3],图示:
Android Folding View(折叠视图、控件)
  再来回想一下折叠效果是怎样实现的。能够把它拆分成例如以下图所看到的:
Android Folding View(折叠视图、控件)

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvd2FuZ2ppbnl1NTAx/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="" />

  对于折叠一次的图形,能够把其分成两个部分,也就是先对图形进行分割,之后再对每个图形做拉伸变换,所以非常easy想到使用Matrix类实现。用的正是setPolyToPoly()方法。以下以一个实例说一下setPolyToPoly()方法的使用,代码例如以下:
package com.example.demo;

import android.support.v7.app.ActionBarActivity;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.os.Bundle;
import android.view.View; public class MainActivity extends ActionBarActivity { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(new MyView(this)); } class MyView extends View {
private Bitmap bitmap;
private Matrix mMatrix;
public MyView(Context context) {
super(context);
bitmap = BitmapFactory.decodeResource(context.getResources(),
R.drawable.a);
mMatrix = new Matrix();
} @Override
protected void onDraw(Canvas canvas) {
float[] src = new float[] { 0, 0, // 左上
bitmap.getWidth(), 0,// 右上
bitmap.getWidth(), bitmap.getHeight(),// 右下
0, bitmap.getHeight() };// 左下
float[] dst = new float[] { 0, 0, bitmap.getWidth(), 50,
bitmap.getWidth(), bitmap.getHeight() - 50, 0,
bitmap.getHeight() };
mMatrix.setPolyToPoly(src, 0, dst, 0, src.length >> 1);
canvas.drawBitmap(bitmap, mMatrix, null);
canvas.drawBitmap(bitmap, 0, bitmap.getHeight(), null);
}
}
}
  在数组中,设置的矩形四个顶点的坐标,在目的矩形的坐标中,改变了右上以及右下的坐标,使其分别向下和向上移动50像素。所以效果例如以下:
Android Folding View(折叠视图、控件)
 能够看到上图正是一个折叠效果(看图别晕。大自然是不是真的非常奇妙)。

FoldingLayout就是在这个基础之上延伸而来的,接下来就看看它是怎样具体实现的。

1、首先看一下  方法,
  /**
* This method is called in order to update the fold's orientation, anchor
* point and number of folds. This creates the necessary setup in order to
* prepare the layout for a fold with the specified parameters. Some of the
* dimensions required for the folding transformation are also acquired here.
*
* After this method is called, it will be in a completely unfolded state by default.
*/
private void prepareFold(Orientation orientation, float anchorFactor, int numberOfFolds) {
//setPolyToPoly方法參数
mSrc = new float[NUM_OF_POLY_POINTS];
mDst = new float[NUM_OF_POLY_POINTS];
//
mDstRect = new Rect(); mFoldFactor = 0;
mPreviousFoldFactor = 0;
//是否折叠
mIsFoldPrepared = false;
//画笔
mSolidShadow = new Paint();
mGradientShadow = new Paint();
//方向
mOrientation = orientation;
mIsHorizontal = (orientation == Orientation.HORIZONTAL);
//折叠线附近的效果
if (mIsHorizontal) {
mShadowLinearGradient = new LinearGradient(0, 0, SHADING_FACTOR, 0, Color.RED,
Color.TRANSPARENT, TileMode.CLAMP);
} else {
mShadowLinearGradient = new LinearGradient(0, 0, 0, SHADING_FACTOR, Color.BLACK,
Color.TRANSPARENT, TileMode.CLAMP);
}
//折叠线附近的效果
mGradientShadow.setStyle(Style.FILL);
mGradientShadow.setShader(mShadowLinearGradient);
mShadowGradientMatrix = new Matrix(); mAnchorFactor = anchorFactor;
mNumberOfFolds = numberOfFolds; mOriginalWidth = getMeasuredWidth();
Log.i(" mOriginalWidth", mOriginalWidth+"");
mOriginalHeight = getMeasuredHeight();
Log.i(" mOriginalHeight", mOriginalHeight+""); mFoldRectArray = new Rect[mNumberOfFolds];
mMatrix = new Matrix [mNumberOfFolds]; for (int x = 0; x < mNumberOfFolds; x++) {
mMatrix[x] = new Matrix();
} int h = mOriginalHeight;
int w = mOriginalWidth; if (FoldingLayoutActivity.IS_JBMR2) {
mFullBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(mFullBitmap);
getChildAt(0).draw(canvas);
Log.i("IS_JBMR2", FoldingLayoutActivity.IS_JBMR2+"");
}
//折叠成几个部分,每隔部分的宽
int delta = Math.round(mIsHorizontal ? ((float) w) / ((float) mNumberOfFolds) :
((float) h) /((float) mNumberOfFolds));
Log.i("delta", delta+"");
/* Loops through the number of folds and segments the full layout into a number
* of smaller equal components. If the number of folds is odd, then one of the
* components will be smaller than all the rest. Note that deltap below handles
* the calculation for an odd number of folds.*/
for (int x = 0; x < mNumberOfFolds; x++) {
if (mIsHorizontal) {
int deltap = (x + 1) * delta > w ? w - x * delta : delta;
Log.i("deltap", x+"x"+deltap+"");
mFoldRectArray[x] = new Rect(x * delta, 0, x * delta + deltap, h);
} else {
int deltap = (x + 1) * delta > h ? h - x * delta : delta;
mFoldRectArray[x] = new Rect(0, x * delta, w, x * delta + deltap);
}
} if (mIsHorizontal) {
mFoldMaxHeight = h;
mFoldMaxWidth = delta;
} else {
mFoldMaxHeight = delta;
mFoldMaxWidth = w;
}
//设置已经准备就绪
mIsFoldPrepared = true;
}

这种方法从名字就能够看出来是用于数据初始化。開始时初始化setPolyToPoly方法參数数组,然后设置了画笔、折叠方向、折叠线附近的折叠效果灯。

最重要的就是初始化这两个參数:

  private Rect[] mFoldRectArray:
  private Matrix [] mMatrix:
 他们分别依据折叠的数目来赋值。 
2、calculateMatrices()
  这种方法是计算的核心部分。
  /*
* Calculates the transformation matrices used to draw each of the separate
* folding segments from this view.
*/
private void calculateMatrices() {
// 设置能够画图了
mShouldDraw = true;
// 没有准备好 返回
if (! mIsFoldPrepared) {
return;
} /**
* If the fold factor is 1 than the folding view should not be seen and
* the canvas can be left completely empty.
*/
if ( mFoldFactor == 1) {
// 已经全然折叠了不须要绘制了
mShouldDraw = false;
return;
}
// 折叠结束
if ( mFoldFactor == 0 && mPreviousFoldFactor > 0) {
mFoldListener.onEndFold();
}
// 折叠開始
if ( mPreviousFoldFactor == 0 && mFoldFactor > 0) {
mFoldListener.onStartFold();
}
// 已经折叠了,将当前的折叠因子赋值到已经折叠过的折叠因子
mPreviousFoldFactor = mFoldFactor; /*
* Reset all the transformation matrices back to identity before
* computing the new transformation
*/
for ( int x = 0; x < mNumberOfFolds; x++) {
mMatrix[x].reset();
}
//依据折叠因子来得到相对系数
float cTranslationFactor = 1 - mFoldFactor;
Log. i("cTranslationFactor" , cTranslationFactor + "" );
//计算总体变换的距离
float translatedDistance = mIsHorizontal ? mOriginalWidth
* cTranslationFactor : mOriginalHeight * cTranslationFactor;
Log. i("translatedDistance" , translatedDistance + "" );
//得到每个折叠视图变换的移动距离
float translatedDistancePerFold = Math.round(translatedDistance
/ mNumberOfFolds);
Log. i("translatedDistancePerFold" , translatedDistancePerFold + "" );
/*
* For an odd number of folds, the rounding error may cause the
* translatedDistancePerFold to be grater than the max fold width or
* height.
*/
Log. i("mFoldMaxWidth" , mFoldMaxWidth + "" );
//计算出被画视图的宽度
mFoldDrawWidth = mFoldMaxWidth < translatedDistancePerFold ? translatedDistancePerFold
: mFoldMaxWidth;
Log. i("mFoldDrawWidth" , mFoldDrawWidth + "" );
//计算出被画视图的高度
mFoldDrawHeight = mFoldMaxHeight < translatedDistancePerFold ? translatedDistancePerFold
: mFoldMaxHeight;
Log. i("mFoldDrawHeight" , mFoldDrawHeight + "" );
float translatedDistanceFoldSquared = translatedDistancePerFold
* translatedDistancePerFold;
Log. i("translatedDistanceFoldSquared" , translatedDistanceFoldSquared
+ "");
/*
* Calculate the depth of the fold into the screen using pythagorean
* theorem.
* 依据勾股定理计算出折叠效果的深度
*/
float depth = mIsHorizontal ? ( float) Math
. sqrt((double) (mFoldDrawWidth * mFoldDrawWidth - translatedDistanceFoldSquared))
: ( float) Math
. sqrt((double) (mFoldDrawHeight * mFoldDrawHeight - translatedDistanceFoldSquared));
Log. i("depth" , depth + "" );
/*
* The size of some object is always inversely proportional to the
* distance it is away from the viewpoint. The constant can be varied to
* to affect the amount of perspective.
* 这个地方又使用了一个因子,它是依据深度depth来得到的。主要也是用来计算相对的值,体如今高度上。
*/
float scaleFactor = DEPTH_CONSTANT / ( DEPTH_CONSTANT + depth);
Log. i("scaleFactor" , scaleFactor + "" );
float scaledWidth, scaledHeight, bottomScaledPoint, topScaledPoint, rightScaledPoint, leftScaledPoint; if ( mIsHorizontal) {
//得到变化后的宽度,事实上和translatedDistance相差无几
scaledWidth = mFoldDrawWidth * cTranslationFactor;
Log. i("scaledWidth" , scaledWidth + "" );
//得到变化后的高度
scaledHeight = mFoldDrawHeight * scaleFactor;
Log. i("scaledHeight" , scaledHeight + "" );
} else {
scaledWidth = mFoldDrawWidth * scaleFactor;
scaledHeight = mFoldDrawHeight * cTranslationFactor;
}
//依据变化后的高度和宽度来计算出坐标点
topScaledPoint = ( mFoldDrawHeight - scaledHeight) / 2.0f;
bottomScaledPoint = topScaledPoint + scaledHeight;
leftScaledPoint = ( mFoldDrawWidth - scaledWidth) / 2.0f;
rightScaledPoint = leftScaledPoint + scaledWidth;
//锚点计算
float anchorPoint = mIsHorizontal ? mAnchorFactor * mOriginalWidth
: mAnchorFactor * mOriginalHeight;
Log. i("anchorPoint" , anchorPoint + "" );
/* The fold along which the anchor point is located. */
float midFold = mIsHorizontal ? (anchorPoint / mFoldDrawWidth)
: anchorPoint / mFoldDrawHeight;
//一下的坐标大家一定非常熟悉,就是之前介绍的方法的參数 表示单个折叠视图
mSrc[0] = 0;
mSrc[1] = 0;
mSrc[2] = 0;
mSrc[3] = mFoldDrawHeight;
mSrc[4] = mFoldDrawWidth;
mSrc[5] = 0;
mSrc[6] = mFoldDrawWidth;
mSrc[7] = mFoldDrawHeight; /*
* Computes the transformation matrix for each fold using the values
* calculated above.
*/
for ( int x = 0; x < mNumberOfFolds; x++) { boolean isEven = (x % 2 == 0); if ( mIsHorizontal) {
mDst[0] = (anchorPoint > x * mFoldDrawWidth) ? anchorPoint
+ (x - midFold) * scaledWidth : anchorPoint
- (midFold - x) * scaledWidth;
Log. i("mDst[0]" , mDst [0] + "" );
mDst[1] = isEven ? 0 : topScaledPoint;
Log. i("mDst[1]" , mDst [1] + "" );
mDst[2] = mDst[0];
Log. i("mDst[2]" , mDst [2] + "" );
mDst[3] = isEven ? mFoldDrawHeight : bottomScaledPoint;
Log. i("mDst[3]" , mDst [4] + "" );
mDst[4] = (anchorPoint > (x + 1) * mFoldDrawWidth) ? anchorPoint
+ (x + 1 - midFold) * scaledWidth
: anchorPoint - (midFold - x - 1) * scaledWidth;
Log. i("mDst[4]" , mDst [4] + "" );
mDst[5] = isEven ? topScaledPoint : 0;
Log. i("mDst[5]" , mDst [5] + "" );
mDst[6] = mDst[4];
Log. i("mDst[6]" , mDst [6] + "" );
mDst[7] = isEven ? bottomScaledPoint : mFoldDrawHeight;
Log. i("mDst[7]" , mDst [7] + "" );
} else {
mDst[0] = isEven ? 0 : leftScaledPoint;
mDst[1] = (anchorPoint > x * mFoldDrawHeight) ? anchorPoint
+ (x - midFold) * scaledHeight : anchorPoint
- (midFold - x) * scaledHeight;
mDst[2] = isEven ? leftScaledPoint : 0;
mDst[3] = (anchorPoint > (x + 1) * mFoldDrawHeight) ? anchorPoint
+ (x + 1 - midFold) * scaledHeight
: anchorPoint - (midFold - x - 1) * scaledHeight;
mDst[4] = isEven ? mFoldDrawWidth : rightScaledPoint;
mDst[5] = mDst[1];
mDst[6] = isEven ? rightScaledPoint : mFoldDrawWidth;
mDst[7] = mDst[3];
} /*
* Pixel fractions are present for odd number of folds which need to
* be rounded off here.
*/
for ( int y = 0; y < 8; y++) {
mDst[y] = Math. round(mDst[y]);
} /*
* If it so happens that any of the folds have reached a point where
* the width or height of that fold is 0, then nothing needs to be
* drawn onto the canvas because the view is essentially completely
* folded.
*/
if ( mIsHorizontal) {
if ( mDst[4] <= mDst[0] || mDst[6] <= mDst[2]) {
mShouldDraw = false;
return;
}
} else {
if ( mDst[3] <= mDst[1] || mDst[7] <= mDst[5]) {
mShouldDraw = false;
return;
}
} /* Sets the shadow and bitmap transformation matrices. */
mMatrix[x].setPolyToPoly( mSrc, 0, mDst, 0, NUM_OF_POLY_POINTS / 2);
}
/*
* The shadows on the folds are split into two parts: Solid shadows and
* gradients. Every other fold has a solid shadow which overlays the
* whole fold. Similarly, the folds in between these alternating folds
* also have an overlaying shadow. However, it is a gradient that takes
* up part of the fold as opposed to a solid shadow overlaying the whole
* fold.
*/ /* Solid shadow paint object. */
int alpha = ( int) ( mFoldFactor * 255 * SHADING_ALPHA); mSolidShadow.setColor(Color. argb(alpha, 0, 0, 0)); if ( mIsHorizontal) {
mShadowGradientMatrix.setScale( mFoldDrawWidth, 1);
mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix);
} else {
mShadowGradientMatrix.setScale(1, mFoldDrawHeight);
mShadowLinearGradient.setLocalMatrix( mShadowGradientMatrix);
} mGradientShadow.setAlpha(alpha);
}

首先依据mFoldFactor得到cTranslationFactor。mFoldFactor是依据手指滑动的距离得到的。是一个非常小的数值,假设滑动距离小的话每次仅仅有0.0几的变化。所以得到的cTranslationFactor就像是一个百分比一样,来计算相对的数值。然后依据cTranslationFactor计算出translatedDistance、mFoldDrawWidth、depth等。

具体说明已经在代码中凝视了,在此就不细说了,如图:

Android Folding View(折叠视图、控件)
3、dispatchDraw(Canvas canvas)
     最后就是把图形绘制出来。这种方法在之前的自己定义ViewGroup博客中已经提到过。假设不重写这种方法。那上面的工作室无法显示出来的。

看代码:

@Override
protected void dispatchDraw(Canvas canvas) {
/**
* If prepareFold has not been called or if preparation has not
* completed yet, then no custom drawing will take place so only need to
* invoke super's onDraw and return.
*/
if (! mIsFoldPrepared || mFoldFactor == 0) {
super.dispatchDraw(canvas);
return;
} if (! mShouldDraw) {
return;
} Rect src;
/*
* Draws the bitmaps and shadows on the canvas with the appropriate
* transformations.
*/
for ( int x = 0; x < mNumberOfFolds; x++) { src = mFoldRectArray[x];
/* The canvas is saved and restored for every individual fold */
canvas.save(); /*
* Concatenates the canvas with the transformation matrix for the
* the segment of the view corresponding to the actual image being
* displayed.
*/
canvas.concat( mMatrix[x]);
if (FoldingLayoutActivity. IS_JBMR2) {
mDstRect.set(0, 0, src.width(), src.height());
canvas.drawBitmap( mFullBitmap, src, mDstRect, null);
} else {
/*
* The same transformation matrix is used for both the shadow
* and the image segment. The canvas is clipped to account for
* the size of each fold and is translated so they are drawn in
* the right place. The shadow is then drawn on top of the
* different folds using the sametransformation matrix.
*/
canvas.clipRect(0, 0, src. right - src.left, src. bottom
- src. top); if ( mIsHorizontal) {
canvas.translate(-src. left, 0);
} else {
canvas.translate(0, -src. top);
} super.dispatchDraw(canvas); if ( mIsHorizontal) {
canvas.translate(src. left, 0);
} else {
canvas.translate(0, src. top);
}
}
/* Draws the shadows corresponding to this specific fold. */
if (x % 2 == 0) {
canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight,
mSolidShadow);
} else {
canvas.drawRect(0, 0, mFoldDrawWidth, mFoldDrawHeight,
mGradientShadow);
}
canvas.restore();
}
}
  事实上这种方法最基本的两个地方就是,canvas.concat( mMatrix[x])和canvas.clipRect(0, 0, src. right - src.left, src. bottom- src. top)。

首先在最外层是一个for循环。这个循环依据折叠的数目迭代,本例中这个数值为2,所以将迭代两次。接着从mFoldRectArray数组中取出一个Rect赋值给src。然后就调用canvas.concat( mMatrix[x])方法了。我们知道,这个Matrix刚才已经使用setPolyToPoly方法进行变换了。那怎样将变换后的矩阵的效果应用到视图上呢,就是使用canvas.concat( mMatrix[x])方法,假设没有这种方法。那就不会看到折叠的效果。然后就是canvas.clipRect(0, 0, src.right - src.left, src.bottom - src.top)方法了,这种方法用于对图形进行裁剪。显示出每个折叠的视图。须要注意的地方是这种方法要在兴许画画操作之前进行剪切才干生效,这也就是为什么这种方法在super.dispatchDraw(canvas)之前调用。接着还须要对视图进行平移操作,canvas.translate(-src.left, 0)。不然的话会显示两个同样的图片。