源碼地址[https://github.com/githubwing/ZoomHeader]
不是共享元素!不是共享元素!不是共享元素!重要的話說(shuō)三遍。共享元素不可以隨手指移動(dòng)的。
先吐槽下餓了么。不提示左右可以滑動(dòng)。我還是無(wú)意中發(fā)現(xiàn)的。不提示我怎么知道可以滑動(dòng)??
這是一個(gè)模仿餓了么詳情頁(yè)的例子。并非一個(gè)庫(kù),并非拿來(lái)就可以用,主要講解思路以及如何實(shí)現(xiàn),可能有一些細(xì)節(jié)沒(méi)有處理。 講述了如何實(shí)現(xiàn)。具體祥見(jiàn)源碼。
他是一個(gè)Activity還是兩個(gè)?
相信你肯定有這樣的疑問(wèn),答案是一個(gè)。所以這不是用共享元素實(shí)現(xiàn)的,使用共享元素會(huì)導(dǎo)致圖片無(wú)法跟隨手指移動(dòng)。
你看到的中間imageview是viewpager。在Viewpager上面是一個(gè)透明的View。當(dāng)然,這個(gè)Activity的背景也是透明的。
實(shí)現(xiàn)思路
我使用CoordinatorLayout+Behavior實(shí)現(xiàn)的。說(shuō)實(shí)話,Behavior真心強(qiáng)大。。
viewpager+頭部
整個(gè)實(shí)現(xiàn)的思路是這樣的。整體布局從上到下依次是:
透明View
viewpager
RecyclerView
其中透明View和Viewpager 合并成一個(gè)自定義的Header。當(dāng)這個(gè)Header上移的時(shí)候,圖片放大,并且RecyclerView聯(lián)動(dòng)上衣,從透明轉(zhuǎn)向并且不透明。
所以首先要定制一個(gè)透明的可移動(dòng)的HeaderView。
在onTouchEvent處理一下手勢(shì)。。
@Override public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
return true;
case MotionEvent.ACTION_MOVE:
if(上下移動(dòng)到閥值){ 展開(kāi)為詳情()
}else if(上下滑動(dòng)到閥值,恢復(fù)viewpager){
}else if(下滑,則關(guān)閉Activity)
將header分為三種狀態(tài):
上移。則展開(kāi)為詳情頁(yè)。
下移,則恢復(fù)為viewpager。
再下移,則finish Activity。
在上移的過(guò)程中,遇到了一點(diǎn)小挑戰(zhàn),這里分享下:
上移的過(guò)程中,圖片需要放大。但是在做的過(guò)程中,不能使用LayoutParams實(shí)現(xiàn)。這里就關(guān)系到一些動(dòng)畫(huà)的小細(xì)節(jié)。
動(dòng)畫(huà)使用LayoutParams實(shí)現(xiàn)是一個(gè)禁忌。他會(huì)導(dǎo)致不停requestLayout,從而影響UI性能。
所以這里我的一個(gè)解法就是,我放大圖片,不是真正的改變ImageView大小,而是去Scale圖片。即使看起來(lái)變大了,他的View真正大小也不會(huì)變。
所以,有一句話叫做真亦是假、假亦是真 真真假假,你又何必當(dāng)真呢?動(dòng)畫(huà)效果只要遵循這句話,基本上都是可以實(shí)現(xiàn)的。你所看到的效果都是假的。都是障眼法。View變大不是真正的變大。View懸浮不是真正的懸浮(有可能是顯隱)。就像變魔術(shù)一樣。。其實(shí)很簡(jiǎn)單。
接下來(lái)又遇到問(wèn)題了。圖片放大了,文字如何對(duì)齊? 文字的位置當(dāng)然也不能真正改變。所以這里使用TranslationX實(shí)現(xiàn)。在圖片放大的過(guò)程中,使用scale的系數(shù),與兩個(gè)端點(diǎn)值進(jìn)行一個(gè)線性變化計(jì)算。主要文字對(duì)齊代碼如下:
bottom.offsetLeftAndRight( (int) (target.getWidth() / 2 - target.getWidth() * (1 + progress) / 2 + MarginConfig.MARGIN_LEFT_RIGHT - bottom.getX()));
第二個(gè)點(diǎn)。就是在圖片放大過(guò)程中,底部文字和按鈕左右padding不能變。這也是我沒(méi)有封裝成一個(gè)拿來(lái)就用的View的原因(其實(shí)還是水平不夠)。因?yàn)檫@些空間需要全部按照上方的方法進(jìn)行動(dòng)態(tài)計(jì)算。。所以也是比較坑爹的。。
ViewPager
拿了網(wǎng)上一個(gè)畫(huà)廊的效果。直接
setPageTransformer(true, new ZoomOutPageTransformer());
這里注意,需要改變一下view的繪制順序,保證當(dāng)前view是最后繪制處于最上層
/改變系統(tǒng)繪制順序
@Override protected int getChildDrawingOrder(int childCount, int i) {
int position = getCurrentItem();
if(position<0){
return i;
}else{ if(i == childCount - 1){//這是最后一個(gè)需要刷新的item
if(position>i){
position=i; }
return position; }
if(i == position){//這是原本要在最后一個(gè)刷新的item return childCount - 1; } }
return i; }}
RecyclerView
RecyclerView最開(kāi)始是完全透明的。并且跟隨HeaderView上移而上移,在上移的過(guò)程中漸漸顯示出來(lái)。 需要監(jiān)聽(tīng)RecyclerView滾動(dòng),當(dāng)RecyclerView滾動(dòng)到頂部的時(shí)候。告知Header,該恢復(fù)最初原樣了。
@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) { //向下滑動(dòng)
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關(guān)聯(lián)起來(lái)的就是Behavior了。Behavior之前寫(xiě)過(guò)幾篇介紹過(guò)了,這里就不再啰嗦。
denpendcy為HeaderView。并且監(jiān)聽(tīng)RecyclerView的滑動(dòng)。
具體的細(xì)節(jié)還是看源碼吧~