自定义View上下滑动、Title栏渐变色、下拉图片放大

利用ViewDragHelper做的一个简单的自定义View,该自定义View分成上下两个部分,下面部分响应滑动事件,上面部分不响应滑动事件,也就是我触摸并滑动下面部分时,下面部分会滑动,并且上面部分会伴随滑动,但是触摸上面部分时上下两部分都不做任何响应。实现的功能有:

1)向上滑动时,下面部分会有覆盖上面部分的效果;

2)向上滑动时,上面部分逐渐透明,状态栏部分逐渐透明;

3)向上滑动不到一半并且松手,则呈现展开状态,超过一半并松手,呈现关闭状态;

4)完全展开时,再向下滑动,上面部分会放大。

效果图如下:


package com.jj.investigation.customebehavior.view;

import android.content.Context;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.InflateException;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.FrameLayout;
import android.widget.ListView;
import android.widget.ScrollView;

/**
 * 垂直滑动的效果
 * Created by ${R.js} on 2018/3/7.
 */

public class VerticalScrollView extends FrameLayout {

    private ViewDragHelper mDragHelper;
    // menu布局
    private View mMenuView;
    // 内容布局
    private View mMainView;
    // 内容布局的孩子
    private View mChildView;
    // 拖动的最大范围
    private int mDragRange = 400;
    // 本类自定义View是否要拦截事件(不是最终的,看onInterceptTouchEvent方法)
    private boolean intercept = false;
    // 滑动距离的监听
    private OnScrollListener onScrollListener;
    /**
     * 是否放大上面menuView:如果为false,则向下滑动时,最多滑动menuView的高度,滑动的距离等于这个高度时,
     * 不能再向下滑动,如果为true,则滑动的距离等于这个高度时,可以再向下滑动,并且向下滑动时放大menuView     */
    private boolean mIsScaleMenuView = true;


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

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

    public VerticalScrollView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        mDragHelper = ViewDragHelper.create(this, callback);
    }

    /**
     * 布局inflate完毕后调用,此时获取到两个子View     * 这里判断了子View是否是可以滑动控件:ScrollViewListView(没有判断GridViewRecyclerView),
     * 判断的目的是为了处理拦截事件,不处理拦截事件也是可以的,但是如果子View是滑动控件,则一些滑动的效果不是
     * 想要的,所以判断了子View是否是滑动控件,或者子view的子view是否是滑动控件
     */
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        if (getChildCount() != 2)
            throw new InflateException("上下滑动布局必须有且仅有两个直接的子View");
        mMenuView = getChildAt(0);
        mMainView = getChildAt(1);
        if (mMainView instanceof ListView) {
            lvOnScrollListener((ListView) mMainView);
            return;
        }

        if (mMainView instanceof ScrollView) {
            svOnScrollViewListener((MyScrollView) mMainView);
            return;
        }

        if (mMainView instanceof ViewGroup) {
            mChildView = ((ViewGroup) mMainView).getChildAt(0);
            if (mChildView instanceof ListView) {
                lvOnScrollListener((ListView) mChildView);
                return;
            }
            if (mChildView instanceof MyScrollView) {
                svOnScrollViewListener((MyScrollView) mChildView);
            }
        }
    }

    /**
     * ScrollView的滑动监听,如果滑动到顶部需要让intercepttrue
     */
    private void svOnScrollViewListener(MyScrollView scrollView) {
        scrollView.setOnScrollInstanceListener(new MyScrollView.OnScrollInstanceListener() {
            @Override
            public void scrollInstance(int instance) {
                intercept = instance == 0;
            }
        });
    }

    /**
     * ListView的滑动监听
     * 如果滑动到顶部需要让intercepttrue
     */
    private void lvOnScrollListener(ListView listView) {
        listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {}

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
                                 int totalItemCount) {
                intercept = firstVisibleItem == 0 && mChildView.getTop() == 0;
            }
        });
    }

    /**
     * 该方法肯定是在测量完成之后调用,所以可以在该方法中获取子View的宽度.
     * 因为该View的大小是不会发生改变的,所以该方法也就只走一次
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mDragRange = mMenuView.getMeasuredHeight();
        open();
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return intercept && mDragHelper.shouldInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        mDragHelper.processTouchEvent(event);
        return true;
    }

    final ViewDragHelper.Callback callback = new ViewDragHelper.Callback() {

        @Override
        public boolean tryCaptureView(View child, int pointerId) {
            return mMainView == child;
        }

        @Override
        public int clampViewPositionHorizontal(View child, int left, int dx) {
            return 0;
        }

        @Override
        public int getViewVerticalDragRange(View child) {
            return mDragRange;
        }

        /**
         * 限制mainView在垂直方向上滑动的距离
         *
         * @return mainView滑动后将要变成的顶部坐标
         */
        @Override
        public int clampViewPositionVertical(View child, int top, int dy) {
            // 计算mainView的顶部将要变成的坐标值:当前的顶部坐标+滑动的距离
            int newTop = top + dy;
            // 如果超出MDragRange的值后不需要放大上部menuView,则需要限制
            if (!mIsScaleMenuView)
                if (newTop > mDragRange) newTop = mDragRange;
            if (newTop < 0) newTop = 0;
            return newTop;
        }

        /**
         * 控制mainView的滑动,同时控制menuView的伴随滑动
         * mainView的滑动范围是:0 - MDragRange
         * menuView的滑动范围是:-MDragRange / 2 - 0
         */
        @Override
        public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
            super.onViewPositionChanged(changedView, left, top, dx, dy);
            // 如果滑动的范围是在0-MDragRange内,则mainView滑动多少就给top值设置为多少,同时让menuView滑动
            // 的距离设置为mainView的一半。
            if (top <= mDragRange) {
                mMainView.layout(0, top, mMainView.getMeasuredWidth(), mMainView.getBottom());
                mMenuView.layout(0, -(mDragRange - top) / 2, mMenuView.getWidth(), mMenuView.getBottom());
                final float alpha = (float) top / mDragRange;
                mMenuView.setAlpha(alpha);
                if (onScrollListener != null)
                    onScrollListener.onScroll(top, mDragRange, 1 - alpha);
            } else {
                // 如果超出了MDragRange的范围,则判断是否需要让menuView进行放大
                if (!mIsScaleMenuView) return;
                final float scale = (float) top / mMenuView.getHeight();
                mMenuView.setScaleX(scale * scale);
                mMenuView.setScaleY(scale * scale);
                mMainView.layout(0, top, mMainView.getMeasuredWidth(), mMainView.getBottom());
            }
        }

        /**
         * 当手指抬起时执行这个方法
         */
        @Override
        public void onViewReleased(View releasedChild, float xvel, float yvel) {
            super.onViewReleased(releasedChild, xvel, yvel);
            whenFingerUp();
        }
    };

    /**
     * 当手抬起时,判断是要close,还是要open
     */
    private void whenFingerUp() {
        if (mMainView.getTop() < mDragRange / 2) {
            close();
        } else {
            open();
        }
    }

    /**
     * 完全遮盖上面menu部分:
     * 调用了smoothSlideViewTo方法之后,onViewPositionChanged方法也会走,在该方法中同时对两个View进行滑动,
     * 所以只需滑动mMainView即可,没有必要再调用mDragHelper.smoothSlideViewTo(mMenuView, 0, -mDragRange);
     */
    public void close() {
        mDragHelper.smoothSlideViewTo(mMainView, 0, 0);
        // mDragHelper.smoothSlideViewTo(mMenuView, 0, -mDragRange);
        ViewCompat.postInvalidateOnAnimation(VerticalScrollView.this);
    }

    /**
     * 完全打开上面menu部分
     */
    public void open() {
        mDragHelper.smoothSlideViewTo(mMainView, 0, mDragRange);
        ViewCompat.postInvalidateOnAnimation(VerticalScrollView.this);
    }

    /**
     * 设置是否放大menuView
     *
     * @param isScaleMenuView
     */
    public void setIsScaleMenuView(boolean isScaleMenuView) {
        this.mIsScaleMenuView = isScaleMenuView;
    }

    /**
     * 动态设置滑动的范围
     * @param dragRange 默认为顶部menuView的高度
     */
    public void setDragRange(int dragRange) {
        this.mDragRange = dragRange;
    }

    /**
     * invalide是为了引起onDraw回调,onDraw又调用computeScroll     * 所以调用invalidate()是为了调用computeScroll()
     */
    @Override
    public void computeScroll() {
        // 若滚动动画没有结束,即位置发生改变,则刷新新的位置
        if (mDragHelper.continueSettling(true)) {
            ViewCompat.postInvalidateOnAnimation(VerticalScrollView.this);
        }
    }

    /**
     * Main布局垂直滑动距离的监听
     */
    public interface OnScrollListener {
        /**
         * @param instance    滑动的距离
         * @param maxInstance 最大的滑动距离
         * @param alpha       滑动的距离与上面menu布局高度的比值
         */
        void onScroll(int instance, int maxInstance, float alpha);
    }

    public void setOnScrollListener(OnScrollListener onScrollListener) {
        this.onScrollListener = onScrollListener;
    }
}

具体的逻辑都在代码中注明了,需要说明的是在使用ViewDragHelper的时候需要在onInterceptTouchEvent和onTouchEvent中用来处理事件,否则ViewDragHelper的CallBack方法是不会走的。

点击查看源码

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值