美文网首页android成神之路Android知识Android开发
何不自己动手去实现一个属于自己的个性化Android下拉刷新库?

何不自己动手去实现一个属于自己的个性化Android下拉刷新库?

作者: 峥嵘岁月Z | 来源:发表于2016-10-20 15:02 被阅读667次

    如今,下拉刷新都已经成为App的标配了,哪个App会没有下拉刷新呢?作为一个普通app用户,想要去刷新内容,习惯性地就下拉一下,已经成为用户的一种习惯啦。作为一个开发者,想要集成下拉刷新,直接就拷贝Android-PullToRefresh或者是android-Ultra-Pull-To-Refresh又或者其他下拉刷新库,直接使用经典的下拉刷新UI,一个标志性的下拉箭头,一句经典的下拉以刷新更多。一开始还好,慢慢的作为一个使用者而言,就觉得没什么新意了,作为一个开发者也觉得没什么挑战了。

    那这样,何不尝试自己动手去实现一个真正属于自己的下拉刷新库呢?嘿嘿,那说干就干呗。那想想,现在下拉刷新的轮子那么多,我们也不可能从零开始写,时间也不允许啊,白天要上班写业务代码,回到宿舍还得重复别人写过的代码,而且自己写的还派不上用场,因为已经有现成的,而且别人写的东西那么多人用上了,踩过的坑肯定比你想的要多。虽然话说凡事要自己去实践,理解的才会更深。说是这么说,但是我还是觉得站在巨人肩膀上,才会看得更远,嘿嘿......

    正因为如此,我才基于android-Ultra-Pull-To-Refresh实现一个很Q的笑脸下拉刷新,不吹不黑,真的很Q哦。这个下拉大概的效果就是,下拉时,随header高度变化而缩放、转眼睛,转啊转。松开时,一个转眼睛的笑脸加载动画。

    扯那么多,我自己都觉得有点不耐烦了。下面来简要分析一下,怎么去实现一个个性化的下拉刷新库。以下是整个库的目录,想不到吧,就仅仅三个类,一个布局文件。就可以实现你自己能想到的下拉效果。

    Paste_Image.png

    下面贴出最主要相关的类

    • PullToRefreshFaceView
    
    /**
     *
     * @作 用:下拉刷新的笑脸
     * @创 建 人: linguoding
     * @日 期: 2016/3/9
     */
    public class PullToRefreshFaceView extends View {
        private Paint paint;//画笔
        private Paint eyePaint;
        private int backgroupColor;
        private int width;
        private int height;
        private int centerWidth;
        private int centerHeight;
        private float degrees;
        private float radius = 0;
        private float sweepRadius = 180;
    
        private int radiusCircle;
        private int eyeRadius;
        private int eyeBallRadius;
        private boolean isDrawFace = false;
    
        AnimatorSet set = new AnimatorSet();
    
    
        public PullToRefreshFaceView(Context context) {
            super(context);
            initView();
        }
    
        public PullToRefreshFaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
            TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.LoadingView);
            backgroupColor = array.getColor(R.styleable.LoadingView_backgroupColor, Color.BLACK);
            array.recycle();
            initView();
    
        }
    
        public void setDegrees(float degrees) {
            this.degrees = degrees;
            invalidate();
        }
    
        public void setRadius(float radius) {
            this.radius = radius;
            invalidate();
        }
    
        public void setSweepRadius(float sweepRadius) {
            this.sweepRadius = sweepRadius;
            invalidate();
        }
    
        public void setRadiusCircle(int radiusCircle) {
            this.radiusCircle = radiusCircle;
        }
    
        public void setEyeRadius(int eyeRadius) {
            this.eyeRadius = eyeRadius;
    
        }
    
        public void setEyeBallRadius(int eyeBallRadius) {
            this.eyeBallRadius = eyeBallRadius;
    
        }
    
        public void setDrawFace(boolean isDrawFace) {
            this.isDrawFace = isDrawFace;
        }
    
        public int getRadiusCircle() {
            return radiusCircle;
        }
    
        public int getEyeRadius() {
            return eyeRadius;
        }
    
        public int getEyeBallRadius() {
            return eyeBallRadius;
        }
    
        public boolean isDrawFace() {
            return isDrawFace;
        }
    
        public void setBackgroupColor(int backgroupColor) {
            this.backgroupColor = backgroupColor;
        }
    
        /**
         * 刷新用的效果
         *
         * @param sunRadius
         * @param per
         */
        public void setPerView(int sunRadius, float per) {
            if (per >= 0.5) {
                isDrawFace = true;
            } else {
                isDrawFace = false;
            }
            per = Math.min(per, 1);
            float tempRadius = sunRadius * per;
            this.radiusCircle = (int) tempRadius;
            invalidate();
        }
    
    
    
    
    
        private void initView() {
            paint = new Paint();
            paint.setAntiAlias(true);
            paint.setColor(backgroupColor);
            //眼眶画笔
            eyePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
            eyePaint.setColor(Color.WHITE);
            eyePaint.setStyle(Paint.Style.FILL);
            createAnimatorSet();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            /*super.onMeasure(widthMeasureSpec, heightMeasureSpec);*/
            width = measureResult(widthMeasureSpec);
            height = measureResult(heightMeasureSpec);
            centerWidth = width >> 1;
            centerHeight = height >> 1;
            setMeasuredDimension(width, height);
    
        }
    
        private int measureResult(int widthMeasureSpec) {
            int result = 0;
            int sizeSpec = MeasureSpec.getSize(widthMeasureSpec);
            int modeSpec = MeasureSpec.getMode(widthMeasureSpec);
            if (modeSpec == MeasureSpec.EXACTLY) {
                result = sizeSpec;
            } else {
                result = 400;
                if (modeSpec == MeasureSpec.AT_MOST) {
                    result = Math.min(result, sizeSpec);
                }
            }
            return result;
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            centerWidth = width / 2;
            centerHeight = height / 2;
            canvas.translate(centerWidth, centerHeight);
            radiusCircle = Math.min(centerWidth, centerHeight);
            //画圆
            canvas.drawCircle(0, 0, radiusCircle, paint);
            if (isDrawFace) {
                //画两个眼框
                eyeRadius = Math.min(centerWidth / 3, centerHeight / 3);
                canvas.drawCircle(-centerWidth / 2, -centerHeight >> 3, eyeRadius, eyePaint);
                canvas.drawCircle(centerWidth / 2, -centerHeight >> 3, eyeRadius, eyePaint);
                //画嘴巴
                canvas.drawArc(new RectF(-eyeRadius, 0, eyeRadius, eyeRadius * 2), 0, 180, true, eyePaint);
    
                canvas.save();
                //画两个眼睛
                eyeBallRadius = Math.min(centerWidth >> 3, centerHeight >> 3);
                canvas.translate(-centerWidth / 2, -centerHeight >> 3);
                canvas.rotate(-degrees);
                canvas.drawCircle(0, (eyeRadius >> 1), eyeBallRadius, paint);
                canvas.restore();
    
                canvas.save();
    
                canvas.translate(centerWidth / 2, -centerHeight >> 3);
                canvas.rotate(-degrees);
                canvas.drawCircle(0, (eyeRadius >> 1), eyeBallRadius, paint);
                canvas.restore();
    
                //画两个眼皮
                canvas.save();
                canvas.translate(-centerWidth / 2, -centerHeight >> 3);
                canvas.drawArc(new RectF(-eyeRadius, -eyeRadius, eyeRadius, eyeRadius), -this.radius, -this.sweepRadius, false, paint);
                canvas.restore();
    
                canvas.save();
                canvas.translate(centerWidth / 2, -centerHeight >> 3);
                canvas.drawArc(new RectF(-eyeRadius, -eyeRadius, eyeRadius, eyeRadius), -this.radius, -this.sweepRadius, false, paint);
                canvas.restore();
            }
    
    
        }
    
        private void createAnimatorSet() {
            ValueAnimator rotateAnimator = ValueAnimator.ofFloat(0, 360).setDuration(3000);
            rotateAnimator.setInterpolator(new LinearInterpolator());
            rotateAnimator.setRepeatCount(-1);
            rotateAnimator.setEvaluator(new FloatEvaluator());
            rotateAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    degrees = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
    
            ValueAnimator translationAnimator = ValueAnimator.ofFloat(0, 90, 0).setDuration(3000);
            translationAnimator.setInterpolator(new LinearInterpolator());
            translationAnimator.setRepeatCount(-1);
            translationAnimator.setEvaluator(new FloatEvaluator());
            translationAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    radius = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
    
            ValueAnimator sweepAnimator = ValueAnimator.ofFloat(180, 0, 180).setDuration(3000);
            sweepAnimator.setInterpolator(new LinearInterpolator());
            sweepAnimator.setRepeatCount(-1);
            sweepAnimator.setEvaluator(new FloatEvaluator());
            sweepAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    sweepRadius = (float) animation.getAnimatedValue();
                    postInvalidate();
                }
            });
            set.playTogether(rotateAnimator, translationAnimator, sweepAnimator);
        }
    
        public void startAnimators() {
            set.start();
        }
    
        public void stopAnimators() {
            set.cancel();
        }
    }
    
    
    • FacePullToRefreshHeader 类
    package com.pulltorefreshlibrary;
    
    import android.content.Context;
    import android.content.SharedPreferences;
    import android.support.v4.view.ViewCompat;
    import android.text.TextUtils;
    import android.util.AttributeSet;
    import android.view.LayoutInflater;
    import android.view.View;
    import android.widget.FrameLayout;
    import android.widget.TextView;
    
    import com.pulltorefreshlibrary.view.PullToRefreshFaceView;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    import in.srain.cube.views.ptr.PtrFrameLayout;
    import in.srain.cube.views.ptr.PtrUIHandler;
    import in.srain.cube.views.ptr.indicator.PtrIndicator;
    
    public class FacePullToRefreshHeader extends FrameLayout implements PtrUIHandler {
        private final static String KEY_SharedPreferences = "face_ptr_classic_last_update";
        private TextView mTitleTextView;
        private PullToRefreshFaceView mLoadView;
        private long mLastUpdateTime = -1;
        private TextView mLastUpdateTextView;
        private String mLastUpdateTimeKey;
        private static SimpleDateFormat sDataFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        private boolean mShouldShowLastUpdate;
    
        private LastUpdateTimeUpdater mLastUpdateTimeUpdater = new LastUpdateTimeUpdater();
    
    
        public FacePullToRefreshHeader(Context context) {
            super(context);
            initViews(null);
        }
    
        public FacePullToRefreshHeader(Context context, AttributeSet attrs) {
            super(context, attrs);
            initViews(attrs);
        }
    
        public FacePullToRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initViews(attrs);
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            if (mLastUpdateTimeUpdater != null) {
                mLastUpdateTimeUpdater.stop();
            }
        }
    
        private void initViews(AttributeSet attrs) {
            View header = LayoutInflater.from(getContext()).inflate(R.layout.face_pull_to_refresh_header, this);
            mTitleTextView = (TextView) header.findViewById(R.id.ptr_face_header_title);
            mLastUpdateTextView = (TextView) header.findViewById(R.id.ptr_face_header_last_update);
            mLoadView = (PullToRefreshFaceView) header.findViewById(R.id.ptr_load_view);
    
        }
    
    
        private void tryUpdateLastUpdateTime() {
            if (TextUtils.isEmpty(mLastUpdateTimeKey) || !mShouldShowLastUpdate) {
                mLastUpdateTextView.setVisibility(GONE);
            } else {
                String time = getLastUpdateTime();
                if (TextUtils.isEmpty(time)) {
                    mLastUpdateTextView.setVisibility(GONE);
                } else {
                    mLastUpdateTextView.setVisibility(VISIBLE);
                    mLastUpdateTextView.setText(time);
                }
            }
        }
    
    
        public void setLastUpdateTimeKey(String key) {
            if (TextUtils.isEmpty(key)) {
                return;
            }
            mLastUpdateTimeKey = key;
        }
    
        public void setLastUpdateTimeRelateObject(Object object) {
            setLastUpdateTimeKey(object.getClass().getName());
        }
    
        
         /**
         * 得到最后刷新时间
         *
         * @return
         */
        private String getLastUpdateTime() {
    
            if (mLastUpdateTime == -1 && !TextUtils.isEmpty(mLastUpdateTimeKey)) {
                mLastUpdateTime = getContext().getSharedPreferences(KEY_SharedPreferences, 0).getLong(mLastUpdateTimeKey, -1);
            }
            if (mLastUpdateTime == -1) {
                return null;
            }
            long diffTime = new Date().getTime() - mLastUpdateTime;
            int seconds = (int) (diffTime / 1000);
            if (diffTime < 0) {
                return null;
            }
            if (seconds <= 0) {
                return null;
            }
            StringBuilder sb = new StringBuilder();
            sb.append(getContext().getString(R.string.ai_jia_ptr_last_update));
    
            if (seconds < 60) {
                sb.append(seconds + getContext().getString(in.srain.cube.views.ptr.R.string.cube_ptr_seconds_ago));
            } else {
                int minutes = (seconds / 60);
                if (minutes > 60) {
                    int hours = minutes / 60;
                    if (hours > 24) {
                        Date date = new Date(mLastUpdateTime);
                        sb.append(sDataFormat.format(date));
                    } else {
                        sb.append(hours + getContext().getString(R.string.ai_jia_ptr_hours_ago));
                    }
    
                } else {
                    sb.append(minutes + getContext().getString(R.string.ai_jia_ptr_minutes_ago));
                }
            }
            return sb.toString();
        }
    
        private void resetView() {
            //隐藏加载view和停止动画
            mLoadView.stopAnimators();
            mLoadView.setVisibility(INVISIBLE);
    
        }
    
    
        /**
         * 重置,回到顶部的
         *
         * @param frame
         */
        @Override
        public void onUIReset(PtrFrameLayout frame) {
            resetView();
            mShouldShowLastUpdate = true;
            tryUpdateLastUpdateTime();
        }
    
    
        /**
         * 准备刷新,Header 将要出现时调用。
         *
         * @param frame
         */
        @Override
        public void onUIRefreshPrepare(PtrFrameLayout frame) {
            mShouldShowLastUpdate = true;
            tryUpdateLastUpdateTime();
            mLoadView.setVisibility(VISIBLE);
            mTitleTextView.setVisibility(VISIBLE);
            mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
    
        }
    
        /**
         * 开始刷新,Header 进入刷新状态之前调用。
         *
         * @param frame
         */
        @Override
        public void onUIRefreshBegin(PtrFrameLayout frame) {
            mShouldShowLastUpdate = false;
    
            /**
             * 让loadView开始动画
             * */
            mLoadView.startAnimators();
            mTitleTextView.setVisibility(VISIBLE);
            mTitleTextView.setText(R.string.ai_jia_ptr_refreshing);
            tryUpdateLastUpdateTime();
            mLastUpdateTimeUpdater.stop();
        }
    
        @Override
        public void onUIRefreshComplete(PtrFrameLayout frame) {
            /*hideRotateView();
            mProgressBar.setVisibility(INVISIBLE);*/
            /*
             * 加载完成,loadView停止动画
             * */
            mLoadView.stopAnimators();
            mTitleTextView.setVisibility(VISIBLE);
            mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_refresh_complete));
    
            // update last update time
            SharedPreferences sharedPreferences = getContext().getSharedPreferences(KEY_SharedPreferences, 0);
            if (!TextUtils.isEmpty(mLastUpdateTimeKey)) {
                mLastUpdateTime = new Date().getTime();
                sharedPreferences.edit().putLong(mLastUpdateTimeKey, mLastUpdateTime).commit();
            }
        }
    
    
        /**
         * 下拉过程中位置变化回调。
         *
         * @param frame
         * @param isUnderTouch
         * @param status
         * @param ptrIndicator
         */
        @Override
        public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
            float percent = Math.min(1f, ptrIndicator.getCurrentPercent());//得到下拉过程的位置比例
            if (status == PtrFrameLayout.PTR_STATUS_PREPARE) {
                mLoadView.setDegrees(percent * 360 * 4);
                ViewCompat.setScaleX(mLoadView, percent);
                ViewCompat.setScaleY(mLoadView, percent);
                mLoadView.setEyeRadius((int) (mLoadView.getEyeRadius()*percent));
                mLoadView.setEyeBallRadius((int) (mLoadView.getEyeBallRadius()*percent));
                mLoadView.setPerView(mLoadView.getRadiusCircle(), percent);
                if (percent < 0.5) {
                    mLoadView.setRadius((percent * 180));
                    mLoadView.setSweepRadius((180 - percent * 360));
                } else {
                    percent = (float) (percent - 0.5);
                    mLoadView.setRadius((90 - percent * 180));
                    mLoadView.setSweepRadius((percent * 360));
                }
    
            }
            final int mOffsetToRefresh = frame.getOffsetToRefresh();
            final int currentPos = ptrIndicator.getCurrentPosY();//当前位置
            final int lastPos = ptrIndicator.getLastPosY();//上一个位置
            if (currentPos < mOffsetToRefresh && lastPos >= mOffsetToRefresh) {
                if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
                    //下拉刷新
                    crossRotateLineFromBottomUnderTouch(frame);
    
                }
            } else if (currentPos > mOffsetToRefresh && lastPos <= mOffsetToRefresh) {
                if (isUnderTouch && status == PtrFrameLayout.PTR_STATUS_PREPARE) {
                    //释放刷新
                    crossRotateLineFromTopUnderTouch(frame);
    
                }
            }
        }
    
    
        /**
         * 释放刷新
         *
         * @param frame
         */
        private void crossRotateLineFromTopUnderTouch(PtrFrameLayout frame) {
            if (!frame.isPullToRefresh()) {
                mTitleTextView.setVisibility(VISIBLE);
                mTitleTextView.setText(R.string.ai_jia_ptr_release_to_refresh);
            }
        }
    
        /**
         * 下拉刷新
         *
         * @param frame
         */
        private void crossRotateLineFromBottomUnderTouch(PtrFrameLayout frame) {
            mTitleTextView.setVisibility(VISIBLE);
            if (frame.isPullToRefresh()) {
                mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
            } else {
                mTitleTextView.setText(getResources().getString(R.string.ai_jia_ptr_pull_down_to_refresh));
            }
        }
    
    
        private class LastUpdateTimeUpdater implements Runnable {
            private boolean mRunning = false;
    
            private void start() {
                if (TextUtils.isEmpty(mLastUpdateTimeKey)) {
                    return;
                }
                mRunning = true;
                run();
            }
    
            private void stop() {
                mRunning = false;
                removeCallbacks(this);
            }
    
            @Override
            public void run() {
                tryUpdateLastUpdateTime();
                if (mRunning) {
                    postDelayed(this, 1000);
                }
            }
        }
    }
    
    • FacePullToRefreshLayout
    
    
    public class FacePullToRefreshLayout extends PtrFrameLayout {
        private FacePullToRefreshHeader mPullToRefreshHeader;
        private RefreshListener refreshListener;
    
        public FacePullToRefreshLayout(Context context) {
            super(context);
            initViews();
        }
    
        public FacePullToRefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            initViews();
        }
    
        public FacePullToRefreshLayout(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            initViews();
        }
    
        private void initViews() {
            mPullToRefreshHeader = new FacePullToRefreshHeader(getContext());
            setHeaderView(mPullToRefreshHeader);
            addPtrUIHandler(mPullToRefreshHeader);
            setPtrHandler(new PtrDefaultHandler() {
                @Override
                public void onRefreshBegin(PtrFrameLayout frame) {
                    if (refreshListener != null) {
                        refreshListener.onRefresh(frame);
                    }
                }
            });
        }
    
        public FacePullToRefreshHeader getHeader() {
            return mPullToRefreshHeader;
        }
    
        public void setLastUpdateTimeKey(String key) {
            if (mPullToRefreshHeader != null) {
                mPullToRefreshHeader.setLastUpdateTimeKey(key);
            }
        }
    
    
        public void setLastUpdateTimeRelateObject(Object object) {
            if (mPullToRefreshHeader != null) {
                mPullToRefreshHeader.setLastUpdateTimeRelateObject(object);
            }
        }
    
    
        public interface RefreshListener {
            void onRefresh(PtrFrameLayout frame);
        }
    
        public void setRefreshListener(RefreshListener refreshListener) {
            this.refreshListener = refreshListener;
        }
    }
    
    
    • 布局
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  android:layout_width="match_parent"
                  android:layout_height="90dp"
                  android:gravity="center"
                  android:orientation="vertical">
    
    
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
    
            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:orientation="vertical">
    
                <com.pulltorefreshlibrary.view.PullToRefreshFaceView
                    android:id="@+id/ptr_load_view"
                    android:layout_width="50dp"
                    android:layout_height="50dp"
                    app:backgroupColor="#88c6c6c6"
                    />
    
                <TextView
                    android:id="@+id/ptr_face_header_title"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="下拉刷新..."
                    android:textColor="#666666"
                    android:textSize="12sp"/>
    
            </LinearLayout>
    
            <TextView
                android:id="@+id/ptr_face_header_last_update"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="2dp"
                android:text="距离上次刷新:08秒之前"
                android:textColor="#999999"
                android:textSize="10sp"/>
        </LinearLayout>
    
    
    </LinearLayout>
    
    

    想想,还是放张图片,比较有吸引力。

    Paste_Image.png

    好啦,就到此为止吧,代码不难,注释写得也清楚,所以就不说明了哈。主要还是因为我懒,以后再慢慢试着写点分析,自己的思路什么的。最后,源码放在Github上,觉得可以学到点东西的,start一下吧!点我,点我吧

    相关文章

      网友评论

      • 予以摩卡:没有上拉加载?
      • LukeMi:我下了你的代码,对底部那个选项实现挺感兴趣的,可是,代码,跑不起来,app module 里面java文件报红:sob:
        峥嵘岁月Z: @LukeMi 那就好,最近没怎么玩简书。没有及时看到
        LukeMi:找到问题了,群主你setting 少配了一个module app

      本文标题:何不自己动手去实现一个属于自己的个性化Android下拉刷新库?

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