第07课:添加自定义组件

知识点:开发一个分享组件。

这节开始介绍怎样自定义我们自己的组件。

分享组件

我们单击分享的时候需要弹出一个单独的层,并且这一层里有几个可配置的显示的分享内容和渠道。

这里利用 Modal 组件来实现,Modal 是一个单独的弹出,默认就遮盖在原有界面的上面,利用这点我们可以非常方便的做出一个遮罩。

在 component 下创建一个新文件ShareView.js,使用一个完整的 view 来做背景,这样不管点击哪儿都可以关闭,使用 Animated 组件则是要自定义动画,Modal 自带的动画实现不了我们需要的效果。

render() {
        return <Modal
            onRequestClose={() => { }}
            animationType="none"
            transparent={true}
            visible={this.state.isShow}>
            <TouchableWithoutFeedback onPress={() => this.cancel()}>
                <View style={styles.bg} ></View>
            </TouchableWithoutFeedback>
            <Animated.View>
                {/*具体的内容*/}
            </Animated.View>
        </Modal>
    }

这里把几个分享小图标对应的方法名等放在后面,添加分享组件的时候加入这些属性,在初始化的时候就直接固定好了分享可以使用的姿势。这里配置了可使用的分享展示用图片名称以及点击事件使用的方法,具体的实现放在分享内部。

/**
 * 分享层显示的类型
 */
exports.SHARETYPE = {
    /**
     * 微信
     */
    WEIXIN: {
        method: "weiFriend", url: require('../images/icon-weixin'), txt: '微信好友'
    },
    /**
     * 微信朋友圈
     */
    PENGYOUQUAN: {
        method: 'weiPyq', url: require('../images/icon-wei'), txt: '朋友圈'
    },
    /**
     * 连接地址
     */
    LIANJIE: {
        method: 'weiLink', url: require('../images/icon-share-link'), txt: '链接'
    },
    /**
     * 二维码
     */
    ERWEIMA: {
        method: 'weiCode', url: require('../images/icon-share-code'), txt: '二维码'
    }
}

使用 types 传入到组件里,通过循环传入的类型渲染好最终的页面。

this.list = this.renderType(this.props.types)
renderType(types) {
        let list = [];
        if (types) {
            types.forEach((item,index) => {
                list.push(<TouchableOpacity key={index}
                    style={styles.shareBtn}
                    onPress={this[item.method].bind(this)}
                    activeOpacity={0.9}>
                    <Image source={{ uri: item.url }}
                        resizeMode='cover'
                        style={{ width: px(96), height: px(96) }} />
                    <Text style={styles.shareTxt} allowFontScaling={false}>{item.txt}</Text>
                </TouchableOpacity>)
            })
        }
        return list;
    }
<ShareModal types={[SHARETYPE.WEIXIN,SHARETYPE.PENGYOUQUAN,SHARETYPE.LIANJIE,SHARETYPE.ERWEIMA]} />

把上面用到的几个方法也实现一次,这里可以先留空,等接入微信之后再真正的实现。

 //链接暂存变量
    link = ''
    //实现微信分享给朋友
    weiFriend() { }
    //实现转发朋友圈
    weiPyq() { }
    //实现复制链接
    weiLink() {
        Clipboard.setString(this.link)
        toast('链接复制成功')
    }
    //实现展示二维码
    weiCode() { }

最后再加上一个主动打开分享的方法,给使用到的时候可以调用就可以了。

//打开分享层
    open(){
        if(!this.state.isShow){
            this.setState({ isShow: true })
        }
    }
添加动画

这里简单的给分享组件添加一个动画,在单击按钮之后分享组件会从下面弹出来,点击取消之后又会往下移动出屏幕。

首先要改造 boxY,让这个变量支持 RN 的动画,这里使用 height 这个固定值来设置分享层从下面出来的位置。

constructor(props) {
        super(props)
        this.height=px(500)
        this.state = {
            isShow: false,//默认不显示弹层
            boxY: new Animated.Value(this.height),
        }
        this.list = this.renderType(this.props.types)
    }

改造 Modal,在 Modal 显示的时候执行动画事件。

<Modal style={styles.view}
            onRequestClose={() => { }}
            onShow={() => this.show()}
            animationType="none"
            transparent={true}
            visible={this.state.isShow}>

RN 提供了几种使用动画的方式,这里使用基于时间轴的动画 timing。第一个参数是我们的位置变量,第二个是动画配置,这里将动画的最终结果设为 0,动画时间设的小一些,让动画显示的快一点。

//打开的动画
show() {
    if (this.state.isShow) {
        Animated.timing(
            this.state.boxY,
            {
                toValue: 0,
                duration: 200
            }
        ).start();
    }
}

这里我突然学会了制作 Gif。

enter image description here

当然,打开的动画有了,关闭的动画也要有,不然你会发现动画只有一次显示的机会,这里注意不要先关闭再动画,关闭之后 UI 就销毁了,动画也就执行不了了。

   //关闭弹层
    cancel() {
        Animated.timing(
            this.state.boxY,
            {
                toValue: this.height,
                duration: 200
            }
        ).start(() => {
            this.setState({
                isShow: false
            })
        });
    }

弹出层组件

App 内部会有很多地方用到弹出层,简单的比如一个 alert 形式的,复杂一点会有一些自定义的 UI 的,这里我们就做一个简单的弹出层组件。

在 component 下面创建一个 ModalView.js 文件,添加一个 Modal 层,后面的所有样式都放在这个 Modal 里面,由于要面对复杂的情况,所以这里设置了标题、内容、自定义组件配置这三方面的配置。

/**
 * alert提示
 * @param {*} opt.title 标题
 * @param {*} opt.content<array> 内容
 * @param {*} opt.btns<array> 按钮组
 * @param {*} opt.btns.txt 按钮标题
 * @param {*} opt.btns.click 按钮点击事件
 */
exports.DialogModal = class extends React.Component {
    constructor(props) {
        super(props)
        this.enabledExit = this.props.enabledExit;
        this.state = {
            show: false,
            opt: { title: "", content: [], btns: [] }
        }
    }
    render() {
        let opt = this.state.opt;
        return <Modal
            visible={this.state.show}
            onShow={() => { }}
            onRequestClose={() => { }}
            animationType="none"
            transparent={true}>
        </Modal>
    }
}

配置里面必须传 content 数组,其他为默认选项,可以不传,这里推荐把参数重载成几个适合的方法。

//弹框参数
    open(opt) {
        if (!opt || !opt.content) return logWarm("alert没有传入内容参数");
        if (!opt.btns || opt.btns.length === 0) {
            opt.btns = [{ txt: "确定", click: () => { } }]
        }
        if (opt.btns.length == 1) {
            opt.btns[0].color = "#d0648f";
        }
        if (opt.btns.length == 2) {
            opt.btns[1].color = "#d0648f";
        }
        this.setState({
            show: true, opt
        })
    }

比如,重载为一个友好的参数类型的方法:

//重载参数
    _alert(title, content, success, cancel) {
        let opt = {
            title, content,
            btns: []
        }
        if (success) opt.btns.push(success)
        if (cancel) opt.btns.push(cancel)
        this.open(opt);
    }

甚至可以将所有参数设置的更随意一些,默认参数和默认类型都可以随意填写。

/**
    * alert提示
    * @param {*} title 标题
    * @param {*} content<array> 内容
    * @param {*} success<array> 成功
    * @param {*} cancel<array> 取消
    * @param {*} success.txt 按钮标题
    * @param {*} success.click 按钮点击事件
    * 重载,(content<string|array>)
    * 重载,(title<string>,content<string|array>)
    * 重载,(title<string>,content<string|array>,success<string|object>)
    * 重载,(title<string>,content<string|array>,success<string|object>,cancel<string|object>)
    */
    alert(title, content, success, cancel) {
        if (!title) {
            title = null
        }
        if (title && !content) {
            const tmp = content;
            content = title;
            title = tmp;
        }
        if (typeof content === "string") content = [content]
        if (typeof success === "string") success = { txt: success }
        if (typeof cancel === "string") success = { txt: cancel }
        this._alert(title, content, success, cancel)
    }

最后我们看看实际效果,弹出的这个样式就是 App 中最常用的状态,比如结果提示、删除确认、支付确认、升级提示等。

this.refs.dialog.alert("标题","测试内容","取消","确定");

enter image description here

查看大图弹层

商品详情页的图片比较多,如果配置了大图的话最好能让用户更清晰的查看大图,同时也要可以下载下来。

在 component/ModalView.js 里面添加看大图的组件。

/**
 * 查看大图的弹层
 * props list 图片列表
 */
exports.ImgsModal = class extends React.Component {
    //滚动
    scroll = null
    //当前图片地址
    currentSrc = ''
    //最大高度
    maxH = deviceHeight * 0.95;
    constructor(props) {
        super(props);
        this.height = px(240)
        this.state = {
            showModal: false,
            boxY: new Animated.Value(this.height),
            current: 1,//当前位置
        };
    }
    render() {
        return <Modal
            visible={this.state.showModal}
            onShow={() => { }}
            onRequestClose={() => { }}
            animationType="none"
            transparent={true}>
        </Modal>
    }
}

在商品详情页将所有图片都加入到一个图片数组中,然后引用大图组件并传入图片数组。

{/*大图弹层*/}
<ImgsModal list={this.state.list}/>

给需要看大图并且已经加入到 imgs 变量中图片添加点击事件:

onPress={()=>this.openBigImg(img.image)} 

点击的时候调用大图组件的打开方法,传入当前点击的图片字符串,打开之前会根据这个字符串找到图片的位置并设置那个位置的图片展示出来。

openBigImg(key){
  this.refs.imgsModal.Open(key);
}

这里主要使用了 scrollView 组件,将要显示的图片放在里面横向排列,左滑与右滑的方法已经天然自带了,我们只需要将图片显示隐藏,长按弹出保存按钮的几个东西实现即可。

<View style={imgsStyles.imgBox}>
                    <ScrollView ref='scroll'
                        contentContainerStyle={[{ height: deviceHeight }, base.line]}
                        keyboardDismissMode='on-drag'
                        onScroll={() => this.cancel()}
                        onMomentumScrollEnd={(e) => {
                            this.setPage(e.nativeEvent.contentOffset)
                        }}
                        pagingEnabled={true}
                        showsHorizontalScrollIndicator={false}
                        showsVerticalScrollIndicator={false}
                        directionalLockEnabled={true}
                        scrollEventThrottle={0.5}
                        horizontal={true}>
                        {this.props.list.map((item, index) => <TouchableWithoutFeedback key={index}
                            delayLongPress={1400}
                            onLongPress={() => this.pop(item.image)}
                            onPress={() => this.close()}>
                            <View style={[imgsStyles.imgItem, base.line]}>
                                {this.resizeImg(item)}
                            </View>
                        </TouchableWithoutFeedback>
                        )}
                    </ScrollView>
                </View>

Git 上的代码比较全,这里就不展示了,感兴趣的读者可以在自己实现一遍,也可以在这个的基础上再加入更复杂的放大、缩小动画。

24169400-228e-11e8-86f8-33ed71c2b78e

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

疯狂紫萧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值