Android中实现滑动(拖动)的几种方法,玩转SlideMenu
2015-11-15 00:26
489 查看
潜水已久,看了CSDN上很多大牛的博客,学了不少东西,很钦佩这种无私奉献共享的精神。
自己平时有些笔记的习惯,虽然都是一些很基础的东西,最近安定了下来,整理一下复习复习,毕竟自己一个马大哈来着。。。各种健忘
滑动一个View,改变其当前所处的位置,这个在我们APP开发中实现常用的,例如侧滑菜单,购物车的拖动等等,所以学习一下是非常有必要的
CSDN客户端上也有一个侧滑的效果
![](http://img.blog.csdn.net/20151114231619452?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
在学习如何实现滑动之前,首先要对Android中窗口坐标体系有一个大概的了解
Android坐标系:
这个没什么好说的,已屏幕最左上角的顶点作为Android坐标系的原点,往右是X轴的正方向,往下是Y轴的正方向
视图坐标系:
描述了子View与父容器之前的位置关系,在这里,原点就不再是屏幕的最上角了,而是父视图左上角为原点
![](http://img.blog.csdn.net/20151114231809185?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
MotionEvent对象:封装了触控事件中一些事件常量和常用的坐标方法
事件常量:
ACTION_DOWN 单点按下动作
ACTION_UP 单点离开动作
ACTION_MOVE 触摸移动动作
ACTION_ACTION_OUTSIDE 触摸动作超出边界
ACTION_POINTER_DOWN 多点触摸按下动作
ACTION_CANCEL 触摸动作取消
ACTION_POINTER_UP 多点离开动作
此外,还提供了很多方法来获取相应的坐标值,基本上就是通过这些坐标值来实现滑动的效果
为了防止遗忘也便于以后查询,画了个图来加深下记忆
![](http://img.blog.csdn.net/20151114232257808?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
View提供获取坐标的方法:
getTop:View 顶部 到父容器 定边的距离
getLeft:View 左边 到父容器 左边的距离
getRight:View 右边 到父容器 左边的距离(将参考点想成父容器的左边,即父容器到该View右边的距离)
etBottom:View 底部 到父容器 顶边的距离
MotionEvent提供的方法:
getX:点击位置 到View 左边的距离(视图坐标)
getY:点击位置 到View 顶部的距离
getRawX:点击位置 到屏幕 左边的距离 (绝对坐标)
getRawY:点击位置 到屏幕 顶边的距离
实现移动:
实现移动的方法有很多,但是基本的原理都是一样的。当触摸View的时候,记录下触摸点的坐标,手指移动的时候,再获取一次坐标,这时将移动时获取到的坐标与前一次坐标做比较,即可得到偏移量,通过这个便宜量修改View的坐标即可实现移动。
下面通过代码来做一个简单的实现
继承View,重写onTouchEvent方法
![](http://img.blog.csdn.net/20151114214204216?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
方法二:
使用LayoutParams
原理:LayoutParams封装了View的所有布局参数,可以通过改变他的leftManager和topManager来实现移动效果
实现的效果是一样的
另外,谷歌还为我们提供了左右,上下移动的API封装,offsetLeftAndRight()和offsetTopAndBottom()
方法三:
ScrollBy和ScrollTo
在View中使用:移动的就是View的内容,(如在Button中,移动的就是button中的text)
在ViewGroup中,移动的将是所有的View(移动可见视图)
在View中使用的效果:
![](http://img.blog.csdn.net/20151114220320440?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
移动的其实就是视图的可视区域
那么什么是可视区域呢?
假如我们要移动的View布局是这样的
使用((View)getParent()).scrollBy(-offsetX,-offsetY)来实现移动,(先不管为什么这里要将计算出来的偏移量设置成负值)
ScorllerTo(by)移动的是可视区域,而不是内容
要知道,Android的坐标系是没有边界的,屏幕显示内容的框也是一个可视区域
![](http://img.blog.csdn.net/20151114232743659?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
是不是挺形象的?到了这里也就能明白scrollBy(-offsetX,-offsetY)为什么要写负值了
本来我们View往右边移动的,偏移量应该是正的,但是scrollBy移动是的可视区域,但是可视区域
向左边移动的,View是往右边的,位置反向
Scroller实现:
前面介绍的几种方法,在完成移动的时候,都是瞬间完成的,看起来非常生硬,让人感觉很粗糙,应该给他加一点动画让他看起来平滑一下
Scroller就是来完成这项工作的。
使用Scroller的步骤
初始化
scroller = new Scrollr(context)
重写computeScroll方法,在使用startScoller后,回不断调用该方法进行重绘
使用StartScroll开发滑动
public void startScroll(int startX,int StartY,int dx,int dy)
参数
startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动
startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动
dx 水平方向滑动的距离,正值会使滚动向左滚动
dy 垂直方向滑动的距离,正值会使滚动向上滚动
下面通过一例子理解一下
![](http://img.blog.csdn.net/20151115002508839?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
综合以上的知识,我们完成可以自己实现一个SlideMenu侧滑控件
代码相当简单,没有什么难点
布局文件
自己平时有些笔记的习惯,虽然都是一些很基础的东西,最近安定了下来,整理一下复习复习,毕竟自己一个马大哈来着。。。各种健忘
滑动一个View,改变其当前所处的位置,这个在我们APP开发中实现常用的,例如侧滑菜单,购物车的拖动等等,所以学习一下是非常有必要的
CSDN客户端上也有一个侧滑的效果
在学习如何实现滑动之前,首先要对Android中窗口坐标体系有一个大概的了解
Android坐标系:
这个没什么好说的,已屏幕最左上角的顶点作为Android坐标系的原点,往右是X轴的正方向,往下是Y轴的正方向
视图坐标系:
描述了子View与父容器之前的位置关系,在这里,原点就不再是屏幕的最上角了,而是父视图左上角为原点
MotionEvent对象:封装了触控事件中一些事件常量和常用的坐标方法
事件常量:
ACTION_DOWN 单点按下动作
ACTION_UP 单点离开动作
ACTION_MOVE 触摸移动动作
ACTION_ACTION_OUTSIDE 触摸动作超出边界
ACTION_POINTER_DOWN 多点触摸按下动作
ACTION_CANCEL 触摸动作取消
ACTION_POINTER_UP 多点离开动作
此外,还提供了很多方法来获取相应的坐标值,基本上就是通过这些坐标值来实现滑动的效果
为了防止遗忘也便于以后查询,画了个图来加深下记忆
View提供获取坐标的方法:
getTop:View 顶部 到父容器 定边的距离
getLeft:View 左边 到父容器 左边的距离
getRight:View 右边 到父容器 左边的距离(将参考点想成父容器的左边,即父容器到该View右边的距离)
etBottom:View 底部 到父容器 顶边的距离
MotionEvent提供的方法:
getX:点击位置 到View 左边的距离(视图坐标)
getY:点击位置 到View 顶部的距离
getRawX:点击位置 到屏幕 左边的距离 (绝对坐标)
getRawY:点击位置 到屏幕 顶边的距离
实现移动:
实现移动的方法有很多,但是基本的原理都是一样的。当触摸View的时候,记录下触摸点的坐标,手指移动的时候,再获取一次坐标,这时将移动时获取到的坐标与前一次坐标做比较,即可得到偏移量,通过这个便宜量修改View的坐标即可实现移动。
下面通过代码来做一个简单的实现
继承View,重写onTouchEvent方法
int downX = 0; int downY = 0; @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //当触摸时,记录坐标 downX = rawX; downY = rawY; break; case MotionEvent.ACTION_MOVE: //移动时,记录坐标,并计算出偏移量 int offsetX = rawX-downX; int offsetY = rawY-downY; //在当前left top bottom right上加上偏移量,得到的就是新的坐标 layout(getLeft()+offsetX,getTop()+offsetY,getRight()+offsetX,getBottom()+offsetY); downX = rawX; downY = rawY; break; } return true; }效果
方法二:
使用LayoutParams
原理:LayoutParams封装了View的所有布局参数,可以通过改变他的leftManager和topManager来实现移动效果
case MotionEvent.ACTION_MOVE: int offsetX = rawX - downX; int offsetY = rawY - downY; ViewGroup.MarginLayoutParams layoutP = (ViewGroup.MarginLayoutParams)getLayoutParams(); layoutP.leftMargin = getLeft()+offsetX; layoutP.topMargin = getTop()+offsetY; setLayoutParams(layoutP); downX = rawX; downY = rawY; break;
实现的效果是一样的
另外,谷歌还为我们提供了左右,上下移动的API封装,offsetLeftAndRight()和offsetTopAndBottom()
方法三:
ScrollBy和ScrollTo
在View中使用:移动的就是View的内容,(如在Button中,移动的就是button中的text)
在ViewGroup中,移动的将是所有的View(移动可见视图)
在View中使用的效果:
移动的其实就是视图的可视区域
那么什么是可视区域呢?
假如我们要移动的View布局是这样的
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" > <com.czp.view.ScrollView android:layout_width="150dp" android:layout_height="150dp" android:background="#f00" > </com.czp.view.ScrollView> </LinearLayout>
使用((View)getParent()).scrollBy(-offsetX,-offsetY)来实现移动,(先不管为什么这里要将计算出来的偏移量设置成负值)
ScorllerTo(by)移动的是可视区域,而不是内容
要知道,Android的坐标系是没有边界的,屏幕显示内容的框也是一个可视区域
是不是挺形象的?到了这里也就能明白scrollBy(-offsetX,-offsetY)为什么要写负值了
本来我们View往右边移动的,偏移量应该是正的,但是scrollBy移动是的可视区域,但是可视区域
向左边移动的,View是往右边的,位置反向
Scroller实现:
前面介绍的几种方法,在完成移动的时候,都是瞬间完成的,看起来非常生硬,让人感觉很粗糙,应该给他加一点动画让他看起来平滑一下
Scroller就是来完成这项工作的。
使用Scroller的步骤
初始化
scroller = new Scrollr(context)
重写computeScroll方法,在使用startScoller后,回不断调用该方法进行重绘
使用StartScroll开发滑动
public void startScroll(int startX,int StartY,int dx,int dy)
参数
startX 水平方向滚动的偏移值,以像素为单位。正值表明滚动将向左滚动
startY 垂直方向滚动的偏移值,以像素为单位。正值表明滚动将向上滚动
dx 水平方向滑动的距离,正值会使滚动向左滚动
dy 垂直方向滑动的距离,正值会使滚动向上滚动
下面通过一例子理解一下
@Override public void computeScroll() { super.computeScroll(); //判断移动是否执行完毕 if(scroller.computeScrollOffset()){ ((View)getParent()).scrollTo(scroller.getCurrX(), scroller.getCurrY()); } } int downX = 0; int downY = 0; @Override public boolean onTouchEvent(MotionEvent event) { int rawX = (int) event.getRawX(); int rawY = (int) event.getRawY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: //当触摸时,记录坐标 downX = rawX; downY = rawY; break; case MotionEvent.ACTION_MOVE: int offsetX = rawX - downX; int offsetY = rawY - downY; ((View)getParent()).scrollBy(-offsetX,-offsetY); downX = rawX; downY = rawY; break; case MotionEvent.ACTION_UP: scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), -scroller.getCurrX(), -scroller.getCurrY(), 400); invalidate(); break; } return true; }
综合以上的知识,我们完成可以自己实现一个SlideMenu侧滑控件
代码相当简单,没有什么难点
public class SlideMenu extends ViewGroup { private View mMainLayout, mMenuLayout; private Scroller scroller; private Context context; public SlideMenu(Context context) { super(context); this.context = context; init(); } public SlideMenu(Context context, AttributeSet attrs) { super(context, attrs); this.context = context; init(); } public SlideMenu(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); this.context = context; init(); } @Override public void computeScroll() { super.computeScroll(); //判断动画是否执行结束 if(scroller.computeScrollOffset()){ scrollTo(scroller.getCurrX(),0); invalidate(); } } public void init(){ scroller = new Scroller(context); } /** * 当一级的子View加载完的时候调用 */ @Override protected void onFinishInflate() { super.onFinishInflate(); mMenuLayout = getChildAt(0); mMainLayout = getChildAt(1); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mMenuLayout.measure(mMenuLayout.getLayoutParams().width,heightMeasureSpec); mMainLayout.measure(widthMeasureSpec,heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { mMenuLayout.layout(-mMenuLayout.getLayoutParams().width,0,0,b); mMainLayout.layout(0,0,r,b); } int downX = 0; @Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_DOWN: downX = (int) event.getRawX(); break; case MotionEvent.ACTION_MOVE: Log.e("DEMO","downX = "+downX +" MOVE == "+event.getRawX()); int offsetX = (int) (event.getRawX() - downX); //当前滚动的位置加要滚动的距离,即得到目标位置 int scrollX = getScrollX()-offsetX; Log.e("DEMO","scrollX == "+scrollX); if(scrollX>=-mMenuLayout.getLayoutParams().width&&scrollX<=0){ scrollTo(scrollX,0); downX = (int) event.getX(); } break; case MotionEvent.ACTION_UP: if(getScrollX()>-mMenuLayout.getLayoutParams().width/2){ closeMenu(); }else{ openMenu(); } break; } return true; } /** * 关闭菜单 */ public void closeMenu(){ // scrollTo(0,0); scroller.startScroll(getScrollX(),0,0-getScrollX(),0,400); invalidate(); } /** * 打开菜单 */ public void openMenu(){ // scrollTo(-mMenuLayout.getLayoutParams().width,0); scroller.startScroll(getScrollX(), 0, -mMenuLayout.getLayoutParams().width-getScrollX(), 0, 400); invalidate(); } }
布局文件
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <com.example.administrator.myapplication.SlideMenu android:layout_width="fill_parent" android:layout_height="fill_parent" > <LinearLayout android:layout_width="150dp" android:background="#f00" android:layout_height="fill_parent"> </LinearLayout> <LinearLayout android:layout_width="fill_parent" android:background="#88dc73" android:layout_height="fill_parent"> </LinearLayout> </com.example.administrator.myapplication.SlideMenu> </RelativeLayout>
相关文章推荐
- 关于android.intent.action.MAIN在manifest里的使用?
- android开发之wheel控件使用详解
- android开发之wheel控件使用详解
- android开发之wheel控件使用详解
- android开发之wheel控件使用详解
- Android 开发获取手机运行内存工具类
- android 下载
- Android调用系统相机拍照及图片保存的Uri方式------菜鸟学习历程
- Android 多线程断点下载project总结
- Android Studio无法利用模板新建Activity与Fragmnet的问题分析与解决
- Android图片旋转,缩放,位移,倾斜,对称完整示例(一)——imageView.setImageMatrix(matrix)和Matrix
- 初学Android项目:开发电子市场<第一天>
- Button设置单框不显示
- android调试时apk可运行,导出签名的apk后, 有些功能闪退,
- rAndroid(11):进度条ProgressBar/SeekBar/RatingBar
- Androidd-XmlPullParser解析XML
- 《Android开发》——1.Activity之间的参数传递
- 最近的一些感想(关于移动客户端开发android,ios)
- android中的MVP模式
- Android的SQlite先天不足:删除 插入后主键不能自动排序 解决(附:SQlite开发的完整demo)