React Native 加载图片的正确姿势和遇到的一些幺蛾子

一、加载图片的正确姿势

做过原生开发和在使用App时比较注重用户体验的都会注意到“List中加载过程中会出现跟业务相关的Loading图,当图片加载成功之后会消失;当图片加载失败之后会展示缺省的error图”,针对这个问题,自己尝试着在做RN的时候优化一下这些细节

RN的提供的图片组件Image显然是没有提供这些优化的接口,但值得高兴的是,整个组件提供了图片加载的完成周期:

onLoadStart:当开始加载图片调用该方法
onLoadEnd:当加载完成回调该方法,不管图片加载成功还是失败都会调用该方法
onLoad:当图片加载成功之后,回调该方法
onError:该属性要赋值一个function,当加载出错执行赋值的这个方法

拿到生命周期之后我们为图片加载定义了三个状态:加载中、加载成功、加载失败

接下来,我们就对官方的Image组件进行一下封装

import React, { Component } from 'react'
import { View, Image, Animated, Easing, ViewPropTypes, StyleSheet, Platform } from 'react-native'
import { Colors } from '../Themes/index'
import Icon from '../Fonts/iconfont'

export default class LoadImage extends Component {
  static propTypes = {
    style: React.PropTypes.oneOfType([
      ViewPropTypes.style,
      React.PropTypes.number
    ]),
    source: React.PropTypes.object.isRequired,
    defaultSource: React.PropTypes.oneOfType([
      React.PropTypes.object,
      React.PropTypes.number
    ])
  }

  constructor (props) {
    super(props)
    this.state = {
      loadStatus: 'pending',
      backgroundColor: new Animated.Value(0)
    }
  }

  componentWillUnmount () {
    if (undefined !== this.backgroundColorAnimated) this.backgroundColorAnimated.stop()
  }

  /**
   * 开始加载
   */
  onLoadStart () {
    // 配置加载动画
    this.backgroundColorAnimated =
    Animated.sequence([
      Animated.timing(this.state.backgroundColor, {
        toValue: 1,
        easing: Easing.ease,
        duration: 400
      }),
      Animated.timing(this.state.backgroundColor, {
        toValue: 0,
        easing: Easing.in,
        duration: 400
      })
    ])

    this.backgroundColorAnimated.start(() => {
      this.state.loadStatus === 'pending' && this.onLoadStart()
    })
  }

  /**
   * 加载结束
   */
  onLoadEnd () {
    // if (undefined !== this.backgroundColorAnimated) this.backgroundColorAnimated.stop()
  }

  /**
   * 加载成功
   */
  handleImageLoaded () {
    this.setState({ loadStatus: 'success' })
  }

  /**
   * 加载失败
   * @param {*} error
   */
  handleImageErrored (error) {
    console.log(error)
    this.setState({ loadStatus: 'error' })
  }

  /**
   * 渲染加载中界面
   */
  renderPending () {
    let { style } = this.props
    return (
      <Animated.View style={[style, { position: 'absolute',
        backgroundColor: this.state.backgroundColor.interpolate({
          inputRange: [0, 1],
          outputRange: ['#F7F9FB', Colors.C7]
        }) }]} />
    )
  }

  /**
   * 渲染加载失败界面
   */
  renderError () {
    let { style, defaultSource } = this.props
    if (typeof style === 'number') {
      style = StyleSheet.flatten(style)
    }
    let iconSize = Math.min(style.height, style.width) / 3
    return (
      defaultSource
      ? <Image source={defaultSource} style={[{ position: 'absolute' }, style]} />
      : <View style={[{ justifyContent: 'center', backgroundColor: Colors.C7, position: 'absolute', alignItems: 'center' }, style]}>
        <Icon name='image' size={iconSize} color={Colors.C6} />
      </View>
    )
  }

  render () {
    let { style, source } = this.props
    let { loadStatus } = this.state
    // 兼容 uri为null的情况
    if (source.hasOwnProperty('uri') && typeof source['uri'] !== 'string') {
      source = { ...source, uri: '' }
    }
    // 兼容Androud无法对空字符串进行处理情况
    if (Platform.OS === 'android' && source.hasOwnProperty('uri') && !source['uri']) {
      source = { ...source, uri: ' ' }
    }
    return (
      <View style={style}>
        <Image source={source} style={style}
          onLoadStart={this.onLoadStart.bind(this)}
          onLoadEnd={this.onLoadEnd.bind(this)}
          onLoad={this.handleImageLoaded.bind(this)}
          onError={this.handleImageErrored.bind(this)}
        />
        {loadStatus === 'pending' && this.renderPending()}
        {loadStatus === 'error' && this.renderError()}
      </View>
    )
  }
}

原理就是在加载未成功之前讲各个状态的展示图片通过绝对定位覆盖列表展示图片,PS:图片占位大小wu保持一致

最新版本的代码做了几点优化
1、pending样式采用“呼吸背景”的动画
2、生命周期控制更加完善
3、增加对uri = null的兼容(null会抛异常,uri required string)
4、增加Android对uri为空字符串的兼容,在android下,uri为空串时不会在加载生命生期中调用onLoadEnd等方法

二、正常使用注意信息

1、 xxxx@2x.png xxxx@3x.png 是怎么回事?

一开始见到这样的图片以为是不同大小的,嘻嘻嘻,打开真的确实不一样大,但是看了文档之后才发现,这个是针对不同屏幕精度的做法

这么用就OK了

2、require() 和 {uri: ”}

文档原文:uri是一个表示图片的资源标识的字符串,它可以是一个http地址或是一个本地文件路径(使用require(相对路径)来引用)

大致扫了一下文档,知道require() 是加载本地资源的,{uri: ”} 是加载网络资源的,但是。 注意文档的细节,require()只能加载静态资源,说白了就是本地的相对、或者绝对地址,是一个string类型的,比如’../Image/success.png’,但是,如果你把这个string赋值给了一个变量再通过require()引用肯定是不行的,这时只能通过{uri: }引用

三、 待续

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值