Cookbook-如何实现全屏左右滑动的效果

Jul 8, 2021

全屏左右滑动是快应用开发中比较常用的一个功能。一般使用场景是结合分类信息来左右滑动切换分类,展示不同分类下的信息;或者左右滑动切换不同的 tab 页(首页、我的页面等)

本篇文章简单介绍全屏左右滑动的几种不同实现方式。

首先,我们先设置一些模拟数据,后面所有的示例都会使用这些模拟数据

export const tabList = [
  "热榜",
  "推荐",
  "原创",
  "科技",
  "娱乐",
  "财经",
  "民生",
  "宠物",
  "教育",
  "旅游",
];

使用 tabs + tab-bar + tab-content 实现

实现全屏左右滑动的效果最简单的方式是使用引擎提供的 tabs + tab-bar + tab-content 三件套的方式来实现

tabs中封装了常见功能和效果:页签支持横向滚动,支持手势滑动切换内容页等

tabs内部仅支持子组件tab-bartab-content,也可以只包含一个子组件,使用说明如下:

  • tab-bar组件用来包含所有页签的标题,属性mode用来配置是否可滚动
  • tab-content组件用来包含所有页签的内容
  • tab-bar组件的第 n 个直接子节点对应tab-content中第 n 个直接子节点,具有联动效果

首先,我们新建一个 ux 文件,并且添加上 ux 文件的通用内容

<template>
  <div></div>
</template>

<script></script>

<style></style>

然后,在 script 标签里面,引入定义好的模拟数据

<script>
import { tabList } from '../../../helper/const.js'

export default {
  data() {
    return {
      list: tabList
    }
  }
}
</script>

之后,在 template 标签中,使用 tabs + tab-bar + tab-content 三个标签和模拟数据,实现全屏左右滑动的效果

<template>
  <div>
    <tabs>
      <tab-bar mode="scrollable">
        <div class="bar-item" for="list">
          <text>{{ $item.title }}</text>
        </div>
      </tab-bar>
      <tab-content>
        <div for="list" class="content">
          <demo-list></demo-list>
        </div>
      </tab-content>
    </tabs>
  </div>
</template>

注意,tab-bar 的 mode 为 scrollable 时,子组件宽度为设置宽度,当宽度之和大于 tab-bar 宽度,子组件可以横向滚动;mode 为 fixed 时,子组件宽度均分 tab-bar 宽度,当宽度之和大于 tab-bar 宽度,子组件依旧均分宽度。如果 tab-bar 的子组件比较多,建议将 mode 设置为 scrollable,否则子组件的展示样式会和设置的 css 样式不一致

此时已经可以实现全屏左右滑动的效果了,但是无法分辨当前展示的是哪一个页签,交互体验不够友好,所以需要增加一些样式让我们能够知道当前展示的是哪一个页签。为此,我们需要先获取到当前处于活跃状态的 index。修改 script 标签里的内容,增加一个 curIdx 变量用于保存当前活跃的 index,并且在 tabs 的 change 处理事件中修改 curIdx 的值

<script>
import { tabList } from '../../../helper/const.js'

export default {
  data() {
    return {
      list: tabList,
      curIdx:0,
    }
  },

  changeTab(e){
    this.curIdx = e.index
  }
}
</script>

然后修改 template 中的内容,给当前处于活跃状态的 tab-bar 的文字设置不一样的文字颜色

<template>
  <div>
    <tabs onchange="changeTab">
      <tab-bar mode="scrollable" class="tab-bar">
        <div class="bar-item" for="list">
          <text style="color: {{curIdx === $idx ? '#456fff' : '#000000'}};">{{
            $item
          }}</text>
          <div
            style="background-color: {{curIdx === $idx ? '#456fff' : '#ffffff'}};"
            class="line"
          ></div>
        </div>
      </tab-bar>
      <tab-content>
        <div for="list" class="content">
          <demo-list></demo-list>
        </div>
      </tab-content>
    </tabs>
  </div>
</template>

这样,就能明显提升全屏左右滑动过程中的交互体验了

完整的代码如下

<import name="demo-list" src="../../../components/demoList.ux"></import>

<template>
  <div>
    <tabs onchange="changeTab">
      <tab-bar mode="scrollable" class="tab-bar">
        <div class="bar-item" for="list">
          <text style="color: {{curIdx === $idx ? '#456fff' : '#000000'}};">{{
            $item
          }}</text>
          <div
            style="background-color: {{curIdx === $idx ? '#456fff' : '#ffffff'}};"
            class="line"
          ></div>
        </div>
      </tab-bar>
      <tab-content>
        <div for="list" class="content">
          <demo-list></demo-list>
        </div>
      </tab-content>
    </tabs>
  </div>
</template>

<script>
import { tabList } from "../../../helper/const.js";

export default {
  data() {
    return {
      list: tabList,
      curIdx: 0,
    };
  },

  changeTab(e) {
    this.curIdx = e.index;
  },
};
</script>

<style lang="less">
@import url("../../../assets/styles/style.less");

.tab-bar {
  border-bottom: 1px solid @border-color;
  height: 7 * @size-factor;
}

.bar-item {
  padding: 0 2 * @size-factor;
  flex-direction: column;
  align-items: center;
  text {
    font-size: 3 * @size-factor;
    line-height: 3 * @size-factor;
  }
  .line {
    width: 5 * @size-factor;
    height: 0.5 * @size-factor;
    margin-top: 1 * @size-factor;
    border-radius: 0.5 * @size-factor;
  }
}

.content {
  justify-content: center;
  text {
    font-size: 5 * @size-factor;
  }
}
</style>

使用 tab-content 加 list 实现

tabs内部可以只可以使用tab-bar或者tab-content,假设开发者需要在页签一侧增加一个按钮,同时页签需要和内容联动。由于tabs仅支持子组件tab-bartab-content,且tab-bartab-content的直接子元素都被当做页签或内容页。因此,仅使用tabs无法实现在页签右侧增加一个图标按钮。

这时,我们可以仅使用 tabs+tab-content,再用 list 来实现页签,并通过 js 代码动态绑定 tabs 的 index 属性来实现页签与内容的联动。

我们简单修改一下上面的代码,将 tab-bar 里的内容用 list 实现,并且在 list 的右侧增加一个按钮图标

<import name="q-icon" src="qaui/src/components/icon/index"></import>
<import name="demo-list" src="../../../components/demoList.ux"></import>

<template>
  <div class="wrap">
    <div class="header">
      <list class="list" id="list">
        <list-item class="bar-item" type="bar-item" for="list">
          <text style="color: {{curIdx === $idx ? '#456fff' : '#000000'}};">{{
            $item
          }}</text>
          <div
            style="background-color: {{curIdx === $idx ? '#456fff' : '#ffffff'}};"
            class="line"
          ></div>
        </list-item>
      </list>
      <div class="shadow"></div>
      <div class="icon-wrap" onclick="clickMenu">
        <q-icon type="menu" size="20"></q-icon>
      </div>
    </div>
    <tabs onchange="changeTab" index="{{curIdx}}">
      <tab-content>
        <div for="list" class="content">
          <demo-list></demo-list>
        </div>
      </tab-content>
    </tabs>
  </div>
</template>

<script>
import { tabList } from "../../../helper/const.js";
import prompt from "@system.prompt";

export default {
  data() {
    return {
      list: tabList,
      curIdx: 0,
    };
  },

  changeTab(e) {
    this.curIdx = e.index;
  },

  clickMenu() {
    prompt.showToast({
      message: "你点击了menu",
    });
  },
};
</script>

<style lang="less">
@import url("../../../assets/styles/style.less");

.wrap {
  flex-direction: column;
}

.header {
  border-bottom: 1px solid @border-color;
}
.list {
  flex-direction: row;
  height: 7 * @size-factor;
}

.icon-wrap {
  height: 7 * @size-factor;
  padding: 1 * @size-factor 2 * @size-factor;
}

.shadow {
  width: 1 * @size-factor;
  height: 7 * @size-factor;
  background: linear-gradient(270deg, #f8f8f8 0%, #ffffff 100%);
}

.bar-item {
  padding: 0 2 * @size-factor;
  flex-direction: column;
  align-items: center;
  text {
    font-size: 3 * @size-factor;
    margin-top: 1.2 * @size-factor;
  }
  .line {
    width: 5 * @size-factor;
    height: 0.5 * @size-factor;
    margin-top: 1 * @size-factor;
    border-radius: 0.5 * @size-factor;
  }
}

.content {
  justify-content: center;
  text {
    font-size: 5 * @size-factor;
  }
}
</style>

注意,list 默认是竖向排列的,这里我们需要给 list 的 class 类里面加上flex-direction: row,让 list 可以横向排列

此时内容左右滑动时,页签无法跟着一起滚动,体验不是很好;同时,点击页签也无法切换内容。所以我们需要做一些修改,让页签和内容能够联动起来。

首先,我们给页签添加一个点击事件,同时将当前页签的 index 作为参数传递给事件处理函数,然后将页签的 index 赋值给 curIdx 变量,并且将 curIdx 变量绑定到 tabs 组件的 index 属性上。这样,就可以实现点击页签切换内容了。最后,在 tabs 的 change 事件的处理函数里面,将页签 list 中当前活跃的页签项滚动到中间。

<import name="q-icon" src="qaui/src/components/icon/index"></import>
<import name="demo-list" src="../../../components/demoList.ux"></import>

<template>
  <div class="wrap">
    <div class="header">
      <list class="list" id="list">
        <list-item
          class="bar-item"
          type="bar-item"
          for="list"
          onclick="clickTab($idx)"
        >
          <text style="color: {{curIdx === $idx ? '#456fff' : '#000000'}};">{{
            $item
          }}</text>
          <div
            style="background-color: {{curIdx === $idx ? '#456fff' : '#ffffff'}};"
            class="line"
          ></div>
        </list-item>
      </list>
      <div class="shadow"></div>
      <div class="icon-wrap" onclick="clickMenu">
        <q-icon type="menu" size="20"></q-icon>
      </div>
    </div>
    <tabs onchange="changeTab" index="{{curIdx}}">
      <tab-content>
        <div for="list" class="content">
          <demo-list></demo-list>
        </div>
      </tab-content>
    </tabs>
  </div>
</template>

<script>
import { tabList } from "../../../helper/const.js";
import prompt from "@system.prompt";

export default {
  data() {
    return {
      list: tabList,
      curIdx: 0,
    };
  },

  changeTab(e) {
    this.curIdx = e.index;
    this.$element("list").scrollTo({
      index: this.curIdx - 2 >= 0 ? this.curIdx - 2 : 0,
      behavior: "smooth",
    });
  },

  clickTab(idx) {
    this.curIdx = idx;
  },

  clickMenu() {
    prompt.showToast({
      message: "你点击了menu",
    });
  },
};
</script>

最终的效果如下,页签和内容能够正确联动,并且点击页签也能够正确切换内容

正确联动

使用 swiper 加 list 实现

其实,内容部分除了使用 tabs 之外,也可以使用 swiper。

使用 swiper 承载内容部分有两个好处,一个是内容部分可以自动滚动,二是可以循环滚动,这在某些场景下十分有用。

使用 swiper 承载内容也有一些需要注意的点

  • swiper 默认高度自适应内容,所以需要将其高度设为 100%,才能占满剩余空间
  • swiper 默认会展示 indicator,如果不需要需要将 swiper 的 indicator 属性设置为 false

我们将上面的代码简单修改一下,将 tabs 及 tabcontent 替换为 swiper 组件

<import name="q-icon" src="qaui/src/components/icon/index"></import>
<import name="demo-list" src="../../../components/demoList.ux"></import>

<template>
  <div class="wrap">
    <div class="header">
      <list class="list" id="list">
        <list-item
          class="bar-item"
          type="bar-item"
          for="list"
          onclick="clickTab($idx)"
        >
          <text style="color: {{curIdx === $idx ? '#456fff' : '#000000'}};">{{
            $item
          }}</text>
          <div
            style="background-color: {{curIdx === $idx ? '#456fff' : '#ffffff'}};"
            class="line"
          ></div>
        </list-item>
      </list>
      <div class="shadow"></div>
      <div class="icon-wrap" onclick="clickMenu">
        <q-icon type="menu" size="20"></q-icon>
      </div>
    </div>
    <swiper
      class="swiper"
      onchange="changeTab"
      index="{{curIdx}}"
      indicator="false"
      loop="true"
      autoplay="true"
    >
      <div for="list" class="content">
        <demo-list></demo-list>
      </div>
    </swiper>
  </div>
</template>

<script>
import { tabList } from "../../../helper/const.js";
import prompt from "@system.prompt";

export default {
  data() {
    return {
      list: tabList,
      curIdx: 0,
    };
  },

  changeTab(e) {
    this.curIdx = e.index;
    this.$element("list").scrollTo({
      index: this.curIdx - 2 >= 0 ? this.curIdx - 2 : 0,
      behavior: "smooth",
    });
  },

  clickTab(idx) {
    this.curIdx = idx;
  },

  clickMenu() {
    prompt.showToast({
      message: "你点击了menu",
    });
  },
};
</script>

<style lang="less">
@import url("../../../assets/styles/style.less");

.wrap {
  flex-direction: column;
}

.header {
  border-bottom: 1px solid @border-color;
}

.swiper {
  height: 100%;
}

.list {
  flex-direction: row;
  height: 7 * @size-factor;
}

.icon-wrap {
  height: 7 * @size-factor;
  padding: 1 * @size-factor 2 * @size-factor;
}

.shadow {
  width: 1 * @size-factor;
  height: 7 * @size-factor;
  background: linear-gradient(270deg, #f8f8f8 0%, #ffffff 100%);
}

.bar-item {
  padding: 0 2 * @size-factor;
  flex-direction: column;
  align-items: center;
  text {
    font-size: 3 * @size-factor;
    margin-top: 1 * @size-factor;
  }
  .line {
    width: 5 * @size-factor;
    height: 0.5 * @size-factor;
    margin-top: 1 * @size-factor;
    border-radius: 0.5 * @size-factor;
  }
}

.content {
  justify-content: center;
  text {
    font-size: 5 * @size-factor;
  }
}
</style>

此时的效果如下,内容能够自动滚动并且能够循环滚动

使用swiper

使用 QaUI 的 tabs 组件实现

除了使用快应用提供的原生组件之外,也可以使用封装好的一些组件库来实现这个功能。例如,我们可以使用 QaUI 的 tabs 组件来实现这个功能。对代码做一下简单修改

<import name="q-tabs" src="qaui/src/components/tabs/index"></import>
<import name="demo-list" src="../../../components/demoList.ux"></import>

<template>
  <div>
    <q-tabs
      index="0"
      type="default"
      data="{{ list }}"
      active-color="#456fff"
      background="#FFF"
      active-background="#FFF"
      ontap="tap"
    >
      <block for="{{ list }}">
        <demo-list></demo-list>
      </block>
    </q-tabs>
  </div>
</template>

<script>
export default {
  data() {
    return {
      list: [
        {
          label: '热榜'
        },
        {
          label: '推荐'
        },
        {
          label: '原创'
        },
        {
          label: '科技'
        },
        {
          label: '娱乐'
        },
        {
          label: '财经'
        },
        {
          label: '民生'
        },
        {
          label: '宠物'
        },
        {
          label: '教育'
        },
        {
          label: '旅游'
        }
      ]
    }
  }
}
</script>

<style></style>

注意:使用组件库时需要注意下使用的组件对于数据的格式是否有一些特殊的要求

此时的效果如下,QaUI 的 tabs 组件还支持很多其他的配置项,具体的可以去看官方文档

使用swiper

体验 Sample

包管理平台二维码

扫码上面的二维码即可体验本文提到的相关 Sample

或者点击右侧链接查看源代码https://rpk.quickapp.cn/s/7cbb4d57ae6443c390a14b4ae4c744b2

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.