欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android中FoldingLayout折叠布局的用法及实战全攻略

程序员文章站 2024-02-23 18:27:22
一、概述 无意中翻到的foldinglayout的介绍的博客,以及github地址。感觉很nice呀,于是花了点时间研究以及编写,本篇博客将带大家从最基本的原理分析,一步...

一、概述
无意中翻到的foldinglayout的介绍的博客,以及github地址。感觉很nice呀,于是花了点时间研究以及编写,本篇博客将带大家从最基本的原理分析,一步一步的实现我们的foldinglayout,当然了,如果你能力过硬,可以直接下载github上的代码进行学习。
博客基本分为以下几个部分:
1、matrix的setpolytopoly使用
2、在图片上使用渐变和阴影
3、初步的foldinglayout的实现,完成图片的折叠显示(可控制折叠次数、包含阴影的绘制)
4、引入手势,手指可以可以foldinglayout的折叠
5、结合drawerlayout实现折叠式侧滑
6、结合slidingpanelayout实现折叠式侧滑
ok,贴下部分的效果图:

Android中FoldingLayout折叠布局的用法及实战全攻略

改图对应上述3,妹子不错吧~

Android中FoldingLayout折叠布局的用法及实战全攻略

ok,对应上述4.

Android中FoldingLayout折叠布局的用法及实战全攻略

对应上述5。
ok,挑选了部分图,不然太占篇幅了。
那么接下来,我们就按照顺序往下学习了~~~

二、matrix的setpolytopoly使用
想要实现折叠,最重要的就是其核心的原理了,那么第一步我们要了解的就是,如何能把一张正常显示的图片,让它能够进行偏移显示。
其实精髓就在于matrix的setpolytopoly的方法。

public boolean setpolytopoly(float[] src, int srcindex,  float[] dst, int dstindex,int pointcount)  
简单看一下该方法的参数,src代表变换前的坐标;dst代表变换后的坐标;从src到dst的变换,可以通过srcindex和dstindex来制定第一个变换的点,一般可能都设置位0。pointcount代表支持的转换坐标的点数,最多支持4个。
如果不明白没事,下面通过一个简单的例子,带大家了解:

package com.zhy.sample.folderlayout; 
 
import android.app.activity; 
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 matrixpolytopolyactivity extends activity 
{ 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    setcontentview(new polytopolyview(this)); 
  } 
 
  class polytopolyview extends view 
  { 
 
    private bitmap mbitmap; 
    private matrix mmatrix; 
 
    public polytopolyview(context context) 
    { 
      super(context); 
      mbitmap = bitmapfactory.decoderesource(getresources(), 
          r.drawable.tanyan); 
      mmatrix = new matrix(); 
      float[] src = { 0, 0,// 
          mbitmap.getwidth(), 0,// 
          mbitmap.getwidth(), mbitmap.getheight(),// 
          0, mbitmap.getheight() }; 
      float[] dst = { 0, 0,// 
          mbitmap.getwidth(), 100,// 
          mbitmap.getwidth(), mbitmap.getheight() - 100,// 
          0, mbitmap.getheight() }; 
      mmatrix.setpolytopoly(src, 0, dst, 0, src.length >> 1); 
    } 
 
    @override 
    protected void ondraw(canvas canvas) 
    { 
      super.ondraw(canvas); 
      canvas.drawbitmap(mbitmap, mmatrix, null); 
    } 
 
  } 
 
} 

我们编写了一个polytopolyview作为我们的activity的主视图。
在polytopolyview中,我们加载了一张图片,初始化我们的matrix,注意src和dst两个数组,src就是正常情况下图片的4个顶点。dst将图片右侧两个点的y坐标做了些许的修改。
大家可以在纸上稍微标一下src和dst的四个点的位置。
最后我们在ondraw的时候进行图像的绘制,效果为:

Android中FoldingLayout折叠布局的用法及实战全攻略

如果你已经在纸上稍微的画了dst的四个点,那么这个结果你一定不陌生。
可以看到我们通过matrix.setpolytopoly实现了图片的倾斜,那么引入到折叠的情况,假设折叠两次,大家有思路么,考虑一下,没有的话,继续往下看。

三、引入阴影
其实阴影应该在实现初步的折叠以后来说,这样演示其实比较方便,但是为了降低其理解的简单性,我们先把阴影抽取出来说。
假设我们现在要给上图加上阴影,希望的效果图是这样的:

Android中FoldingLayout折叠布局的用法及实战全攻略

可以看到我们左侧加入了一点阴影,怎么实现呢?
主要还是利用lineargradient,我们从左到右添加一层从黑色到透明的渐变即可。

public class matrixpolytopolywithshadowactivity extends activity 
{ 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    setcontentview(new polytopolyview(this)); 
 
  } 
 
  class polytopolyview extends view 
  { 
 
    private bitmap mbitmap; 
    private matrix mmatrix; 
     
    private paint mshadowpaint; 
    private matrix mshadowgradientmatrix; 
    private lineargradient mshadowgradientshader; 
 
    public polytopolyview(context context) 
    { 
      super(context); 
      mbitmap = bitmapfactory.decoderesource(getresources(), 
          r.drawable.tanyan); 
      mmatrix = new matrix(); 
 
      mshadowpaint = new paint(); 
      mshadowpaint.setstyle(style.fill); 
      mshadowgradientshader = new lineargradient(0, 0, 0.5f, 0, 
          color.black, color.transparent, tilemode.clamp); 
      mshadowpaint.setshader(mshadowgradientshader); 
 
      mshadowgradientmatrix = new matrix(); 
      mshadowgradientmatrix.setscale(mbitmap.getwidth(), 1); 
      mshadowgradientshader.setlocalmatrix(mshadowgradientmatrix); 
      mshadowpaint.setalpha((int) (0.9*255)); 
 
    } 
 
    @override 
    protected void ondraw(canvas canvas) 
    { 
      super.ondraw(canvas); 
      canvas.save(); 
      float[] src = //...; 
      float[] dst = //...; 
      mmatrix.setpolytopoly(src, 0, dst, 0, src.length >> 1); 
 
      canvas.concat(mmatrix); 
      canvas.drawbitmap(mbitmap, 0, 0, null); 
      //绘制阴影                                                            canvas.drawrect(0, 0, mbitmap.getwidth(), mbitmap.getheight(), 
          mshadowpaint); 
      canvas.restore(); 
 
    } 
 
  } 
 
} 

重点看mshadowpaint,mshadowgradientshader,mshadowgradientmatrix一个是画笔,我们为画笔设置了一个渐变的shader,这个shader的参数为
new lineargradient(0, 0, 0.5f, 0,color.black, color.transparent, tilemode.clamp);
起点(0,0)、终点(0.5f,0);颜色从和black到透明;模式为clamp,也就是拉伸最后一个像素。
这里你可能会问,这才为0.5个像素的区域设置了渐变,不对呀,恩,是的,继续看接下来我们使用了setlocalmatrix(mshadowgradientmatrix);,而这个
mshadowgradientmatrix将和坐标扩大了mbitmap.getwidth()倍,也就是说现在设置渐变的区域为(0.5f*mbitmap.getwidth(),0)半张图的大小,那么后半张图呢?
后半张应用clamp模式,拉伸的透明。
关于shader、setlocalmatrix等用法也可以参考:android bitmapshader 实战 实现圆形、圆角图片

四、初步实现折叠
了解了原理以及阴影的绘制以后,接下来要开始学习真正的去折叠了,我们的目标效果为:

Android中FoldingLayout折叠布局的用法及实战全攻略

妹子折叠成了8份,且阴影的范围为:每个沉下去夹缝的左右两侧,左侧黑色半透明遮盖,右侧短距离的黑色到透明阴影(大家可以仔细看)。
现在其实大家以及会将图片简单倾斜和添加阴影了,那么唯一的难点就是怎么将一张图分成很多快,我相信每块的折叠大家都会。
其实我们可以通过绘制该图多次,比如第一次绘制往下倾斜;第二次绘制网上倾斜;这样就和我们标题2的实现类似了,只需要利用setpolytopoly。
那么绘制多次,每次显示肯定不是一整张图,比如第一次,我只想显示第一块,所以我们还需要cliprect的配合,说到这,应该以及揭秘了~~~

package com.zhy.sample.folderlayout; 
 
import android.app.activity; 
import android.content.context; 
import android.graphics.bitmap; 
import android.graphics.bitmapfactory; 
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.shader.tilemode; 
import android.os.bundle; 
import android.view.view; 
 
public class simpleuseactivity extends activity 
{ 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    setcontentview(new polytopolyview(this)); 
 
  } 
 
  class polytopolyview extends view 
  { 
 
    private static final int num_of_point = 8; 
    /** 
     * 图片的折叠后的总宽度 
     */ 
    private int mtranslatedis; 
 
    /** 
     * 折叠后的总宽度与原图宽度的比例 
     */ 
    private float mfactor = 0.8f; 
    /** 
     * 折叠块的个数 
     */ 
    private int mnumoffolds = 8; 
 
    private matrix[] mmatrices = new matrix[mnumoffolds]; 
     
    private bitmap mbitmap; 
 
    /** 
     * 绘制黑色透明区域 
     */ 
    private paint msolidpaint; 
 
    /** 
     * 绘制阴影 
     */ 
    private paint mshadowpaint; 
    private matrix mshadowgradientmatrix; 
    private lineargradient mshadowgradientshader; 
 
    /*** 
     * 原图每块的宽度 
     */ 
    private int mflodwidth; 
    /** 
     * 折叠时,每块的宽度 
     */ 
    private int mtranslatedisperflod; 
 
    public polytopolyview(context context) 
    { 
      super(context); 
      mbitmap = bitmapfactory.decoderesource(getresources(), 
          r.drawable.tanyan); 
       
      //折叠后的总宽度 
      mtranslatedis = (int) (mbitmap.getwidth() * mfactor); 
      //原图每块的宽度 
      mflodwidth = mbitmap.getwidth() / mnumoffolds; 
      //折叠时,每块的宽度 
      mtranslatedisperflod = mtranslatedis / mnumoffolds; 
       
      //初始化matrix 
      for (int i = 0; i < mnumoffolds; i++) 
      { 
        mmatrices[i] = new matrix(); 
      } 
 
      msolidpaint = new paint(); 
      int alpha = (int) (255 * mfactor * 0.8f) ; 
      msolidpaint 
          .setcolor(color.argb((int) (alpha*0.8f), 0, 0, 0)); 
 
      mshadowpaint = new paint(); 
      mshadowpaint.setstyle(style.fill); 
      mshadowgradientshader = new lineargradient(0, 0, 0.5f, 0, 
          color.black, color.transparent, tilemode.clamp); 
      mshadowpaint.setshader(mshadowgradientshader); 
      mshadowgradientmatrix = new matrix(); 
      mshadowgradientmatrix.setscale(mflodwidth, 1); 
      mshadowgradientshader.setlocalmatrix(mshadowgradientmatrix); 
      mshadowpaint.setalpha(alpha); 
 
      //纵轴减小的那个高度,用勾股定理计算下 
      int depth = (int) math.sqrt(mflodwidth * mflodwidth 
          - mtranslatedisperflod * mtranslatedisperflod)/2; 
 
      //转换点 
      float[] src = new float[num_of_point]; 
      float[] dst = new float[num_of_point]; 
 
      /** 
       * 原图的每一块,对应折叠后的每一块,方向为左上、右上、右下、左下,大家在纸上自己画下 
       */ 
      for (int i = 0; i < mnumoffolds; i++) 
      { 
        src[0] = i * mflodwidth; 
        src[1] = 0; 
        src[2] = src[0] + mflodwidth; 
        src[3] = 0; 
        src[4] = src[2]; 
        src[5] = mbitmap.getheight(); 
        src[6] = src[0]; 
        src[7] = src[5]; 
 
        boolean iseven = i % 2 == 0; 
 
        dst[0] = i * mtranslatedisperflod; 
        dst[1] = iseven ? 0 : depth; 
        dst[2] = dst[0] + mtranslatedisperflod; 
        dst[3] = iseven ? depth : 0; 
        dst[4] = dst[2]; 
        dst[5] = iseven ? mbitmap.getheight() - depth : mbitmap 
            .getheight(); 
        dst[6] = dst[0]; 
        dst[7] = iseven ? mbitmap.getheight() : mbitmap.getheight() 
            - depth; 
 
        //setpolytopoly 
        mmatrices[i].setpolytopoly(src, 0, dst, 0, src.length >> 1); 
      } 
 
    } 
 
    @override 
    protected void ondraw(canvas canvas) 
    { 
      super.ondraw(canvas); 
      //绘制mnumoffolds次 
      for (int i = 0; i < mnumoffolds; i++) 
      { 
         
        canvas.save(); 
        //将matrix应用到canvas 
        canvas.concat(mmatrices[i]); 
        //控制显示的大小 
        canvas.cliprect(mflodwidth * i, 0, mflodwidth * i + mflodwidth, 
            mbitmap.getheight()); 
        //绘制图片 
        canvas.drawbitmap(mbitmap, 0, 0, null); 
        //移动绘制阴影 
        canvas.translate(mflodwidth * i, 0); 
        if (i % 2 == 0) 
        { 
          //绘制黑色遮盖 
          canvas.drawrect(0, 0, mflodwidth, mbitmap.getheight(), 
              msolidpaint); 
        }else 
        { 
          //绘制阴影 
          canvas.drawrect(0, 0, mflodwidth, mbitmap.getheight(), 
              mshadowpaint); 
        } 
        canvas.restore(); 
      } 
 
    } 
 
  } 
 
} 

简单讲解下,不去管绘制阴影的部分,其实折叠就是:
1、初始化转换点,这里注释说的很清楚,大家最好在纸上绘制下,标一下每个变量。
2、为matrix.setpolytopoly
3、绘制时使用该matrix,且cliprect控制显示区域(这个区域也很简单,原图的第一块到最后一块),最好就是绘制bitmap了。
阴影这里大家可以换个明亮点的图片去看看~~

五、foldlayout的实现
1、实现
我们的想法是这样的,我们的foldlayout只能有一个直接子元素,当然这个子元素可以是relativelayout什么的,可以很复杂。然后只要外层套了我们的foldlayout,就能实现折叠效果。
那么也就是说,我们的foldlayout折叠效果展示的是它的子元素的“样子”,那么如何或者这个“样子”呢?
大家都知道,我们的viewgroup有个方法叫做:dispatchdraw(canvas)主要用来绘制子元素,我们可以对这个canvas进行设置matrix,以及重复调用dispatchdraw(canvas)来实现类似上篇博客最后的效果,这样就完成了我们的可行性的分析。

package com.zhy.view; 
 
import android.content.context; 
import android.graphics.bitmap; 
import android.graphics.bitmap.config; 
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.shader.tilemode; 
import android.util.attributeset; 
import android.view.view; 
import android.view.viewgroup; 
 
public class foldlayout extends viewgroup 
{ 
 
  private static final int num_of_point = 8; 
  /** 
   * 图片的折叠后的总宽度 
   */ 
  private float mtranslatedis; 
 
  protected float mfactor = 0.6f; 
 
  private int mnumoffolds = 8; 
 
  private matrix[] mmatrices = new matrix[mnumoffolds]; 
 
  private paint msolidpaint; 
 
  private paint mshadowpaint; 
  private matrix mshadowgradientmatrix; 
  private lineargradient mshadowgradientshader; 
 
  private float mflodwidth; 
  private float mtranslatedisperflod; 
 
  public foldlayout(context context) 
  { 
    this(context, null); 
  } 
 
  public foldlayout(context context, attributeset attrs) 
  { 
    super(context, attrs); 
 
    for (int i = 0; i < mnumoffolds; i++) 
    { 
      mmatrices[i] = new matrix(); 
    } 
 
    msolidpaint = new paint(); 
    mshadowpaint = new paint(); 
    mshadowpaint.setstyle(style.fill); 
    mshadowgradientshader = new lineargradient(0, 0, 0.5f, 0, color.black, 
        color.transparent, tilemode.clamp); 
    mshadowpaint.setshader(mshadowgradientshader); 
    mshadowgradientmatrix = new matrix(); 
    this.setwillnotdraw(false); 
 
  } 
 
  @override 
  protected void onmeasure(int widthmeasurespec, int heightmeasurespec) 
  { 
    view child = getchildat(0); 
    measurechild(child, widthmeasurespec, heightmeasurespec); 
    setmeasureddimension(child.getmeasuredwidth(), 
        child.getmeasuredheight()); 
 
  } 
 
  @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()); 
 
    mbitmap = bitmap.createbitmap(getmeasuredwidth(), getmeasuredheight(), 
        config.argb_8888); 
    mcanvas.setbitmap(mbitmap); 
    updatefold(); 
 
  } 
 
  private void updatefold() 
  { 
    int w = getmeasuredwidth(); 
    int h = getmeasuredheight(); 
 
    mtranslatedis = w * mfactor; 
    mflodwidth = w / mnumoffolds; 
    mtranslatedisperflod = mtranslatedis / mnumoffolds; 
 
    int alpha = (int) (255 * (1 - mfactor)); 
    msolidpaint.setcolor(color.argb((int) (alpha * 0.8f), 0, 0, 0)); 
 
    mshadowgradientmatrix.setscale(mflodwidth, 1); 
    mshadowgradientshader.setlocalmatrix(mshadowgradientmatrix); 
    mshadowpaint.setalpha(alpha); 
 
    float depth = (float) (math.sqrt(mflodwidth * mflodwidth 
        - mtranslatedisperflod * mtranslatedisperflod) / 2); 
 
    float[] src = new float[num_of_point]; 
    float[] dst = new float[num_of_point]; 
 
    for (int i = 0; i < mnumoffolds; i++) 
    { 
      mmatrices[i].reset(); 
      src[0] = i * mflodwidth; 
      src[1] = 0; 
      src[2] = src[0] + mflodwidth; 
      src[3] = 0; 
      src[4] = src[2]; 
      src[5] = h; 
      src[6] = src[0]; 
      src[7] = src[5]; 
 
      boolean iseven = i % 2 == 0; 
 
      dst[0] = i * mtranslatedisperflod; 
      dst[1] = iseven ? 0 : depth; 
 
      dst[2] = dst[0] + mtranslatedisperflod; 
      dst[3] = iseven ? depth : 0; 
      dst[4] = dst[2]; 
      dst[5] = iseven ? h - depth : h; 
      dst[6] = dst[0]; 
      dst[7] = iseven ? h : h - depth; 
 
      for (int y = 0; y < 8; y++) 
      { 
        dst[y] = math.round(dst[y]); 
      } 
 
      mmatrices[i].setpolytopoly(src, 0, dst, 0, src.length >> 1); 
    } 
  } 
 
  private canvas mcanvas = new canvas(); 
  private bitmap mbitmap; 
  private boolean isready; 
 
  @override 
  protected void dispatchdraw(canvas canvas) 
  { 
 
    if (mfactor == 0) 
      return; 
    if (mfactor == 1) 
    { 
      super.dispatchdraw(canvas); 
      return; 
    } 
    for (int i = 0; i < mnumoffolds; i++) 
    { 
      canvas.save(); 
 
      canvas.concat(mmatrices[i]); 
      canvas.cliprect(mflodwidth * i, 0, mflodwidth * i + mflodwidth, 
          getheight()); 
      if (isready) 
      { 
        canvas.drawbitmap(mbitmap, 0, 0, null); 
      } else 
      { 
        // super.dispatchdraw(canvas); 
        super.dispatchdraw(mcanvas); 
        canvas.drawbitmap(mbitmap, 0, 0, null); 
        isready = true; 
      } 
      canvas.translate(mflodwidth * i, 0); 
      if (i % 2 == 0) 
      { 
        canvas.drawrect(0, 0, mflodwidth, getheight(), msolidpaint); 
      } else 
      { 
        canvas.drawrect(0, 0, mflodwidth, getheight(), mshadowpaint); 
      } 
      canvas.restore(); 
    } 
  } 
    //...dispatchdraw 
 
  public void setfactor(float factor) 
  { 
    this.mfactor = factor; 
    updatefold(); 
    invalidate(); 
  } 
 
  public float getfactor() 
  { 
    return mfactor; 
  } 
 
} 

上述代码大家应该不陌生,只是把从view对单个图片进行绘制的修改为了viewgroup。
既然是viewgroup少不了onmeasure,onlayout等。测量和布局完全依赖于它的子view。
然后将需要初始化的一些东西,不依赖于宽度的,比如画笔什么的都放在构造中;依赖宽高的,都在onlayout之后,调用了updatefold();进行初始化相关代码。
updatefold中的代码,我们也不陌生,因为和上篇博客基本一致。主要就是计算mflodwidth,mtranslatedisperflod以及根据设置的mnumoffolds去循环初始化我们的matrix.
matrix完成setpolytopoly以后,我们就可以去绘制了:

private canvas mcanvas = new canvas(); 
  private bitmap mbitmap; 
  private boolean isready; 
 
  @override 
  protected void dispatchdraw(canvas canvas) 
  { 
 
    if (mfactor == 0) 
      return; 
    if (mfactor == 1) 
    { 
      super.dispatchdraw(canvas); 
      return; 
    } 
    for (int i = 0; i < mnumoffolds; i++) 
    { 
      canvas.save(); 
 
      canvas.concat(mmatrices[i]); 
      canvas.cliprect(mflodwidth * i, 0, mflodwidth * i + mflodwidth, 
          getheight()); 
      if (isready) 
      { 
        canvas.drawbitmap(mbitmap, 0, 0, null); 
      } else 
      { 
        // super.dispatchdraw(canvas); 
        super.dispatchdraw(mcanvas); 
        canvas.drawbitmap(mbitmap, 0, 0, null); 
        isready = true; 
      } 
      canvas.translate(mflodwidth * i, 0); 
      if (i % 2 == 0) 
      { 
        canvas.drawrect(0, 0, mflodwidth, getheight(), msolidpaint); 
      } else 
      { 
        canvas.drawrect(0, 0, mflodwidth, getheight(), mshadowpaint); 
      } 
      canvas.restore(); 
    } 
  } 

mfactor主要代表折叠后的总宽度与原宽度的比值,默认不折叠时为1,所以直接调用super.dispatchdraw(canvas);
那么如果为0,说明全部折起来了,我们直接if (mfactor == 0)return;就不要绘制了。
如果(0,1)之间就是正常情况了,如果还记得上一篇博客内容,无非就是根据mnumoffolds循环绘制多次,每次绘制的时候设置matrix,利用cliprect就可以实现我们的折叠。
这里大家注意看,我在第一次绘制的时候,调用了:

super.dispatchdraw(mcanvas); 
canvas.drawbitmap(mbitmap, 0, 0, null); 
isready = true; 

在我们自己new的mbitmap中也绘制了一份图片,因为我不希望每次都是调用super.dispatchdraw,所以只要isready=true,我们就可以去调用绘制mbitmap而避免调用super.dispatchdraw()。
绘制完成图片,就是绘制黑色的遮盖和阴影了~~,就是两个rect的绘制。
完成这些以后,我们可以简单的坐下测试,使用我们的布局。

2、测试
布局文件:

<com.zhy.view.foldlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_fold_layout" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" > 
 
  <imageview 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:scaletype="fitxy" 
    android:src="@drawable/xueshan" /> 
 
</com.zhy.view.foldlayout> 

activity

package com.zhy.sample.folderlayout; 
 
import com.zhy.view.foldlayout; 
 
import android.animation.objectanimator; 
import android.annotation.suppresslint; 
import android.app.activity; 
import android.os.bundle; 
 
public class foldlayoutactivity extends activity 
{ 
  private foldlayout mfoldlayout; 
 
  @override 
  protected void oncreate(bundle savedinstancestate) 
  { 
    super.oncreate(savedinstancestate); 
    setcontentview(r.layout.activity_fold); 
 
    mfoldlayout = (foldlayout) findviewbyid(r.id.id_fold_layout); 
   
    /*mfoldlayout.post(new runnable() 
    { 
 
      @suppresslint("newapi") 
      @override 
      public void run() 
      { 
        objectanimator.offloat(mfoldlayout, "factor", 1, 0, 1) 
            .setduration(5000).start(); 
      } 
    });*/ 
 
  } 
} 

现在的效果是,我们把mfactor改为0.6f:

Android中FoldingLayout折叠布局的用法及实战全攻略

当然了,此时只是静态的,但是我们成功的完成了绘制一个静态图到flodlayout。
接下来我们为其增加手指的触摸折叠功能。

六、touchfoldlayout
1、实现
增加触摸功能其实很简单,我们的绘制依赖mfactor这个值,我们只要在ontouchevent里面去累加手指移动距离,然后动态更新这个值就可以了。

package com.zhy.view; 
 
import android.content.context; 
import android.graphics.canvas; 
import android.util.attributeset; 
import android.view.gesturedetector; 
import android.view.motionevent; 
 
public class touchfoldlayout extends foldlayout 
{ 
 
  private gesturedetector mscrollgesturedetector; 
 
  public touchfoldlayout(context context, attributeset attrs) 
  { 
    super(context, attrs); 
    init(context, attrs); 
  } 
 
  public void init(context context, attributeset attrs) 
  { 
    mscrollgesturedetector = new gesturedetector(context, 
        new scrollgesturedetector()); 
 
  } 
 
  @override 
  public boolean ontouchevent(motionevent event) 
  { 
    return mscrollgesturedetector.ontouchevent(event); 
  } 
 
  private int mtranslation = -1; 
 
  @override 
  protected void dispatchdraw(canvas canvas) 
  { 
    if (mtranslation == -1) 
      mtranslation = getwidth(); 
    super.dispatchdraw(canvas); 
  } 
 
  class scrollgesturedetector extends gesturedetector.simpleongesturelistener 
  { 
    @override 
    public boolean ondown(motionevent e) 
    { 
      return true; 
    } 
 
    @override 
    public boolean onscroll(motionevent e1, motionevent e2, 
        float distancex, float distancey) 
    { 
      mtranslation -= distancex; 
 
      if (mtranslation < 0) 
      { 
        mtranslation = 0; 
      } 
      if (mtranslation > getwidth()) 
      { 
        mtranslation = getwidth(); 
      } 
 
      float factor = math.abs(((float) mtranslation) 
          / ((float) getwidth())); 
 
      setfactor(factor); 
 
      return true; 
    } 
  } 
 
} 

我们选择继承foldlayout,重写其ontouchevent,然后通过mscrollgesturedetector获取移动的距离,最终和width做比值得到我们的factor,然后调用setfactor进行改变。

public void setfactor(float factor) 
{ 
  this.mfactor = factor; 
  updatefold(); 
  invalidate(); 
} 

ok,这样就完成了引入手指的控制。

2、测试
现在改变下布局文件里面的类:

<com.zhy.view.touchfoldlayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_fold_layout" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" > 
 
  <imageview 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" 
    android:scaletype="fitxy" 
    android:src="@drawable/xueshan" /> 
 
</com.zhy.view.touchfoldlayout> 

activity不变,看一下测试效果:(测试前记得把mfactor改为默认值1.0f)

Android中FoldingLayout折叠布局的用法及实战全攻略

至此我们完成了炫酷的效果,但是我们还需要应用到具体的案例上,否则就是特技,有必要duang一下。
于是我们首先考虑增加到侧滑菜单中去,侧滑菜单有很多选择,google也提供了两个,一个是drawerlayout,另一个是slidingpanelayout。
下面分别展示如何整合入这两个布局。
首先看slidingpanelayout,因为drawerlayout还有些地方需要修改。

八、foldslidingpanellayout

1、实现
对于slidingpanelayout的使用,应该没什么问题吧,就是把布局文件的根布局设置为slidingpanelayout,然后里面放两个子布局,一个代表侧滑菜单,一个代表内容区域。
那么,我们怎么整合到slidingpanelayout种去呢?大致两种方法:
1、把我们的折叠菜单作为侧滑布局的根布局,然后在activity种去监听setpanelslidelistener做出改变。
2、直接继承slidingpanelayout,再其内部将child(0)用foldlayout包起来,然后监听setpanelslidelistener。
这里我们选择后者,因为后者封装好了,就能直接按照slidingpanelayout原本的方式去使用了,不需要做多余的操作。
下面看代码:

package com.zhy.view; 
 
import android.content.context; 
import android.support.v4.widget.slidingpanelayout; 
import android.util.attributeset; 
import android.view.view; 
import android.view.viewgroup; 
 
public class foldslidingpanellayout extends slidingpanelayout 
{ 
  public foldslidingpanellayout(context context, attributeset attrs) 
  { 
    super(context, attrs); 
  } 
  @override 
  protected void onattachedtowindow() 
  { 
    super.onattachedtowindow(); 
     
    view child = getchildat(0); 
    if (child != null) { 
 
      removeview(child); 
      final foldlayout foldlayout = new foldlayout(getcontext()); 
      //foldlayout.setanchor(0); 
      foldlayout.addview(child); 
      viewgroup.layoutparams laypar = child.getlayoutparams(); 
      addview(foldlayout, 0, laypar); 
       
      setpanelslidelistener(new panelslidelistener() 
      { 
         
        @override 
        public void onpanelslide(view arg0, float arg1) 
        { 
          foldlayout.setfactor(arg1); 
        } 
         
        @override 
        public void onpanelopened(view arg0) 
        { 
          // todo auto-generated method stub 
           
        } 
         
        @override 
        public void onpanelclosed(view arg0) 
        { 
           
        } 
      }); 
       
    } 
  } 
} 

我们继承了slidingpanelayout,然后在onattachedtowindow中,取出侧滑的布局,在外层包上一个foldlayout;并且在内部去监听setpanelslidelistener,在onpanelslide种根据参数,去动态设置foldlayout的factor.
2、测试
(1)、布局文件

<com.zhy.view.foldslidingpanellayout xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_drawerlayout" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" > 
 
  <fragment 
    android:id="@+id/id_left_menu" 
    android:name="com.zhy.sample.folderlayout.leftmenufragment" 
    android:layout_width="240dp" 
    android:layout_height="match_parent" /> 
 
  <relativelayout 
    android:layout_width="match_parent" 
    android:layout_height="match_parent" > 
 
    <imageview 
      android:layout_width="match_parent" 
      android:layout_height="match_parent" 
      android:scaletype="fitcenter" 
      android:src="@drawable/xueshan" /> 
  </relativelayout> 
 
</com.zhy.view.foldslidingpanellayout> 

我们的菜单使用的是一个fragment。
(2)、菜单布局

package com.zhy.sample.folderlayout; 
 
import android.os.bundle; 
import android.support.v4.app.fragment; 
import android.view.layoutinflater; 
import android.view.view; 
import android.view.viewgroup; 
import android.widget.arrayadapter; 
import android.widget.listview; 
 
public class leftmenufragment extends fragment 
{ 
  private listview mmenus; 
  private string[] mmenuitemstr = { "bear", "bird", "cat", "tigers", "panda" }; 
 
  @override 
  public view oncreateview(layoutinflater inflater, viewgroup container, 
      bundle savedinstancestate) 
  { 
 
    view view = inflater.inflate(r.layout.fragment_left_menu, container, 
        false); 
    mmenus = (listview) view.findviewbyid(r.id.id_left_menu_lv); 
    mmenus.setadapter(new arrayadapter<string>(getactivity(), 
        r.layout.item_left_menu, mmenuitemstr)); 
    return view; 
  } 
} 

<listview xmlns:android="http://schemas.android.com/apk/res/android" 
  xmlns:tools="http://schemas.android.com/tools" 
  android:id="@+id/id_left_menu_lv" 
  android:layout_width="240dp" 
  android:layout_height="match_parent" 
  android:layout_gravity="start" 
  android:background="#fff" 
  android:choicemode="singlechoice" /> 

item就是一个textview,就不贴了~~
3、activity

package com.zhy.sample.folderlayout; 
 
import android.os.bundle; 
import android.support.v4.app.fragmentactivity; 
 
public class slidingpanellayoutsampleactivity extends fragmentactivity 
{ 
  @override 
  protected void oncreate(bundle arg0) 
  { 
    super.oncreate(arg0); 
    setcontentview(r.layout.activity_slidingpanellayout); 
  } 
} 

恩,activity里面什么都不用做,引入布局文件就行了。
最好看看效果图。
3、效果图

Android中FoldingLayout折叠布局的用法及实战全攻略

这里菜单块数比较多,大家可以自行修改运行。
ok,到此我们将foldlayout与slidingpanelayout进行了整合,构造了这么个个性的侧滑。
最好还剩下与drawerlayout的整合。

九、folddrawerlayout
1、实现
关于drawerlayout的使用,与上面的slidingpanelayout类似,写写布局文件,引入activity就好了。我们依然使用上述的方法2,去实现一个drawerlayout的子类。

package com.zhy.view; 
 
import android.content.context; 
import android.support.v4.view.gravitycompat; 
import android.support.v4.view.viewcompat; 
import android.support.v4.widget.drawerlayout; 
import android.util.attributeset; 
import android.util.log; 
import android.view.gravity; 
import android.view.view; 
import android.view.viewgroup; 
 
public class folddrawerlayout extends drawerlayout 
{ 
  private static final string tag = "drawerfoldlayout"; 
 
  public folddrawerlayout(context context, attributeset attrs) 
  { 
    super(context, attrs); 
  } 
 
  @override 
  protected void onattachedtowindow() 
  { 
    super.onattachedtowindow(); 
 
    final int childcount = getchildcount(); 
    for (int i = 0; i < childcount; i++) 
    { 
      final view child = getchildat(i); 
      if (isdrawerview2(child)) 
      { 
        log.e(tag, "at" + i); 
        foldlayout foldlayout = new foldlayout( 
            getcontext()); 
        //<span style="font-family: arial, helvetica, sans-serif;">foldlayout</span><span style="font-family: arial, helvetica, sans-serif;">.setanchor(1);</span> 
        removeview(child); 
        foldlayout.addview(child); 
        viewgroup.layoutparams laypar = child.getlayoutparams(); 
        addview(foldlayout, i, laypar); 
      } 
 
    } 
    setdrawerlistener(new drawerlistener() 
    { 
 
      @override 
      public void ondrawerstatechanged(int arg0) 
      { 
        // todo auto-generated method stub 
 
      } 
 
      @override 
      public void ondrawerslide(view drawerview, float slideoffset) 
      { 
 
        if (drawerview instanceof foldlayout) 
        { 
          foldlayout foldlayout = ((foldlayout) drawerview); 
          log.e(tag, "slideoffset = " + slideoffset); 
          foldlayout.setfactor(slideoffset); 
        } 
 
      } 
 
      @override 
      public void ondraweropened(view arg0) 
      { 
 
      } 
 
      @override 
      public void ondrawerclosed(view arg0) 
      { 
 
      } 
    }); 
 
  } 
 
  boolean isdrawerview2(view child) 
  { 
    final int gravity = ((layoutparams) child.getlayoutparams()).gravity; 
    final int absgravity = gravitycompat.getabsolutegravity(gravity, 
        viewcompat.getlayoutdirection(child)); 
    return (absgravity & (gravity.left | gravity.right)) != 0; 
  } 
 
} 

看到这,大家可能会想,然后就和slidingpanelayout一样,写写布局文件就好了?其实不是的,如果你这么做了,你会发现侧滑很难拉出来,因为是这样的:
drawelayout的侧滑菜单,比如我们拉出来50%,那么正常来说显示的时侧滑布局右侧的50%,但是这个0.5如果设置给我们的factor,它会把布局缩小到50%且在左边。
导致,你拉了50%其实还是上面都看不到,因为折叠到左侧的50%去了。这里依然有两种解决方案:
(1)、结合属性动画,做偏移,具体可参考:android drawerlayout 高仿qq5.2双向侧滑菜单
(2)、让我们的折叠,收缩到最终的位置可以控制,我们现在统统往最坐标收缩,如果可以设置为最右边,那么本例就没有问题了。
2、引入anchor
我们引入一个manchor变量,值范围[0,1],控制foldlayout最终折叠到的位置。其实修改的代码比较少,我贴一下修改的代码:

private void updatefold() 
  { 
     
    //... 
    float anchorpoint = manchor * w; 
    float midfold = (anchorpoint / mflodwidth); 
 
    for (int i = 0; i < mnumoffolds; i++) 
    { 
      //引入anchor 
      dst[0] = (anchorpoint > i * mflodwidth) ? anchorpoint 
          + (i - midfold) * mtranslatedisperflod : anchorpoint 
          - (midfold - i) * mtranslatedisperflod; 
      dst[2] = (anchorpoint > (i + 1) * mflodwidth) ? anchorpoint 
          + (i + 1 - midfold) * mtranslatedisperflod : anchorpoint 
          - (midfold - i - 1) * mtranslatedisperflod;                               }         

                                                                                                     } 
             

唯一改变的就是dst[0]和dst[2]的坐标,当然了,anchor引入以后,你需要判断原始的坐标是否小于anchorpoint,如果小于需要加一些偏移量,大于则反之。
记得:

public void setanchor(float anchor) 
  { 
    this.manchor = anchor; 
    updatefold(); 
    invalidate(); 
  } 

打开上述的folddrawerlayout的这行代码:foldlayout.setanchor(1);让其最后合并位置为右侧。
使用方式,现在就是写好布局文件,大家直接使用slidingpanelayout那个布局文件,改一个根布局类就行。
3、效果图

Android中FoldingLayout折叠布局的用法及实战全攻略