高仿京东2020版首页布局及刷新效果

GitHub:https://github.com/baiyuliang/JdRefresh

CSDN下载地址:https://download.csdn.net/download/baiyuliang2013/12739758

优化升级版:高仿京东2020版首页效果2

效果图:
在这里插入图片描述

看本篇文章之前,建议最好先打开京东app,体验一下原版效果,并试着自己去思考其布局和效果的实现方法,那么再看此文章时,可达到事半功倍的效果!

我们先来根据实际效果,分析布局方式:

  • 底层有一背景色,跟搜索栏背景色一致;
  • Tab为首页时,有整体(带Tab栏)下拉刷新效果,且向上滑动时,也是整体向上滑动的;
  • 搜索栏除了一个伸缩效果外是固定不动的;
  • 切换到其它Tab后,不再有整体下拉效果,而是Tab栏以下刷新;

根据上述直观的效果,我大致将布局分为4层:

在这里插入图片描述

根据实际效果,广告图的顶部实际是超过布局的,如marginTop=-300dp,方可实现所需效果!

接下来,我们再分析具体的布局方式:

目前大部分app,基本都是 TabLayout+ViewPager+Fragment的方式,而这个也不列外,所以第三层内容页同样是这种实现方式;

内容层布局:

 <LinearLayout
        android:id="@+id/ll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_refresh_state"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center"
            android:text="下拉刷新"
            android:textColor="#dddddd" />

        <net.lucode.hackware.magicindicator.MagicIndicator
            android:id="@+id/magicIndicator"
            android:layout_width="match_parent"
            android:layout_height="35dp" />

        <com.byl.jdrefresh.CustomViewPager
            android:id="@+id/viewPager"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </LinearLayout>

再看Tab为首页时,为整体滑动效果,如果内容页仅为TabLayout+ViewPager布局,是不可能有整体滑动效果的,因此,外层我目前想出的只有套一层ScrollView(这里使用的是NestedScrollVIew),并且要实现需求效果,那必然要重写NestedScrollVIew,并监听手势状态!

而第一层,第二层和第三层效果联系非常紧密,所以这三层可以合并为一层,一起放进重写的NestedScrollVIew中,当然,这三层布局方式必然是帧布局或者相对布局!

主界面布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#f7f7f7"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <com.byl.jdrefresh.JdScrollView
        android:id="@+id/jdScrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:fillViewport="true" />

    <RelativeLayout
        android:id="@+id/rl_top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="#acddee">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:gravity="center_vertical"
            android:orientation="horizontal"
            android:paddingLeft="15dp"
            android:paddingRight="15dp">

            <LinearLayout
                android:id="@+id/ll_search"
                android:layout_width="0dp"
                android:layout_height="30dp"
                android:layout_weight="1"
                android:background="@drawable/bg_solid_ff_50"
                android:gravity="center_vertical"
                android:orientation="horizontal">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="搜索"
                    android:layout_marginLeft="15dp"
                    android:textColor="#666666"
                    android:textSize="12sp" />

            </LinearLayout>


        </LinearLayout>

    </RelativeLayout>


</RelativeLayout>

自定义NestedScrollView布局:
在这里插入图片描述
布局方式基本分析完毕,接下来就是核心部分,NestedScrollView的重写!

无论什么效果,都可以从最简单的实现方式开始,我早前写过一篇如何自己实现listview下拉刷新和上拉加载效果的文章手把手教你轻松实现listview下拉刷新

如果你看过这篇文章,或者自己写过类似效果,了解原理,并对事件分发机制了如指掌,那么这篇文章说实话就没必要再看下去了,仅仅看一个思路即可!而对初学者来说,这篇文章以及上面提到的文章,是一个不错的学习机会!

这篇文章,主要讲解实现思路,和关键核心的地方,具体代码可以直接下载,或访问GitHub克隆!

手指在屏幕上向下滑动,布局整体跟随手指向下滑动,如何实现?

这其实就类似于一个跟随效果,通过监听onTouch事件,实时获取手指坐标以及滑动距离,并实时设置view的坐标,便可实现手指跟随效果!当然对于本项目的效果,并不是通过不断设置坐标实现的,而是通过不断设置View的paddingTop或marginTop实现的,上面提到的listview刷新文章中也提到过!

  • 手指按下时(MotionEvent.ACTION_DOWN)记录触点位置;
  • 滑动时(MotionEvent.ACTION_MOVE),计算滑动距离并实时设置内容页的paddingTop值;
  • 手指离开屏幕(MotionEvent.ACTION_UP)时,复原View的初始位置;

其实最核心的地方就是上面这样非常简单的道理,而我们要做的就是去处理此过程中遇到的各种手势冲突等复杂问题,以及一些UI细节等体验问题!

我们往细节了看,下拉时搜索栏颜色渐变至消失(设置Alpha实现),广告图渐变至完全显示(设置Alpha实现),另外注意一个 细节,广告图是滑动到一定距离,才开始和内容页一起向下滚动的,同样下拉刷新也是滑动到一定距离才到释放刷新状态的!这些细节都是要考虑的地方!

监听onTouchMove时,需要注意的是,我们仅需要在ScrollView在最顶部时向下拉,才接管手势处理,其它情况都不应该阻止move事件,这样可以保证下拉和正常的上划操作互不影响!

  @Override
    public void onScrollChanged(int l, int t, int oldl, int oldt) {
        super.onScrollChanged(l, t, oldl, oldt);
        scrollY = t;
    }

仅当scrollY==0时才能执行下拉操作!

另一需要注意的地方是,从顶部下拉后松开手指,View回弹复原,一切正常,但从顶部下拉后不松开手指,而是再向上反方向滑动,此时就会有问题了,Move事件会和ScrollView本身的滑动事件onScroll冲突,此时我们就要做好处理,判断在这种情况下,要禁止onScroll事件(通过重写onOverScrolled方法)!

    @Override
    protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
        if (!disable && !isInterceptScroll)
            super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
    }

在即将实现最终效果时遇到了一个大问题:正在刷新状态时,此时手指按下屏幕,并向下滑动一段距离后停留,待刷新完成,顶部复位后,又瞬间自动移动到了手指停留的地方,我已经做了刷新中截断move事件的处理,为什么会这样?

其实在了解清楚事件分发后,就不难理解了:

原本是全部在ScrollView中监听ACTION_DOWN和ACTION_MOVE ,ACTION_DOWN记录初始位置,ACTION_MOVE记录实时位置,然后通过ev.getRawY() - startY计算滑动距离,而实际当你按下手指时,先走的是ViewPager中dispatchTouchEvent的ACTION_DOWN,ScrollView中的OnTouch是最后才走的,这个没有争议,对吧,当正在刷新时,我在ScrollView的ACTION_MOVE中判断了状态,即刷新中时如果有滑动操作则直接返回true消费掉事件,不会走MOVE后面的代码,看似没问题,但实际上我仅仅是截断了ScrollView的onMove,而它的子View,ViewPager依然在执行onMove事件,它在实时传递给ScrollView的onMove,所以当刷新完成的一刹那,ScrollView的ACTION_MOVE截断解除,会瞬间执行后面的代码,而此时并没有走ScrollView的ACTION_DOWN,也就是startY=0,而ACTION_MOVE中计算移动距离:ev.getRawY() - startY得出的直接就是手指当前位置,造成这种bug效果也就不言而喻了!

解决办法:
1.VIewPager中:

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //消费掉(即不走自身的onTouch,也不走父容器的onTouch)
        if (isRefreshing) {
            startX = 0;
            startY = 0;
            return true;
        }
        float x = ev.getRawX();
        float y = ev.getRawY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startX = x;
                startY = y;
                break;

2.ScrollView中:

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        //先走的是ViewPager的OnTouch,所以从ViewPager取startY最准确
        startY = viewPager.getStartY() == 0 ? ev.getRawY() : viewPager.getStartY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_MOVE:
            if (startY != 0 && viewPager.getStartY() == 0) return true;

接下来要说的是回弹动画,如果在ACTION_UP时,直接通过设置setMarginTop复位,那么效果可想而知,非常突兀,这里就会用到属性动画中的ValueAnimator,它可以通过给定的值,在你设置的一段事件内平滑的将给定的值返回给你,这样我就可以在设置的时间内平滑的去设置MarginTop直至复位,效果立竿见影,这里一定要注意,广告图和内容页移动距离是不同的!

        ValueAnimator animator = ValueAnimator.ofInt(AD_START_SCROLL_DISTANCE);
        animator.setDuration(100);
        animator.start();
        animator.addUpdateListener(animation -> {
            int value = (int) animation.getAnimatedValue();
            ll_content.setPadding(0, paddingTop + AD_START_SCROLL_DISTANCE - value, 0, 0);
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                tv_refresh_state.setText("下拉刷新");
                isInterceptTouch = false;
                isInterceptScroll = false;
                REFRESH_STATUS = REFRESH_DONE;
                viewPager.setRefreshing(false);
                layoutView(0);
                layoutAd(marginTop);
                iv_ad.setImageAlpha(0);
                if (onPullListener != null) onPullListener.onPull(255);
                ll_content.setPadding(0, paddingTop, 0, 0);
                enableTab();
            }

            @Override
            public void onAnimationCancel(Animator animation) {

            }

            @Override
            public void onAnimationRepeat(Animator animation) {

            }
        });

最后关于ScrollView嵌套ViewPager的问题还是有必要说明一下,这样干过的同学应该是都知道这么布局的问题所在的,如果不做任何处理,ViewPager的内容是无法显示的,有两种解决办法:

1.ScrollView添加属性:fillIViewPort=true,但这样做你会发现显示没问题了,但滑动却出现了问题;

2.ViewPager设置固定高度,这样确实完美了,但内容一般是不固定的,所以直接设置固定高度自然不合适,那就只有根据内容动态设置高度了,当Tab为首页时,高度设为实际内容高度,同时要禁止RecyclerView的滚动事件,这样上下滑动时就是整体滑动,其它Tab时,再将高度设为match_parent(同时禁止ScrollView滑动),这样就仅有ViewPager内部的滑动事件了!

GitHub:https://github.com/baiyuliang/JdRefresh

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白玉梁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值