1. 程式人生 > >Recyclerview下拉刷新干貨(自定義)

Recyclerview下拉刷新干貨(自定義)

今天無聊看看csdn,看到了篇好有 啟發的部落格,因為RecyclerView是我們開發中不可避免的會用到的(可以說用到機率100%),這篇乾貨看了啟發很大啊。再也不愁自定義重新整理介面了。看嚴大大部落格果然每次都受益匪淺 = =

老樣子,開頭先來句居里夫人的名言,是我網上找的,感覺啟發性很大。轉載收藏Make下。

——— 居里夫人


下拉重新整理視差動畫也是這幾天公司的一個動畫,今晚終於不用加班了,加上好多小夥伴問我這個效果,就把這個動畫用部落格的形式介紹給大家吧,對了如果你想和我交流更多,可以加我部落格聯絡方式中的QQ群。

首先要說明,今天講的是自定義下拉重新整理動畫,不是下拉重新整理框架怎麼寫,所以就算不是你想要的,你看看也無防哈哈哈哈……

效果重新整理

下拉重新整理動畫演示

Ultra-Pull-To-Refresh下拉重新整理庫的介紹

推薦這個庫是一方面是因為PullToRefresh的停止更新,另一方面是Ultra-Pull-To-Refresh的合理設計,滿足了我所有的幻想,它唯一的不足是:當頂部巢狀類似ViewPager這種左右滑動的View時下拉重新整理會變的很靈敏,多使用者體驗不太好,不過這一點我已經給出了一個臨時解決方案,如果要知道詳情請移步此部落格: 
http://blog.csdn.net/yanzhenjie1003/article/details/51319181

不過今天的部落格中的庫我已經把修復了的原始碼附上了,所以大家也可以看完本文後直接下載所有原始碼。

自定義動畫的分析

首先是Ultra-Pull-To-Refresh的特點,此庫提供了一個Layout類:PtrFramLayout作為Wrapper來包涵ContentView,今天用到兩個方法:第一個PtrFramLayout#setHeaderView(View)用來設定頭部顯示的重新整理View,第二個PtrFramLayout#addPtrUIHandler(PtrHandler)用來設定監聽使用者下拉狀態、下拉offset、重新整理完成狀態等。

其次是動畫的,根據效果圖,第一點是下拉的時候人物從左側走過來到中間,到中間後手指再繼續往下拉,此時人物也不走了,第二點是當手指鬆開時或者處於下拉狀態時,人物不停的走動,並且背景產生一個相對位移,給人的視覺上造成一個視覺差,也就是我們想要的視差動畫了,這就是整個視差動畫的實現步驟。

那麼幾個動畫拆分開來就是,人物向右中間移動、人物原地踏步、背景無限向左移動。

頭View和重新整理Layout的實現

我把實現步驟分開講解,方便讀者理解:

  1. 實現自定義的頭View。
  2. 繼承PtrFramLayout實現一個ParallaxPtrFrameLayout,設定自定頭和PtrHandler監聽下拉動作。
  3. 實現人物向左走的動畫。
  4. 鬆開手時背景不停的向右移動,人物在原地邁步,形成一個視差上的向右走的動畫。

自定義頭部View

頭View的底下是這樣一個圖: 
頭View背景

那麼一個圖是如何做到不停的向左移動還是無限重複的呢?用HTML做很簡單,但是Android中並沒有repeat這樣的屬性,於是我們想到:在螢幕上放一個ImageView向左移動100%,在這張圖的右側再放一個ImageView,以同樣的速度向左移動100%,結果就是當螢幕上的圖移動到左邊外螢幕的時候,螢幕右邊的圖剛好移動到螢幕上完全顯示,然後我們的動畫又有重複播放的屬性,結合起來就產生了一個背景無限長的動畫效果。對於人物原地踏步就很簡單了,直接用一個ImageView不停的切換圖形成一個人物在走動的視覺效果。

所以我們用兩個ImageView作為背景圖來相間向左移動,用一個ImageView不停的切換圖模擬人物走動,來達到一個人物走動的視差效果,我打算用FrameLayout來作為頭ViewLayout,所以佈局用merge包裹了一下: 
refresh_parallax.xml

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android">

    <ImageView
        android:id="@+id/iv_background_1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/app_name"
        android:src="@drawable/refresh_down_background" />

    <ImageView
        android:id="@+id/iv_background_2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="@string/app_name"
        android:src="@drawable/refresh_down_background" />

    <ImageView
        android:id="@+id/iv_refresh_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:contentDescription="@string/app_name"
        android:scaleType="center" />

</merge>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

然後定義頭View載入剛才寫好的佈局,因為PtrFrameLayout是通過PtrHandler介面來監聽下拉狀態和重新整理狀態,然後以狀態為依據來重新整理頭View的動畫,所以頭View直接實現PtrHandler介面,然後操作自身的狀態和動畫也就更加方便了,所以頭View初步的程式碼是: 
ParallaxHeader

public class ParallaxHeader extends FrameLayout implements PtrUIHandler {

    ImageView mIvBack1;
    ImageView mIvBack2;
    ImageView mIvIcon;

    private void initialize() {
        // 載入剛才的
        LayoutInflater.from(getContext()).inflate(R.layout.refresh_parallax, this);

        // 設定一個藍色天空的背景。
        setBackgroundColor(ContextCompat.getColor(getContext(), R.color.refresh_background));

        mIvBack1 = (ImageView) findViewById(R.id.iv_background_1);
        mIvBack2 = (ImageView) findViewById(R.id.iv_background_2);
        mIvIcon = (ImageView) findViewById(R.id.iv_refresh_icon);
    }

    public ParallaxHeader(Context context) {
        this(context, null, 0);
    }

    public ParallaxHeader(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public ParallaxHeader(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onUIReset(PtrFrameLayout frame) {
        // 重置頭View的動畫狀態,一般停止重新整理動畫。
    }

    @Override
    public void onUIRefreshPrepare(PtrFrameLayout frame) {
        // 準備重新整理的UI。
    }

    @Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {
        // 開始重新整理的UI動畫。
    }

    @Override
    public void onUIRefreshComplete(PtrFrameLayout frame) {
        // 重新整理完成,停止重新整理動畫。
    }

    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        // 手指下拉的時候的狀態,我們的下拉動畫的控制就是通過這個方法:
        // frame是重新整理的root layout。
        // isUnderTouch是手指是否按下,因為還有自動重新整理,手指肯定是鬆開狀態。
        // status是現在的載入狀態,準備、載入中、完成:PREPARE、LOADING、COMPLETE。
        // ptrIndicator是一些下拉偏移量的引數封裝。
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

裡面的程式碼很簡單,就是載入剛才定義好的頭View對應的Layout.xml檔案,然後把兩個背景View和人物View給找出來。頭View定義好了,接下來定義重新整理的Layout

實現ParallaxPtrFrameLayout載入頭View

Ultra-Pull-To-Refresh的重新整理Layout都是繼承PtrFramLayout,然後設定頭View和重新整理狀態監聽等,所以我們定義一個ParallaxPtrFrameLayout繼承PtrFrameLayout,在裡面設定頭ViewPtrHandler等來回調操作的頭View的動畫,很簡單的幾行程式碼:

public class ParallaxPtrFrameLayout extends PtrFrameLayout {

    public ParallaxPtrFrameLayout(Context context) {
        super(context);
        initViews();
    }

    public ParallaxPtrFrameLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initViews();
    }

    public ParallaxPtrFrameLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initViews();
    }

    private void initViews() {
        // 這裡初始化上面的頭View:
        ParallaxHeader parallaxHeader = new ParallaxHeader(getContext());

        // 這裡設定頭View為上面自定義的頭View:
        setHeaderView(parallaxHeader);

        // 下拉和重新整理狀態監聽:
        // 因為ParallaxHeader已經實現過PtrUIHandler介面,所以直接設定為ParallaxHeader:
        addPtrUIHandler(parallaxHeader);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29

由於Ultra-Pull-To-Refresh的合理設計,到這裡為止,我們的頭View和重新整理的Layout就完成了,接下來就專心研究動畫吧。

動畫的實現

上文也提過了,這裡的動畫拆分開幾個,一是下拉的時候人物向右中間移動,二是重新整理的時候人物不停的原地踏步,三是重新整理的時候背景一個向左平移,為了方便理解,這裡把下拉時候人物向右中間移動放到最後來講。

一、人物原地踏步動畫

首先想到的就是幀動畫,沒錯就是這傢伙,用幀動畫可以做到每多少時間換一張圖片,所以我們的人物有三張不同的動畫,不停的切換就形成了一個人物走動並車輪轉動的效果: 
icon1 
icon2 
icon3

我們用幀動畫控制每張圖顯示100毫秒,然後就切換下一張圖,這樣便達到我們說的人物走動的效果了,用xml來實現:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="false">
    <item
        android:drawable="@drawable/refresh_down_icon_1"
        android:duration="100" />
    <item
        android:drawable="@drawable/refresh_down_icon_2"
        android:duration="100" />
    <item
        android:drawable="@drawable/refresh_down_icon_3"
        android:duration="100" />
</animation-list>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

因為這是一個幀動畫,需要在程式碼中觸發,所以我們要把這個動畫放在drawable資料夾,並且把這個drawable當圖片設定頭View中的人物ImageView

<ImageView
    android:id="@+id/iv_refresh_icon"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="bottom"
    android:scaleType="center"
    android:src="@drawable/refresh_down_icon" />
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

二、背景無限向左移動

這個動畫就厲害了word哥,當時我先做出來,然後給iOS的同學講實現的原理,他還是花了點時間來理解的,所以我再費點口舌解釋一下。

這裡是兩個ImageView,一個在螢幕正中央,並且佔據整屏寬,一個在螢幕外的右側,寬度等於螢幕寬度。動畫開始時,螢幕上的ImageView開始一步步向左移動100%,螢幕之外的ImageView以同樣的速度向左移動100%,當螢幕上的ImageView移動到左邊外螢幕的時候,螢幕右邊的圖剛好移動到螢幕上完全顯示,然後我們的動畫又有重複播放的屬性,結合起來就產生了一個背景無限長的動畫效果。

為了方便大家理解,我畫了一張圖:

背景動畫 
圖是畫