美团下拉刷新效果实现

在上一次【http://www.cnblogs.com/webor2006/p/7989766.html】完成了文字箭头的下拉刷新效果,其实是一个通用下拉刷新方案,这次接着这个刷新方案实现一下美团外卖下拉的效果,这个我想用过美团的亲们肯定都比较熟了,还是看一下本尊:

而这次实现的最终效果如下:

 

效果基本上跟本尊差不多,而且只是换了个头部效果,其核心下拉的代码全是上一次咱们写好的,所以通过这个框架就能实现万能下拉刷新的效果,所以接下来看一下如何在尽量小的改动前提下达到我们更换头的下拉的效果,在正式实现美团效果之前先需要对代码进行一些调整,还是基于上次写的万能下拉刷新的代码进行,下面开始:

内容视图变为其它视图后的处理:

既然咱们打造的是一个万能的下拉刷新,而上一次内容区域是用的RecyclerView,如下:

那此时将内容进行更换一下,测试下是否都能正常的下拉刷新,首先将内容换成一个文本,如下:

由于将RecyclerView从布局中注释掉了,那在MainActivity中关于它的初始化也得暂且注释掉,否则会编译错误的:

//加入RecyclerView之后的事件处理
public class MainActivity extends AppCompatActivity implements RefreshLayout.OnRefreshingListener {

    private RefreshLayout lay_refresh_layout;
    private RecyclerView lay_rlv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        lay_refresh_layout = (RefreshLayout) findViewById(R.id.lay_refresh_layout);
        lay_refresh_layout.setSelfHeaderViewManager(new SelfHeaderViewManager(this));
        lay_refresh_layout.setOnRefreshingListener(this);
//        initRecyclerView();
    }

    @Override
    public void onRefresh() {
        //获取网络数据
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                //当获取完数据后通知RefreshLayout还原
                lay_refresh_layout.endRefreshing();
            }
        }, 2000);
    }

    private void initRecyclerView() {
        lay_rlv = (RecyclerView) findViewById(R.id.lay_rlv);
        lay_rlv.setLayoutManager(new LinearLayoutManager(this));
        List<String> datas = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            datas.add("条目" + i);
        }
        MainActivity.MyAdapter adapter = new MainActivity.MyAdapter(datas);
        lay_rlv.setAdapter(adapter);
    }


    private class MyAdapter extends RecyclerView.Adapter<MainActivity.MyAdapter.MyViewHolder> {
        private List<String> datas;

        public MyAdapter(List<String> datas) {
            this.datas = datas;
        }

        @Override
        public MainActivity.MyAdapter.MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            View view = LayoutInflater.from(parent.getContext()).inflate(android.R.layout.simple_list_item_1, null);//TODO
            return new MainActivity.MyAdapter.MyViewHolder(view);
        }

        @Override
        public void onBindViewHolder(MainActivity.MyAdapter.MyViewHolder holder, int position) {
            holder.tv.setText(datas.get(position));
        }

        @Override
        public int getItemCount() {
            return datas.size();
        }

        class MyViewHolder extends RecyclerView.ViewHolder {
            private TextView tv;

            public MyViewHolder(View itemView) {
                super(itemView);
                tv = (TextView) itemView.findViewById(android.R.id.text1);
            }
        }
    }
}

编译运行:

嗯~~木问题,那接下来再将内容由文本换成ScrollView试试,修改布局文件:

<?xml version="1.0" encoding="utf-8"?>
<com.pulltorefresh.test.meituan.RefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/lay_refresh_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.pulltorefresh.test.meituan.MainActivity">

    <!--
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="测试文本" />

        <android.support.v7.widget.RecyclerView
            android:id="@+id/lay_rlv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    -->

    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="150dp"
                android:text="测试文本"
                android:textSize="30dp" />
        </LinearLayout>
    </ScrollView>

</com.pulltorefresh.test.meituan.RefreshLayout>

这时编译运行:

呃~~有问题,居然换成ScrollView之后下拉刷新出不来了,这是什么原因造成的呢?照理来说如果发现滑动到顶部之后,再往下拉则应该拉出头部的,那咱们先看一下这块的代码逻辑:

而此时已经木有recyclerView这个控件了,所以RefreshScollingUtil.isRecyclerViewToTop(null)参数就为null了,这时看一下它里面的具体逻辑:

所以~~解决之道就是需要加一个ScollView的判断,具体如下:

/**
 * 下拉刷新控件
 */
public class RefreshLayout extends LinearLayout {
    //constants
    /* 头部视图超出最大范围的系数 */
    public static final float MAX_WHOLE_HEADER_VIEW_PADDING_TOP_RADIO = 0.3f;
    /* 阻尼效果的拉出系数 */
    public static final float DRAG_RADIO = 1.8f;

    /* 刷新状态 */
    public enum RefreshStatus {
        IDLE/*静止*/, PULL_DOWN/*下拉*/, RELEASE_REFRESH/*释放刷新*/, REFRESHING/*刷新*/
    }

    //views
    /* 头部根布局 */
    private LinearLayout wholeHeaderView;

    //variables
    /* 具体头部管理器 */
    private SelfHeaderViewManager selfHeaderViewManager;
    /* 头部视图的最大上边距,也就是默认需通过它来将头部隐藏掉 */
    private int minWholeHeaderViewPaddingTop;
    /* 头部视图的最大上边距=头部视图的高度*头部视图超出最大范围的系数,也就是下拉头部显示高度的最大值 */
    private int maxWholeHeaderViewPaddingTop;
    private int downY;
    private RefreshLayout.RefreshStatus currentStatus = RefreshLayout.RefreshStatus.IDLE;
    /* 刷新回调监听 */
    private RefreshLayout.OnRefreshingListener onRefreshingListener;
    private float interceptDownX;
    private float interceptDownY;
    private RecyclerView recyclerView;
    private ScrollView scrollView;

    public void setOnRefreshingListener(RefreshLayout.OnRefreshingListener onRefreshingListener) {
        this.onRefreshingListener = onRefreshingListener;
    }

    /**
     * 设置自定义头部管理器
     */
    public void setSelfHeaderViewManager(SelfHeaderViewManager selfHeaderViewManager) {
        this.selfHeaderViewManager = selfHeaderViewManager;
        initSelfHeaderView();
    }

    public RefreshLayout(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.setOrientation(LinearLayout.VERTICAL);
        init();
    }

    private void init() {
        //1、初始化头部的视图
        initWholeHeaderView();
    }

    private void initWholeHeaderView() {
        //动态添加一个头部的根部局,以便未来可以动态更换头部:比如上下箭头效果、美团效果等
        wholeHeaderView = new LinearLayout(getContext());
        wholeHeaderView.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
        wholeHeaderView.setBackgroundColor(Color.parseColor("#FF4081"));
        addView(wholeHeaderView);
    }

    //初始化具体的头部内容
    private void initSelfHeaderView() {
        View selfHeaderView = selfHeaderViewManager.getSelfHeaderView();
        int selfHeaderViewMeasuredHeight = this.selfHeaderViewManager.getSelfHeaderViewHeight();
        minWholeHeaderViewPaddingTop = -selfHeaderViewMeasuredHeight;
        //最大边界定义为头部高度的30%
        maxWholeHeaderViewPaddingTop = (int) (selfHeaderViewMeasuredHeight * MAX_WHOLE_HEADER_VIEW_PADDING_TOP_RADIO);
        //利用给头部根布局设置padding为负达到隐藏它里面子视图的效果
        wholeHeaderView.setPadding(0, minWholeHeaderViewPaddingTop, 0, 0);
        wholeHeaderView.addView(selfHeaderView);
    }

    //处理滑动冲突
    @Override
    public boolean onInterceptTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                interceptDownX = event.getX();
                interceptDownY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float dy = event.getY() - interceptDownY;
                //1、y方向的变化:y方向的变化量>x方向的变化量
                if (Math.abs(event.getX() - interceptDownX) < Math.abs(dy)) {
                    //2、y方向向下滑动
                    if (dy > 0 && handleRefresh()) {
                        return true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                break;
        }
        return super.onInterceptTouchEvent(event);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        View contentView = getChildAt(1);
        if (contentView instanceof RecyclerView) {
            this.recyclerView = (RecyclerView) contentView;
        } else if (contentView instanceof ScrollView) {
            this.scrollView = (ScrollView) contentView;
        }
    }

    //判断是否RecyclerListView滑到了顶端,只有在顶端的时候再向下滑才会出现刷新头部
    private boolean handleRefresh() {
        if (RefreshScrollingUtil.isRecyclerViewToTop(recyclerView)) {
            return true;
        }
        if (RefreshScrollingUtil.isScrollViewOrWebViewToTop(scrollView)) {
            return true;
        }
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = (int) event.getY();
                Log.e("cexo", "onTouchEvent() ACTION_DOWN downY=" + downY);
                return true;
            case MotionEvent.ACTION_MOVE:
                if (handleActionMove(event))
                    return true;
                break;
            case MotionEvent.ACTION_UP:
                if (handleActionUp(event))
                    return true;
                break;
        }
        return super.onTouchEvent(event);//注意:由于将来会套在ListView上面,所以这里不能一股脑的将它返回true
    }

    private boolean handleActionMove(MotionEvent event) {
        if (currentStatus == RefreshLayout.RefreshStatus.REFRESHING)//如果是刷新状态了,则不允许移动了
            return false;
        //由于在down的时候,当前控件并没有拦截事件,down事件被recyclerview消费掉了,
        // 当前控件直接进入move状态,由于当前控件的onTouchEvent的down事件未执行,造成downY为0,我们要在move的时候给downY重新赋值
        if (downY == 0)
            downY = (int) event.getY();
        int moveY = (int) event.getY();
        int dY = moveY - downY;
        Log.e("cexo", "dY:" + dY + ";moveY:" + moveY + "downY:" + downY);
        //只有向下移动才能拉出头部
        if (dY > 0) {
//            int paddingTop = minWholeHeaderViewPaddingTop + dY;
            //阻尼效果:就是类似弹簧的效果,随距离越来越长,拉动越来越难,让dy除以一个系数,不让它是线性变化
            int paddingTop = (int) (minWholeHeaderViewPaddingTop + dY / DRAG_RADIO);
            Log.e("cexo", "paddingTop:" + paddingTop);
            if (paddingTop < 0 && currentStatus != RefreshLayout.RefreshStatus.PULL_DOWN) {
                currentStatus = RefreshLayout.RefreshStatus.PULL_DOWN;
                //改变文字为下拉刷新
                handleRefreshStatusChanged();
            } else if (paddingTop >= 0 && currentStatus != RefreshLayout.RefreshStatus.RELEASE_REFRESH) {
                currentStatus = RefreshLayout.RefreshStatus.RELEASE_REFRESH;
                //改变文字为释放刷新,并箭头进行旋转
                handleRefreshStatusChanged();
            }
            //判断如果paddingTop>maxWholeHeaderViewPaddingTop,就不能再滑动了
            paddingTop = Math.min(paddingTop, maxWholeHeaderViewPaddingTop);
            wholeHeaderView.setPadding(0, paddingTop, 0, 0);
            return true;
        }
        return false;
    }

    private boolean handleActionUp(MotionEvent event) {
        downY = 0;//解决有时后现拉拉不动的bug
        if (currentStatus == RefreshLayout.RefreshStatus.PULL_DOWN) {
            //如果是下拉刷新状态则松开手时直接让头部隐藏
            hiddenRefreshView();
            currentStatus = RefreshLayout.RefreshStatus.IDLE;
            //如果换为美团下拉刷新等,当头部回到初始状态时需要做一些还原操作
            handleRefreshStatusChanged();
        } else if (currentStatus == RefreshLayout.RefreshStatus.RELEASE_REFRESH) {
            beginRefreshing();
        }
        //只要将头部拉出一点点UP事件就由当前控件处理
        return wholeHeaderView.getPaddingTop() > minWholeHeaderViewPaddingTop;
    }

    private void hiddenRefreshView() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(wholeHeaderView.getPaddingTop(), minWholeHeaderViewPaddingTop);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取值动画在动画变化过程中的值
                int currentPaddingTop = (int) valueAnimator.getAnimatedValue();
                wholeHeaderView.setPadding(0, currentPaddingTop, 0, 0);
            }
        });
        valueAnimator.setDuration(300);
        valueAnimator.start();
    }

    /**
     * 开始刷新
     */
    private void beginRefreshing() {
        currentStatus = RefreshLayout.RefreshStatus.REFRESHING;
        changeHeaderViewPaddingTopToZero();
        handleRefreshStatusChanged();
        if (onRefreshingListener != null)
            onRefreshingListener.onRefresh();
    }

    /**
     * 结束刷新
     */
    public void endRefreshing() {
        hiddenRefreshView();
        currentStatus = RefreshLayout.RefreshStatus.IDLE;
        //做SelfHeaderView的还原处理
        selfHeaderViewManager.endRefreshing();
    }

    /**
     * 将头部的paddingTop改变为0,也就是还原成头部的高度
     */
    private void changeHeaderViewPaddingTopToZero() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(wholeHeaderView.getPaddingTop(), 0);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                //获取值动画在动画变化过程中的值
                int currentPaddingTop = (int) valueAnimator.getAnimatedValue();
                wholeHeaderView.setPadding(0, currentPaddingTop, 0, 0);
            }
        });
        valueAnimator.setDuration(300);
        valueAnimator.start();
    }

    /**
     * 根据当前的下拉状态来做界面刷新,具体实现由manager处理
     */
    private void handleRefreshStatusChanged() {
        switch (currentStatus) {
            case IDLE:
                selfHeaderViewManager.changeToIdle();
                break;
            case PULL_DOWN:
                selfHeaderViewManager.changeToPullDown();
                break;
            case RELEASE_REFRESH:
                selfHeaderViewManager.changeToReleaseRefresh();
                break;
            case REFRESHING:
                selfHeaderViewManager.changeToRefreshing();
                break;
        }
    }

    public interface OnRefreshingListener {
        void onRefresh();
    }
}

这时再编译运行:

嗯~~这次算是解决了一个小缺陷,在继续实现之前还是将布局还原成RecyclerView。

SelfHeaderViewManager的基类抽象:

接下来则开始要着手实现美团的下拉刷新效果啦,不过在实现之前还得对代码进行一个抽象重构,因为目前代码的框架还不太灵活,为了打造“万能”,所以这个抽象是非常有必要的,怎么个抽象法呢? 还记得SelfHeaderViewManager这个头部管理器类吧,当时之所以设计这个类也就是为了适应未来不同的头部下拉效果,所以照理它应该是一个抽象的类,封装了通用的处理逻辑,而不同的头部效果则由具体的头部管理器去提供,而目前对于咱们的这个带文字箭头的头部管理端是直接写死的,如下:

显然这是不合理的,所以接下来就得将其合理化,首先咱们定义一个默认的头部管理器,直接从SelfHeaderViewManager类中进行拷贝重命名,其效果就是咱们目前看到的带箭头的这个效果,如下:

这时NormalSelfHeaderViewManager和SelfHeaderViewManager的代码肯定是一模一样的,此时将NormalSelfHeaderViewManager去继承SelfHeaderViewManager,当然此时会报错:

接下来就是要将通用的行为全部提到SelfHeaderViewManager抽象类中啦,所以下面开始:

先分析一下哪些是通用的:

所以将这些行为进行抽象化,都是需要由具体的管理器来提供的,具体如下:

此时则需要对NormalSelfHeaderViewManager基于这个基类进行相应的改造,对抽象方法进行实现,并且将公共的部分给删除,此时它就变成这样了:

/**
 * 默认的头部视图管理器
 */
public class NormalSelfHeaderViewManager extends SelfHeaderViewManager {

    /* 箭头旋转向上动画 */
    private RotateAnimation upAnimation;
    /* 箭头旋转向下动画 */
    private RotateAnimation downAnimation;
    /* Loading动画 */
    private AnimationDrawable animationDrawable;

    /* 提示文本 */
    private TextView tv_normal_refresh_header_status;
    /* 箭头 */
    private ImageView iv_normal_refresh_header_arrow;
    /* loadingView */
    private ImageView iv_normal_refresh_header_loading;

    public NormalSelfHeaderViewManager(Context context) {
        super(context);
        initAnimation();
    }

    private void initAnimation() {
        upAnimation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        upAnimation.setDuration(100);
        //动画执行完成后不会回到原点
        upAnimation.setFillAfter(true);

        downAnimation = new RotateAnimation(-180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        downAnimation.setDuration(100);
        //动画执行完成后不会回到原点
        downAnimation.setFillAfter(true);
    }

    @Override
    protected View getSelfHeaderView() {
        if (selfHeaderView == null) {
            selfHeaderView = View.inflate(context, R.layout.view_refresh_header_normal, null);
            selfHeaderView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            tv_normal_refresh_header_status = selfHeaderView.findViewById(R.id.tv_normal_refresh_header_status);
            iv_normal_refresh_header_arrow = selfHeaderView.findViewById(R.id.iv_normal_refresh_header_arrow);
            iv_normal_refresh_header_loading = selfHeaderView.findViewById(R.id.iv_normal_refresh_header_loading);
            animationDrawable = (AnimationDrawable) iv_normal_refresh_header_loading.getDrawable();
        }
        return selfHeaderView;
    }

    @Override
    protected void changeToIdle() {
        //TODO
    }

    @Override
    protected void changeToPullDown() {
        tv_normal_refresh_header_status.setText("下拉刷新");
        iv_normal_refresh_header_arrow.startAnimation(downAnimation);
    }

    @Override
    protected void changeToReleaseRefresh() {
        tv_normal_refresh_header_status.setText("释放刷新");
        iv_normal_refresh_header_arrow.startAnimation(upAnimation);
    }

    @Override
    protected void changeToRefreshing() {
        tv_normal_refresh_header_status.setText("加载中...");
        //由于iv_normal_refresh_header_arrow之前设置过动画,所以在隐藏之前先要清除动画之后再设置隐藏这样隐藏才会生效
        iv_normal_refresh_header_arrow.clearAnimation();
        iv_normal_refresh_header_arrow.setVisibility(View.INVISIBLE);
        iv_normal_refresh_header_loading.setVisibility(View.VISIBLE);
        animationDrawable.start();
    }

    @Override
    protected void endRefreshing() {
        tv_normal_refresh_header_status.setText("下拉刷新");
        iv_normal_refresh_header_loading.setVisibility(View.INVISIBLE);
        iv_normal_refresh_header_arrow.setVisibility(View.VISIBLE);
        downAnimation.setDuration(0);//让箭头立即旋转至向下方向
        iv_normal_refresh_header_arrow.startAnimation(downAnimation);
    }
}

最后在设置头部管理器时就得更改为:

再去编译运行,保证运行跟之前的效果一样。

MeiTuan的SelfHeaderViewManager基本实现:

有了前期的准备工作之后,接下来就可以真正开始实现美团的头部效果啦,下面来看一下在万能刷新框架之下要想加入一个新的下拉效果是何等的简单:

首先新建一个美团的头部管理器并继承管理器基类,并重写父类的抽象方法,如下:

/**
 * 美团的头部视图管理器,分为三个阶段:
 * 1、缩放阶段:PULL_DOWN;
 * 2、小人跳出来的阶段 RELEASE_REFRESH;
 * 3、小人头部左右摇晃阶段:REFRESHING;
 */
public class MeiTuanSelfHeaderViewManager extends SelfHeaderViewManager {

    public MeiTuanSelfHeaderViewManager(Context context) {
        super(context);
    }

    @Override
    protected View getSelfHeaderView() {
        return null;
    }

    @Override
    protected void changeToIdle() {

    }

    @Override
    protected void changeToPullDown() {

    }

    @Override
    protected void changeToReleaseRefresh() {

    }

    @Override
    protected void changeToRefreshing() {

    }

    @Override
    protected void endRefreshing() {

    }
}

然后将它设置到管理器中,如下:

接下来要做的就是填充这些方法,首先当然得提供美团头部视图的View啦,先准备布局文件,如下:

view_refresh_header_meituan.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:orientation="vertical">

    <FrameLayout
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:layout_marginBottom="10dp"
        android:layout_marginTop="10dp">

        <ImageView
            android:id="@+id/iv_meituan_pull_down"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@mipmap/refresh_mt_pull_down" />

        <ImageView
            android:id="@+id/iv_meituan_release_refreshing"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:visibility="invisible" />
    </FrameLayout>
</LinearLayout>

其布局文件比较简单中,下拉刷新一个ImageView,释放刷新一个ImageView,而对于下拉刷新而言其实就是一张表态的图片refresh_mt_pull_down.png,如下:

那我们看效果在下拉时这个图片是会随着我们手指的滑动不断伸缩的嘛,那直接对图片进行缩放既可,这块不需要额外的资源图。

另外对于拉出头部之后,会有一个小人跳出的动画,而释放刷新之后小人会左右摇晃脑袋的动画,实际上都是用图片一帧帧播放出来的,也就是执行一个帧动画,所以下面将这两个动画文件定义好:

小人跳出动画:release_mt_refresh.xml:

<?xml version="1.0" encoding="utf-8"?>
<animation-list xmlns:android="http://schemas.android.com/apk/res/android"
    android:oneshot="true">
    <item
        android:drawable="@mipmap/refresh_mt_change_to_release_refresh_01"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_change_to_release_refresh_02"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_change_to_release_refresh_03"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_change_to_release_refresh_04"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_change_to_release_refresh_05"
        android:duration="100" />
</animation-list>

其涉及到的图片按顺序如下:

小人左右摇晃动画:refresh_mt_refreshing.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="@mipmap/refresh_mt_refreshing_01"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_02"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_03"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_04"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_05"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_06"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_07"
        android:duration="100" />
    <item
        android:drawable="@mipmap/refresh_mt_refreshing_08"
        android:duration="100" />
</animation-list>

其涉及到的图片按顺序如下:

接下来先填充布局到管理器当中,如下:

/**
 * 美团的头部视图管理器,分为三个阶段:
 * 1、缩放阶段:PULL_DOWN;
 * 2、小人跳出来的阶段 RELEASE_REFRESH;
 * 3、小人头部左右摇晃阶段:REFRESHING;
 */
public class MeiTuanSelfHeaderViewManager extends SelfHeaderViewManager {

    /* 下拉状态View */
    private ImageView iv_meituan_pull_down;
    /* 释放刷新View */
    private ImageView iv_meituan_release_refreshing;

    public MeiTuanSelfHeaderViewManager(Context context) {
        super(context);
    }

    @Override
    protected View getSelfHeaderView() {
        if (selfHeaderView == null) {
            selfHeaderView = View.inflate(context, R.layout.view_refresh_header_meituan, null);
            iv_meituan_pull_down = selfHeaderView.findViewById(R.id.iv_meituan_pull_down);
            iv_meituan_release_refreshing = selfHeaderView.findViewById(R.id.iv_meituan_release_refreshing);
        }
        return selfHeaderView;
    }

    @Override
    protected void changeToIdle() {

    }

    @Override
    protected void changeToPullDown() {

    }

    @Override
    protected void changeToReleaseRefresh() {

    }

    @Override
    protected void changeToRefreshing() {

    }

    @Override
    protected void endRefreshing() {

    }
}

接下来先来处理拉出头部小人跳出来的效果,也就是changeToReleaseRefresh()方法:

接下来看一下效果:

嗯~~有那点意思了~~不过现在不居中,是啥原因呢?因为没有给View设置布局参数,如下:

再次编译运行:

不过状态有点问题,那是因为还有很多方法还木有处理嘛,接下来再处理下拉时的状态changeToPullDown(),这时应该就显示那张静止的图,如下:

这时再运行:

嗯~~效果好一些了,不过还得处理其它状态,这里处理释放刷新时小人左右摇晃的效,也就是处理changeToRefreshing()方法,如下:

编译运行:

另外还有一个细节需要处理,就是当数据加载完成之后,应该将小人的状态回到那个静止的图片上来,也就是在endRefreshing()方法上处理,如下:

缩放和收尾处理:

接下来就得处理下拉时那个静止的图片有个缩放的效果,那如何做呢?先来找到处理下拉的这块代码:

很明显目前这个条件里面的代码在下拉时只会执行一遍,因为加了状态判断,而跟需要根据下拉距离不断让图片进行缩放目标有点违背了,所以这里将条件进行如下调整:

而为了扩展,所以这里将其处理下拉时View的状态转由Manager去处理,如下:

这时当然具体子类得重写这个新定义的方法啦,这里就不贴代码了。

那问题的焦点就回到了如何去计算这个缩放比的值,其实比较简单:用当前的paddingTop/头部视图的最大上边距minWholeHeaderViewPaddingTop不就可以得出一个比例了么?具体如下:

那下面运行观察一下这个值的变化规律:

其值的变化为:

01-20 08:22:02.379 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9875
01-20 08:22:02.395 6125-6125/com.pulltorefresh.test E/cexo: scale:0.975
01-20 08:22:02.411 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:22:02.427 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:22:02.443 6125-6125/com.pulltorefresh.test E/cexo: scale:0.95
01-20 08:22:02.463 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:22:02.479 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:22:02.495 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:22:02.511 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:22:02.531 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:22:02.547 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:22:02.579 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:22:02.611 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:22:02.627 6125-6125/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:22:02.643 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8875
01-20 08:22:02.659 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8875
01-20 08:22:02.679 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8875
01-20 08:22:02.711 6125-6125/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:22:02.731 6125-6125/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:22:02.743 6125-6125/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:22:02.759 6125-6125/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:22:02.779 6125-6125/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:22:02.795 6125-6125/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:22:02.811 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8375
01-20 08:22:02.827 6125-6125/com.pulltorefresh.test E/cexo: scale:0.825
01-20 08:22:02.843 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8125
01-20 08:22:02.859 6125-6125/com.pulltorefresh.test E/cexo: scale:0.8
01-20 08:22:02.879 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7875
01-20 08:22:02.895 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7875
01-20 08:22:02.911 6125-6125/com.pulltorefresh.test E/cexo: scale:0.775
01-20 08:22:02.931 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7625
01-20 08:22:02.959 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7625
01-20 08:22:02.979 6125-6125/com.pulltorefresh.test E/cexo: scale:0.75
01-20 08:22:02.995 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7375
01-20 08:22:03.015 6125-6125/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:22:03.027 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7375
01-20 08:22:03.055 6125-6125/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:22:03.079 6125-6125/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:22:03.099 6125-6125/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:22:03.111 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7125
01-20 08:22:03.127 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7125
01-20 08:22:03.147 6125-6125/com.pulltorefresh.test E/cexo: scale:0.7
01-20 08:22:03.179 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6625
01-20 08:22:03.207 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6625
01-20 08:22:03.227 6125-6125/com.pulltorefresh.test E/cexo: scale:0.65
01-20 08:22:03.255 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6375
01-20 08:22:03.267 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6375
01-20 08:22:03.279 6125-6125/com.pulltorefresh.test E/cexo: scale:0.625
01-20 08:22:03.295 6125-6125/com.pulltorefresh.test E/cexo: scale:0.625
01-20 08:22:03.311 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6125
01-20 08:22:03.327 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6
01-20 08:22:03.347 6125-6125/com.pulltorefresh.test E/cexo: scale:0.6
01-20 08:22:03.363 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5875
01-20 08:22:03.379 6125-6125/com.pulltorefresh.test E/cexo: scale:0.575
01-20 08:22:03.395 6125-6125/com.pulltorefresh.test E/cexo: scale:0.575
01-20 08:22:03.423 6125-6125/com.pulltorefresh.test E/cexo: scale:0.575
01-20 08:22:03.427 6125-6125/com.pulltorefresh.test E/cexo: scale:0.55
01-20 08:22:03.443 6125-6125/com.pulltorefresh.test E/cexo: scale:0.55
01-20 08:22:03.463 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5375
01-20 08:22:03.479 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5375
01-20 08:22:03.495 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5375
01-20 08:22:03.511 6125-6125/com.pulltorefresh.test E/cexo: scale:0.525
01-20 08:22:03.531 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5125
01-20 08:22:03.543 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5125
01-20 08:22:03.563 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5125
01-20 08:22:03.579 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5125
01-20 08:22:03.595 6125-6125/com.pulltorefresh.test E/cexo: scale:0.5
01-20 08:22:03.611 6125-6125/com.pulltorefresh.test E/cexo: scale:0.4875
01-20 08:22:03.627 6125-6125/com.pulltorefresh.test E/cexo: scale:0.475
01-20 08:22:03.643 6125-6125/com.pulltorefresh.test E/cexo: scale:0.475
01-20 08:22:03.659 6125-6125/com.pulltorefresh.test E/cexo: scale:0.4625
01-20 08:22:03.679 6125-6125/com.pulltorefresh.test E/cexo: scale:0.4625
01-20 08:22:03.695 6125-6125/com.pulltorefresh.test E/cexo: scale:0.45
01-20 08:22:03.711 6125-6125/com.pulltorefresh.test E/cexo: scale:0.45
01-20 08:22:03.727 6125-6125/com.pulltorefresh.test E/cexo: scale:0.425
01-20 08:22:03.763 6125-6125/com.pulltorefresh.test E/cexo: scale:0.4
01-20 08:22:03.779 6125-6125/com.pulltorefresh.test E/cexo: scale:0.4
01-20 08:22:03.795 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3875
01-20 08:22:03.811 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3875
01-20 08:22:03.831 6125-6125/com.pulltorefresh.test E/cexo: scale:0.375
01-20 08:22:03.843 6125-6125/com.pulltorefresh.test E/cexo: scale:0.375
01-20 08:22:03.859 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3625
01-20 08:22:03.879 6125-6125/com.pulltorefresh.test E/cexo: scale:0.35
01-20 08:22:03.895 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3375
01-20 08:22:03.911 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3375
01-20 08:22:03.927 6125-6125/com.pulltorefresh.test E/cexo: scale:0.325
01-20 08:22:03.947 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3125
01-20 08:22:03.963 6125-6125/com.pulltorefresh.test E/cexo: scale:0.3125
01-20 08:22:03.995 6125-6125/com.pulltorefresh.test E/cexo: scale:0.275
01-20 08:22:04.011 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2625
01-20 08:22:04.027 6125-6125/com.pulltorefresh.test E/cexo: scale:0.25
01-20 08:22:04.047 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2375
01-20 08:22:04.059 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2375
01-20 08:22:04.079 6125-6125/com.pulltorefresh.test E/cexo: scale:0.225
01-20 08:22:04.095 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2125
01-20 08:22:04.111 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2125
01-20 08:22:04.127 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2125
01-20 08:22:04.143 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2
01-20 08:22:04.163 6125-6125/com.pulltorefresh.test E/cexo: scale:0.2
01-20 08:22:04.179 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1875
01-20 08:22:04.207 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1625
01-20 08:22:04.227 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1625
01-20 08:22:04.247 6125-6125/com.pulltorefresh.test E/cexo: scale:0.15
01-20 08:22:04.263 6125-6125/com.pulltorefresh.test E/cexo: scale:0.15
01-20 08:22:04.279 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1375
01-20 08:22:04.295 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1375
01-20 08:22:04.311 6125-6125/com.pulltorefresh.test E/cexo: scale:0.125
01-20 08:22:04.347 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1
01-20 08:22:04.363 6125-6125/com.pulltorefresh.test E/cexo: scale:0.1
01-20 08:22:04.383 6125-6125/com.pulltorefresh.test E/cexo: scale:0.075
01-20 08:22:04.423 6125-6125/com.pulltorefresh.test E/cexo: scale:0.075
01-20 08:22:04.443 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0625
01-20 08:22:04.463 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0625
01-20 08:22:04.479 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0625
01-20 08:22:04.495 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0375
01-20 08:22:04.511 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0375
01-20 08:22:04.527 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0375
01-20 08:22:04.543 6125-6125/com.pulltorefresh.test E/cexo: scale:0.025
01-20 08:22:04.563 6125-6125/com.pulltorefresh.test E/cexo: scale:0.025
01-20 08:22:04.579 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0125
01-20 08:22:04.595 6125-6125/com.pulltorefresh.test E/cexo: scale:0.0125

从0.9到0.0,显然这个比值跟咱们预期的刚好相反,比值应该是随着下拉距离的增大而增大,所以这里咱们手动将其按咱们的意图来走,也就是从0.0到0.9,如下:

这时再来看一下比值变化:

01-20 08:25:48.875 6199-6199/com.pulltorefresh.test E/cexo: scale:0.024999976
01-20 08:25:48.879 6199-6199/com.pulltorefresh.test E/cexo: scale:0.024999976
01-20 08:25:49.863 6199-6199/com.pulltorefresh.test E/cexo: scale:0.024999976
01-20 08:25:49.879 6199-6199/com.pulltorefresh.test E/cexo: scale:0.037500024
01-20 08:25:49.895 6199-6199/com.pulltorefresh.test E/cexo: scale:0.050000012
01-20 08:25:49.911 6199-6199/com.pulltorefresh.test E/cexo: scale:0.0625
01-20 08:25:49.927 6199-6199/com.pulltorefresh.test E/cexo: scale:0.0625
01-20 08:25:49.943 6199-6199/com.pulltorefresh.test E/cexo: scale:0.087499976
01-20 08:25:49.959 6199-6199/com.pulltorefresh.test E/cexo: scale:0.100000024
01-20 08:25:49.979 6199-6199/com.pulltorefresh.test E/cexo: scale:0.11250001
01-20 08:25:49.999 6199-6199/com.pulltorefresh.test E/cexo: scale:0.11250001
01-20 08:25:50.015 6199-6199/com.pulltorefresh.test E/cexo: scale:0.11250001
01-20 08:25:50.027 6199-6199/com.pulltorefresh.test E/cexo: scale:0.125
01-20 08:25:50.043 6199-6199/com.pulltorefresh.test E/cexo: scale:0.125
01-20 08:25:50.059 6199-6199/com.pulltorefresh.test E/cexo: scale:0.125
01-20 08:25:50.079 6199-6199/com.pulltorefresh.test E/cexo: scale:0.14999998
01-20 08:25:50.095 6199-6199/com.pulltorefresh.test E/cexo: scale:0.14999998
01-20 08:25:50.111 6199-6199/com.pulltorefresh.test E/cexo: scale:0.16250002
01-20 08:25:50.127 6199-6199/com.pulltorefresh.test E/cexo: scale:0.16250002
01-20 08:25:50.143 6199-6199/com.pulltorefresh.test E/cexo: scale:0.17500001
01-20 08:25:50.159 6199-6199/com.pulltorefresh.test E/cexo: scale:0.17500001
01-20 08:25:50.179 6199-6199/com.pulltorefresh.test E/cexo: scale:0.17500001
01-20 08:25:50.195 6199-6199/com.pulltorefresh.test E/cexo: scale:0.1875
01-20 08:25:50.211 6199-6199/com.pulltorefresh.test E/cexo: scale:0.1875
01-20 08:25:50.227 6199-6199/com.pulltorefresh.test E/cexo: scale:0.19999999
01-20 08:25:50.243 6199-6199/com.pulltorefresh.test E/cexo: scale:0.21249998
01-20 08:25:50.259 6199-6199/com.pulltorefresh.test E/cexo: scale:0.21249998
01-20 08:25:50.279 6199-6199/com.pulltorefresh.test E/cexo: scale:0.22500002
01-20 08:25:50.295 6199-6199/com.pulltorefresh.test E/cexo: scale:0.23750001
01-20 08:25:50.311 6199-6199/com.pulltorefresh.test E/cexo: scale:0.25
01-20 08:25:50.327 6199-6199/com.pulltorefresh.test E/cexo: scale:0.27499998
01-20 08:25:50.343 6199-6199/com.pulltorefresh.test E/cexo: scale:0.2625
01-20 08:25:50.363 6199-6199/com.pulltorefresh.test E/cexo: scale:0.27499998
01-20 08:25:50.379 6199-6199/com.pulltorefresh.test E/cexo: scale:0.27499998
01-20 08:25:50.395 6199-6199/com.pulltorefresh.test E/cexo: scale:0.28750002
01-20 08:25:50.411 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3
01-20 08:25:50.427 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3
01-20 08:25:50.447 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3
01-20 08:25:50.463 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3125
01-20 08:25:50.479 6199-6199/com.pulltorefresh.test E/cexo: scale:0.33749998
01-20 08:25:50.495 6199-6199/com.pulltorefresh.test E/cexo: scale:0.33749998
01-20 08:25:50.511 6199-6199/com.pulltorefresh.test E/cexo: scale:0.33749998
01-20 08:25:50.527 6199-6199/com.pulltorefresh.test E/cexo: scale:0.35000002
01-20 08:25:50.543 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3625
01-20 08:25:50.563 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3625
01-20 08:25:50.579 6199-6199/com.pulltorefresh.test E/cexo: scale:0.3875
01-20 08:25:50.595 6199-6199/com.pulltorefresh.test E/cexo: scale:0.39999998
01-20 08:25:50.611 6199-6199/com.pulltorefresh.test E/cexo: scale:0.39999998
01-20 08:25:50.627 6199-6199/com.pulltorefresh.test E/cexo: scale:0.41250002
01-20 08:25:50.647 6199-6199/com.pulltorefresh.test E/cexo: scale:0.425
01-20 08:25:50.659 6199-6199/com.pulltorefresh.test E/cexo: scale:0.45
01-20 08:25:50.679 6199-6199/com.pulltorefresh.test E/cexo: scale:0.46249998
01-20 08:25:50.695 6199-6199/com.pulltorefresh.test E/cexo: scale:0.46249998
01-20 08:25:50.711 6199-6199/com.pulltorefresh.test E/cexo: scale:0.47500002
01-20 08:25:50.727 6199-6199/com.pulltorefresh.test E/cexo: scale:0.4875
01-20 08:25:50.743 6199-6199/com.pulltorefresh.test E/cexo: scale:0.4875
01-20 08:25:50.763 6199-6199/com.pulltorefresh.test E/cexo: scale:0.4875
01-20 08:25:50.779 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5
01-20 08:25:50.795 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5125
01-20 08:25:50.811 6199-6199/com.pulltorefresh.test E/cexo: scale:0.525
01-20 08:25:50.827 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5375
01-20 08:25:50.847 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5375
01-20 08:25:50.867 6199-6199/com.pulltorefresh.test E/cexo: scale:0.55
01-20 08:25:50.907 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5625
01-20 08:25:50.931 6199-6199/com.pulltorefresh.test E/cexo: scale:0.5875
01-20 08:25:50.943 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6
01-20 08:25:50.963 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6
01-20 08:25:50.995 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6125
01-20 08:25:51.011 6199-6199/com.pulltorefresh.test E/cexo: scale:0.625
01-20 08:25:51.027 6199-6199/com.pulltorefresh.test E/cexo: scale:0.625
01-20 08:25:51.047 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6375
01-20 08:25:51.063 6199-6199/com.pulltorefresh.test E/cexo: scale:0.65
01-20 08:25:51.079 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6625
01-20 08:25:51.103 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6625
01-20 08:25:51.131 6199-6199/com.pulltorefresh.test E/cexo: scale:0.675
01-20 08:25:51.159 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6875
01-20 08:25:51.179 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6875
01-20 08:25:51.195 6199-6199/com.pulltorefresh.test E/cexo: scale:0.6875
01-20 08:25:51.211 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7125
01-20 08:25:51.227 6199-6199/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:25:51.243 6199-6199/com.pulltorefresh.test E/cexo: scale:0.725
01-20 08:25:51.263 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7375
01-20 08:25:51.279 6199-6199/com.pulltorefresh.test E/cexo: scale:0.75
01-20 08:25:51.295 6199-6199/com.pulltorefresh.test E/cexo: scale:0.75
01-20 08:25:51.311 6199-6199/com.pulltorefresh.test E/cexo: scale:0.75
01-20 08:25:51.331 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7625
01-20 08:25:51.347 6199-6199/com.pulltorefresh.test E/cexo: scale:0.775
01-20 08:25:51.363 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7875
01-20 08:25:51.379 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7875
01-20 08:25:51.395 6199-6199/com.pulltorefresh.test E/cexo: scale:0.7875
01-20 08:25:51.411 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8
01-20 08:25:51.427 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8125
01-20 08:25:51.443 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8125
01-20 08:25:51.463 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8125
01-20 08:25:51.479 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8375
01-20 08:25:51.495 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8375
01-20 08:25:51.511 6199-6199/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:25:51.531 6199-6199/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:25:51.547 6199-6199/com.pulltorefresh.test E/cexo: scale:0.85
01-20 08:25:51.579 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8625
01-20 08:25:51.595 6199-6199/com.pulltorefresh.test E/cexo: scale:0.8625
01-20 08:25:51.631 6199-6199/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:25:51.647 6199-6199/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:25:51.659 6199-6199/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:25:51.679 6199-6199/com.pulltorefresh.test E/cexo: scale:0.875
01-20 08:25:51.695 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:25:51.715 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:25:51.727 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9
01-20 08:25:51.747 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:25:51.763 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:25:51.783 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9125
01-20 08:25:51.795 6199-6199/com.pulltorefresh.test E/cexo: scale:0.925
01-20 08:25:51.811 6199-6199/com.pulltorefresh.test E/cexo: scale:0.925
01-20 08:25:51.827 6199-6199/com.pulltorefresh.test E/cexo: scale:0.925
01-20 08:25:51.847 6199-6199/com.pulltorefresh.test E/cexo: scale:0.925
01-20 08:25:51.859 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:25:51.879 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:25:51.895 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9375
01-20 08:25:51.911 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:51.931 6199-6199/com.pulltorefresh.test E/cexo: scale:0.95
01-20 08:25:51.947 6199-6199/com.pulltorefresh.test E/cexo: scale:0.95
01-20 08:25:51.963 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:51.979 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:51.995 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:52.011 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:52.027 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:52.043 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:52.059 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9625
01-20 08:25:52.079 6199-6199/com.pulltorefresh.test E/cexo: scale:0.975
01-20 08:25:52.095 6199-6199/com.pulltorefresh.test E/cexo: scale:0.975
01-20 08:25:52.115 6199-6199/com.pulltorefresh.test E/cexo: scale:0.975
01-20 08:25:52.127 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9875
01-20 08:25:52.159 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9875
01-20 08:25:52.179 6199-6199/com.pulltorefresh.test E/cexo: scale:0.9875

嗯~~如预期,接下来到美团的管理器根据这个缩放的比例来对ImageView进行缩放,直接调用ImageView现成的API处理既可:

编译运行:

嗯~~完美~~不过还差最后一个细节木有收尾,那就是:

所以咱们在这个endRefreshing()方法中做一些善后处理:比如小人动画正在执行时由于数据处理速度过快导致动画还木有执行完就结束了,那得在结束的时候强行将动画给停掉之类的,所以处理如下:

/**
 * 美团的头部视图管理器,分为三个阶段:
 * 1、缩放阶段:PULL_DOWN;
 * 2、小人跳出来的阶段 RELEASE_REFRESH;
 * 3、小人头部左右摇晃阶段:REFRESHING;
 */
public class MeiTuanSelfHeaderViewManager extends SelfHeaderViewManager {

    /* 下拉状态View */
    private ImageView iv_meituan_pull_down;
    /* 释放刷新View */
    private ImageView iv_meituan_release_refreshing;

    /* 向下刷新小人跳出动画 */
    private AnimationDrawable releaseAnimationDrawable;
    /* 释放刷新小人摇晃动画 */
    private AnimationDrawable refreshingAnimationDrawable;

    public MeiTuanSelfHeaderViewManager(Context context) {
        super(context);
    }

    @Override
    protected View getSelfHeaderView() {
        if (selfHeaderView == null) {
            selfHeaderView = View.inflate(context, R.layout.view_refresh_header_meituan, null);
            selfHeaderView.setLayoutParams(new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
            iv_meituan_pull_down = selfHeaderView.findViewById(R.id.iv_meituan_pull_down);
            iv_meituan_release_refreshing = selfHeaderView.findViewById(R.id.iv_meituan_release_refreshing);
        }
        return selfHeaderView;
    }

    @Override
    protected void changeToIdle() {
        iv_meituan_pull_down.setVisibility(View.VISIBLE);
        iv_meituan_release_refreshing.setVisibility(View.INVISIBLE);
        if (releaseAnimationDrawable != null)
            releaseAnimationDrawable.stop();
        if (refreshingAnimationDrawable != null)
            refreshingAnimationDrawable.stop();
    }

    @Override
    protected void changeToPullDown() {
        iv_meituan_pull_down.setVisibility(View.VISIBLE);
        iv_meituan_release_refreshing.setVisibility(View.INVISIBLE);
    }

    @Override
    protected void changeToReleaseRefresh() {//小人跳出来的阶段
        iv_meituan_pull_down.setVisibility(View.INVISIBLE);
        iv_meituan_release_refreshing.setVisibility(View.VISIBLE);
        iv_meituan_release_refreshing.setImageResource(R.drawable.release_mt_refresh);//设置帧动画
        releaseAnimationDrawable = (AnimationDrawable) iv_meituan_release_refreshing.getDrawable();
        releaseAnimationDrawable.start();
    }

    @Override
    protected void changeToRefreshing() {//小人头部左右摇晃阶段
        iv_meituan_release_refreshing.setImageResource(R.drawable.refresh_mt_refreshing);
        refreshingAnimationDrawable = (AnimationDrawable) iv_meituan_release_refreshing.getDrawable();
        refreshingAnimationDrawable.start();
    }

    @Override
    protected void endRefreshing() {
        iv_meituan_pull_down.setVisibility(View.VISIBLE);
        iv_meituan_release_refreshing.setVisibility(View.INVISIBLE);
    }

    @Override
    protected void handleScale(float scale) {
        iv_meituan_pull_down.setScaleX(scale);
        iv_meituan_pull_down.setScaleY(scale);
    }
}

至此美团下拉刷新效果就完美实现~ 

posted on 2017-12-13 22:51  cexo  阅读(771)  评论(0编辑  收藏  举报

导航