《群英传》笔记:
概述:滑动的本质是改变View当前所处的位置,通过不断地改变坐标来实现这一效果。所以滑动方法就是监听用户的触摸事件,根据事件传入的目标,不断地改变View的坐标。
Android:坐标系:在Android中,坐标原点在屏幕的左上角(游戏是左下角),向左为X轴正方向,向下为Y轴正方向。系统提供getLocationOnScreen(intlocation[])这样的方法来获取坐标系中点的位置。在触控事件中使用getRawX()、getRawY()获得Android坐标系坐标 。
视图坐标系:描述子视图在父视图中位置关系。坐标原点是以父视图左上角为坐标原点,通过getX()、getY()获得视图坐标系中的坐标。
触控事件(MotionEvent):
通常情况下,通过onTouchEvent(MotionEvent event)方法中通过event.getAction()方法来获取触控事件的类型,使用switch-case方法进行筛选。
模板代码:
public boolean onTouchEvent(MotionEvent event) {
// 获取当前输入点的X、Y视图坐标
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
// 处理输入的按下事件
break;
case MotionEvent.ACTION_MOVE:
// 处理输入的移动事件
break;
case MotionEvent.ACTION_UP:
// 处理输入的离开事件
break;
}
return true;
}
实现滑动的七种方法:
1、layout方法:
public class ScrollLayout1 extends View{
private int lastX;
private int lastY;
public ScrollLayout1(Context context) {
super(context);
ininView();
}
public ScrollLayout1(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public ScrollLayout1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
// 给View设置背景颜色,便于观察
setBackgroundColor(Color.BLUE);
}
// 视图坐标方式
@Override
public boolean onTouchEvent(MotionEvent event) {
// 获取当前输入点的X、Y视图坐标
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);
break;
}
return true;
}
}
2、offsetLeftAndRight()和offsetTopAndBottom():
// 同时对Left和Right进行偏移
offsetLeftAndRight(offsetX);
// 同时对Top和Bottom进行偏移
offsetTopAndBottom(offsetY);
3、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不需要考虑父布局的类型。
ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
4、scrollTo和scrollBy:
scrollTo(x,y)表示移动到一个具体的坐标点(x,y),而scrollBy(dx,dy)表示移动的增量为dx、dy。
@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;
}
5、Scroller:
scrollTo和scrollBy在事件执行的时候会瞬间平移完成,而Scroller类可以实现平滑移动的效果。原理是利用了人眼的视觉暂留特性,就像翻小人书一样,很多个微小瞬间偏移量集合在一块就可以获得平滑移动的效果。
三步骤:
public class ScrollScroller extends View{
private int lastX;
private int lastY;
private Scroller mScroller;
public ScrollScroller(Context context) {
super(context);
ininView(context);
}
public ScrollScroller(Context context, AttributeSet attrs) {
super(context, attrs);
ininView(context);
}
public ScrollScroller(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);
}
// 第二步:重写computeScroll()方法,实现模拟滑动。核心内容
@Override
public void computeScroll() {
super.computeScroll();
// 判断Scroller是否执行完毕,computeScrollOffset判断是否完成了整个滑动
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;
}
}
6、属性动画
这一块放在Android动画模块里。
7、ViewDragHelper:
基本可以实现各种不同的滑动、拖放需求,功能强大意味着复杂。
package com.android.scroll;
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;
/**
* 滑动侧边栏布局
*
* @author Administrator
*
*/
public class DragViewGroup extends FrameLayout {
private ViewDragHelper mViewDragHelper;
private View mMenuView, mMainView;
private int mWidth;
public DragViewGroup(Context context) {
super(context);
initVew();
}
public DragViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
initVew();
}
public DragViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initVew();
}
// 第一步:初始化ViewDragHelper,定义在ViewGroup的内部,通过其静态工厂方法进行初始化
private void initVew() {
mViewDragHelper = ViewDragHelper.create(this, callback);
}
// 按顺序将子View分别定义成MenuView和MainView
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mMenuView = getChildAt(0);
mMainView = getChildAt(1);
}
// 获得View的宽度
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = mMenuView.getMeasuredWidth();
}
// 第二步:重写拦截事件方法,将事件传递给ViewDragHelper方法
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mViewDragHelper.shouldInterceptTouchEvent(ev);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
// 将触摸事件传递给ViewDragHelper,此操作必不可少
mViewDragHelper.processTouchEvent(event);
return true;
}
// 第三步:处理回调Callback
private ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {
// 判断何时开始检测触摸事件
// 指定parentView中的哪一个子View可以被移动
@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等。Top代表在垂直方向上child移动的距离,
// 而dy则表示比较前一次的增量。
@Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
super.onViewPositionChanged(changedView, left, top, dx, dy);
}
// 处理垂直滑动
public int clampViewPositionVertical(View child, int top, int dy) {
return 0;
}
// 处理水平滑动
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left;
}
// 拖动结束后调用(手指离开屏幕时的操作),方法内部是通过Scroller类来实现的,这是重写computeScroll()方法的原因
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
// 手指抬起后缓慢移动到指定位置
if (mMainView.getLeft() < 350) {
// 关闭菜单
// 相当于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);
}
}
}