本文主要记录一些零碎的东西

先说说要实现的效果:

菜单滑动最左边,还可以拖动一定距离,松开手后,view自动反弹会原位置

主要的坑:控制滑动的view响应touch事件,里面的子view无法响应click事件

左右滑动很多可以实现,最简单是 HorizontalScrollView,下面这个布局就可以实现上下左右滑动


<!-- 上下左右滑动 -->
        <ScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            
            <HorizontalScrollView
                android:layout_width="match_parent"
                android:layout_height="match_parent">
                
                <View
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"/>
                
            </HorizontalScrollView>
            
        </ScrollView>

我一开始是直接在activity里监听view的touch事件,然后,遇到了上面的坑

Android CoordinatorLayout滑动到首位_ide

自定义一个view,处理touch事件,View的移动使用

import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;
import android.widget.Scroller;


/**
 * <p>Description: 滑动到边界,反弹效果的 ScrollView </p>
 * Created by slack on 2016/10/18 18:37 .
 */
public class BoundScrollView extends HorizontalScrollView {

    private static final String TAG = "BoundScrollView";

    private static final int DEFAULT_MOVE = 30; // 每次移动距离
    private static final long DELAY_DURATION = 10L; // 默认延时时间

    private View innerView;// 子View
    private int downX, tempX, moveX;
    private boolean isFirstTouch;

    public BoundScrollView(Context context) {
        this(context, null);
    }

    public BoundScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BoundScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        isFirstTouch = true;
        // 取消滑动到顶部或底部时边缘的黄色或蓝色底纹
        if (Build.VERSION.SDK_INT >= 9) {
            this.setOverScrollMode(View.OVER_SCROLL_NEVER);
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            innerView = getChildAt(0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (innerView != null) {
            handleTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

    /*
    *  这是因为ACTION_DOWN和子View的OnClick有冲突,如果touch点在有click事件的view上ACTION_DOWN进入不了
    * */
    private void handleTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "ACTION_DOWN...");
                break;

            case MotionEvent.ACTION_MOVE:

                if (isFirstTouch) {
                    downX = (int) event.getX();
                    moveX = downX;
                    isFirstTouch = false;
                }

                if ((tempX = (int) event.getX() - moveX) != 0) {
                    innerView.scrollBy(-tempX / 2, 0);
//                  innerView.offsetLeftAndRight(tempX); // 都可以实现随手指滑动效果
                }
                moveX = (int) event.getX();

//                Log.i(TAG, "ACTION_MOVE..." + tempX+ " , " + innerView.getScrollX());
                break;

            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
                isFirstTouch = true;
                // 这里需要处理反弹回去
                if(innerView.getScrollX() != 0) {
                    innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
                }
//                innerView.scrollBy( -innerView.getScrollX() , 0);
//                innerView.offsetLeftAndRight(downX - (int)event.getX() );
                break;

            default:
                break;
        }
    }


    // 按滑动长度 反弹
    private class BoundTask implements Runnable{

        private int distanceX;

        public BoundTask(int distance) {
            this.distanceX = distance;
        }

        @Override
        public void run() {
//            Log.i(TAG, "BoundTask..." + innerView.getScrollX() + " " + distanceX);
            if(distanceX > 0){
                if(distanceX > DEFAULT_MOVE) {
                    innerView.scrollBy(DEFAULT_MOVE, 0);
                    distanceX -= DEFAULT_MOVE;
                }else {
                    innerView.scrollBy(distanceX, 0);
                    distanceX = 0;
                }
                innerView.postDelayed(this,DELAY_DURATION);
            }else if(distanceX < 0){
                if(distanceX < -DEFAULT_MOVE) {
                    innerView.scrollBy(-DEFAULT_MOVE, 0);
                    distanceX += DEFAULT_MOVE;
                }else {
                    innerView.scrollBy(distanceX, 0);
                    distanceX = 0;
                }
                innerView.postDelayed(this,DELAY_DURATION);
            }
        }
    }

}



上面的代码有些问题,反弹动画是自己写的,感觉有些死板,调整移动距离和时间也感觉怪怪的

,觉得还是使用Google提供好的吧 Scroller,再自定义一个布局

FrameLayoutView

import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.Scroller;

/**
 * <p>Description: innerView 纯粹为实现反弹效果 </p>
 * Created by slack on 2016/10/19 14:45 .
 * * Scroller的基本用法其实还是比较简单的,主要可以分为以下几个步骤:
 * 1. 创建Scroller的实例
 * 2. 调用startScroll()方法来初始化滚动数据并刷新界面
 * 3. 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
 */
public class FrameLayoutView extends FrameLayout {

    private Scroller mScroller;

    public FrameLayoutView(Context context) {
        this(context,null);
    }

    public FrameLayoutView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public FrameLayoutView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mScroller = new Scroller(context);
    }

    @Override
    public void computeScroll() {
        // 重写computeScroll()方法,并在其内部完成平滑滚动的逻辑
        if ( mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }


    public void startScroll(int startX, int startY, int dx, int dy, int duration){
        mScroller.startScroll(startX,  startY,  dx,  dy,  duration);
        invalidate();
    }

}




BoundScrollView


handleTouchEvent 里 case MotionEvent.ACTION_UP:修改


case MotionEvent.ACTION_UP:
//                Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
                isFirstTouch = true;
                // 这里需要处理反弹回去
                if(innerView.getScrollX() != 0) {
                    //这里使用了 viewGroup.getScrollX() 和 viewGroup.getScrollY() 作为起始坐标,
                    // ScrollY 和 ScrollX 记录了使用 scrollBy 进行偏移的量
                    //所以使用他们就等于是使用了现在的坐标作为起始坐标,
                    // 目的坐标为他们的负数,就是偏移量为0的位置,也是view在没有移动之前的位置
                    innerView.startScroll(innerView.getScrollX(),
                            innerView.getScrollY(),
                            -innerView.getScrollX(),
                            -innerView.getScrollY(),
                            800);

//                    innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
                }

//                innerView.scrollBy( -innerView.getScrollX() , 0);
//                innerView.offsetLeftAndRight(downX - (int)event.getX() );
                break;

完整的

import android.content.Context;
import android.os.Build;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.HorizontalScrollView;


/**
 * <p>Description: 滑动到边界,反弹效果的 ScrollView </p>
 * Created by slack on 2016/10/18 18:37 .
 */
public class BoundScrollView extends HorizontalScrollView {

    private static final String TAG = "BoundScrollView";

    private static final int DEFAULT_MOVE = 30; // 每次移动距离
    private static final long DELAY_DURATION = 15L; // 默认延时时间

    private FrameLayoutView innerView;// 子View
    private int downX, tempX, moveX;
    private boolean isFirstTouch;


    public BoundScrollView(Context context) {
        this(context, null);
    }

    public BoundScrollView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BoundScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        isFirstTouch = true;
        // 取消滑动到顶部或底部时边缘的黄色或蓝色底纹
        if (Build.VERSION.SDK_INT >= 9) {
            this.setOverScrollMode(View.OVER_SCROLL_NEVER);
        }
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() > 0) {
            innerView = (FrameLayoutView)getChildAt(0);
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (innerView != null) {
            handleTouchEvent(ev);
        }
        return super.onTouchEvent(ev);
    }

    /*
    *  这是因为ACTION_DOWN和子View的OnClick有冲突,如果touch点在有click事件的view上ACTION_DOWN进入不了
    * */
    private void handleTouchEvent(MotionEvent event) {
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
//                Log.i(TAG, "ACTION_DOWN...");
                break;

            case MotionEvent.ACTION_MOVE:

                if (isFirstTouch) {
                    downX = (int) event.getRawX();
                    moveX = downX;
                    isFirstTouch = false;
                }

                if ((tempX = (int) event.getRawX() - moveX) != 0) {
                    innerView.scrollBy(-tempX / 2, 0);
//                  innerView.offsetLeftAndRight(tempX); // 都可以实现随手指滑动效果
                }
                moveX = (int) event.getX();

//                Log.i(TAG, "ACTION_MOVE..." + tempX+ " , " + innerView.getScrollX());
                break;

            case MotionEvent.ACTION_UP:
//                Log.i(TAG, "ACTION_UP..." + innerView.getScrollX());
                isFirstTouch = true;
                // 这里需要处理反弹回去
                if(innerView.getScrollX() != 0) {
                    //这里使用了 viewGroup.getScrollX() 和 viewGroup.getScrollY() 作为起始坐标,
                    // ScrollY 和 ScrollX 记录了使用 scrollBy 进行偏移的量
                    //所以使用他们就等于是使用了现在的坐标作为起始坐标,
                    // 目的坐标为他们的负数,就是偏移量为0的位置,也是view在没有移动之前的位置
                    innerView.startScroll(innerView.getScrollX(),
                            innerView.getScrollY(),
                            -innerView.getScrollX(),
                            -innerView.getScrollY(),
                            800);

//                    innerView.postDelayed(new BoundTask(-innerView.getScrollX()), DELAY_DURATION);
                }

//                innerView.scrollBy( -innerView.getScrollX() , 0);
//                innerView.offsetLeftAndRight(downX - (int)event.getX() );
                break;

            default:
                break;
        }
    }


    // 按滑动长度 反弹  , 自己处理的感觉有些死板
    private class BoundTask implements Runnable{

        private int distanceX;

        public BoundTask(int distance) {
            this.distanceX = distance;
        }

        @Override
        public void run() {
//            Log.i(TAG, "BoundTask..." + innerView.getScrollX() + " " + distanceX);
            if(distanceX > 0){
                if(distanceX > DEFAULT_MOVE) {
                    innerView.scrollBy(DEFAULT_MOVE, 0);
                    distanceX -= DEFAULT_MOVE;
                }else {
                    innerView.scrollBy(distanceX, 0);
                    distanceX = 0;
                }
                innerView.postDelayed(this,DELAY_DURATION);
            }else if(distanceX < 0){
                if(distanceX < -DEFAULT_MOVE) {
                    innerView.scrollBy(-DEFAULT_MOVE, 0);
                    distanceX += DEFAULT_MOVE;
                }else {
                    innerView.scrollBy(distanceX, 0);
                    distanceX = 0;
                }
                innerView.postDelayed(this,DELAY_DURATION);
            }
        }
    }

}



然后界面的布局就变成

<com.benqu.wuta.views.BoundScrollView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center"
            >
            <com.benqu.wuta.views.FrameLayoutView
                android:layout_width="match_parent"
                android:layout_height="match_parent">

                // your view....

            </com.benqu.wuta.views.FrameLayoutView>
           
        </com.benqu.wuta.views.BoundScrollView>

这样修改之后,反弹动画果然舒服多了。