AndroidFoldingView(折叠视图、控件)

很早之前看过有人求助以下这个效果是如何实现的,

也就是侧滑菜单的一个折叠效果,其实关于这个效果的实现,谷歌的一名工程师已经完成,并开放源码到devbytes上面了。如下面所示:

地址是: https://android.googlesource.com/platform/development/+/master/samples/devbytes/graphics/,还有相应的视频解说:DevBytes: Folding Layout - YouTube,这个想看的话需要fq。运行效果如下:

那后来就有人在此基础之上添加了侧滑菜单效果,包括DrawerLayout和PaneLayout两种,此项目的github地址是Folding-Android,运行效果如下:

下面主要围绕实现原理来展开,以谷歌的devbytes为例,看一下它的自定义ViewGroup:FoldingLayout。代码如下,
/*
* Copyright (C) 2013 The <a href="http://www.it165.net/pro/ydad/" target="_blank" class="keylink">Android</a> 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中,这个类是这样描述的,

这个方法就是设置指定的矩阵的src点映射到指定的dst点。这些点代表一个float数组,每一点由两个float值组成。比如一个矩形,用数组可以这样表示:
[x0,y0,x1,y1,x2,y2,x3,y3],图示:
再来回顾一下折叠效果是如何实现的,可以把它拆分成如下图所示:

对于折叠一次的图形,可以把其分成两个部分,也就是先对图形进行切割,之后再对每个图形做拉伸变换,所以很容易想到使用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像素。所以效果如下:

可以看到上图正是一个折叠效果(看图别晕,大自然是不是真的很神奇)。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等。详细说明已经在代码中注释了,在此就不细说了,如图:

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),不然的话会显示两个相同的图片。

猜你喜欢

转载自iaiai.iteye.com/blog/2173591