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

来源:互联网 发布:电脑定闹钟软件 编辑:程序博客网 时间:2024/05/16 01:51

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

一个自己制作的简单的下拉刷新的小控件,主要基于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
原创粉丝点击