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

  1. scrollTo(x,y):移动到具体位置
  2. 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();
        }
    }
}

这里写图片描述

六、写在后面

最近写的博客估计都会比较简单,公司的项目也迭代了好几个版本,每次增加的东西都不错,趁着这个事件强化一下基础。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值