好久沒寫博客了活孩,小編之前一段時間一直在找工作物遇,從天津來到了我們的大帝都,感覺還不錯。好了廢話不多說了询兴,開始我們今天的主題吧∧松常現(xiàn)如今的APP各式各樣,同樣也帶來了各種需求诗舰,一個下拉刷新都能玩出花樣了警儒,前兩天訂飯的時候不經(jīng)意間看到了“百度外賣”的下拉刷新,今天的主題就是它--自定義下拉刷新動畫始衅。
看一下實現(xiàn)效果吧:
動畫
我們先來看看Android中的動畫吧:
Android中的動畫分為三種:
- Tween動畫冷蚂,這一類的動畫提供了旋轉、平移汛闸、縮放等效果蝙茶。
- Alpha -- 淡入淡出
- Scale -- 縮放效果
- Roate -- 旋轉效果
- Translate -- 平移效果
- Frame動畫(幀動畫),這一類動畫可以創(chuàng)建一個Drawable序列诸老,按照指定時間間歇一個一個顯示出來隆夯。
- Property動畫(屬性動畫),Android3.0之后引入出來的屬性動畫别伏,它更改的是對象的實際屬性蹄衷。
分析
我們可以看到百度外賣的下拉刷新的頭是一個騎車的快遞員在路上疾行,分析一下我們得到下面的動畫:
- 背景圖片的平移動畫
- 太陽的自旋轉動畫
- 兩個小輪子的自旋轉動畫
這就很簡單了厘肮,接下來我們去百度外面的圖片資源文件里找到這幾張圖片:(下載百度外賣的apk直接解壓即可)
定義下拉刷新頭文件:headview.xml
這里注意一下:我們定義了兩張背景圖片的ImageView是為了可以實現(xiàn)背景的平移動畫效果愧口。
<?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">
<ImageView
android:id="@+id/iv_back1"
android:src="@drawable/pull_back"
android:layout_width="match_parent"
android:layout_height="100dp" />
<ImageView
android:id="@+id/iv_back2"
android:src="@drawable/pull_back"
android:layout_width="match_parent"
android:layout_height="100dp" />
<RelativeLayout
android:id="@+id/main"
android:layout_centerHorizontal="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:layout_marginTop="45dp"
android:id="@+id/iv_rider"
android:background="@drawable/pull_rider"
android:layout_width="50dp"
android:layout_height="50dp" />
<ImageView
android:id="@+id/wheel1"
android:layout_marginLeft="10dp"
android:layout_marginTop="90dp"
android:background="@drawable/pull_wheel"
android:layout_width="15dp"
android:layout_height="15dp" />
<ImageView
android: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>
<ImageView
android: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>
接下來我們定義動畫效果:
背景圖片的平移效果:
實現(xiàn)兩個animation xml文件,一個起始位置在100%类茂,結束位置在0%耍属,設置repeat屬性為循環(huán)往復。
<?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度開始循環(huán)旋轉厚骗,旋轉所用時間為1s,旋轉中心距離view的左定點上邊緣為50%的距離兢哭,也就是正中心领舰。
下面是具體屬性:
android:fromDegrees 起始的角度度數(shù)
android:toDegrees 結束的角度度數(shù),負數(shù)表示逆時針迟螺,正數(shù)表示順時針冲秽。如10圈則比android:fromDegrees大3600即可
android:pivotX 旋轉中心的X坐標
浮點數(shù)或是百分比。浮點數(shù)表示相對于Object的左邊緣煮仇,如5; 百分比表示相對于Object的左邊緣劳跃,如5%; 另一種百分比表示相對于父容器的左邊緣,如5%p; 一般設置為50%表示在Object中心
android:pivotY 旋轉中心的Y坐標
浮點數(shù)或是百分比浙垫。浮點數(shù)表示相對于Object的上邊緣,如5; 百分比表示相對于Object的上邊緣,如5%; 另一種百分比表示相對于父容器的上邊緣夹姥,如5%p; 一般設置為50%表示在Object中心
android:duration 表示從android:fromDegrees轉動到android:toDegrees所花費的時間杉武,單位為毫秒≌奘郏可以用來計算速度轻抱。
android:interpolator表示變化率,但不是運行速度旦部。一個插補屬性祈搜,可以將動畫效果設置為加速,減速士八,反復容燕,反彈等。默認為開始和結束慢中間快婚度,
android:startOffset 在調用start函數(shù)之后等待開始運行的時間蘸秘,單位為毫秒,若為10蝗茁,表示10ms后開始運行
android:repeatCount 重復的次數(shù)醋虏,默認為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">
<rotate
android:fromDegrees="0"
android:toDegrees="360"
android:duration="1000"
android:repeatCount="-1"
android:pivotX="50%"
android:pivotY="50%" />
</set>
同理輪子的動畫也一樣柑肴,不占代碼了。
動畫定義完了我們開始定義下拉刷新列表旬薯,下拉刷新網(wǎng)上有很多晰骑,不詳細的說了,簡單的改造一下绊序,根據(jù)刷新狀態(tài)開啟關閉動畫即可硕舆。
注釋寫的很詳細,看一下代碼吧:
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; //刷新完畢狀態(tài)
private static final int PULL_TO_REFRESH = 1; //下拉刷新狀態(tài)
private static final int RELEASE_TO_REFRESH = 2; //釋放狀態(tài)
private static final int REFRESHING = 3; //正在刷新狀態(tài)
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; //狀態(tài)值
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抚官、背景圖片2
private 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();
}
/**
* 回調接口阶捆,想實現(xiàn)下拉刷新的listview實現(xiàn)此接口
* @param onRefreshListener
*/
public void setOnBaiduRefreshListener(OnBaiduRefreshListener onRefreshListener){
mOnRefreshListener = onRefreshListener;
isRefreable = true;
}
/**
* 刷新完畢凌节,從主線程發(fā)送過來钦听,并且改變headerView的狀態(tài)和文字動畫信息
*/
public void setOnRefreshComplete(){
//一定要將isEnd設置為true,以便于下次的下拉刷新
isEnd = true;
state = DONE;
changeHeaderByState(state);
}
private void init(Context context) {
//關閉view的OverScroll
setOverScrollMode(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;
}
@Override
public void onScrollStateChanged(AbsListView absListView, int i) {
}
@Override
public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
if (isEnd) {//如果現(xiàn)在時結束的狀態(tài)倍奢,即刷新完畢了朴上,可以再次刷新了,在onRefreshComplete中設置
if (isRefreable) {//如果現(xiàn)在是可刷新狀態(tài) 在setOnMeiTuanListener中設置為true
switch (ev.getAction()){
//用戶按下
case MotionEvent.ACTION_DOWN:
//如果當前是在listview頂部并且沒有記錄y坐標
if (mFirstVisibleItem == 0 && !isRecord) {
//將isRecord置為true卒煞,說明現(xiàn)在已記錄y坐標
isRecord = true;
//將當前y坐標賦值給startY起始y坐標
startY = ev.getY();
}
break;
//用戶滑動
case MotionEvent.ACTION_MOVE:
//再次得到y(tǒng)坐標痪宰,用來和startY相減來計算offsetY位移值
float tempY = ev.getY();
//再起判斷一下是否為listview頂部并且沒有記錄y坐標
if (mFirstVisibleItem == 0 && !isRecord) {
isRecord = true;
startY = tempY;
}
//如果當前狀態(tài)不是正在刷新的狀態(tài),并且已經(jīng)記錄了y坐標
if (state!=REFRESHING && isRecord ) {
//計算y的偏移量
offsetY = tempY - startY;
//計算當前滑動的高度
float currentHeight = (-headViewHeight+offsetY/3);
//用當前滑動的高度和頭部headerView的總高度進行比 計算出當前滑動的百分比 0到1
float currentProgress = 1+currentHeight/headViewHeight;
//如果當前百分比大于1了畔裕,將其設置為1衣撬,目的是讓第一個狀態(tài)的橢圓不再繼續(xù)變大
if (currentProgress>=1) {
currentProgress = 1;
}
//如果當前的狀態(tài)是放開刷新,并且已經(jīng)記錄y坐標
if (state == RELEASE_TO_REFRESH && isRecord) {
setSelection(0);
//如果當前滑動的距離小于headerView的總高度
if (-headViewHeight+offsetY/RATIO<0) {
//將狀態(tài)置為下拉刷新狀態(tài)
state = PULL_TO_REFRESH;
//根據(jù)狀態(tài)改變headerView扮饶,主要是更新動畫和文字等信息
changeHeaderByState(state);
//如果當前y的位移值小于0具练,即為headerView隱藏了
}else if (offsetY<=0) {
//將狀態(tài)變?yōu)閐one
state = DONE;
stopAnim();
//根據(jù)狀態(tài)改變headerView,主要是更新動畫和文字等信息
changeHeaderByState(state);
}
}
//如果當前狀態(tài)為下拉刷新并且已經(jīng)記錄y坐標
if (state == PULL_TO_REFRESH && isRecord) {
setSelection(0);
//如果下拉距離大于等于headerView的總高度
if (-headViewHeight+offsetY/RATIO>=0) {
//將狀態(tài)變?yōu)榉砰_刷新
state = RELEASE_TO_REFRESH;
//根據(jù)狀態(tài)改變headerView贴届,主要是更新動畫和文字等信息
changeHeaderByState(state);
//如果當前y的位移值小于0靠粪,即為headerView隱藏了
}else if (offsetY<=0) {
//將狀態(tài)變?yōu)閐one
state = DONE;
//根據(jù)狀態(tài)改變headerView,主要是更新動畫和文字等信息
changeHeaderByState(state);
}
}
//如果當前狀態(tài)為done并且已經(jīng)記錄y坐標
if (state == DONE && isRecord) {
//如果位移值大于0
if (offsetY>=0) {
//將狀態(tài)改為下拉刷新狀態(tài)
state = PULL_TO_REFRESH;
changeHeaderByState(state);
}
}
//如果為下拉刷新狀態(tài)
if (state == PULL_TO_REFRESH) {
//則改變headerView的padding來實現(xiàn)下拉的效果
headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0,0);
}
//如果為放開刷新狀態(tài)
if (state == RELEASE_TO_REFRESH) {
//改變headerView的padding值
headView.setPadding(0,(int)(-headViewHeight+offsetY/RATIO) ,0, 0);
}
}
break;
//當用戶手指抬起時
case MotionEvent.ACTION_UP:
//如果當前狀態(tài)為下拉刷新狀態(tài)
if (state == PULL_TO_REFRESH) {
//平滑的隱藏headerView
this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO)+headViewHeight, 500);
//根據(jù)狀態(tài)改變headerView
changeHeaderByState(state);
}
//如果當前狀態(tài)為放開刷新
if (state == RELEASE_TO_REFRESH) {
//平滑的滑到正好顯示headerView
this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO), 500);
//將當前狀態(tài)設置為正在刷新
state = REFRESHING;
//回調接口的onRefresh方法
mOnRefreshListener.onRefresh();
//根據(jù)狀態(tài)改變headerView
changeHeaderByState(state);
}
//這一套手勢執(zhí)行完毫蚓,一定別忘了將記錄y坐標的isRecord改為false占键,以便于下一次手勢的執(zhí)行
isRecord = false;
break;
}
}
}
return super.onTouchEvent(ev);
}
/**
* 根據(jù)狀態(tài)改變headerView的動畫和文字顯示
* @param state
*/
private void changeHeaderByState(int state){
switch (state) {
case DONE://如果的隱藏的狀態(tài)
//設置headerView的padding為隱藏
headView.setPadding(0, -headViewHeight, 0, 0);
startAnim();
break;
case RELEASE_TO_REFRESH://當前狀態(tài)為放開刷新
break;
case PULL_TO_REFRESH://當前狀態(tài)為下拉刷新
startAnim();
break;
case REFRESHING://當前狀態(tài)為正在刷新
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();
}
}
好了,自定義下拉刷新動畫我們就實現(xiàn)了元潘,其實很簡單畔乙,所有的下拉刷新動畫都類似這樣實現(xiàn)的。源碼我已經(jīng)上傳到Github上了:
https://github.com/Hankkin/BaiduGoingRefreshLayout
求star啊翩概。有不合理的地方還希望大家多多指正牲距,共同進步哈。