实现滑动的七种方法
当了解了Android坐标系和触控事件后,我们来学习Android实现滑动的七种方法。
首先准备一个简单的布局:
2.offsetLeftAndRight()与offsetTopAndBottom()
这个方法啊相当于系统提供的一个队左右、上下移动的API的封装,当计算出偏移量后,只需要使用如下代码就可以完成View的重新布局,效果与使用Layout方法一样。
3.LayoutParams
LayoutParams保存了一个View的布局参数,因此可以在程序中通过改变LayoutParams来动态地修改一个布局的位置参数,从而达到改变View位置的效果。
不过需要注意的是,通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型,比如View放在LinearLayout中,那么就使用LinearLayout.LayoutParams。同理如果在RelativeLayout中就要使用RelativeLayout.LayoutParams。
还有一种获取方式,就是通过ViewGroup.MarginLayoutParams,使用这种方法更加的方便,不需要考虑父布局的类型,当然他们的本职都是一样的。最后,这一切的前提是必须要有个父布局,不然系统是无法获取LayoutParams的。
4.scrollTo与scrollBy
在一个View中,系统提供了srollTo和scrollBy两种方式来改变一个View的位置。scrollTo:移动到一个具体的坐标点(x,y);而scrollBy:表示移动的增量dx,dy。
scrollTo、scrollBy方法移动的是View的content,即让View的内容移动了,如果在ViewGroup中使用scrollTo、scrollBy方法,那么移动的将是所有子View,例如TextView,content就是它的文本;ImageView,content就是它的drawable对象。
也就是说我们要用getParent()来使用scrollTo、scrollBy方法,否则,TextView只是偏移它的文本,ImageView只是偏移它的图片。
要注意的是,scrollTo、scrollBy是以Android坐标系移动的,所以要使用我们正常的坐标系,则在x和y加一个负号
5.Scroller
Scroller使用步骤:
1)初始化Scroller
2)重写computeScroll()方法,实现模拟滑动
6.属性动画
属性动画就先搁置一下。
7.ViewDragHelper
通过ViewDragHelper,基本可以实现各种不同的滑动、拖放需求,因此这个方式也是各种滑动解决方案中的终极绝招。ViewDragHelper虽然功能强大,但其使用也是最为复杂的下面是类似QQ滑动侧边栏的布局的实现
1)初始化ViewDragHelper
2)拦截事件
3)处理computeScroll()
4)处理回调Callback
在ViewDragHelper.Callback中,系统还定义了大量的监听事件来帮助我们处理各种事件:
onViewCaptured():这个事件在用户触摸到View后回调
onViewDragStateChanged():这个事件在拖拽状态改变时回调,比如idle、dragging等状态。
当了解了Android坐标系和触控事件后,我们来学习Android实现滑动的七种方法。
首先准备一个简单的布局:
<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"
tools:context="com.itman.scrolldemo.MainActivity" >
<com.itman.scrolldemo.DragView
android:layout_width="100dp"
android:layout_height="100dp"
android:text="#2498e2" />
</LinearLayout>
1.layout方法:
在触摸是就获取触摸点的绝对坐标,然后移动时就计算偏移量,接着把基础的位置加上偏移量,最后重新设置初始坐标。
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DragView extends View {
private int lastX;
private int lastY;
public DragView(Context context) {
super(context);
ininView();
}
public DragView(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
}
// 绝对坐标方式
@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.offsetLeftAndRight()与offsetTopAndBottom()
这个方法啊相当于系统提供的一个队左右、上下移动的API的封装,当计算出偏移量后,只需要使用如下代码就可以完成View的重新布局,效果与使用Layout方法一样。
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
public class DragView1 extends View {
private int lastX;
private int lastY;
public DragView1(Context context) {
super(context);
ininView();
}
public DragView1(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView1(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
}
// 视图坐标方式
@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);
//同时对left和right进行偏移
offsetLeftAndRight(offsetX);
//同时对top和bottom进行偏移
offsetTopAndBottom(offsetY);
break;
}
return true;
}
}
3.LayoutParams
LayoutParams保存了一个View的布局参数,因此可以在程序中通过改变LayoutParams来动态地修改一个布局的位置参数,从而达到改变View位置的效果。
不过需要注意的是,通过getLayoutParams()获取LayoutParams时,需要根据View所在父布局的类型来设置不同的类型,比如View放在LinearLayout中,那么就使用LinearLayout.LayoutParams。同理如果在RelativeLayout中就要使用RelativeLayout.LayoutParams。
还有一种获取方式,就是通过ViewGroup.MarginLayoutParams,使用这种方法更加的方便,不需要考虑父布局的类型,当然他们的本职都是一样的。最后,这一切的前提是必须要有个父布局,不然系统是无法获取LayoutParams的。
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DragView2 extends View {
private int lastX;
private int lastY;
public DragView2(Context context) {
super(context);
ininView();
}
public DragView2(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView2(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
}
@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;
LinearLayout.LayoutParams layoutParams =
(LinearLayout.LayoutParams) getLayoutParams();
// ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft() + offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
}
4.scrollTo与scrollBy
在一个View中,系统提供了srollTo和scrollBy两种方式来改变一个View的位置。scrollTo:移动到一个具体的坐标点(x,y);而scrollBy:表示移动的增量dx,dy。
scrollTo、scrollBy方法移动的是View的content,即让View的内容移动了,如果在ViewGroup中使用scrollTo、scrollBy方法,那么移动的将是所有子View,例如TextView,content就是它的文本;ImageView,content就是它的drawable对象。
也就是说我们要用getParent()来使用scrollTo、scrollBy方法,否则,TextView只是偏移它的文本,ImageView只是偏移它的图片。
要注意的是,scrollTo、scrollBy是以Android坐标系移动的,所以要使用我们正常的坐标系,则在x和y加一个负号
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
public class DragView3 extends View {
private int lastX;
private int lastY;
public DragView3(Context context) {
super(context);
ininView();
}
public DragView3(Context context, AttributeSet attrs) {
super(context, attrs);
ininView();
}
public DragView3(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
ininView();
}
private void ininView() {
setBackgroundColor(Color.BLUE);
}
@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);
// ((View) getParent()).scrollTo(-offsetX, -offsetY);
break;
}
return true;
}
}
5.Scroller
Scroller使用步骤:
1)初始化Scroller
2)重写computeScroll()方法,实现模拟滑动
3)startScroll开启模拟过程
import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Scroller;
public class DragView4 extends View {
private int lastX;
private int lastY;
private Scroller mScroller;
public DragView4(Context context) {
super(context);
ininView(context);
}
public DragView4(Context context, AttributeSet attrs) {
super(context, attrs);
ininView(context);
}
public DragView4(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;
}
}
运行结果:(1-5的运行结果都是差不多的)
6.属性动画
属性动画就先搁置一下。
7.ViewDragHelper
通过ViewDragHelper,基本可以实现各种不同的滑动、拖放需求,因此这个方式也是各种滑动解决方案中的终极绝招。ViewDragHelper虽然功能强大,但其使用也是最为复杂的下面是类似QQ滑动侧边栏的布局的实现
1)初始化ViewDragHelper
2)拦截事件
3)处理computeScroll()
4)处理回调Callback
在ViewDragHelper.Callback中,系统还定义了大量的监听事件来帮助我们处理各种事件:
onViewCaptured():这个事件在用户触摸到View后回调
onViewDragStateChanged():这个事件在拖拽状态改变时回调,比如idle、dragging等状态。
onViewPositionChanged():这个事件在位置改变时回调,常用于滑动时更改scale进行缩放等效果。
代码:
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;
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);
}
}
}
布局:
<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="com.itman.scrolldemo.MainActivity" >
<com.itman.scrolldemo.DragViewGroup
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#fe9920" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Menu" />
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#2498e2" >
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Main" />
</FrameLayout>
</com.itman.scrolldemo.DragViewGroup>
</RelativeLayout>
运行结果(模拟器有点菜,真机上是Menu停留是会出现的):