Android实现滑动的几种方式演示
一、前言
*近闲来无事,也不知道研究点什么比较好。就买了几本书,加强基础。这编博客是从徐宜生的Android群英传中总结而来的,非常好的一本书,推荐大家入手。
我将用这几种方式,实现一个可拖动的View。
二、 layout方式
我们都知道View绘制流程的主要三个步骤,onMeaure测量 -onLayout摆放-onDraw绘制。关于这方面的博文太多太多,我也就不再多说。
layout可以控制View在父布局中的摆放位置。
我们只需要监听View的事件,然后一直调用layout方法,让View跟随手指的位移即可。
/**
* Created by AItsuki on 2016/3/1.
*/
public class SlideByLayoutView extends View {
private int startX;
private int startY;
public SlideByLayoutView(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getX();
startY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
int endX = (int) event.getX();
int endY = (int) event.getY();
int dx = endX- startX;
int dy = endY – startY;
layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
break;
}
return true;
}
}
三、scrollTo和scrollBy
scrollTo(x,y):移动到具体位置
scrollBy(dx,dy):移动增量,相对于自身位置。
将二中的layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);替换成scrollBy(dx,dy)
会发现不能拖动方块,因为scroll移动的是View的内容,而不是自身。所以在使用scroll的时候,我们应该移动父布局的内容。
/**
* Created by AItsuki on 2016/3/1.
*/
public class SlideByScroll extends View {
private int startX;
private int startY;
public SlideByScroll(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = (int) event.getRawX();
startY = (int) event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
int endX = (int) event.getRawX();
int endY = (int) event.getRawY();
int dx = endX – startX;
int dy = endY – startY;
// layout(getLeft() + dx, getTop() + dy, getRight() + dx, getBottom() + dy);
View parent = (View) getParent();
parent.scrollBy(dx, dy);
startX = endX;
startY = endY;
break;
}
return true;
}
}
注意这里不能使用getX(), 而需要获取getRawX()。因为getX是获取View自身内部的坐标,我们需要移动的是父布局,所以我们应该获取屏幕的坐标。
然后我们再次运行会发现,方块居然是反方向运行的。
但是,为什么会这样呢?
因为scrollBy滚动的手机屏幕,看下面这张图给就可以很好的理解了(黑框请看作手机屏幕)。
解决方式就是:在dx和dy前面加个负号就行了,parent.scrollBy(-dx, -dy);
3.1 Scroller
既然说到了scrollBy和scrollTo,那么这里就不能说一下scroller了。
如果我想实现这么个效果,当我点击方块的时候,让他平滑滚动一段距离,我们应该怎么做呢?
google提供了一个很方便的东西,那就是scroller。虽然属性动画也可以,但是我们现在就来说说scroller。
scroller是什么?
scroller可以模拟一个滚动过程,它有两个方法开启模拟效果。
public void startScroll(int startX, int startY, int dx, int dy)
public void startScroll(int startX, int startY, int dx, int dy, int duration)
其实scroller并没有作用于View,它只是模拟了一个过程而已。
实际滚动其实还需要我们自己调用scrollTo方法。
/**
* Created by AItsuki on 2016/3/1.
*/
public class ScrollerDemo extends View implements View.OnClickListener {
private final Scroller mScroller;
public ScrollerDemo(Context context, AttributeSet attrs) {
super(context, attrs);
mScroller = new Scroller(context);
setOnClickListener(this);
}
// computerScroll方法会在invalidate执行的时候调用。
@Override
public void computeScroll() {
super.computeScroll();
// 判断scroller是否执行完毕
if(mScroller.computeScrollOffset()) {
((View)getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
invalidate();
}
}
@Override
public void onClick(View v) {
View parent = (View) getParent();
mScroller.startScroll(parent.getScrollX(),parent.getScrollY(),-100,-100);
invalidate();
}
}
四、属性动画
/**
* Created by AItsuki on 2016/3/2.
*/
public class SlideByAnimator extends View {
private float startX;
private float startY;
public SlideByAnimator(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = event.getAction();
switch (action) {
case MotionEvent.ACTION_DOWN:
startX = event.getRawX();
startY = event.getRawY();
break;
case MotionEvent.ACTION_MOVE:
float endX = event.getRawX();
float endY = event.getRawY();
float dx = endX – startX;
float dy = endY – startY;
ObjectAnimator.ofFloat(this, “translationX”, getTranslationX() + dx).setDuration(0).start();
ObjectAnimator.ofFloat(this, “translationY”, getTranslationY() + dy).setDuration(0).start();
startX = endX;
startY = endY;
break;
}
return true;
}
}
虽然属性动画会改变控件的位置,并且能获取到点击事件。
但是,实际上View的位置并没有改变,通过getLeft(),getTop()等方法获取的值也未曾改变。
属性动画会把你位移过的距离保存起来,所以可以通过getleft()+getTranslationX()获取到当前View显示的准确位置。
五、ViewDragHelper
ViewDragHelper是一个非常强大的类,可以帮我们处理复杂的拖动逻辑。
Android自带的侧边栏就用到了这个类,在这里我们也用它实现一个简单的侧边栏。
<?xml version=”1.0″ encoding=”utf-8″?>
<com.aitsuki.slidedemo.SimpleDrawerLayout 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”>
<LinearLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”#000″>
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center_vertical”
android:text=”侧边栏”
android:textColor=”#fff”
android:textSize=”30dp” />
</LinearLayout>
<FrameLayout
android:layout_width=”match_parent”
android:layout_height=”match_parent”
android:background=”#fff”>
<TextView
android:layout_width=”wrap_content”
android:layout_height=”wrap_content”
android:layout_gravity=”center”
android:text=”主页面”
android:textColor=”#000″
android:textSize=”30dp” />
</FrameLayout>
</com.aitsuki.slidedemo.SimpleDrawerLayout>
/**
* Created by AItsuki on 2016/3/3.
*/
public class SimpleDrawerLayout extends FrameLayout {
private final ViewDragHelper mDragHelper;
private View mContent;
private int mMenuWidth;
public SimpleDrawerLayout(Context context, AttributeSet attrs) {
super(context, attrs);
mDragHelper = ViewDragHelper.create(this, mCallBack);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mContent = getChildAt(1);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mMenuWidth = w / 3 * 2;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
mDragHelper.processTouchEvent(event);
return true;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
return mDragHelper.shouldInterceptTouchEvent(ev);
}
private ViewDragHelper.Callback mCallBack = new ViewDragHelper.Callback() {
// 触摸到View的时候就会回调这个方法。
// return true表示抓取这个View。
@Override
public boolean tryCaptureView(View child, int pointerId) {
return mContent == child;
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return left > 0 ? left > mMenuWidth ? mMenuWidth : left : 0; // 只能右划出菜单,并且菜单*大宽度为屏幕3分之2
}
@Override
public void onViewReleased(View releasedChild, float xvel, float yvel) {
super.onViewReleased(releasedChild, xvel, yvel);
if (xvel > 300) {
open();
} else if (xvel < -300) {
close();
} else {
if (mContent.getLeft() > mMenuWidth / 2) {
open();
} else {
close();
}
}
}
};
private void close() {
mDragHelper.smoothSlideViewTo(mContent, 0, 0);
invalidate();
}
private void open() {
mDragHelper.smoothSlideViewTo(mContent, mMenuWidth, 0);
invalidate();
}
@Override
public void computeScroll() {
super.computeScroll();
if (mDragHelper.continueSettling(true)) {
invalidate();
}
}
}
————————————————