在Android4.x之后,滑动操作大量的出现在Android系统中,滑动的操作方式具有更好的用户体验。所以本章会对Android中
实现滑动效果的方式做一个小归纳。
在介绍滑动方法前,先讲一下Android的坐标系,Android的坐标系分为两种。
1,Android坐标,
以屏幕的左上角为坐标0点,0点向右为X轴正方向,0点向下为Y轴的正方向。在触摸事件中event.getRawX()与event.getRawY()可一个获得
触点的X坐标值与Y轴坐标值。
2,视图坐标
以父控件的左上角为坐标0点,0点向右为X轴的正方向,0点向下为Y轴的正方向。在触摸事件中event.getX(),与event.getY()可以获得在视图坐标中
X轴坐标与Y轴坐标。
在Android中系统提供了非常多的方法来获取坐标值和相对位置,我刚学的时候常常弄混。我就画了张图,来理清这些问题。
View提供的获取坐标的方法。
getLeft():view本身左边到父控件左边的距离。
getRight():view本身右边到父控件的左边的距离。
getTop():view本身顶部到父控件顶部的距离。
getBottom():view本身底部到父控件顶部的距离。
MotionEvent提供的触点获取坐标的的方法。
getX(),视图坐标X的坐标
getY(),视图坐标Y轴坐标。
getRawX(),Android坐标系X轴坐标。
getRawY(),Android坐标系Y轴坐标。
讲完坐标系后,我们正式归纳scroll滑动的方法,这里我们就以实现View跟随手指触摸的滑动为例。
在计算手指偏移的时候我们有两种方法。源于两种坐标系的选择(Android坐标系,视图坐标系);
视图坐标
@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;//X偏移
int offsetY = y - lastY;//Y偏移
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
Android坐标
@Override
public boolean onTouchEvent(MotionEvent event) {
//android坐标
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;
layout(getLeft() + offsetX, getTop() + offsetY,getRight() + offsetX, getBottom() + offsetY);
lastX = rawX;
lastY = rawY;
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
一,Layout方法。
调用Layout方法对自己重新布局。达到移动的效果。
layout(getLeft() + offsetX, getTop() + offsetY,getRight() + offsetX, getBottom() + offsetY);
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
LayoutParams这个类相信大家都不陌生,这个类是View(子视图)向ViewGroup(父视图)传达自己意愿的方式,是ViewGroup的内部类。
不同ViewGroup有不同的LayoutParams的实现类。所以这个使用比较基础的MarginLayoutParams。
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + offsetX;
lp.topMargin = getTop() + offsetY;
setLayoutParams(lp);
scrollTo(x,y)标示移动到(x,y)这个坐标。
scrollBy (dx,dy),dx,dy表示X轴与Y轴方向上的偏移量。
同时scroll&移动的并不是View本身,移动的是View的内容,ViewGroup使用移动的是所有的子View。
所以个要想View有滑动效果。我应该调用的其父控件(getParent())的scrol$方法。然后再想想相对运动。所以代码如下。
((View)getParent()).scrollBy(-offsetX, -offsetY);
((View)getParent()).scrollTo(((View)getParent()).getScrollX() -offsetX,((View)getParent()).getScrollY() -offsetY);
大家看到这个类的名称就知道,它和scrollTo与scrollBy方法羁绊很深。其实功能是一样的。不多它可以定义滑动的时长,而ScrollTo与scrollBy
方法几乎都是一瞬间完成的。
Scroller的使用步骤。
1,初始化:
mScroller = new Scroller(context);
@Override
public void computeScroll() {
super.computeScroll();
if (mScroller.computeScrollOffset()) {
((View) getParent()).scrollTo(
mScroller.getCurrX(),
mScroller.getCurrY()
);
//通过重绘制来不停的调用computeScroll
invalidate();
}
}
case MotionEvent.ACTION_UP:
View viewGroup = (View) getParent();
mScroller.startScroll(viewGroup.getScrollX(), viewGroup.getScrollY(), -viewGroup.getScrollX(), -viewGroup.getScrollY(), 3000);
invalidate();
break;
属性动画
7,ViewDragHelper方法。
ViewDragHelper是所有滑动方法中最复杂的,但是也是功能最强大的。下面是ViewDragHelper的使用步骤:
1,ViewDragHelper的初始化:
//第一步
private void initView() {
mViewDragHelper = ViewDragHelper.create(this, mCallback);
}
2,处理事件拦截:
//第二步
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
3,重写computeScroll()方法。
//第三步
@Override
public void computeScroll() {
//super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
@Override
public boolean tryCaptureView(View child, int pointerId) {
//开始滑动检测的条件,返回true开始检测,即指定跟随手指滑动的View
return child == mMainView;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;//通常只要这样写MainView在水平方向就能滑动了
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;//默认返回0,不能滑动。
}
package com.example.songbinwang.littledemo.view;
import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.widget.FrameLayout;
/**
* Created by songbinwang on 2016/6/2.
*/
public class MyDrawerLayout extends FrameLayout {
View mMenuView, mMainView;
ViewDragHelper mViewDragHelper;
private int mMenuWidth;
//第四步
ViewDragHelper.Callback mCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
//开始滑动检测的条件,返回true开始检测
return child == mMainView;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
//通常只要return left就可以实现滑动。
//return left;
//这里我们复杂一点,控制其滑动的范围为(leftPadding, leftPadding+mMenuView.Width)
int newLeft = Math.min(Math.max(getPaddingLeft(), left), getPaddingLeft() + mMenuWidth);
return newLeft;
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
/**
* 用户手指离开时回调。
* @param releasedChild
* @param xvel
* @param yvel
*/
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (mMainView.getLeft() > mMenuWidth / 2) {
//滑到一半时展开菜单
mViewDragHelper.smoothSlideViewTo(mMainView, mMenuWidth, 0);
ViewCompat.postInvalidateOnAnimation(MyDrawerLayout.this);
} else {
//未滑到一半时,关闭菜单
mViewDragHelper.smoothSlideViewTo(mMainView, 0, 0);
ViewCompat.postInvalidateOnAnimation(MyDrawerLayout.this);
}
}
/**
* 用户触摸到MainView时回调
* @param capturedChild
* @param activePointerId
*/
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
super.onViewCaptured(capturedChild, activePointerId);
}
/**
* 滑动状态改变时调用
* @param state
*/
@Override
public void onViewDragStateChanged(int state) {
super.onViewDragStateChanged(state);
}
/**
* 在位置改变时回调,通常用于滑动时进行缩放效果
* @param changedView
* @param left
* @param top
* @param dx
* @param dy
*/
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
};
public MyDrawerLayout(Context context) {
super(context);
initView();
}
public MyDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public MyDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView();
}
public MyDrawerLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initView();
}
//第一步
private void initView() {
mViewDragHelper = ViewDragHelper.create(this, mCallback);
}
@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);
mMenuWidth = mMenuView.getMeasuredWidth();
}
//第二步
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//将触摸事件传递给ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
//第三步
@Override
public void computeScroll() {
//super.computeScroll();
if (mViewDragHelper.continueSettling(true)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
}