美文网首页Weex专栏大前端-BFEWeex开发
自定义Weex组件——Weex的学习之路(八)

自定义Weex组件——Weex的学习之路(八)

作者: 安卓搬砖小曾 | 来源:发表于2019-06-04 16:52 被阅读5次

    在四月份和五月份的时候我用业余时间来学习weex,在这期间一直在看文档写demo,每一个组件都自己写demo运行一遍。我本人是做Android开发的,对JS,CSS和Html有一定的了解,所以学习weex不是很难。然后我把自己所学的主要经历和过程用博客记录下来,其目的是想巩固自己的学习知识,还有就是希望能给weex学习的小伙伴一点帮助。最近有一部分小伙伴私信我说,问我weex好学吗、weex学了之后有用吗、weex现在是不是没有人维护了,还有weex和flutter哪个更好?针对这些问题,我后续会专门用一篇博客来统一回答的。不过我还是想说的就是好不好学、有没有用,只有等你学了以后才会知道!

    到了这篇博客,我目前weex所有的组件和模块都使用过了,那么我发现,有一些weex原生组件最终在安卓和苹果终端上显示和交互的交过不一样,有一些UI效果内置组件达不到的时候,就需要自定义组件了。自定义组件主要分为两大步骤:

    1.重写内置组件

    前面几篇博客写的都是比较基础的,那么从这篇博客开始,复杂度就会高一些的。就拿TabPage来说吧,他原生是长这个样子的

    TabPage原生样式

    我们在实际项目中样式也不定和这个相同,所以这个时候我们就需要将源码拷贝下来,根据自己的需求改动代码。还是以TabPage为例,我这里的需求是顶部的tab只有两个,并且这两个tab不是按权重等分的,而是相对来说比较靠近的,并且在下方还要有一条分割线。那么我将TabPage的源码从GitHub上拷贝下来。拷贝地址是:TabPage源码地址

    经过改动后的代码:

    <template>
      <div class="wxc-tab-page"
           :style="{ height: (tabPageHeight)+'px', backgroundColor:wrapBgColor }">
        <scroller class="tab-title-list"
                  ref="tab-title-list"
                  :show-scrollbar="false"
                  scroll-direction="horizontal"
                  :data-spm="spmC"
                  :style="{
                    backgroundColor: tabStyles.bgColor,
                    height: (tabStyles.height)+'px',
                    leftOffset:'100px',
                    rightOffset: '100px'
                  }">
    
          <div class="title-item"
               v-for="(v,index) in tabTitles"
               :key="index"
               :ref="'wxc-tab-title-'+index"
               @click="setPage(index,v.url, clickAnimation)"
               :style="{
                 width: tabStyles.width +'px',
                 height: tabStyles.height +'px',
                 backgroundColor: currentPage === index ? tabStyles.activeBgColor : tabStyles.bgColor,
                 borderBottomWidth: tabStyles.normalBottomHeight,
                 borderBottomColor: tabStyles.normalBottomColor
               }"
               :accessible="true"
               :aria-label="`${v.title?v.title:'标签'+index}`">
    
            <image :src="currentPage === index ? v.activeIcon : v.icon"
                   v-if="titleType === 'icon' && !titleUseSlot"
                   :style="{ width: tabStyles.iconWidth + 'px', height:tabStyles.iconHeight+'px'}"/>
    
            <text class="icon-font"
                  v-if="titleType === 'iconFont' && v.codePoint && !titleUseSlot"
                  :style="{fontFamily: 'wxcIconFont',fontSize: tabStyles.iconFontSize+'px', color: currentPage === index ? tabStyles.activeIconFontColor : tabStyles.iconFontColor}">{{v.codePoint}}</text>
    
            <text
              v-if="!titleUseSlot"
              :style="{ fontSize: tabStyles.fontSize+'px', fontWeight: (currentPage === index && tabStyles.isActiveTitleBold)? 'bold' : 'normal', color: currentPage === index ? tabStyles.activeTitleColor : tabStyles.titleColor, paddingLeft:(tabStyles.textPaddingLeft?tabStyles.textPaddingLeft:10)+'px', paddingRight:(tabStyles.textPaddingRight?tabStyles.textPaddingRight:10)+'px'}"
              class="tab-text">{{v.title}}</text>
            <div class="border-bottom"
                 v-if="tabStyles.hasActiveBottom && !titleUseSlot"
                 :style="{ width: tabStyles.activeBottomWidth+'px', left: (tabStyles.width-tabStyles.activeBottomWidth)/2+'px', height: tabStyles.activeBottomHeight+'px', backgroundColor: currentPage === index ? tabStyles.activeBottomColor : 'transparent' }"></div>
            <slot :name="`tab-title-${index}`" v-if="titleUseSlot"></slot>
    
          </div>
    
          <div v-if="tabStyles.hasRightIcon"
               class="rightIcon"
               :style="{
                top: rightIconStyle.top,
                right: rightIconStyle.right,
                paddingLeft: rightIconStyle.paddingLeft,
                paddingRight: rightIconStyle.paddingRight
               }">
            <slot name="rightIcon"></slot>
          </div>
    
        </scroller>
        <div class="tab-page-wrap"
             ref="tab-page-wrap"
             @horizontalpan="startHandler"
             :style="{ height: (tabPageHeight-tabStyles.height)+'px' }">
          <div ref="tab-container"
               class="tab-container">
            <slot></slot>
          </div>
          <div class="line"></div>
        </div>
      </div>
    </template>
    
    <style scoped>
    .wxc-tab-page {
      width: 750px;
    }
    .tab-title-list {
      flex-direction: row;
      justify-content: center;
      align-items: center;
    }
    .title-item {
      justify-content: center;
      align-items: center;
      border-bottom-style: solid;
    }
    .border-bottom {
      position: absolute;
      bottom: 0;
    }
    .tab-page-wrap {
      width: 750px;
      overflow: hidden;
    }
    .tab-container {
      flex: 1;
      flex-direction: row;
      position: absolute;
    }
    .tab-text {
      lines: 1;
      text-overflow: ellipsis;
    }
    .rightIcon {
      position: fixed;
      background-color: #ffffff;
      box-shadow: -50px 0 20px #ffffff;
    }
    .line {
      background-color: #ebebeb;
      width: 750px;
      height: 2px;
    }
    </style>
    
    <script>
    const dom = weex.requireModule("dom");
    const animation = weex.requireModule("animation");
    const swipeBack = weex.requireModule("swipeBack");
    import { Utils, BindEnv } from "weex-ui";
    //   import BindEnv from 'weex-ui';
    import Binding from "weex-bindingx/lib/index.weex.js";
    export default {
      props: {
        tabTitles: {
          type: Array,
          default: () => []
        },
        panDist: {
          type: Number,
          default: 200
        },
        spmC: {
          type: [String, Number],
          default: ""
        },
        titleUseSlot: {
          type: Boolean,
          default: false
        },
        tabStyles: {
          type: Object,
          default: () => ({
            bgColor: "#FFFFFF",
            titleColor: "#666666",
            activeTitleColor: "#3D3D3D",
            activeBgColor: "#FFFFFF",
            isActiveTitleBold: true,
            iconWidth: 70,
            iconHeight: 70,
            width: 160,
            height: 120,
            fontSize: 24,
            hasActiveBottom: true,
            activeBottomColor: "#FFC900",
            activeBottomWidth: 120,
            activeBottomHeight: 6,
            textPaddingLeft: 10,
            textPaddingRight: 10,
            leftOffset: 0,
            rightOffset: 0,
            normalBottomColor: "#F2F2F2",
            normalBottomHeight: 0,
            hasRightIcon: false
          })
        },
        titleType: {
          type: String,
          default: "icon"
        },
        tabPageHeight: {
          type: [String, Number],
          default: 1334
        },
        needSlider: {
          type: Boolean,
          default: true
        },
        isTabView: {
          type: Boolean,
          default: true
        },
        duration: {
          type: [Number, String],
          default: 300
        },
        timingFunction: {
          type: String,
          default: "cubic-bezier(0.25, 0.46, 0.45, 0.94)"
        },
        wrapBgColor: {
          type: String,
          default: "#f2f3f4"
        },
        clickAnimation: {
          type: Boolean,
          default: true
        },
        rightIconStyle: {
          type: Object,
          default: () => ({
            top: 0,
            right: 0,
            paddingLeft: 20,
            paddingRight: 20
          })
        }
      },
      data: () => ({
        currentPage: 0,
        gesToken: 0,
        isMoving: false,
        startTime: 0,
        deltaX: 0,
        translateX: 0
      }),
      created() {
        const { titleType, tabStyles } = this;
        if (titleType === "iconFont" && tabStyles.iconFontUrl) {
          dom.addRule("fontFace", {
            fontFamily: "wxcIconFont",
            src: `url(${tabStyles.iconFontUrl})`
          });
        }
      },
      mounted() {
        if (swipeBack && swipeBack.forbidSwipeBack) {
          swipeBack.forbidSwipeBack(true);
        }
        if (BindEnv.supportsEBForIos() && this.isTabView && this.needSlider) {
          const tabPageEl = this.$refs["tab-page-wrap"];
          Binding.prepare &&
            Binding.prepare({
              anchor: tabPageEl.ref,
              eventType: "pan"
            });
        }
      },
      methods: {
        next() {
          let page = this.currentPage;
          if (page < this.tabTitles.length - 1) {
            page++;
          }
          this.setPage(page);
        },
        prev() {
          let page = this.currentPage;
          if (page > 0) {
            page--;
          }
          this.setPage(page);
        },
        startHandler() {
          if (BindEnv.supportsEBForIos() && this.isTabView && this.needSlider) {
            this.bindExp(this.$refs["tab-page-wrap"]);
          }
        },
        bindExp(element) {
          if (element && element.ref) {
            if (this.isMoving && this.gesToken !== 0) {
              Binding.unbind({
                eventType: "pan",
                token: this.gesToken
              });
              this.gesToken = 0;
              return;
            }
            const tabElement = this.$refs["tab-container"];
            const { currentPage, panDist } = this;
            const dist = currentPage * 750;
            // x-dist
            const props = [
              {
                element: tabElement.ref,
                property: "transform.translateX",
                expression: `x-${dist}`
              }
            ];
            const gesTokenObj = Binding.bind(
              {
                anchor: element.ref,
                eventType: "pan",
                props
              },
              e => {
                const { deltaX, state } = e;
                if (state === "end") {
                  if (deltaX < -panDist) {
                    this.next();
                  } else if (deltaX > panDist) {
                    this.prev();
                  } else {
                    this.setPage(currentPage);
                  }
                }
              }
            );
            this.gesToken = gesTokenObj.token;
          }
        },
        setPage(page, url = null, animated = true) {
          if (!this.isTabView) {
            this.jumpOut(url);
            return;
          }
          if (this.isMoving === true) {
            return;
          }
          this.isMoving = true;
          const previousPage = this.currentPage;
          const currentTabEl = this.$refs[`wxc-tab-title-${page}`][0];
          const { width } = this.tabStyles;
          const appearNum = parseInt(750 / width);
          const tabsNum = this.tabTitles.length;
          const offset = page > appearNum ? -(750 - width) / 2 : -width * 2;
          if (appearNum < tabsNum) {
            (previousPage > appearNum || page > 1) &&
              dom.scrollToElement(currentTabEl, {
                offset,
                animated
              });
            page <= 1 &&
              previousPage > page &&
              dom.scrollToElement(currentTabEl, {
                offset: -width * page,
                animated
              });
          }
          this.isMoving = false;
          this.currentPage = page;
          this._animateTransformX(page, animated);
          this.$emit("wxcTabPageCurrentTabSelected", { page });
        },
        jumpOut(url) {
          url && Utils.goToH5Page(url);
        },
        _animateTransformX(page, animated) {
          const { duration, timingFunction } = this;
          const computedDur = animated ? duration : 0.00001;
          const containerEl = this.$refs[`tab-container`];
          const dist = page * 750;
          animation.transition(
            containerEl,
            {
              styles: {
                transform: `translateX(${-dist}px)`
              },
              duration: computedDur,
              timingFunction,
              delay: 0
            },
            () => {}
          );
        }
      }
    };
    </script>
    

    其中顶部tab的数据源改动后代码 :

    /**
     * Created by Tw93 on 2016/11/4.
     */
    
    export default {
    
        tabTitles: [
          {
            title: '出借中',
            icon: 'https://gw.alicdn.com/tfs/TB1MWXdSpXXXXcmXXXXXXXXXXXX-72-72.png',
            activeIcon: 'https://gw.alicdn.com/tfs/TB1kCk2SXXXXXXFXFXXXXXXXXXX-72-72.png',
          },
          {
            title: '已完成',
            icon: 'https://gw.alicdn.com/tfs/TB1ARoKSXXXXXc9XVXXXXXXXXXX-72-72.png',
            activeIcon: 'https://gw.alicdn.com/tfs/TB19Z72SXXXXXamXFXXXXXXXXXX-72-72.png'
          }
        ],
        tabStyles: {
          bgColor: '#FFFFFF',
          titleColor: '#666666',
          activeTitleColor: '#3D3D3D',
          activeBgColor: '#FFFFFF',
          isActiveTitleBold: true,
          iconWidth: 70,
          iconHeight: 70,
          width: 280,
          height: 100,
          fontSize: 36,
          hasActiveBottom: true,
          activeBottomColor: '#FFC900',
          activeBottomHeight: 6,
          activeBottomWidth: 120,
          textPaddingLeft: 10,
          textPaddingRight: 10,
          normalBottomColor: '#fb4343',
          normalBottomHeight: 0,
          hasRightIcon: true,
          rightOffset: 0,
    
        },
        // 使用 iconfont 模式的tab title配置
        tabIconFontTitles: [
          {
            title: '首页',
            codePoint: '\ue623'
          },
          {
            title: '特别推荐',
            codePoint: '\ue608'
          },
          {
            title: '消息中心',
            codePoint: '\ue752',
            badge: 5
          },
          {
            title: '我的主页',
            codePoint: '\ue601',
            dot: true
          }
        ],
        tabIconFontStyles: {
          bgColor: '#FFFFFF',
          titleColor: '#666666',
          activeTitleColor: '#3D3D3D',
          activeBgColor: '#FFFFFF',
          isActiveTitleBold: true,
          width: 160,
          height: 120,
          fontSize: 24,
          textPaddingLeft: 10,
          textPaddingRight: 10,
          iconFontSize: 50,
          iconFontColor: '#333333',
          iconFontMarginBottom: 8,
          activeIconFontColor: 'red',
          iconFontUrl: '//at.alicdn.com/t/font_501019_mauqv15evc1pp66r.ttf'
        }
      }
    

    2.在页面中引用

    上面我们将源码修改好了,那么接下来就是在weex页面中编写了。引用自定义的组件很关键。

    1)在<script>标签中导入自定义的组件

    import tabTitleList from "@/utils/tabTitleList";
    import ZengTabPage from "@/components/zeng-tab-page";
    

    注意:这里一定要注意自定义组件的命名规则

    2)在<template>标签中使用

    <zeng-tab-page ref="wxc-tab-page"
                    :tab-titles="tabTitles"
                    :tab-styles="tabStyles"
                    title-type="text"
                    :tab-page-height="tabPageHeight"
                    @wxcTabPageCurrentTabSelected="wxcTabPageCurrentTabSelected">
    
        <list v-for="(v,index) in tabList"
              :key="index"
              class="item-container"
              :style="{ height: (tabPageHeight - tabStyles.height) + 'px' }">
          <!-- <cell class="border-cell"></cell> -->
          <cell v-for="(demo,key) in v"
                class="cell"
                :key="key">
                <!-- url="https://h5.m.taobao.com/trip/ticket/detail/index.html?scenicId=2675" -->
            <wxc-pan-item :ext-id="'1-' + (v) + '-' + (key)"
                          @wxcPanItemClicked="wxcPanItemClicked(demo,key)"
                          @wxcPanItemPan="wxcPanItemPan">
             <div class="content">
                <text>{{demo.productName}}</text>
             </div>
            </wxc-pan-item>
          </cell>
        </list>
      </zeng-tab-page>
    

    说明:自定义组件在源码的基础上改动的话,原来自带的属性,比如:tab-titles="tabTitles"、:tab-styles="tabStyles"、title-type="text"这些属性是不会变的。

    最后我们将项目进行编译运行如图:

    自定义TabPage样式

    由于代码较多,就不把全部代码贴出来,在开发中遇到坑的同学可以评论中讨论。生命不止,学习不停!

    相关文章

      网友评论

        本文标题:自定义Weex组件——Weex的学习之路(八)

        本文链接:https://www.haomeiwen.com/subject/eurrxctx.html