一:滑动的产生原因
滑动一个View原理:就是通过不断的改变View的坐标。
实现View的滑动,必须监听用户触摸的事件,根据触摸传入的坐标,动态的改变View的坐标,从而实现滑动
Android的坐标系:Android中屏幕的最左上端的点就是原点,向右就是X坐标轴的正方向,向下就是Y坐标轴的正方向
系统提供的getLocationOnScreen(intlocation[])方法获取Android坐标系中位置 触控事件中的getRawX 和getRawY方法实 现同样效果
视图坐标系:相对于Android坐标系,视图坐标系主要描述子View在父 View中的位置,将父视图 左上角作为坐标原点。触控事件中getX getY可以获取View的视图坐标系
触控事件:
常用到的:单点触摸、单点触摸离开屏幕、触摸点移动
public static final int ACTION_DOWN = 0;
public static final int ACTION_UP = 1;
public static final int ACTION_MOVE = 2;
通常会在onTouchEvent(MotionEvent event)中通过event.getAction()方法获取触控事件的类型。
常用获取坐标的方法:
(1)View提供的获取坐标的方法
getTop:获取当前View自身的顶边到父布局顶边的距离
getLeft:获取当前View自身的左边到父布局左边的距离
getRight:获取当前View自身的右边到父布局右边的距离
getBottom:获取当前View自身的底边到父布局底边的距离
(2)触控事件MotionEvent中提供的方法
getX:视图坐标 点击事件到控件左边的距离
getY:视图坐标
getRawX:Android坐标 点击事件距离屏幕左端的距离
getRawY:Android坐标
二.实现滑动的七种方法
第一种:layout方法
采用getX,getY
// 视图坐标方式
@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);
//同时对left和right进行偏移
// offsetLeftAndRight(offsetX);
//同时对top还有bottom进行偏移
// offsetTopAndBottom(offsetY);
break;
}
return true;
}
采用getRawX getRawY
// 绝对坐标方式
@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);
// 重新设置初始坐标
lastX = rawX;
lastY = rawY;
break;
}
return true;
}
}
执行完Action_Move后这里需要重新设置初始坐标,具体原因尚未搞懂、
第二种:offsetLeftAndRight() offsetTopAndBottom()方法 见第一段代码
第三种 LayoutParams
@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
// LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
LayoutParams保存一个View的布局参数,因此可以改变LayoutParams来实现View位置改变
使用此方法的前提是 此View必须有一个父布局
第四种 scrollTo scrollBy
scrollTo(x,y)移动到坐标是x,y的位置 scrollBy(x,y)表示移动的增量
这种方法需要注意两点:
第一点:这两种方法移动的是View的content,在ViewGroup中移动的是子View
第二点:将scrollBy中的参数设为正,那么content将向坐标轴负方向移动;同理,将scrollBy中的参数设为负,那么content将向坐标轴正方向移动
((View) getParent()).scrollBy(-offsetX,-offsetY);
第五种 Scroller
使用scrollTo和scrollBy方法,子View的平移是瞬时的,采用Scroller类可以实现平滑移动,而不是瞬时移动
( 1 )初始化 Scroller
( 2 )重写computeScroll方法,实现模拟滑动
(3)startScroll开启模拟过程
private void initView() {
setBackgroundColor(Color.BLUE);
mScroller=new Scroller(getContext());
}
@Override
public void computeScroll() {
super.computeScroll();
//computeScrollOffset方法判别是否完成整个滑动,未完成返回true
if(mScroller.computeScrollOffset()){
//.getCurrX() getCurrY()获取当前的滑动坐标
((View)getParent()).scrollTo( mScroller.getCurrX(),mScroller.getCurrY());
//通过重绘(draw)不断调用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=x;
lastY=y;
break;
case MotionEvent.ACTION_MOVE:
int offsetX=x-lastX;
int offsetY=y-lastY;
((View) getParent()).scrollBy(-offsetX,-offsetY);
break;
case MotionEvent.ACTION_UP:
View viewGroup= (View) getParent();
mScroller.startScroll(viewGroup.getScrollX(),viewGroup.getScrollY(),-viewGroup.getScrollX(),-viewGroup.getScrollY());
invalidate();
break;
}
return true;
}
第六种 属性动画
第七种 ViewDragHelper
通过使用
ViewDragHelper 基本可以实现各种不同的滑动,拖放要求
(1)初始化ViewDragHelper
(2)拦截事件
(3)处理computeScroll
(4)处理回调CallBack
public class DragViewGroup extends FrameLayout {
private ViewDragHelper mViewDragHelper;
private View mMenuView;
private View 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) {
mViewDragHelper.processTouchEvent(event);
return true;
}
private void initView(){
//初始化ViewDragHelper 第一个参数是要监听的View,通常是ViewGroup
//第二个参数是一个CallaBack的回调 ,这个回调是整个ViewDragHelper的逻辑核心
mViewDragHelper=ViewDragHelper.create(this,callback);
}
private ViewDragHelper.Callback callback=new ViewDragHelper.Callback() {
@Override
//通过此方法确定要移动的子View
public boolean tryCaptureView(View child, int pointerId) {
return mMainView==child;
}
@Override
//下面两种方法分别对应垂直和水平移动
//其中top 就是垂直方向上移动的距离 left就是水平移动的距离
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 releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if(mMainView.getLeft()<500){
//关闭菜单
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);
}
}
}