自定义简单实现滑动下拉刷新效果

最近在看了一遍关于触摸事件的流程,所以索性写个例子总结一下,然后就产生了这个小例子,大神请绕过~:

一个自己制作的简单的下拉刷新的小控件,主要基于LinearLayout实现的,在内部只是关联了一个listview,非常简单,下面见效果图:

这里写图片描述

主要过程在于触摸事件的把控以及scroller的运用,还是比较简单的,首先看看布局:

<com.my.ownpulltorefreshdemo.ThisView
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:id="@+id/father"
        android:layout_height="match_parent">
        <TextView
            android:layout_width="match_parent"
            android:layout_height="80dp"

            android:layout_above="@+id/list"
            android:text="anny_lin的刷新小demo"
            android:layout_gravity="center"
            android:gravity="center"
            android:textColor="#ffffff"
            //这句话是将刷新头部隐藏在上方的关键
            android:layout_marginTop="-80dp"
            android:textSize="20sp"
            android:background="#5537c1"/>
        <ListView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/list"

            ></ListView>
    </com.my.ownpulltorefreshdemo.ThisView>

上面主要使用了marginTop=负数来进行隐藏刷新头部的作用。

看自定义LinearLayout的代码:

首先我们将ThisView继承自LinearLayout,重写他的构造函数,并且在构造函数中出事后一些参数:

 private void init(Context context) {
        //滑动的最小距离
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
        mScroller=new Scroller(context);
        maxSmoothDistance= DensityUtil.dip2px(context,MAX_DISTANCE);
        Log.e("maxSmoothDistance==",maxSmoothDistance+"");
    }

mTouchSlop:主要运用于判断是否滑动的最小距离。

maxSmoothDistance:主要用来定义用户下拉的最大距离,我们需要把最大距离即MAX_DISTANCE转换成像素使用,写了一个基本的dp和px的转换类DensityUtil。

mScroller:主要运用于各种情况下的下拉刷新的自主滑动操作。

重写onLayout函数进行ThisView中子View的获取:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int count = this.getChildCount();
        for (int i = 0; i < count; i++) {
            if (getChildAt(i) instanceof ListView) {
                listView = (ListView) getChildAt(i);
            }
            if (getChildAt(i)instanceof TextView)
            {
                 refreshTextView= (TextView) getChildAt(i);
                putRefreshHeight=refreshTextView.getHeight();
                Log.e("putRefreshHeight","==="+putRefreshHeight);
            }

        }
        if (listView == null) {
            try {
                throw new Exception("please checked out the Linearayout surely has a Listvew!");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

这里说明一下为什么在onLayout中获取子View,而不是其他方法中,我们知道View的绘制流程是通过onMeasure->onLayout->onDraw进行依次调用的,onLayout用来进行子View布局的确定,从而确定父容器的本身布局,所以我们在onLayout完成后在拿到子View对象的引用是最保险的。
其次,很多时候我们会遇到要使用getWidth()或者getHeight()进行空间或者布局的宽高获取,一般而言,我们极大可能会遇到获取的宽高=0的情况,可能的原因就是布局还没有初始化完成的时候我们却想要获取宽高,所以导致的这个问题,所以我们在layout完成后进行布局高宽的获取,即:
refreshTextView.getHeight();

接下来就是重头戏了
触摸事件的判断:
先看我们的onInterceptTouchEvent():

@Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept=false;
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            oldInterceptX = (int) ev.getX();
            oldInterceptY = (int) ev.getY();
            intercept=false;
            if (!mScroller.isFinished()){
                mScroller.abortAnimation();
                intercept=true;
            }

        } else if (action == MotionEvent.ACTION_MOVE) {
            interceptX = (int) ev.getX();
            interceptY = (int) ev.getY();
            int distance = interceptY - oldInterceptY;
            //向下滑动,listview到头顶了,滑动最小距离超过mTouchSlop,
            // 且当前正在刷新,则进行下拉刷新的实现
            if (interceptY >oldInterceptY
                    && Math.abs(distance) > mTouchSlop
                    && isOnTop()
                    &&!isRefresh) {
//                Log.e("canReshresh",true+"");
                intercept=true;
            }else
                intercept=false;
        } else if (action == MotionEvent.ACTION_UP) {
            oldInterceptX = (int) ev.getX();
            oldInterceptY = (int) ev.getY();
            intercept=false;
        }
        return intercept;
    }

关于触摸事件的原理,在这里就不过多的去详述了,不理解的请自行查阅。
看上面的代码,我们定义了几个变量来记录触摸时候的坐标:

//正在触摸的坐标
 private int interceptX, interceptY;
 //上一次触摸是的坐标
 private int oldInterceptX, oldInterceptY;

我们在ACTION_DOWN的时候返回了false,让父容器下发事件,Listview能够接受到事件进行滑动
当我们在ACTION_MOVE的时候,我们要进行判断是父容器拦截事件还是向下继续传递事件,我们常规来想就是当listview到达头部的时候,我们进行控件的下拉操作,即:

 private boolean isOnTop() {
        View child_one = listView.getChildAt(0);
        //通过第一个child判断是否到达顶端,并且进行下拉操作
        if (child_one.getY() - listView.getPaddingTop() == 0) {
//            Log.e("isOnTop===", true + "");
            return  true;
        }
        return false;
    }

且,我们是需要进行下拉操作,并且父容器现在并没有进行刷新操作:

interceptX = (int) ev.getX();
            interceptY = (int) ev.getY();
            int distance = interceptY - oldInterceptY;
            //向下滑动,listview到头顶了,滑动最小距离超过mTouchSlop,
            // 且当前不在刷新,则进行下拉刷新的实现
            if (interceptY >oldInterceptY
                    && Math.abs(distance) > mTouchSlop
                    && isOnTop()
                    &&!isRefresh) {
//                Log.e("canReshresh",true+"");
                intercept=true;
            }else
                intercept=false;

ACTION_UP的时候我们还是不拦截事件。

当父容器拦截了事件后,就会执行onTouchEvent:

int action = event.getAction();
        lastX = oldInterceptX;
        lastY = oldInterceptY;
//        Log.e("lastY",lastY+"");
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                //由于onInterceptTouchEvent在ACTION_DOWN的时候返回的是false,所以事件往下发放,这里
                //如果写:
//                    lastX = (int) event.getX();
//                    lastY = (int) event.getY();
                //是拦截不到的
                if (!mScroller.isFinished()){
                    mScroller.abortAnimation();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                currentX = (int) event.getX();
                currentY = (int) event.getY();
                int moveX = Math.abs(currentX - lastX);
                int moveY = Math.abs(currentY - lastY);
//                Log.e("currentY",currentY+"");
//                Log.e("lastY",lastY+"");

//                &&Math.abs(getScrollY())<maxSmoothDistance

                if (moveY > moveX) {
                    scrollTo(0, -(currentY-lastY));
                }
                break;
            case MotionEvent.ACTION_UP:

                lastX = (int) event.getX();
                lastY = (int) event.getY();
                currentX = (int) event.getX();
                currentY = (int) event.getY();
                smoothScrollTo();
                break;
        }
        return true;

请注意看ACTION_DOWN的注释,因为我们父容器并没有拦截此事件,所以我们在onTouchEvent中是不会执行ACTION_DOWN操作的。

ACTION_DOWN中我们要判断是否是竖直方向的下拉,如果是,则进行scrollTo操作:

currentX = (int) event.getX();
                currentY = (int) event.getY();
                int moveX = Math.abs(currentX - lastX);
                int moveY = Math.abs(currentY - lastY);
//                Log.e("currentY",currentY+"");
//                Log.e("lastY",lastY+"");

//                &&Math.abs(getScrollY())<maxSmoothDistance

                if (moveY > moveX) {
                    scrollTo(0, -(currentY-lastY));
                }

在ACTION_UP的时候我们要进行scroller的使用,判断是否完成刷新,或者达不到刷新的要求就自动滑动回原来的位置,smoothScrollTo方法:

 //移动要移动到得位置坐标destX,destY
    public void smoothScrollTo(){
        int scrollY=getScrollY();
        Log.e("scrolly-----------",""+getScrollY());
        if (Math.abs(scrollY)>0){
            //当下拉刷新布局全部显示时候,进行刷新操作,否则,不进行刷新操作
            if (Math.abs(scrollY)>putRefreshHeight){
                mScroller.startScroll(0,scrollY,0,-scrollY-putRefreshHeight,800);
                isRefresh=true;
                invalidate();
            }else
            {
                mScroller.startScroll(0,scrollY,0,-scrollY,800);
                invalidate();
                isRefresh=false;
            }
        }

主要代码展示如上所示,如果想要这个demo的同学,可以去我的gitHub中查看下载:
https://github.com/JerryChan123/android-learning

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值