ViewGroup的手势和绘制机制(个人看法)

    学习最好的办法就是从实际出发,所以我选择了ScrollLayout(左右滑动切换屏幕控件)来作为例子,讲述一下我对ViewGroup的一些机制的个人理解。

首先先贴一下ScrollLayout的代码:

1package cn.edu.scau.hci.widget; 2 3import android.content.Context; 4import android.util.AttributeSet; 5import android.view.MotionEvent; 6import android.view.VelocityTracker; 7import android.view.View; 8import android.view.ViewConfiguration; 9import android.view.ViewGroup; 10import android.widget.Scroller; 11 12/** 13 * 左右滑动切换屏幕控件 14 * 15 * @author Yao.GUET date: 2011-05-04 16 * @modify liux (http://my.oschina.net/liux) 17 */ 18public class ScrollLayout extends ViewGroup { 19 private Scroller mScroller; 20 private VelocityTracker mVelocityTracker; 21 private int mCurScreen; 22 private int mDefaultScreen = 0; 23 private static final int TOUCH_STATE_REST = 0; 24 private static final int TOUCH_STATE_SCROLLING = 1; 25 private static final int SNAP_VELOCITY = 600; 26 private int mTouchState = TOUCH_STATE_REST; 27 private int mTouchSlop; 28 private float mLastMotionX; 29 private float mLastMotionY; 30 private OnViewChangeListener mOnViewChangeListener; 31 32 /** 33 * 设置是否可左右滑动 34 * 35 * @author liux 36 */ 37 private boolean isScroll = true; 38 39 public void setIsScroll(boolean b) { 40 this.isScroll = b; 41 } 42 43 public ScrollLayout(Context context, AttributeSet attrs) { 44 this(context, attrs, 0); 45 } 46 47 public ScrollLayout(Context context, AttributeSet attrs, int defStyle) { 48 super(context, attrs, defStyle); 49 mScroller = new Scroller(context); 50 mCurScreen = mDefaultScreen; 51 mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); 52 } 53 54 @Override 55 protected void onLayout(boolean changed, int l, int t, int r, int b) { 56 int childLeft = 0; 57 final int childCount = getChildCount(); 58 for (int i = 0; i < childCount; i++) { 59 final View childView = getChildAt(i); 60 if (childView.getVisibility() != View.GONE) { 61 final int childWidth = childView.getMeasuredWidth(); 62 childView.layout(childLeft, 0, childLeft + childWidth, childView.getMeasuredHeight()); 63 childLeft += childWidth; 64 } 65 } 66 } 67 68 @Override 69 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 70 // Log.e(TAG, "onMeasure"); 71 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 72 final int width = MeasureSpec.getSize(widthMeasureSpec); 73 final int widthMode = MeasureSpec.getMode(widthMeasureSpec); 74 if (widthMode != MeasureSpec.EXACTLY) { 75 throw new IllegalStateException("ScrollLayout only canmCurScreen run at EXACTLY mode!"); 76 } 77 final int heightMode = MeasureSpec.getMode(heightMeasureSpec); 78 if (heightMode != MeasureSpec.EXACTLY) { 79 throw new IllegalStateException("ScrollLayout only can run at EXACTLY mode!"); 80 } 81 82 // The children are given the same width and height as the scrollLayout 83 final int count = getChildCount(); 84 for (int i = 0; i < count; i++) { 85 getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec); 86 } 87 // Log.e(TAG, "moving to screen "+mCurScreen); 88 scrollTo(mCurScreen * width, 0); 89 } 90 91 /** 92 * According to the position of current layout scroll to the destination page. 93 */ 94 public void snapToDestination() { 95 final int screenWidth = getWidth(); 96 final int destScreen = (getScrollX() + screenWidth / 2) / screenWidth; 97 snapToScreen(destScreen); 98 } 99 100 public void snapToScreen(int whichScreen) { 101 // 是否可滑动 102 if (!isScroll) { 103 this.setToScreen(whichScreen); 104 return; 105 } 106 107 scrollToScreen(whichScreen); 108 } 109 110 public void scrollToScreen(int whichScreen) { 111 // get the valid layout page 112 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 113 if (getScrollX() != (whichScreen * getWidth())) { 114 final int delta = whichScreen * getWidth() - getScrollX(); 115 mScroller.startScroll(getScrollX(), 0, delta, 0, Math.abs(delta) * 1);// 持续滚动时间 以毫秒为单位 116 mCurScreen = whichScreen; 117 118 invalidate(); // Redraw the layout 119 120 if (mOnViewChangeListener != null) { 121 mOnViewChangeListener.OnViewChange(mCurScreen); 122 } 123 } 124 125 } 126 127 public void setToScreen(int whichScreen) { 128 whichScreen = Math.max(0, Math.min(whichScreen, getChildCount() - 1)); 129 mCurScreen = whichScreen; 130 scrollTo(whichScreen * getWidth(), 0); 131 132 if (mOnViewChangeListener != null) { 133 mOnViewChangeListener.OnViewChange(mCurScreen); 134 } 135 } 136 137 public int getCurScreen() { 138 return mCurScreen; 139 } 140 141 @Override 142 public void computeScroll() { 143 if (mScroller.computeScrollOffset()) { 144 scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 145 postInvalidate(); 146 } 147 } 148 149 @Override 150 public boolean onTouchEvent(MotionEvent event) { 151 // 是否可滑动 152 if (!isScroll) { 153 return false; 154 } 155 156 if (mVelocityTracker == null) { 157 mVelocityTracker = VelocityTracker.obtain(); 158 } 159 mVelocityTracker.addMovement(event); 160 final int action = event.getAction(); 161 final float x = event.getX(); 162 final float y = event.getY(); 163 switch (action) { 164 case MotionEvent.ACTION_DOWN: 165 // Log.e(TAG, "event down!"); 166 if (!mScroller.isFinished()) { 167 mScroller.abortAnimation(); 168 } 169 mLastMotionX = x; 170 171 // ---------------New Code---------------------- 172 mLastMotionY = y; 173 // --------------------------------------------- 174 175 break; 176 case MotionEvent.ACTION_MOVE: 177 int deltaX = (int) (mLastMotionX - x); 178 179 // ---------------New Code---------------------- 180 int deltaY = (int) (mLastMotionY - y); 181 if (Math.abs(deltaX) < 200 && Math.abs(deltaY) > 10) break; 182 mLastMotionY = y; 183 // ------------------------------------- 184 185 mLastMotionX = x; 186 scrollBy(deltaX, 0); 187 break; 188 case MotionEvent.ACTION_UP: 189 // Log.e(TAG, "event : up"); 190 // if (mTouchState == TOUCH_STATE_SCROLLING) { 191 final VelocityTracker velocityTracker = mVelocityTracker; 192 velocityTracker.computeCurrentVelocity(1000); 193 int velocityX = (int) velocityTracker.getXVelocity(); 194 // Log.e(TAG, "velocityX:" + velocityX); 195 if (velocityX > SNAP_VELOCITY && mCurScreen > 0) { 196 // Fling enough to move left 197 // Log.e(TAG, "snap left"); 198 snapToScreen(mCurScreen - 1); 199 } else if (velocityX < -SNAP_VELOCITY && mCurScreen < getChildCount() - 1) { 200 // Fling enough to move right 201 // Log.e(TAG, "snap right"); 202 snapToScreen(mCurScreen + 1); 203 } else { 204 snapToDestination(); 205 } 206 if (mVelocityTracker != null) { 207 mVelocityTracker.recycle(); 208 mVelocityTracker = null; 209 } 210 // } 211 mTouchState = TOUCH_STATE_REST; 212 break; 213 case MotionEvent.ACTION_CANCEL: 214 mTouchState = TOUCH_STATE_REST; 215 break; 216 } 217 return true; 218 } 219 220 @Override 221 public boolean onInterceptTouchEvent(MotionEvent ev) { 222 // Log.e(TAG, "onInterceptTouchEvent-slop:" + mTouchSlop); 223 final int action = ev.getAction(); 224 if ((action == MotionEvent.ACTION_MOVE) && (mTouchState != TOUCH_STATE_REST)) { 225 return true; 226 } 227 final float x = ev.getX(); 228 final float y = ev.getY(); 229 switch (action) { 230 case MotionEvent.ACTION_MOVE: 231 final int xDiff = (int) Math.abs(mLastMotionX - x); 232 if (xDiff > mTouchSlop) { 233 mTouchState = TOUCH_STATE_SCROLLING; 234 } 235 break; 236 case MotionEvent.ACTION_DOWN: 237 mLastMotionX = x; 238 mLastMotionY = y; 239 mTouchState = mScroller.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING; 240 break; 241 case MotionEvent.ACTION_CANCEL: 242 case MotionEvent.ACTION_UP: 243 mTouchState = TOUCH_STATE_REST; 244 break; 245 } 246 return mTouchState != TOUCH_STATE_REST; 247 } 248 249 /** 250 * 设置屏幕切换监听器 251 * 252 * @param listener 253 */ 254 public void SetOnViewChangeListener(OnViewChangeListener listener) { 255 mOnViewChangeListener = listener; 256 } 257 258 /** 259 * 屏幕切换监听器 260 * 261 * @author liux 262 */ 263 public interface OnViewChangeListener { 264 public void OnViewChange(int view); 265 } 266}

下面我就根据我的理解讲述一下整个滑动的过程

首先是整个组建的初始化,调用构造函数(这个就不详细说了)

调用onMeasure(int widthMeasureSpec, int heightMeasureSpec)函数,循环计算没一个子View的宽高。

调用onLayout(boolean changed, int l, int t, int r, int b)设置每一个子View的布局

调用onDraw方法开始画图。在调用onDraw方法时候会调用computeScroll()方法,假如正在滑动的时候就调用使ViewscrollTo方法滑动,然后调用postInvalidate更新界面。

用户滑动的时候,会先到用onInterceptTouchEvent(MotionEvent ev),第一次ACTION_DOWN的时候,onInterceptTouchEvent方法会返回false,表示ViewGroup不拦截这个之后的手势(即这一系列的手势会传到其子View),然后会触发ScrollLayout的onTouchEvent(MotionEvent event)方法(子View的onTouchEvent均返回false的情况下)。

onTouchEvent(MotionEvent event)方法中

ACTION_DOWN事件:

记下事件的坐标。

ACTION_MOVE事件:

计算滑动的距离,当水平华东大于200,垂直滑动大于10时候组建滑动。

ACTION_UP事件:

使用VelocityTracker计算滑动的速度,当速度大于600 千像素/微秒 时候,根据速度方向滑动。然后注销VelocityTracker。当速度达不到的时候,调用snapToDestination()方法判断应该返回到那一个页面。

参考链接

onMeasure和onLayout:http://blog.csdn.net/czh0766/article/details/5846460

Scroller:http://www.cnblogs.com/over140/archive/2010/12/16/1907528.htmlhttp://ipjmc.iteye.com/blog/1615828

onInterceptTouchEvent和onTouchEvent:http://blog.csdn.net/ddna/article/details/5473293

点赞
收藏

评论区

加载中...

相关推荐

JAVA回调机制(CallBack)之小红是怎样买到房子的??

JAVA回调机制CallBack序言最近学习java,接触到了回调机制(CallBack)。初识时感觉比较混乱,而且在网上搜索到的相关的讲解,要么一言带过,要么说的比较单纯的像是给CallBack做了一个定义。当然了,我在理解了回调之后,再去看网上的各种讲解,确实没什么问题。但是,对于初学的我来说,缺了一个循序渐进的过程。此处,将我对回调机制的个人理解,按

仿QQ6.0主页面侧滑效果

1.概述最近一直都在带实习生做项目,发现自己好久没有写博客了,这几天更新会比较频繁,今天玩QQ的时候发现QQ主页菜单滑动效果早就变了,实在忍不住晚上就来实现一下了!    这里写图片描述2.实现2.1.实现的方式多种多样  2.1.1 自定义ViewGroup,处理其onTouch事件  2.1.2 FrameLayout

Go 源码阅读之 flag 包

Go源码阅读系列是我的源码阅读笔记。因为本人的电脑上Go的版本是1.13.4,所以就选择了该版本作为学习的版本。为此我在Github上Fork了Go的源码,并创建了study1.13.4分支,来记录对于源码的个人理解或者说中文注释也行。每当阅读完一个包后都会进行一下小结,就像这篇是对flag包的总结整理。当然在整理的过程中发现Go夜读

2019年Android岗位BAT等大厂面试题,希望对新的一年的你有所帮助

2019年Android岗位BAT等大厂面试题知识点小结2019年了搜集了很多面试题,希望能对大家有所帮助1.View的绘制流程;自定义View如何考虑机型适配;自定义View的事件分发机制;View和ViewGroup分别有哪些事件分发相关的回调方法;自定义View如何提供获取View属

(绝对有用)iOS获取UUID,并使用keychain存储

UDID被弃用,使用UUID来作为设备的唯一标识。获取到UUID后,如果用NSUserDefaults存储,当程序被卸载后重装时,再获得的UUID和之前就不同了。使用keychain存储可以保证程序卸载重装时,UUID不变。但当刷机或者升级系统后,UUID还是会改变的。但这仍是目前为止最佳的解决办法了,如果有更好的解决办法,欢迎留言。(我整理的解决办法的参

Django开发个人博客入门学习经验贴

【写在前面】  入门学习搭建个人博客系统首先还是参考大佬们的经验,记得刚入手Django的时候,一篇博客大佬说过一句话,做技术的不要一开始就扎头于细节中,先把握整体框架,了解这个对象之后再去了解细节,进行优化,深入的学习,所以开始学习搭建个人博客系统的时候还是先从简单入门的开始,这个帖子不是教程贴,是一个经验贴,主要是分享一下学习搭建博客的过程中找到的几