Android果冻效果滑动控件

类似于iOS有阻尼效果的滑动控件,一般我们比较亲切地称之为果冻控件

比较常见的地方,如微信里[我]的那个面板模块,即使没有再多的选项,也不会很生硬的不允许用户滑动。

微信是的处理方法是让用户滑动,但最终还是回滚到最初的地方,这样的效果很生动(毕竟成功还是取决于细节)。那么在安卓我们要怎么弄呢。下面为大家介绍一下JellyScrollView,是我继承ScrollView的一个有阻尼的效果的果冻滑动控件。废话少说,看图…

这里写图片描述

(在虚拟机或者真机跑起来是很流畅,可能是录制视频做成gif的时候有点卡顿。)

实现原理:其实只需要重写下它的拦截方法的逻辑就好了,ScrollView的拦截方法onInterceptTouchEvent一般情况下都默认地返回false,表示不拦截该事件。(如果不了解事件机制的同学先补补基础touch事件的分发和消费机制)我们只需要在用户滑动了界面的情况下,拦截下来并移动布局就好了,当用户松开手就恢复布局。很简单

第一步:集成ScrollView重写几个构造方法;
第二步:重写下onFinishInflate方法,获得第一个子view;
第三步:在拦截方法里面处理上面所说的逻辑;

public class JellyScrollView extends ScrollView {

    private View inner;// 子View

    private float y;// 点击时y坐标

    private Rect normal = new Rect();// 矩形(这里只是个形式,只是用于判断是否需要动画.)

    private boolean isCount = false;// 是否开始计算

    private boolean isMoving = false;// 是否开始移动.

    private int top;// 拖动时时高度。

    private int mTouchSlop;//系统最少滑动距离

    public JellyScrollView(Context context) {
        super(context);
    }

    public JellyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public JellyScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /***
     * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆盖了 onFinishInflate
     * 方法,也应该调用父类的方法,使该方法得以执行.
     */
    @Override
    protected void onFinishInflate() {
        if (getChildCount() > 0) {
            inner = getChildAt(0);
        }
    }


    /**拦截事件*/
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (inner != null) {
            int action = ev.getAction();
            switch (action) {
                case MotionEvent.ACTION_DOWN:
                    y = ev.getY();
                    top = 0;
                    break;

                case MotionEvent.ACTION_UP:
                    // 手指松开.
                    isMoving = false;
                    if (isNeedAnimation()) {
                        animation();
                    }
                    break;
                /***
                 * 排除出第一次移动计算,因为第一次无法得知y坐标, 在MotionEvent.ACTION_DOWN中获取不到,
                 * 因为此时是ScrollView的touch事件传递到到了ListView的子item上面.所以从第二次计算开始.
                 * 然而我们也要进行初始化,就是第一次移动的时候让滑动距离归0. 之后记录准确了就正常执行.
                 */
                case MotionEvent.ACTION_MOVE:
                    final float preY = y;// 按下时的y坐标
                    float nowY = ev.getY();// 每时刻y坐标
                    int deltaY = (int) (nowY - preY);// 滑动距离
                    if (!isCount) {
                        deltaY = 0; // 在这里要归0.
                    }

                    if (Math.abs(deltaY) < mTouchSlop && top <= 0)
                        return true;

                    // 当滚动到最上或者最下时就不会再滚动,这时移动布局
                    isNeedMove();

                    if (isMoving) {
                        // 初始化头部矩形
                        if (normal.isEmpty()) {
                            // 保存正常的布局位置
                            normal.set(inner.getLeft(), inner.getTop(), inner.getRight(), inner.getBottom());
                        }

                        // 移动布局
                        inner.layout(inner.getLeft(), inner.getTop() + deltaY / 3, inner.getRight(), inner.getBottom() + deltaY / 3);

                        top += (deltaY / 6);
                    }

                    isCount = true;
                    y = nowY;
                    break;
            }
        }
        return super.onInterceptTouchEvent(ev);
    }


    /***
     * 回缩动画
     */
    public void animation() {
        // 开启移动动画
        TranslateAnimation ta = new TranslateAnimation(0, 0, inner.getTop(), normal.top);
        ta.setDuration(200);
        inner.startAnimation(ta);

        // 设置回到正常的布局位置
        inner.layout(normal.left, normal.top, normal.right, normal.bottom);
        normal.setEmpty();

        // 手指松开要归0.
        isCount = false;
        y = 0;
    }

    // 是否需要开启动画
    public boolean isNeedAnimation() {
        return !normal.isEmpty();
    }

    /***
     * 是否需要移动布局
     * inner.getMeasuredHeight():获取的是控件的总高度
     * getHeight():获取的是屏幕的高度
     *
     * @return
     */
    public void isNeedMove() {
        int offset = inner.getMeasuredHeight() - getHeight();
        int scrollY = getScrollY();
        // scrollY == 0是顶部
        // scrollY == offset是底部
        if (scrollY == 0 || scrollY == offset) {
            isMoving = true;
        }
    }
}

然后在布局里面在最外层就使用我们的JellyScrollView
(为了方便展示,我只是大概写了一部分布局代码)

<?xml version="1.0" encoding="utf-8"?>
<cn.ichengxi.fang.view.JellyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg"
    android:scrollbars="none">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">
        <TextView
            android:id="@+id/personal_setting_txt"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:alpha="0.8"
            android:gravity="center_vertical"
            android:text="设置"
            android:textColor="@android:color/black" />

    </LinearLayout>

</cn.ichengxi.fang.view.JellyScrollView>

然后就可以很优雅的实现了果冻控件的效果啦。。。成功的同学记得回来点赞

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值