Android仿百度外卖自定义下拉刷新效果

现如今的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仿百度外卖自定义下拉刷新效果,小编就给大家介绍到这里,希望对大家有所帮助!

肯承认错误则错已改了一半

Android仿百度外卖自定义下拉刷新效果

相关文章:

你感兴趣的文章:

标签云: