您的位置:首页 > 移动开发 > Android开发

Android中View的事件体系(3)——自定义横向滚动viewGroup

2016-06-30 18:00 417 查看
通过前几个知识,可以自定义一个横向滚动的viewGroup这个可以横向类似于viewpage+fragment而fragment中包含listView的效果,如果不做处理会有横向和纵向的滚动冲突,现在处理后可以实现既可以横向滚动又可以走纵向滚动,这里比较简单,只支持每个子view都是同样的宽高的情况,而且不支持子view的padding和margin。

具体的代码如下,里面有详细说明:

@SuppressLint("ClickableViewAccessibility")
public class HorizontalScrollViewEx extends ViewGroup {
private static final String TAG = "HorizontalScrollViewEx";
/** 全部子元素的个数 */
private int mChildrenSize;
/** 每个子元素的宽度 */
private int mChildWidth;
/** 当前子元素序号 */
private int mChildIndex;
/** 最后一次触摸的X坐标 */
private int mLastX = 0;
/** 最右一次触摸的Y坐标 */
private int mLastY = 0;
/** 记录上次滑动的坐标 */
private int mLastXIntercept = 0;
/** 记录上次滑动的坐标 */
private int mLastYIntercept = 0;
/** 用来滑动到对应的位置 */
private Scroller mScroller;
/** 速度监听类 */
private VelocityTracker mVelocityTracker;
public HorizontalScrollViewEx(Context context) {
super(context);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public HorizontalScrollViewEx(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
if (mScroller == null) {
mScroller = new Scroller(getContext());
mVelocityTracker = VelocityTracker.obtain();
}
}
/** 用阿里判断是否拦截当前事件 */
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
// 是否拦截的标识
boolean intercepted = false;
// 获取点击的X值
int x = (int) ev.getX();
// 获取点击的Y值
int y = (int) ev.getY();
// 分情况拦截
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN: {
// 点击时,都不拦截点击事件
intercepted = false;
if (!mScroller.isFinished()) {
// 如果是快速滑动,则拦截点击事件,使滑动停止?
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
// 得到水平和竖直方向的移动距离
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
// 如果是水平滑动
if (Math.abs(deltaX) > Math.abs(deltaY)) {
// 拦截当前事件
intercepted = true;
} else {
// 不拦截当前事件
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
// 抬起事件不做拦截
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG, "intercepted = " + intercepted);
// 重置最后触摸位置的坐标
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
/** 确认拦截后执行的方法 */
@Override
public boolean onTouchEvent(MotionEvent event) {
mVelocityTracker.addMovement(event);
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
}
break;
}
case MotionEvent.ACTION_MOVE: {
// 获取水平方向的移动距离
int deltaX = x - mLastX;
// 因为是水平移动,所以这个变量并没有使用
int deltaY = y - mLastY;
// 移动到对应的地方
scrollBy(-deltaX, 0);
break;
}
case MotionEvent.ACTION_UP: {
// 获取当前滚动参数
int scrollX = getScrollX();
// 设置滚动时间
mVelocityTracker.computeCurrentVelocity(1000);
// 当水平距离大于某个值时候,移动到下一页,否则停留在当前页
float xVelocity = mVelocityTracker.getXVelocity();
if (Math.abs(xVelocity) >= 50) {
// 更新当前屏幕的页数
mChildIndex = xVelocity > 0 ? mChildIndex - 1 : mChildIndex + 1;
} else {
// 更新单前显示的页数
mChildIndex = (scrollX + mChildWidth / 2) / mChildWidth;
}
// 如果是最后一页,则显示最后一页
mChildIndex = Math.max(0, Math.min(mChildIndex, mChildrenSize - 1));
// 计算要滚动的距离
int dx = mChildIndex * mChildWidth - scrollX;
// 滚动到指定的位置
smoothScrollBy(dx, 0);
mVelocityTracker.clear();
break;
}
default:
break;
}
// 更新最后触摸点的坐标
mLastX = x;
mLastY = y;
return true;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int measuredWidth = 0;
int measuredHeight = 0;
// 获得子元素个数
final int childCount = getChildCount();
// 排列子元素
measureChildren(widthMeasureSpec, heightMeasureSpec);
// 获取子元素的尺寸和模式
int widthSpaceSize = MeasureSpec.getSize(widthMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpaceSize = MeasureSpec.getSize(heightMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
// 如果子元素为零
if (childCount == 0) {
// 把自己的宽高设置为0
setMeasuredDimension(0, 0);
} else if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
// 如果宽和高采用了wrap_content
final View childView = getChildAt(0);
// 宽度等于所有子元素的和
measuredWidth = childView.getMeasuredWidth() * childCount;
// 高度等于子元素的高
measuredHeight = childView.getMeasuredHeight();
// 设置自己的宽高
setMeasuredDimension(measuredWidth, measuredHeight);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
// 如果高采用了wrap_content
final View childView = getChildAt(0);
// 高就是第一个子元素的高
measuredHeight = childView.getMeasuredHeight();
// 设置自己的宽高
setMeasuredDimension(widthSpaceSize, childView.getMeasuredHeight());
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
// 如果宽的模式是wrap_content
final View childView = getChildAt(0);
// 用第一个元素的宽度乘以子元素个数,得出自己的宽度
measuredWidth = childView.getMeasuredWidth() * childCount;
// 设置自己的宽高
setMeasuredDimension(measuredWidth, heightSpaceSize);
}
// 此处没有处理HorizontalScrollViewEx的padding,以及子元素的Margin,如果要处理时候可以遍历全部来执行
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// 每个子元素左边的距离
int childLeft = 0;
// 获取所有的子元素个数
final int childCount = getChildCount();
// 将子元素个数赋值到成员变量
mChildrenSize = childCount;
// 循环排列每个子元素
for (int i = 0; i < childCount; i++) {
// 获取每个子元素
final View childView = getChildAt(i);
// 判断是否处于隐藏状态
if (childView.getVisibility() != View.GONE) {
// 获取子元素的宽度
final int childWidth = childView.getMeasuredWidth();
// 把子元素宽度赋值到成员变量
mChildWidth = childWidth;
// 放置子元素到对应的位置(子元素左上左边,子元素右下坐标)
childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight());
// 子元素距离右边的距离累加
childLeft += childWidth;
}
}
}
/** 滚动到对应的位置 */
private void smoothScrollBy(int dx, int i) {
// 改变坐标
mScroller.startScroll(getScrollX(), 0, dx, 0, 500);
// 重绘
invalidate();
}
/** 在父类方法中这个是一个空的实现 */
@Override
public void computeScroll() {
// 重绘过程中会调用如下方法
if (mScroller.computeScrollOffset()) {// if中的方法汇根据时间流逝来计算当前的scrollX和scrollY方法
// 这个方法返回true时候表示滑动还没有结束
// 然后移动到对饮的位置
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
// 重绘
postInvalidate();
}
}
@Override
protected void onDetachedFromWindow() {
mVelocityTracker.recycle();
super.onDetachedFromWindow();
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: