- 原文地址:Workcation App – Part 3. RecyclerView interaction with Animated Markers
- 原文作者:Mariusz Brona
- 譯文出自:掘金翻譯計劃
- 譯者:龍騎將楊影楓
- 校對者:Vivienmm、張拭心
Workcation App – 第三部分. 帶有動畫的標記(Animated Markers) 與 RecyclerView 的互動
歡迎閱讀本系列文章的第三篇,此係列文章和我前一段時間完成的“研發”專案有關。在文章裡,我會針對開發中遇到的動畫問題分享一些解決辦法。
Part 1: 自定義 Fragment 轉場
Part 2: 帶有動畫的標記(Animating Markers) 與 MapOverlayLayout
Part 3: 帶有動畫的標記(Animated Markers) 與 RecyclerView 的互動
Part 4: 場景(Scenes)和 RecyclerView 的共享元素轉場動畫(Shared Element Transition)
專案的 Git 地址: Workcation App
動畫的 Dribbble 地址: dribbble.com/shots/28812…
序言
幾個月前我們開了一個部門會議,在會議上我的朋友 Paweł Szymankiewicz 給我演示了他在自己的“研發”專案上製作的動畫。我非常喜歡這個動畫,會後決定用程式碼實現它。我可沒想到到我會攤上啥…
GIF 1 “動畫效果”
開始吧!
就像上面 GIF 動畫展示的,需要做的事情有很多。
-
在點選底部選單欄最右方的選單後,我們會跳轉到一個新介面。在此介面中,地圖通過縮放和漸顯的轉場動畫在螢幕上方載入,Recycleview 的 item 隨著轉場動畫從底部載入,地圖上的標記點在轉場動畫執行的同時被新增到地圖上.
-
當滑動底部的 RecycleView item 的時候,地圖上的標記會通過閃爍來顯示它們的位置(譯者注:原文是show their position on the map,個人認為 position 有兩層含義:一代表標記在地圖上的位置,二代表標記所對應的 item 在 RecycleView 裡的位置。)
-
在點選一個 item 以後,我們會進入到新介面。在此介面中,地圖通過動畫方式來顯示出路徑以及起始/結束標記。同時此 RecyclerView 的item 會通過轉場動畫展示一些關於此地點的描述,背景圖片也會放大,還附有更詳細的資訊和一個按鈕。
-
當後退時,詳情頁通過轉場變成普通的 RecycleView Item,所有的地圖示記再次顯示,同時路徑一起消失。
就這麼多啦,這就是我準備在這一系列文章中向你展示的東西。在本文中,我會解決如何讓標記與 RecycleView 產生互動。
需求
RecyclerView 有一些本地工具來管理自身的狀態。我們可以設定 ItemAnimator 或者 ItemDecorator 來新增一些不錯的動畫效果,通過 ViewHolder 和 LayoutManager 來控制佈局的尺寸和位置。我們還有 listener 來監聽 RecyclerView 的特殊狀態。
如上所示,這是一個橫向的 RecyclerView,該 RecycleView 包含一組記錄巴厘島周邊詳情的 CardViews。當滑動 RecyclerView 的時候,對應的標記要做出閃爍。所以如何實現呢?當然是有一些問題需要解決的 ?!
OnScrollListener
OnScrollListener 是一個允許我們在 RecyclerView 的滑動事件被觸發時接收回撥的類(參見此處)。該類有 onScrolled 方法 —— 這是聯絡滾動位置(position)和標記的關鍵。該回撥方法監聽滾動事件。讓我們看一看它長啥樣:
Java
@Override
public void onScrolled(final RecyclerView recyclerView,final int dx,final int dy){
super.onScrolled(recyclerView,dx,dy);
}複製程式碼
如我們所見,此回撥傳入一個RecyclerView物件作為引數,還有整數型引數 dx 和 dy。“dx” 是橫移量,“dy”是縱移量。在本專案中,我們只對 recycleview 引數感興趣.
第一個想法
好吧,既然我們已經有了含有 onScrolled 方法的 OnScrollListener 類,那就不復雜了吧?我們需要判斷某個 RecycleView 的 item 是否處於正中心,如果是的話就通知對應的標記閃爍。簡單不?確實很簡單,但是不管用 ?。再看一下動畫,第一個 item 和最後一個 item 永遠不會到達 RecycleView 的中心。
第二個想法
該怎麼做呢?觸發標記閃爍的觸發點是隨著 RecyclerView 的滑動而移動的。所以這個觸發點的起始位置應該在第一個 item 的中心,最終位置應該在最後一個 item 的中心。我們需要做些數學計算來判斷觸發點和閃爍標記的關聯。
管用嗎?
還是不管用 ?。 onScrolle 方法不是每一個畫素都被觸發的。如果我們滑動 RecycleView 的速度太快,收到的回撥就很少。那麼應該怎麼辦呢?
第三個想法
很簡單。既然不能計算移動的觸發點 —— 因為看起來它不會包含“偏移量”的引數,那就移動“範圍”。當該範圍覆蓋比如說 70% 的 RecycleView 子佈局時,觸發標記的閃爍。不妨把它想想成一個從左至右移動的矩形。讓我們看看實現吧:
Java
public class HorizontalRecyclerViewScrollListener extends RecyclerView.OnScrollListener{
private static final int OFFSET_RANGE = 50;
private static final double COVER_FACTOR = 0.7;
private int[] itemBounds = null;
private final OnItemCoverListener listener;
public HorizontalRecyclerViewScrollListener(final OnItemCoverListener listener){
this.listener=listener;
}
@Override
public void onScrolled(final RecyclerView recyclerView,final int dx,final int dy){
super.onScrolled(recyclerView,dx,dy);
if(itemBounds == null)
fillItemBounds(recyclerView.getAdapter().getItemCount(),recyclerView);
for(int i=0;i<itemBounds.length;i++){
if(isInChildItemsRange(recyclerView.computeHorizontalScrollOffset(),itemBounds[i],OFFSET_RANGE))
listener.onItemCover(i);
}
}
private void fillItemBounds(final int itemsCount,final RecyclerView recyclerView){
itemBounds=new int[itemsCount];
int childWidth=(recyclerView.computeHorizontalScrollRange()-recyclerView.computeHorizontalScrollExtent())/itemsCount;
for(inti=0;i<itemsCount;i++){
itemBounds[i]=(int)(((childWidth*i+childWidth*(i+1))/2)*COVER_FACTOR);
}
}
private boolean isInChildItemsRange(final int offset,final int itemBound,final int range){
int rangeMin=itemBound-range;
int rangeMax=itemBound+range;
return (Math.min(rangeMin,rangeMax)<=offset) && (Math.max(rangeMin,rangeMax)>=offset);
}
public interface OnItemCoverListener{
void onItemCover(final int position);
}
}複製程式碼
首先,我們不希望新程式碼和 Fragment/Activity 混到一起,因此繼承 RecyclerView.OnScrollListener 的類並重寫必要的方法。在建構函式中傳一個 listener 進去,當 RecycleView 的 item 的範圍符合時條件時就呼叫該 listener 的 onItemCover 方法。在 onScrolled 方法中,如果 itemBounds 為空我們可以呼叫 fillItemBounds 進行初始化。否則迴圈判斷所有的邊距,判斷 RecycleView 的 item 是否被指定的範圍覆蓋。
方法 fillItemBounds 以 RecyclerView 的 item 個數為長度建立了一個整數陣列。接下來它計算了子佈局的寬度(也就是 RecyclerView 的 item 的寬度)。在最後它用“item 的範圍”給陣列賦值 —— 事實上,這些就是用來計算 RecycleView 是否處於子佈局內的“中心”點。
當呼叫 onScrolled 方法時,我們遍歷 RecyclerView 的 item,並使用 isInChildItemsRange 方法來判斷他們所處的位置是否在範圍內。該方法實際上就是當我們移動 RecycleView 時候的“矩形”。該方法計算 item 的區域(也就是我們計算並儲存在 itemBounds裡的中心點)與當前的偏移量是否重疊。如果符合條件的話,OnItemCoverListener 會呼叫 onItemCover 方法,傳遞指定的位置(position) 。通過此引數,我們就可以拿到判斷當前的地圖示記是哪個,讓它進行閃爍。
//Implementation of the HorizontalRecyclerViewScrollListener
// HorizontalRecyclerViewScrollListener 的具體實現
...
recyclerView.addOnScrollListener(new HorizontalRecyclerViewScrollListener(this));
}
//OnItemCoverListener method implementation
// 實現 OnItemCoverListener 的方法
@Override
public void onItemCover(final int position){
mapOverlayLayout.showMarker(position);// 在此處重新整理標記
}
//PulseOverlayLayout - see the 2nd article from the series
//PulseOverlayLayout - 參見系列的第二篇
public void showMarker(final int position){
((PulseMarkerView)markersList.get(position)).pulse();
}
//PulseMarkerView - see the 2nd article from the series
//PulseOverlayLayout - 參見系列的第二篇
public void pulse(){
startAnimation(scaleAnimation);
}複製程式碼
效果如下
總結
如我們所見,Android Framework 中有一些了不起的工具,但是在很多情況下還是需要思考怎麼呼叫才能把事情按我們所想的實現。最開始的時候還不是很明確,但是現在我們已經找到解決辦法了 ?。
多謝閱讀!最後一篇會在星期二 4.04 釋出。如果有疑問的話歡迎評論,如果覺得有用的話一定要分享喲!
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、React、前端、後端、產品、設計 等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃。