- 在android中View为所有控件的基类,简单的控件和VIewGroup都继承自View。
- 一个View占用了屏幕上的一个矩形区域并且负责界面绘制和事件处理。
- View是一个宽泛的概念,既可以指复杂的ViewGroup(控件组,诸如RelativeLayout,LinearLayout),也可以是简单的控件(诸如TextView,Button)。
- View树,由于View既可以做简单控件,也可为ViewGroup,故而View体系组成了View树,View的显示和事件处理,都是依赖于这个View树。绘制和事件处理的起始点,都是从根View开始一级一级的往下传递。我们从任意一层发起绘制,都将反馈到根View,然后再从上往下传递。
android坐标系中的view
View的四个顶点决定了其显示的位置,分别为left、top、right、bottom(分别对应着view的左上顶点的横坐标、左上顶点的纵坐标、右下顶点的横坐标、右下顶点的纵坐标),
分别通过getLeft()、getTop()、getRight()、getBottom()方法获取(相对于ViewGroup)
width=getRight()-getLeft();
height=getBottom()-getTop();
常见触摸事件
从手指触摸屏幕开始到手指离开屏幕,触发了一系列事件,常见动作及触发时机
MotionEvent.ACTION_DOWN ,触摸屏幕时触发
MotionEvent.ACTION_MOVE,滑动时触发
MotionEvent.ACTION_UP,离开屏幕时触发
MotionEvent.ACTION_CANCEL,当焦点滑动到控件之外时触发
MotionEvent对象获取事件发生时的坐标
getX() / getY() , 获取相对于View自身的左上角的x/y坐标
getRawX() / getRawY() , 获取相对于屏幕左上角的x/y坐标
Distance in pixels a touch can wander before we think the user is scrolling,即可以视为触摸而非滑动事件的最大距离,大于这个值一般可视为滑动。其值为常量且与设备有关,不同设备值可能不同。
mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
作用:手势检测,如单击,滑动,长按,双击等
使用:
实现OnGesturerListener接口,注册事件监听
接管目标View滑动事件onTouchEvent方法。在onTouchEvent方法中
boolean isConsume = mGestureDetector.onTouchEvent(event);
return isConsume;
在对应的方法中执行相应的操作
onDown Notified when a tap occurs with the down MotionEvent that triggered it.
onFling Notified of a fling event when it occurs with the initial on down MotionEvent and the matching up MotionEvent.//手指以某个速率滑离屏幕时
onLongPress Notified when a long press occurs with the initial on down MotionEvent that trigged it.
onScroll Notified when a scroll occurs with the initial on down MotionEvent and the current move MotionEvent.
onShowPress The user has performed a down MotionEvent and not performed a move or up yet.
onSingleTapUp Notified when a tap occurs with the up MotionEvent that triggered it.
onSingleTapConfirmed(MotionEvent e); Notified when a single-tap occurs. Unlike {@link OnGestureListener#onSingleTapUp(MotionEvent)}, this will only be called after the detector is confident that the user’s first tap is not followed by a second tap leading to a double-tap gesture.// 严格的单击事件,不可能为双击事件的一击
作用:识别缩放手势
使用:
实现OnScaleGestureListener接口,注册事件监听
接管目标View滑动事件onTouchEvent方法。在onTouchEvent方法中
boolean isConsume = mScaleGestureDetector.onTouchEvent(event);
return isConsume;
在对应的方法中执行相应的操作
// 获取缩放因子
float scaleFactor = detector.getScaleFactor();
mMatrix.postScale(scaleFactor,scaleFactor,detector.getFocusX(), detector.getFocusY()); //手势中点
setImageMatrix(mMatrix);
onScaleBegin,onScale返回值为true时才会有缩放效果
onScale Responds to scaling events for a gesture in progress.缩放时
onScaleBegin Responds to the beginning of a scaling gesture. 缩放开始
onScaleEnd Responds to the end of a scale gesture. 缩放结束
弹性滑动对象,本身并不能产生弹性滑动,需要配合computeScroll () 与scrollTo()方法才能实现view的弹性滑动,示例、
/**
* 弹性滑动
* @param startX
* @param startY
* @param dX ,x方向的偏移量,>0往左滑 ,<0往右滑
* @param dY ,y方向的偏移量,>0往上滑 ,<0往下滑
* @param duration , 持续时长
*/
private void smoothScroll(int startX, int startY, int dX, int dY, int duration) {
if (Math.abs(duration) > 800) duration = 800;
mScroller.startScroll(startX, startY, dX, dY, Math.abs(duration));
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) { // computeScrollOffset返回值为true则滑动还没结束
scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
当从左往右滑getXVelocity>0 , 反之getXVelocity<0
当从上往下滑getXVelocity>0 , 反之getXVelocity<0
获取VelocityTracker
if (velocityTracker == null) {
velocityTracker = VelocityTracker.obtain();
}
添加用户的movement到tracker
velocityTracker.addMovement(ev); // ACTION_DOWN与ACTION_MOVE都要
设置时间间隔计算速度
velocityTracker.computeCurrentVelocity(50); // 计算当前速率,按每50毫秒
获取x与y方向速度
final float yVelocity = velocityTracker.getYVelocity();
final float xVelocity = velocityTracker.getXVelocity();
回收资源
velocityTracker.clear(); // 重置到初始状态
当velocityTracker不再使用时,调用velocityTracker.recycle();回收资源,以便其他使用
特点:瞬间完成,scrollTo绝对移动,scrollBy相对移动scrollTo是滑动到指定位置,scrollBy是相对自身的位置滑动指定距离
scrollBy基于scrollTo实现
/**
* @param x the amount of pixels to scroll by horizontally
* @param y the amount of pixels to scroll by vertically
*/
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
getScrollX获取的是view内容左边缘相对于view边界左边缘的偏移量,值=view边界左边缘 x- view内容左边缘x
getScrollY获取的是view内容上边缘相对于view边界上边缘的偏移量,值=view边界上边缘y - view内容上边缘y
由上可总结如下,
view内容左边缘滑动到view左边缘左边时,getScrollX>0,反之,右边时,getScrollX<0
view内容上边缘滑动到view上边缘上边时,getScrollY>0,反之,下边时,getScrollY<0
- 补间动画
包括AlphaAnimation、TranslateAnimation、ScaleAnimation、RotateAnimation
特点:不改变view的位置属性,移动的只是view的副本
设置setFillAfter为true,保持最后状态
- 属性动画
包括ObjectAnimator,ValueAnimator
特点:真实改变View的属性
Android2.3以下使用nineoldandroids兼容库
示例
ObjectAnimator.ofFloat(targetView,"alpha",0.0f,1.0f);
通过改变View的自身的位置参数实现滑动效果
在目标view旁置一个空view,通过改变此空view的位置参数来间接改变目标view位置,
获取并修改位置参数
ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) textView.getLayoutParams();
mlp.leftMargin += 10;
mlp.width += 10;
textView.setLayoutParams(mlp); // 或textView.requestLayout();
int contentLeft = centerContent.getLeft() + dX;
centerContent.layout(contentLeft,centerContent.getTop(),contentLeft+centerContent.getMeasuredWidth(), centerContent.getBottom());
offsetLeftAndRight : 通过特定的pixels对view进行水平方向偏移
Offset this view's horizontal location by the specified amount of pixels.
offsetTopAndBottom : 通过特定的pixels对view进行竖直方向偏移
Offset this view's vertical location by the specified number of pixels.
create(forParent, callBack) —创建
forParent - Parent view to monitor 要监控的父view
callBack - Callback to provide information and receive events 回调
tryCaptureView (View child, int pointerId) —决定哪个view可以被捕获
child - Child the user is attempting to capture 要捕获的view
pointerId - ID of the pointer attempting the capture touch的id
clampViewPositionHorizontal (View child, int left, int dx) —限制被拖拽view的水平方向滑动
child - Child view being dragged 被拖拽的view
left - Attempted motion along the X axis x方向尝试滑动的left
dx - Proposed change in position for left x方向的增量
clampViewPositionVertical —限制被拖拽view的水竖直向滑动
onViewPositionChanged(View changedView, int left, int top, int dx, int dy) —当view位置改变时
param left 此view的左边界的x
param top 此view的上边界的y
param dx 与上次相比的x位置增量
param dy 与上次相比的y位置增量
onViewReleased(View releasedChild, float xvel, float yvel) — 释放拖动后调用
param releasedChild 被释放的view
param xvel x方向的速度
param yvel y方向的速度
getViewHorizontalDragRange与getViewVerticalDragRange 返回view的水平与竖直拖动范围,设置为0则不可拖动
onEdgeDragStarted 在边界拖动时回调
mViewDragHelper.smoothSlideViewTo(View child,int finalLeft, int finalTop) 开启滑动
ViewCompat. postInvalidateOnAnimation(View view) 引导重绘
computeScroll中continueSettling判断是否完成
注意事项:
1. 滑动的是view的内容而不是view本身位置改变
2. mScroller.getCurrX() / mScroller.getCurrY()获得的值是根据时间流逝比例计算所得,为scrollTo将要滑动到的位置
3. mScroller.computeScrollOffset() 返回值为true表示滑动还未结束
4. Scroller内部只是保存了我们传递的参数,没有实现滑动
5. mScroller.startScroll并不会开始滑动,调用invalidate()才会导致view重绘,并开始滑动
6. Scroller本身需要配合computeScroll方法和scrollTo方法才能实现弹性滑动,通过不断的view重绘和小步位移完成view的滑动效果
7. 如果滑动过程执行在非UI线程,在computeScroll方法中应调用postInvalidate()进行view重绘,否则会导致异常产生
/**
* @param targetView 目标view
* @param dx , 偏移量
* @param duration
*/
private void smoothScroll(final View targetView, final int dx, int duration) {
final int left = targetView.getLeft();
final int right = targetView.getRight();
final ValueAnimator animator = ObjectAnimator.ofFloat(0, 1);
animator.setInterpolator(new AccelerateDecelerateInterpolator(mContext,null));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
final float fraction = animation.getAnimatedFraction();
targetView.layout((int) (left + fraction * dx), targetView.getTop(), (int) (right + fraction * dx), targetView.getBottom());
}
});
animator.start();
}
/**
* @param targetView 目标view
* @param dx , 偏移量
*/
private void smoothScrollRunnable(final View targetView, final int dx) {
targetView.postDelayed(new ScrollRunnable(targetView, dx),10);
}
class ScrollRunnable implements Runnable {
private View targetView;
private int dx;
private int left, right; // targetView初始的左右位置
private float fraction; //位移比率
public ScrollRunnable(View targetView, int dx) {
this.targetView = targetView;
this.dx = dx;
left = targetView.getLeft();
right = targetView.getRight();
}
@Override
public void run() {
fraction += 0.04;
if (fraction < 1.0f) {
int delta = (int) (fraction * dx);
Log.e("", delta+"-------------- ");
targetView.layout(left + delta, targetView.getTop(), right + delta, targetView.getBottom());
postDelayed(this, 5);
}else {
targetView.layout(left + dx, targetView.getTop(), right + dx, targetView.getBottom());
}
}
}
public boolean dispatchTouchEvent(MotionEvent ev) 事件分发
public boolean onInterceptTouchEvent(MotionEvent ev)(ViewGroup) 事件拦截
public boolean onTouchEvent (MotionEvent ev) 事件处理
注意:只有ViewGroup才有onInterceptTouchEvent方法,简单的view不具备onInterceptTouchEvent方法
onTouch > onTouchEvent > onClick
#四、 滑动冲突解决办法
###1. 滑动冲突处理规则
根据滑动的方向来判断该由谁来拦截事件
在父容器中onInterceptTouchEvent的处理细节
boolean intercepted=false;
if(父容器要消耗的事件){
intercepted=true;
}else{
intercepted=false;
}
return intercepted;
注:此篇为总结篇,在前人的基础上,增加部分内容,作了自我梳理