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(); } }
相关文章推荐
- 利用 LeakCanary 来检查 Android 内存泄漏
- android 使用Canvas画箭头
- RxBus使用
- Android MVP模式的详情以及和MVC模式的区别
- Android 应用监听后台切换前台的方法
- android 常见分辨率(mdpi、hdpi 、xhdpi、xxhdpi )及屏幕适配注意事项
- Android进阶(二十二)设置TextView文字水平垂直居中
- Android进阶(二十二)设置TextView文字水平垂直居中
- Androidstudio中gradle配置和使用
- Android JNI简介
- Android 机顶盒开发过程中Listview指定item获取焦点
- Android6.0权限问题,并撰写常用权限工具类
- Android 在机顶盒开发过程当中如何禁止listview的item项获得焦点,而让item的子控件获得焦点
- Android USB Host开发笔记
- android-root
- AndroidStudio设置代码注释模块
- Ubuntu15.04安装AndroidStudio时出现“unable to run mksdcard sdk tool”解决
- In android studio,cannot load 2 facets-unknown facet type:android and android-gradle
- Android之特效五种Toast详解
- android:versionCode和android:versionName 用途