一个Android下拉刷新样式

近来需求要求下拉刷新动画Android和iOS统一样式, 效果如下:

下拉刷新gif

使用SmartRefreshLayout这个框架自定义Header动画. 感觉这个框架还是很好用的.

  1. SmartRefreshLayout加入到项目:

    compile 'com.android.support:appcompat-v7:25.3.1'
    compile 'com.scwang.smartrefresh:SmartRefreshLayout:1.0.2-alpha-5'
    
  2. 在自定义的Application文件里初始化:

    public class App extends Application {
        @Override
        public void onCreate() {
            super.onCreate();
            initRefreshViewLayout();
        }
    
        private void initRefreshViewLayout() {
            MyRefreshLayout.init();
        }
    }
    
  3. 自定义个继承自SmartRefreshLayoutMyRefreshLayout文件:

    public class MyRefreshLayout extends SmartRefreshLayout {
        public static void init() {
            // 指定全局的下拉Header
            SmartRefreshLayout.setDefaultRefreshHeaderCreater(new DefaultRefreshHeaderCreater() {
                @Override
                public RefreshHeader createRefreshHeader(Context context, RefreshLayout layout) {
                    return new MyRefreshHeader(context);
                }
            });
    
            // 指定全局的上拉Footer
            SmartRefreshLayout.setDefaultRefreshFooterCreater(new DefaultRefreshFooterCreater() {
                @NonNull
                @Override
                public RefreshFooter createRefreshFooter(Context context, RefreshLayout layout) {
                    return new ClassicsFooter(context);
                }
            });
        }
    
        public MyRefreshLayout(Context context) {
            super(context);
            initView(context);
        }
    
        public MyRefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
            initView(context);
        }
    
        public MyRefreshLayout(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context);
        }
    
        private void initView(Context context) {
            setReboundDuration(300); // 设置回弹动画时长
            setPrimaryColorsId(R.color.white);  // 主题色
            setEnableAutoLoadmore(false); // 设置是否监听列表在滚动到底部时触发加载事件
    
        }
    
        // 下拉/上拉完成
        public void complete() {
            if (mState == RefreshState.Loading) {
                finishLoadmore();
            } else {
                finishRefresh();
            }
        }
    }
    
  4. 创建个MyRefreshHeader文件, 在这里做自定义的动画:

    public class MyRefreshHeader extends RelativeLayout implements RefreshHeader {
        private CircleProgressView mCircleProgressView;
        private TextView mTextView;
        private ImageView mArrow;
        private boolean isArrowDown = false;
    
        public MyRefreshHeader(Context context) {
            super(context);
            this.initView(context, null, 0);
        }
    
        public MyRefreshHeader(Context context, AttributeSet attrs) {
            super(context, attrs);
            this.initView(context, attrs, 0);
        }
    
        public MyRefreshHeader(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            this.initView(context, attrs, defStyleAttr);
        }
    
        private void initView(Context context, AttributeSet attrs, int defStyleAttr) {
            setMinimumHeight(dp2px(context, 80));
            LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            params.addRule(CENTER_IN_PARENT);
            View headerView = View.inflate(context, R.layout.my_refresh_header, null);
            mCircleProgressView = (CircleProgressView) headerView.findViewById(R.id.circleProgressView);
            mTextView = (TextView) headerView.findViewById(R.id.textview);
            mArrow = (ImageView) headerView.findViewById(R.id.iv_refresh_center);
            addView(headerView, params);
        }
    
        @Override
        public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { // 尺寸定义完成
        }
    
        @Override
        public void onPullingDown(float percent, int offset, int headHeight, int extendHeight) { // 手指拖动下拉(会连续多次调用)
            if (mCircleProgressView == null) return;
            float startPercent = 0.20f;
    
            if (percent > startPercent && percent < 1) {
                float tempPercent = (percent-startPercent) * 1.0f / (1 - startPercent);
                mCircleProgressView.setProgressPersent(tempPercent);
            }
        }
    
        @Override
        public void onReleasing(float percent, int offset, int headHeight, int extendHeight) {  // 手指释放之后的持续动画
        }
    
        @Override
        public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) {
            RotateAnimation ta = new RotateAnimation(0, 360,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
            ta.setDuration(500);
            ta.setRepeatCount(10000);
            ta.setInterpolator(new LinearInterpolator());
            ta.setFillAfter(true);
            mCircleProgressView.startAnimation(ta);
        }
    
        @Override
        public int onFinish(RefreshLayout layout, boolean success) {
            mCircleProgressView.clearAnimation();
            return 100; // 动画结束,延迟多少毫秒之后再收回
        }
    
        @Override
        public void setPrimaryColors(int... colors) {
            setBackgroundColor(getResources().getColor(R.color.activity_bg));
        }
    
        @NonNull
        public View getView() {
            return this;
        }
    
        @Override
        public SpinnerStyle getSpinnerStyle() {
            return SpinnerStyle.Translate;
        }
    
        @Override
        public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { // 状态改变事件
            switch (newState) {
                case None: // 无状态
                    if (mCircleProgressView != null) mCircleProgressView.setProgressPersent(0);
                    if (isArrowDown) {
                        arrowAnimation();
                        isArrowDown = false;
                    }
                    break;
                case PullDownToRefresh: // 可以下拉状态
                    mTextView.setText("下拉即可刷新");
                    break;
                case Refreshing: // 刷新中状态
                    mTextView.setText("正在刷新数据...");
                    break;
                case ReleaseToRefresh:  // 释放就开始刷新状态
                    mTextView.setText("松开立即刷新");
                    if (!isArrowDown) {
                        arrowAnimation();
                        isArrowDown = true;
                    }
                    break;
            }
        }
    
        private void arrowAnimation() {
            RotateAnimation ra = new RotateAnimation(0, isArrowDown ? 0 : 180.0f,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
            ra.setDuration(200);
            ra.setRepeatCount(0);
            ra.setInterpolator(new LinearInterpolator());
            ra.setFillAfter(true);
            mArrow.startAnimation(ra);
        }
    
        /**
         * dp转px
         */
        private int dp2px(Context context, float dpVal) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dpVal, context.getResources().getDisplayMetrics());
        }
    }   
    
  5. 创建一个贝塞尔画圆工具CircleProgressView来画圆弧:

    public class CircleProgressView extends View {
        private RectF mRectF;
        private Paint mPaint;
    
        private int width;
        private int height;
        private float persent = 0.0f;  // 进度
        private float lineWidth = 2; // 线宽
    
        public CircleProgressView(Context context) {
            super(context);
            lineWidth = dp2px(context, 1.0f);
            initView();
        }
    
        public CircleProgressView(Context context, AttributeSet attrs) {
            super(context, attrs);
            lineWidth = dp2px(context, 1.0f);
            initView();
        }
    
        @Override
        public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    
            width = MeasureSpec.getSize(widthMeasureSpec);
            height = MeasureSpec.getSize(heightMeasureSpec);
            if (width != height) {
                int min = Math.min(width, height);
                width = min;
                height = min;
            }
            mRectF.left = lineWidth / 2;
            mRectF.top = lineWidth / 2;
            mRectF.right = width - lineWidth / 2;
            mRectF.bottom = height - lineWidth / 2;
        }
    
        private void initView() {
            mRectF = new RectF();
            mPaint = new Paint();
            mPaint.setAntiAlias(true); 
            mPaint.setColor(getResources().getColor(R.color.theme_color_80));
            mPaint.setStyle(Paint.Style.STROKE);
            mPaint.setStrokeWidth(lineWidth);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
    
            canvas.drawColor(Color.TRANSPARENT);
            canvas.drawArc(mRectF, 90, persent * 350, false, mPaint);
        }
    
        public void setProgressPersent(float persent) {
            this.persent = persent;
            this.invalidate();
        }
    
        // dp转px
        private int dp2px(Context context, float dpVal) {
            return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                    dpVal, context.getResources().getDisplayMetrics());
        }
    }
    
  6. res\layout\创建一个my_refresh_header.xml文件:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingTop="5dp"
        >
    
        <ImageView
            android:id="@+id/iv_head"
            android:layout_width="135dp"
            android:layout_height="15dp"
            android:layout_centerHorizontal="true"
            android:src="@mipmap/refresh_head"
            />
    
        <RelativeLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:layout_marginTop="12dp"
            android:layout_below="@+id/iv_head"
            >
    
            <RelativeLayout
                android:id="@+id/rlCircleContainer"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                >
                <app.laowen.com.downrefreshdemo.CircleProgressView
                    android:id="@+id/circleProgressView"
                    android:layout_width="22dp"
                    android:layout_height="22dp"
                    android:layout_centerInParent="true"
                    />
                <ImageView
                    android:id="@+id/iv_refresh_center"
                    android:layout_width="15dp"
                    android:layout_height="15dp"
                    android:layout_centerInParent="true"
                    android:scaleType="fitCenter"
                    android:src="@mipmap/arrow_up"
                    />
            </RelativeLayout>
    
            <TextView
                android:id="@+id/textview"
                style="@style/text_24_80"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_centerVertical="true"
                android:layout_toRightOf="@+id/rlCircleContainer"
                android:paddingLeft="10dp"
                android:text="下拉即可刷新"
                />
        </RelativeLayout>
    
    </RelativeLayout>
    
  7. 使用示例:

    public class MainActivity extends AppCompatActivity {
        private MyRefreshLayout refreshLayout;
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            refreshLayout = (MyRefreshLayout)findViewById(R.id.refreshLayout);
            refreshLayout.setOnRefreshListener(new OnRefreshListener() {
                @Override
                public void onRefresh(RefreshLayout refreshlayout) {
                    refreshlayout.finishRefresh(3000); // 模拟请求数据, 3秒后结束
                }
            });
            refreshLayout.setOnLoadmoreListener(new OnLoadmoreListener() {
                @Override
                public void onLoadmore(RefreshLayout refreshlayout) {
                    refreshlayout.finishLoadmore(3000);
                }
            });
        }
    }
    

    xml:

    <?xml version="1.0" encoding="utf-8"?>
    <app.laowen.com.downrefreshdemo.MyRefreshLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:id="@+id/refreshLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/activity_bg"
        >
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerview"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:overScrollMode="never"
            android:background="#236678"
            />
    
    </app.laowen.com.downrefreshdemo.MyRefreshLayout>
    
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349

推荐阅读更多精彩内容