效果图:
这个自定义控件涉及到的知识点:
自定义ViewGroup中onMeasure和onLayout的写法
弹性滚动Scroller的用法
速度轨迹追踪器VelocityTracker的用法
如何处理滑动事件冲突
dispatchTouchEvent:(外部拦截)告诉此ScrollLayout的父布局,什么时候该拦截触摸事件,什么时候不该拦截触摸事件
onInterceptTouchEvent:(内部拦截)ScrollLayout告诉自己什么时候要拦截内部子View的触摸事件,什么时候不要拦截内部子View的触摸事件
处理触摸滑动的思路:
- 先实现布局跟着手指的滑动而滑动 scrollBy
- 处理好边界条件(这次的处理边界,仅适用于低速滑动情况下)
- 如果是快速滑动VelocityTracker,必须再次考虑边界问题(上面考虑的边界问题不适用于快速滑动情况)
- 如果是低速滑动,要根据手指滑动方向和布局滑动的距离一起做判断,来确定,页面该滑动到那个页面,这里用到了弹性滑动Scroller
- 难点来了:算法,
- 即确定当前显示的子控件的位置,
- 确定弹性滑动滑向那个位置
if (Math.abs(velocityY) > criticalVelocityY) {//当手指滑动速度快时,按照速度方向直接翻页
// 重点二、快速滑动时,如何判断当前显示的是第几个控件,并且再次包含边界判断(必须包含边界判断,因为前面的边界判断,只适用于低速滑动时)
if (shouZhiXiangXiaHuaDong) {
if (currentPage > 1) {//★★★★★★★★边界限制,防止滑倒第一个,还继续滑动,注意★(currentPage-2)
mScroller.startScroll(0, getScrollY(), 0, childHeight * (currentPage - 2) - getScrollY());
currentPage--;
}
} else {
if (currentPage < childCount) {//★★★★★★★边界限制,防止滑倒最后一个,还继续滑动,注意★currentPage
mScroller.startScroll(0, getScrollY(), 0, childHeight * currentPage - getScrollY());
currentPage++;
}
}
Log.e("eee", currentPage + "");
总结
当要写一个算法时,不要着急试图一下子写出来,这样往往陷入盲目,应该是一步一步地推导,一步一步实现代码,指导最后找到规律,类似于归纳总结、通项公式的方法。
代码如下:(注释很全)
package beautlful.time.com.beautlfultime.view;
import android.content.Context;
import android.support.v4.view.ViewConfigurationCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.Scroller;
/**
* 注意:此自定义viewgroup只适用于每个子控件为match_parent的情况,其实一般情况也都是这种情况
* 注意:此自定义viewgroup,没有考虑padding的情况,使用者不要在ScrollerLayout里使用任何padding,否则你看到的不是你想要的,
* 为了实现padding效果,你可以为ScrollerLayout的外层再套一层线性布局(或其他布局),在外层布局里使用padding值
* 此自定义viewgroup基于郭霖博客改编,想了解具体实现细节,请参照:
* Android Scroller完全解析,关于Scroller你所需知道的一切
* http://blog.youkuaiyun.com/guolin_blog/article/details/48719871
*/
public class VerticalViewPager extends ViewGroup {
int currentPage = 1;
/**
* 速度轨迹追踪器
*/
private VelocityTracker mVelocityTracker;
/**
* 此次计算速度你想要的最大值
*/
private final int mMaxVelocity;
/**
* 第一个触点的id, 此时可能有多个触点,但至少一个
*/
private int mPointerId;
/**
* 计算出的竖向滚动速率
*/
private float velocityY;
/**
* 手指横向滑动的速率临界值,大于这个值时,不考虑手指滑动的距离,直接滚动到最左边或者最右边
*/
private int criticalVelocityY = 2500;
/**
* 用于完成滚动操作的实例
*/
private Scroller mScroller;
/**
* 判定为拖动的最小移动像素数
*/
private int mTouchSlop;
/**
* 手机按下时的屏幕坐标
*/
private float mYDown;
/**
* 手机当时所处的屏幕坐标
*/
private float mYMove;
/**
* 上次触发ACTION_MOVE事件时的屏幕坐标
*/
private float mYLastMove;
/**
* 界面可滚动的顶部边界
*/
private int topBorder;
/**
* 界面可滚动的底部边界
*/