这交互炸了:饿了么是怎么让Image变成详情页的

VirgilioHar 8年前
   <p>晚上叫外卖,打开饿了么,发现推了一个版本,更新以后,点开了个鸡腿,哇,交互炫炸了。</p>    <p>不过还是有槽点。我是无意中才发现可以左右滑动的。这。。。你不告诉我,我怎么知道左右可以滑。</p>    <p>直接上图啊:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/095224cfdecbf29cdc21a3ada41461cd.gif"></p>    <p>挺有意思的,对吧? 所以我就想模仿一下。下面是我做出来的效果:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/3a491632cf47abd203d14cd89066f370.gif"></p>    <p>额。。不过图片不是长条的哈。大概意思一样就行了。接下来将和大家分享这个效果是如何实现的。讲思路以及遇到的问题。但是不会讨论细节,具体的细节请看源码。</p>    <h3>他是一个Activity还是两个?</h3>    <p>相信你肯定有这样的疑问,答案是一个。你看到的中间imageview是viewpager。在Viewpager上面是一个透明的View。当然,这个Activity的背景也是透明的。</p>    <h3>实现思路</h3>    <p>我使用CoordinatorLayout+Behavior实现的。说实话,Behavior真心强大。。</p>    <h3>viewpager+头部</h3>    <p>整个实现的思路是这样的。整体布局从上到下依次是:</p>    <ul>     <li>透明View</li>     <li>viewpager</li>     <li>RecyclerView</li>    </ul>    <p>其中透明View和Viewpager 合并成一个自定义的Header。当这个Header上移的时候,图片放大,并且RecyclerView联动上衣,从透明转向并且不透明。</p>    <p>所以首先要定制一个透明的可移动的HeaderView。</p>    <p>在onTouchEvent处理一下手势。。</p>    <pre>  <code class="language-java">@Override public boolean onTouchEvent(MotionEvent ev) {      switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:          return true;        case MotionEvent.ACTION_MOVE:           if(上下移动到阀值){               展开为详情()           }else if(上下滑动到阀值,恢复viewpager){           }else if(下滑,则关闭Activity)</code></pre>    <p>将header分为三种状态:</p>    <ul>     <li>上移。则展开为详情页。</li>     <li>下移,则恢复为viewpager。</li>     <li>再下移,则finish Activity。</li>    </ul>    <p>在上移的过程中,遇到了一点小挑战,这里分享下:</p>    <p>上移的过程中,图片需要放大。但是在做的过程中,不能使用LayoutParams实现。这里就关系到一些动画的小细节。</p>    <p>动画使用LayoutParams实现是一个禁忌。他会导致不停requestLayout,从而影响UI性能。</p>    <p>所以这里我的一个解法就是,我放大图片,不是真正的改变ImageView大小,而是去Scale图片。即使看起来变大了,他的View真正大小也不会变。</p>    <p>所以,有一句话叫做 <strong>真亦是假、假亦是真</strong> 真真假假,你又何必当真呢?动画效果只要遵循这句话,基本上都是可以实现的。你所看到的效果都是假的。都是障眼法。View变大不是真正的变大。View悬浮不是真正的悬浮(有可能是显隐)。就像变魔术一样。。其实很简单。</p>    <p>接下来又遇到问题了。图片放大了,文字如何对齐? 文字的位置当然也不能真正改变。所以这里使用TranslationX实现。在图片放大的过程中,使用scale的系数,与两个端点值进行一个线性变化计算。主要文字对齐代码如下:</p>    <pre>  <code class="language-java">bottom.offsetLeftAndRight(              (int) (target.getWidth() / 2 - target.getWidth() * (1 + progress) / 2                  + MarginConfig.MARGIN_LEFT_RIGHT - bottom.getX()));</code></pre>    <p>第二个点。就是在图片放大过程中,底部文字和按钮左右padding不能变。这也是我没有封装成一个拿来就用的View的原因(其实还是水平不够)。因为这些空间需要全部按照上方的方法进行动态计算。。所以也是比较坑爹的。。</p>    <h3>ViewPager</h3>    <p>拿了网上一个画廊的效果。直接</p>    <pre>  <code class="language-java">setPageTransformer(true, new ZoomOutPageTransformer());</code></pre>    <p>这里注意,需要改变一下view的绘制顺序,保证当前view是最后绘制处于最上层</p>    <pre>  <code class="language-java">/改变系统绘制顺序    @Override protected int getChildDrawingOrder(int childCount, int i) {        int position = getCurrentItem();      if(position<0){        return i;      }else{        if(i == childCount - 1){//这是最后一个需要刷新的item          if(position>i){            position=i;          }          return position;        }        if(i == position){//这是原本要在最后一个刷新的item          return childCount - 1;        }      }      return i;    }  }</code></pre>    <h3>RecyclerView</h3>    <p>RecyclerView最开始是完全透明的。并且跟随HeaderView上移而上移,在上移的过程中渐渐显示出来。 需要监听RecyclerView滚动,当RecyclerView滚动到顶部的时候。告知Header,该恢复最初原样了。</p>    <pre>  <code class="language-java">@Override    public boolean onNestedFling(CoordinatorLayout coordinatorLayout, View child, View target,        float velocityX, float velocityY, boolean consumed) {        //向下Fling并且到顶部      if (velocityY < 0 && ((RecyclerView) target).getChildAt(0).getY() == 0) {        mDependency.restore(mDependency.getY());      }      return super.onNestedFling(coordinatorLayout, child, target, velocityX, velocityY, consumed);    }      @Override    public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,        int dx, int dy, int[] consumed) {        //如果在顶部      if (((RecyclerView) target).getChildAt(0).getY() == 0) {        //向下滑动        if (dy < 0) {          mDependency.setY(mDependency.getY() - dy);          //小于阀值          if (mDependency.getY() < 500) {            mDependency.restore(mDependency.getY());          }        }      }      super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);    }  }</code></pre>    <h3>Behavior</h3>    <p>让header和RecyclerView关联起来的就是Behavior了。Behavior之前写过几篇介绍过了,这里就不再啰嗦。</p>    <p>denpendcy为HeaderView。并且监听RecyclerView的滑动。</p>    <p> </p>    <p> </p>    <p> </p>