精华内容
下载资源
问答
  • uniapp我的经历 解决加载图片 闪烁出现问题
    千次阅读
    2020-11-18 10:30:03

    使用image的@load,使其加载好再进行展示
    <image src="/static/img/common/bx11.jpg" class="image12" @load="onoff='1'"></image>

    更多相关内容
  • 瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。 在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形 ...

    瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。 在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形 ...

    瀑布流布局是一种比较流行的页面布局方式,最典型的就是Pinterest.com,每个卡片的高度不都一样,形成一种参差不齐的美感。

    在HTML5中,我们可以找到很多基于jQuery之类实现的瀑布流布局插件,轻松做出这样的布局形式。在微信小程序中,我们也可以做出这样的效果,不过由于小程序框架的一些特性,在实现思路上还是有一些差别的。

    今天我们就来看一下如何在小程序中去实现这种瀑布流布局:

    小程序瀑布流布局

    我们要实现的是一个固定2列的布局,然后将图片数据动态加载进这两列中(而加载进来的图片,会根据图片实际的尺寸,来决定到底是放在左列还是右列中)。

    /* 单个图片容器的样式 */
    .img_item {
      width: 48%;
      margin: 1%;
      display: inline-block;
      vertical-align: top;
    }

    我们知道,在HTML中,我们要动态加载图片的话,通常会使用new Image()创建一个图片对象,然后通过它来动态加载一个url指向的图片,并获取图片的实际尺寸等信息。而在小程序框架中,并没有提供相应的JS对象来处理图片加载。其实我们可以借助wxml中的<image>组件来完成这样的功能,虽然有点绕,但还是能满足我们的功能要求的。

    <!-- 在页面上放一个隐藏区域,并用image组件去加载一个或多个图片资源 -->
    <view style="display:none">
      <image wx:for="{{images}}" wx:key="id" id="{{item.id}}" src="{{item.pic}}" bindload="onImageLoad"></image>
    </view>

    我们可以在Page中通过数据绑定,来传递要加载的图片信息到wxml中,让<image>组件去加载图片资源,然后当图片加载完成的时候,通过bindload指定的事件处理函数来做进一步处理。

    我们来看一下Page文件中定义的onImageLoad函数。在其中,我们可以从传入的事件对象e上,获取到<image>组件的丰富信息,包括通过它加载进来的图片的实际大小。然后我们将图片按照页面上实际需要显示的尺寸,计算出同比例缩放后的尺寸。接着,我们可以根据左右两列目前累积的内容高度,来决定把当前加载进来的图片放到哪一边。

    let col1H = 0;
    let col2H = 0;
    
    Page({
    
        data: {
            scrollH: 0,
            imgWidth: 0,
            loadingCount: 0,
            images: [],
            col1: [],
            col2: []
        },
    
        onLoad: function () {
            wx.getSystemInfo({
                success: (res) => {
                    let ww = res.windowWidth;
                    let wh = res.windowHeight;
                    let imgWidth = ww * 0.48;
                    let scrollH = wh;
    
                    this.setData({
                        scrollH: scrollH,
                        imgWidth: imgWidth
                    });
    
                    //加载首组图片
                    this.loadImages();
                }
            })
        },
    
        onImageLoad: function (e) {
            let imageId = e.currentTarget.id;
            let oImgW = e.detail.width;         //图片原始宽度
            let oImgH = e.detail.height;        //图片原始高度
            let imgWidth = this.data.imgWidth;  //图片设置的宽度
            let scale = imgWidth / oImgW;        //比例计算
            let imgHeight = oImgH * scale;      //自适应高度
    
            let images = this.data.images;
            let imageObj = null;
    
            for (let i = 0; i < images.length; i++) {
                let img = images[i];
                if (img.id === imageId) {
                    imageObj = img;
                    break;
                }
            }
    
            imageObj.height = imgHeight;
    
            let loadingCount = this.data.loadingCount - 1;
            let col1 = this.data.col1;
            let col2 = this.data.col2;
    
            //判断当前图片添加到左列还是右列
            if (col1H <= col2H) {
                col1H += imgHeight;
                col1.push(imageObj);
            } else {
                col2H += imgHeight;
                col2.push(imageObj);
            }
    
            let data = {
                loadingCount: loadingCount,
                col1: col1,
                col2: col2
            };
    
            //当前这组图片已加载完毕,则清空图片临时加载区域的内容
            if (!loadingCount) {
                data.images = [];
            }
    
            this.setData(data);
        },
    
        loadImages: function () {
            let images = [
                { pic: "../../images/1.png", height: 0 },
                { pic: "../../images/2.png", height: 0 },
                { pic: "../../images/3.png", height: 0 },
                { pic: "../../images/4.png", height: 0 },
                { pic: "../../images/5.png", height: 0 },
                { pic: "../../images/6.png", height: 0 },
                { pic: "../../images/7.png", height: 0 },
                { pic: "../../images/8.png", height: 0 },
                { pic: "../../images/9.png", height: 0 },
                { pic: "../../images/10.png", height: 0 },
                { pic: "../../images/11.png", height: 0 },
                { pic: "../../images/12.png", height: 0 },
                { pic: "../../images/13.png", height: 0 },
                { pic: "../../images/14.png", height: 0 }
            ];
    
            let baseId = "img-" + (+new Date());
    
            for (let i = 0; i < images.length; i++) {
                images[i].id = baseId + "-" + i;
            }
    
            this.setData({
                loadingCount: images.length,
                images: images
            });
        }
    
    })

    这里是显示在两列图片的wxml代码,我们可以看到在<scroll-view>组件上,我们通过使用bindscrolltolower设置了事件监听函数,当滚动到底部的时候,会触发loadImages去再加载下一组的图片数据,这样就形成了无限的加载:

    <scroll-view scroll-y="true" style="height:{{scrollH}}px" bindscrolltolower="loadImages">
      <view style="width:100%">
        <view class="img_item">
          <view wx:for="{{col1}}" wx:key="id">
            <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px"></image>
          </view>
        </view>
        <view class="img_item">
          <view wx:for="{{col2}}" wx:key="id">
            <image src="{{item.pic}}" style="width:100%;height:{{item.height}}px"></image>
          </view>
        </view>
      </view>
    </scroll-view>

    好了,挺简单的一个例子,如果你有更好的方法,不吝分享一下哦。

    完整代码可以在我的Github下载:https://github.com/zarknight/wx-falls-layout

    文件下载:wx-falls-layout-master (1).zip
    展开全文
  • 假设是一个图片瀑布流的页面, 当用户浏览瀑布流页面时, 加入由于网速的原因。 在看下面图片的时候, 上面图片突然加载进来, 这时候会使下方的图片自动往下跑, 这个体验肯定很不好。 滚动锚定的策略 通过一个CSS...

    scroll-view 是一个可以滚动的视图区域的容器组件。

    一、重要属性

    在这里插入图片描述
    scroll-view 的滚动属性,实现了两套功能

    1. 左右或上下滚动
    2. 下拉更新

    1.1 与滚动有关的属性:

    • scroll-x 允许横向滚动
    • scroll-y 允许纵向滚动

    纵向滚动
    在这里插入图片描述

    <scroll-view scroll-y style="height: 300rpx;">
    	<view id="demo1" class="scroll-view-item demo-text-1"></view>
    	<view id="demo2" class="scroll-view-item demo-text-2"></view>
    	<view id="demo3" class="scroll-view-item demo-text-3"></view>
    </scroll-view>
    

    横向滚动
    在这里插入图片描述

    <scroll-view class="scroll-view_H" scroll-x style="width: 100%">
    	<view id="demo1" class="scroll-view-item_H demo-text-1"></view>
    	<view id="demo2" class="scroll-view-item_H demo-text-2"></view>
    	<view id="demo3" class="scroll-view-item_H demo-text-3"></view>
    </scroll-view>
    

    双向滚动
    在这里插入图片描述

    <scroll-view bindscroll="onScroll" class="scroll-view_H" scroll-x scroll-y style="width: 100%;height:200rpx;">
    	<view id="demo1" class="scroll-view-item_H demo-text-1">1</view>
    	<view id="demo2" class="scroll-view-item_H demo-text-2">2</view>
    	<view id="demo3" class="scroll-view-item_H demo-text-3">3</view>
    </scroll-view>
    
    .scroll-view_H {
      white-space: nowrap;
    }
    
    .scroll-view-item {
      height: 300rpx;
    }
    
    .scroll-view-item_H {
      display: inline-block;
      width: 100%;
      height: 300rpx;
    }
    
    .demo-text-1 {
      background-color: 	#E6E6FA;
    }
    
    .demo-text-2 {
      background-color: 	#E1FFFF;
    }
    
    .demo-text-3 {
      background-color: #FDF5E6;
    }
    
    • scroll-top 、 scroll-left
      在这里插入图片描述
    这两个属性,它们都是可以通过属性绑定、控制组件行为的属性。
    如果我们想让内部的滚动实体滚动到某个位置,
    并不能直接去调用它的类似于scrollTo的方法。
    我们只能在js里面动态改变scroll-top,scroll-left  这两个属性绑定的变量。
    然后在视图渲染更新以后,组件会自动发生滚动
    
    • scroll-into-view 滚动到某个元素,值应为某子元素id。
    假如同时开启scroll-x、scroll-y横纵这两个方向的滚动,
    当通过scroll-into-view滚动时,
    那么它的滚动行为是怎样变化的呢?
    
    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20210713153829430.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MjU4MDcwNA==,size_16,color_FFFFFF,t_70)
    
    通过测试结果来看,结论很不明确,
    如果不加scroll-with-animation的话,
    也就是不开启动画,可以同时在x、y方向上瞬时移动到目标位置。
    
    如果开启动画,同一时间只能在一个方向上滚动,
    有时候在x方向滚动,有时候在y方向滚动,行为很不明确。
    
    scroll-x scroll-y 最好不要同时开启,
    如果功能需要,我们可以基于view实现同样的功能
    或者是先在x方向上开启,完成移动后,再在y方向上开启,依次进行
    
     <view class="page-section">
    	<view class="page-section-title">
    		<text>9 测试scroll-into-view滚动</text>
    	</view>
    	<view class="page-section-spacing">
    		<scroll-view enable-flex scroll-into-view="{{scrollIntoViewId}}" bindscroll="onScroll" scroll-y scroll-x scroll-with-animation="{{false}}" style="width: 100%;height:300rpx;">
    			<view id="childview{{item}}0" wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9,10]}}" class="scroll-row">{{item}}
    				<view wx:for="{{[0, 1, 2, 3, 4, 5,6,7,8,9]}}" class="scroll-item" id="childview{{item}}{{item2}}" wx:for-item="item2">{{item}}:{{item2}}</view>
    			</view>
    		</scroll-view>
    	</view>
    	<view class="btn-area">
    		<button type="primary" bindtap="scrollToView1">滚动到子组件2</button>
    	</view>
    </view> 
    
    data: {
      scrollIntoViewId: '',
    },
    scrollToView1() {
      viewId += 2
      this.setData({
        scrollIntoViewId: 'childview' + viewId
      })
      console.log(this.data.scrollIntoViewId)
    },
    
    • scroll-anchoring 滚动锚定,默认false,控制滚动位置不随内容的变化而抖动的。
    滚动锚定是什么?
    假设是一个图片瀑布流的页面,
    当用户浏览瀑布流页面时,
    加入由于网速的原因。
    在看下面图片的时候,
    上面图片突然加载进来,
    这时候会使下方的图片自动往下跑,
    这个体验肯定很不好。
    
    
    滚动锚定的策略
    通过一个CSS样式控制滚动实体在内容变化的时候不发生滚动。
    scroll-anchoring 就是干这个的。
    
    
    `这个属性某种情况下,可能会给开发者带来意想不到的bug`,
    这个页面可能会陷入一种自循环,表现出一种`抖动不止的现象`。
    当出现这个现象的时候,简单的解决方法就是`关闭滚动锚定策略`,
    或者`设置一个具有相同效果的样式`
    overflow-anchor:none;
    
    
    
    scroll-anchoring 该属性目前`小程序只支持IOS``android手机需要自己通过CSS处理
    overflow-anchor:auto;`
    

    1.2 scroll-view组件的下拉更新属性

    - upper-threshold
    - lower-threshold  
      这两个属性是为了控制scrolltoupper、scrolltolowe事件何时派发的。默认都是50px
    - bindscrolltoupper
    - bindscrolltolower 
    	这两个事件是状态事件,upper-threshold为50的时候,当scroll-top小于50的时候,
    	只要滚动行为发生着bindscrolltoupper事件会多次派发,并且这种派发基本上是随心所欲的,
    	派发基本是没有规律的,所以`说基于crolltoupper、scrolltolower这两个事件写业务逻辑的时候,
    	我们要注意特别判断一下,是否已经处理过了,以免造成重复的处理。
    	不是说滚动一次派发一次,有可能是滚动一次派发多次`- bindscroll
    

    在这里插入图片描述

    <!-- 测试scrolltoupper的随心所欲  -->
    
    <view class="page-section">
        <view class="page-section-title">
            <text>片12 测试scrolltoupper的随心所欲</text>
        </view>
        <view class="page-section-spacing">
            <scroll-view upper-threshold="50" bindscrolltoupper="viewScrollToUpperEvent" scroll-y style="height: 300rpx">
                <view id="demo1" class="scroll-view-item demo-text-1"></view>
                <view id="demo2" class="scroll-view-item demo-text-2"></view>
                <view id="demo3" class="scroll-view-item demo-text-3"></view>
            </scroll-view>
        </view>
    </view>
    
    
    upper-threshold="50"
    scroll-top在小于等于这个值的时候派发bindscrolltoupper事件
    
    
    .page-section-spacing {
      margin-top: 60rpx;
    }
    .scroll-view-item {
      height: 300rpx;
    }
    
    .demo-text-1 {
      background-color: 	#E6E6FA;
    }
    
    .demo-text-2 {
      background-color: 	#E1FFFF;
    }
    
    .demo-text-3 {
      background-color: #FDF5E6;
    }
    
    viewScrollToUpperEvent(e) {
        console.log('测试scrolltoupper事件', e.detail);
    },
    

    二、自定义实现下拉刷新

    2.1 与自定义下拉刷新相关的属性

    - refresher-enabled      boolean,默认false,是否开启自定义下拉刷新
    - refresher-threshold    number,触发下拉更新的临界值
    - refresher-triggered    boolean,默认false,它是为了在更新后取消下拉更新的状态,当组件处于下拉更新的状态后,它的值变为true,此时程序要去做一些异步耗时的事情,例如网络加载,待处理完成了,再将这个值设置为false
    - bindrefresherpulling
    - bindrefresherrefresh
    - bindrefresherrestore
    - bindrefresherabort   后面4个事件是自定义实现下拉动画效果的关键
    

    2.2 使用wxs实现自定义下拉刷新

    bindrefresherpulling  手指按住往下拉的过程中派发的,自定义的动画效果要在这个事件里面处理
    
    当向下拉动时,区域慢慢的放大,同时箭头图标有一个方向的翻转。
    
    主要是做了三件事情,
    第一,计算拉到哪里了,占总量高度80的多少,找到icon图标,设置它的旋转角度
    第二,找到下拉动画的容器,设置它的缩放,达到看起来越往下拉,容器越来越大的一个效果。
    第三,当拉到refresher-threshold 临界值时,改变下拉更新的提示文本
    
    <!-- 自定义下拉更新 -->
    <!-- 使用wxs自定义实现下拉刷新-->
    <!--module="refresh"可以让我们在WXML中引用-->
    <wxs module="refresh">
        var pullingMessage = "下拉刷新"
    
        module.exports = {
            // onPulling 下拉的过程当中我们干什么事情
            /*
            主要是做了三件事情,
            第一,计算拉到哪里了,占总量高度80的多少,找到icon图标,设置它的旋转角度
            第二,找到下拉动画的容器,设置它的缩放,达到看起来越往下拉,容器越来越大的一个效果。
            第三,当拉到refresher-threshold 临界值时,改变下拉更新的提示文本
            */
            onPulling: function (e, instance) { // instance 传进来的页面的实例对象
                // 80的高度,因为refresher-threshold设置的是80,手指按住往下拉的状态
                var p = Math.min(e.detail.dy / 80, 1) // 目前拉倒哪里了,进度 不大于1
                // console.log(p)
                // 这里在视图层,不怕频繁操作DOM
                var icon = instance.selectComponent('#refresherIcon') // 图标 WeUi组件
                icon.setStyle({
                    opacity: p, // 透明度,越往下拉越清晰
                    transform: "rotate(" + (90 + p * 180) + "deg)" // 旋转角度
                })
                var view = instance.selectComponent('.refresh-container') // 拉动的动画本身的容器
                view.setStyle({
                    opacity: p,
                    transform: "scale(" + p + ")" // 设置缩放
                })
                //拉到80的高度,可以释放状态
                if (e.detail.dy >= 80) {
                    if (pullingMessage == "下拉刷新") {
                        pullingMessage = "释放更新"
                        instance.callMethod("setData", { // wxs 调用js的setData方法
                            pullingMessage
                        })
                    }
                }
            },
            // 此时手拉开了,进入了加载中的状态
            onRefresh: function (e, instance) {
                // 此时手拉开了,进入了加载中的状态
                pullingMessage = "更新中"
                console.log(pullingMessage)
                instance.callMethod("setData", {
                    pullingMessage: pullingMessage,
                    refresherTriggered: true
                })
                instance.callMethod("willCompleteRefresh", {}) //调用js方法
                //willCompleteRefresh 方法
            },
            onAbort: function (e, instance) {
                // 异常状态,例如被事件突然打断,事件包括电话等,被迫松手了
                pullingMessage = "下拉刷新"
                console.log(pullingMessage)
            },
            onRestore: function (e, instance) {
                // 回去了,松手了,恢复原位了,不刷了
                pullingMessage = "下拉刷新"
                console.log(pullingMessage)
            },
    
        }
    </wxs>
    
    
    
    这是一段WXS代码,是在视图层执行的,在这里基本上可以肆意操作更新视图,
    而不用担心更新频繁操作导致开销太大影响性能
    
    在我们的代码里面之所以用callMethod方法,调用页面主体的setData方法,
    就是为了曲线救国,达到更新视图的目的。
    每个WXS代码里的事件句柄函数,在执行的时候都有两个参数传递进来,
    事件对象与当前页面的实例对象,如果没有这两个参数,动画就实现不了。
    
    WXS是在视图层里面执行的,js文件中的js代码是在逻辑层执行的。
    WXS是WeXin Script的简写,它有自己的语法。严格按照官方文档去写。
    
    <view class="page-section">
        <view class="page-section-title">自定义下拉刷新</view>
        <!-- 
            bindrefresherpulling  下拉的过程当中我们干什么事情
    
            bindrefresherrefresh   当它拉到可以松手的状态的时候
    
            refresher-triggered  为true是小程序设定的,异步操作完成之后,要设置为false,下拉状态就自己回去了
         -->
        <scroll-view scroll-y style="width: 100%; height: 400px;overflow-anchor:auto;" bindscroll="onScroll"
            bindscrolltoupper="onScrolltoupper" scroll-top="{{scrollTopValue}}" scroll-into-view="{{scrollIntoViewId}}"
            scroll-with-animation enable-back-to-top enable-flex scroll-anchoring refresher-enabled
            refresher-threshold="{{80}}" refresher-default-style="none" refresher-background="#FFF"
            bindrefresherpulling="{{refresh.onPulling}}" bindrefresherrefresh="{{refresh.onRefresh}}"
            bindrefresherrestore="{{refresh.onRestore}}" bindrefresherabort="{{refresh.onAbort}}"
            refresher-triggered="{{refresherTriggered}}">
            <!-- slot="refresher"  写死的名字,写其他的不行  -->
            <view slot="refresher" class="refresh-container"
                style="display: block; width: 100%; height: 80px; background: #F8f8f8; display: flex; align-items: center;">
                <view class="view1"
                    style="position: absolute; text-align: center; width: 100%;display:flex;align-items:center;justify-content:center;color:#888;">
                    <mp-icon id="refresherIcon" icon="arrow" color="#888" size="{{20}}"
                        style="margin-right:5px;transform:rotate(90deg)"></mp-icon>
                    <text style="min-width:80px;text-align:left;">{{pullingMessage}}</text>
                </view>
            </view>
    
            <view wx:for="{{arr}}" id="view{{item+1}}" style="display: flex;height: 100px;">
                <text style="position:relative;top:5px;left:5px;color:black;">{{item+1}}</text>
                <image src="https://p.qqan.com/up/2021-6/16232893151517011.jpg"></image>
                <image src="https://p.qqan.com/up/2021-6/16232893724729414.jpg"></image>
                <image src="https://p.qqan.com/up/2021-6/16232893157513212.jpg"></image>
            </view>
        </scroll-view>
        <view class="btn-area">
            <button bindtap="plusScrollUpValue">向上滚动</button>
            <button bindtap="scrollToView1">滚动到子视图</button>
            <button bindtap="unshiftOnePic">顶部添加一张图</button>
        </view>
    
    </view>
    

    bindrefresherrestore 事件,是状态恢复了,是设置了refresher-triggered 为false动画完成以后派发的事件。
    bindrefresherabort 是下拉行为被打断时派发的事件,正常情况下这种事件是不会收到的

      data: {
        pullingMessage: '下拉刷新', //下拉刷新,释放更新,加新中...
        refresherTriggered: false, //
      },
        willCompleteRefresh() {
        console.log('更新中')
        // ... 的动画
        let intervalId = setInterval(() => {
          let pullingMessage = this.data.pullingMessage
          console.log(pullingMessage, pullingMessage == '更新中')
          if (pullingMessage.length < 7) {
            pullingMessage += '.'
          } else {
            pullingMessage = '更新中'
          }
          this.setData({
            pullingMessage
          })
        }, 500)
        // 2s 1.清理定时器  2.setData  
        setTimeout(() => {
          console.log('更新完成了')
          clearInterval(intervalId)
          this.setData({
            pullingMessage: "已刷新",
            refresherTriggered: false,   
            // 为true是小程序设定的,异步操作完成之后,要设置为false,下拉状态就自己回去了
          })
        }, 2000)
      },
    
    bindrefresherrefresh事件,它是组件进入更新中状态时派发的事件,我们需要一个定时器,模拟网络异步加载,
    但是WXS没有定时器,它只有一个页面实例对象的requestAnimationFrame函数,要么使用requestAnimationFrame方法模拟一个定时器,要么在js中实现
    
    在js中定义willCompleteRefresh的方法,然后在WXS里面,在合适的时机,通过callMethod去调用它。
    
    在这个组件中,willCompleteRefresh主要做了两件事情,
    第一,使用一个定时器模拟实现 "更新中..." 后面的 ... 跳动的动画
    第二,通过一个延时定时器在两秒以后设置刷新完成
    

    在这里插入图片描述

    在这里插入图片描述
    在这里插入图片描述

    2.3 总结

    关于下拉刷新组件有两个开源项目,

    mescroll     https://github.com/mescroll/mescroll
    minirefresh   https://github.com/minirefresh/minirefresh
    

    三、最佳实践

    使用scroll-view组件有几点需要我们注意:

    第一点,启用scroll-anchoring 属性时,同时添加一个overflow-anchor:auto;的样式,来应对Android机型不兼容的情况,
    
    第二点,任何时候只开启一个方向的滚动,scroll-x 或者 scroll-y 只取其一,
    当开启scroll-y时,必须给组件一个高度,子组件的高度之和一定要大于这个高度,
    当开启scroll-x时,必须给组件一个宽度,一般这个值是100%,等于屏宽,子组件的宽度之和要大于屏宽,
    在启用scroll-x时,宽度为100%,如果出现不滚动的现象,可以尝试给滚动容器添加两个这样的样式
    white-space:nowrap;  不换行
    display:inline-block;  行内块元素
    目的是让子元素在横向上排列成一排,
    
    第三点,开启enable-flex,这个属性是在scroll-view组件上启用Flex布局的,相当于添加了一个display:flex这样的样式,但是如果是我们自己添加的话,是添加在了外围的容器上,只有通过这个属性添加才能加到内部真正的容器上,
    
    第四点,如果需要使用refresher-enabled 启用下拉动画的自定义,自定义可以很方便实现一些有创意的交互效果,
    下拉动画容器的slot属性要标记为refresher。
    
    第五点,下拉动画组件的背景色用#F8F8F8,前景色包括图标与文本用#888888这个颜色,符合微信设计规范
    
    第六点,尽量不在js代码里面在scroll事件的句柄函数中直接更新视图,`把相关的频繁的更新视图的代码,放在WXS模块中``在大列表视图中`,尤其要这么做。
    

    四、在开发中遇到的问题

    4.1 如何优化使用setData向其传递大数据、渲染长列表?scroll-view追加数据会自动回到顶部,怎么解决?

    //更新二维数组
    const updateList = `tabs[${activeTab}].list[${page}]`
    const updatePage = `tabs[${activeTab}].page`
    this.setData({
            [updateList]:res.data,
            [updatePage]:page + 1
    })
    
    <view wx-for="{{gameListWrap}}" wx:for-item="gameList">
    ......
    </view>
    

    上面的代码中,作者是想实现一个多tab页的功能,
    数据是tabs,gameListWrap是对tabs子数据访问的再封装,
    activeTab、page是模板字符串中的变量
    updateList、updatePage是setData更新的时候用的key,因为是变量,所以需要用中括号

    let  tabData = this.data.tabs[activeTab];
    
    tabData.list.push(res.data);
    tabData.page=page + 1;
    
    let key = `tabs[${activeTab}]`
    this.setData({
            [key]:tabData
    })
    
    作者为什么不直接使用push方法呢?
    当有新数据进来的时候,直接往tab页数据的底部推入新数据,这样不就可以了吗?
    但这种操作有一个问题,setData受限于视图层与逻辑层之间,用于传话的evaluateJavascript函数,
    每次携带的数据大小,官方要求在文本序列化以后,大小不能超过256KB,
    如果每个tab页是一个瀑布流页面,它的tabData.list可能是一个越来越大的数据,很有可能就超过256KB。
    
    
    
    将tab数据与页面数据分开,在当前页面循环渲染时,按照pages[activeTab].page的数字循环,
    取数据的时候依照page当前的值,从gameListData[activeTab]中查取,
    gameListData这个时候在形式上相当于一个数据,但实际上它是一个map。
    
    另外在渲染长列表的时候,微信在WeUI扩展组件库中,给出了一个长列表组件recycle-view,它用于渲染无限长的列表。
    
    
    
    `那么这个问题怎么解决呢?`
    
    使用`recycle-view扩展组件`:
    
    
    https://developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html
    
    
    
    这个长列表实现的原理也很简单,通过监听scroll事件,
    只渲染当前视图窗口的内的list列表,看不见的地方用空白的占位符代替。
    

    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    在使用recycle-view扩展组件的时候,batch属性的值必须为batchSetRecycleData,这是由组件自动管理的。
    在js代码中调用createRecycleContext时,传入的dataKey:recycleList,
    这个名称必须与WXML中的wx:for指定的数据名称一致,
    如果一个页面中还使用了另外一个长列表,则需要再换一个名字。
    

    在这里插入图片描述

    <view class="page-section">
    	<view class="page-section-title">使用recycle-view扩展组件</view>
        <recycle-view height="200" batch="{{batchSetRecycleData}}" id="recycleId" batch-key="batchSetRecycleData" style="background:white;">
            <recycle-item wx:for="{{recycleList}}" wx:key="index" class='item'>
                <view>
                    {{item.id}}: {{item.name}}
                </view>
            </recycle-item>
        </recycle-view>
    </view>
    

    在这里插入图片描述
    在这里插入图片描述

    const createRecycleContext = require('miniprogram-recycle-view')
    
    function rpx2px(rpx) {
      return (rpx / 750) * wx.getSystemInfoSync().windowWidth
    }
    
    onReady: function () {
        
        var ctx = createRecycleContext({
          id: 'recycleId',
          dataKey: 'recycleList',
          page: this,
          itemSize: {
            width: rpx2px(650),
            height: rpx2px(100)
          }
        })
        let newList = []
        for (let i = 0; i < 20; i++) {
          newList.push({
            id: i,
            name: `标题${i + 1}`
          })
        }
        ctx.append(newList)
      },
    

    总结

    当从后端拉取大数据渲染长列表的时候,大多数情况下卡顿并不是手机真的卡了,
    这个时候如果打开App就会发现很流畅。
    很可能这个时候只是试图渲染不及时,
    影响小程序渲染效率的罪魁祸首就是底层的evaluateJavascript这个通信函数,
    它可以说是逻辑层与视图层之间的一个很小的独木桥,
    他无法承载过大的数据量,
    所以我们要尽量减少大数据的渲染,在视图中的互动操作要尽量在WXS代码中去完成
    

    4.2 如何实现购物类小程序,分类选择物品的页面

    在这里插入图片描述

    从效果图看,需要实现两个功能,
    第一,单击左侧菜单,右侧区域自动滚动到相应的位置,
    第二,在右侧滚动的时候,左侧菜单自动同步并高亮显示。
    

    在这里插入图片描述

    <!--实现小程序页面分类选择物品页面  -->
    
    <!-- 左侧菜单 -->
    <scroll-view class='nav' scroll-y='true'>
    	<view wx-for='{{list}}' wx:key='{{item.id}}'  id='{{item.id}}'
    		class='navList{{currentIndex == index ? "active":""}}'  bindTap='menuListOnClick'  data-index='{{index}}'
    	>{{item.name}}</view>
    </scroll-view>
    
    <!-- 右侧内容 -->
    <scroll-view scroll-y='true'  scroll-into-view='{{activeViewId}}' bindscroll='scrollFunc'>
    	<view class='fishList' wx:for='{{content}}' id='{{item.id}}' wx:key='{{item.id}}'>
    		<p>{{item.name}}</p>
    	</view>
    </scroll-view>
    
      //单击左侧菜单
      menuListOnClick(e){
        let me = this;
        me.setData({
          activeViewId:e.target.id,
          currentIndex:e.target.dataset.index
        })
      },
       //滚动时触发,计算当前滚动到的位置对应的菜单是哪个
      scrollFunc(e){
        //看向上滚动了多少
        this.setData({
          scrollTop:e.detail.scrollTop
        });
    
        // 右侧的内容区域有一个heightList
        // 每一块区域高度我们一个一个去对比
        // 看目前滚动大概是处于哪一个高度的范围之内
    
        //我们要事先在渲染的时候,heightList区域高度要事先计算出来并存储出来
        for(let i= 0;i<this.data.heightList.length;i++){
          let height1 = this.data.heightList[i];
          let height2 = this.data.heightList[i + 1];
    
          if(!height2  ||  (e.detail.scrollTop >= height1 && e.detail.scrollTop < height2) ){
              this.setData({
                currentIndex:i  // 设置当前选择的是哪一个
              });
          }
          return ;
        }
    
        this.setData({
          currentIndex: 0
        });
      }
    

    4.3 WeUI组件库中有一个vtabs组件,实现效果同问题 4.2,是一个有侧边栏的浏览组件,使用该组件。

    在这里插入图片描述

    案例

    在这里插入图片描述
    在这里插入图片描述

    vtabs.wxml

    <mp-vtabs 
      vtabs="{{vtabs}}" 
      activeTab="{{activeTab}}" 
      bindtabclick="onTabCLick"
      bindchange="onChange"
      class="test"
    >
      <block wx:for="{{vtabs}}" wx:key="title" >
        <mp-vtabs-content tabIndex="{{index}}">
          <view class="vtabs-content-item">我是第{{index + 1}}项: {{item.title}}</view>
        </mp-vtabs-content>
      </block>
    </mp-vtabs>
    

    vtabs.js

    Page({
      data: {
        vtabs: [],
        activeTab: 0,
      },
    
      onLoad() {
        const titles = ['热搜推荐', '手机数码', '家用电器',
          '生鲜果蔬', '酒水饮料', '生活美食', 
          '美妆护肤', '个护清洁', '女装内衣', 
          '男装内衣', '鞋靴箱包', '运动户外', 
          '生活充值', '母婴童装', '玩具乐器', 
          '家居建材', '计生情趣', '医药保健', 
          '时尚钟表', '珠宝饰品', '礼品鲜花', 
          '图书音像', '房产', '电脑办公']
        const vtabs = titles.map(item => ({title: item}))
        this.setData({vtabs})
      },
    
      onTabCLick(e) {
        const index = e.detail.index
        console.log('tabClick', index)
      },
    
      onChange(e) {
        const index = e.detail.index
        console.log('change', index)
      }
    
    })
    

    vtabs.json

    {
      "usingComponents": {
        "mp-vtabs": "../../components/vtabs/index",
        "mp-vtabs-content": "../../components/vtabs-content/index"
      }
    }
    

    vtabs.wxss

    @import '../common.wxss';
    
    page{
        background-color: #FFFFFF;
        height: 100%;
    }
    
    .vtabs-content-item {
        width: 100%;
        height: 300px;
        box-sizing: border-box;
        border-bottom: 1px solid #ccc;
        padding-bottom: 20px;
    }
    
    

    common.wxss

    @import '../components/weui-wxss/dist/style/weui.wxss';
    
    page{
        background-color: #F8F8F8;
        font-size: 16px;
        font-family: -apple-system-font,Helvetica Neue,Helvetica,sans-serif;
    }
    .page__hd {
        padding: 40px;
    }
    .page__bd {
        padding-bottom: 40px;
    }
    .page__bd_spacing {
        padding-left: 15px;
        padding-right: 15px;
    }
    
    .page__ft{
        padding-bottom: 10px;
        text-align: center;
    }
    
    .page__title {
        text-align: left;
        font-size: 20px;
        font-weight: 400;
    }
    
    .page__desc {
        margin-top: 5px;
        color: #888888;
        text-align: left;
        font-size: 14px;
    }
    .weui-cell_example:before{
        left:52px;
    }
    /* .weui-btn{width:184px;} */
    

    组件源码
    在这里插入图片描述
    在这里插入图片描述

    展开全文
  • 目录1、scroll-view 相关问题2、应用场景3、主要属性讲解 3.1,scroll-x、scroll-y,scroll-top、scroll-left、scroll-into-view 3.2,滚动锚定:scroll-anchoring 3.3,upper-threshold、lower-threshold、...

    目录

    1、scroll-view 相关问题2、应用场景3、主要属性讲解  3.1,scroll-x、scroll-y,scroll-top、scroll-left、scroll-into-view  3.2,滚动锚定:scroll-anchoring  3.3,upper-threshold、lower-threshold、bindscrolltoupper、bindscrolltolower、bindscroll  3.4,refresher-enabled、refresher-threshold、refresher-triggered、bindrefresherpulling、bindrefresherrefresh、bindrefresherrestore、bindrefresherabort4、示例代码与最佳实践  4.1、示例代码:  4.2、实践建议:5、开发者经常遇到哪些问题?  5.1,使用 scroll-view 时,如何优化使用 setData 向其传递大数据、渲染长列表?  5.2,scroll-view 开启自定义下拉刷新,scroll-view 里面内容太少无法触发刷新?  5.3,scroll-view 在 ios 中下拉刷新,触发两次 bindscrolltoupper 事件?  5.4,scroll-view 组件为什么有时候 scroll-x 不作用?  5.5,scroll-view 中两个 scroll-x 和 scroll-y 同时启用有 bug?  5.6,什么情况下需要使用 scroll-view 的下拉刷新,而不使用页面本身的下拉刷新?  5.7,scroll-view 内不支持嵌套原生组件吗?  5.8、如何实现购物类小程序分类选物品页面?6、如何在小程序中使用 WeUI 组件库?阶段源码参考文献

    文 / 石桥码农

    ==本文约 21126 字,阅读 22 分钟==

    说什么真理无穷,进一寸有一寸的欢喜。大家好,我是石桥码农,今天继续为大家分享微信小程序实践相关的技术内容。

    一个框架内每个组件的设计,都有设计者的考虑,每个组件都有其特殊的用途。如果说view的存在,主要是为了实现各种常见的ui布局,那么今天分享的,是「三动」容器组件之一的scroll-view。它与movable-view、cover-view,是三动组件,都是为了方便开发者实现特定场景下的特殊业务功能而设计的。

    没有这些组件,开发者自己通过view也能实现这些功能;但有了这些组件,实现起来简单了,学习成本也高了。特别当组件的设计过于随心所欲时,学习者的学习负担也更大了

    1、scroll-view 相关问题

    ecbbc976e579a1238b7d00dfad9ee085.gif

    scroll-view是可滚动视图区域组件。这个组件几乎是每一个复杂的多页面小程序都会用的,是使用最广泛的组件之一,但也是在社区被开发者最广为诟病的组件之一。

    关于这个组件,有以下几个问题值得思考:

    1,当我们说滚动时,涉及到外面的滚动容器与里面的可滚动实体两个对象,我们说滚动到顶部、滚动到底部,指的是什么呢?是什么到顶部,什么到底部了?

    2,当滚动事件派发时,滚动到顶部是一个状态,还是一个单一的事件,它会触发多次吗?

    3,scrolltoupper事件、scrolltolower事件是什么时候触发的?直接改变scroll-top属性可以触发吗?

    4,设置scroll-into-view这个属性,可以将内容盒子滚动到某个子元素处,具体是滚动到哪里呢?如何理解这个属性?

    5,如果一个瀑布流页面中有许多图,上面的图比下面的图加载慢,当看到下面图的时候,上面的图突然加载出来,把下面的图挤跑了,这种情况有没有办法解决?是什么技术?

    6,有时候在一个后台vue页面中,没有人动它,它自己抖动不止,这可能是什么情况?

    7,如何在scroll-view中自定义实现一个下拉刷新交互动画?

    8,使用scroll-view实现瀑布流功能时,如果页面比较卡顿,可以朝哪个方向优化?

    9,在一些列表中,有时候出于性能考虑,可能需要故意放置一个空白、不显示的子项。空白子项虽然无形中增加了软件包的size,但是也默默提高了性能。

    10,在一些购物类或订餐类小程序中,左侧有物品分类,左侧是物品列表,单击分类,右侧自动滚动到相关位置,右侧列表上下滚动,左侧分类菜单自动切换,获得高亮焦点,这样的功能是怎么实现的?

    如果这些问题你都比较明白,这个组件相关的内容就没必要看了。

    2、应用场景

    在某购物App上,有这样一个功能:

    6aa74e4f634860f6691664f99f3e1d86.png

    因为导航按钮太多,产品人员将非常用的按钮放在了第二屏,需向左滚动才可以看到。

    在这个地方,有一个实际内容宽度大于手机屏幕的容器,它支持用户用手指左右滑动。下方还有一个滚动提示条,这是根据滚动位置计算出来的。这是自定义实现的效果,相当于浏览器的滚动条,效果是通过css样式控制的。

    3、主要属性讲解

    scroll-view是一个略显复杂的组件。它的属性主要支持了两个功能:左右滚动与下拉更新。

    3.1,scroll-x、scroll-y,scroll-top、scroll-left、scroll-into-view

    scroll-x、scroll-y默认都是false,不开启滚动。当scroll-y为真时,允许纵向滚动;当添加scroll-x属性时,允许横向滚动。

    9e4e04d08a0f53f8b3a917e10eee7ebc.gif

    在这张动图中,上面启用的是scroll-x,下方启用的是scroll-y。由于手机屏幕比较窄,横向滚动需求比较常见。

    从实践结果看,scroll-x与scroll-y不是一对互斥的属性,并不是设置了scroll-y,就不能设置scroll-x。两个方向的滚动可以同时开启,但在操作时,只能同时朝一个方向滚动。

    scroll-top指内部的滚动实体,高于顶部边缘多少距离。单位默认是px,也可以传入rpx。默认情况下scroll-top是0,当实体向上滚动时,其值慢慢增加。

    7d4f9d56e415fc9e61e87ec5b19092f3.png

    同理,scroll-left类似。当开启的是横向滚动时,scroll-left是距离左边界、子实体向左滚动的距离。

    我们一般说「滚动到顶部、滚动到底部」,指的还不是内部滚动实体滚动到了它所能达到的最大值、最小值,而是指滚动实体顶部边缘到达了滚动外框的顶部,及底滚动实体底部边缘到达了滚动外框的底部。都是以滚动外框为参照物的。

    同样scroll-top、scroll-left这两个属性,它们也是以滚动外框的位置为参照物的。

    像scroll-top、scroll-left这两个属性,它们是通过属性绑定、控制组件行为的属性。如果我们想让滚动实体滚动到某个位置,并不能直接调用它的一个类似于scrollTo()的方法。我们只能在JS里动态改变scroll-top、scroll-left这两个属性绑定的变量,然后视图渲染后,组件会自动发生滚动。

    在vue、小程序中到处都是这样的响应式控制机制,不是直接去调用页面上组件的方法,而只是给组件属性设置一个值,然后静静地等待组件自己更新。

    在软件设计中,一般我们为一个对象定义一个类,这个类既有方法,又有属性。我们将这个类实例化,既可以改变实例的属性,又可以调用实例的方法;并且在大多数情况下,我们改变属性时,并不会使实例发生什么行为,而只有明确调用它的方法时,它才会有所动作。

    现在在前端这一块,像vue、小程序这样的框架,把这个传统给颠覆了。直接传一个值,让组件自己负责更新,这样看起来更简单。但是在复杂的业务逻辑中,如果能直接能调用组件的方法,可能会更简单一些,因为那样连用于属性绑定的变量都不需要声明了。有时候这种声明是完全没有必要的。

    与scroll-top、scroll-left类似的属性,还有scroll-into-view,它用于滚动到某个元素。这个属性很好理解,它的值必须是一个子视图的id,滚动时微信小程序是以子视图的上、左边界为测算依据的。也就是说,纵向滚动,使scroll-top等于子视图的上边界;横向滚动,使scroll-left等于子视图的左边界。

    这是一个语法糖属性,它帮助开发者做了一些事情。没有这个属性,我们通过id查找组件,找到组件的上、左边距离上、左滚动边框的距离,通过设置scroll-top、scroll-left属性,同样可以达到目的。

    官方文档说,在使用scroll-into-view时,「设置哪个方向可滚动,则在哪个方向滚动到该元素」。

    这里有一个问题,前面我们知道了scroll-x、scroll-y这两个布尔属性并不互斥,假如我们同时开启横向、纵向滚动,当通过scroll-into-view向某个子view滚动时,滚动行为是怎么样的?

    是先向x方向滚动,还是先向y方向滚动?还是两个方向同时滚动?

    答案是小程序错乱了,它既不会同时滚动,也不会先后依次滚动。

    程序都是人编出来的,功能也都是有边界的,没有编写过那部分代码,自然也不会那部分功能。

    3.2,滚动锚定 scroll-anchoring

    这个属性非常值得一提。它是控制「滚动锚定」特征的,即控制滚动位置不随内容变化而抖动,这种情况据说在用户浏览行为中占比1%。这个属性默认是false,添加后,功能才会开启。

    什么是滚动锚定?

    假设我们有一个图片瀑布流页面,这样的页面在网站上有许多,随处在一个设计网站上都可以看到。

    用户浏览瀑布时,假如由于网速原因,在看下面的图片时,上面的图片突然加载出来。这时候因为上面的图片会使下方的图片自动往下跑。

    这个体验肯定很不好。

    为了解决这个1%的问题。谷歌提出了「滚动锚定」策略,即通过一个css样式,控制滚动实体在内容变化时不发生滚动

    微信小程序scroll-anchoring这个属性,就是干这个用的。它是一个布尔属性,添加它以后,当上面内容扩充时,微信会自动向上滚动一段扩充的距离。这就是「滚动锚定」策略。不是没有滚动,而是滚动冲抵了,scroll-top已经不一样了。

    但是这个属性在某种情况下会给开发者带来意想不到的bug。

    vue作为响应式框架,视图自动响应数据更新而重新渲染。假设在某个后台vue项目中,如果恰巧某个滚动实体监听了滚动事件,在滚动发生时自动干了一个改变滚动内容的事。这件事可能很小,只是改变一个边框、或一处字体1个px的大小,但是由于启用了滚动锚定,这个页面可能陷入一种自循环,发生抖动不止的现象。

    当出现这样的「抖动永动机」时,简单解决的方法,就是关闭「滚动锚定」策略,或设置一个这样的样式:

    overflow-anchor:none;

    同时,开启这个策略才可以通过样式开启。scroll-anchoring这个属性,目前小程序只支持iOS手机,在Android手机上需要开发者自己处理。在Android手机上可以添加这样的样式实现相同的功能:

    overflow-anchor: auto;

    3.3,upper-threshold、lower-threshold、bindscrolltoupper、bindscrolltolower、bindscroll

    upper-threshold、lower-threshold这两个属性,是用于控制scrolltoupper和scrolltolower事件的,默认都是50px。

    当scroll-top小于upper-threshold时,组件派发scrolltoupper事件;同理,当scroll-top小于lower-threshold时,派发scrolltolower事件。这是纵向滚动的情况,当是横向滚动时,是拿scroll-left作为比对值。

    这里需要注意,这两个事件不是点事件,而是状态事件。也就是说,upper-threshold为50,当scroll-top小于50时,只要滚动行为在发生,scrolltoupper事件会多次派发。

    并且派发的是随心所欲。滚动事件是scroll,并不是scroll派发一次,scrolltoupper派发一次;也不是scroll派发三次或五次,scrolltoupper派发一次。是毫无规律可言。

    在这里我们看小程序组件的属性命名,也是随心所欲,毫无章法。

    在flex布局里,我们知道当flex-direction的样式值为不同的row或column时,样式值flex-start、flex-end分别也代表了不同的含义。这种思维更像是程序员的思维模式。

    但是你看小程序组件的属性是怎么命名的?

    scroll-x、scroll-y本应该是两个互斥的属性,结果不互斥;这两个属性应该合并为一个scroll-direction属性,值应该参照css,取row和column。为什么要给程序额外制造心智负担呢?

    还有scroll-top、scroll-left,也应该合并为scroll-start。

    既然upper-threshold代表了距离顶部 / 左边多远,lower-threshold代表了距离底部 / 右边多远,它俩都是以一抵二的属性,为什么scroll-top、scroll-left,还有scroll-x、scroll-y,要分成两个呢?同一套组件为啥用两套标准?

    太随心所欲了。这样的随心所欲,除了增加新手学习的负担,别无用途。怪不得人都说开发难学,一部分门槛是程序员兄弟为自己人量身打造的。

    3.4,refresher-enabled、refresher-threshold、refresher-triggered、bindrefresherpulling、bindrefresherrefresh、bindrefresherrestore、bindrefresherabort

    看到这些事件名,就想到没有句读的古文。名称长,没有小驼峰,也没有连字符、下划线分隔,非常不一目了然。

    前面三个属性,还有后面四个事件,都是与下拉刷新有关的。刚流行iPhone智能手机的时候,下拉刷新是一个体验亮点。后来这种功能设计渐渐成为了App设计规范。

    refresher-enabled用于控制是否开启自定义下拉刷新,默认为false。refresher-threshold是触发下拉更新的临界值,向下拉,松手又回去了,列表没有更新,这是没有达到refresher-threshold的值;达到这个值后,松手是「更新中」的提示。

    refresher-triggered这个布尔值,默认为false。它是为了在更新后,取消下拉更新状态的。当组件处于「下拉更新」状态后,它的值变为true,此时程序要去做一些耗时的事情,例如网络加载。待处理完成了,将这个值置为false,下拉更新的状态就恢复回去了。

    后面四个事件,是实现自定义下拉动画的关键。

    1212e0f5d2b6a57302fef46c4ba8d09f.gif

    bindrefresherpulling这个事件,是手指按住了,往下拉的过程中派发的。自定义的动画要在这个事件里处理。上面的动画就是自实现的下拉更新动画。

    WXS代码:

        ...        onPulling: function(e, instance) {            var p = Math.min(e.detail.dy / 80, 1)            var icon = instance.selectComponent('#refresherIcon')            icon.setStyle({                opacity: p,                transform: "rotate(" + (90 + p * 180) + "deg)"            })            var view = instance.selectComponent('.refresh-container')            view.setStyle({                opacity: p,                transform: "scale(" + p + ")"            })            if (e.detail.dy >= 80) {                if (pullingMessage == "下拉刷新") {                    pullingMessage = "释放更新"                    instance.callMethod("setData", {                        pullingMessage                    })                }            }        }    }

    这段代码稍微有点复杂,主要干了三件事:

    1,计算拉到哪了,占总量80的多少,找到icon图标,设置它的旋转角度

    2,找到下拉动画的容器,设置它的缩放,看起来越往下拉、容器越大

    3,当拉到refresher-threshold临界值时,改变下拉更新的提示文本

    这是WXS代码,是在视图层执行的,在这里可以肆意地操作DOM、更新视图,而不用担心因更新渲染开销大。因为它压根儿就不会更新。代码里之所以用callMethod调用页面主体的setData方法,就是为了曲线救国、达到更新视图的目的。

    每个WXS代码中的事件句柄函数,执行时都有两个参数传递进来:事件对象与当前页面的实例对象。如果没有这两个参数,这个动画就实现不了啦。默认情况下,WXS在视图层执行,与页面JS中的代码不是一路的,后者是在逻辑层执行的。

    如微信官方文档所讲,WXS是一套不一样的脚本语言,它是WeXin Script的简写。WXS 与 JS 是不同的语言,有自己的语法,并不和 JS 一致。

    举个例子,在JS中我们一般使用let代表var声明变量,这可以避免因变量作用域不合适而产生奇怪的bug。但是在WXS中,如果我们使用let声明变量的话,微信开发者工具立刻就给我们爆出一个奇怪的bug。

    此时代码错乱,无法执行。编辑器会报一个没有什么任何文本提示的错误。这种错误最让人抓狂,毫无征兆、毫无线索,根本无从查证。这个时候只有运气和耐心,还有上帝能帮助自己。

    WXS真的是和JS不一样的语言。

    我们再看一下bindrefresherrefresh事件。这个事件应该这样读:bind-refresher-refresh,我第一次看到它,就错看成了是err-refresh,以为是发生某个错误时派发的事件,其实不是。

    它是组件进入更新中状态时派发的事件。

    WXS代码:

    onRefresh: function(e, instance) {    // 此时手拉开了,进入了加载中的状态    pullingMessage = "更新中"    instance.callMethod("setData", {        pullingMessage: pullingMessage,        refresherTriggered: true    })    instance.callMethod("willCompleteRefresh", {})},

    在这个地方需要一个定时器模拟网络加载,但是WXS里没有定时器。要么使用页面实例的requestAnimationFrame方法模拟一个定时器,要么在JS中实现。

    我选择了后者,这个方案看起来更简单。我在JS中定义了一个willCompleteRefresh方法,然后再在WXS中在合适的时机通过callMethod调用它。

    JS代码:

    willCompleteRefresh(){  let intervalId = setInterval(()=>{    let pullingMessage = this.data.pullingMessage    console.log(pullingMessage,pullingMessage == '更新中')    if (pullingMessage.length < 7){      pullingMessage += '.'    }else{      pullingMessage = '更新中'    }    this.setData({      pullingMessage    })  },500)  setTimeout(()=>{    clearInterval(intervalId)    this.setData({      pullingMessage:"已刷新",      refresherTriggered:false,    })  },2000)},

    bindrefresherrestore事件是状态恢复了,是设置了refresher-triggered为false,动画完成之后派发的事件;bindrefresherabort是下拉行为被打断时派发的事件,正常情况下这种事件不会收到。这两个事件属于不可或缺,但不重要的事件。

    具体可以看我的阶段性源码,在下方有链接。

    关于下拉刷新的组件,有两个开源项目可以参考:

    mescroll:github.com/mescroll/mescrollminirefresh:github.com/minirefresh/minirefresh

    4、示例代码与最佳实践

    4.1、示例代码

      ...{{pullingMessage}}    ...

    这里用到的mp-icon,是WeUI的icon组件,后面会详细介绍它的使用方法。

    4.2、最佳实践

    1. 启用scroll-anchoring,同时添加overflow-anchor:auto样式,应对Android机型
    2. 只开启一个方向的滚动,scroll-y或scroll-x只取其一。当开启scroll-y时,必须给组件一个高度,例如400px,或其它值;当启用scroll-x时,必须给组件一个宽度,一般这个值是100%,取屏幕宽度。
    3. 开启enable-flex,这个属性是启用 flexbox 布局的,相当于添加display:flex样式。但是如果是自己添加,是加在了外围容器上,只有通过这个属性添加,才能加到内围真正的容器上。这是个复杂的容器。
    4. 当需要时,使用refresher-enabled启用下拉动画的自定义。自定义可以很方便地实现这样的小人跑动动画:自定义的代码最好在WXS中实现,以bindrefresher开头的事件句柄都在WXS中定义。这可以提高渲染效率,减少页面卡顿。
    5. 下拉动画组件的背景色用#F8f8f8,前景色——包括图标与文本,用#888,这更符合微信设计规范。
    6. 在下拉动画组件中,可以启用flexbox布局,参见上面的WXSS代码。这容易使图标、文本上、下、左、右居中。
    7. 在自定义下拉动画时,容器的slot要标记为refresher,虽然官方文档没有这样写,但如果你不这样做,你的自定义下拉动画是拒绝工作的。
    8. 尽量不要在JS代码中,在scroll事件句柄中,直接更新视图,把相关的频繁的更新视图的代码,放在WXS模块中。在大列表视图中尤其要如此。
    9. 在启用scroll-x时,一般设置宽度为100%,横向满屏。如果出现不滚动的现象,可以尝试给外框容器添加样式:white-space:nowrap;display:inline-block,并且保证内容的实际宽度大于屏幕宽度。

    5、开发者经常遇到哪些问题?

    5.1,使用 scroll-view 时,如何优化使用 setData 向其传递大数据、渲染长列表?

    JS代码:

    // 更新二维数组const updateList = `tabs[${activeTab}].list[${page}]`const updatePage = `tabs[${activeTab}].page`this.setData({    [updateList]: res.data,    [updatePage]: page + 1})

    wxml:

       ...

    这是微信开发者社区上的一个问题,原问题是「scroll-view 追加数据会自动回到顶部,请问怎样解决?」。

    作者可能是想实现一个多tab页的功能,数据是tabs,这是一个大数组。gameListWrap应该是对这个tabs子数据访问的再封装。

    在JS代码中,${activeTab}、${page}都是模板字符串中的变量。updateList、updatePage是setData更新时用的key,因为是变量,所以在使用时要用[]括起来。

    作者为什么不直接使用push方法呢?例如:

    let tabData = this.data.tabs[activeTab]tabData.list.push(res.data)tabData.page = page+1let key = `tabs[${activeTab}]`this.setData({  [key]: tabData})

    当有新数据进来时,直接往某个tab页数据的底部推入新数据。

    但这种操作有一个问题。setData受限于视图层逻辑层之间用于传话的evaluateJavascript函数,其每次携带的数据大小,官方评测标准要求在文本序列化以后大小不能超过256KB。如果某个tab页是一个瀑布流,其tabData.list可能是一个越来越大的数据,不超过256KB是很难的。

    这就犯了「每次 setData 都传递大量新数据」的忌讳,是不被微信小程序官方建议的。

    虽然传递的不全是新数据,但微信小程序不知道哪些是新的,哪些是旧的,凡是在list中传递过来的,它都认为是新数据。

    那么这个问题如何解决呢?如何再优化一下呢?方法是只更新新数据,可以参照作者在实践过程中找到的解决方法。代码:

    const updateListStr = `gameListData[${activeTab}][${page}]`const updatePageStr = `pages[${activeTab}]`this.setData({  [updateListStr]: res,  [updatePageStr]: page + 1})

    将tab数据与页面数据分开。在循环渲染时,按照pages[activeTab].page循环;取数据时,依照page当前的值,从gameListData[activeTab]中查取。gameListData此时在形式上是一个数组,但实际上相当于是一个map。

    在渲染长列表时,微信给出了一个长列表组件recycle-view:

    developers.weixin.qq.com/miniprogram/dev/extended/component-plus/recycle-view.html

    用于渲染无限长的列表。实现原理也很简单,通过监听scroll事件,只渲染当前视图窗口内的list列表,看不见的地方用空的占位符代替。

    25b9c228fb03f68cc7d3031a40ef82cf.gif

    我在vue项目中曾实现过一个类似的长列表组件,以前推过文章,可以在这里查看:

    v-if 条件渲染与 v-for 列表渲染:mp.weixin.qq.com/s/llWnXmMKpXyayK860n1iLQ

    不知道这个问题我讲明白没有,从后端拉取大数据渲染长列表时,现在你明白应该怎么做了吗?

    关键是明白卡顿并不定是手机真卡了,并不一定是GPU运转不过来了,而是视图渲染不及时。我们看到页面卡顿时,可能GPU空闲率有90%。

    影响小程序渲染效率的罪魁祸首是evaluateJavascript这个底层通讯函数,它是逻辑层视图层之间一个很小的独立桥,无法承接过大、过快的派遣。

    5.2,scroll-view 开启自定义下拉刷新,scroll-view 里面内容太少无法触发刷新?

    这个问题在旧的基础库版本中存在,经测试在新的2.10.4版本下该问题已经解决了。

    之所以出现这个问题,是因为scroll-view组件所有事件,除了scroll本身,都是scroll事件的次生代事件。也就是说,像refresher开头的事件是以scroll事件为基础,在内部做了计算之后派发的。

    内容太少,根本无法触发scroll事件,还怎么触发下拉更新呢?

    52ca7e28cdf7eb0353f8fe625d40cf71.gif

    在新的基础库版本中虽然解决了这个问题,但是当内容少的时候,却是连页面内容也滑动了。这是可以理解的,因为除了在父容器上监听scroll事件,可能也没有其它的解决方法了。

    问题是解决了,但牺牲了一些性能。如果内容少,建议直接添加一个看不见的容器,使内容高度一定大于滚动框架的高度,就没有这个问题了。

    在一些展示列表中,开始的时候可能只有一二个子项,这个时候也想触发下拉更新,合适的做法是在列表里故意放一个无用的空项。看以无用,实则有用。

    5.3,scroll-view 在 iOS 中下拉刷新,触发两次 bindscrolltoupper 事件?

    这个问题前面讲过了,scrolltoupper是scroll的次生代事件,是状态事件,不是单点事件,存在多次派发的情况。这种情况只能自己在业务逻辑中做一些特别的防抖动处理。

    5.4,scroll-view 组件为什么有时候 scroll-x 不作用?

    有时候是鼠标无法滑动,在mac电脑上,用触控板就可以滑动。

    如果不是这个问题,可以考虑以下三点:

    1. 内容宽度是否大于外框容器宽度
    2. 可以给外框添加white-space:nowrap;display:inline-block样式,看能否解决
    3. 如果内容使用flexbox布局,要确保scroll-view组件启用了enable-flex属性。启用enable-flex属性,与直接添加display:inline-block并不冲突。

    这里有一个延伸问题,white-space设置为nowrap好理解,是不换行;display设置为inline-block是什么意思呢?为什么不设置为block或inline呢?

    block是块元素样式,将组件设置为块元素,可以设置它的宽、度、margin、padding等值。block会自动换行。inline是内联元素样式,容器设置为inline后,子元素将在一行内显示、不换行。inline-block兼具两者优势,子元素既在一行内显示、不换行,又能设置其宽、高等块元素属性。

    举个例子,ul的li默认是自上而下换行显示的,如果给ul添加display:inline-block,所有li会排行成一行。

    理解了inline-block样式值的作用,回头再看为什么添加display:inline-block这个样式,就好理解了。

    5.5,scroll-view 中两个 scroll-x 和 scroll-y 同时启用有 bug?

    据描述现象是这样的:苹果iOS手机正常,在安卓手机上乱跳。

    不要同时启用这两个属性。他们虽然形式上不是互斥的,但实际上却是互斥的。这是架构师在框架设计上的疏忽。

    5.6,什么情况下需要使用 scroll-view 的下拉刷新,而不使用页面本身的下拉刷新?

    除了使用scroll-view的下拉刷新,有一种替代方案,是直接使用Page的下拉刷新。如何使用呢?我们看一下。

    很简单,在app.json的window选项中或页面配置中,开启enablePullDownRefresh。通过wx.startPullDownRefresh触发下拉刷新,此时页面将处于「更新中」的状态。当处理完异步加载后,使用wx.stopPullDownRefresh停止更新状态。

    并且,在滚动 scroll-view 时,小程序会阻止页面回弹;在 scroll-view 中滚动,无法触发 onPullDownRefresh事件。基于此有人建议,尽量不要使用scroll-view的下拉刷新,直接使用Page的就可以了。

    但是,有时候必须基于scroll-view实现局部页面的刷新,这种情况是很普遍的。

    b357608739fa6cd2850367f59a72f075.png

    在顶部自定义一个navigatorBar导航栏,单击一个按钮切换到一个页面,每个页面都是一个独立的scroll-view组件。这时候下拉刷新使用page整体的就不合适了,下拉刷新的动画必须出现在navigatorBar下方才合理。这时候就必然用到scroll-view的自定义下拉刷新功能了。

    使用自定义刷新功能,scroll-view需要一个固定的高度,这个高度需要我们自己计算。

    8abcfab55a2eb9917cd86360908b39c4.png

    通过wx.getSystemInfo可以获取到两个屏幕高度:screenHeight和windowHeight,前者是屏幕高度,是手机上会亮的那块玻璃板的高度;后者是一个计算值,是screenHeight减去系统状态栏——有电量提示、wifi信号的那一栏(statusBarHeight)、再减去导航栏——有标题和胶囊按钮的那一栏、再减去微信自带的tabBar组件的高度,之后得到的才是windowHeight,是可用的窗口高度。

    如果页面配置启用了navigationStyle:"custom",开发者自定义页面导航栏,则导航栏高度不会在windowHeight中减去;还有,如果某个页面没有启用tabBar,高度又会增大一些。这些我们都要注意。

    拿到windowHeight之后,它还不是scroll-view应有的高度,因为页面上还可能有自定义的底部导航栏、顶部导航栏,这些高度也要减去。

    因为这些原因,给scroll-view设置高度,在不同页面是不一样的,必须区别对待。

    5.7,scroll-view 内不支持嵌套原生组件吗?

    不支持也情有可原,因为要滚动,普通组件与原生组件都不在一个层,一个在上面,一个在下面,怎么同步?

    网上有人说,小程序scroll-view不支持嵌套textarea等组件,那是旧版本。网上有许多教程是旧的。

    从基础库2.4开始,已经开始支持嵌套textarea、map、canvas、video 这些原生组件了。其它原生组件不支持。支持的越多功能越完备,学习起来越复杂,性能流失也越大。

    5.8、如何实现购物类小程序分类选物品的页面?

    d625e03e37fc7c4385ecd45c135b3a56.png

    这里主要需要实现两个功能:

    1. 单击左侧菜单,右侧自动滚动到相应位置
    2. 在右侧滚动,左侧菜单自动同步高亮

    第一个功能点,可以通过scroll-into-view属性实现,将左侧菜单与右侧每块区域的id对应起来,单击时更新scroll-into-view绑定的id。

    wxml代码:

    {{item.name}}

    {{item.name}}

    JS代码:

    // 单击左侧菜单menuListOnClick:function(e){  let me=this;  me.setData({    activeViewId:e.target.id,    currentIndex:e.target.dataset.index  })}// 滚动时触发,计算当前滚动到的位置对应的菜单是哪个scrollFunc:function(e){  this.setData({    scrollTop:e.detail.scrollTop  })  for (let i = 0; i < this.data.heightList.length; i++) {    let height1 = this.data.heightList[i];    let height2 = this.data.heightList[i + 1];    if (!height2 || (e.detail.scrollTop >= height1 && e.detail.scrollTop < height2)) {      this.setData({        currentIndex: i      })      return;    }  }  this.setData({    currentIndex: 0  })}

    第二个功能点,是通过计算实现的。在列表数据绑定时,把右侧每块物品区域的高度记录下来,就是上面代码中的heightList。右侧列表滚动时,通过绑定scroll事件,拿到scrollTop,循环对比在哪个区域,就把哪个区域对应的菜单高亮。

    6、如何在小程序中使用 WeUI 组件库?

    首先,从网站上下载组件包:

    developers.weixin.qq.com/miniprogram/dev/extended/weui/download.html
    1cb18d3791c8989d5338fe1944eb078a.png

    微信提供了按需下载的功能,可以只下载自己用到的组件。

    接着解压组件包,将解压到的目录weui-miniprogram复制到项目根目录下。如果开启了云开发,一般为miniprogram目录。

    再着,在app.wxss里面引入weui.wxss:

    @import './weui-miniprogram/weui-WXSs/dist/style/weui.wxss'

    可以将这句代码直接拷贝到app.wxss文件内。这是WeUI组件库的样式表。没有没有这个文件,组件不能正常显示。

    再着,在哪个页面引用什么组件,就在它的json配置文件中添加usingComponents组件使用声明。以icon为例:

    {  "usingComponents": {    "mp-icon": "./weui-miniprogram/icon/icon"  }}

    mp-icon是完全可以自定义的。WeUI组件库默认以mp开头。

    最后,在wxml页面中使用组件:

    icon属性是图标类型。具体有哪些类型可以使用,可以在这个网址查看:

    developers.weixin.qq.com/miniprogram/dev/extended/weui/icon.html
    623be0989fa3b07d8dfd338b1c2f8af6.png

    这个页面上有一个icon列表,列表里的图标名称都可以使用。

    注意:mp-icon的颜色不能从父组件直接继承,所以即使父组件已经设置了颜色,这个组件也需要额外通过color属性再设置一次。还有,原生icon组件控制类型的属性名称是type,但是这个mp-icon,控制类型的却是icon。

    阶段源码

    源码链接:git.weixin.qq.com/rxyk/weapp-practice/repository/archive.zip?ref=2.3-scroll-view

    与本文相关的代码主要位于:miniprogram/pages/2.3/scroll-view

    这篇文章是「微信小程序开发实践2020」专栏的一部分。

    好了,我是石桥码农,今天分享就到这里,今天主要讲了scroll-view这个组件,希望对你的学习有帮助。有什么问题欢迎留言,也欢迎进群讨论。

    2020年4月10日

    参考文献

    • WEIXIN."cover-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/cover-view.html(2020.04).
    • WEIXIN."movable-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/movable-view.html(2020.04).
    • WEIXIN."scroll-view".[Online]Available:developers.weixin.qq.com/miniprogram/dev/component/scroll-view.html(2020.04).
    • "滚动锚定".[Online]Available:cnblogs.com/ziyunfei/p/6668101.html(2017.04).
    • "下载WeUI组件库".[Online]Available:developers.weixin.qq.com/miniprogram/dev/extended/weui/download.html
    • WXS.[Online]Available:developers.weixin.qq.com/miniprogram/dev/framework/view/WXS/
    • Element.scrollIntoView().[Online]Available:developer.mozilla.org/zh-CN/docs/Web/API/Element/scrollIntoView
    • 「scroll-view 追加数据会自动回到顶部」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0004c47ef14280f7101ab161151800
    • 「scroll-view 开启自定义下拉刷新」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0000685ea0cd78c16ef9fd3a65b800
    • 「scroll-view ios中下拉刷新触发两次bindscrolltouppe」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0004667f0e0ad8da7de8adc9e56000?_at=1586743575247
    • 「scroll-view 组件为什么 scroll-x 不启用」.[Online]Available:developers.weixin.qq.com/community/develop/doc/0006cae8ce02e0b29548700385b800?_at=1586763031642
    • 「微信小程序的高度和scroll-view」.[Online]Available:jianshu.com/p/198c62482afa
    • 「CSS中display:inline-block的作用」.[Online]Available:jianshu.com/p/22c4bae88566
    • 「微信小程序scroll-view详解及案例」.[Online]Available:segmentfault.com/a/1190000021223987
    • 「微信小程序开发实践2020」yishulun.com/post/DwHiQLxx2/(2020.04)
    c09d500a64cde0064d2d73900452fdab.png

    真正的勇士,是看清生活的真想后,依然热爱生活。

    书山有路勤为径,艺海无涯苦作舟。

    说什么真理无穷,进一寸有一寸的欢喜。

    不能穷尽所有,但要尽己所能。

    展开全文
  • android安卓源码海量项目合集打包-1

    万次阅读 多人点赞 2019-06-11 16:16:24
    │ │ D-左边图片的文本框,当文字输入时改变图片,模仿微博登录框.rar │ │ Eclipse编写的Android获取输入框内容,并且改变标题的程序.rar │ │ EditText内容分不同块显示,支持校验,删除块,添加块,得到块代表的...
  • android高级面试题(二)

    千次阅读 2019-05-06 14:53:43
    开放问题:如果提高启动速度,设计一个延迟加载框架或者sdk的方法和注意的问题 二、App绘制优化 三、App内存优化 内存抖动(代码注意事项): 内存抖动是由于短时间内有大量对象进出新生区导致的,它伴随着...
  • │ Android应用源码之listview实现图片的异步加载.zip │ Android应用源码之listview快速滑动,修改默认的滑动条.zip │ Android应用源码之ListView滚动气泡提示.zip │ Android应用源码之listview获取网络图片...
  • ,表示进程优先级越高,越不容易被杀回收 普通app进程的oom_adj>=0,系统进程的oom_adj才可能 有些手机厂商把这些知名的app放入了自己的白名单中,保证了进程不死来提高用户体验(如微信、QQ、陌陌都在小米的白...
  • 有两个View:view1和view2,view2在view1上面且比view1,如何判断点击view1之内的屏幕是应该由view1处理事件还是由view2处理(腾讯-微信-一面) NDK是否可以加载任意目录下的so文件,so文件有几种加载方式(腾讯-...
  • github开源项目大集合

    万次阅读 2018-03-14 11:31:22
    包括依赖注入、图片缓存、网络请求、数据库 ORM 工具包、Android 公共库、高版本向低版本兼容库、多媒体、事件总线、传感器、安全、插件化、文件、其他 Android 开源项目第三篇——优秀项目篇 比较有意思的完整的 ...
  • 最流行的android组件大全

    千次阅读 2017-08-19 15:42:47
    随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考, 如果你发现本文还没有列出的组件,欢迎在评论中贴出来...
  • Android源码大放送(实战开发必备)

    万次阅读 多人点赞 2015-08-01 16:09:58
    │ listview实现图片的异步加载.rar │ listview快速滑动,修改默认的滑动条.rar │ ListView滚动气泡提示.rar │ listview获取网络图片缓存优化.zip │ 一个ExpandableListView的例子,实现多级菜单分类展示.rar │...
  • 随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考, 如果你发现本文还没有列出的组件,欢迎在评论中贴出来...
  • 较全的JAVA基础问题

    2019-11-25 15:09:20
    但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决...
  • 随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考, 如果你发现本文还没有列出的组件,欢迎在评论中贴...
  • 随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考, 如果你发现本文还没有列出的组件,欢迎在评论中贴出来...
  • Web 前端常用插件

    2022-03-25 17:31:34
    小程序 GraphQL JS Plugins仓库 工具类 方便操作对象,数组等的工具库 underscore.js lo-dash与underscore.js的api基本一致。与underscore比其优势是,效率高;可自定义构建 Sugar在原生对象..
  • ym——android源码大放送(实战开发必备)

    万次阅读 多人点赞 2014-08-10 10:06:32
    │ listview实现图片的异步加载.rar │ listview快速滑动,修改默认的滑动条.rar │ ListView滚动气泡提示.rar │ listview获取网络图片缓存优化.zip │ 一个ExpandableListView的例子,实现多级菜单分类...
  • 9).JavaI/O输入输出:File和FileRandomAccess类,字节InputStream和OutputStream,字符Reader和Writer,以及相应实现类,IO性能分析,字节和字符的转化,包装的概念,以及常用包装类,计算机编码。...
  • 首先 介绍几本书籍(下载...03_Android的Linux内核与驱动程序 04_Android的底层库和程序 05_Android的JAVA虚拟机和JAVA环境 06_Android的GUI系统 07_Android的Audio系统 08_Android的Video 输入输出系统 09_Android的多
  • 知识总结

    千次阅读 2015-08-09 21:28:00
    图片抖动:使用CAKeyframeAnimation关键帧动画切换layer的transform.rotation属性,实现图片抖动效果 翻页动画:为UIImageView添加手势识别,监听事件重新赋值数据并执行CATrasition动画实现翻页/淡入淡出/Push/水滴...
  • 本文主要涉及Android相关的问题,之后我会陆续公布Java,以及算法的相关面试题总结。 目录 一、基本问题 Android的四大组件以及作用 描述下Activity的生命周期? 屏幕旋转时的Activity生命周期: Activity的...
  • Android 流行框架

    2018-01-17 11:06:22
    随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考, 如果你发现本文还没有列出的组件,欢迎在评论中贴出来...
  • Android 是目前最流行的移动操作系统之一。 随着新版本的不断发布, Android的功能也日益强大, 涌现了很多流行的应用程序, 也催生了一大批的优秀的组件。 本文试图将目前流行的组件收集起来以供参考
  • 基于Android的Android异步加载图像结 (含线程池,缓存方法).zip 103 毕业设计 基于Android的GesturesDemos.zip 104 毕业设计 基于Android的ListView 中的item随意拖动.zip 105 毕业设计 基于Android的android...
  • Android开源经典项目

    千次阅读 2014-09-13 17:21:00
     包括依赖注入、图片缓存、网络相关、数据库ORM工具包、Android公共库、高版本向低版本兼容库、多媒体、事件总线、传感器、安全、插件化、文件、其他 Android开源项目第三篇——优秀项目篇  比较有意思的完整...
  • 但有时候会存在一些使用接口很难解决的问题,这个时候我们可以利用内部类提供的、可以继承多个具体的或者抽象的类的能力来解决这些程序设计问题。可以这样说,接口只是解决了部分问题,而内部类使得多重继承的解决...
  • 9).JavaI/O输入输出:File和FileRandomAccess类,字节InputStream和OutputStream,字符Reader和Writer,以及相应实现类,IO性能分析,字节和字符的转化,包装的概念,以及常用包装类,计算机编码。...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 489
精华内容 195
关键字:

微信小程序瀑布流加载图片抖动问题

微信小程序 订阅
友情链接: CSharp.zip