美文网首页微信小程序
微信小程序UI之旅:可分页加载和设置列数的智能瀑布流组件

微信小程序UI之旅:可分页加载和设置列数的智能瀑布流组件

作者: 码途有道 | 来源:发表于2019-05-01 23:47 被阅读0次

前言

在开始正文之前,不得不吐槽一下,小程序的限制好多。本来想在自定义组件中使用 slot插槽 ,让组件更加灵活的,结果 slot 并不能和 for循环列表一起配合使用,当 slot 写在循环体中时,只能被渲染一次,而使用 抽象节点 来代替的话,每次定制循环都要自定义组件,则更加麻烦,希望微信能做些优化。

瀑布流结构

瀑布流是一种很常见的布局,为了以后可以更加简便的使用,我们在本文会将瀑布流布局封装成一个组件。首先,先介绍一下本文的瀑布流的实现原理。瀑布流有很多种实现方法,我们选用一种较为简单的实现,将每一列瀑布流都作为一个竖向的列表,那么整个瀑布流就是由多个竖向的列表排列组成。然后我们只要将拿到的数据进行分列处理,分成多组数据,交由对应列的的瀑布流进行渲染即可。

瀑布流展示

两列瀑布流 三列瀑布流

自定义属性和方法

自定义属性 描述
column-count 瀑布流的列数
horizontal-space 列与列之间的间距(rpx)
vertical-space 上下 item 之间的间距(rpx)
page-no 当前页的页码(主要用于分页加载)
page-src 当前页的未分组的数据源
placeholder 图片加载时的占位图
error 图片加载失败时显示的图片

注:page-no 默认为0,如果没有分页加载的需要,不需要做任何修改,page-src直接接收数据即可。如果有分页加载,page-no 则为当前页码数(从0开始算),page-src 则为当前页的数据数组。

自定义方法 描述
binditemtap 瀑布流 item 的点击监听

github传送门:https://github.com/albert-lii/wx-abui/tree/master/abui/widgets/ab-waterfall
demo传送门:https://github.com/albert-lii/wx-abui/tree/master/pages/waterfall

使用示例

  • waterfall.wxml
<view class="container">
  <ab-waterfall class="waterfall" page-no="{{pageNo}}" page-src="{{pageSrc}}" column-count="3" binditemtap="itemTap" />
</view>
  • waterfall.wxss
page {
  background: #ececec;
}

.waterfall {
  width: 100%;
  padding:14rpx;
  box-sizing: border-box;
}
  • waterfall.js
const WATERFALL_SOURCE = require('../../utils/waterfall_source.js');

Page({
  data: {
    pageNo: 0,
    pageSrc: []
  },

  onLoad: function(options) {
    this.setData({
      pageSrc: WATERFALL_SOURCE.source
    });
  },

  itemTap:function(e){
    console.log(e)
    wx.showToast({
      icon:'none',
      title:e.detail.dataset.item.title,
      duration:1200
    });
  },

  onPullDownRefresh: function() {
    wx.showNavigationBarLoading();
    this.setData({
      pageSrc: WATERFALL_SOURCE.source
    }, () => {
      wx.hideNavigationBarLoading();
      wx.stopPullDownRefresh();
    });
  },

  onReachBottom: function() {
    let _pageNo = this.data.pageNo + 1;
    this.setData({
      pageNo: _pageNo,
      pageSrc: this.data.pageSrc
    });
  }
})

源码

注:源码中,还引用了 ab-easy-image 组件,如需使用此图片组件,需要另外引入。

  • ab-waterfall.wxml
<view class="waterfall">
  <view class="waterfall-column" style="width:{{columnWidth}}rpx;margin-right:{{columnNo<(columnArr.length-1)?horizontalSpace:0}}rpx;" wx:for-index="columnNo" wx:for-item="pageArr" wx:for="{{columnArr}}" wx:key="{{columnNo}}">
    <block wx:for-index="pageNo" wx:for-item="dataArr" wx:for="{{pageArr}}" wx:key="{{pageNo}}">
      <view class="waterfall-column-item" wx:for-index="itemNo" wx:for-item="item" wx:for="{{dataArr}}" wx:key="{{itemNo}}">
        <ab-easy-image class="waterfall-column-item__image" style="height:{{item.imgH}}rpx;" src="{{item.imgUrl}}" mode="aspectFill" radius="4px 4px 0 0" placeholder="{{placehodler}}" error="{{error}}" />
        <view class="waterfall-column-item__bottom">{{item.title}}</view>
      </view>
    </block>
  </view>
</view>
  • ab-waterfall.wxss
.waterfall {
  display: flex;
  flex-direction: row;
  justify-content: center;
  width: 100%;
}

.waterfall-column {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.waterfall-column-item {
  position: relative;
  display: flex;
  flex-direction: column;
  width: 100%;
  margin-bottom: 14rpx;
}

.waterfall-column-item__image {
  width: 100%;
  font-size: 0;
}

.waterfall-column-item__bottom {
  display: flex;
  flex-direction: column;
  align-content: center;
  width: 100%;
  font-size: 23rpx;
  color: #2e2e2e;
  background: white;
  border-radius: 0 0 4px 4px;
  padding: 8rpx 18rpx 8rpx 18rpx;
  box-sizing: border-box;
}
  • ab-waterfall.js
Component({
  properties: {
    // 列数
    columnCount: {
      type: Number,
      value: 2
    },
    // 列与列之间的间距(rpx)
    horizontalSpace: {
      type: Number,
      value: 14
    },
    // 上下 item 之间的间距(rpx)
    verticalSpace: {
      type: Number,
      value: 14
    },
    // 当前页(仅在启用了分页加载时有效)
    pageNo: {
      type: Number,
      value: 0
    },
    // 当前页的未分组的数据源
    pageSrc: {
      type: Array,
      value: [],
      observer: function(newVal, oldVal) {
        if (this.data.pageNo == 0) {
          this._init(newVal);
        } else {
          // let _addlist = newVal.slice(oldVal.length,newVal.length);
          this._splitData(newVal);
        }
      }
    },
    // 图片加载时的占位图
    placeholder: {
      type: String,
      value: null
    },
    // 图片加载失败时显示的图片
    error: {
      type: String,
      value: null
    }
  },

  data: {
    // 列宽
    columnWidth: 354,
    // 分组后的数据源
    columnArr: [],
    // 记录每一列的高度
    columnHeights: []
  },

  methods: {
    /**
     * 初始化
     */
    _init: function(list) {
      let _columnArr = [];
      let _columnHeights = [];
      for (let i = 0, len = this.data.columnCount; i < len; i++) {
        _columnArr[i] = [];
        _columnHeights[i] = 0;
      }
      this.setData({
        columnArr: _columnArr,
        columnHeights: _columnHeights
      });

      // 列数
      let _columnCount = this.data.columnCount;
      // 列与列之间的间距
      let _hspace = this.data.horizontalSpace;
      let _query = wx.createSelectorQuery();
      let _page = this;
      _query.select('.waterfall').boundingClientRect();
      // 计算列的宽度
      _query.exec(function(rect) {
        if (rect == null || rect[0] == null) {
          return;
        }
        let _systemInfo = wx.getSystemInfoSync();
        let _screenWidth = _systemInfo.windowWidth;
        let _containerWidth = rect[0].width / _screenWidth * 750;
        console.log(rect)
        let _columnWidth = (_containerWidth - (_columnCount - 1) * _hspace) / _columnCount;
        _page.setData({
          columnWidth: _columnWidth
        });
        _page._splitData(list);
      });
    },
    /**
     * 分割数据源
     * 
     * @param list 新增加的数据
     */
    _splitData: function(list) {
      // 列数
      let _columnCount = this.data.columnCount;
      // 记录每一列的高度,用于判断 item 应该添加在哪一列
      //(因为一般情况下,item 中图片之外的高度基本是固定的,所以此处只计算图片高度)
      let _columnHeights = this.data.columnHeights;
      let _columnArr = new Array(this.data.columnCount);
      for (let i in list) {
        let _item = list[i];
        // 原始图片的宽高 
        let _oriImgW = parseInt(_item.imgW);
        let _oriImgH = parseInt(_item.imgH);
        // item 中图片的宽高
        let _imgW = this.data.columnWidth;
        let _imgH = parseInt(_oriImgH * _imgW / _oriImgW);
        _item['imgH'] = _imgH;
        // 最短的列的序号
        let _minNo = 0;
        // 最短的列的高度
        let _minCH = _columnHeights[0];
        // 分列添加 item
        for (let j in _columnHeights) {
          if (_columnHeights[j] < _minCH) {
            _minNo = j;
            _minCH = _columnHeights[j];
          }
        }
        _columnHeights[_minNo] += _imgH;
        if (_columnArr[_minNo] == undefined || _columnArr[_minNo] == null) {
          _columnArr[_minNo] = [];
        }
        _columnArr[_minNo].push(_item);
      }
      for (let i in _columnArr) {
        this.setData({
          ['columnArr[' + i + '][' + this.data.pageNo + ']']: _columnArr[i]
        });
      }
    }
  },
})
  • ab-waterfall.json
{
  "component": true,
  "usingComponents": {
    "ab-easy-image": "../ab-easy-image/ab-easy-image"
  }
}

相关文章

网友评论

    本文标题:微信小程序UI之旅:可分页加载和设置列数的智能瀑布流组件

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