SwipeRefreshLayout 仿IOS ,下拉弹回效果

本文介绍如何为SwipeRefreshLayout添加下拉时的视图弹出效果,通过重写dispatchTouchEvent方法并计算滑动距离来实现子控件的平滑移动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前的项目下拉刷新用的都是XListView,用久了之后就有些审美疲劳了。谷歌推出的SwipeRefreshLayout很好用,样式也蛮好看的,用起来也比较方便。但是强迫症表示,还是希望能有下拉弹出和弹回去的效果,就尝试下SwipeRefreshLayout增加下拉弹出效果。
效果图

这里写图片描述


大致思路:获取SwipeRefreshLayout子控件的位置,在SwipeRefreshLayout的dispatchTouchEvent方法里处理滑动监听事件,计算滑动的距离,移动子控件的位置,达到弹出的效果。
直接上代码:
package com.example.zet.uncaughtexception;

import android.content.Context;
import android.graphics.Rect;
import android.support.v4.widget.SwipeRefreshLayout;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.TranslateAnimation;

/**
 * Created by 仓颉 on 2015/11/16.
 */
public class MySwiperefreshlayout extends SwipeRefreshLayout {

    private static final String TAG = "MySwiperefreshlayout ";

    //比如手指移动了100px, 那么View就只移动50px
    //目的是达到一个延迟的效果
    private static final float MOVE_MODE = 0.5f;

    //弹回去的时间
    private static final int ANIM_TIME = 300;

    //MySwiperefreshlayout的子View,这个表示RecycleView
    private View view;

    //MySwiperefreshlayout的子View,这个表示CircleImageView(就是那个圈圈)
    private View child;

    //手指按下时的Y值, 用于在移动时计算移动距离
    private float startY;

    //用于记录正常的布局位置
    private Rect rect = new Rect();

    //在手指滑动的过程中记录是否移动了布局
    private boolean isMoved = false;

    //是否已经移动到顶部,手指按下时,判断是否能够下拉
    private boolean isTop = false;

    //延迟时间
    public static int time;

    public static int getTime() {
        return time;
    }

    public static void setTime(int time) {
        MySwiperefreshlayout.time = time;
    }

    public MySwiperefreshlayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            child = getChildAt(0);
            view = getChildAt(getChildCount() - 1);
        }
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        if (view == null) return;
        //RecycleView的初始信息,这个位置保留之后,保持不变
        rect.set(view.getLeft(), view.getTop(), view
                .getRight(), view.getBottom());
    }

    /**
     * 滑动监听,处理下拉事件
     */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (view == null) {
            return super.dispatchTouchEvent(ev);
        }
        int action = ev.getAction();
        switch (action) {

            case MotionEvent.ACTION_DOWN:
                canDropDown();
                //记录按下时的Y值
                startY = ev.getY();
                break;

            case MotionEvent.ACTION_UP:
                if (!isMoved) break;  //没有移动布局,不执行
                //移动的距离
                int distance = (int) ((int) (ev.getY() - startY) * MOVE_MODE);
                //如果移动的距离大于CircleImageView.getTop,那么缩小移动距离,达到加载中的效果
                if (distance > child.getTop()) {
                    distance = child.getTop() + 30;
                }
                //Recycle移动到CircleImageView下拉的距离,表示加载中
                view.layout(rect.left, rect.top + distance,
                        rect.right, rect.bottom + distance);
                // 开启动画
                TranslateAnimation anim = new TranslateAnimation(0, 0, view.getTop(),
                        rect.top);
                anim.setDuration(ANIM_TIME);
                if (time > 0) {
                    //设置延迟时间
                    //移动的最大距离 小于这个距离不刷新
                    if (distance > 92) {
                        anim.setStartOffset(time);
                    }
                }
                view.startAnimation(anim);
                // 设置回到正常的布局位置
                view.layout(rect.left, rect.top,
                        rect.right, rect.bottom);
                //重置
                isTop = false;
                isMoved = false;
                startY = 0;
                break;
            case MotionEvent.ACTION_MOVE:
                //是否允许移动
                if (!canDropDown()) {
                    break;
                }
                //计算手指移动的距离
                int distanceMove = (int) (ev.getY() - startY);
                //允许移动并且是上拉的情况次下,移动布局
                if (isTop && distanceMove > 0) {
                    int offset = (int) (distanceMove * MOVE_MODE);
                    //随着手指的移动而移动布局
                    view.layout(rect.left, rect.top + offset,
                            rect.right, rect.bottom + offset);
                    isMoved = true;  //记录移动了布局
                }
                break;
            default:
                break;
        }

        return super.dispatchTouchEvent(ev);
    }


    /**
     * 判断是否滚动到顶部
     */
    private boolean canDropDown() {
        if (child.getVisibility() == View.VISIBLE)
            isTop = true;
        else
            isTop = false;
        return isTop;
    }

}
实现原理很简单,唯一不好判断的子控件是否滑动到顶部了。因为在Swiperefreshlayout的 getScrollY()一直是为0的,这里用了一个取巧的办法,就是判断CircleImageView是否显示,查看源码我们可以看到,CircleImageView是Swiperefreshlayout初始化的时候加载进去的,当滑动到顶部的时候,CircleImageView状态就为VISIBLE了。

这里写图片描述

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值