仿oppo手机浏览器首页的滑动布局


周末的午后,我悠然的躺着床上,甚是无聊,拿起我的oppo手机,打开浏览器准备输入我熟悉的xxx .com,
忽然我发现了一个比较好玩的滑动效果,滑动了几次决定我也要来写一个类似的,就这样,这个事情终结了我访问xxx的行为
话不多说直接上图

原效果图

在这里插入图片描述

我大概实现的样子

在这里插入图片描述
gif只能传小于5M的,所以会有掉帧卡顿的表现,其实一点也不卡顿!

这个其实主要是一些事件分发的一些处理

手指在布局上下滑动时,TabLayout和底下的ViewPager一个从上至下滑动,一个从上到下滑动,两个View的终点是Toolbar的bottom(同时到达),在TabLayout和ViewPager滑动时中间的View也稍许上下移动,这边我给到的移动范围是TabLayout的高度,外加一个透明度的变化
————————————————————————————————————————————————
这个效果一开始坑也比较多,本来是重写的ScrollView,ScrollView中包含了ViewPager,
ViewPager的item是Fragment,Fragment中是SwipeRefreshLayout+RecycleVIew。

这样嵌套的坑遇到了两个
第一:ViewPager的没有显示出来,应该是嵌套后高度出现了问题,我重写了它的onMeasure方法,遍历它的子view,重新测量它子view的高度,找到高度最高的一个算出heightMeasureSpec,再super回去,ViewPager是显示出来了,又出现了第二个问题;
第二:合并后的滑动问题,视图合并后,RecycleView一直流畅上滑,滑到底部后接着上滑ScrollView把事件拿去了,ScrollView在滑动了,因为我是通过setY的方式改变了布局的位置,所有ScrollView的高度是没变的,继续上滑会滑出空白,后来也想过处理RecycleView的onTouch,但感觉也会比较麻烦,就放弃了这种方案。

后面就直接继承了RelativeLayout,这样看起来就一个地方要修改了,因为是match_parent,ViewPager的高度是除了Toolbar高度+Content高度,但我setY的改变ViewPager位置的时候,尴尬的地方就出现了,ViewPager滑上来的时候底下是空白的,没有RecycleView的填充,因为RecycleView的滚动范围高度也是ViewPager的高度;
这个ViewPager最多可以移动到Toobar的底下,他的高度应该为原本高度+Content的高度!!这样问题就解决了,下面贴一贴主要代码

先贴下布局的代码

<?xml version="1.0" encoding="utf-8"?>
<com.ljp.newsdemo.widget.NewsScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/news_scrollview"
    android:layout_width="match_parent"
    android:layout_height="match_parent">


    <RelativeLayout
        android:id="@+id/rl_content"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="64dp"
            android:background="@color/colorAccent"
            app:title="我是标题"/>

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_below="@+id/toolbar"
            android:padding="10dp"
            android:text="@string/content"
            android:textColor="@color/colorPrimary"
            android:textSize="14sp"/>

    </RelativeLayout>


    <android.support.design.widget.TabLayout
        android:id="@+id/tab_layout"
        android:layout_width="match_parent"
        android:layout_height="64dp"
        android:background="@color/colorPrimary"/>


    <com.ljp.newsdemo.widget.NewsViewPager
        android:id="@+id/view_pager"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@+id/rl_content"
        android:background="#ffffff"/>

</com.ljp.newsdemo.widget.NewsScrollView>

NewsScrollView为RelativeLayout,拦截了整个view的触摸事件做了处理

 @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //点击事件不拦截
        int evY = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downEvY = evY;
                return super.onInterceptTouchEvent(ev);
            case MotionEvent.ACTION_UP:
                Log.d(TAG, "onInterceptTouchEvent: evY - downEvY = " + (evY - downEvY));
                //小于等于5 就算是点击,正常的点击 down-up=0
                if (Math.abs(evY - downEvY) <= 5) {
                    return super.onInterceptTouchEvent(ev);
                }
                break;
            default:
                break;
        }
        //其他情况看 是否已经合并了 如果已经合并了  不拦截;如果没合并,拦截掉
        if (isMerge) {
            return super.onInterceptTouchEvent(ev);
        } else {
            return true;
        }
    }

下面是滑动的处理,一开始写了很多代码,慢慢简化到了这一点。这块是主要逻辑代码了
逻辑是这样的,要有一个滑动距离范围,这个范围就是整个布局View除了Toolbar和底下的ViewPager的高度;
move的时候来计算移动的距离,通过回调把move的距离传出去,如果已经是合并状态的话,这边就不处理了,就交由下面的子view处理,可能就是刷新和点击事件了;
up和cancel的时候判断滑动的距离是否超过了范围的一半,如果超过了一半就做合并处理,反之就还原处理,up和cancel的事件都是回调出去做的动画效果处理;

	@Override
    public boolean onTouchEvent(MotionEvent ev) {
        //手指距离view顶部的位置
        int evY = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downEvY = evY;
                return super.onTouchEvent(ev);
            case MotionEvent.ACTION_MOVE:
                if (isMerge) {
                    return super.onTouchEvent(ev);
                }
                //最大可移动范围 为scrollview中去除recyclerView的距离
                //计算滚动距离
                mMoveY = downEvY - evY;
                if (mMoveY <= 0) {
                    mMoveY = 0;
                } else if (mMoveY >= mMaxScrollViewRange) {
                    mMoveY = mMaxScrollViewRange;
                }
                if (mOnScrollListener != null) {
                    mOnScrollListener.onScrollChange(-mMoveY, mMaxScrollViewRange, false);
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mOnScrollListener != null) {
                    //如果滑动的距离小于于最大距离的一半 让两个view做动画还原
                    if (Math.abs(mMoveY) < mMaxScrollViewRange / 2) {
                        //还原
                        isMerge = false;
                        mOnScrollListener.onScrollChange(0, mMaxScrollViewRange, true);
                    } else {
                        //合并
                        isMerge = true;
                        mOnScrollListener.onScrollChange(-mMaxScrollViewRange, mMaxScrollViewRange, true);
                    }
                }
                break;
            default:
                break;
        }
        if (isMerge) {
            return super.onTouchEvent(ev);
        } else {
            return true;
        }
    }

重写的viewPager中的主要代码

	private int maxHeight = 0;
  @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (maxHeight != 0) {
            heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight, MeasureSpec.EXACTLY);
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    public void setMaxHeight(int maxHeight) {
        this.maxHeight = maxHeight;
        requestLayout();
    }
	//外面调用
   mViewPager.setMaxHeight(mTvContent.getMeasuredHeight() + mViewPager.getMeasuredHeight());

MainActivity中的主要代码


   	 private final int animDuration = 500;
	  
		mViewPager.post(new Runnable() {
            @Override
            public void run() {
                //设置viewpager的高度
                mViewPager.setMaxHeight(mTvContent.getMeasuredHeight() + mViewPager.getMeasuredHeight());
                //设置滑动的最大距离
                mNewScrollview.setMaxScrollViewRange(mTvContent.getMeasuredHeight());
                //初始化时把tablayout移除屏幕外
                mTabLayout.setTranslationY(-mTabLayout.getMeasuredHeight());
                initData();
                initListener();
            }
        });
        
		mNewScrollview.setOnScrollListener(new NewsScrollView.OnScrollListener() {
            @Override
            public void onScrollChange(final float y, int maxRange, boolean isAnim) {
                //计算tabLayout要移动的距离
                float height = mTabLayout.getHeight();
                //计算倍率
                float power = height / maxRange;
                //计算tablayout的滚动距离
                float tabMoveY = -height + power * Math.abs(y);
                //透明度的计算0—1
                float alphaPower = 1f / maxRange;
                float alpha = 1 - (alphaPower * Math.abs(y));
                //mRlContent 也一起移动 最多移动tablayout的高度,计算如下
                float contentMoveY = -(height + tabMoveY);
                if (isAnim) {
                    AnimListener animListener = new AnimListener();
                    ViewCompat.animate(mViewPager).translationY(y).setDuration(animDuration).setListener(animListener).start();
                    ViewCompat.animate(mTabLayout).translationY(tabMoveY).setDuration(animDuration).start();
                    ViewCompat.animate(mRlContent).translationY(contentMoveY).setDuration(animDuration).start();
                    if (y == 0) {
                        //还原了
                        ViewCompat.animate(mRlContent).alpha(1).setDuration(animDuration).start();
                    } else {
                        //合并了
                        ViewCompat.animate(mRlContent).alpha(0).setDuration(animDuration).start();
                    }
                } else {
                    mViewPager.setTranslationY(y);
                    mTabLayout.setTranslationY(tabMoveY);
                    mRlContent.setTranslationY(contentMoveY);
                    mRlContent.setAlpha(alpha);
                }
            }
        });

	//恢复layout的动画
    private void restoreLayoutAnim() {
        ViewCompat.animate(mViewPager).translationY(0).setDuration(animDuration).start();
        ViewCompat.animate(mTabLayout).translationY(-mTabLayout.getHeight()).setDuration(animDuration).start();
        ViewCompat.animate(mRlContent).alpha(1).setDuration(animDuration).start();
        ViewCompat.animate(mRlContent).translationY(0).setDuration(animDuration).start();
        //要让recyclerview第一个条目滚动置顶
        try {
            NewsFragment fragment = ((ClassifyVpAdapter) mViewPager.getAdapter()).getItem(mViewPager.getCurrentItem());
            fragment.setRvScrollTop();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d(TAG, "restoreLayoutAnim: e=" + e.getMessage());
        }
    }
    
	@Override
    public boolean onKeyDown(int keyCode, KeyEvent event) {
        if (keyCode == KeyEvent.KEYCODE_BACK) {
            if (mNewScrollview.getIsMerge()) {
                mNewScrollview.scrollTo(0, 0);
                //恢复原来的样子
                restoreLayoutAnim();
                mNewScrollview.setMergeState(false);
                return true;
            }
        }
        return super.onKeyDown(keyCode, event);
    }

写完了发现比较简单,但是感觉优化的地方还是有的,比如ViewPager的高度问题,在Activiy的init时候获取view宽高的问题,感觉用post的方式不太合理,大佬有建议希望指点一二!


github地址:https://github.com/15863459070/news_scrollview

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值