NestScrollView嵌套RecyclerView实现淘宝首页滑动效果

目录
  • 一.概述
  • 二.开搞
  • 三.处理嵌套滑动
  • 四.实现惯性滑动

一.概述

本文主要实现淘宝首页嵌套滑动,中间tab吸顶效果,以及介绍NestScrollView嵌套RecyclerView处理滑动冲突的方法,淘宝首页的效果图如下:

二.开搞

首先我们通过一张图来分析下页面的布局结构:

先把最基础的页面搭出来,禁用Recycler滑动只需要重写onInterceptTouchEvent、onTouchEvent返回值都设为false即可:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".activiy.ViewPagerActivity"
    android:background="#f2f2f2">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">

        <com.aykj.nestscrolldemo.widget.NoScrollRecyclerView
            android:id="@+id/top_recycler_view"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="#e0e0e0"/>

            <com.google.android.material.tabs.TabLayout
                android:id="@+id/tab_view"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />

            <View
                android:layout_width="match_parent"
                android:layout_height="1px"
                android:background="#e0e0e0"/>

            <androidx.viewpager.widget.ViewPager
                android:id="@+id/view_pager"
                android:layout_width="match_parent"
                android:layout_height="match_parent"/>

        </LinearLayout>

    </LinearLayout>

</ScrollView>
public class ViewPagerActivity extends AppCompatActivity {

    private List<String> topDatas = new ArrayList<>();
    private List<String> tabTitles = new ArrayList<>();
    ActivityViewPagerBinding viewBinding;
    private RecyclerAdapter topAdapter;
    private DividerItemDecoration divider;
    private TabFragmentAdapter pagerAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        viewBinding = ActivityViewPagerBinding.inflate(LayoutInflater.from(this));
        setContentView(viewBinding.getRoot());

        initDatas();
        initView();
    }

    private void initDatas() {
        topDatas.clear();
        for(int i=0; i<5; i++) {
            topDatas.add("top item " + (i + 1));
        }

        tabTitles.clear();
        tabTitles.add("tab1");
        tabTitles.add("tab2");
        tabTitles.add("tab3");
    }

    private void initView() {
        //init topRecycler
        divider = new DividerItemDecoration(this, LinearLayout.VERTICAL);
        divider.setDrawable(new ColorDrawable(Color.parseColor("#ffe0e0e0")));
        viewBinding.topRecyclerView.setLayoutManager(new LinearLayoutManager(this));
        viewBinding.topRecyclerView.addItemDecoration(divider);
        topAdapter = new RecyclerAdapter(this, topDatas);
        viewBinding.topRecyclerView.setAdapter(topAdapter);

        //initTabs with ViewPager
        pagerAdapter = new TabFragmentAdapter(getSupportFragmentManager(), tabTitles);
        viewBinding.viewPager.setAdapter(pagerAdapter);
        viewBinding.tabView.setupWithViewPager(viewBinding.viewPager);
        viewBinding.tabView.setTabMode(TabLayout.MODE_FIXED);
    }
}

可以看到ViewPager没有正常显示出来,这个时候可以重写ViewPager的onMeasure,重新测量ViewPager的宽高。也可以换用ViewPager2

public class CustomViewPager extends ViewPager {
  	...
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //重写ViewPager的onMeasure
        int width = 0;
        int height = 0;
        for(int i=0; i<getChildCount(); i++) {
            View childView = getChildAt(0);
            measureChild(childView, widthMeasureSpec, heightMeasureSpec);
            width = Math.max(width, childView.getMeasuredWidth());
            height = Math.max(height, childView.getMeasuredHeight());
        }

        height += getPaddingTop() + getPaddingBottom();
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
}

从上面的效果图可以看到,ViewPager能正常显示出来了,但是在RecyclerView上滑动的时候发现,RecyclerView滑动完了之后,ScrollView才会滑动,并且ScrollView只滑动了一小段距离,这是因为首先ScrollView是不支持嵌套滑动的

ScrollView内部的第一个子View中所有子View的高度 = 顶部的RecyclerView高度 + TabLayout高度 + 底部RecyclerView中所有可见Item的高度

这个高度只比ScrollView的高度大一点点导致的。为了实现嵌套滑动需要使用NestedScrollView,接下来把ScrollView替换成NestedScrollView:

整个页面可以滑完,看起来就像是两个Scroll被合并成一个了,如果单单只是实现上面的界面效果,我们完全可以使用一个RecyclerView即可,但是Tab没有吸顶,这是因为:

ScrollView内部的第一个子View中所有子View的高度 = 顶部的RecyclerView高度 + TabLayout高度 + 底部RecyclerView中所有Item的高度

要实现Tab吸顶,只需要重写NestedScrollView的onMeasue方法,将TabLayout的高度和ViewPager的高度之和设置为NestedScrollView的高度:

public class StickyScrollLayout extends NestedScrollView {
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        int count = getChildCount();
        if(count == 1) {
            View firstChild = getChildAt(0);
            if(firstChild != null && firstChild instanceof ViewGroup) {
                int childCount = ((ViewGroup) firstChild).getChildCount();
                if(childCount > 1) {
                    topView = ((ViewGroup) firstChild).getChildAt(0);
                    contentView = ((ViewGroup) firstChild).getChildAt(1);
                }
            }
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

        if(contentView != null) {
            ViewGroup.LayoutParams contentLayoutParams = contentView.getLayoutParams();
            contentLayoutParams.height = getMeasuredHeight();
            contentView.setLayoutParams(contentLayoutParams);
        }
    }
}

此时TabLayout可以吸顶了

三.处理嵌套滑动

从上图中可以看出,当我们在RecyclerView上向上滑动时,需要等RecyclerView滑动完,外部的NestedScrollView才开始滑动,而我们希望NestedScrollView中顶部的RecyclerView滑完之后,底部的RecyclerView才开始滑动,这是为什么呢?

查看NestedScrollView和RecyclerView的源码,可以知道NestedScrollView和RecyclerView分别实现了NestedScrollingParent3,NestedScrollingChild3接口,分别用来表示嵌套滑动的父View、嵌套滑动的子View,当我们的手指在RecyclerView上滑动时,滑动事件会从上往下分发至RecyclerView的onTouchEvent中,RecyclerView会依次响应ACTION_DOWN、ACTION_MOVE、ACTION_UP

RecyclerView在处理ACTION_DOWN时的关键代码如下:

public boolean onTouchEvent(MotionEvent e) {
  switch (action) {
    case MotionEvent.ACTION_DOWN: {
      if (canScrollHorizontally) {
        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL;
      }
      if (canScrollVertically) {
        nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL;
      }
      startNestedScroll(nestedScrollAxis, TYPE_TOUCH);
    } break;
  }
  return true;
}

当手指按下屏幕时会调用其作为NestedScrollingChild的实现方法startNestedScroll,在startNestedScroll的具体实现中,会一级一级的往上查找是否有NestedScrollingParent,如果有,会调用NestedScrollingParent的onStartNestedScroll方法通知它我即将要开始滑动了,然后NestedScrollingParent会调用onNestedScrollAccepted继续传递给上层的NestedScrollingParent,此处的NestedScrollingParent整好由NestedScrollView来充当,而NestedScrollView的上层已经找不到NestedScrollingParent了,时间传给NestedScrollView之后就中断了。

紧接着处理一系列的ACTION_MOVE:

public boolean onTouchEvent(MotionEvent e) {
  switch (action) {
    case MotionEvent.ACTION_MOVE: {
      if (dispatchNestedPreScroll(
        canScrollHorizontally ? dx : 0,
        canScrollVertically ? dy : 0,
        mReusableIntPair, mScrollOffset, TYPE_TOUCH
      )) {
        dx -= mReusableIntPair[0];
        dy -= mReusableIntPair[1];
        // Updated the nested offsets
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];
        // Scroll has initiated, prevent parents from intercepting
        getParent().requestDisallowInterceptTouchEvent(true);
      }

      if (scrollByInternal(
        canScrollHorizontally ? dx : 0,
        canScrollVertically ? dy : 0,
        e)) {
        getParent().requestDisallowInterceptTouchEvent(true);
      }
    } break;
  }
  return true;
}

RecyclerView接收到ACTION_MOVE后,首先会调用其作为NestedScrollingChild的实现方法dispatchNestedPreScroll,在dispatchNestedPreScroll的具体实现中,会一级一级的往上查找是否有NestedScrollingParent,如果有,会调用NestedScrollingParent的dispatchNestedPreScroll,紧接着调用NestedScrollView的onNestedPreScroll,来告诉NestedScrollView我即将要滑动 xxx 距离,你需不需要滑动,在NestedScrollView的onNestedPreScroll方法中并不会去响应滑动,又会把自己作为一个NestedScrollingChild,把事件继续往上传递,而在NestedScrollView的上层已经没有可以处理嵌套滑动的NestedScrollingParent了

@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed,
int type) {
	dispatchNestedPreScroll(dx, dy, consumed, null, type);
}

具体的事件传递流程如下图:

因此我们可以重写NestedScrollView的onNestedPreScroll方法来使NestedScrollView滑动

public class StickyNestedScrollLayout extends NestedScrollView {

  	@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        int count = getChildCount();
        if(count == 1) {
            View firstChild = getChildAt(0);
            if(firstChild != null && firstChild instanceof ViewGroup) {
                int childCount = ((ViewGroup) firstChild).getChildCount();
                if(childCount > 1) {
                    topView = ((ViewGroup) firstChild).getChildAt(0);
                    contentView = ((ViewGroup) firstChild).getChildAt(1);
                }
            }
        }
    }

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        boolean topIsShow = getScrollY() >=0 && getScrollY() < topView.getHeight();
        if(topIsShow) {
            scrollBy(0, dy);
        } else {
          super.onNestedPreScroll(target, dx, dy, consumed, type);
        }
    }
}

此时NestedScrollView能滑动了,但是NestedScrollView滑动的同时,RecyclerView也会跟着滑动,这是为什么呢?

在RecyclerView的dispatchNestedPreScroll方法具体实现中,有这样一段代码

public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed,
            @Nullable int[] offsetInWindow, @NestedScrollType int type) {
  if (isNestedScrollingEnabled()) {
      consumed[0] = 0;
      consumed[1] = 0;
      ViewParentCompat.onNestedPreScroll(parent, mView, dx, dy, consumed, type);
    	//consumed[0]、consumed[1]的值仍为0
      return consumed[0] != 0 || consumed[1] != 0;//返回false
    }
  }
  return false;
}

再结合RecyclerView的ACTION_MOVE来看:

public boolean onTouchEvent(MotionEvent e) {
  switch (action) {
    case MotionEvent.ACTION_MOVE: {
      if (dispatchNestedPreScroll(
        canScrollHorizontally ? dx : 0,
        canScrollVertically ? dy : 0,
        mReusableIntPair, mScrollOffset, TYPE_TOUCH
      )) {
        //dispatchNestedPreScroll返回了false,此处的if语句不会执行,因此RecyclerView也会滑动
        dx -= mReusableIntPair[0];
        dy -= mReusableIntPair[1];
        // Updated the nested offsets
        mNestedOffsets[0] += mScrollOffset[0];
        mNestedOffsets[1] += mScrollOffset[1];
        // Scroll has initiated, prevent parents from intercepting
        getParent().requestDisallowInterceptTouchEvent(true);
      }

      if (scrollByInternal(
        canScrollHorizontally ? dx : 0,
        canScrollVertically ? dy : 0,
        e)) {
        getParent().requestDisallowInterceptTouchEvent(true);
      }
    } break;
  }
  return true;
}

因此,我们,在NestedScrollView的onNestedPreScroll方法中,处理完滑动后,通过consumed告诉RecyclerView我滑动了多少,这样

RecyclerView会重新设置dx、dy的值,因此RecyclerView就不会跟着滑动了

public class StickyNestedScrollLayout extends NestedScrollView {
    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        int count = getChildCount();
        if(count == 1) {
            View firstChild = getChildAt(0);
            if(firstChild != null && firstChild instanceof ViewGroup) {
                int childCount = ((ViewGroup) firstChild).getChildCount();
                if(childCount > 1) {
                    topView = ((ViewGroup) firstChild).getChildAt(0);
                    contentView = ((ViewGroup) firstChild).getChildAt(1);
                }
            }
        }
    }

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        boolean topIsShow = getScrollY() >=0 && getScrollY() < topView.getHeight();
        if(topIsShow) {
            scrollBy(0, dy);
            //告诉RecyclerView,我滑动了多少距离
            consumed[1] = dy;
        } else {
            super.onNestedPreScroll(target, dx, dy, consumed, type);
        }
    }
}

四.实现惯性滑动

实现思路:

记录父控件惯性滑动的速度判断NestedScrollView是否滚动到底部,若滚动到底部,判断子控件是否需要继续滚动滚动将惯性滑动的速度转化成距离,计算子控件应滑的距离 = 惯性距离 - 父控件已滑动距离,并将子控件应滑的距离转化成速度交给子控件进行惯性滑动

1.记录父控件惯性滑动的速度

public void fling(int velocityY) {
  super.fling(velocityY);
  if (velocityY <= 0) {
  	mVelocityY = 0;
  } else {
  	mVelocityY = velocityY;
  }
}

2.判断NestedScrollView是否滚动到底部,若滚动到底部,判断子控件是否需要继续滚动

@Override
protected void onScrollChanged(int scrollX, int scrollY, int oldScrollX, int oldScrollY) {
  super.onScrollChanged(scrollX, scrollY, oldScrollX, oldScrollY);
  /*
         * scrollY == 0 即还未滚动
         * scrollY == getChildAt(0).getMeasuredHeight() - v.getMeasuredHeight()即滚动到底部了
         */
  //判断NestedScrollView是否滚动到底部,若滚动到底部,判断子控件是否需要继续滚动
  if (scrollY == getChildAt(0).getMeasuredHeight() - this.getMeasuredHeight()) {
    dispatchChildFling();
  }
  //累计自身滚动的距离
  mConsumedY += scrollY - oldScrollY;
}

3.将惯性滑动的速度转化成距离,计算子控件应滑的距离 = 惯性距离 - 父控件已滑动距离,并将子控件应滑的距离转化成速度交给子控件进行惯性滑动

private void dispatchChildFling() {
    if(mFlingHelper == null) {
      mFlingHelper = new FlingHelper(getContext());
    }
    if (mVelocityY != 0) {
        //将惯性滑动速度转化成距离
        double distance = mFlingHelper.getSplineFlingDistance(mVelocityY);
        //计算子控件应该滑动的距离 = 惯性滑动距离 - 已滑距离
        if (distance > mConsumedY) {
            RecyclerView recyclerView = getChildRecyclerView(mContentView);
            if (recyclerView != null) {
                //将剩余滑动距离转化成速度交给子控件进行惯性滑动
                int velocityY = mFlingHelper.getVelocityByDistance(distance - mConsumedY);
                recyclerView.fling(0, velocityY);
            }
        }
    }

    mConsumedY = 0;
    mVelocityY = 0;
}

//递归获取子控件RecyclerView
private RecyclerView getChildRecyclerView(ViewGroup viewGroup) {
  for (int i = 0; i < viewGroup.getChildCount(); i++) {
    View view = viewGroup.getChildAt(i);
    if (view instanceof RecyclerView && Objects.requireNonNull(((RecyclerView) view).getLayoutManager()).canScrollVertically()) {
      return (RecyclerView) view;
    } else if (viewGroup.getChildAt(i) instanceof ViewGroup) {
      RecyclerView childRecyclerView = getChildRecyclerView((ViewGroup) viewGroup.getChildAt(i));
      if (childRecyclerView != null && Objects.requireNonNull((childRecyclerView).getLayoutManager()).canScrollVertically()) {
        return childRecyclerView;
      }
    }
  }
  return null;
}

到此这篇关于NestScrollView嵌套RecyclerView实现淘宝首页滑动效果的文章就介绍到这了,更多相关NestScrollView嵌套RecyclerView内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • android RecyclerView的一些优化点介绍

    目录 1.RecycledPool的重用 2.setHasFixedSize(boolean)的使用 3.setHasStableIds(boolean)的使用 4.ViewCacheExtension的使用 5.预加载 6.更新列表的方式 item局部更新 整体列表更新 总结 1.RecycledPool的重用 场景以及使用: 多个RecyclerView出现,并且他们的item布局结构一致,这时候可以进行重用. 在进行RecyclerView的初始化设置时候进行RecycledPool的设置

  • 在RecyclerView中实现button的跳转功能

    目录 一>实现功能 二>在xml中添加布局文件 三>完善java文件 四>完善adapter文件 五>完善JAVA文件 一>实现功能 在实验二中我们已经实现了在类微信界面添加recyclview并添加相应的imageview,本次实验就是在recyclview中添加一个button控件并实现监听,使鼠标点击时可以跳转到另外一个设计好的界面,具体操作如下. 二>在xml中添加布局文件 首先我们要设计点击后的跳转界面,我直接采用了淘宝中的购物界面添加了一个textvi

  • Android MVVM架构实现RecyclerView列表详解流程

    目录 效果图 导入引用 导入Recyclerview依赖 导入dataBinding引用 代码解析 建立实体类 建立RecyclerView子项 适配器 建立适配器 设置子项点击事件 adapter全部代码 建立VM层 子项点击事件的使用 VM层代码 数据与视图交互 效果图 导入引用 导入Recyclerview依赖 implementation 'androidx.recyclerview:recyclerview:1.1.0' 导入dataBinding引用 dataBinding { en

  • NestScrollView嵌套RecyclerView实现淘宝首页滑动效果

    目录 一.概述 二.开搞 三.处理嵌套滑动 四.实现惯性滑动 一.概述 本文主要实现淘宝首页嵌套滑动,中间tab吸顶效果,以及介绍NestScrollView嵌套RecyclerView处理滑动冲突的方法,淘宝首页的效果图如下: 二.开搞 首先我们通过一张图来分析下页面的布局结构: 先把最基础的页面搭出来,禁用Recycler滑动只需要重写onInterceptTouchEvent.onTouchEvent返回值都设为false即可: <?xml version="1.0" en

  • 原生js实现淘宝首页点击按钮缓慢回到顶部效果

    淘宝首页的回到顶部按钮是这样的:下拉到一定距离后按钮才显示出来,鼠标放到按钮上时,按钮背景会变成灰色,并且图标变成了文字.点击按钮缓慢回到顶部 我们先分析下实现这样的效果需要添加哪些事件.鼠标移进移出按钮,按钮表现发生变化,所以需要给按钮添加mouseover, mouseout事件.要侦听滚动条的变化,所以需要给window添加scroll事件,点击按钮回到顶部,按钮添加click事件.我们将事件处理程序封装成三个函数moveIn, moveOut, goTop; 下面先给出html/css代

  • Android仿淘宝首页头条View垂直滚动效果

    之前本来是打算做TextView垂直向上滚动的,后来发现一位大神做得很好,https://github.com/sfsheng0322/MarqueeView 孙福生大神,然后自己要用到多个View向上滚动,也就是类似淘宝首页头条的那种滚动,所以就按照那个思路想了系啊,可以把View拿来滚动,这样可以自己随意的修改View里面的内容,还比较简单一些.所以这个整个思路就是把View就行循环滚动. 看一下循环滚动View的内容咋写的吧,非常简单. package com.dreamlive.upma

  • Android仿淘宝view滑动至屏幕顶部会一直停留在顶部的位置

    在刚刚完成的项目中,在一个页面中,用户体验师提出引用户操作的入住按钮要一直保留在页面当中,不管页面能滚动多长都得停留在页面的可视区域.最终实现效果如下图所示:   如图中的红色框中的view始终会停留在页面中,如果滑动至页面的顶部,会一直保留在顶部. 下面来说下具体的实现思路: 思路:其实整个页面当中一共有两个视觉效果一样的View,通过滑动的位置来进行View的隐藏和显示来达到这种效果.整个页面的在上下滑动的过程中可以总结为两个状态,状态A(如图1所示),view2在可视区域内时,view1不

  • js实现淘宝首页的banner栏效果

    本文实例为大家分享了js淘宝首页banner栏展示的具体代码,供大家参考,具体内容如下 <div class="bg clearfix"> <div class="title"> <a class="on" href="javascript:" >最热团购</a> <a href="javascript:" >商城特惠</a> <

  • 微信小程序淘宝首页双排图片布局排版代码(推荐)

    效果图: 使用技术主要是flex布局,绝对定位布局,小程序前端页面开发,以及一些样式! 直接贴代码,都有详细注释,熟悉一下,方便以后小程序开发! wxml: <view class="taobaolist"> <block wx:for="{{imagelist}}" wx:key="item"> <view class="taobao-list"> <view class="

  • 基于JS分页控件实现简单美观仿淘宝分页按钮效果

    最新版本代码请移步到https://github.com/pgkk/kkpager 在线测试链接:http://pgkk.github.io/kkpager/example/pager_test.html 分页按钮思想: 1.少于9页,全部显示 2.大于9页,1.2页显示,中间页码当前页为中心,前后各留两个页码 附件中有完整例子的压缩包下载.已更新到最新版本 先看效果图: 01输入框焦点效果 02效果 模仿淘宝的分页按钮效果控件kkpager JS代码: Js代码 var kkpager = {

  • JavaScript实现类似淘宝的购物车效果

    前言 相信大家都很熟悉商品购物车这一功能,每当我们在某宝某东上购买商品的时候,看中了哪件商品,就会加入购物车中,最后结算.购物车这一功能,方便消费者对商品进行管理,可以添加商品,删除商品,选中购物车中的某一项或几项商品,最后商品总价也会随着消费者的操作随着变化.本文介绍的是通过JavaScript实现类似于淘宝的购物车效果,包括商品的单选.全选.删除.修改数量.价格计算.数目计算.预览等功能. 功能如下 1. 实现兼容低版本IE的getElementsByClassName()方法 2. JS表

  • JS实现的仿淘宝交易倒计时效果

    本文实例讲述了JS实现的仿淘宝交易倒计时效果.分享给大家供大家参考,具体如下: <script type="text/javascript"> var StartTime = new Date("2015/11/11 12:34:03"); document.write("订购时间: " + StartTime.toLocaleDateString() + StartTime.toLocaleTimeString() + "

  • JS实现兼容性好,自动置顶的淘宝悬浮工具栏效果

    本文实例讲述了JS实现兼容性好,自动置顶的淘宝悬浮工具栏效果.分享给大家供大家参考.具体如下: 这是一款兼容性好,自动置顶的淘宝悬浮工具栏,如果你把滚动条滚动至最上边了,那么它会自动判断是否到顶端了,然后一直置顶从而不怕遮挡,其实明白了这种思路,你就能举一返三了,演示一下看看效果. 运行效果截图如下: 在线演示地址如下: http://demo.jb51.net/js/2015/js-tb-float-top-tools-menu-codes/ 具体代码如下: <!DOCTYPE html PU

随机推荐