大家假设喜欢我的博客,请关注一下我的微博,请点击这里(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. JavaScript编程:使用DOM操作样式表

    6.使用DOM操作样式表: 操纵元素的Style样式属性:         background-color:style.backgroundColor         color:style.col ...

  2. shakes hands

    Description On February, 30th n students came in the Center for Training Olympiad Programmers (CTOP) ...

  3. Windows server 2008 R2实现多用户远程连接

    原文 Windows server 2008 R2实现多用户远程连接 经常使用远程桌面的朋友可能会注意到,Windows server 2008 R2中,远程桌面最多只允许两个人远程连接,第三个人就无 ...

  4. jquery 动态添加和删除 ul li列表

    今天需要实现一个jquery动态添加和删除  ul li列表中的li行,自己简单的实现乐一个,分享一下 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML ...

  5. SqlHelper初探之二

    在上一篇简单的介绍了sqlhelper的基本知识,接下来就让我们进一步学习他的实践过程. 首先:我们要明白的一件事Sqlhelper不是写出来的,而是在D层的代码中提炼出来的?那么就会反问一句“D层中 ...

  6. [LeetCode] Unique Paths 2

    Follow up for "Unique Paths": Now consider if some obstacles are added to the grids. How m ...

  7. php可获取客户端信息

    <?php echo "<br>".$_SERVER['PHP_SELF'];#当前正在执行脚本的文件名,与 document root相关 echo " ...

  8. Test oracle db iops

    Today, i need to test one database's iops and do something for oracle db's io test. How to test the ...

  9. HDU 4981 Goffi and Median(水)

    HDU 4981 Goffi and Median 思路:排序就能够得到中间数.然后总和和中间数*n比較一下就可以 代码: #include <cstdio> #include <c ...

  10. 在VC++中启用内存泄露检测

    检测内存泄漏的主要工具是调试器和 CRT 调试堆函数.若要启用调试堆函数,请在程序中包括以下语句: #define CRTDBG_MAP_ALLOC#include <stdlib.h># ...