現如今的APP各式各樣,同樣也帶來了各種需求,一個下拉重新整理都能玩出花樣了,前兩天訂飯的時候不經意間看到了“百度外賣”的下拉重新整理,今天的主題就是它–自訂下拉重新整理動畫。
看一下實現效果吧:
動畫
我們先來看看Android中的動畫吧:
Android中的動畫分為三種:
Tween動畫,這一類的動畫提供了旋轉、平移、縮放等效果。
Alpha – 淡入淡出
Scale – 縮放效果
Roate – 旋轉效果
Translate – 平移效果
Frame動畫(幀動畫),這一類動畫可以建立一個Drawable序列,按照指定時間間歇一個一個顯示出來。
Property動畫(屬性動畫),Android3.0之後引入出來的屬性動畫,它更改的是對象的實際屬性。
分析
我們可以看到百度外賣的下拉重新整理的頭是一個騎車的快遞員在路上疾行,分析一下我們得到下面的動畫:
背景圖片的平移動畫
太陽的自旋轉動畫
兩個小輪子的自旋轉動畫
這就很簡單了,接下來我們去百度外面的圖片資源檔裡找到這幾張圖片:(下載百度外賣的apk直接解壓即可)
定義下拉重新整理標頭檔:headview.xml
這裡注意一下:我們定義了兩張背景圖片的ImageView是為了可以實現背景的平移動畫效果。
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/iv_back1"android:src="@drawable/pull_back"android:layout_width="match_parent"android:layout_height="100dp" /><ImageViewandroid:id="@+id/iv_back2"android:src="@drawable/pull_back"android:layout_width="match_parent"android:layout_height="100dp" /><RelativeLayoutandroid:id="@+id/main"android:layout_centerHorizontal="true"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageViewandroid:layout_marginTop="45dp"android:id="@+id/iv_rider"android:background="@drawable/pull_rider"android:layout_width="50dp"android:layout_height="50dp" /><ImageViewandroid:id="@+id/wheel1"android:layout_marginLeft="10dp"android:layout_marginTop="90dp"android:background="@drawable/pull_wheel"android:layout_width="15dp"android:layout_height="15dp" /><ImageViewandroid:id="@+id/wheel2"android:layout_marginLeft="40dp"android:layout_marginTop="90dp"android:background="@drawable/pull_wheel"android:layout_width="15dp"android:layout_height="15dp" /></RelativeLayout><ImageViewandroid:id="@+id/ivsun"android:layout_marginTop="20dp"android:layout_toRightOf="@+id/main"android:background="@drawable/pull_sun"android:layout_width="30dp"android:layout_height="30dp" /></RelativeLayout>
接下來我們定義動畫效果:
背景圖片的平移效果:
實現兩個animation xml檔案,一個起始位置在100%,結束位置在0%,設定repeat屬性為迴圈往複。
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"><translate android:fromXDelta="100%p" android:toXDelta="0%p"android:repeatMode="restart"android:interpolator="@android:anim/linear_interpolator"android:repeatCount="infinite"android:duration="5000" /></set>
另一個起始位置在0%,結束位置在-100%
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android" android:interpolator="@android:anim/accelerate_interpolator"><translate android:fromXDelta="0%p" android:toXDelta="-100%p"android:repeatMode="restart"android:interpolator="@android:anim/linear_interpolator"android:repeatCount="infinite"android:duration="5000" /></set>
太陽圍繞中心旋轉動畫:
從0-360度開始迴圈旋轉,旋轉所用時間為1s,旋轉中心距離view的左定點上邊緣為50%的距離,也就是正中心。
下面是具體屬性:
android:fromDegrees 起始的角度度數
android:toDegrees 結束的角度度數,負數表示逆時針,正數表示順時針。如10圈則比android:fromDegrees大3600即可
android:pivotX 旋轉中心的X座標
浮點數或是百分比。浮點數表示相對於Object的左邊緣,如5; 百分比表示相對於Object的左邊緣,如5%; 另一種百分比表示相對於父容器的左邊緣,如5%p; 一般設定為50%表示在Object中心
android:pivotY 旋轉中心的Y座標
浮點數或是百分比。浮點數表示相對於Object的上邊緣,如5; 百分比表示相對於Object的上邊緣,如5%; 另一種百分比表示相對於父容器的上邊緣,如5%p; 一般設定為50%表示在Object中心
android:duration 表示從android:fromDegrees轉動到android:toDegrees所花費的時間,單位為毫秒。可以用來計算速度。
android:interpolator表示變動率,但不是運行速度。一個插補屬性,可以將動畫效果設定為加速,減速,反覆,反彈等。預設為開始和結束慢中間快,
android:startOffset 在調用start函數之後等待開始啟動並執行時間,單位為毫秒,若為10,表示10ms後開始運行
android:repeatCount 重複的次數,預設為0,必須是int,可以為-1表示不停止
android:repeatMode 重複的模式,預設為restart,即重頭開始重新運行,可以為reverse即從結束開始向前重新運行。在android:repeatCount大於0或為infinite時生效
android:detachWallpaper 表示是否在壁紙上運行
android:zAdjustment 表示被animated的內容在運行時在z軸上的位置,預設為normal。
normal保持內容當前的z軸順序
top運行時在最頂層顯示
bottom運行時在最底層顯示
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"><rotateandroid:fromDegrees="0"android:toDegrees="360"android:duration="1000"android:repeatCount="-1"android:pivotX="50%"android:pivotY="50%" /></set>
同理輪子的動畫也一樣,不佔代碼了。
動畫定義完了我們開始定義下拉重新整理列表,下拉重新整理網上有很多,不詳細的說了,簡單的改造一下,根據重新整理狀態開啟關閉動畫即可。
注釋寫的很詳細,看一下代碼吧:
package com.hankkin.baidugoingrefreshlayout;import android.widget.AbsListView;import android.widget.ListView;import android.content.Context;import android.util.AttributeSet;import android.view.LayoutInflater;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.view.animation.Animation;import android.view.animation.AnimationUtils;import android.widget.ImageView;import android.widget.RelativeLayout;/*** Created by Hankkin on 16/4/10.*/public class BaiDuRefreshListView extends ListView implements AbsListView.OnScrollListener{private static final int DONE = 0; //重新整理完畢狀態private static final int PULL_TO_REFRESH = 1; //下拉重新整理狀態private static final int RELEASE_TO_REFRESH = 2; //釋放狀態private static final int REFRESHING = 3; //正在重新整理狀態private static final int RATIO = 3;private RelativeLayout headView; //下拉重新整理頭private int headViewHeight; //頭高度private float startY; //開始Y座標private float offsetY; //Y軸位移量private OnBaiduRefreshListener mOnRefreshListener; //重新整理介面private int state; //狀態值private int mFirstVisibleItem; //第一項可見item索引private boolean isRecord; //是否記錄private boolean isEnd; //是否結束private boolean isRefreable; //是否重新整理private ImageView ivWheel1,ivWheel2; //輪組圖片組件private ImageView ivRider; //騎手圖片組件private ImageView ivSun,ivBack1,ivBack2; //太陽、背景圖片1、背景圖片2private Animation wheelAnimation,sunAnimation; //輪子、太陽動畫private Animation backAnimation1,backAnimation2; //兩張背景圖動畫public BaiDuRefreshListView(Context context) {super(context);init(context);}public BaiDuRefreshListView(Context context, AttributeSet attrs) {super(context, attrs);init(context);}public BaiDuRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);init(context);}public interface OnBaiduRefreshListener{void onRefresh();}/*** 回調介面,想實現下拉重新整理的listview實現此介面* @param onRefreshListener*/public void setOnBaiduRefreshListener(OnBaiduRefreshListener onRefreshListener){mOnRefreshListener = onRefreshListener;isRefreable = true;}/*** 重新整理完畢,從主線程發送過來,並且改變headerView的狀態和文字動畫資訊*/public void setOnRefreshComplete(){//一定要將isEnd設定為true,以便於下次的下拉重新整理isEnd = true;state = DONE;changeHeaderByState(state);}private void init(Context context) {//關閉view的OverScrollsetOverScrollMode(OVER_SCROLL_NEVER);setOnScrollListener(this);//載入頭布局headView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.headview,this,false);//測量頭布局measureView(headView);//給ListView添加頭布局addHeaderView(headView);//設定標頭檔隱藏在ListView的第一項headViewHeight = headView.getMeasuredHeight();headView.setPadding(0, -headViewHeight, 0, 0);//擷取頭布局圖片組件ivRider = (ImageView) headView.findViewById(R.id.iv_rider);ivSun = (ImageView) headView.findViewById(R.id.ivsun);ivWheel1 = (ImageView) headView.findViewById(R.id.wheel1);ivWheel2 = (ImageView) headView.findViewById(R.id.wheel2);ivBack1 = (ImageView) headView.findViewById(R.id.iv_back1);ivBack2 = (ImageView) headView.findViewById(R.id.iv_back2);//擷取動畫wheelAnimation = AnimationUtils.loadAnimation(context, R.anim.tip);sunAnimation = AnimationUtils.loadAnimation(context, R.anim.tip1);backAnimation1 = AnimationUtils.loadAnimation(context, R.anim.a);backAnimation2 = AnimationUtils.loadAnimation(context, R.anim.b);state = DONE;isEnd = true;isRefreable = false;}@Overridepublic void onScrollStateChanged(AbsListView absListView, int i) {}@Overridepublic void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {mFirstVisibleItem = firstVisibleItem;}@Overridepublic boolean onTouchEvent(MotionEvent ev) {if (isEnd) {//如果現在時結束的狀態,即重新整理完畢了,可以再次重新整理了,在onRefreshComplete中設定if (isRefreable) {//如果現在是可重新整理狀態 在setOnMeiTuanListener中設定為trueswitch (ev.getAction()){//使用者按下case MotionEvent.ACTION_DOWN://如果當前是在listview頂部並且沒有記錄y座標if (mFirstVisibleItem == 0 && !isRecord) {//將isRecord置為true,說明現在已記錄y座標isRecord = true;//將當前y座標賦值給startY起始y座標startY = ev.getY();}break;//使用者滑動case MotionEvent.ACTION_MOVE://再次得到y座標,用來和startY相減來計算offsetY位移值float tempY = ev.getY();//再起判斷一下是否為listview頂部並且沒有記錄y座標if (mFirstVisibleItem == 0 && !isRecord) {isRecord = true;startY = tempY;}//如果目前狀態不是正在重新整理的狀態,並且已經記錄了y座標if (state!=REFRESHING && isRecord ) {//計算y的位移量offsetY = tempY - startY;//計算當前滑動的高度float currentHeight = (-headViewHeight+offsetY/3);//用當前滑動的高度和頭部headerView的總高度進行比 計算出當前滑動的百分比 0到1float currentProgress = 1+currentHeight/headViewHeight;//如果當前百分比大於1了,將其設定為1,目的是讓第一個狀態的橢圓不再繼續變大if (currentProgress>=1) {currentProgress = 1;}//如果當前的狀態是放開重新整理,並且已經記錄y座標if (state == RELEASE_TO_REFRESH && isRecord) {setSelection(0);//如果當前滑動的距離小於headerView的總高度if (-headViewHeight+offsetY/RATIO<0) {//將狀態置為下拉重新整理狀態state = PULL_TO_REFRESH;//根據狀態改變headerView,主要是更新動畫和文字等資訊changeHeaderByState(state);//如果當前y的位移值小於0,即為headerView隱藏了}else if (offsetY<=0) {//將狀態變為donestate = DONE;stopAnim();//根據狀態改變headerView,主要是更新動畫和文字等資訊changeHeaderByState(state);}}//如果目前狀態為下拉重新整理並且已經記錄y座標if (state == PULL_TO_REFRESH && isRecord) {setSelection(0);//如果下拉距離大於等於headerView的總高度if (-headViewHeight+offsetY/RATIO>=0) {//將狀態變為放開重新整理state = RELEASE_TO_REFRESH;//根據狀態改變headerView,主要是更新動畫和文字等資訊changeHeaderByState(state);//如果當前y的位移值小於0,即為headerView隱藏了}else if (offsetY<=0) {//將狀態變為donestate = DONE;//根據狀態改變headerView,主要是更新動畫和文字等資訊changeHeaderByState(state);}}//如果目前狀態為done並且已經記錄y座標if (state == DONE && isRecord) {//如果位移值大於0if (offsetY>=0) {//將狀態改為下拉重新整理狀態state = PULL_TO_REFRESH;changeHeaderByState(state);}}//如果為下拉重新整理狀態if (state == PULL_TO_REFRESH) {//則改變headerView的padding來實現下拉的效果headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0,0);}//如果為放開重新整理狀態if (state == RELEASE_TO_REFRESH) {//改變headerView的padding值headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0, 0);}}break;//當使用者手指抬起時case MotionEvent.ACTION_UP://如果目前狀態為下拉重新整理狀態if (state == PULL_TO_REFRESH) {//平滑的隱藏headerViewthis.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO)+headViewHeight, 500);//根據狀態改變headerViewchangeHeaderByState(state);}//如果目前狀態為放開重新整理if (state == RELEASE_TO_REFRESH) {//平滑的滑到正好顯示headerViewthis.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO), 500);//將目前狀態設定為正在重新整理state = REFRESHING;//回調介面的onRefresh方法mOnRefreshListener.onRefresh();//根據狀態改變headerViewchangeHeaderByState(state);}//這一套手勢執行完,一定別忘了將記錄y座標的isRecord改為false,以便於下一次手勢的執行isRecord = false;break;}}}return super.onTouchEvent(ev);}/*** 根據狀態改變headerView的動畫和文字顯示* @param state*/private void changeHeaderByState(int state){switch (state) {case DONE://如果的隱藏的狀態//設定headerView的padding為隱藏headView.setPadding(0, -headViewHeight, 0, 0);startAnim();break;case RELEASE_TO_REFRESH://目前狀態為放開重新整理break;case PULL_TO_REFRESH://目前狀態為下拉重新整理startAnim();break;case REFRESHING://目前狀態為正在重新整理break;default:break;}}/*** 測量View* @param child*/private void measureView(View child) {ViewGroup.LayoutParams p = child.getLayoutParams();if (p == null) {p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);int lpHeight = p.height;int childHeightSpec;if (lpHeight > 0) {childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,MeasureSpec.EXACTLY);} else {childHeightSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);}child.measure(childWidthSpec, childHeightSpec);}/*** 開啟動畫*/public void startAnim(){ivBack1.startAnimation(backAnimation1);ivBack2.startAnimation(backAnimation2);ivSun.startAnimation(sunAnimation);ivWheel1.startAnimation(wheelAnimation);ivWheel2.startAnimation(wheelAnimation);}/*** 關閉動畫*/public void stopAnim(){ivBack1.clearAnimation();ivBack2.clearAnimation();ivSun.clearAnimation();ivWheel1.clearAnimation();ivWheel2.clearAnimation();}}
好了,自訂下拉重新整理動畫我們就實現了,其實很簡單,所有的下拉重新整理動畫都類似這樣實現的。源碼我已經上傳到Github上了:
https://github.com/Hankkin/BaiduGoingRefreshLayout
求star啊。有不合理的地方還希望大家多多指正,共同進步哈。
關於Android仿百度外賣自訂下拉重新整理效果,小編就給大家介紹到這裡,希望對大家有所協助!