zoukankan      html  css  js  c++  java
  • 美团下拉刷新效果实现

    在上一次【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);
        }
    }

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

  • 相关阅读:
    Ceph14.2.5 RBD块存储的实战配置和详细介绍,不看后悔! -- <3>
    常见SQL命令总结学习 -- <1>
    全网最详细的新手入门Mysql命令和基础,小白必看!
    全网最详细的Linux命令系列-nl命令
    全网最详细的Linux命令系列-cat命令
    全网最详细的Linux命令系列-touch命令
    全网最详细的Ceph14.2.5集群部署及配置文件详解,快来看看吧! -- <2>
    什么是Ceph存储?什么是分布式存储?简单明了带你学Ceph -- <1>
    一款专注于阅读的博客园主题-(cnblogs-theme-silence)
    Prometheus 配置文件中 metric_relabel_configs 配置--转载
  • 原文地址:https://www.cnblogs.com/webor2006/p/8035335.html
Copyright © 2011-2022 走看看