之前在做项目的时候碰到过一个问题,废话不多说,先看我自己录制的一个效果

刚开始是想着百度搜索去看有没有合适的工具类,果不其然,搜到了很多,这里我贴上两个博客地址,大家可以先去看他们的再来看我的

https://blog.csdn.net/jky_yihuangxing/article/details/51981888

https://blog.csdn.net/zhangjg_blog/article/details/19193671

说的都很详细

可是应用到项目中会发现若我们的界面停留在顶部或底部,你再次去上拉下拉松手时就会导致回弹一部分,这是因为本身的效果就是可弹性滑动,还可回归原本位置。导致这一问题很影响用户体验,我就在此基础之上修改了他们的代码整合了起来。先贴出来如下

package com.sam.im.fddmall.view;import android.content.Context;import android.graphics.Canvas;import android.graphics.LinearGradient;import android.graphics.Paint;import android.graphics.Rect;import android.graphics.Shader;import android.util.AttributeSet;import android.view.MotionEvent;import android.view.View;import android.view.animation.TranslateAnimation;import android.widget.ScrollView;import com.sam.im.fddmall.R;import static com.sam.im.fddmall.app.App.selectedColor;import static com.sam.im.fddmall.mall.activity.MallCircleDetailActivity.linearHegiht;import static com.sam.im.fddmall.mall.fragment.MallCircleFragment.linearHegiht_Circle;import static com.sam.im.fddmall.mall.fragments.HomePageFragment.linearHegiht_Home;import static com.sam.im.fddmall.uis.fragments.SetTwoFragment.linearHegiht_setTwo;/** * 带滚动监听的scrollview,可以计算向上滑动的距离来判断标题栏的透明度颜色 */public class ObservableScrollView extends ScrollView {    private static final String TAG = "ObservableScrollView";    // 当滑动的距离最小到10,才认为是上下滑动    private static final float MINI_DISTANCE = 10.0f;    //移动因子, 是一个百分比, 比如手指移动了100px, 那么View就只移动50px    //目的是达到一个延迟的效果    private static final float MOVE_FACTOR = 0.5f;    //松开手指后, 界面回到正常位置需要的动画时间    private static final int ANIM_TIME = 200;    //ScrollView的子View, 也是ScrollView的唯一一个子View    private View contentView;    //手指按下时的Y值, 用于在移动时计算移动距离    //如果按下时不能上拉和下拉, 会在手指移动时更新为当前手指的Y值    private float startY;    //用于记录正常的布局位置    private Rect originalRect = new Rect();    //手指按下时记录是否可以继续下拉    private boolean canPullDown = false;    //手指按下时记录是否可以继续上拉    private boolean canPullUp = false;    //在手指滑动的过程中记录是否移动了布局    private boolean isMoved = false;    //用来判断背景颜色的处理    private int deltaY = 0;    private int colorStart = 0;//下拉    private int colorEnd = 0;//上拉    public ObservableScrollView(Context context) {        super(context);    }    public ObservableScrollView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public ObservableScrollView(Context context, AttributeSet attrs, int defStyle) {        super(context, attrs, defStyle);    }    @Override    protected void onFinishInflate() {        super.onFinishInflate();        if (getChildCount() > 0) {            contentView = getChildAt(0);        }    }    @Override    protected void onLayout(boolean changed, int l, int t, int r, int b) {        super.onLayout(changed, l, t, r, b);        if (contentView == null) return;        //ScrollView中的唯一子控件的位置信息, 这个位置信息在整个控件的生命周期中保持不变        originalRect.set(contentView.getLeft(), contentView.getTop(), contentView                .getRight(), contentView.getBottom());    }    @Override    protected void onDraw(Canvas canvas) {        super.onDraw(canvas);        //获取View的宽高        int width = getWidth();        int height = getHeight();//        Log.e(TAG, "ObservableScrollView:拿到首页的高度 " + linearHegiht_Home);//        Log.e(TAG, "ObservableScrollView:拿到圈子的高度 " + linearHegiht_Circle);//        Log.e(TAG, "ObservableScrollView:拿到子我的高度 " + linearHegiht_setTwo);//        Log.e(TAG, "onDraw:拿到ScrollView的高度 " + height);        if (selectedColor == 1) {//首页界面下拉拉伸的背景颜色            height = resultHeight(linearHegiht_Home, height);            colorStart = getResources().getColor(R.color.background_white);//下拉            colorEnd = getResources().getColor(R.color.background_white);//上拉        }else if (selectedColor == 2) {//圈子界面下拉拉伸的背景颜色            height = resultHeight(linearHegiht_Circle,height);            colorStart = getResources().getColor(R.color.background_white);//下拉            colorEnd = getResources().getColor(R.color.background_white);//上拉        } else if (selectedColor == 5) {//我的界面下拉拉伸的背景颜色            height = resultHeight(linearHegiht_setTwo,height);            colorStart = getResources().getColor(R.color.colorPrimary);//下拉            colorEnd = getResources().getColor(R.color.background_white);//上拉        } else {//其他位置的,通常是出fragment的,但目前只有一个activity在使用,临时不做判断,直接归到else中    MallCircleDetailActivity.class            height = resultHeight(linearHegiht, height);            colorStart = getResources().getColor(R.color.colorPrimary);//下拉            colorEnd = getResources().getColor(R.color.background_white);//上拉        }//        Log.e(TAG, "onDraw:拿到最后要使用的高度 " + height);//float[] position = {0f, 0.7f, 1.0f};//Log.e(TAG, "onDraw:开始打印y轴的数值 " + deltaY);        //通过下拉来判断,大于0为下拉,小于0上拉        if (deltaY > 0) {            //LinearGradient线性渐变,这种设置的比较细腻,可设置多种颜色渐变            Paint paint = new Paint();            LinearGradient backGradient = new LinearGradient(0, 0, 0, height, new int[]{colorStart, colorStart}, null, Shader.TileMode.CLAMP);            paint.setShader(backGradient);            canvas.drawRect(0, 0, width, height, paint);        } else {            //LinearGradient线性渐变,这种设置的比较细腻,可设置多种颜色渐变            Paint paint = new Paint();            LinearGradient backGradient = new LinearGradient(0, 0, 0, height, new int[]{colorEnd, colorEnd}, null, Shader.TileMode.CLAMP);            paint.setShader(backGradient);            canvas.drawRect(0, 0, width, height, paint);        }    }    private int resultHeight(int linearHegiht, int height) {        if (linearHegiht < height) {            height = linearHegiht;        } else {            height = getHeight();        }        return height;    }    /**     * 在触摸事件中, 处理上拉和下拉的逻辑     */    @Override    public boolean dispatchTouchEvent(MotionEvent ev) {        if (contentView == null) {            return super.dispatchTouchEvent(ev);        }        int action = ev.getAction();        switch (action) {            case MotionEvent.ACTION_DOWN:                //判断是否可以上拉和下拉                canPullDown = isCanPullDown();                canPullUp = isCanPullUp();                //记录按下时的Y值                startY = ev.getY();                break;            case MotionEvent.ACTION_MOVE:                //在移动的过程中, 既没有滚动到可以上拉的程度, 也没有滚动到可以下拉的程度                if (!canPullDown && !canPullUp) {                    startY = ev.getY();                    canPullDown = isCanPullDown();                    canPullUp = isCanPullUp();                    break;                }                //计算手指移动的距离                float nowY = ev.getY();                //纪录下滑动时Y,当滑动的距离小于MINI_DISTANCE 的时候则不认为是滑动                if (Math.abs(nowY - startY) < MINI_DISTANCE)                    break;                deltaY = (int) (nowY - startY);                boolean shouldMove = isSingleSliping(false, deltaY);                if (shouldMove) {                    //计算偏移量                    int offset = (int) (deltaY * MOVE_FACTOR);                    //写这段代码的目的 1.解决上拉下拉的回弹动画 2.解决根据上拉下拉数值来判断title透明度显示的bug,有时会半透明。在此记录                    //先判断是在顶部、底部还是其他位置在通过下拉来判断,大于0为下拉,小于0上拉                    //isCanPullDown() 顶部 isCanPullUp() 底部                    if(isCanPullDown()){                        //在顶部时,我们想要的上拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,有时候会影响圈子详情页的透明标题显示,并且本身也影响用户体验                        //在顶部时判断上拉不增加偏移量,下拉依旧可以,因为仿ios阻尼效果                        if (deltaY > 0) {                            //随着手指的移动而移动布局                            contentView.layout(originalRect.left, originalRect.top + offset,                                    originalRect.right, originalRect.bottom + offset);                        } else {                            contentView.layout(originalRect.left, originalRect.top,                                    originalRect.right, originalRect.bottom );                        }                    } else if(isCanPullUp()){                        //在底部时,我们想要的下拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,并且本身也影响用户体验                        //在底部时判断下拉不增加偏移量,上拉依旧可以,因为仿ios阻尼效果                        if (deltaY > 0) {                            //随着手指的移动而移动布局                            contentView.layout(originalRect.left, originalRect.top,                                    originalRect.right, originalRect.bottom);                        } else {                            contentView.layout(originalRect.left, originalRect.top + offset,                                    originalRect.right, originalRect.bottom + offset);                        }                    } else {                        //在其他位置时,也就是既不在顶部也不再底部,我们就不用考虑ios的阻尼效果,归置为普通滑动即可                        //不设置这句应该也可以,没尝试.....                        contentView.layout(originalRect.left, originalRect.top,                                originalRect.right, originalRect.bottom);                    }                    isMoved = true;  //记录移动了布局                }                break;            case MotionEvent.ACTION_UP:                if (!isMoved) break;  //如果没有移动布局则跳过执行                // 开启动画                TranslateAnimation anim = new TranslateAnimation(0, 0, contentView.getTop(), originalRect.top);                anim.setDuration(ANIM_TIME);                contentView.startAnimation(anim);                // 设置回到正常的布局位置                contentView.layout(originalRect.left, originalRect.top, originalRect.right, originalRect.bottom);                //将标志位设回false                canPullDown = false;                canPullUp = false;                isMoved = false;                break;            default:                break;        }        return super.dispatchTouchEvent(ev);    }//    /**//     * 是否可以向下拉//     * ScrollView 是否在顶部//     *///    private boolean isCanPullDown() {////        return getScrollY() == 0;////    }//////    /**//     * 是否可以向上滑动//     * ScrollView 是否在底部//     * 

// * 说明:为什么要减去 10dp 主要原因是在UseInfoActivity ScrollView 中子View margin Parent View 10dp 可查看 R.layout.activity_user_info。// */// private boolean isCanPullUp() {//// return contentView.getHeight() <= getHeight() || contentView.getHeight() == getHeight() + getScrollY();//// } /** * 判断是否滚动到顶部 */ private boolean isCanPullDown() { return getScrollY() == 0 || contentView.getHeight() < getHeight() + getScrollY(); } /** * 判断是否滚动到底部 */ private boolean isCanPullUp() { return contentView.getHeight() <= getHeight() + getScrollY(); } /** * 设置是否单向滑动 * * @param isSingleSliping false 手势向上拖动,子View随着拖动而向上,之后不放开点击,在向下滑动使ScroView中的子View可以向下拖动。 * true 子View随着手势可以任意方向反复拖动 * @param distance isSingleSliping 为true 的情况下才有效 * @return 是否应该移动布局 true 应该 false 不滑动布局 */ private boolean isSingleSliping(boolean isSingleSliping, int distance) { if (!isSingleSliping) return canPullDown || canPullUp; else return (canPullDown && distance > 0) //可以下拉, 并且手指向下移动 || (canPullUp && distance < 0) //可以上拉, 并且手指向上移动 || (canPullUp && canPullDown); //既可以上拉也可以下拉(这种情况出现在ScrollView包裹的控件比ScrollView还小) } private ScrollViewListener scrollViewListener = null; public interface ScrollViewListener { void onScrollChanged(ObservableScrollView scrollView, int x, int y, int oldx, int oldy); } public void setScrollViewListener(ScrollViewListener scrollViewListener) { this.scrollViewListener = scrollViewListener; } @Override protected void onScrollChanged(int x, int y, int oldx, int oldy) { super.onScrollChanged(x, y, oldx, oldy); if (scrollViewListener != null) { scrollViewListener.onScrollChanged(this, x, y, oldx, oldy); } }}

这里作一下说明:

onScrollChanged   重写这个方法的原因是我自己的项目中要用到滑动,没有需要的可不用查看与此相关的接口变量等
onDraw  重写绘制这个方法是因为此类我在多处使用,而且多处使用的下拉上拉背景颜色不一致,我在此加的标志位来判断每个类中不同的背景颜色 有需要的话可以咨询我,我在这里不作解释

最重要的就是在触摸事件中,处理上下移动,我们可以这样判断

1.首先判断是在顶部、底部还是其他位置(isCanPullDown() 顶部 isCanPullUp() 底部,这两个方法在代码中都有可以自行查看)

2.在通过上拉还是下拉来判断(deltaY  这个变量即是上下拉的数值,大于0为下拉,小于0上拉,如果看过前两篇文章对此会很熟悉,一看就明白)

(1)开始顶部判断

在顶部时,我们想要的上拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画 ,下拉就没有关系,因为下拉我们本来就想要弹性动画和回弹效果,所以不用去掉偏移量

(2)开始底部判断

在底部时,我们想要的下拉不想去有偏移量,因为偏移量会导致一个简单的回弹动画,相反,在此底部上拉就没有关系,我们依旧不做任何处理

说明:偏移量在代码中是 offset,就是因为有了这个偏移量我们的弹性上下拉和回弹动画才会完成

大家可以结合代码去查看自己需要的部分,也可以私我。都在代码里!

希望能帮助到大家

更多相关文章

  1. android 手机号码运营商判断
  2. Android(安卓)仿Facebook滑动菜单-支持android 2.2
  3. listview所带来的滑动冲突
  4. android自带的下拉刷新控件SwipeRefreshLayout
  5. Android(安卓)Viewpager与WebView轮播滑动冲突的解决方案
  6. Android(安卓)中判断一个程序是否为输入法程序
  7. Broadcast监听网络状态,ping判断网络是否可用
  8. android 判断摄像头是否可用(6.0以下 )
  9. Android(安卓)RecyclerView 监听滑动

随机推荐

  1. 网站301跳转问题的探讨和用法,网站做301跳
  2. 维度规约(降维)算法在WEKA中应用
  3. 亚马逊商品销售数据爬虫分析报告
  4. 百度指数是什么意思?其中的数值又代表什么
  5. 时间序列建模三部曲
  6. 二手闲置物品交易数据快照
  7. 百度蜘蛛ip地址大全,百度搜索引擎蜘蛛的IP
  8. 用Rapidminer做文本挖掘的应用:情感分析
  9. 如何爬取百度热榜,百度热榜可以抓取吗
  10. 常用的几款抓包工具_ 常见的4种抓包工具