美文网首页实用控件Android知识Android开发
仿【咪咕动漫】列表下拉刷新上拉加载

仿【咪咕动漫】列表下拉刷新上拉加载

作者: 文淑 | 来源:发表于2017-04-03 12:55 被阅读369次

    一、概述

    本篇续 厦门之旅 的第二篇。这期间找工作真的心态几多变化,刚开始兴致高昂,信心满满,面试了几家不错的公司,结果都是因为工资问题不了了之。后面的连面试机会都没有了,每天在狭小的租房里面吃了睡,睡了玩,陌生的环境消磨这我的意志。我很讨厌消沉的自我,这边招 Android 开发并没有我以为的那么多,实在是太少了,想找到满意的工作更是难上加难。引用公众号【AndroidDeveloper】的一句话

    愿意积极争取,肯努力上进的年轻人,运气不会太差

    在我快要放弃在这边找工作的时候,收到了一家公司的面试通知,并顺利的拿到了 offer,他们公司旗下有一款 咪咕动漫 的产品。本篇我以自己的方式实现了 App 中列表的下拉刷新以及上拉加载。

    二、效果展示

    效果图一栏:

    refresh

    三、具体实现

    1、图片资源

    下载 咪咕动漫App ,修改成 .zip 格式并解压。获取到图片资源如下:

    pullrefresh

    pull_down (下拉)

    pullrelease

    pull_end (释放刷新)

    refreshing1

    refreshing_01 (正在刷新图片_1)

    refreshing2

    refreshing_02 (正在刷新图片_2)

    refreshing3

    refreshing_03 (正在刷新图片_3)

    正在刷新的帧动画:

    <?xml version="1.0" encoding="utf-8"?>
    <animation-list
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:oneshot="false">
    
        <item android:duration="100" android:drawable="@drawable/refreshing_01" />
        <item android:duration="100" android:drawable="@drawable/refreshing_02" />
        <item android:duration="100" android:drawable="@drawable/refreshing_03" />
    
    </animation-list>
    

    属性 android:oneshot="false" 表示动画循环播放。 如果为 true,表示动画只播放一次停止在最后一帧上。

    2、下拉刷新

    原理浅析

    下拉刷新作为一个单独控件添加到列表顶部,并且初始状态的高度为 0 ,随着手指触摸的偏移量高度而发生改变,并且在不同的状态之间来回切换。控件的四种状态:

    • STATE_NORMAL 下拉状态 (高度小于刷新的临界高度) 默认 40dp

    • STATE_RELEASE_TO_REFRESH 释放刷新状态 (高度大于刷新的临界高度)

    • STATE_REFRESHING 刷新状态 (高度大于刷新的临界高度,手指释放后的状态)

    • STATE_DONE 刷新完成状态

    处了四种状态,还需要实现三个方法:

    • void onMove(float delta); 移动,参数 delta 两点之间的偏移量

    • boolean releaseAction(); 释放是否满足刷新状态

    • void refreshComplete(); 刷新完成

    刷新控件(MiGuRefreshHeader)

    MiGuRefreshHeader 控件继承 LinearLayout ,MiGuRefreshHeader 的构造方法:

        public MiGuRefreshHeader(Context context) {
            this(context, null);
        }
    
        public MiGuRefreshHeader(Context context, @Nullable AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public MiGuRefreshHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView();
        }
    

    初始化 View,比较简单我这里就不在细讲,文章最后会附上源码:

        private void initView() {
    
            // 初始情况,设置下拉刷新view高度为0
            mContainer = (LinearLayout) LayoutInflater.from(getContext()).inflate(R.layout.refresh_header, null);
            LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
            lp.setMargins(0, 0, 0, 0);
            this.setLayoutParams(lp);
            this.setPadding(0, 0, 0, 0);
    
            addView(mContainer, new LayoutParams(LayoutParams.MATCH_PARENT, 0));
            setGravity(Gravity.BOTTOM);
    
            //图片控件
            mMiGuImageView = (ImageView) findViewById(R.id.iv_refresh);
            //文本控件
            mStatusTextView = (TextView) findViewById(R.id.tv_status);
            
            //获取控件的默认高度方法一
            measure(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            mMeasureHeight = getMeasuredHeight();//获取控件高度 默认 40dp 由于测试机密度为 3 所以像素为 120px
        }
    

    获取控件的默认高度方式二:

        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            mMeasureHeight = h;
        }
    

    不同状态下文本控件,图片控件的显示:

        public void setState(int state) {
            if (state == mState) return;
    
            if (state == STATE_NORMAL) {
                //下拉
                mMiGuImageView.setImageResource(R.drawable.pull_down);
                mStatusTextView.setText(R.string.pull_refresh);
            } else if (state == STATE_RELEASE_TO_REFRESH) {
                //释放
                mMiGuImageView.setImageResource(R.drawable.pull_end);
                mStatusTextView.setText(R.string.release_refresh);
            } else if (state == STATE_REFRESHING) {
                //刷新
                mStatusTextView.setText(R.string.refreshing);
                mMiGuImageView.setImageResource(R.drawable.refreshing);
                mMiGuDrawable = (AnimationDrawable) mMiGuImageView.getDrawable();
                //播放动画
                mMiGuDrawable.start();
    
                smoothScrollTo(mMeasureHeight);
            } else if (state == STATE_DONE) {
                //完成
                if (mMiGuDrawable != null)
                    //停止动画
                    mMiGuDrawable.stop();
            }
    
            mState = state;
        }
    

    如果你需要替换文本或图片,请修改这里。

    以下是3个方法的实现,onMove(float delta) 方法:

            if (getVisibleHeight() > 0 || delta > 0) {
                //控件滑动的距离
                setVisibleHeight((int) delta + getVisibleHeight());
                //处于释放刷新状态
                if (mState <= STATE_RELEASE_TO_REFRESH) {
                    //判定距离是否大于刷新的临界值
                    if (getVisibleHeight() < mMeasureHeight) {
                        setState(STATE_NORMAL);
                    } else {
                        setState(STATE_RELEASE_TO_REFRESH);
                    }
                }
            }
    

    releaseAction() 方法:

        @Override
        public boolean releaseAction() {
    
            boolean isOnRefresh = false;
    
            int height = getVisibleHeight();
    
            if (height == 0) {
                isOnRefresh = false;
            }
    
            if (getVisibleHeight() > mMeasureHeight && mState < STATE_REFRESHING) {
                //刷新状态
                setState(STATE_REFRESHING);
                isOnRefresh = true;
            }
    
            if (mState == STATE_REFRESHING && height <= mMeasureHeight) {
                //处于刷新状态,手指还在向上滑动
            }
    
            if (mState != STATE_REFRESHING) {
                smoothScrollTo(0);
            }
    
            if (mState == STATE_REFRESHING) {
                int destHeight = mMeasureHeight;
                smoothScrollTo(destHeight);
            }
    
            return isOnRefresh;
        }
    

    刷新完成 refreshComplete() 方法:

        @Override
        public void refreshComplete() {
            setState(STATE_DONE);
            reset();
        }
    

    理解了刷新控件的四种状态,再来分析代码就比较容易了。接着我们处理 RecyclerView 的 onTouchEvent 方法获取 Y 轴的偏移量作为参数传入 onMove(float delta) 方法中。

    MiGuRecyclerView

    MiGuRecyclerView 继承 RecyclerView 控件。主要分析 onTouchEvent 方法,如果你对其他地方还有疑问,请留言。

    注意:本文使用的 RecyclerView 基于RecyclerView 之通用适配,重写 setAdapter 方法添加头部刷新控件:

     mRefreshAdapter.addHeaderView(mMiGuRefreshHeader, 0);
    

    onTouchEvent 方法代码如下:

        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (mLastY == -1) {
                mLastY = ev.getRawY();
            }
    
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mLastY = ev.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    if (mRefreshAdapter != null) {
                        final float deltaY = ev.getRawY() - mLastY;
                        mLastY = ev.getRawY();
                        if (isScrollTop && isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) {
                            mMiGuRefreshHeader.onMove(deltaY / DRAG_RATE);
                            if (mMiGuRefreshHeader.getVisibleHeight() > 0 && mMiGuRefreshHeader.getState() < MiGuRefreshHeader.STATE_REFRESHING) {
                                return false;
                            }
                        }
                    }
                    break;
                default:
                    if (mRefreshAdapter != null) {
                        mLastY = -1; // reset
                        if (isScrollTop && isOnTop() && pullRefreshEnabled && appbarState == AppBarStateChangeListener.State.EXPANDED) {
                            if (mMiGuRefreshHeader.releaseAction()) {
                                if (mRefreshListener != null) {
                                    mRefreshListener.onRefresh();
                                }
                            }
                        }
                    }
                    break;
            }
    
            return super.onTouchEvent(ev);
        }
    

    ACTION_DOWN 手势获取相对于屏幕触摸点 Y 坐标

        final float deltaY = ev.getRawY() - mLastY;
        mLastY = ev.getRawY();
    

    获取手势滑动两点的偏移量,isScrollTop 判定当前 RecyclerView 是否滑动到顶部。我采取了重写 onScrolled 方法通过:

    findFirstCompletelyVisibleItemPosition();
    

    来判定 isScrollTop 值,如果您有好的方案可以给我留言,万分感谢。

    3、上拉加载

    【咪咕动漫】采取的是播放帧动画,比较简单。实现方案你可以参考源码。

    技术交流群欢迎你的加入

    qq

    源码传送门

    参考文献

    http://www.open-open.com/lib/view/open1474266969512.html

    http://blog.csdn.net/xk632172748/article/details/53939161

    相关文章

      网友评论

      本文标题:仿【咪咕动漫】列表下拉刷新上拉加载

      本文链接:https://www.haomeiwen.com/subject/kianottx.html