大家假设喜欢我的博客,请关注一下我的微博,请点击这里(http://weibo.com/kifile),谢谢

转载请标明出处,再次感谢

#######################################################################

自己定义 ViewGroup 支持无限循环翻页系列

自己定义 ViewGroup 支持无限循环翻页之中的一个(重写 onLayout以及 dispatchDraw)

自己定义 ViewGroup 支持无限循环翻页之二(处理触摸事件)

自己定义 ViewGroup 支持无限循环翻页之三(响应回调事件)

#######################################################################

在上一篇博文中,我曾提到过,我是用了一个 TouchHandler 来处理 view 的 touch 事件,再在 TouchHandler 内通过回调接口通知 view 处理相应的回调事件.

如今我们就来具体分析一下,怎样通过处理回调事件,实现界面的滑动与切换

首先,我们先了解一下在 Android 的View 类中是怎样进行界面位置的

在 View 类中,有两个方法 scrollBy,scrollTo, 来控制当前屏幕的显示区域,同一时候内部有两个变量 mScrollX,mScrollY 来记录当前的位移量

对于 scrollBy 方法,他事实上是通过之前记录的 mScrollX,mScrollY 来计算出应该位移到的坐标点,然后调用 scrollTo 方法进行移动,这种方法的作用也正如其名,就是用来将整个视图进行偏移的.

此外 Android 还为我们提供了一个 Scroller 类帮助我们进行平滑的移动, Scroller 类主要通过一个插值器计算经过某段时间后,当前界面的坐标位置,然后 Android 系统通过在调用 invalidate方法时所调用到得 computeScroll 方法中,对界面位置进行又一次获取,从而让移动看起来连续.

Ok,如今我们正式開始响应回调事件,我们先查看一下起始界面,

如图所看到的,在默认情况下,我们一共同拥有三组全然一致的图片,可能会被显示到屏幕上,以实现我们连续滑动的效果.

当我们手指按下,開始移动的时候,我们也希望界面可以尾随我们的手指进行移动,比如当我们手指向左滑动的时候,我们希望看到的界面是这样子的

因此,我们希望屏幕的显示区域进行一个位移,来达到这样的效果.

我们在 TouchHandler 中的 handleTouchEvent 方法中,有过对手指移动的事件进行处理,假设手指移动区域超过某个限值后,我们会获取每次 touch 事件的差值,调用 onScrollBy 的回调方法,所以我们仅仅须要在 SerialScreenLayout 中继承这个接口,并实现方法就可以.

实现方案例如以下:

@Override
public void onScrollBy(int dx, int dy) {
scrollBy(dx, 0);
}

依据接口方法的传入值进行界面偏移就可以,注意因为我们仅仅须要在 x 轴上做变换,因此 y 轴的偏移我们能够令他恒为0.

这样一来,当我们的手指在屏幕上进行滑动的时候,界面就会尾随我们手指的移动而移动了.

然后当我们的手指释放的时候,我们事实上并不希望界面停留在之前的位置,而是希望他依据之前我们手指的位移偏量或者滑动速度,切换到一个独立的 View 界面去,例如以下:

所以我们在 TouchHandler 类中,当手指停止触摸,触发 ACTION_UP 的时候,我们向 View, 进行一次 onRelease 回调,然后在方法中对当前位置和速度进行判别,以确定应该跳转的屏幕位置,然后再进行一次屏幕平滑移动,具体代码例如以下:

@Override
public void onRelease(int velocityX, int velocityY, boolean cancel) {
cleanPosition();
int targetPosition = mCurrPosition;
if (mDirty) {
mDirty = false;
if (Math.abs(mLastPosition - getScrollX()) > mGutterSize && Math.abs(velocityX) > mMinimumVelocity) {
if (mTmpDirectionLeft) {
if (velocityX > 0) {
targetPosition -= 1;
} else {
if (getScrollX() > mCurrPosition * mWidth) {
targetPosition += 1;
}
}
} else {
if (velocityX > 0) {
if (getScrollX() > mCurrPosition * mWidth) {
targetPosition -= 1;
}
} else {
targetPosition += 1;
}
}
}
scrollToPosition(targetPosition, 0);
} else if (!cancel) {
int startX = mWidth * mCurrPosition;
int deltaX = getScrollX() - startX;
if (deltaX > 0) {
if (velocityX <= 0) {
if (Math.abs(deltaX) > mGutterSize) {
targetPosition = mCurrPosition + 1;
} else if (Math.abs(velocityX) > mMinimumVelocity) {
targetPosition = mCurrPosition + 1;
}
}
} else {
if (velocityX >= 0) {
if (Math.abs(deltaX) > mGutterSize) {
targetPosition = mCurrPosition - 1;
} else if (Math.abs(velocityX) > mMinimumVelocity) {
targetPosition = mCurrPosition - 1;
}
}
}
scrollToPosition(targetPosition, velocityX);
}else{
scrollToPosition(targetPosition, velocityX);
}
}

上面代码中的 mDirty 将会在之后的部分被讨论,我们先来看看其它的部分, cancel 仅当 touch 事件为 ACTION_CANCEL 时为 true,说明整个滑动过程被取消,所以移动到之前的位置,否则就依据当前的速度以及移动偏移进行推断,然后选择下一个位置进行平滑位移.

平滑位移的代码例如以下:

private void scrollToPosition(int targetPosition, int velocityX) {
cleanPosition();
targetPosition = formatPosition(targetPosition);
if (mCurrPosition == 0 && targetPosition == getChildCount() - 1) {
targetPosition = -1;
} else if (mCurrPosition == getChildCount() - 1 && targetPosition == 0) {
targetPosition = getChildCount();
}
int startX = getScrollX();
int finalX = targetPosition * mWidth;
final int delta = Math.abs(finalX - startX);
velocityX = Math.abs(velocityX); int duration = (int) (1.0f * DEFAULT_DURATION * delta / mWidth);
if (velocityX > mMinimumVelocity) {
final int width = mWidth;
final int halfWidth = width / 2;
final float distanceRatio = Math.min(1f, 1.0f * delta / width);
final float distance = halfWidth + halfWidth *
distanceInfluenceForSnapDuration(distanceRatio);
int velocityDuration = 4 * Math.round(1000 * Math.abs(distance / velocityX));
duration = Math.min(duration, velocityDuration);
}
mScroller.startScroll(startX, 0, finalX - startX, 0, duration);
invalidate();
mCurrPosition = targetPosition;
}

在这里,我们依据滑动速度计算出一个移动耗时,然后启动 mScroller, 进行滑动位置计算,而后调用 invalidate 进行位移

这样一来,当我们松开手指,界面就会平滑的移动到相应的 View 去.

当时这样一来,一个新的问题就冒出来了,假如如今我们的位置是这种:

那么我们移动的时候我们手指向右滑动,左側仍然会出现空白的 View 而非最后一张,这种话,事实上我们就仅仅是攻克了中间部分的连续,可是边缘的连续还是会出现故障,为了解决问题,我们建了一个 cleanPosition()方法,用来计算当前的位置,代码例如以下:

  private void cleanPosition() {
final int position = mCurrPosition;
final int sx = position * mWidth;
final int cx = getScrollX();
int delta = cx - sx;
if (Math.abs(delta) >= mWidth) {
//scrolled to a new page
if (delta > 0) {
mCurrPosition = cx / mWidth;
} else {
mCurrPosition = cx / mWidth - 1;
}
}
final int tmp = formatPosition(mCurrPosition);
if (tmp != mCurrPosition) {
scrollBy((tmp - mCurrPosition) * mWidth, 0);
mCurrPosition = tmp;
}
} private int formatPosition(int position) {
if (position < 0) {
do {
position += getChildCount();
} while (position < 0);
} else if (position >= getChildCount()) {
do {
position -= getChildCount();
} while (position >= getChildCount());
}
return position;
}

当我们的移动范围超过 View 的真实显示范围之后,我们会通过计算,将显示范围平移回正常的界面,这样,我们终于的显示范围始终都在主要的三个 View 内部,这样就攻克了可能出现的边缘不连续问题.

当时,在实际滑动过程中,我们滑动的时候,假设双指交替滑动,那么我们就能够强行将 view 滑动至边缘而出现不连续问题,所以我们针对多点触控的回调方法中,再调用了 cleanPosition, 来保持 view 始终处于正确位置.

实际滑动过程中,还可能出现某次滑动未结束,我们手指就已经開始了下一次的滑动,那么,之前的 onRelease 推断就会出现逻辑问题,由于之前我们是依据滑动速度和偏移量进行推断的下一个位置,可是如今滑动还未结束,起始的偏移量和位置都与之前不同,因此我们在 onTouchEvent 中,做了例如以下实现:

 @Override
public void onTouch(MotionEvent event) {
if (!mScroller.isFinished()) {
if (Math.abs(mScroller.getCurrX() - mScroller.getFinalX()) > mGutterSize) {
mDirty = true;
cleanPosition();
mLastPosition = getScrollX();
if (mScroller.getFinalX() > mScroller.getCurrX()) {
mTmpDirectionLeft = true;
} else {
mTmpDirectionLeft = false;
}
mScroller.abortAnimation();
}
} else {
mDirty = false;
}
}

在这里,我们记录下当前滑动的一些相关属性,并令 mDirty 变量为 true,使得自身可以推断是否是在滑动过程中突然中断,然后出发事件,然后再在 onRelease 中依据当前的相关属性进行推断,选择下一位置.

写到这里,整个 SerialScreenLayout 的实现逻辑已经介绍完了,其主要原理就是:

在 onLayout 中依据每一个 view 的顺序让他们单独享用一个屏幕的固定空间,再在 dispatchDraw 的时候,在左右两側分别多绘制一组图像,使得显示连续,最后处理 touch 事件,使得整个自己定义 ViewGroup 可以移动,并在停止触摸后可以移动到某段屏幕位置

自己定义 ViewGroup 支持无限循环翻页之三(响应回调事件)的更多相关文章

  1. [置顶] ios 无限循环翻页源码例子

    原创文章,转载请注明出处:http://blog.csdn.net/donny_zhang/article/details/9923053 demo功能:ios 无限循环翻页源码例子.iphone 6 ...

  2. HTML多图无缝循环翻页效果

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  3. 扩展ViewFlow避免和ViewPager滑动冲突,同时支持无限循环,并完美和CircleFlowIndicator结合

    首先,为了避免滑动冲突,我们要继承ViewFlow,重写onInterceptTouchEvent public class MyViewFlow extends ViewFlow { private ...

  4. MVC+JSON 无限滚动翻页

    public partial class News { public int ID{ get; set; } public int Title{ get; set; } } ) { Response. ...

  5. jQuery垂直缩略图相册插件 支持鼠标滑动翻页

    在线演示 本地下载

  6. HTML5 book响应式翻页效果

    翻页,HTML5源码下载,HTML5响应式翻页效果,鼠标移到右上角会看到翻页效果,需要鼠标拖动后翻页,支持ie9+,html5浏览器. 单页和双页. 自动播放和暂停. 点击左右翻页. 鼠标点击左右页面 ...

  7. Android无限循环轮播广告位Banner

     Android无限循环轮播广告位Banner 现在一些app通常会在头部放一个广告位,底部放置一行小圆圈指示器,指示广告位当前的页码,轮播展示一些图片,这些图片来自于网络.这个广告位banner ...

  8. Yii: 设置数据翻页

    一种方法是使用CPagination处理翻页需要的数据如:总数据项数,每页数据项数,当前页,然后在视图中使用CBasePager来绘制. 控制器动作的代码示范: function actionInde ...

  9. 自己定义ViewGroup控件(二)-----&gt;流式布局进阶(二)

    main.xml <?xml version="1.0" encoding="utf-8"? > <com.example.SimpleLay ...

随机推荐

  1. 1.0.2-学习Opencv与MFC混合编程之---为播放AVI视频添加滑动条

    源代码地址:http://download.csdn.net/detail/nuptboyzhb/3961642 版本1.0.2新增内容 Ø  全局变量和函数的添加: 在CVMFCview.cpp文件 ...

  2. poj 3778

    这就是个超级水题……!!!!写一写来纪念一下自己的错误…… 如果某个学生的的成绩是其他俩个或三个学生成绩的和则给予奖励 直接暴力,所以一开始直接用数组标记两个人或三个人的和,但是忽略了这种情况 20( ...

  3. 基于android的实时音频频谱仪

    前一段实习,本来打算做c++,到了公司发现没啥项目,于是乎转行做了android,写的第一个程序竟然要我处理信号,咱可是一心搞计算机的,没接触过信号的东西,什么都没接触过,于是乎, 找各种朋友,各种熟 ...

  4. 已成功与服务器建立连接,但是在登录过程中发生错误。 (provider: SSL Provider, error: 0 - 接收到的消息异常,或格式不正确。)

    之前做好的asp.net部署后,发现 访问数据库时: 异常:已捕获: "已成功与服务器建立连接,但是在登录过程中发生错误. (provider: SSL Provider, error: 0 ...

  5. JavaScript快速入门(二)——JavaScript变量

    变量声明 JavaScript的变量声明分为显式声明跟隐式声明. 显式声明 即带var关键字声明,例如 var example = example; 要注意JavaScript里面声明的关键字只有fu ...

  6. Perl入门(四)Perl的正則表達式

    正則表達式是Perl语言的特色.主要的语法不是非常难,可是编写一个符合需求.高效的正則表達式.还是有一些挑战的. Perl的三种匹配模式 1.查找 语法:m/正則表達式内容/; 作用:查找匹配内容中是 ...

  7. Aerospike | Aerospike Chinese

    Aerospike | Aerospike Chinese 如果您的企业依赖于: 庞大的数据量(超过任何结构化数据库所能处理的数据量) 可预见(且快速)的性能 透明的扩展 始终正常运行 那么您只有一个 ...

  8. 配置虚拟主机并更改Apache默认解析路径

    配置虚拟主机,非常easy 改动以下文件: 加入以下几句话 <VirtualHost *:80> ##ServerAdmin webmaster@dummy-host2.example.c ...

  9. Swift - 使用表格组件(UITableView)实现单列表

    1,样例说明: (1)列表内容从Controls.plist文件中读取,类型为Array. (2)点击列表项会弹出消息框显示该项信息. (3)按住列表项向左滑动,会出现删除按钮.点击删除即可删除该项. ...

  10. UVA 534 - Frogger(kruskal扩展)

    UVA 534 - Frogger 题目链接 题意:给定一些点.如今要求一条路径从第一个点能跳到第二个点,而且这个路径上的最大距离是最小的 思路:利用kruskal算法,每次加最小权值的边进去,推断一 ...