5.1 滑动效果是如何产生的
滑动的本质是改变View的坐标
5.1.1 Android坐标系
屏幕左上角为原点,向右为x轴正方形,向下为y轴正方向
系统提供了View.getLocationOnScreen(int location[])获取View左上角在整个屏幕中的坐标。View.getLocationInWindow(int location[])获取View左上角相对于父布局的位置。详见https://blog.csdn.net/ouyang_peng/article/details/46902957。
获取各种XY坐标图示
5.1.2 视图坐标系
以父视图的左上角坐标为原点的相对坐标系,通过getX()getY()获取相对坐标
5.1.3 触控事件
触控事件的不同类型
//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_CANCLE=3;
//触摸动作超出边界
public static final int ACTION_OUTSIDE=4;
//多点触摸按下动作
public static final int ACTION_POINTER_DOWN=5;
//多点离开动作
public static final int ACTION_POINTER_UP=6;
通常通过在onTouchEvent(MotionEvent ev)中使用ev.getAction()来获取触控事件类型
@Override
public boolean onTouchEvent(MotionEvent ev){
switch(ev.getAction()){
case ACTION_DOWN:
...
break;
case ACTION_UP:
...
break;
}
return true;
}
Android获取坐标值,距离的方法。
1、View提供的方法:getTop,getBottom,getLeft,getRight
2、MotionEvent提供的方法:getX,getY(获取相对坐标),getRawX,getRawY(获取绝对坐标)
5.2 实现滑动的7种方法
滑动的基本思想是触摸View时,记下坐标,移动后,记下坐标,算出位移,移动View。
我们在LinearLayout(ViewGroup)中方一个View
5.2.1 layout方法
ViewGroup.setOnTouchListener((event)->{
int x=(int)event.getX();
int y=(int)event.getY();
switch(event.getAction){
case ACTION_DOWN:
lastX=x;
lastY=y;
case ACTION_MOVE:
int offsetX=x-lastX;
int offsetY=y=lastY;
//在当前left,right,top,bottom增加偏移量
layout(getLeft()+offsetX,
getTop()+offsetY,
getRight()+offsetX
getBottom()+offsetY);
break;
}
return true;
});
同样,可以使用getRawX和getRawY计算偏移量。
5.2.2 offsetLeftAndRight()和offsetTopAndBottom()
此方法为系统提供的上下左右偏移API,参数为XY偏移量
//同时对Left和Right偏移
offsetLeftAndRight(offsetX);
//同时对Top和Bottom偏移
offsetTopandBottom(offsetY);
5.2.3 LayoutParams
LayoutParams保存了一个View的布局参数,可以通过修改LayoutParams动态的修改View的位置
LinearLayout.LayoutParams params=(LinearLayout.LayoutParams)getLayoutParams();
params.leftMargin=getLeft()+offsetX();
params.topMargin=getTop()+offsetY();
setLayoutParams(params);
需要注意,获取LayoutParams,需指定父布局的类型(如LinearLayout,RelativeLayout),不然获取不到
也可以通过MarginLayoutParams修改坐标,原理相同,需要指定ViewGroup。
ViewGroup.MarginLayoutParams params=(ViewGroup.MarginLayoutParams)getLayoutParams();
params.leftMargin=getLeft()+offsetX;
params.rightMargin=getTop()+offsetY;
setParams(params);
5.2.4 scrollTo()和scrollBy()
scrollTo(x,y)移动到指定坐标,scrollBy(dx,dy)移动偏移量。在ViewGroup中使用,移动的是子View,在View中使用,移动的是内容,如TextView的文字,ImageView的图像。
scrollby(dx,dy)的移动方式相当于固定canvas,移动屏幕玻璃。所以比如dx=-5,就是画布不动,屏幕玻璃左移5,其实就相当于玻璃不动,画布向右移5。所以scrollby用法如下:
int offsetX=x-lastX;
int offsetY=y-lastY;
((View)getParent()).scrollby(-offsetX,-offsetY);
5.2.5 Scroller
Scroller与scrollTo与scrollBy相比,实现了平滑移动的效果,而不是瞬间移动。实现原理是在ACTION_MOVE中不断移动微笑的偏移量,获得平滑移动的效果。
例:手指滑动时View跟着滑动,手指松开时View返回左上角
//1、初始化Scroller
mScroller=new Scroller();
//2、重写computeScroll()方法,系统绘制View的时候会在draw()方法中调用该方法
@Override
public void computeScroll(){
super.computeScroll();
if(mScroller.computeScrollOffset()){
((View)getParent).scrollerTo(mScroll.getCurrX,mScroller.getCurrY);
//通过重绘不断调用computeScroll()
invalidate();
}
}
//3、startScroll
//public void startScroll(int x,inty,int dx,int dy,int duration);//duration类似于动画持续时长
//public void startScroll(int x,inty,int dx,int dy);
case MotionEvent.ACTION_UP:
//手指离开时,返回左上角
View viewGroup=((View)getParent());
mScroller.startScroll(viewGroup.getScrollX(),
viewGroup.getScrollY(),
-viewGroup.getScrollX(),
-viewGroup.getScrollY()
);
invalidate();
break;
computeScrollOffset()判断是否滑动完成。只能在computeScroll中获取XY,但computeScroll是不会自动调用的。只能通过
invalidate()->draw()->computeScroll()来调用。
5.2.6 属性动画
(挖坑,相减动画章节)
5.2.7 ViewDragHelper
Google在其support库中提供了DrawerLayout和SlidingPaneLayout两个布局来帮助开发者实现侧边栏。布局背后有一个强大的类——ViewDragHelper。(drug 拖拽)
例:实现仿QQ侧边栏,拖拽超过一定距离,侧滑显示菜单。
1、初始化ViewDrugHelper
ViewDrugHelper通常定义在ViewGroup内部,使用静态工厂初始化。
//第一个参数是ViewGroup,用来监听View,第二个参数是callback回调,是逻辑核心
mViewDrugHelper=ViewDrugHelper.create(this,callback);
2、拦截事件
重写拦截事件,将事件传给ViewDrugHelper处理。
@Override
public boolean onInterceptTouchEvent(MotionEvent ev){
return mViewDrugHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent ev){
//将触摸事件传给ViewDrugHelper,此操作必不可少
mViewDrugHelper.processTouchEvent(ev);
return true;
}
3、处理computeScroll()
ViewDrugHelper内部是通过Scroller实现滑动的
@Override
public coid computeScroll(){
if(mViewDrugHelper.continueSetting(true)){
ViewCompat.postInvalidateOnAnimation(this);
}
}
4、处理回调Callback
private ViewDragHelper.Callback callback=new ViewDragHelper.Callback(){
//控制那个View可以被移动
@Override
public boolean tryCaptureView(View child,int pointId){
//触摸的是mainView开始滑动,触摸的是menuView不滑动
return mMainView==child;
}
//第二个参数为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;
}
//拖拽结束后调用
@Override
public void onViewReleased(View releaseChild,int xvel,int yvel){
super.onViewReleased(releaseChild,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);
}
}
};
重写onFinishInflate()和onSizeChanged()
@Override
protected void onFinishInflated(){
super.onFinishInflated();
mMainView=getChildAt(0);
mMenuView=getChildAt(1);
}
@Override
protected void onSizeChanged(int w,int h,int oldw,int oldh){
super.onSizeChanged(w,h,pldw,oldh);
mWidth=mMenuView.getMeasureWidth();
}