您的位置:首页 > 其它

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

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

一个自己制作的简单的下拉刷新的小控件,主要基于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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: