Android仿百度外賣自定義下拉刷新效果
現(xiàn)如今的APP各式各樣,同樣也帶來了各種需求,一個(gè)下拉刷新都能玩出花樣了,前兩天訂飯的時(shí)候不經(jīng)意間看到了“百度外賣”的下拉刷新,今天的主題就是它–自定義下拉刷新動(dòng)畫。
看一下實(shí)現(xiàn)效果吧:
動(dòng)畫
我們先來看看Android中的動(dòng)畫吧:
Android中的動(dòng)畫分為三種:
Tween動(dòng)畫,這一類的動(dòng)畫提供了旋轉(zhuǎn)、平移、縮放等效果。
Alpha – 淡入淡出
Scale – 縮放效果
Roate – 旋轉(zhuǎn)效果
Translate – 平移效果
Frame動(dòng)畫(幀動(dòng)畫),這一類動(dòng)畫可以創(chuàng)建一個(gè)Drawable序列,按照指定時(shí)間間歇一個(gè)一個(gè)顯示出來。
Property動(dòng)畫(屬性動(dòng)畫),Android3.0之后引入出來的屬性動(dòng)畫,它更改的是對(duì)象的實(shí)際屬性。
分析
我們可以看到百度外賣的下拉刷新的頭是一個(gè)騎車的快遞員在路上疾行,分析一下我們得到下面的動(dòng)畫:
背景圖片的平移動(dòng)畫
太陽的自旋轉(zhuǎn)動(dòng)畫
兩個(gè)小輪子的自旋轉(zhuǎn)動(dòng)畫
這就很簡(jiǎn)單了,接下來我們?nèi)グ俣韧饷娴膱D片資源文件里找到這幾張圖片:(下載百度外賣的apk直接解壓即可)
定義下拉刷新頭文件:headview.xml
這里注意一下:我們定義了兩張背景圖片的ImageView是為了可以實(shí)現(xiàn)背景的平移動(dòng)畫效果。
<?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>
接下來我們定義動(dòng)畫效果:
背景圖片的平移效果:
實(shí)現(xiàn)兩個(gè)animation xml文件,一個(gè)起始位置在100%,結(jié)束位置在0%,設(shè)置repeat屬性為循環(huán)往復(fù)。
<?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>
另一個(gè)起始位置在0%,結(jié)束位置在-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>
太陽圍繞中心旋轉(zhuǎn)動(dòng)畫:
從0-360度開始循環(huán)旋轉(zhuǎn),旋轉(zhuǎn)所用時(shí)間為1s,旋轉(zhuǎn)中心距離view的左定點(diǎn)上邊緣為50%的距離,也就是正中心。
下面是具體屬性:
android:fromDegrees 起始的角度度數(shù)
android:toDegrees 結(jié)束的角度度數(shù),負(fù)數(shù)表示逆時(shí)針,正數(shù)表示順時(shí)針。如10圈則比android:fromDegrees大3600即可
android:pivotX 旋轉(zhuǎn)中心的X坐標(biāo)
浮點(diǎn)數(shù)或是百分比。浮點(diǎn)數(shù)表示相對(duì)于Object的左邊緣,如5; 百分比表示相對(duì)于Object的左邊緣,如5%; 另一種百分比表示相對(duì)于父容器的左邊緣,如5%p; 一般設(shè)置為50%表示在Object中心
android:pivotY 旋轉(zhuǎn)中心的Y坐標(biāo)
浮點(diǎn)數(shù)或是百分比。浮點(diǎn)數(shù)表示相對(duì)于Object的上邊緣,如5; 百分比表示相對(duì)于Object的上邊緣,如5%; 另一種百分比表示相對(duì)于父容器的上邊緣,如5%p; 一般設(shè)置為50%表示在Object中心
android:duration 表示從android:fromDegrees轉(zhuǎn)動(dòng)到android:toDegrees所花費(fèi)的時(shí)間,單位為毫秒??梢杂脕碛?jì)算速度。
android:interpolator表示變化率,但不是運(yùn)行速度。一個(gè)插補(bǔ)屬性,可以將動(dòng)畫效果設(shè)置為加速,減速,反復(fù),反彈等。默認(rèn)為開始和結(jié)束慢中間快,
android:startOffset 在調(diào)用start函數(shù)之后等待開始運(yùn)行的時(shí)間,單位為毫秒,若為10,表示10ms后開始運(yùn)行
android:repeatCount 重復(fù)的次數(shù),默認(rèn)為0,必須是int,可以為-1表示不停止
android:repeatMode 重復(fù)的模式,默認(rèn)為restart,即重頭開始重新運(yùn)行,可以為reverse即從結(jié)束開始向前重新運(yùn)行。在android:repeatCount大于0或?yàn)閕nfinite時(shí)生效
android:detachWallpaper 表示是否在壁紙上運(yùn)行
android:zAdjustment 表示被animated的內(nèi)容在運(yùn)行時(shí)在z軸上的位置,默認(rèn)為normal。
normal保持內(nèi)容當(dāng)前的z軸順序
top運(yùn)行時(shí)在最頂層顯示
bottom運(yùn)行時(shí)在最底層顯示
<?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>
同理輪子的動(dòng)畫也一樣,不占代碼了。
動(dòng)畫定義完了我們開始定義下拉刷新列表,下拉刷新網(wǎng)上有很多,不詳細(xì)的說了,簡(jiǎn)單的改造一下,根據(jù)刷新狀態(tài)開啟關(guān)閉動(dòng)畫即可。
注釋寫的很詳細(xì),看一下代碼吧:
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坐標(biāo) private float offsetY; //Y軸偏移量 private OnBaiduRefreshListener mOnRefreshListener; //刷新接口 private int state; //狀態(tài)值 private int mFirstVisibleItem; //第一項(xiàng)可見item索引 private boolean isRecord; //是否記錄 private boolean isEnd; //是否結(jié)束 private boolean isRefreable; //是否刷新 private ImageView ivWheel1,ivWheel2; //輪組圖片組件 private ImageView ivRider; //騎手圖片組件 private ImageView ivSun,ivBack1,ivBack2; //太陽、背景圖片1、背景圖片2 private Animation wheelAnimation,sunAnimation; //輪子、太陽動(dòng)畫 private Animation backAnimation1,backAnimation2; //兩張背景圖動(dòng)畫 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(); } /** * 回調(diào)接口,想實(shí)現(xiàn)下拉刷新的listview實(shí)現(xiàn)此接口 * @param onRefreshListener */ public void setOnBaiduRefreshListener(OnBaiduRefreshListener onRefreshListener){ mOnRefreshListener = onRefreshListener; isRefreable = true; } /** * 刷新完畢,從主線程發(fā)送過來,并且改變headerView的狀態(tài)和文字動(dòng)畫信息 */ public void setOnRefreshComplete(){ //一定要將isEnd設(shè)置為true,以便于下次的下拉刷新 isEnd = true; state = DONE; changeHeaderByState(state); } private void init(Context context) { //關(guān)閉view的OverScroll setOverScrollMode(OVER_SCROLL_NEVER); setOnScrollListener(this); //加載頭布局 headView = (RelativeLayout) LayoutInflater.from(context).inflate(R.layout.headview,this,false); //測(cè)量頭布局 measureView(headView); //給ListView添加頭布局 addHeaderView(headView); //設(shè)置頭文件隱藏在ListView的第一項(xiàng) 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); //獲取動(dòng)畫 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)在時(shí)結(jié)束的狀態(tài),即刷新完畢了,可以再次刷新了,在onRefreshComplete中設(shè)置 if (isRefreable) {//如果現(xiàn)在是可刷新狀態(tài) 在setOnMeiTuanListener中設(shè)置為true switch (ev.getAction()){ //用戶按下 case MotionEvent.ACTION_DOWN: //如果當(dāng)前是在listview頂部并且沒有記錄y坐標(biāo) if (mFirstVisibleItem == 0 && !isRecord) { //將isRecord置為true,說明現(xiàn)在已記錄y坐標(biāo) isRecord = true; //將當(dāng)前y坐標(biāo)賦值給startY起始y坐標(biāo) startY = ev.getY(); } break; //用戶滑動(dòng) case MotionEvent.ACTION_MOVE: //再次得到y(tǒng)坐標(biāo),用來和startY相減來計(jì)算offsetY位移值 float tempY = ev.getY(); //再起判斷一下是否為listview頂部并且沒有記錄y坐標(biāo) if (mFirstVisibleItem == 0 && !isRecord) { isRecord = true; startY = tempY; } //如果當(dāng)前狀態(tài)不是正在刷新的狀態(tài),并且已經(jīng)記錄了y坐標(biāo) if (state!=REFRESHING && isRecord ) { //計(jì)算y的偏移量 offsetY = tempY - startY; //計(jì)算當(dāng)前滑動(dòng)的高度 float currentHeight = (-headViewHeight+offsetY/3); //用當(dāng)前滑動(dòng)的高度和頭部headerView的總高度進(jìn)行比 計(jì)算出當(dāng)前滑動(dòng)的百分比 0到1 float currentProgress = 1+currentHeight/headViewHeight; //如果當(dāng)前百分比大于1了,將其設(shè)置為1,目的是讓第一個(gè)狀態(tài)的橢圓不再繼續(xù)變大 if (currentProgress>=1) { currentProgress = 1; } //如果當(dāng)前的狀態(tài)是放開刷新,并且已經(jīng)記錄y坐標(biāo) if (state == RELEASE_TO_REFRESH && isRecord) { setSelection(0); //如果當(dāng)前滑動(dòng)的距離小于headerView的總高度 if (-headViewHeight+offsetY/RATIO<0) { //將狀態(tài)置為下拉刷新狀態(tài) state = PULL_TO_REFRESH; //根據(jù)狀態(tài)改變headerView,主要是更新動(dòng)畫和文字等信息 changeHeaderByState(state); //如果當(dāng)前y的位移值小于0,即為headerView隱藏了 }else if (offsetY<=0) { //將狀態(tài)變?yōu)閐one state = DONE; stopAnim(); //根據(jù)狀態(tài)改變headerView,主要是更新動(dòng)畫和文字等信息 changeHeaderByState(state); } } //如果當(dāng)前狀態(tài)為下拉刷新并且已經(jīng)記錄y坐標(biāo) 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,主要是更新動(dòng)畫和文字等信息 changeHeaderByState(state); //如果當(dāng)前y的位移值小于0,即為headerView隱藏了 }else if (offsetY<=0) { //將狀態(tài)變?yōu)閐one state = DONE; //根據(jù)狀態(tài)改變headerView,主要是更新動(dòng)畫和文字等信息 changeHeaderByState(state); } } //如果當(dāng)前狀態(tài)為done并且已經(jīng)記錄y坐標(biāo) 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來實(shí)現(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; //當(dāng)用戶手指抬起時(shí) case MotionEvent.ACTION_UP: //如果當(dāng)前狀態(tài)為下拉刷新狀態(tài) if (state == PULL_TO_REFRESH) { //平滑的隱藏headerView this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO)+headViewHeight, 500); //根據(jù)狀態(tài)改變headerView changeHeaderByState(state); } //如果當(dāng)前狀態(tài)為放開刷新 if (state == RELEASE_TO_REFRESH) { //平滑的滑到正好顯示headerView this.smoothScrollBy((int)(-headViewHeight+offsetY/RATIO), 500); //將當(dāng)前狀態(tài)設(shè)置為正在刷新 state = REFRESHING; //回調(diào)接口的onRefresh方法 mOnRefreshListener.onRefresh(); //根據(jù)狀態(tài)改變headerView changeHeaderByState(state); } //這一套手勢(shì)執(zhí)行完,一定別忘了將記錄y坐標(biāo)的isRecord改為false,以便于下一次手勢(shì)的執(zhí)行 isRecord = false; break; } } } return super.onTouchEvent(ev); } /** * 根據(jù)狀態(tài)改變headerView的動(dòng)畫和文字顯示 * @param state */ private void changeHeaderByState(int state){ switch (state) { case DONE://如果的隱藏的狀態(tài) //設(shè)置headerView的padding為隱藏 headView.setPadding(0, -headViewHeight, 0, 0); startAnim(); break; case RELEASE_TO_REFRESH://當(dāng)前狀態(tài)為放開刷新 break; case PULL_TO_REFRESH://當(dāng)前狀態(tài)為下拉刷新 startAnim(); break; case REFRESHING://當(dāng)前狀態(tài)為正在刷新 break; default: break; } } /** * 測(cè)量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); } /** * 開啟動(dòng)畫 */ public void startAnim(){ ivBack1.startAnimation(backAnimation1); ivBack2.startAnimation(backAnimation2); ivSun.startAnimation(sunAnimation); ivWheel1.startAnimation(wheelAnimation); ivWheel2.startAnimation(wheelAnimation); } /** * 關(guān)閉動(dòng)畫 */ public void stopAnim(){ ivBack1.clearAnimation(); ivBack2.clearAnimation(); ivSun.clearAnimation(); ivWheel1.clearAnimation(); ivWheel2.clearAnimation(); } }
好了,自定義下拉刷新動(dòng)畫我們就實(shí)現(xiàn)了,其實(shí)很簡(jiǎn)單,所有的下拉刷新動(dòng)畫都類似這樣實(shí)現(xiàn)的。源碼我已經(jīng)上傳到Github上了:
https://github.com/Hankkin/BaiduGoingRefreshLayout
求star啊。有不合理的地方還希望大家多多指正,共同進(jìn)步哈。
關(guān)于Android仿百度外賣自定義下拉刷新效果,小編就給大家介紹到這里,希望對(duì)大家有所幫助!
- Android自定義StepView仿外賣配送進(jìn)度
- Android仿外賣購物車功能
- Android仿美團(tuán)下拉菜單(商品選購)實(shí)例代碼
- Android仿美團(tuán)分類下拉菜單實(shí)例代碼
- Android編程實(shí)現(xiàn)仿美團(tuán)或淘寶的多級(jí)分類菜單效果示例【附demo源碼下載】
- Android仿美團(tuán)淘寶實(shí)現(xiàn)多級(jí)下拉列表菜單功能
- Android模仿美團(tuán)頂部的滑動(dòng)菜單實(shí)例代碼
- 模仿美團(tuán)點(diǎn)評(píng)的Android應(yīng)用中價(jià)格和購買欄懸浮固定的效果
- Android使用RecyclerView仿美團(tuán)分類界面
- Android仿美團(tuán)外賣菜單界面
相關(guān)文章
Android進(jìn)程間通信(IPC)機(jī)制Binder簡(jiǎn)要介紹
本文主要介紹 Android進(jìn)程間通信(IPC)機(jī)制Binder簡(jiǎn)要介紹, 這里介紹了Binder機(jī)制如何實(shí)現(xiàn)進(jìn)程通信機(jī)制,有研究Android源碼的朋友可以看下2016-08-08Input系統(tǒng)截?cái)嗖呗缘姆治雠c應(yīng)用詳解
這篇文章主要為大家介紹了Input系統(tǒng)截?cái)嗖呗缘姆治雠c應(yīng)用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-02-02android 開發(fā)教程之日歷項(xiàng)目實(shí)踐(三)
決定開始學(xué)習(xí) Android 平臺(tái)下的軟件開發(fā),以日歷作為實(shí)踐項(xiàng)目,進(jìn)行一周后,基本完成,有需要的朋友可以參考下2013-01-01Android項(xiàng)目中引入aar包的正確方法介紹
生成aar之后下一步就是如何引用本地的aar文件,下面這篇文章主要給大家介紹了關(guān)于Android項(xiàng)目中引入aar包的相關(guān)資料,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08Android學(xué)習(xí)筆記-保存數(shù)據(jù)到SQL數(shù)據(jù)庫中(Saving Data in SQL Databases)
這篇文章主要介紹了Android學(xué)習(xí)筆記-保存數(shù)據(jù)到SQL數(shù)據(jù)庫中的(Saving Data in SQL Databases)2014-10-10Android TV開發(fā):使用RecycleView實(shí)現(xiàn)橫向的Listview并響應(yīng)點(diǎn)擊事件的代碼
這篇文章主要介紹了Android TV開發(fā):使用RecycleView實(shí)現(xiàn)橫向的Listview并響應(yīng)點(diǎn)擊事件的代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,一起跟隨小編過來看看吧2018-05-05Android畢業(yè)設(shè)計(jì)記事本APP
這篇文章主要介紹了一個(gè)Android畢業(yè)設(shè)計(jì)記事本APP,它是一款輕量級(jí)的便簽工具,使用Java語言開發(fā),風(fēng)格簡(jiǎn)練,可實(shí)現(xiàn)便簽的添加、刪除、修改、查看功能2021-08-08