关闭

Android上仿IOS弹性ScrollView

标签: android自定义View伸缩回弹效果
207人阅读 评论(0) 收藏 举报
分类:

这里写代码片#Android上仿IOS弹性ScrollView

滑动的原理:由于在ACTION_MOVE事件中不断获取手指移动的微小的偏移量,这样就将一段距离划分成了N个非常小的偏移量。虽然在每个偏移量里面,通过scrollBy方法进行了瞬间移动,但在整体上却可以获得一个平滑移动效果。

阻尼效果的实现原理:比如你下拉50dp,innerView只移动25dp。从效果上看就好像有个力在阻挠你下拉。

难点:
1. 移动过程中的处理:

MotionEvent.ACTION_MOVE中,在手指移动过程中,ev.getY()会不断获取当前手指的y坐标。

通过不断的计算前一次y的坐标和现在y坐标的偏移量detailY,然后把detailY在传到innerView.layout中,从而实现了下拉的效果。

2.Rect normal = new Rect()的作用?

用于记录innerView的原始位置。具体逻辑:在MotionEvent.ACTION_MOVE中判断normal为空,把innerView的四个点存起来。然后在MotionEvent.ACTION_UP中,即让innerView弹回原位的动画结束后,为innerView提供原始位置,并令normal再次为空。

3.isNeedMove()方法

作用:判断是否触发弹性移动效果

innerView.getMeasuredHeight():获取的是控件的总高度

getHeight():获取的是屏幕的高度

当控件的高度<屏幕的高度时候,getScrollY始终为0.
当控件的高度>屏幕的高度时候,上下滑动innerView时,getScrollY才会有变化的值。

针对第一种情况。只要scrollY == 0就会触发效果。
第二种情况。下拉的触发效果的条件scrollY == 0,上拉触发效果的条件是scrollY == offset

    private boolean isNeedMove() {
        int offset = innerView.getMeasuredHeight() - getHeight();
        int scrollY = getScrollY();
        // 0是顶部,后面那个是底部
        if (scrollY == 0 || scrollY == offset) {
            return true;
        }
        return false;
    }

代码:

import android.content.Context;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.view.MotionEvent;
    import android.view.View;
    import android.view.animation.Animation;
    import android.view.animation.TranslateAnimation;
    import android.widget.ScrollView;

    /**
     * Created by Administrator on 2015/12/13.
     */
    public class MyScrollview extends ScrollView {

        //要操作的布局 孩子View
        private View innerView;
        // 点击时y坐标
        private float y;
        // 矩形(用于保存原innerView的四点坐标.)
        private Rect normal = new Rect();
        //用于记录动画是否结束
        private boolean animationFinish = true;

        public MyScrollview(Context context) {
            super(context, null);
        }

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

        public MyScrollview(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
        }
        /***
         * 根据 XML 生成视图工作完成.该函数在生成视图的最后调用,在所有子视图添加完之后. 即使子类覆* 盖了 onFinishInflate 方法,也应该调用父类的方法,使该方法得以执行.
         */
        @Override
        protected void onFinishInflate() {
            int childCount = getChildCount();
            if (childCount > 0) {
                innerView = getChildAt(0);
            }
        }
        // 首先要保存父类ScrollView的onTouchEvent事件,在这基础上加入回弹的逻辑
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            if (innerView == null) {
                return super.onTouchEvent(ev);
            } else {
                commonTouchEvent(ev);
            }
            return super.onTouchEvent(ev);
        }

        /**
         * 自定义touch事件处理
         *
         * @param ev
         */
        private void commonTouchEvent(MotionEvent ev) {
            if (animationFinish) {
                switch (ev.getAction()) {
                    case MotionEvent.ACTION_DOWN:
                        y = ev.getY();
                        break;
                    case MotionEvent.ACTION_MOVE:
                        float preY = y;
                        float nowY = ev.getY();
                        int detailY = (int) (preY - nowY);
                        y = nowY;
                        //操作view进行拖动detailY的一半
                        if (isNeedMove()) {
                            //布局改变位置之前,记录一下正常状态的位置
                            if (normal.isEmpty()) {
                                normal.set(innerView.getLeft(), innerView.getTop(), innerView.getRight(), innerView.getBottom());
                            }
                            innerView.layout(innerView.getLeft(), innerView.getTop() - detailY / 2, innerView.getRight(), innerView.getBottom() - detailY / 2);
                        }
                        break;
                    case MotionEvent.ACTION_UP:
                        y = 0;
                        //手指松开 布局回滚到原来的位置.
                        if (isNeedAnimation()) {
                            animation();
                        }
                        break;
                }
            }
        }

        private void animation() {
            // 开启移动动画
            TranslateAnimation ta = new TranslateAnimation(0, 0, 0, normal.top - innerView.getTop());
            ta.setDuration(200);
            ta.setAnimationListener(new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                    animationFinish = false;
                }

                @Override
                public void onAnimationEnd(Animation animation) {
                    innerView.clearAnimation();
                    // 设置回到正常的布局位置
                    innerView.layout(normal.left, normal.top, normal.right, normal.bottom);
                    normal.setEmpty();
                    animationFinish = true;
                }

                @Override
                public void onAnimationRepeat(Animation animation) {

                }
            });
            innerView.startAnimation(ta);
        }

        /**
         * 判断是否需要回滚 是否需要开启动画
         *
         * @return
         */
        private boolean isNeedAnimation() {
            return !normal.isEmpty();
        }

        /**
         * 判断是否需要移动 innerView.getMeasuredHeight():获取的是控件的总高度
         * getHeight():获取的是屏幕的高度
         * @return
         */
        private boolean isNeedMove() {
            int offset = innerView.getMeasuredHeight() - getHeight();
            int scrollY = getScrollY();
            // 0是顶部,后面那个是底部
            if (scrollY == 0 || scrollY == offset) {
                return true;
            }
            return false;
        }
    }
1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:2791次
    • 积分:157
    • 等级:
    • 排名:千里之外
    • 原创:13篇
    • 转载:3篇
    • 译文:0篇
    • 评论:0条
    文章存档