打造Android万能上拉下拉刷新框架--XRefreshView(一)

打造Android万能上拉下拉刷新框架--XRefreshView(三)



一、前言

自从上次发表了打造android万能上拉下拉刷新框架——XRefreshView (一)之后,期间的大半个月一直都非常忙。可是我每天晚上下班以后都有在更新和维护XRefreshView,也依据一些朋友的意见攻克了一些问题,这次之所以写这篇文章。是由于XRefreshView已经到了一个功能相对可靠和稳定的一个阶段。以下我会介绍下XrefreshView的最新功能和使用方法。以及实现的主要思路。

二、更新

2.1推断下拉上拉刷新时机方式的改动

之前是通过 refreshView.setRefreshViewType(XRefreshViewType.ABSLISTVIEW);这样来预先设置view的类型来选择相应推断时机的方法。如今已经不用这样做了,改成了以下这样。

/**
* @return Whether it is possible for the child view of this layout to
* scroll up. Override this if the child view is a custom view.
*/
public boolean canChildPullDown() {
if (child instanceof AbsListView) {
final AbsListView absListView = (AbsListView) child;
return canScrollVertically(child, -1)
|| absListView.getChildCount() > 0
&& (absListView.getFirstVisiblePosition() > 0 || absListView
.getChildAt(0).getTop() < absListView
.getPaddingTop());
} else {
return canScrollVertically(child, -1) || child.getScrollY() > 0;
}
} public boolean canChildPullUp() {
if (child instanceof AbsListView) {
AbsListView absListView = (AbsListView) child;
return canScrollVertically(child, 1)
|| absListView.getLastVisiblePosition() != mTotalItemCount - 1;
} else if (child instanceof WebView) {
WebView webview = (WebView) child;
return canScrollVertically(child, 1)
|| webview.getContentHeight() * webview.getScale() != webview
.getHeight() + webview.getScrollY();
} else if (child instanceof ScrollView) {
ScrollView scrollView = (ScrollView) child;
View childView = scrollView.getChildAt(0);
if (childView != null) {
return canScrollVertically(child, 1)
|| scrollView.getScrollY() != childView.getHeight()
- scrollView.getHeight();
}
}else{
return canScrollVertically(child, 1);
}
return true;
} /**
* 用来推断view在竖直方向上能不能向上或者向下滑动
* @param view v
* @param direction 方向 负数代表向上滑动 ,正数则反之
* @return
*/
public boolean canScrollVertically(View view, int direction) {
return ViewCompat.canScrollVertically(view, direction);
}

正如你所见,ViewCompat.canScrollVertically(view, direction)这种方法能够用来推断view能不能向上或者向下滑动,从而能够推断view有没有到达顶部或者底部。在4.0以后在个方法一般是非常管用的。可是2.3.3曾经则不是这样,为了兼容2.3.3我又做了一些view类型的推断。通过view的类型来提供特别的推断到达顶部或者底部的方法。普通情况下,经常使用的view通过上述的方法都能够准确的推断出有没有到达顶部或者底部,可是假设你要刷新的是一个复杂的或者自己定义的view,也能够通过下面的方式来做

refreshView.setOnTopRefreshTime(new OnTopRefreshTime() {

			@Override
public boolean isTop() {
return stickyLv.getFirstVisiblePosition() == 0;
}
});
refreshView.setOnBottomLoadMoreTime(new OnBottomLoadMoreTime() { @Override
public boolean isBottom() {
return stickyLv.getLastVisiblePosition() == mTotalItemCount - 1;
}
});

XRefreshView把推断view到达顶部和底部的工作交给你去做了,你仅仅要告诉XRefreshView什么时候是正确的刷新时机即可了,与上次博客中提到的方法不同的是,XRefreshView这次提供了两个接口,把顶部和底部的推断时机给分开了。主要是考虑到下拉刷新和上拉载入有的时候并非都须要的。

2.2headview和footview上下移动时的方式的改动

一開始,移动headview和footview我是通过属性动画来移动的

public static void moveChildAndAddedView(View child, View addView,
float childY, float addY, int during, AnimatorListener... listener) {
// 属性动画移动
ObjectAnimator y = ObjectAnimator.ofFloat(child, "y", child.getY(),
childY);
ObjectAnimator y2 = ObjectAnimator.ofFloat(addView, "y",
addView.getY(), addY); AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(y, y2);
animatorSet.setDuration(during);
if (listener.length > 0)
animatorSet.addListener(listener[0]);
animatorSet.start();
}

后来为了兼容2.3.3我还专门下载了动画开源库NineOldAndroidsNineOldAndroids,这个库到底是干嘛的呢?在API3.0(Honeycomb),
SDK新增了一个android.animation包,里面的类是实现动画效果相关的类。通过Honeycomb API,可以实现非常复杂的动画效果,可是假设开发人员想在3.0下面使用这一套API, 则须要使用开源框架Nine Old Androids,在这个库中会依据我们执行的机器推断其SDK版本号,假设是API3.0以上则使用Android自带的动画类,否则就使用Nine Old Androids库中。这是一个兼容库。 (注:红色部分的字我是直接引用夏安明大神的博客原文,一直都在看他的博客。所以一直非常佩服他。他的博客的质量都非常不错。)之后兼容性的问题就算处理好了。但后来Xutils
4群的大炮告诉我,XRefreshView在下拉的时候会有抖动的情况,我知道了这个情况以后就開始找问题,后来发现是由于用属性动画来移动header的问题,不用属性动画就好了。细致想一想。属性动画事实上是通过反射来属性相应的get/set方法来运行的,毕竟是反射。而在手指移动的时候会触发大量的action_move。每一个action_move都会做一次反射,那么就会做大量的反射工作,大量的密集的反射就会导致性能方面有所减少,所以出现了抖动的情况。放弃反射以后,我用的是view.offsetTopAndBottom(deltaY)这种方法。看方法的凝视

    /**
     * Offset this view's vertical location by the specified number of pixels.
     *
     * @param offset the number of pixels to offset the view by
     */

翻译过来就是在竖直方向上以像素为单位来移动view。

没什么好说的。用起来非常easy,你值得拥有。

2.3demo用了流式布局

非常easy,感兴趣的能够看看

2.4点击button刷新和支持回弹

如今有支持点击button刷新,

	protected void onResume() {
super.onResume();
xRefreshView.startRefresh();
}

还有就是能够支持设置是否下拉刷新和上拉载入

// 设置能否够下拉刷新
refreshView.setPullRefreshEnable(false);
// 设置能否够上拉载入
refreshView.setPullLoadEnable(false);

大炮说假设能够在不能够下拉刷新和上拉载入的情况下也能够有回弹的效果就好了,于是如今的版本号就支持了。

三、实现相关

3.1前后变化

之前我是把headview,被刷新的childview和footview当成了三个部分来看待,而且分别记录了一開始的各个view的位置

/**
* 在開始上拉载入很多其它的时候,记录下childView一開始的Y轴坐标
*/
private float mChildY = -1;
/**
* 在開始上拉载入很多其它的时候,记录下FootView一開始的Y轴坐标
*/
private float mFootY = -1;
/**
* 在開始上拉载入很多其它的时候,记录下HeadView一開始的Y轴坐标
*/
private float mHeadY = -1;

然后在手指移动的时候不断更新当前各个view的y轴坐标。最后再来逐个移动各个view,这样做无意中就加大了工作量以及工作的复杂度,后来我想到了把三个部分当成一个总体。这样以来就简单非常多了。也就不再须要那么多的变量。

3.2实现过程

3.2.1測量

/*
* 丈量视图的宽、高。宽度为用户设置的宽度。高度则为header, content view, footer这三个子控件的高度之和。
*
* @see android.view.View#onMeasure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = MeasureSpec.getSize(widthMeasureSpec);
int childCount = getChildCount();
int finalHeight = 0;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
measureChild(child, widthMeasureSpec, heightMeasureSpec);
finalHeight += child.getMeasuredHeight();
}
setMeasuredDimension(width, finalHeight);
}

3.2.2布局

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
LogUtils.d("onLayout mHolder.mOffsetY=" + mHolder.mOffsetY);
mFootHeight = mFooterView.getMeasuredHeight();
int childCount = getChildCount();
int top = getPaddingTop() + mHolder.mOffsetY;
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
if (child == mHeaderView) {
// 通过把headerview向上移动一个headerview高度的距离来达到隐藏headerview的效果
child.layout(0, top - mHeaderViewHeight,
child.getMeasuredWidth(), top);
} else {
child.layout(0, top, child.getMeasuredWidth(),
child.getMeasuredHeight() + top);
top += child.getMeasuredHeight();
}
}
}

当中

int top = getPaddingTop() + mHolder.mOffsetY;

mHolder.mOffsetY是用来记录整个view在y轴方向上的偏移量的。这里之所以加上mHolder.mOffsetY。是由于在拖动刷新的过程中view的改变会引起系统又一次測量和布局,加上这个偏移量以后。能够在系统又一次布局的时候保住view当前的位置。不恢复到初始位置。

3.2.3 事件处理并移动view

	public boolean dispatchTouchEvent(MotionEvent ev) {
final int action = MotionEventCompat.getActionMasked(ev);
int deltaY = 0;
switch (action) {
case MotionEvent.ACTION_DOWN:
mHasSendCancelEvent = false;
mHasSendDownEvent = false;
mLastY = (int) ev.getRawY();
mInitialMotionY = mLastY; if (!mScroller.isFinished() && !mPullRefreshing && !mPullLoading) {
mScroller.forceFinished(true);
}
break;
case MotionEvent.ACTION_MOVE:
if (mPullLoading || mPullRefreshing || !isEnabled()) {
return super.dispatchTouchEvent(ev);
}
mLastMoveEvent = ev;
int currentY = (int) ev.getRawY();
deltaY = currentY - mLastY;
mLastY = currentY;
// intercept the MotionEvent only when user is not scrolling
if (!isIntercepted && Math.abs(deltaY) < mTouchSlop) {
isIntercepted = true;
return super.dispatchTouchEvent(ev);
}
LogUtils.d("isTop=" + mContentView.isTop() + ";isBottom="
+ mContentView.isBottom());
deltaY = (int) (deltaY / OFFSET_RADIO);
if (mContentView.isTop()
&& (deltaY > 0 || (deltaY < 0 && mHolder
.hasHeaderPullDown()))) {
sendCancelEvent();
updateHeaderHeight(currentY, deltaY);
} else if (mContentView.isBottom()
&& (deltaY < 0 || deltaY > 0 && mHolder.hasFooterPullUp())) {
sendCancelEvent();
updateFooterHeight(deltaY);
} else if (mContentView.isTop() && !mHolder.hasHeaderPullDown()
|| mContentView.isBottom() && !mHolder.hasFooterPullUp()) {
if (deltaY > 0)
sendDownEvent();
}
break;
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
// if (mHolder.mOffsetY != 0 && mRefreshViewListener != null
// && !mPullRefreshing && !mPullLoading) {
// mRefreshViewListener.onRelease(mHolder.mOffsetY);
// }
if (mContentView.isTop() && mHolder.hasHeaderPullDown()) {
// invoke refresh
if (mEnablePullRefresh && mHolder.mOffsetY > mHeaderViewHeight) {
mPullRefreshing = true;
mHeaderView.setState(XRefreshViewState.STATE_REFRESHING);
if (mRefreshViewListener != null) {
mRefreshViewListener.onRefresh();
}
}
resetHeaderHeight();
} else if (mContentView.isBottom() && mHolder.hasFooterPullUp()) {
if (mEnablePullLoad) {
int offset = 0 - mHolder.mOffsetY - mFootHeight;
startScroll(offset, SCROLL_DURATION);
startLoadMore();
} else {
int offset = 0 - mHolder.mOffsetY;
startScroll(offset, SCROLL_DURATION);
}
}
mLastY = -1; // reset
mInitialMotionY = 0;
isIntercepted = true;
break;
}
return super.dispatchTouchEvent(ev);
}

首先能够看到,所以的事件处理都在dispatchTouchEvent(MotionEvent ev)方法里进行,而之前则是分成两部分进行的。在onInterceptTouchEvent(MotionEvent ev)方法中进行拦截。事件处理则在onTouchEvent(MotionEvent ev)中进行。

这样做是由于大炮说他下拉刷新的时候,由于子view很复杂,子view有时候会抢占事件,造成卡住不刷新了。我们都知道子view是能够通过requestDisallowInterceptTouchEvent来请求父类不要拦截事件,那么onInterceptTouchEvent方法就不会运行。那我们下拉刷新也就不可靠了,所以为了解决问题,我把全部的处理都丢到dispatchTouchEvent方法中做。

再来看看sendCancelEvent()和sendDownEvent()这两个方法

private void sendCancelEvent() {
if (!mHasSendCancelEvent) {
setRefreshTime();
mHasSendCancelEvent = true;
mHasSendDownEvent = false;
MotionEvent last = mLastMoveEvent;
MotionEvent e = MotionEvent.obtain(
last.getDownTime(),
last.getEventTime()
+ ViewConfiguration.getLongPressTimeout(),
MotionEvent.ACTION_CANCEL, last.getX(), last.getY(),
last.getMetaState());
dispatchTouchEventSupper(e);
}
} private void sendDownEvent() {
if (!mHasSendDownEvent) {
LogUtils.d("sendDownEvent");
mHasSendCancelEvent = false;
mHasSendDownEvent = true;
isIntercepted = false;
final MotionEvent last = mLastMoveEvent;
if (last == null)
return;
MotionEvent e = MotionEvent.obtain(last.getDownTime(),
last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(),
last.getY(), last.getMetaState());
dispatchTouchEventSupper(e);
}
}

触摸事件一開始肯定会被子view接收到的。假设是listview的话,就会有item的点击效果出现,这非常正常,可是假设此时触发下拉刷新的话,同一时候又有item的点击效果,那么看起来就不是非常自然,全部此时能够通过sendCancelEvent()来给子view发送一个cancel事件。这样item的点击效果就会消失。还有当我们拉下headerview以后没有达到刷新条件,而且接着有往上推把headerview又全然隐藏了,此时就应该i把事件交还给子view。让子view接收到事件并移动,能够通过sendDownEvent来达到效果。

最后说下移动view的处理

当手指在拖动的时候,

public void moveView(int deltaY) {
mHolder.move(deltaY);
mChild.offsetTopAndBottom(deltaY);
mHeaderView.offsetTopAndBottom(deltaY);
mFooterView.offsetTopAndBottom(deltaY);
invalidate();
}
public int mOffsetY;

	public void move(int deltaY) {
mOffsetY += deltaY;
}

通过moveView方法来移动view。并把偏移量存了下来。

当手指离开以后,通过scroller来移动view

mScroller = new Scroller(getContext(), new LinearInterpolator());

这里用了线性的插值器,表示移动的时候是匀速变动的

/**
*
* @param offsetY
* 滑动偏移量,负数向上滑。正数反之
* @param duration
* 滑动持续时间
*/
public void startScroll(int offsetY, int duration) {
mScroller.startScroll(0, mHolder.mOffsetY, 0, offsetY, duration);
invalidate();
}
	public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
int lastScrollY = mHolder.mOffsetY;
int currentY = mScroller.getCurrY();
int offsetY = currentY - lastScrollY;
lastScrollY = currentY;
moveView(offsetY); LogUtils.d("currentY=" + currentY + ";mHolder.mOffsetY="
+ mHolder.mOffsetY);
} else {
LogUtils.d("scroll end mOffsetY=" + mHolder.mOffsetY);
}
}

从上面能够看出,整个移动过程中仅仅用到了一个mOffsetY变量来储存偏移量,代码相较于之前瞬间变得非常easy。

四、最后的说明

假设你对XRefreshView感兴趣。能够在github上关注XRefreshView

当然你也能够点此直接下载

打造android万能上拉下拉刷新框架——XRefreshView (二)的更多相关文章

  1. 打造Android万能上拉下拉刷新框架--XRefreshView(三)

    转载请注明出处:http://blog.csdn.net/footballclub/ 打造Android万能上拉下拉刷新框架–XRefreshView(一) 打造Android万能上拉下拉刷新框架–X ...

  2. 【PullToRefresh 系列基本用法】 Android装上拉下拉刷新控制具体的解释

    转载请注明:http://blog.csdn.net/duguang77/article/details/40921601 作者信息: Chris Banes大神详情:https://github.c ...

  3. 练习使用XRecyclerView,可上拉下拉刷新。

    package com.lixu.testxrecyclerview; import android.support.v7.app.AppCompatActivity; import android. ...

  4. iOS不得姐项目--推荐关注模块(一个控制器控制两个tableView),数据重复请求的问题,分页数据的加载,上拉下拉刷新(MJRefresh)

    一.推荐关注模块(一个控制器控制两个tableView) -- 数据的显示 刚开始加载数据值得注意的有以下几点 导航控制器会自动调整scrollView的contentInset,最好是取消系统的设置 ...

  5. 解决iscroll.js上拉下拉刷新手指划出屏幕页面无法回弹问题

    博客已迁移至http://zlwis.me. 使用过iscroll.js的上拉下拉刷新效果的朋友应该都碰到过这个问题:在iOS的浏览器中,上拉或下拉刷新时,当手指划出屏幕后,页面无法弹回.很多人因为解 ...

  6. swift实现UItableview上拉下拉刷新模块

    最近用写个项目 发现上拉下拉刷新模块没找到合适的 so 自己写了一个 由于最近忙 教程就不写了 里面有 直接贴地址https://github.com/DaChengTechnology/DCRefr ...

  7. ListView实现上拉下拉刷新加载功能

    第一步.首先在你项目中创建一个包存放支持下拉刷新和上拉加载的类:

  8. Android-PullToRefresh上拉下拉刷新加载更多,以及gridview刷新功能的Library下载地址

    作者:程序员小冰,CSDN博客:http://blog.csdn.net/qq_21376985,转载请说明出处. 首先大家应该都听说过此开源框架的强大之处,支持单列以及双列的 上拉加载以及下拉刷新功 ...

  9. iOS 上拉下拉刷新简单实现代码

    一般说到上拉刷新下拉刷新,很多人可能想到的是一个第三方开源框架EGORefresh,下面说下,如何自己写代码实现. UITableView本身是一个UIScrollView,所以UITableView ...

随机推荐

  1. dfs序题目练习

    参考博文:http://blog.csdn.net/qwe2434127/article/details/49819975 http://blog.csdn.net/qq_24489717/artic ...

  2. Linux命令之dig命令实例讲解

    1.查看域名的A记录 # dig yahoo.com; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.10.rc1.el6_3.2 <<> ...

  3. Linux 用户篇——用户管理命令之id、whoami、su、chage

    一.浅谈id.whoami.su.chage 本篇是续写上一篇<Linux 用户篇——用户管理命令之useradd.passwd.userdel.usermod>. (1)id命令 命令格 ...

  4. 关于wordpress插件WP SMTP的邮箱设置

     花了两天的时间把邮箱设置好了,把大概的步骤写下,放一下查到的资料. 1.去域名服务商那里添加MX记录      如下图的MX      2.测试主机是否禁用了mail()函数      参考链接wo ...

  5. js 表

    setInterval("body.innerHTML=new Date().toLocaleString()+' 星期'+'日一二三四五六'.charAt(new Date().getDa ...

  6. HTML5实战与剖析之原生拖拽(一拖拽历史概述)

    提起拖拽,我就想起了在JavaScript培训的时候一个非常好玩的效果,那就是拖拽了.可以用鼠标任意拖拽着一个物体到任何你想去的地方. 最早拥有JavaScript拖拽功能的是IE4浏览器.当时,网页 ...

  7. 微信用户授权,获取code

    1.进入微信公众平台 2.进入到   开发->接口授权,点击 网页服务->网页授权->网页授权获取用户基本信息   后面的“修改“. 3.点击网页授权域名的设置 4.设置授权回调域名 ...

  8. Anaconda 安装 OpenCV 遇到的问题

    1. 使用 pip install   安装 OpenCV 2. 对于 Ananconda 安装 OpenCV ,通常会遇到无法 import 的情况, 这是由于 anaconda 本身没有遵循 PE ...

  9. 哈尔滨理工大学第七届程序设计竞赛决赛(网络赛-高年级组)I - 没有名字

    题目描述 tabris实在是太菜了,没打败恶龙,在绿岛也只捡到一块生铁回去了,为了不在继续拉低acimo星球的平均水平逃离地球,来到了Sabi星球. 在这里tabris发现了一种神奇的生物,这种生物不 ...

  10. 洛谷P4768 [NOI2018]归程 [可持久化并查集,Dijkstra]

    题目传送门 归程 格式难调,题面就不放了. 分析: 之前同步赛的时候反正就一脸懵逼,然后场场暴力大战,现在呢,还是不会$Kruskal$重构树,于是就拿可持久化并查集做. 但是之前做可持久化并查集的时 ...