自定义下拉刷新之仿AcFun下拉刷新

俗话说好记性不如烂笔头,决定以后将研究过的东西写到博客里,方便自己以后查找,也方便技术分享。第一篇从基础的自定义下拉刷新开始。这里说下,我是在大神的肩膀上进行自定义的,因为自己重写下拉刷新的话会有很多边界,状态和动画等问题要处理,以前写过一次,效果和功能可以实现,但是有不少bug,而且封装也不好。因此直接使用android-Ultra-Pull-To-Refresh

效果图

本文是基于github上的一个开源项目:android-Ultra-Pull-To-Refresh(下面简称UltraPtr) ,有兴趣的同学可以去看看。废话不多说,开车。

1.准备headview头部布局

<?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:layout_centerHorizontal="true"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:paddingTop="0dp">

    <ImageView
        android:id="@+id/iv_ptr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="100dp"
        android:src="@drawable/ptr_dra" />

    <TextView
        android:id="@+id/tv_ptr"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="下拉刷新..." />
</LinearLayout>

布局很简单,就是线性布局套一个imageview和textview。

2. 实现PtrUIHandler,处理下拉刷新回调

这一步是最重要的,几乎所有的刷新样式变化的逻辑都是在这里进行处理的,看代码。

public class RefreshHeadView extends FrameLayout implements PtrUIHandler {
    private Context context;
    private ImageView iv;
    private TextView tv;
    private AnimationDrawable animationDrawable;

    public RefreshHeadView(Context context) {
        super(context);
        this.context = context;
        initView();
    }

    private void initView() {
        View.inflate(context, R.layout.ptrheadview, this);
        iv = (ImageView) findViewById(R.id.iv_ptr);
        tv = (TextView) findViewById(R.id.tv_ptr);
        animationDrawable = (AnimationDrawable) iv.getDrawable();
    }

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

    public RefreshHeadView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public void onUIReset(PtrFrameLayout frame) {
        //onUIRefreshComplete之后调用,用于复位
        setImageAndText(R.mipmap.ptr_loading_9, "刷新完成...");
    }

    @Override
    public void onUIRefreshPrepare(PtrFrameLayout frame) {
        //开始下拉的时候调用一次
        setImageAndText(R.mipmap.ptr_loading_1, "下拉刷新...");
    }

    @Override
    public void onUIRefreshBegin(PtrFrameLayout frame) {
        //正在刷新的时候调用,开始帧动画
        iv.setImageDrawable(animationDrawable);
        animationDrawable.start();
        tv.setText("F5的能量女朋友出现了");
    }

    @Override
    public void onUIRefreshComplete(PtrFrameLayout frame) {
        //刷新完成的时候调用
        animationDrawable.stop();
        setImageAndText(R.mipmap.ptr_loading_9, "刷新完成...");
    }

    @Override
    public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) {
        //触发刷新的高度  默认是头部高度的1.2倍
        final int offsetToRefresh = ptrIndicator.getOffsetToRefresh();
        //当前下拉的高度
        final int currentPos = ptrIndicator.getCurrentPosY();
        //计算下拉的百分比
        int persent = (int) (((double) currentPos / offsetToRefresh) * 100);
        //只有在prepare状态的时候才执行下面的替换图片
        if (status!=PtrFrameLayout.PTR_STATUS_PREPARE) {
            return;
        }
        //根据不同的比例替换不同的图片和文字
        if (persent < 50) {
            setImageAndText(R.mipmap.ptr_loading_1, "下拉刷新");
        } else if (persent < 60) {
            setImageAndText(R.mipmap.ptr_loading_2, "下拉刷新");
        } else if (persent < 70) {
            setImageAndText(R.mipmap.ptr_loading_3, "下拉刷新");
        } else if (persent < 80) {
            setImageAndText(R.mipmap.ptr_loading_4, "下拉刷新");
        } else if (persent < 100) {
            setImageAndText(R.mipmap.ptr_loading_5, "下拉刷新");
        } else {
            setImageAndText(R.mipmap.ptr_loading_6, "松开松开~");
        }
    }

    private void setImageAndText(int res, String text) {
        iv.setImageResource(res);
        tv.setText(text);
    }
}
代码里基本上注释都写很清楚了,只需要注意几个地方。 

在构造方法中initView().将刚才定义的headview填充进来,并找到里面的imageview和textview

实现PtrUIHandler,需要重写几个方法。
onUIRefreshPrepare 下拉准备,刚开始下拉的时候就会调用一次。这里我也没什么好准备的,就设置了第一张图片。

onUIRefreshBegin 这是刷新的时候调用一次,按照acfun的下拉刷新,会显示一个萌萌的妹纸动画。所以对图片设置帧动画,并开启帧动画,这样刷新的时候就是小人动的效果

onUIRefreshComplete 刷新完成,停止帧动画

onUIRefreshReset 复位,最后调用,设置了一张图片。

onUIPositionChange 这是比较复杂一些的方法。这个回调会传给我们下拉的距离和手指是否在下拉等参数,用于下拉的时候变换图片。这里介绍一下参数,frame不用说了,父类。isUnderTouch,表示手指是否按在屏幕上。status:表示当前的刷新状态,有init  prepare loading comlete四种。最后ptrIndicator是手势类。首先获取到触发刷新的高度,这个是可以自己设置的,默认头部高度1.2倍。然后获取当前下拉的高度,根据两个高度计算下拉的百分比。最后根据百分比来替换不同的图片就可以了。其中有一点要注意,只有status在prepare的时候才执行替换图片,否则高度每次变化都会调用此方法不停替换图片。

 //只有在prepare状态的时候才执行下面的替换图片
        if (status!=PtrFrameLayout.PTR_STATUS_PREPARE) {
            return;
        }
至此,整个headview和uihandler都处理完成。

3,自定义PullToRefreshLayout

public class PullToRefreshLayout extends PtrFrameLayout {
    private Context context;
    private float startY;
    private float startX;
    // 记录viewPager是否拖拽的标记
    private boolean mIsHorizontalMove;
    // 记录事件是否已被分发
    private boolean isDeal;
    // viewpager触发滑动的距离
    private int mTouchSlop;
    private ArrayList<ViewPager> mViewPagers = new ArrayList<>();

    public PullToRefreshLayout(Context context) {
        super(context,null);
    }

    public PullToRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        this.context=context;
        initView();
    }

    private void initView() {
        RefreshHeadView headview = new RefreshHeadView(context);
        //设置headview
        setHeaderView(headview);
        //设置uihandler回调  因为headview实现了PtrUIHandler接口,所以这里还是headview
        addPtrUIHandler(headview);

        //阻尼系数 默认1.7f 越大下拉越吃力
        setResistance(1.7f);
        //触发刷新时移动的位置比例 默认,1.2f,移动达到头部高度1.2倍时可触发刷新操作。
        setRatioOfHeaderHeightToRefresh(1.2f);
        //回弹延时  默认 200ms,回弹到刷新高度所用时间
        setDurationToClose(500);
        //头部回弹时间 默认1000ms
        setDurationToCloseHeader(1500);
        //下拉刷新还是释放刷新  默认下拉刷新default is false
        setPullToRefresh(false);
        // default is true 刷新时是否保持头部
        setKeepHeaderWhenRefresh(true);
        //viewpager处理
        final ViewConfiguration configuration = ViewConfiguration.get(getContext());
        mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration);
    }


    //处理下拉刷新和viewpager滑动冲突
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mViewPagers.size() ==0) {
            return super.dispatchTouchEvent(ev);
        }
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                // 记录手指按下的位置
                startY = ev.getY();
                startX = ev.getX();
                // 初始化标记
                mIsHorizontalMove = false;
                isDeal = false;
                break;
            case MotionEvent.ACTION_MOVE:
                // 如果已经判断出是否由横向还是纵向处理,则跳出
                if (isDeal) {
                    break;
                }
                /**拦截禁止交给Ptr的 dispatchTouchEvent处理**/
                mIsHorizontalMove = true;
                // 获取当前手指位置
                float endY = ev.getY();
                float endX = ev.getX();
                float distanceX = Math.abs(endX - startX);
                float distanceY = Math.abs(endY - startY);
                if (distanceX != distanceY) {
                    // 如果X轴位移大于Y轴位移,那么将事件交给viewPager处理。
                    //横向滑动的距离大于触发viewpager滑动事件的距离并且 横向大于竖向
                    if (distanceX > mTouchSlop && distanceX > distanceY) {
                        mIsHorizontalMove = true;
                        isDeal = true;
                    } else if (distanceY > mTouchSlop) {
                        mIsHorizontalMove = false;
                        isDeal = true;
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //下拉刷新状态时如果滚动了viewpager 此时mIsHorizontalMove为true 会导致PtrFrameLayout无法恢复原位
                // 初始化标记,
                mIsHorizontalMove = false;
                isDeal = false;
                break;
        }
        if (mIsHorizontalMove) {
            //相当于拦截事件,不交给ptr处理,直接向下分发
            return dispatchTouchEventSupper(ev);
        }
        //交给ptr,不做拦截,正常下拉
        return super.dispatchTouchEvent(ev);
    }

    @Override
    protected void onLayout(boolean flag, int i, int j, int k, int l) {
        super.onLayout(flag, i, j, k, l);
        if (flag) {
            getAlLViewPager(mViewPagers, this);
        }
    }

    /**
     * 获取SwipeBackLayout里面的ViewPager的集合
     */
    private void getAlLViewPager(List<ViewPager> mViewPagers, ViewGroup parent) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            if (child instanceof ViewPager) {
                mViewPagers.add((ViewPager) child);
            } else if (child instanceof ViewGroup) {
                getAlLViewPager(mViewPagers, (ViewGroup) child);
            }
        }
    }
}
这个类就是我们最终要使用到布局里面的类。比较简单,分为两个部分

1.initPara()  用于设置各种参数,具体看代码都有注释

2,处理下拉刷新和viewpager的滑动冲突。虽然UIPtr的作者也更新了处理冲突的方法,但是处理的并不太好,在viewpager上的滑动还是太敏感。所以这里重写dispatchTouchEvent方法,判断子view里是否有viewpager,如果没有,直接交给父类处理。如果有,判断滑动方向和距离。如果是横向滑动,那么就不要交给父类处理,直接跳过父类,交给父类的父类处理。否则就是正常下拉刷新,还是交给父类。

至此,整个自定义下拉刷新完成。

源码地址 https://github.com/itwangyu/MyPullRefreshDemo



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值