第五章目录
5.1 滑动效果是如何产生的
- 5.1.1 Android坐标系
- 5.1.2 视图坐标系
- 5.1.3 触控事件——MotionEvent
5.2 实现滑动的七种方法
- 5.2.1 layout方法
- 5.2.2 offsetLeftAndRight()与offsetTopAndBottom()
- 5.2.3 LayoutParams
- 5.2.4 scrollTo与scrollBy
- 5.2.5 Scroller
- 5.2.6 属性动画
- 5.2.7 ViewDragHelper
第五章读书笔记
5.1 滑动效果是如何产生的
要实现View的滑动,就必须监听用户触摸的事件,并根据事件传入的坐标,动态且不断地改变View的坐标,从而实现View跟随用户触摸的滑动而滑动
特别注意:View.getX()和event.getX()两个不同的位置获取的区别
- getTop(),getBottom(),getLeft(),getRight()是相对于父控件的距离,单位为像素,
- 看一张图就好理解了:
- event.getRowX():触摸点距离屏幕圆点的X坐标
- event.getX():触摸点距离组件圆点的X坐标
- 再看一张图,就也能迅速理解:
5.1.1 Android 坐标系
Android坐标系相当于第四象限,但是Y轴方向相反,向下是正方向
触控事件中通过getRawX()、getRawY()方法获得的坐标
就是该视图左上角在Android坐标系中的坐标,也就是距离屏幕圆点的坐标
一张图就明白了:
5.1.2 视图坐标系
视图坐标系也就是说父控件中的坐标,跟上面说的差不多,只不过上一个是指整个屏幕,而这一个是指组件
触控事件中通过getX()、getY()方法获得的坐标
就是该视图左上角在视图坐标系中的坐标,也就是距离组件圆点的坐标
5.1.3 触控事件——MotionEvent
MotionEvent中封装的一些常用的事件常量
- ACTION_DOWN:单点触摸按下的动作
- ACTION_UP:单点触摸离开的动作
- ACTION_MOVE:单点触摸移动的动作
- ACTION_CANCEL:单点触摸取消
- ACTION_OUTSIDE:单点触摸超出边界
- ACTION_POINTER_DOWN:多点触摸按下的动作
- ACTION_POINTER_UP:多点触摸离开的动作
通常会在onTouchEvent(MotionEvent event)方法中通过event.getAction()方法获取触控事件类型,以下代码为模板式子:
@Override public boolean onTouchEvent(MotionEvent event) { //获取当前输入点的X,Y坐标(视图坐标) int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: //处理输入的按下动作 break; case MotionEvent.ACTION_MOVE: //处理输入的移动动作 break; case MotionEvent.ACTION_UP: //处理输入的离开动作 break; } return true; }
很尴尬的是看到这里了,发现我上面已经总结了这里的知识点,在贴一遍吧:
系统还提供了非常多的方法来获取坐标值、相对距离等:
View提供的获取坐标方法:
- getTop():获取到的是View自身的顶边到其父布局顶边的距离
- getLeft():获取到的是View自身的左边到其父布局左边的距离
- getRight():获取到的是View自身的右边到其父布局右边的距离
- getBottom():获取到的是View自身的底边到其父布局底边的距离
MotionEvent提供的方法:
- getX():获取点击事件距离控件左边的距离,即视图坐标
- getY():获取点击事件距离控件顶边的距离,即视图坐标
- getRawX:获取点击事件距离整个屏幕左边的距离,即绝对坐标
- getRawY:获取点击事件距离整个屏幕顶边的距离,即绝对坐标
再来一张图,哈哈哈:
5.2 实现滑动的七种方法
5.2.1 layout方法
- 在View进行绘制时,会调用onLayout()来设置显示的位置
- 一定要重新设置初始坐标,这样才能准确地获取偏移量
public class DragView2 extends View { // 上一次的坐标值 private int lastX; private int lastY; public DragView2(Context context) { super(context); ininView(); } public DragView2(Context context, AttributeSet attrs) { super(context, attrs); ininView(); } public DragView2(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(); } private void ininView() { setBackgroundColor(Color.BLUE); } // 绝对坐标方式 @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) (event.getRawX()); int rawY = (int) (event.getRawY()); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸点坐标 lastX = rawX; lastY = rawY; break; case MotionEvent.ACTION_MOVE: // 计算偏移量 int offsetX = rawX - lastX; int offsetY = rawY - lastY; // 在当前left、top、right、bottom的基础上加上偏移量 layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); // 重新设置初始坐标 // 当你移动后肯定要设置此时的坐标为接下来移动后的last坐标 lastX = rawX; lastY = rawY; break; } return true; } }
5.2.2 offsetLeftAndRight()与offsetTopAndBottom()
- 这两个方法相当于提供一个对左右、上下移动的封装
public class DragView1 extends View { private int lastX; private int lastY; public DragView1(Context context) { super(context); ininView(); } public DragView1(Context context, AttributeSet attrs) { super(context, attrs); ininView(); } public DragView1(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(); } private void ininView() { // 给View设置背景颜色,便于观察 setBackgroundColor(Color.BLUE); } // 视图坐标方式 @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸点坐标 lastX = x; lastY = y; break; case MotionEvent.ACTION_MOVE: // 计算偏移量 int offsetX = x - lastX; int offsetY = y - lastY; // 在当前left、top、right、bottom的基础上加上偏移量 layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); // 使用此方法就不用重置坐标了 offsetLeftAndRight(offsetX); offsetTopAndBottom(offsetY); break; } return true; } }
5.2.3 LayoutParams
通过Margin()方法,设置params距离left和top的偏移量
public class DragView3 extends View { private int lastX; private int lastY; public DragView3(Context context) { super(context); ininView(); } public DragView3(Context context, AttributeSet attrs) { super(context, attrs); ininView(); } public DragView3(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(); } private void ininView() { setBackgroundColor(Color.BLUE); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: // 记录触摸点坐标 lastX = (int) event.getX(); lastY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: // 计算偏移量 int offsetX = x - lastX; int offsetY = y - lastY; ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams(); //LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams(); layoutParams.leftMargin = getLeft() + offsetX; layoutParams.topMargin = getTop() + offsetY; setLayoutParams(layoutParams); break; } return true; } }
5.2.4 scrollTo与scrollBy
- scrollTo指的是移动到一个具体的坐标点
- scrollBy指的是移动多少的距离
- 这两个方法只对内容有效,这里的内容其实就是指的是child,比如父布局中的子控件,TextView中的文本
- 最形象理解那就是RecyclerView当中调用这两个方法,它们的子View会滚动
- 要注意的是,scrollTo、scrollBy是以Android坐标系移动的,所以要使用我们正常的坐标系,则在x和y加一个负号(和正常相反)
public class DragView4 extends View { private int lastX; private int lastY; public DragView4(Context context) { super(context); ininView(); } public DragView4(Context context, AttributeSet attrs) { super(context, attrs); ininView(); } public DragView4(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(); } private void ininView() { setBackgroundColor(Color.BLUE); } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getX(); lastY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: int offsetX = x - lastX; int offsetY = y - lastY; ((View) getParent()).scrollBy(-offsetX, -offsetY); break; } return true; } }
5.2.5 Scroller
public class DragView5 extends View { private int lastX; private int lastY; private Scroller mScroller; public DragView5(Context context) { super(context); ininView(context); } public DragView5(Context context, AttributeSet attrs) { super(context, attrs); ininView(context); } public DragView5(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); ininView(context); } private void ininView(Context context) { setBackgroundColor(Color.BLUE); // 初始化Scroller mScroller = new Scroller(context); } // 此方法可以理解为,只要view稍微动一点点,就会调用computeScroll() // getCurrX()是这个核心的方法,随时随地都能得到当前的位置,通过scrollTo进行滑动 @Override public void computeScroll() { super.computeScroll(); // 判断Scroller是否执行完毕 // 当滑动停止后,返回的是false // 正在滑动时,调用invalidate()->draw()->computeScroll()会一直调用此方法 if (mScroller.computeScrollOffset()) { ((View) getParent()).scrollTo( mScroller.getCurrX(), mScroller.getCurrY()); // 通过重绘来不断调用computeScroll invalidate(); } } @Override public boolean onTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: lastX = (int) event.getX(); lastY = (int) event.getY(); break; case MotionEvent.ACTION_MOVE: int offsetX = x - lastX; int offsetY = y - lastY; ((View) getParent()).scrollBy(-offsetX, -offsetY); break; case MotionEvent.ACTION_UP: // 手指离开时,执行滑动过程 // 这个构造方法意为初始的X坐标,初始的Y坐标,滑动的X偏移量,滑动的Y偏移量 // 实现了抬起手指后回到原位置的功能 View viewGroup = ((View) getParent()); mScroller.startScroll( viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY()); invalidate(); break; } return true; } }
5.2.6 属性动画
这个在后面会有更加详细的讲述,这里就不说了
5.2.7 ViewDragHelper
ViewDragHelper通常定义在一个ViewGroup的内部
public class DragViewGroup extends FrameLayout { private ViewDragHelper mViewDragHelper; private View mMenuView, mMainView; private int mWidth; public DragViewGroup(Context context) { super(context); initView(); } public DragViewGroup(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(); } @Override protected void onFinishInflate() { super.onFinishInflate(); mMenuView = getChildAt(0); mMainView = getChildAt(1); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = mMenuView.getMeasuredWidth(); } // 拦截事件,拦截 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mViewDragHelper.shouldInterceptTouchEvent(ev); } // 处理 @Override public boolean onTouchEvent(MotionEvent event) { //将触摸事件传递给ViewDragHelper,此操作必不可少 mViewDragHelper.processTouchEvent(event); return true; } private void initView() { // 初始化ViewDragHelper,通过静态工厂方法 mViewDragHelper = ViewDragHelper.create(this, callback); } private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { // 何时开始检测触摸事件,指定哪个子View可以被拖动 @Override public boolean tryCaptureView(View child, int pointerId) { //如果当前触摸的child是mMainView时开始检测 return mMainView == child; } // 触摸到View后回调 @Override public void onViewCaptured(View capturedChild, int activePointerId) { super.onViewCaptured(capturedChild, activePointerId); } // 当拖拽状态改变,比如idle,dragging @Override public void onViewDragStateChanged(int state) { super.onViewDragStateChanged(state); } // 当位置改变的时候调用,常用与滑动时更改scale等 @Override public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { super.onViewPositionChanged(changedView, left, top, dx, dy); } // 处理垂直滑动,返回0为不滑动 @Override public int clampViewPositionVertical(View child, int top, int dy) { return 0; } // 处理水平滑动 @Override public int clampViewPositionHorizontal(View child, int left, int dx) { return left; } // 拖动结束后调用,当MainView移动后距离左边小于500像素,就回到初始状态 // 否则就将侧边栏显示出来 @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); //手指抬起后缓慢移动到指定位置 if (mMainView.getLeft() < 500) { //关闭菜单 //相当于Scroller的startScroll方法 mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); } else { //打开菜单 mViewDragHelper.smoothSlideViewTo(mMainView, 300, 0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); } } }; // 处理滑动 @Override public void computeScroll() { if (mViewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } }
布局:
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <com.handsome.qunyingzhuang.chapter_5.DragViewGroup android:layout_width="match_parent" android:layout_height="match_parent"> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_blue_light"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Menu" /> </FrameLayout> <FrameLayout android:layout_width="match_parent" android:layout_height="match_parent" android:background="@android:color/holo_orange_dark"> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Main" /> </FrameLayout> </com.handsome.qunyingzhuang.chapter_5.DragViewGroup> </RelativeLayout>
总结
这章学了Android的坐标系,以及触控事件,还有滑动的7种方法,然后对这些都有一个比较深的印象了,希望在接下来的学习当中能把这些东西运用自如,提高自己的技术水平!