[譯]Workcation App – 第三部分. 帶有動畫的標記(Animated Markers) 與 RecyclerView 的互動

Workcation App – 第三部分. 帶有動畫的標記(Animated Markers) 與 RecyclerView 的互動

歡迎閱讀本系列文章的第三篇,此係列文章和我前一段時間完成的「研發」項目有關。在文章裏,我會針對開發中遇到的動畫問題分享一些解決辦法。javascript

Part 1: 自定義 Fragment 轉場html

Part 2: 帶有動畫的標記(Animating Markers) 與 MapOverlayLayout 前端

Part 3: 帶有動畫的標記(Animated Markers) 與 RecyclerView 的互動java

Part 4: 場景(Scenes)和 RecyclerView 的共享元素轉場動畫(Shared Element Transition)react

項目的 Git 地址: Workcation Appandroid

動畫的 Dribbble 地址: dribbble.com/shots/28812…ios

序言

幾個月前咱們開了一個部門會議,在會議上個人朋友 Paweł Szymankiewicz 給我演示了他在本身的「研發」項目上製做的動畫。我很是喜歡這個動畫,會後決定用代碼實現它。我可沒想到到我會攤上啥...git

GIF 1 「動畫效果」github

開始吧!

就像上面 GIF 動畫展現的,須要作的事情有不少。後端

  1. 在點擊底部菜單欄最右方的菜單後,咱們會跳轉到一個新界面。在此界面中,地圖經過縮放和漸顯的轉場動畫在屏幕上方加載,Recycleview 的 item 隨着轉場動畫從底部加載,地圖上的標記點在轉場動畫執行的同時被添加到地圖上.

  2. 當滑動底部的 RecycleView item 的時候,地圖上的標記會經過閃爍來顯示它們的位置(譯者注:原文是show their position on the map,我的認爲 position 有兩層含義:一表明標記在地圖上的位置,二表明標記所對應的 item 在 RecycleView 裏的位置。)

  3. 在點擊一個 item 之後,咱們會進入到新界面。在此界面中,地圖經過動畫方式來顯示出路徑以及起始/結束標記。同時此 RecyclerView 的item 會經過轉場動畫展現一些關於此地點的描述,背景圖片也會放大,還附有更詳細的信息和一個按鈕。

  4. 當後退時,詳情頁經過轉場變成普通的 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 發佈。若是有疑問的話歡迎評論,若是以爲有用的話必定要分享喲!


掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 AndroidiOSReact前端後端產品設計 等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃

相關文章
相關標籤/搜索