《Android群英传》读书笔记(4)第五章:Android(安卓)Scroll分析
1.Android坐标系
在Android中,将屏幕左上角的顶点作为Android坐标系的原点,从这个点向右是X轴正方向,向下是Y轴正方向。
系统提供了getLocatinoOnScreen(int location[]);
这样的方法来获取Android坐标系中点的位置。
2.视图坐标系
视图坐标系描述子视图在父视图中的位置关系,视图坐标系的原点是父视图的左上角。通过getX()
和getY()
获得的是视图坐标系中的坐标。
3.触控事件MotionEvent
MotionEvent
中封装着一些常用的事件常量:
//单点触摸按下动作public static final int ACTION_DOWN = 0;//单点触摸离开动作public static final int ACTION_UP = 1;//触摸点移动动作public static final int ACTION_MOVE = 2;//触摸动作取消public static final int ACTION_CANCEL = 3;//触摸动作超出边界public static final int ACTION_OUTSIDE = 4;//多点触摸按下动作public static final int ACTION_POINTER_DOWN = 5;//多点离开动作public static final int ACTION_POINTER_UP = 6;
View提供的获取坐标的方法
getTop()
:获取到的是View自身的顶边到其父布局顶边的距离
getLeft()
:获取到的是View自身的左边到其父布局左边的距离
getRight()
:获取到的是View自身的右边到期父布局左边的距离
getBottom()
:获取到的是View自身的底边到其父布局顶边的距离
MotionEvent提供的方法
getX()
:获取点击事件距离控件左边的距离,即视图坐标
getY()
:获取点击事件距离控件顶边的距离,即视图坐标
getRawX()
:获取点击事件距离整个屏幕左边的距离,即绝对坐标
getRawY()
:获取点击事件距离整个屏幕顶边的距离,即绝对坐标
可以参考下图:
4.实现滑动的7种方法
1.使用layout方法
用View中的layout(int left,int top,int right,int bottom)
方法在每次触摸移动后加上相应的偏移量就能实现滑动:
@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; layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); break; case MotionEvent.ACTION_UP: break; } return true; }
或者使用getRawX()
和getRawY()
方法来获取坐标,不过要记得在ACTION_MOVE
逻辑的最后为lastX
和lastY
重新赋值。
@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; layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY); lastX = rawX; lastY = rawY; break; case MotionEvent.ACTION_UP: break; } return true; }
2.offsetLeftAndRight()
与offsetTopAndBottom()
这个方法相当于系统提供的左右、上下移动的API的封装,计算出偏移量后,只需要使用如下的代码完成View的重新布局:
//对left和right进行偏移offsetLeftAndRight(offsetX);//对top和bottom进行偏移offsetTopAndBottom(offsetY);
3.使用LayoutParams
LayoutParams
保存了一个View的布局参数,因此可以在程序中通过改变LayoutParams
来动态地修改一个布局的位置参数。从而达到改变View位置的效果。使用getLayoutParams()
来获取一个View的LayoutParams
然后通过setLayoutParams()
来改变View的LayoutParams
,代码如下所示:
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)getLayoutParams();layoutParams.leftMargin = getLeft() + offsetX;layoutParams.topMargin = getTop() + offsetY;setLayoutParams(layoutParams);
4.使用scrollTo
和scrollBy
scrollTo(x,y)
表示移动到一个具体的具体的坐标点(x,y),scrollBy(dx,dy)
表示移动的偏移量为dx、dy;
使用scrollBy
或scrollTo
时需要通过父布局来进行移动,因为这两个方法移动的是View的内容或者ViewGroup的子view。因此通过父View调用该方法移动的才是子View,还有一点是此时移动的方向正好和offset的方向相反,需要将其取反。代码如下:
((View)getParent()).scrollBy(-offsetX,-offsetY);
5.Scroller
scrollBy()
和scrollTo()
方法实现的移动都是瞬间完成的,而Scroller
类可以实现平滑移动的效果。使用Scroller
都要重写computeScroll()
方法,系统在绘制View时会在draw()
方法中调用它,这个方法实际上就是使用scrollTo()
的方法,再结合Scroller
对象帮助获取当前的滚动值,来实现滚动效果。下面是computeScroll()
的代码:
@Overridepublic void computeScroll(){ super.computeScroll(); if(mScroller.computeScrollOffset()){ ((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); invalidate(); }}
computeScrollOffset()
方法判断是否完成了整个滑动过程,过程结束后该方法会返回false,getCurrX()和getCurrY()
来获得当前的滑动坐标。通过invalidate()
方法重绘来循环调用computeScroll()
方法。
使用Scroller
的startScroll()
方法来开启滑动过程,Scroller
类提供了两个重载的方法:
startScroll(int startX,int startY,int dx,int dy,int duration);startScroll(int startX,int startY,int dx,int dy);
下面的代码实现了View跟随手指滑动,当手指离开屏幕时,View自动回到原位置的效果:
int lastX;int lastY;View parent = ((View)getParent());@Overridepublic 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; .scrollBy(-offsetX,-offsetY); break; case MotionEvent.ACTION_UP: mScroller.startScroll( parent.getScrollX(), parent.getScrollY(), -parent.getScrollX(), -parent.getScrollY()); invalidate(); break; }}
6.属性动画
使用属性动画也能完成对View的移动
7.ViewDragHelper
通过ViewDragHelper
可以实现各种不同的滑动、拖放需求。通过实现类似QQ侧滑栏的布局来了解一下ViewDragHelper
首先初始化ViewDragHelper:
mViewDragHelper = ViewDragHelper.create(this,mCallback);
第一个参数是要监听的View,通常是一个ViewGroup,即ParentView,第二个参数是一个Callback回调,这个回调是整个ViewDragHelper的逻辑核心。
然后还要重写事件拦截方法,将事件传递给ViewDragHelper来处理:
@Overridepublic boolean onInterceptTouchEvent(MotionEvent event){ return mViewDragHelper.shouldInterceptTouchEvent(event);}@Overridepublic boolean onTouchEvent(MotoinEvent event){ mViewDragHelper.processTouchEvent(event); return true;}
处理computeScroll()
@Overridepublic void computeScroll(){ if(mViewDragHelper.continueSetling(true)){ ViewCompat.postInvalidateOnAnimation(this); }}
处理callback
回调
private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback(){ @Override public boolean tryCaptureView(View child,int pointerId){ return mMainView == child; }}
通过这个方法我们可以指定创建ViewDragHelper
时,参数parentView
中哪一个子view可以被移动,在这个实例中自定义了一个ViewGroup,里面有两个子view——mMenuView和mMainView,当指定了上述代码时,只有mMainView是可以被拖动的。
然后处理具体的滑动方法——clampViewPositionVertical()
和clampViewPositionHorizontal()
,分别对应垂直和水平滑动。必须重写这两个方法实现滑动。它们的默认返回值是0,即不发生滑动,只重写其中一个,就会实现对应方向上的滑动。代码如下所示:
@Overridepublic int clampViewPositionVertical(View child,int top,int dy){ return top;}@Overridepublic int clampViewPositionHorizontal(View child,int left,int dx){ return left;}
参数top和left分别代表在对应方向上的child移动的距离,dy、dx则表示相对于前一次的增量。通常情况下只需要返回top和left即可,但当需要更加精确地计算padding等属性时,就需要针对left和top进行一些处理,并返回合适的值。
下面是实现水平滑动的代码:
private ViewDragHelpe.Callback mCallback = new ViewDragHelper.Callback(){ @Override public boolean tryCaptureView(View child,int pointerId){ return mMainView == child; } @Override public int clampViewPositionVertical(View child,int top,int dy){ return 0; } @Override public int clampViewPositionHorizontal(View child,int left,int dx){ return left; }}
下面实现手指离开屏幕后View返回原来位置的效果:
在ViewDragHelper.Callback 中重写onViewReleased
方法
@Overridepublic void onViewReleased(View releasedChild,float xvel,float yvel){ super,onViewReleased(releasedChild,xvel,yvel); if(mMainView.getLeft() < mWidth / 3){ mViewDragHelper.smoothSlideViewTo(mMainView,0,0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); }else{ mViewDragHelper.smoothSlideViewTo(mMainView,300,0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); }}
上述代码让MainView移动后左边距小于其三分之一时回到原位置,否则就滑动到300的位置。
下面是自定义DragViewGroup的完整代码:
package com.chaoyang805.chapter5scrolldemo.view;import android.content.Context;import android.support.v4.view.ViewCompat;import android.support.v4.widget.ViewDragHelper;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.widget.FrameLayout;/** * Created by chaoyang805 on 2015/12/16. */public class DragViewGroup extends FrameLayout { private ViewDragHelper mHelper; private View mMenuView; private View mMainView; private int mWidth; public DragViewGroup(Context context) { this(context, null); } public DragViewGroup(Context context, AttributeSet attrs) { this(context, attrs, 0); } public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } private void init() { mHelper = ViewDragHelper.create(this,mCallback); } @Override protected void onFinishInflate() { super.onFinishInflate(); mMenuView = getChildAt(0); mMainView = getChildAt(1); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { mHelper.processTouchEvent(event); return true; } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { mWidth = mMenuView.getMeasuredWidth(); } @Override public void computeScroll() { if (mHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } } private ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() { @Override public boolean tryCaptureView(View child, int pointerId) { return mMainView == child; } @Override public int clampViewPositionHorizontal(View child, int left, int dx) { if (left < 0 && dx < 0) { return 0; } return left; } @Override public int clampViewPositionVertical(View child, int top, int dy) { return 0; } @Override public void onViewReleased(View releasedChild, float xvel, float yvel) { super.onViewReleased(releasedChild, xvel, yvel); if (mMainView.getLeft() < mWidth / 3) { mHelper.smoothSlideViewTo(mMainView, 0, 0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); } else { mHelper.smoothSlideViewTo(mMainView,300,0); ViewCompat.postInvalidateOnAnimation(DragViewGroup.this); } } };}
ViewDragHelper.Callback
中其他事件介绍:
//这个事件在用户触摸到View后回调onViewCaptured(View capturedChild,int activePointerId);//这个事件在拖拽状态改变时的回调,比如idle、dragging等onViewDragStateChanged(int state);//这个事件在位置改变时回调,常用于滑动时更改scale进行缩放等效果onViewPositionChanged(View changedView,int left,int top,int dx,int dy);
更多相关文章
- 浅谈Java中Collections.sort对List排序的两种方法
- Python list sort方法的具体使用
- python list.sort()根据多个关键字排序的方法实现
- Android中View的坐标常用方法
- AirPods怎么连接Android设备 AirPods与安卓设备连接方法
- Android下基于XML的Graphics shape使用方法
- Android(安卓)App开发基础篇—四大组件之Service
- Android事件总线EventBus的用法详解
- Android项目源码混淆问题解决方法