這交互炸了:餓了么是怎么讓Image變成詳情頁的
晚上叫外賣,打開餓了么,發現推了一個版本,更新以后,點開了個雞腿,哇,交互炫炸了。
不過還是有槽點。我是無意中才發現可以左右滑動的。這。。。你不告訴我,我怎么知道左右可以滑。
直接上圖啊:
挺有意思的,對吧? 所以我就想模仿一下。下面是我做出來的效果:
額。。不過圖片不是長條的哈。大概意思一樣就行了。接下來將和大家分享這個效果是如何實現的。講思路以及遇到的問題。但是不會討論細節,具體的細節請看源碼。
他是一個Activity還是兩個?
相信你肯定有這樣的疑問,答案是一個。你看到的中間imageview是viewpager。在Viewpager上面是一個透明的View。當然,這個Activity的背景也是透明的。
實現思路
我使用CoordinatorLayout+Behavior實現的。說實話,Behavior真心強大。。
viewpager+頭部
整個實現的思路是這樣的。整體布局從上到下依次是:
- 透明View
- viewpager
- RecyclerView
其中透明View和Viewpager 合并成一個自定義的Header。當這個Header上移的時候,圖片放大,并且RecyclerView聯動上衣,從透明轉向并且不透明。
所以首先要定制一個透明的可移動的HeaderView。
在onTouchEvent處理一下手勢。。
@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)
將header分為三種狀態:
- 上移。則展開為詳情頁。
- 下移,則恢復為viewpager。
- 再下移,則finish Activity。
在上移的過程中,遇到了一點小挑戰,這里分享下:
上移的過程中,圖片需要放大。但是在做的過程中,不能使用LayoutParams實現。這里就關系到一些動畫的小細節。
動畫使用LayoutParams實現是一個禁忌。他會導致不停requestLayout,從而影響UI性能。
所以這里我的一個解法就是,我放大圖片,不是真正的改變ImageView大小,而是去Scale圖片。即使看起來變大了,他的View真正大小也不會變。
所以,有一句話叫做 真亦是假、假亦是真 真真假假,你又何必當真呢?動畫效果只要遵循這句話,基本上都是可以實現的。你所看到的效果都是假的。都是障眼法。View變大不是真正的變大。View懸浮不是真正的懸浮(有可能是顯隱)。就像變魔術一樣。。其實很簡單。
接下來又遇到問題了。圖片放大了,文字如何對齊? 文字的位置當然也不能真正改變。所以這里使用TranslationX實現。在圖片放大的過程中,使用scale的系數,與兩個端點值進行一個線性變化計算。主要文字對齊代碼如下:
bottom.offsetLeftAndRight(
(int) (target.getWidth() / 2 - target.getWidth() * (1 + progress) / 2
+ MarginConfig.MARGIN_LEFT_RIGHT - bottom.getX()));
第二個點。就是在圖片放大過程中,底部文字和按鈕左右padding不能變。這也是我沒有封裝成一個拿來就用的View的原因(其實還是水平不夠)。因為這些空間需要全部按照上方的方法進行動態計算。。所以也是比較坑爹的。。
ViewPager
拿了網上一個畫廊的效果。直接
setPageTransformer(true, new ZoomOutPageTransformer());
這里注意,需要改變一下view的繪制順序,保證當前view是最后繪制處于最上層
/改變系統繪制順序
@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;
}
}
RecyclerView
RecyclerView最開始是完全透明的。并且跟隨HeaderView上移而上移,在上移的過程中漸漸顯示出來。 需要監聽RecyclerView滾動,當RecyclerView滾動到頂部的時候。告知Header,該恢復最初原樣了。
@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);
}
}
Behavior
讓header和RecyclerView關聯起來的就是Behavior了。Behavior之前寫過幾篇介紹過了,這里就不再啰嗦。
denpendcy為HeaderView。并且監聽RecyclerView的滑動。