Android之仿美拍主要菜单滑动反弹效果

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

先说说要实现的效果:

菜单滑动最左边,还可以拖动一定距离,松开手后,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事件,然后,遇到了上面的坑


自定义一个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>
这样修改之后,反弹动画果然舒服多了。

 






  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的jQuery代码,可以实现类似腾讯云的滑动下拉导航菜单效果: HTML代码: ```html <div class="dropdown"> <button class="dropbtn">菜单</button> <div class="dropdown-content"> <a href="#">链接1</a> <a href="#">链接2</a> <a href="#">链接3</a> </div> </div> ``` CSS样式: ```css .dropdown { position: relative; display: inline-block; } .dropdown-content { display: none; position: absolute; z-index: 1; } .dropdown-content a { display: block; text-align: left; } .dropdown:hover .dropdown-content { display: block; } .dropbtn { background-color: #4CAF50; color: white; padding: 16px; font-size: 16px; border: none; cursor: pointer; } .dropbtn:hover { background-color: #3e8e41; } ``` jQuery代码: ```javascript $(document).ready(function() { $(".dropdown").hover(function() { $(this).children(".dropdown-content").slideDown("fast"); }, function() { $(this).children(".dropdown-content").slideUp("fast"); }); }); ``` 代码解释: - 首先,在HTML中创建一个包含菜单按钮和下拉内容的元素。 - 用CSS样式设置菜单按钮和下拉内容的样式。 - 使用jQuery代码设置当鼠标悬停在菜单按钮上时,下拉内容被显示出来。当鼠标移开时,下拉内容又被隐藏起来。具体实现是通过jQuery的`hover()`方法来完成的,方法的第一个参数是当鼠标进入时要执行的函数,第二个参数是当鼠标移出时要执行的函数。在这里,我们使用`slideDown()`和`slideUp()`方法来实现下拉和隐藏的效果,其中`"fast"`表示动画的速度,也可以使用其他值或者自定义速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值