潜水已久,看了CSDN上很多大牛的博客,学了不少东西,很钦佩这种无私奉献共享的精神。
自己平时有些笔记的习惯,虽然都是一些很基础的东西,最近安定了下来,整理一下复习复习,毕竟自己一个马大哈来着。。。各种健忘
滑动一个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>