Android Scroll分析 (二) 教你使用七种方法实现滑动
2016-05-26 22:09
453 查看
实现滑动的基本思想是:当触摸View时,系统记下当前触摸点坐标;当手指移动时,系统记下移动后的触摸点坐标,从而获取到相对于前一次坐标点的偏移量,并通过偏移量来修改View的坐标,这样不断重复,从而实现滑动过程.
通过修改View的left,top,right,bottom四个属性来控制View的坐标,在每次回调onTouchEvent的时候,获取一下触摸点的坐标:
使用getRawX(),getRawY()来获取坐标,并使用绝对坐标来计算偏移量,要在每次执行完ACTION_MOVE的逻辑后,一定要重新设置初始坐标,这样才能准确地获取偏移量.
使用getLayoutParams()来获取一个View的LayoutParams.
通过setLayoutParams来改变其LayoutParams.
通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型.
还可以使用ViewGroup.MarginLayoutParams来实现这样的功能.
scrollTo,scrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有子View,但如果在View中使用,那么移动的将是View的内容.
将scrollBy中的参数dx和dy设置为正数,那么content将向坐标轴负方向移动;如果将scrollBy中的参数dx和dy设置为负数,那么content将向坐标轴正方向移动.
因此,要实现跟随手指移动而滑动的效果,就必须将偏移量改为负值:
在使用绝对坐标时,也可以通过使用scroll方法来实现.
1.初始化Scroller
首先,通过它的构造方法来创建一个Scroller对象:
2.重写computeScroll()方法,实现模拟滑动
重写computeScroll()方法,它是Scroller类的核心,系统在绘制View的时候会在draw()方法中调用该方法:
Scroller类提供了computeScrollOffset()方法来判断是否完成了整个滑动,通过getCurrX(),getCurrY()方法来获得当前的滑动坐标,注意invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX和scrollY坐标.但computeScroll()方法是不会自动调用的,只能通过invalidate()->draw()->computeScroll()来间接调用computeScroll()方法,所有需要调用invalidate()方法,实现循环获取scrollX和scrollY的目的,而当模拟过程结束后,scroller.computeScrollOffset()方法会返回false,从而中断循环,完成整个平滑移动过程.
3.startScroll开启模拟过程
在需要使用平滑移动的事件中,使用Scroller类的startScroll()方法来开启平滑移动过程.
startScroll()方法具有两个重载方法:
它们的区别在于:是否具有指定的持续时长.其它参数分别为起始坐标与偏移量.
在获取坐标时,使用getScrollX()和getScrollY()方法来获取父视图中content所滑动到的点的坐标.注意正负值,与scrollBy,scrollTo的情况相同.
例子:
演示一下如何使用Scroller类实现平滑移动.在这个实例中,让子View跟随手指的滑动而滑动,但是在手指离开屏幕是,让子View平滑的移动到初始位置,即屏幕左上角:
在startScroll()方法中,获取子View移动的距离–getScrollX(),getScrollY(),并将偏移量设置为其相反数,从而将子View滑动到原位置.注意invalidate()方法,需要使用这个方法来通知View进行重绘,从而来调用computeScroll()的模拟过程.
使用方法:
初始化ViewDragHelper
首先,自然是需要初始化ViewDragHelper.ViewDragHelper通常定义在一个ViewGroup的内部,并通过其静态工厂方法进行初始化.
第一个参数是要监听的View,通常需要是一个ViewGroup,即parentView;第二个参数是一个Callback回调,这个回调是整个ViewDragHelper核心.
拦截事件
接下来,重写事件拦截方法,将事件传递给ViewDragHelper进行处理:
处理computeScroll()
因为ViewDragHelp内部是通过Scroller来实现平滑移动的,所以需要实现处理computeScroll().
处理回调Callback
下面就是最关键的Callback实现:
实现侧滑菜单的完整例子:
2.1 Layout方法
在View进行绘制时,会调用onLayout()方法来设置显示的位置通过修改View的left,top,right,bottom四个属性来控制View的坐标,在每次回调onTouchEvent的时候,获取一下触摸点的坐标:
// 视图坐标方式 @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; } //使用getX(),getY()方法来获取坐标值,即通过视图坐标来获取偏移量
使用getRawX(),getRawY()来获取坐标,并使用绝对坐标来计算偏移量,要在每次执行完ACTION_MOVE的逻辑后,一定要重新设置初始坐标,这样才能准确地获取偏移量.
// 绝对坐标方式 @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; }
2.2 offsetLeftAndRight()与offsetTopAndBottom()
当计算出偏移量后,只需使用如下代码就可以完成View的重新布局://同时对left和right进行偏移 offsetLeftAndRight(offsetX); //同时对top和bottom进行偏移 offsetTopAndBottom(offsetY);
2.3 LayoutParams
LayoutParams保存了一个View的布局参数,通过LayoutParams来动态改变View的位置参数,从而改变View位置效果.使用getLayoutParams()来获取一个View的LayoutParams.
通过setLayoutParams来改变其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.LayoutParams layoutParams = (LinearLayout.LayoutParams) getLayoutParams(); layoutParams.leftMargin = getLeft() + offsetX; layoutParams.topMargin = getTop() + offsetY; setLayoutParams(layoutParams); break; } return true; }
通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型.
还可以使用ViewGroup.MarginLayoutParams来实现这样的功能.
2.4 scrollTo与scrollBy
在View中,提供了scrollTo,scrollBy两种方式来改变一个View的位置,两者的区别:与英文中To与By的区别类似,scroll(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(dx,dy)表示移动的增量为dx,dy.scrollTo,scrollBy方法移动的是View的content,即让View的内容移动,如果在ViewGroup中使用scrollTo,scrollBy方法,那么移动的将是所有子View,但如果在View中使用,那么移动的将是View的内容.
将scrollBy中的参数dx和dy设置为正数,那么content将向坐标轴负方向移动;如果将scrollBy中的参数dx和dy设置为负数,那么content将向坐标轴正方向移动.
因此,要实现跟随手指移动而滑动的效果,就必须将偏移量改为负值:
@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; }
在使用绝对坐标时,也可以通过使用scroll方法来实现.
2.5 Scroller
使用Scroller对象,需要三个步骤:1.初始化Scroller
首先,通过它的构造方法来创建一个Scroller对象:
//初始化Scroller mScroller=new Scroller(context);
2.重写computeScroll()方法,实现模拟滑动
重写computeScroll()方法,它是Scroller类的核心,系统在绘制View的时候会在draw()方法中调用该方法:
@Override public void computeScroll() { super.computeScroll(); // 判断Scroller是否执行完毕 if (mScroller.computeScrollOffset()) { ((View) getParent()).scrollTo( mScroller.getCurrX(), mScroller.getCurrY()); // 通过重绘来不断调用computeScroll invalidate(); } }
Scroller类提供了computeScrollOffset()方法来判断是否完成了整个滑动,通过getCurrX(),getCurrY()方法来获得当前的滑动坐标,注意invalidate()方法,因为只能在computeScroll()方法中获取模拟过程中的scrollX和scrollY坐标.但computeScroll()方法是不会自动调用的,只能通过invalidate()->draw()->computeScroll()来间接调用computeScroll()方法,所有需要调用invalidate()方法,实现循环获取scrollX和scrollY的目的,而当模拟过程结束后,scroller.computeScrollOffset()方法会返回false,从而中断循环,完成整个平滑移动过程.
3.startScroll开启模拟过程
在需要使用平滑移动的事件中,使用Scroller类的startScroll()方法来开启平滑移动过程.
startScroll()方法具有两个重载方法:
public void startScroll(int startX,int startY,int dx,int dy,int duration) public void startScroll(int startX,int startY,int dx,int dy)
它们的区别在于:是否具有指定的持续时长.其它参数分别为起始坐标与偏移量.
在获取坐标时,使用getScrollX()和getScrollY()方法来获取父视图中content所滑动到的点的坐标.注意正负值,与scrollBy,scrollTo的情况相同.
例子:
演示一下如何使用Scroller类实现平滑移动.在这个实例中,让子View跟随手指的滑动而滑动,但是在手指离开屏幕是,让子View平滑的移动到初始位置,即屏幕左上角:
public class DragView extends View { private int lastX; private int lastY; private Scroller mScroller; public DragView(Context context) { super(context); ininView(context); } public DragView(Context context, AttributeSet attrs) { super(context, attrs); ininView(context); } public DragView(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); } @Override public void computeScroll() { super.computeScroll(); // 判断Scroller是否执行完毕 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: // 手指离开时,执行滑动过程 View viewGroup = ((View) getParent()); mScroller.startScroll( viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY()); invalidate(); break; } return true; } }
在startScroll()方法中,获取子View移动的距离–getScrollX(),getScrollY(),并将偏移量设置为其相反数,从而将子View滑动到原位置.注意invalidate()方法,需要使用这个方法来通知View进行重绘,从而来调用computeScroll()的模拟过程.
2.6 ViewDragHelper
ViewDragHelper可以实现各种不同的滑动 拖放需求.使用方法:
初始化ViewDragHelper
首先,自然是需要初始化ViewDragHelper.ViewDragHelper通常定义在一个ViewGroup的内部,并通过其静态工厂方法进行初始化.
mViewDragHelper=ViewDragHelper.create(this,callback);
第一个参数是要监听的View,通常需要是一个ViewGroup,即parentView;第二个参数是一个Callback回调,这个回调是整个ViewDragHelper核心.
拦截事件
接下来,重写事件拦截方法,将事件传递给ViewDragHelper进行处理:
@Override public boolean onInterceptTouchEvent(MotionEvent ev) { return mViewDragHelper.shouldInterceptTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent event) { //将触摸事件传递给ViewDragHelper,此操作必不可少 mViewDragHelper.processTouchEvent(event); return true; }
处理computeScroll()
因为ViewDragHelp内部是通过Scroller来实现平滑移动的,所以需要实现处理computeScroll().
@Override public void computeScroll() { if (mViewDragHelper.continueSettling(true)) { ViewCompat.postInvalidateOnAnimation(this); } }
处理回调Callback
下面就是最关键的Callback实现:
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { // 何时开始检测触摸事件 @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); } // 处理垂直滑动 @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 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); } } };
实现侧滑菜单的完整例子:
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() {
mViewDragHelper = ViewDragHelper.create(this, callback);
}
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() { // 何时开始检测触摸事件 @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); } // 处理垂直滑动 @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 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);
}
}
}
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories