高仿蘑菇街欢迎页

MaxReiniger 7年前
   <p>蘑菇街欢迎页</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ff26705538758211cada01003d7a8651.gif"></p>    <p style="text-align:center">蘑菇街欢迎页.gif</p>    <p>高仿效果</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c8c9f998f195b387458745a06afad893.gif"></p>    <p style="text-align:center">高仿版本.gif</p>    <h3><strong>前言</strong></h3>    <p>第一次看蘑菇街欢迎页的时候,觉得还是挺复杂的一个页面效果,有点不知从何下手的感觉。不过经过仔细分析后,发现想要实现这种效果,最重要的原理有2点:</p>    <p>1.ViewPager切换时,通过offset偏移量动态修改View元素属性</p>    <p>2.canvas上细致的控制旋,移,缩,透明等view操作,进行绘制</p>    <p>本文将介绍如何对蘑菇街欢迎页效果进行分析,拆分,并一步步实现1个高仿版本。</p>    <h3><strong>效果拆解</strong></h3>    <p>做之前,经过不断的复看原版效果,分析出其实可以把整体效果拆分为静态,动态2部分。</p>    <p><img src="https://simg.open-open.com/show/0e1610240bd75cc26c435e3abe0f08e5.png"></p>    <p style="text-align:center">整体布局设计.png</p>    <ul>     <li> <p>静态效果:1个支持4个页面的ViewPager,每个页面的展示相对固定,不会根据offset进行改变。</p>      <ul>       <li>第1-4页的顶部文案</li>       <li>第4页的开始按钮</li>      </ul> </li>     <li> <p>动态效果:摆放在viewPager上会变形的自定义View,根据offset动态调整需要绘制的元素的宽高,left,top,透明度等。</p>      <ul>       <li>第1页->第2页        <ul>         <li>0%->50%,矩形背景高度增加,先上移,再下移</li>         <li>0%->50%,模特图,文案,下移,渐变消失</li>         <li>50%-100%,左右裂变出2张背景图,并左右移开</li>         <li>50%->100%,第2页,顶部,底部图,渐变显示</li>         <li>50%->100%,第2页,3张模特图逐步放大显示</li>         <li>0%->100%,底部背景图跟随向左偏移,并消失</li>        </ul> </li>       <li>第2页->第3页        <ul>         <li>0%->50%,矩形背景宽度减少,上移</li>         <li>0%->50%,顶部,底部图,3张模特图渐变消失</li>         <li>0%->50%,2张裂变背景图跟随向左偏移,并消失</li>         <li>50%->100%,第3页,6张模特图逐步放大,渐变显示</li>        </ul> </li>       <li>第3页->第4页        <ul>         <li>0%->50%,矩形背景宽度,高度减少,并逆时针进行旋转</li>         <li>0%->50%,6张模特图缩小,渐变消失</li>         <li>50%->100%,左右裂变出2张背景图,并左右移开</li>         <li>50%->100%,顶部模特,文案,渐变显示</li>         <li>50%->100%,底部3长模特图逐步放大,渐变显示</li>        </ul> </li>      </ul> </li>    </ul>    <h3><strong>实现步骤</strong></h3>    <p>分析完整体要做哪些以后,需要分步骤,从简单的开始一步步实现</p>    <p>1.先把静态的ViewPager实现</p>    <p>2.根据offset实现矩形背景变化</p>    <p>3.根据offset实现第1页底部背景,裂变图变化</p>    <p>4.根据offset实现页面切换时,每个页面图片元素的隐藏,显示,变形等效果</p>    <ul>     <li>先把静态的ViewPager实现</li>    </ul>    <p>自定义ViewPager,每个页面是一个独立layout,可以自由实现顶部文案,和最后一个页面Button</p>    <pre>  <code class="language-java">public class MoguViewPager extends RelativeLayout {        private MoguViewPagerAdapter mAdapter;      private ViewPager mViewPager;      private List<View> mViewList = new ArrayList<>();      /** 每个页面都是一个layout */      private int[] mLayouts = new int[] {R.layout.guide_view_one, R.layout.guide_view_two, R.layout.guide_view_three,          R.layout.guide_view_four};        public MoguViewPager(Context context) {          super(context);          init();      }        public MoguViewPager(Context context, AttributeSet attrs) {          super(context, attrs);          init();      }        private void init() {          inflate(getContext(), R.layout.layout_mogu_viewpager, this);            mViewPager = (ViewPager) this.findViewById(R.id.viewpager);            {              /** 初始化4个页面 */              for (int i = 0; i < mLayouts.length; i++) {                  View view = View.inflate(getContext(), mLayouts[i], null);                  mViewList.add(view);              }          }            mAdapter = new MoguViewPagerAdapter(mViewList, getContext());          mViewPager.setAdapter(mAdapter);      }    }</code></pre>    <p>xml布局</p>    <pre>  <code class="language-java"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                  android:layout_width="match_parent"                  android:layout_height="match_parent">        <android.support.v4.view.ViewPager          android:id="@+id/viewpager"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:layout_centerHorizontal="true"          android:clipChildren="false"/>        <!--这里准备放个自定义View-->  </RelativeLayout></code></pre>    <p>第一步完成,实现还是比较简单的,直接看效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4f72dc5fbf37d6b1cfa1b50de4e53e28.gif"></p>    <p style="text-align:center">第1版.gif</p>    <ul>     <li>根据offset实现矩形背景变化</li>    </ul>    <p>自定义会变形的TransforView,在xml布局中摆放在ViewPager之上</p>    <pre>  <code class="language-java"><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"                  android:layout_width="match_parent"                  android:layout_height="match_parent">        <android.support.v4.view.ViewPager          android:id="@+id/viewpager"          android:layout_width="match_parent"          android:layout_height="match_parent"          android:layout_centerHorizontal="true"          android:clipChildren="false"/>        <com.listen.test_mogu_viewpager.viewpager.TransforView          android:id="@+id/transfor_view" android:layout_width="match_parent"          android:layout_height="450dp"          android:layout_centerInParent="true"/>  </RelativeLayout></code></pre>    <p>给ViewPager添加addOnPageChangeListener(),在onPageScrolled()的时候将position,positionOffset,positionOffsetPixels传递给TransforView。</p>    <pre>  <code class="language-java">mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {              @Override              public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {                  mTransforView.transfor(position, positionOffset, positionOffsetPixels);              }          });</code></pre>    <p>在TransforView中,首先定义页面切换时变化的参数,比如第1页->第2页会发生的变化有高度放大40%,上移30dp,下移60dp,则只需要定义0.4,-30dp,60dp三个参数即可。</p>    <pre>  <code class="language-java">/**   * 第1页->第2页   * 0%->50%,矩形背景高度增加40%,先上移30dp,再下移60dp   */  public static final float FIRST_HEIGHT = 0.4f;// 第1个页面高度缩放比例,正:放大,负:缩小  public final int FIRST_TOP1 = -dp2px(30);// 第1个页面top移动距离,正:下移,负:上移  public final int FIRST_TOP2 = dp2px(60);// 第1个页面top移动距离,正:下移,负:上移  public static final float FIRST_RATE = 0.5f;// 在偏移50%处,进行下一页的显示  /**   * 第2页->第3页   * 0%->50%,矩形背景宽度减少15%,上移20dp   */  public static final float SECOND_WIDTH = -0.15f;// 第2个页面宽度缩放比例,正:放大,负:缩小  public final int SECOND_TOP = -dp2px(20);// 第2个页面top移动距离比例,正:下移,负:上移  public static final float SECOND_RATE = 0.5f;// 在偏移50%处,进行下一页的显示  /**   * 第3页->第4页   * 0%->50%,矩形背景宽度,高度减少10%,并逆时针进行旋转10度   */  public static final float THIRD_WIDTH = -0.1f;// 第3个页面宽度缩放比例,正:放大,负:缩小  public static final float THIRD_HEIGHT = -0.1f;// 第3个页面高度缩放比例,正:放大,负:缩小  public static final int THIRD_DEGREE = -10;// 第3个页面角度调整,正:顺时针,负:逆时针  public static final float THIRD_RATE = 0.5f;// 在偏移50%处,进行下一页的显示    /**   * 第1页初始化矩形背景的宽,高,left,top   */  private float mPage1RectBgDefaultWidth = dp2px(260);  private float mPage1RectBgDefaultHeight = dp2px(230);  private float mPage1RectBgDefaultLeft = getScreenWidth() / 2 - mPage1RectBgDefaultWidth / 2;//left=屏幕宽度/2-矩形宽度/2  private float mPage1RectBgDefaultTop = dp2px(80);    /**   * 第1页->第2页   * 在第1页的基础上进行变化   * 1.height放大   * 2.top先上移n,在下移n*2   */  private float mPage2RectBgDefaultWidth = mPage1RectBgDefaultWidth;  private float mPage2RectBgDefaultHeight = mPage1RectBgDefaultHeight * (1 + FIRST_HEIGHT);// 第2页的高度=第一页高度*1.4  private float mPage2RectBgDefaultLeft = mPage1RectBgDefaultLeft;  private float mPage2RectBgDefaultTop = mPage1RectBgDefaultTop + FIRST_TOP1 + FIRST_TOP2;//第2页的top=第一页的top-30dp+60dp    /**   * 第2页->第3页   * 在第2页的基础上进行变化   * 1.宽度缩小   * 2.top上移   */  private float mPage3RectBgDefaultWidth = mPage2RectBgDefaultWidth * (1 + SECOND_WIDTH);  private float mPage3RectBgDefaultHeight = mPage2RectBgDefaultHeight;  private float mPage3RectBgDefaultLeft = getScreenWidth() / 2 - mPage3RectBgDefaultWidth / 2;//第3页的left=屏幕的宽度/2-矩形背景宽度/2  private float mPage3RectBgDefaultTop = mPage2RectBgDefaultTop + SECOND_TOP;    /**   * 第3页->第4页   * 在第3页的基础上进行变化   * 1.宽度缩小   * 2.高度缩小   * 2.逆时针旋转   */  private float mPage4RectBgDefaultWidth = mPage3RectBgDefaultWidth * (1 + THIRD_WIDTH);  private float mPage4RectBgDefaultHeight = mPage3RectBgDefaultHeight * (1 + THIRD_HEIGHT);  private float mPage4RectBgDefaultLeft = getScreenWidth() / 2 - mPage4RectBgDefaultWidth / 2;  private float mPage4RectBgDefaultTop = mPage3RectBgDefaultTop;  private float mPage4ModelDefaultWidth = (mPage4RectBgDefaultWidth - padding() * 4) / 3;</code></pre>    <p>TransforView的transfor()负责接收position,positionOffset,positionOffsetPixels,并根据position判断当前第几页,从而决定要实现哪些效果,通过positionOffset计算view的宽,高,left,top变化比例,实现缩小,放大,左右,上下移动,旋转等效果</p>    <pre>  <code class="language-java">public void transfor(int position, float positionOffset, int positionOffsetPixels) {      mCurrentPageIndex = position;      if (fromPage1ToPage2(position)) {          if (positionOffset < FIRST_RATE) {              /** 第1页,在0->50%区间偏移 */              /** 矩形背景,高度放大40% */              /**               * 偏移到50%的时候height需要放大40%,defaultHeight=400,targetHeight=400*1.4=560               *               * offset=0               * 400 * (1 + 0.4 * 0 * (1 / 0.5)) = 400               *               * offset=0.25               * 400 * (1 + 0.4 * 0.25 * (1 / 0.5)) = 400 * 1.2 = 480               *               * offset=0.5               * 400 * (1 + 0.4 * 0.5 * (1 / 0.5)) = 400 * 1.4 = 560               *               */              mRectBgCurrentHeight =                      (int) (mPage1RectBgDefaultHeight * (1 + FIRST_HEIGHT * positionOffset * (1 / FIRST_RATE)));              /** 矩形背景,向上移动30dp */              mRectBgCurrentTop = (int) (mPage1RectBgDefaultTop + (FIRST_TOP1 * positionOffset * (1 / FIRST_RATE)));            } else {              /** 第1页,在50%->100%区间偏移 */                /** 矩形背景,上移30dp后,向下偏移60dp */              mRectBgCurrentTop =                      (int) (mPage1RectBgDefaultTop + FIRST_TOP1 + (FIRST_TOP2 * (positionOffset - FIRST_RATE) * 1.0 / (1 - FIRST_RATE)));          }      } else if (fromPage2ToPage3(position)) {          /** 矩形背景,宽度缩小15% */          mRectBgCurrentWidth = (int) (mPage2RectBgDefaultWidth * (1 + SECOND_WIDTH * positionOffset));          mRectBgCurrentLeft = getScreenWidth() / 2 - mRectBgCurrentWidth / 2;            /** 矩形背景,上移20dp */          mRectBgCurrentTop = (int) (mPage2RectBgDefaultTop + (SECOND_TOP * positionOffset));        } else if (fromPage3ToPage4(position)) {            /** 背景矩形的宽度,减少10% */          mRectBgCurrentWidth = mPage3RectBgDefaultWidth * (1 + THIRD_WIDTH * positionOffset);          mRectBgCurrentLeft = getScreenWidth() / 2 - mRectBgCurrentWidth / 2;            /** 背景矩形的高度,减少10% */          mRectBgCurrentHeight = mPage3RectBgDefaultHeight * (1 + THIRD_HEIGHT * positionOffset);            /** 逆时针旋转10度 */          mRectBgCurrentDegree = THIRD_DEGREE * positionOffset;      }       /** 请求重新绘制 */      postInvalidate();  }</code></pre>    <p>最后在onDraw方法中,将计算好宽,高,left,top的view在绘制在canvas上</p>    <pre>  <code class="language-java">protected void onDraw(Canvas canvas) {      super.onDraw(canvas);        RectF rect = new RectF();      rect.left = mRectBgCurrentLeft;      rect.top = mRectBgCurrentTop;      rect.right = rect.left + mRectBgCurrentWidth;      rect.bottom = rect.top + mRectBgCurrentHeight;        canvas.rotate(mRectBgCurrentDegree, rect.left + mRectBgCurrentWidth / 2, rect.top + mRectBgCurrentHeight / 2);      canvas.drawRoundRect(rect, mRectBgDefaultCorner, mRectBgDefaultCorner, mRectBgPaint);  }</code></pre>    <p>第2步实现了通过ViewPager的偏移offset,动态修改矩形背景的宽,高,left,top,和角度,效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/70a16a134dfc6e71c5a38a3af5f7f29b.gif"></p>    <p style="text-align:center">第2版.gif</p>    <ul>     <li>根据offset实现第1页底部背景,裂变图变化</li>    </ul>    <p>在TransforView的init()初始化方法中,获取并设置图片的默认宽,高,left,top。这里封装了1个ViewModel,里面记录了在canvas上绘制图形需要的bitmap,paint,matrix,width,height,left,top等属性,在调用ViewModel.create()的时候,通过Matrix将Bitmap缩放到合适的宽高,才能通过宽高计算left,top,以便在矩形背景上进行精确的绘制。</p>    <pre>  <code class="language-java">public ViewModel create() {      /** 缩放图片尺寸到合适的比例 */      matrix.postScale(currentWidth / bitmap.getWidth(), currentHeight / bitmap.getHeight());      bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);      return this;  }    private void init() {      /** 第1页,底部背景图 */      mPage1BottomBg =          new ViewModel(getContext(), R.drawable.one_bottom_bg).alpha(255)              .width(mPage1RectBgDefaultWidth - padding() * 2)              .left(mPage1RectBgDefaultLeft + padding())              // top距离=矩形背景top+height+5dp边距              .top(mPage1RectBgDefaultTop + mPage1RectBgDefaultHeight + padding())              .create();        /** 第2页,裂变背景图 */      for (int i = 0; i < 2; i++) {          mPage2Split[i] =              new ViewModel(getContext(), R.drawable.two_bg).width(mPage2RectBgDefaultWidth)                  .height(mPage2RectBgDefaultHeight)                  .left(mPage2RectBgDefaultLeft)                  .top(mPage2RectBgDefaultTop)                  .create();      }      /** 第4页,2张裂变背景图 */      for (int i = 0; i < mPage4Split.length; i++) {          mPage4Split[i] =              new ViewModel(getContext(), R.drawable.four_bg)                      .width(mPage4RectBgDefaultWidth)                  .height(mPage4RectBgDefaultHeight)                  .left(mPage4RectBgDefaultLeft)                  .top(mPage4RectBgDefaultTop);        }  }</code></pre>    <p>在transfor()中根据偏移offset,修改图片位置,实现移动;第1页的背景图,offset多少则左移多少即可,第2页的裂变图,在第1页滑动到50%时,显示裂变背景图,根据offset分别左右平移,第4页裂变图原理一致,只是绘制前需要通过Matrix将图进行旋转。</p>    <pre>  <code class="language-java">private void transfor(int position, float positionOffset, int positionOffsetPixels) {          if (fromPage1ToPage2(position)) {              /** 第1页,底部背景图,根据页面向左偏移 */              mPage1BottomBg.currentLeft = mPage1BottomBg.defaultLeft - positionOffsetPixels;                if (positionOffset < FIRST_RATE) {                } else {                  /** 第2页,计算裂变背景图的偏移px,并修改透明度渐变显示 */                  float offset = (mPage1RectBgDefaultWidth + dp2px(15)) * ((positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE)));                  mPage2Split[0].currentLeft = mPage2Split[0].defaultLeft - offset;                  mPage2Split[1].currentLeft = mPage2Split[0].defaultLeft + offset;                  /**                   * 偏移到50%的时候alpha需要为0,偏移到100%,alpha需要为255,不过此时positionOffset的取值=0.5~1                   *                   * offset=0.5                   * 255 * (0.5 - 0.5) * (1 / (1 - 0.5)))=255 * 0 = 0                   *                   * offset=0.75                   * 255 * (0.75 - 0.5) * (1 / (1 - 0.5)))=255 * 0.5 = 127.5                   *                   * offset=1                   * 255 * (1 - 0.5) * (1 / (1 - 0.5)))=255 * 1 = 255                   */                  mPage2Split[0].alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));                  mPage2Split[1].alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));              }          } else if (fromPage2ToPage3(position)) {              if (positionOffset < SECOND_RATE) {                }          } else if (fromPage3ToPage4(position)) {                if (positionOffset < THIRD_RATE) {                } else {                  /** 显示第4页,裂变背景图,并向左右平移 */                  float offset = (mPage4RectBgDefaultWidth + dp2px(40)) * ((positionOffset - THIRD_RATE) * (1 / (1 - THIRD_RATE)));                  for (int i = 0; i < mPage4Split.length; i++) {                      mPage4Split[i].matrix.reset();                      mPage4Split[i].matrix.postScale(mPage4RectBgDefaultWidth / mPage4Split[i].bitmap.getWidth(), mPage4RectBgDefaultHeight / mPage4Split[i].bitmap.getHeight());                        float currentLeft = 0;                      if (i == 0) {                          // 左移                          currentLeft = mPage4RectBgDefaultLeft - offset;                      } else if (i == 1) {                          // 右移                          currentLeft = mPage4RectBgDefaultLeft + offset;                      }                        // 平移                      mPage4Split[i].matrix.postTranslate(currentLeft, mPage4RectBgDefaultTop);                      // 旋转角度                      mPage4Split[i].matrix.postRotate(THIRD_DEGREE, currentLeft + mPage4RectBgDefaultWidth/2,                              mPage4RectBgDefaultTop + mPage4RectBgDefaultHeight/2);                        mPage4Split[i].alpha((int) (255 * ((positionOffset - THIRD_RATE) * (1 / (1 - THIRD_RATE)))));                  }              }          }      }</code></pre>    <p>效果如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/dcb83d6ac05e88fc95563f295cf6db74.gif"></p>    <p style="text-align:center">第3版.gif</p>    <ul>     <li>根据offset实现页面切换时,每个页面图片元素的隐藏,显示,变形等效果</li>    </ul>    <p>架子已经搭好,接下来就是实现每个页面的子view在切换时的效果变化,其实原理就是根据offset偏移比例,不断的计算view的宽高,实现缩放;计算left,top,实现移动;计算alpha,实现显示,隐藏。</p>    <p>以下是第1页->第2页时主要的代码,其他页面切换效果原理大致一致,只是算法不同,源码上都有详细的注释,感兴趣的朋友可以直接上 <a href="/misc/goto?guid=4959729787727298706" rel="nofollow,noindex">github</a> 下载。</p>    <pre>  <code class="language-java">/**   * 在第1页从0%->50%的过程中,修改透明度,逐渐隐藏第1页的顶部,底部图   *   **/  private void stepByHidePage1Views(float positionOffset) {          /**           * 偏移到50%的时候alpha需要为0,view不可见           *           * offset=0           * 255-(255*0.0*(1/0.5)) = 0           *           * offset=0.25           * 255-(255*0.25*(1/0.5)) = 127           *           * offset=0.5           * 255-(255*0.5*(1/0.5)) = 255           */          mPage1Top.alpha((int) (255 - (255 * positionOffset * (1 / FIRST_RATE))));          mPage1Bottom.alpha((int) (255 - (255 * positionOffset * (1 / FIRST_RATE))));            /** 第1页,顶部图向下移动 */          mPage1Top.currentTop = mPage1Top.defaultTop + (FIRST_TOP2 + FIRST_TOP1) * positionOffset * (1 / FIRST_RATE);            /** 第1页,底部图跟随顶部图向下移动 */          mPage1Bottom.currentTop = mPage1Top.currentTop + mPage1Top.defaultHeight + padding();      }    /**   * 在第1页从50%->100%的过程中,逐渐隐藏第2页的顶部,底部图片,   * 并控制中间3张模特图,依据上1张状态实现放大展示   **/  private void stepByShowPage2Views(float positionOffset) {          /** 第2页,顶部图,跟随矩形背景下移 */          mPage2Top.currentTop = mRectBgCurrentTop + padding();            /** 第2页,底部图,跟随矩形背景下移 */          mPage2Bottom.currentTop = mPage2Center[0].currentTop + mPage2Center[0].defaultHeight + padding();            /** 第2页,计算裂变背景图的偏移px,并修改透明度渐变显示 */          float offset =              (mPage1RectBgDefaultWidth + dp2px(15)) * ((positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE)));          mPage2Split[0].currentLeft = mPage2Split[0].defaultLeft - offset;          mPage2Split[1].currentLeft = mPage2Split[0].defaultLeft + offset;          /**           * 偏移到50%的时候alpha需要为0,偏移到100%,alpha需要为255,不过此时positionOffset的取值=0.5~1           *           * offset=0.5           * 255 * (0.5 - 0.5) * (1 / (1 - 0.5)))=255 * 0 = 0           *           * offset=0.75           * 255 * (0.75 - 0.5) * (1 / (1 - 0.5)))=255 * 0.5 = 127.5           *           * offset=1           * 255 * (1 - 0.5) * (1 / (1 - 0.5)))=255 * 1 = 255           */          mPage2Split[0].alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));          mPage2Split[1].alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));            /** 第2页,顶部,底部图,透明度渐变显示,偏移量达到100%,完成显示 */          mPage2Top.alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));          mPage2Bottom.alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));            /** 第2页,显示中间3张模特图 */          for (int i = 0; i < mPage2Center.length; i++) {              if (i == 0) {                  /** 第2页,显示第1张模特图 */                  mPage2Center[i].currentWidth =                      mPage2Center[i].defaultWidth * (positionOffset - FIRST_RATE) * 1 / (1 - FIRST_RATE);                  mPage2Center[i].currentHeight =                      mPage2Center[i].defaultHeight * (positionOffset - FIRST_RATE) * 1 / (1 - FIRST_RATE);                  mPage2Center[i].alpha((int) (255 * (positionOffset - FIRST_RATE) * (1 / (1 - FIRST_RATE))));                  mPage2Center[i].currentTop = mPage2Top.currentTop + mPage2Top.currentHeight + padding();              } else {                  /** 第2,3张模特图,在前1张显示到一半时才显示 */                  if (mPage2Center[i - 1].currentWidth >= mPage2Center[i - 1].defaultWidth / 2) {                      float rate = mPage2Center[i - 1].widthRate() - 0.5f;                      mPage2Center[i].currentWidth = mPage2Center[i].defaultWidth * (rate * 2);                      mPage2Center[i].currentHeight = mPage2Center[i].defaultHeight * (rate * 2);                      /** 第2,3张模特图,需要根据第1张图计算left */                      mPage2Center[i].currentLeft =                          mPage2Center[0].currentLeft + mPage2Center[0].currentWidth + padding();                      mPage2Center[i].currentTop = mPage2Top.currentTop + mPage2Top.currentHeight + padding();                      if (i == 2) {                          /** 第3张模特图,根据第2张图计算top */                          mPage2Center[i].currentTop =                              mPage2Center[1].currentTop + mPage2Center[1].currentHeight + padding();                      }                      mPage2Center[i].alpha((int) (255 * (positionOffset * rate * 2)));                  } else {                      mPage2Center[i].alpha(0);                  }              }          }      }    private void tranfor(int position, float positionOffset, int positionOffsetPixels) {          if (positionOffset <= 0) {              return;          }          if (fromPage1ToPage2(position)) {              /** 第1页,底部背景图,根据页面向左偏移 */              mPage1BottomBg.currentLeft = mPage1BottomBg.defaultLeft - positionOffsetPixels;                if (positionOffset < FIRST_RATE) {                  /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里重新设置一次,保证第2页的view不可见 */                  hidePage2Views();                    /** 第1页,在0->50%区间偏移 */                  /** 矩形背景,高度放大40%,向上移动30dp */                  transformRectBgFrom1To2Before(positionOffset);                    /** 第1页,渐渐隐顶部图,底部图;透明度渐变消失,偏移到50%时完全消失 */                  stepByHidePage1Views(positionOffset);                } else {                  /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里调重新设置一次。保证第1页的view不可见 */                  hidePage1Views();                    /** 第1页,在50%->100%区间偏移 */                  /** 矩形背景,上移30dp后,向下偏移60dp */                  transformRectBgFrom1To2After(positionOffset);                    /** 第2页,渐渐显示顶部,3张模特图,底部图 */                  stepByShowPage2Views(positionOffset);                }          } else if (fromPage2ToPage3(position)) {              /** 矩形背景,宽度缩小15%,上移20dp */              transformRectBgFrom2To3(positionOffset);                if (positionOffset < SECOND_RATE) {                  /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里调重新设置一次。保证第3页滑回到第2页时,第3页的view不可见 */                  hidePage3Views();                  /** 第2页,在0->50%区间偏移,渐渐隐藏顶部,中间,底部,裂变背景图 */                  stepByHidePage2Views(positionOffset, positionOffsetPixels);              } else {                  /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里调重新设置一次,保证第2页的view不可见 */                  hidePage2Views();                  /** 第2页,在50->100%区间偏移,渐渐显示第3页,6张模特图 */                  stepByShowPage3Views(positionOffset);              }          } else if (fromPage3ToPage4(position)) {                /** 背景矩形的宽度,高度减少10%,逆时针旋转10度 */              transformRectBgFrom3To4(positionOffset);                if (positionOffset < THIRD_RATE) {                    /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里调重新设置一次,保证第4页的view不可见 */                  hidePage4Views();                    /** 渐渐缩放,隐藏第3页,6张模特图 */                  stepByHidePage3Views(positionOffset);                } else {                  /** 快速滑动的时候,可能丢失最后一次绘制,所以需要在这里调重新设置一次,保证第3页的view缩放完成 */                  scaleHidePage3Views();                    /** 渐渐显示第4页,顶部图,底部3张模特图,分裂背景图 */                  stepByShowPage4Views(positionOffset);              }          }          postInvalidate();      }</code></pre>    <p>目前只仿了80%左右,还有一些细节的效果,以及适配,性能调优还没实现。其实真正的难点在于如何精细化的控制每个view的属性,因为页面中每个图片的位置,大小都是在参照其他view的基础上进行计算后得出的。</p>    <p> </p>    <p> </p>