代码编织梦想

9602aafbf330b4bae197654c2f78b259.gif

推荐语:这篇文章图文并茂地介绍了淘宝搜索滚动容器的技术演进过程,结合代码讲解页面结构划分、数据处理、交互效果,还包含了对逻辑抽象、功能拓展的思考,最后总结了可复用的架构。非常具有实践意义,推荐阅读学习!

——大淘宝技术终端开发工程师 门柳

d75016d2d62c11c72abb09b4a20bbe2a.png

前言

XSearch 做为搜索客户端的基础框架,已经在线上运行多年,它同时支撑了主搜、拍立淘、云主题、店铺内等业务。XSearch深刻践行了 Native + 动态化的理念, 端上框架内提供动态化坑位,定义好交互行为,而服务端数据定义有哪些坑位,以及坑位内渲染哪些数据。这一套运作机制,保证了搜索的业务能够快速迭代。然而随着业务的不断发展,框架在不断壮大的同时,也存在了隐含的问题。例如代码复用率低、有些体验问题无法解决等。本文就基于业务容器和业务架构两方面进行总结阐述。

b02e98bfa70ef427ad8075239b10195b.png

容器升级

  1.0时代

XSearch列表容器按照功能划分,主要分为 SRP 和 XSearchList,前者是给主搜索使用的,面相 Native 开发,支持端上开发进行各种特殊交互定制。而后者目前支撑了拍立淘结果页、云主体二跳页、分类等业务,面相前端开发,框架内定制了标准的数据协议以及交互行为,前端可以根据自己的需求进行使用。

  • SRP

f6db9854b9769d64fb6d559d056586b3.gif

动图中展示了一个带场景层的搜索结果页案例,整个页面的联动分为两个部分

  1. 头部场景层区域

  2. 列表内排序条区域

场景层结构

79ed02bf47e747ccc3a44046edcdd045.jpeg

通用头部容器

SearchAppBarLayout 是一个通用头部容器,容器对每个子 View 定义了 layout_level 和 layout_position

level 越小的子 View,越先消耗 offset

position 越小的子 View,在y 轴上越靠上

offset 指的是列表的累积滚动距离

以下面这个 demo 为例,看一下完整效果

a2e34754d6e5a86833f98c4c20968a86.gif

<com.taobao.scrolltosample.demo.SearchAppBarLayout
        android:id="@+id/slide_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:id="@+id/libsf_srp_header_bottom_fold_part_container"
            app:layout_level="0"
            app:layout_position="6">
        />
        <LinearLayout
            android:id="@+id/libsf_srp_header_folder_part_container"
            app:layout_level="0"
            app:layout_position="3">


            <LinearLayout
                android:id="@+id/libsf_srp_header_fold_container"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="vertical">
            />
        </LinearLayout>
        <FrameLayout
            android:id="@+id/libsf_srp_header_tab_container"
            app:layout_level="1"
            app:layout_position="2"/>
        <LinearLayout
            android:id="@+id/libsf_srp_header_half_sticky_container"
            app:layout_level="1"
            app:layout_position="4"/>
        <LinearLayout
            android:id="@+id/libsf_srp_header_sticky_container"
            app:layout_level="2"
            app:layout_position="5"/> 
        <FrameLayout
            android:id="@+id/libsf_srp_header_searchbar_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_level="1"
            app:layout_position="0"/>
    </com.taobao.scrolltosample.demo.SearchAppBarLayout>

容器内部对子 View 做了两个排序,一个队列是根据子 View 的 layout_position 属性,以从小到大的顺序排列(队列 mSortedViews)。另一个对了根据子 View 的 layout_level 属性,按从小到大的顺序排列,若有level 相同的子 View,则按照 layout_position 从小到大排列(队列 mLevelSortedViews)。

完成子 View 的排列之后,进入布局逻辑,按照 position 升序,从上往下顺序放置 View

protected void layoutChildViews(int left, int top, int right, int bottom) {
    final int parentLeft = 0;
    final int parentRight = right - left;
    final int parentTop = 0;
    int nextTop = parentTop;
    int size = mSortedViews.size();
    for (int i = 0; i < size; i++) {
      ViewTuple viewTuple = mSortedViews.get(i);
      View child = viewTuple.view;
      final LayoutParams lp = (LayoutParams) child.getLayoutParams();
      child.layout(
              parentLeft + lp.leftMargin,
              nextTop + lp.topMargin,
              parentRight - lp.rightMargin,
              nextTop + child.getMeasuredHeight()
      );
      viewTuple.start = nextTop;
      viewTuple.height = child.getMeasuredHeight();
      viewTuple.level = lp.level;
      viewTuple.offset = 0;
      nextTop += child.getMeasuredHeight() + lp.bottomMargin;
    }

然后是处理各子 View 移动的逻辑

protected void calChildrenOffset(int toConsume) {
    int size = mLevelSortedViews.size();


    // 将level最低的view之上的view向下移动尽量多的距离.直到消耗完toConsume.
    for (int i = 0; i < size; i++) {
      ViewTuple tuple = mLevelSortedViews.get(i);
      if (tuple.view.getVisibility() == GONE) {
        continue;
      }
      //移动不能超过本身的高度.
      int move = Math.min(tuple.height - tuple.pinBottom, toConsume);
      calViewNewOffsetBefore(tuple, move);
      toConsume -= move;


      //消耗完成
      if (toConsume <= 0) {
        break;
      }
    }
  }
  /**
   * 计算位置在tuple之前的view的newOffset,会相应的向下移动move距离.
   *
   * @return 是否移动了任何viewTuple
   */
  protected boolean calViewNewOffsetBefore(ViewTuple tuple, int move) {
    boolean moved = false;
    int size = mSortedViews.size();
    for (int i = 0; i < size; i++) {
      ViewTuple viewTuple = mSortedViews.get(i);
      if (viewTuple.view.getVisibility() == GONE) {
        continue;
      }
      if (viewTuple == tuple) {
        break;
      }
      viewTuple.newOffset = viewTuple.tempOffset + move;
      viewTuple.tempOffset = viewTuple.newOffset;
      moved = true;
    }
    return moved;
  }

根据 level 升序进行遍历,在移动一个 View 之前,需要先计算出position 在它之上的 View 的移动距离,例如在 demo 中,按照 position 排列是tab- fold - half_sticky - sticky - bottom_fold,按照 level 排列是fold - bottom_fold - tab - half_sticky - sticky。

因此,假如一开始移动50px,则先遍历到 fold,然后移动 position 在fold 之上的 View,那么此时 tab 先触发移动。当 offset 超过 tab 的高度时,开始处理bottom_fold,此时移动 position 在 bottom_fold 之上的 View,因此会将 tab + fold + half_sticky + sticky 都进行移动,如下动图所示。

c851468560f70828124169c1d4883405.gif

在处理完子 View 的移动后,再将容器本身进行反方向移动,达到子 View 看着是向上移动的效果。

/**
   * @param offset 绝对偏移
   */
  public void setOffset(int offset) {
    ...
    //先清空之前的newOffset
    for (int i = 0; i < size; i++) {
      ViewTuple tuple = mLevelSortedViews.get(i);
      tuple.newOffset = 0;
      tuple.tempOffset = 0;
    }
    calChildrenOffset(toConsume);
    mOffsetTop = offset;
    //根据计算结果移动子view
    moveChildren();
    //移动自身
    ViewCompat.offsetTopAndBottom(this, mOffsetTop - (getTop() - mLayoutTop));
  }
  /**
   * 按viewTuple中的newOffset移动view并重设offset.
   */
  private void moveChildren() {
    int size = mSortedViews.size();
    for (int i = 0; i < size; i++) {
      //遍历子 View,根据 newOffset 和 offset 的差值,进行 View 的偏移
      if (viewTuple.newOffset != viewTuple.offset) {
        ViewCompat.offsetTopAndBottom(viewTuple.view, viewTuple.newOffset - viewTuple.offset);
        ...
      }
    }
  }

以上代码实现了容器内子 View 根据列表滚动距离的 offset 值进行同步移动的效果。然后说一下 sticky 效果的实现,容器中维护了一个最小 level 值,子 View 中小于这个 level 的,经过移动,最终都会隐藏,而大于等于这个 level 的子 View,则会呈现 sticky 的效果。容器在布局完成后,会计算出每个 level 的子 View 最大允许的滚动距离,然后在输入 offset 值时,进行 offset 范围限制,保证 sticky 对应的子 View 不会移动出屏幕范围。当然这个方案显而易见的问题是, 只有在容器最下方的 View 才能实现 sticky 效果。

场景层实现

5cc6e2fe5ae0c1738d6d269fcdb957f8.jpeg

淘宝的  TRecyclerView 组件支持添加Header和 Footer,在场景层 case 中,添加了一个透明的 HeaderView,headerView 的高度和场景层的高度相等,从而扩大列表滚动区域,同时给列表设置一个负偏移,保证 blankView 被列表父容器裁切掉。而在头部容器中,同时在 fold 区域添加了一个占位的 View,高度也和场景层相同,用来将头部容器撑开。当列表滚动时,通过 onScroll 回调同步通知给头部容器,使其内容一起向上移动,在头部容器移动的同时,给列表父容器同时设置一个反方向偏移,保证抵消列表的滚动,保证内容不变。

筛选条吸顶实现

筛选条的吸顶比较简单,就是在 列表负容器内同时放置一个 View,在场景层收起时,RecyclerView 和筛选条的父容器会同时做平移,保证列表和筛选条整体一起向上移动,并且能一直遮住列表。

  • XSearchList

31744ff2be3808b78a8000f8e251e817.gif

XSL 页面结构

430cf9795f681f2bd14c080687eda03a.png

如上图所示,XSL分上下两个区域,上半区域包含 topHeader 和 tab;下半区域是一个多 tab 结构,每个子 tab 都支持 fold、sticky 和 listHeader。

上半区域实现

topHeader 和 tab 的联动通过 NestedScroll api 实现,当下方列表开始滚动时,外部容器进行拦截,然后将上半区域的内容进行展开或者收起,待上半区域无法再被滚动时,才继续滚动列表内容。

9b8db1f09c35190ce6f1c2cce32a14fc.gif

为了让手指滑动上半区域时能联动下半区域,topHeader和 tab 的容器全部使用特殊的 NestedScrollView(一个支持根据内容高度展开的的 NestedScrollView),当手指滑动上半区域时,外部容器同样能进行拦截。

efb3e9ea6b18f92da721b5e36602ef71.gif

下半区域实现

fold和 listHeader 的实现较为简单,前文说过,手淘的 RecyclerView 组件支持 HeaderView(就是个特殊的 ViewHolder),XSL 将 fold、stickyMask 和listHeader全部放到了 HeaderView,因此可以跟随列表正常滚动。比较特殊的 stickyHeader 的实现,为了让stickyHeader 能吸顶盖住列表,在列表的上方放置了一个 StickyLayout,用来存放 stickyHeader,然后监听 stickyMask 的位置,动态调整 stickyLayout 的位置,从而实现吸顶的效果。

0291917e47612c4980c6b5dd13e2ca7d.gif

  2.0时代

随着搜索业务的不断迭代,业务场景越发复杂,2021年我们开始跨入消费搜索的领域,引来了更大的挑战。在消费搜索场景中,我们为 XSL 容器引入了 section 能力。在有了 section 的加持之后,我们可以在一个长列表中构建多楼层电梯结构。

  • 新容器

在了解了 SRP 和 XSearchList 的实现之后,我们不难发现以下问题

  1. 框架对于 Header 的处理非常定制化,不利于拓展

  2. 现有支持的 header 的顺序较为固定,不够灵活

  3. 缺乏一套统一的机制,SRP 和 XSearchList 对于 header 的实现完全不同

  4. 用了较多的 hack 逻辑,后续维护成本较高

  5. SRP 的头部联动有时候会不跟手

6728243a71b07fb1f1898ca92316620a.gif

基于以上种种原因,安卓端借着新搜索迁移 native 的机会,开发了一套全新的列表容器。

6a2f6e4a9532707592e2a71e9dac6f8d.gif

新的列表容器完全基于 NestedScroll API 实现,具有以下特点

  1. header 行为完全自定义

  2. header 顺序可以任意排列

  3. 头部区域与列表区域完全联动

  4. 多少 Header 多少 View,不会存在多余的 View,且头部区域完全平铺,不存在布局嵌套

  • 容器实现

列表容器整体基于 RecyclerView 的嵌套滚动机制实现,主要基于以下两个方法

public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
    //列表没消耗掉的滚动
  }


  public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, int type) {
    //列表发生滚动时,先触发这个方法,处理完成后,剩余的 scrollY 才会给 RecyclerView 消耗
  }
列表起始位置

列表容器存在一个基线位置,即非沉浸式的子内容(包含 header 和列表)的 top 值不允许小于这个数值


Header 优先级

header 存在两种优先级:

  1. 滑动优先级 listHeader = stickyHeader = foldHeader < list < halfSticky

    滑动优先级越高的 header,在触发滚动时,优先处理滑动事件

  2. 绘制优先级 listHeader = list = fold < halfSticky < stickHeader

    绘制优先级越高,在渲染时的 Z 层级就越高,因此高优先级的header会盖住低优先级的 header,touch 事件的响应顺序也是。

当添加 header 时,根据这两个优先级,维护两个数组。优先级支持自定义,在创建 header 时可以指定自己的优先级,比 halfSticky 优先级更高也是可以的

内置Behavior

框架内置了 List、Sticky、HalfSticky 这三种滚动类型的Behavior


Header 布局

根据 header 顺序,从上往下遍历 header。

header 具备三个属性:

  1. 高度

  2. 偏移

  3. 底部 padding

当要确定一个 header 的位置时,需要关注它上一个 header 的起始位置,以及它的高度,偏移量,以及底部 padding。因此一个 header 的最终位置计算如下

header.y = lastHeader.y  + lastHeader.高度 + lastHeader.偏移 - lastHeader.底部 padding

滑动处理

当列表发生滚动时,按照滑动处理的优先级以及各优先级内的 header 添加顺序进行遍历。

  • 拓展 header

例如我们要实现拍立淘的结果页布局

37efa8a51c4b2da15b236e82b70c9ac2.gif

页面拆解为下图

ef492ef90dbbf065c732c2aeb11ed98d.png

我们需要实现以下几个 header:

  1. 图片背景

  2. 图片操作栏

  3. 商品卡片

  4. 类目筛选

实现效果如下

28ff3ec506c5665e146ff52e8934298e.gif

图片背景
@Override
  public int consumeScroll(int dy, int listStart) {
    int temp = accumulatedY;
    int max = rootView.getMeasuredHeight() - listStart;
    int min = DIVIDER;
    temp += dy;
    temp = Math.min(max, temp);
    temp = Math.max(min, temp);
    int delta = temp - accumulatedY;
    accumulatedY = temp;
    return delta;
  }


  @Override
  public int getParallexHeight() {
    return accumulatedY;
  }
  @Override
  public int getTranslation() {
    return 0;
  }
多主体操作栏
private final Priority priority = new Priority(HALF_STICKY, Priority.Draw.LIST_HEADER);
  @Override
  public int consumeScroll(int dy, int listStart) {
    if ((int)getView().getTranslationY() > listStart) {
      return 0;
    }
    int temp = accumulatedY;
    temp += dy;
    temp = Math.min(getHeight(), temp);
    temp = Math.max(0, temp);
    int delta = temp - accumulatedY;
    accumulatedY = temp;
    return delta;
  }
  @Override
  public int getTranslation() {
    return 0;
  }
  @Override
  public int getParallexHeight() {
    return accumulatedY;
  }
商品卡片
private ListBehavior behavior;
  private View contentView;


  @Override
  public int consumeScroll(int dy, int listStart,int type) {
    int result = behavior.consumeScroll(dy, listStart,type);
    contentView.setTranslationY(behavior.getTranslation());
    return result;
  }


  @Override
  public int getTranslation() {
    return 0;
  }


  @Override
  public int getParallexHeight() {
    return -behavior.getTranslation();
  }

通过以上的处理,我们就在新容器里简单实现了下拍立淘的嵌套布局。

fd1544832ce40b1a0883790f4d6c3cdb.png

业务架构升级

当前的 XSearch 框架下,存在比较多的冗余逻辑,SRP 、XSL、新搜索都有一套各自的渲染和数据逻辑。

  数据结构统一

在进行框架统一前,我们需要先基于各种业务场景进行分析和归纳。

  • SRP

4704756edbaf8aa91b7deb73d8da167c.png

搜索框不属于业务数据,是srp 场景内固有区块,但是在 UI 结构上属于外层 header。

  • XSearchList

这里以饿了么的 xsearchlist 场景为例

9f01b6e983314f99f3bc272a9e358a6c.png

  • 新搜索

新搜索的 UI 结构和 srp 类似,这里就不再赘述。

从以上分析我们可以看到,SRP 和 XSearchList基本数据结构可以归纳为以下结构。

8517727551c748d74f568e61d80b5001.png

  楼层结构抽象

原先的 XSearch 框架内,卡片内容 items 就是单纯的一个商品卡数组,页面每个 tab 对应一个 DataSource,而每个 DataSource 都会对应一个 Result。

  1. DataSource 负责管理请求的发送与处理、请求参数存储

  2. Result 负责保存页面 UI 数据

74f5ea073cb3c540f1c04966cfda2612.png

在新搜索业务内,我们首次引入了多楼层结构,每个楼层的卡片数据与页码管理都是隔离的,为此,我们将楼层 数据抽象成一个 Combo,那么新的 Result 如下

9b3eb0ce6b0b08ae25e68a4228925269.png

  渲染层统一

原先的 XSearch 框架内,SRP、XSL和新搜索都有一套各自的UI渲染框架,究其原因有以下几点

  1. 旧容器无法支持所有业务场景

  2. 数据结构不统一,导致无法复用渲染逻辑

  3. 渲染逻辑与业务逻辑强耦合,导致拓展性差

第一点我们基于 MetaLayout 构建渲染框架,就可以解决。而第二点上文已经处理了,那么我们还需要解决第三点,需要将渲染和业务逻辑进行分离。

新的渲染框架包含以下内容:

c4a8ddeb50bc4b80afe93ef354304aa4.png

我们只需要基于抽象的数据结构进行各个部分的渲染即可。

  逻辑串联与拓展

在完成了渲染组件之后,我们还需要处理业务逻辑。SRP 和 XSL 的业务逻辑较为简单,可以归纳为以下几点

  1. 列表数据支持刷新,例如触发排序、筛选等

  2. 列表支持翻页

  3. 各tab 数据隔离,tab 打开时请求对应 tab 的数据

其中,XSL 分端上发请求和前端发请求,需要特殊处理。

而新搜索与老搜索的业务逻辑完全不同,最明显的区别是

  1. 老搜索各个 tab 之间完全独立,每个 tab 的数据都是在tab 打开时才去请求的

  2. 新搜索的tab 分为主 tab 和子 tab,页面打开时获取主 tab 数据,子 tab 数据由主 tab 内的各个对应楼层决定

基于以上业务特点以及拓展性的考量,我们抽象出以下接口

91c93b3d8704308549a95072fbd7551e.png

根据不同的业务场景,我们实现对应的 Controller。以主搜场景为例,在新的框架下,新老搜索合为一体,共用一个 MainSearchController。

在初始化请求完成后,根据服务端返回的业务字段,决定后续执行新搜索的业务逻辑还是老搜索的业务逻辑。

对于新老搜索的不同的 tab 数据管理,我们可以在 【onCreateWidgetModel】回调中进行处理,老搜索只会创建初始数据源,并且在后续tab 打开时发起初始化请求。而新搜索在创建数据源的同时,会将对应楼层的数据进行一次深拷贝,作为首屏数据,而后续打开 tab 则不会触发初始化请求。

f74ad70e5828bb9617593dece0c9b03f.jpeg

总结

基于以上内容,我们可以得到以下架构图:

fc80093899ed36319218002443aa33ad.png

架构统一的好处有以下几点

  1. 提高代码复用率,降低包大小

  2. 渲染层特性统一,当新增功能时,srp 和 xsl 可以同时使用,不需要重复开发

  3. 架构分层更加清晰,业务逻辑与渲染层解耦,降低维护成本,提高拓展性

  4. View 层统一,新 header 特性在 srp 和 xsl 可以同时使用,例如在新框架下,xsearchlist 也能支持长颈鹿

目前我们已经完成了主搜索的架构升级,优化了部分功能的体验,以下是展示环节。

  新框架主搜

5a7a8189753cc516290917087f4e1f0f.gif

  支持排序条置顶后弹出


  • 旧框架

在旧框架下,用户点击排序或者前置筛选的下拉框浮层,不支持吸顶后再弹出,导致在一些小屏幕机型上,下拉浮层会被屏幕底部遮住,不方便使用。

a560c24f355795b69223669081cbe2f5.gif


  • 新框架

4e4c3d61e483ed7237fbb5f3ca137a9c.gif

  支持场景层滑动与列表联动


  • 旧框架

在旧框架下,用户如果手指在场景层快速滑动抬起,动画会卡在商品卡顶部,无法传递到商品卡区域。

0f2037faa78b88db1dcf04af922e4c46.gif


  • 新框架

996f84b6bc11de93607a54cc656134bd.gif

b2f394a3d3989213f167a3b2a389e447.jpeg

团队介绍

我们是大淘宝技术搜索推荐移动端团队,负责集团核心电商搜索推荐,图像视频搜索业务研发、技术平台建设、新业务和前沿技术探索等工作,我们负责的业务拥有亿级流量,能为您提供巨大的机遇和成长空间,期待您的加入。感兴趣的同学可将简历发送到 taozi.ly@taobao.com

¤ 拓展阅读 ¤

3DXR技术 | 终端技术 | 音视频技术

服务端技术 | 技术质量 | 数据算法

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/Taobaojishu/article/details/128015258

淘系技术,实力为2019年双11而战!稳!-爱代码爱编程

2019 天猫双 11 全球狂欢节96秒成交额破百亿,24小时总成交额2684亿,创造了交易创建峰值 54.4万笔/秒的历史记录。天猫双11,已经不仅是购物节,也是品牌成长节、消费者互动节。天猫双11更是是商业的奥林匹克。 今年,双11走到了第11个年头,史无前例的,阿里经济体内有49支技术团队共同参与作战,也是第一次,双11的核心系统将1

直播预告:淘宝因果表征学习与模型泛化实践_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

直播预约 10月19日,19:00-20:30,大淘宝技术与DataFun联合策划了本次活动,邀请大淘宝技术两位算法工程师就因果表征学习和模型泛化相关主题进行深度分享与交流,欢迎大家按时收看直播~ 视辰 大淘宝技术 高级算法专家 个人介绍:大淘宝技术,视觉基础算法,主要负责直播、点淘、首猜、逛逛和中台的内容分类标签与内容负向治理。 演讲

“当迷茫时就回到原点,或者退一步再思考” | 技术人金句系列_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

工程师的内心,开放又包容~他们不给人设限也不给事设限。他们大胆思考,小心求证,随时准备在新领域学习新东西,然后慢慢越懂越多。 今天,我们想分享来自大淘宝技术工程师们的《人间清醒语录》,这些金句里凝结了他们多年实践经验的智慧,希望可以给你“打怪升级”的过程带来一些帮助。 此篇为本系列第三篇: 第一篇:“从幼稚到成熟,是从不负责任到承担责任的过程

设计模式最佳实践探索—策略模式_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

根据不同的应用场景与意图,设计模式主要分为创建型模式、结构型模式和行为型模式三类。本文主要探索行为型模式中的策略模式如何更好地应用于实践中。 前言 在软件开发的过程中,需求的多变性几乎是不可避免的,而作为一名服务端开发人员,我们所设计的程序应尽可能支持从技术侧能够快速、稳健且低成本地响应纷繁多变的业务需求,从而推进业务小步快跑、快速迭代。设计模

通用feeds组件封装技巧_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

前端交互中,无限滚动feeds流是最常见的交互形式,常见的feeds功能上基本都包括兜底、接口请求、报错提示、刷新、feeds卡片排列规则等。用户滑动页面不断加载feeds数据, DOM节点不断累加创建,因为DOM节点过多,导致页面数据更新时卡顿问题, 一般会通过虚拟滚动减少页面DOM数量来解决。这些必要基础能力在每个feeds流需求中都存在,所以

本周推荐 | 前端架构师的一些思考和总结_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

推荐语:有幸在大淘宝与洋风同学共事了一段时间。他在终端架构侧有丰富的经验,他所打造的终端调试工具 AppDevTools 、终端请求库 mtop.js 在阿里内部被广泛使用。洋风从自己的经验出发,在这篇文章中讲述了前端架构师的工作范畴和如何做好前端架构,相信对于期望往该方向成长的同学会有所帮助。 ——大淘宝技术前端工程师 梧忌 加入大淘宝到现在

如何避免写重复代码:善用抽象和组合_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

通过抽象和组合,我们可以编写出更加简洁、易于理解和稳定的代码;类似于金字塔的建筑过程,我们总是可以在一层抽象之上再叠加一层,从而达到自己的目标。但是在日常的开发工作中,我们如何进行实践呢?本文将以笔者在Akka项目中的一段社区贡献作为引子分享笔者的一点心得。 场景 通常,为了简化我们对数据流的处理,我们可能会使用 Java8 中首次引入的

3d技术在数字藏品中的应用_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

本文通过图文的方式详细介绍了在淘宝App中如何使用3D相关技术,优化淘宝App中的数字藏品的展示。从背景介绍、方案设计、模型预处理,模型处理、脚本操作等过过程出发来介绍,同时重点分析了其中的一些核心技术问题的解法。希望通过这篇文章,能够给初次接触 blender 和 unity 的前端开发同学有一定的启发和参考。 背景&挑战 ▐  

「聚变」前端 & 客户端,第十七届 d2 终端技术大会来了!_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

D2 的前身「前端技术论坛」已经举办了 16 届,相信有很多前端同学对它已经非常了解了。伴随着互联网技术的发展,今年的 D2 全面升级为「阿里巴巴 D2 终端技术大会」: D2 终端技术大会 (Mobile Developer & Frontend Developer Technology Conference, 简称 D2)是

大数据计算系统 blink 在端侧的应用实践_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

本文主要介绍了端侧通过Blink任务对埋点数据进行实时聚合和清洗,解决端侧日志时效性问题,并基于实时日志搭建线上监控运维体系,从而提升端侧整体的稳定性。 Blink简介 介绍 Blink 前需要先认识下 Flink,其最初是柏林工业大学的一个研究性项目(StratoSphere),早期专注于批计算,于2014年捐赠给 Apache 并进行孵

mnn卷积性能提升90%!armv86正式投用_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

ARMv86指令集新增了通用矩阵乘指令与bf16的支持,这些指令理论性能是ARMv82sdot的2倍;使用这些指令实现int8/bf16矩阵乘能够带来显著的性能提升。本文使用ARMv86的新增指令对MNN的ConvInt8和MatMul算子进行实现,最高得到了大约90%的性能提升。 技术背景 为了提升端侧推理速度,降低内存占用,MNN除了支

淘宝拍立淘ios相册架构设计小结_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

推荐语:这篇文章从系统权限、API 调用、架构设计等角度,生动演示了一个设计友好、模块独立、易拓展以及用户体验优秀的相册是如何开发出来的。除此之外,作者针对各种小细节也做了优化和解析,使得功能实现更加的丰满。文章整体读下来,可以让读者对于相册的设计和开发有深刻的印象,具备极大的指导意义,推荐阅读! ——大淘宝技术终端开发工程师 隽弦 前言

第14个天猫双11,技术创新带来消费新体验_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

“技术和商业是密不可分的,今年是第14届天猫双11,在过去的13年里,伴随着商业的快速发展,天猫淘宝的底层技术基础设施得到了深厚的积累,同时也支撑了云计算的大规模发展。未来,我们将通过持续的技术创新和突破,让商家更好的做生意,让用户享受更好买、好逛、好玩的线上体验。” 淘宝天猫CTO若海说。 今年,聚焦“技术引领新消费”的方向,大淘宝技术团队在“

初次接触氛围系统架构,聊聊我这三个月的理解_阿里巴巴淘系技术团队官网博客的博客-爱代码爱编程

本文主要介绍了作者对于氛围中心的业务理解。从氛围的概念出发,阐述了氛围系统的必要性,然后展示了配置端的数据写入、调用端的配置读取等氛围系统的架构细节,最后作者提出了一些对于氛围中心未来的想法和思考。 概述 ▐  氛围的概念 氛围是能够刺激消费者购买欲望的一类视觉表达。氛围一般与优惠、价格相关,其目的在于烘托当前商品所处的营销氛围,促进消费

听8位淘宝工程师聊聊他们眼中的元宇宙 | 1024特辑-爱代码爱编程

我们是技术工作者,致力于思考和创新,用代码去解决生活中的问题,为消费者的快乐和幸福而努力。 今天是1024程序员节,我们邀请了8位不同岗位的淘宝工程师,聊了聊他们眼中的未来消费生活,他们眼中的元宇宙,听一听这群探路者在时代新科技与新文化氛围下,有哪些新的思考。 01 @宋五 ▌工作范围:家居家装行业智能设计&场景化导购团队,

monorepo,大型前端项目管理模式实践-爱代码爱编程

阅读本文您将了解到:什么是 monorepo、为什么要 monorepo、如何实践 monorepo。 项目管理模式 Monorepo 这个词您可能不是首次听说,在当下大型前端项目中基于 monorepo 的解决方案已经深入人心,无论是比如 Google、Facebook,社区内部知名的开源项目 Babel、Vue-next ,还是集团

mapstruct,降低无用代码的神器-爱代码爱编程

在学习《告别BeanUtils,Mapstruct从入门到精通》后,我发觉MapStruct确实是一个提升系统性能,降低无用代码的神器。然而,在实践这篇文章过程中,我遇到了些问题,并由此对MapStruct框架有了更深入的理解,以下将我的学习收获分享给大家。 本文与《告别BeanUtils,Mapstruct入门到精通》的主要不同之处主要在

大淘宝技术 noslate 正式开源 -爱代码爱编程

继 2019 年开源 Midway 框架之后,大淘宝技术一直在 Node.js 的前沿进行深度研究,除了加入 TC39 参与标准化建设,向上游 Node.js 项目持续贡献,与龙蜥社区合作优化之外,也在 Serverless 领域有了不小的成果。 今天,向大家介绍我们最新的面向云原生场景,面向 Serverless 架构下的新产品, 代号

浅析设计模式3 —— 装饰者模式-爱代码爱编程

推荐语:本文从装饰者模式的核心思想到与其他设计模式的横向对比,从代码示例到业务实战,向读者娓娓呈现装饰者模式的真貌。深入浅出的JDK源码透析,使用场景的利弊权衡,真的值得一阅! ——大淘宝技术开发工程师 玄苏 装饰者模式的核心思想是通过创建一个装饰对象(即装饰者),动态扩展目标对象的功能,并且不会改变目标对象的结构,提供了一种比继承更灵