7. 偷用Swiper简改

实验性项目无法发布到市场,决定整改

  1. 项目改名为RNDouBan,决定做一个用react-native写的简单豆瓣客户端
  2. 测试版发布到google市场以获取豆瓣sdk做第三方豆瓣登录才好往下做Google市场需访问外国网站
  3. 修改bug以及以前代码留下的坑
  4. 简单修改react-native-swiper以实现滑动切屏
  5. 先上效果图(代码地址

宣传图.jpg

google市场要的宣传图

  • 由于react-native-router-flux封装了NavBav,但是不太喜欢,主要是给导航栏添加右侧功能健不太亲民,所有自己封装了一个精简版,有很多不如意的地方,水平有限:app/components/navbar.js:
'use strict';

import React, {
    Component
} from 'react';

import {
    StyleSheet,
    View,
    Text,
    TouchableOpacity
} from 'react-native';
import {
    Actions
} from 'react-native-router-flux';
import Button from 'react-native-button';
import Icon from 'react-native-vector-icons/Ionicons';
import Style from '../common/style';

class NavBar extends Component {
    render() {
        return (
            <View style={styles.navbar}>
                <View style={styles.backBtn}>
                    {this._renderBack()}
                </View>
                <View style={styles.navbarTitle}>
                    <Text style={styles.title}>{this.props.title}</Text>
                </View>
                <View style={[Style.valignCenter, styles.rightBtn]}>
                    {this._renderRight()}
                </View>
            </View>
        );
    }
    _renderBack() {

        switch (this.props.back) {
            case 'back':
                return (
                    <TouchableOpacity onPress={() => Actions.pop()}>
                        <Icon name='md-arrow-back' size={20} color='#cdcdcd'></Icon>
                    </TouchableOpacity>
                )
            case 'cancel':
                return (
                    <TouchableOpacity onPress={() => Actions.pop()}>
                        <Text style={Style.blueText}>取消</Text>
                    </TouchableOpacity>
                )
            default: 
                return null;
        }

    }
    _renderRight() {

        if (typeof this.props.renderRight === 'function') {
            return this.props.renderRight();
        }

        switch (this.props.renderRight) {
            case 'share':
                return (
                    <TouchableOpacity onPress={()=>alert('分享')} style={[styles.buttonStyle]}>
                        <Icon name='ios-redo-outline' size={14} color='#cdcdcd'></Icon>
                    </TouchableOpacity>
                )
            case 'button':
                return (
                    <Button onPress={this.props.onRight.bind(this)} containerStyle={[Style.redBtn, styles.buttonStyle]}>
                        <Icon name={this.props.rightIcon} size={14} color='#fff'> {this.props.rightTitle}</Icon>
                    </Button>
                )
            case 'text':
                return (
                    <TouchableOpacity onPress={this.props.onRight.bind(this)}>
                        <Text style={Style.redText}> {this.props.rightTitle}</Text>
                    </TouchableOpacity>
                )
            default:
                return null;
        }

    }
}

const styles = StyleSheet.create({
    navbar: {
        // flex: 3,
        height: 44,
        backgroundColor: '#fff',
        borderBottomWidth: 0,
        borderBottomColor: '#fff',
        flexDirection: 'row',
        justifyContent: 'center',
    },
    navbarTitle: {
        flex: 2,
        alignItems: 'center',
        justifyContent: 'center',
    },
    title: {
        fontSize: 16,
        color: 'black',
    },
    backBtn: {
        flex: 1,
        alignItems: 'flex-start',
        justifyContent: 'center',
        marginLeft: 16,
    },
    rightBtn: {
        flex: 1,
        alignItems: 'flex-end',
        justifyContent: 'center',
        marginRight: 16,
    },
    buttonStyle: {
        padding: 6,
    }
});


export default NavBar;

缺点:不能根据导航栈是否为空自动渲染返回按钮

  • action如前文所示,豆瓣北京周末活动API地址:http://api.douban.com/v2/event/list?loc=108288&day_type=weekend&type=party
  • react-native-swiper地址不是特别的好用但是目前我没发现更加实用的,这个组件本来是用来做轮播图的,看了一下源码,应用的是ViewPagerAndroidScrollView,所以觉得可以一用,有其他需求的童鞋可以看看源码适当修改,类似的组件大体如此,源码也简单,不到600行,核心:
renderScrollView(pages) {
     if (Platform.OS === 'ios')
         return (
            <ScrollView ref="scrollView"
             {...this.props}
             {...this.scrollViewPropOverrides()}
                       contentContainerStyle={[styles.wrapper, this.props.style]}
                       contentOffset={this.state.offset}
                       onScrollBeginDrag={this.onScrollBegin}
                       onMomentumScrollEnd={this.onScrollEnd}
                       onScrollEndDrag={this.onScrollEndDrag}>
             {pages}
            </ScrollView>
         );
      return (
         <ViewPagerAndroid ref="scrollView"
          {...this.props}
            initialPage={this.props.loop ? this.state.index + 1 : this.state.index}
            onPageSelected={this.onScrollEnd}
            style={{flex: 1}}>
            {pages}
         </ViewPagerAndroid>
      );
  },

看这段代码应该就很清楚了,如果是android系统就渲染Pager如果是ios就使用横向的ScrollView,修改后的app首页如下:

import React, {
    PropTypes,
} from 'react';

import {
    View,
    ScrollView,
    Text,
    Image,
    ListView,
    StyleSheet,
    TouchableOpacity,
    RefreshControl,
    Platform,
} from 'react-native';

import {
    connect
} from 'react-redux';
import {
    Actions
} from 'react-native-router-flux';
import Swiper from 'react-native-swiper';

import {
    fetchMovies,
    fetchEvents
} from './action';
import Loading from '../components/loading';
import LoadMore from '../components/loadMore';
import Util from '../common/util';
import MovieCard from '../components/movieCard';
import EventCard from '../components/eventCard';
import NavBar from '../components/navbar';
import Style from '../common/style';

class Home extends React.Component {
    constructor(props) {
        super(props);

        let moviesDS = new ListView.DataSource({
            rowHasChanged: (r1, r2) => r1 !== r2,
        });

        let eventsDS = new ListView.DataSource({
            rowHasChanged: (r1, r2) => r1 !== r2,
        });

        this.state = {
            moviesDS: moviesDS,
            eventsDS: eventsDS,
            // data: [],
            refreshing: false,
        };
    }
    componentDidMount() {
        const {
            dispatch,
            movies,
        } = this.props;
        if (movies.subjects.length === 0) {
            dispatch(fetchMovies());
            dispatch(fetchEvents());
        }
    }
    render() {
        const {
            isFetching,
            movies,
            events,
        } = this.props;

        // if (!isFetching && movies.subjects.length > 0) {
        //     this.state.data = this.state.data.concat(movies.subjects);
        // }

        return (
            <View style={styles.wrapper}>
                <NavBar title='书影音&活动'></NavBar>
                <Swiper loop={false} renderPagination={this._renderPagination.bind(this)} style={Style.mt1}
                >
                    <View style={styles.children} dotTitle='电影'>
                        {isFetching && movies.subjects.length === 0 ? <Loading/ > : (
                      <ListView dataSource={this.state.moviesDS.cloneWithRows(movies.subjects)} renderRow={this._renderRow.bind(this)}
                        enableEmptySections={true}
                        onEndReached={this._handleLoadMore.bind(this)} 
                        onEndReachedThreshold={10}
                        initialListSize={3}
                        refreshControl={
                          <RefreshControl
                            refreshing={this.state.refreshing}
                            onRefresh={this._onRefresh.bind(this)}
                            colors={['#00B51D']}
                            titleColor='#00B51D'
                          />
                        }
                        renderFooter={() => this.props.hasMore ? <LoadMore active={isFetching}/> : null }
                      />
                      )}
                    </View>
                    <View style={styles.children} dotTitle='活动'>
                        {isFetching && movies.subjects.length === 0 ? <Loading/ > : (
                      <ListView dataSource={this.state.eventsDS.cloneWithRows(events.events)} renderRow={this._renderEvent.bind(this)}
                        enableEmptySections={true}
                        onEndReached={this._eventLoadMore.bind(this)} 
                        onEndReachedThreshold={10}
                        initialListSize={3}
                        refreshControl={
                          <RefreshControl
                            refreshing={this.state.refreshing}
                            onRefresh={this._onRefreshEvent.bind(this)}
                            colors={['#00B51D']}
                            titleColor='#00B51D'
                          />
                        }
                        renderFooter={() => this.props.hasMore ? <LoadMore active={isFetching}/> : null }
                      />
                      )}
                    </View>
                </Swiper>
            </View>

        );
    }
    _renderPagination(index, total, swiper) {
        // By default, dots only show when `total` > 2
        if (total <= 1) return null;

        let dotStyle = {
            width: Util.window.width / total,
            flex: 1,
            alignItems: 'center',
            justifyContent: 'center',
            paddingTop: 11,
            paddingBottom: 11,
        }

        let children = swiper.props.children;

        let dots = children.map((v, k) => {
            return (
                <View style={[dotStyle, index === k ? styles.activeDot : styles.dot]} key={k}>
                    <Text style={index === k ? styles.activeDotTitle : styles.dotTitle}>{v.props.dotTitle}</Text>
                </View>
            )
        })

        return (
            <View pointerEvents='none' style={[styles.paginationStyle]}>
            {dots}
          </View>
        )
    }
    _onRefresh() {
        // 刷新
        const {
            dispatch
        } = this.props;
        dispatch(fetchMovies())
    }
    _handleLoadMore() {
        // 加载更多
        if (this.props.hasMore) {
            const {
                dispatch,
                movies
            } = this.props;
            let start = movies.start + movies.count;
            dispatch(fetchMovies(start))
        }

    }
    _onRefreshEvent() {
        // 刷新
        const {
            dispatch
        } = this.props;
        dispatch(fetchEvents())
    }
    _eventLoadMore() {
        // 加载更多
        if (this.props.hasMore) {
            const {
                dispatch,
                events
            } = this.props;
            let start = events.start + events.count;
            dispatch(fetchEvents(start))
        }

    }
    _renderRow(row) {

        return (
            <TouchableOpacity style={styles.movieItem} onPress={() => Actions.detail({url: row.alt})}>
                <MovieCard movie={row}></MovieCard>
            </TouchableOpacity>
        )
    }
    _renderEvent(row) {

        return (
            <TouchableOpacity style={styles.movieItem} onPress={() => Actions.detail({url: row.alt})}>
                <EventCard event={row}></EventCard>
            </TouchableOpacity>
        )
    }
}

Home.propTypes = {};

Home.defaultProps = {};

const styles = StyleSheet.create({
    wrapper: {
        flex: 1,
    },
    movieItem: {
        marginTop: 10,
    },
    paginationStyle: {
        position: 'absolute',
        top: 1,
        height: 44,
        width: Util.window.width,
        flexDirection: 'row',
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#fff',
        borderBottomWidth: 0.5,
        borderBottomColor: '#cdcdcd',
    },
    activeDot: {
        borderBottomWidth: 2,
        borderBottomColor: '#00B51D',
    },
    dot: {
        borderBottomWidth: 2,
        borderBottomColor: '#fff',
    },
    dotTitle: {

    },
    activeDotTitle: {
        color: '#00B51D',
    },
    children: {
        flex: 1,
        paddingTop: 44,
    },
    cellSeparator: {
        height: 0.5,
        backgroundColor: "#DDD"
    },
});

function mapStateToProps(state) {
    return {
        ...state.moviesReducer,
        ...state.eventsReducer,
    }
}

export default connect(mapStateToProps)(Home)

主要添加_renderPagination覆盖原始的pagination,通过调正style将滑动条置顶,在每一页View加上dotTitle,效果图如上,一些style可能不适用或有冲突,检查源码以及高度做调整即可。

前文的reducer书写不太对,修改如下:

case RECEIVE_MOVIES:
            const movies = {...state.movies};
            return {
                ...state,
                isFetching: false,
                hasMore: (action.movies.start + action.movies.count) < action.movies.total,
                movies: {
                    start: action.movies.start,
                    count: action.movies.count,
                    subjects: action.movies.start === 0 ? action.movies.subjects : [].concat(movies.subjects, action.movies.subjects)
                }
            }

主要修改分页逻辑,起因是因为在ListView里面会有三个数据加载

  1. 初始化,初始化的时候数据为空[]显示页面加载条
  2. 下拉刷新,不显示页面加载条,清空原来的数据
  3. 上拉加载,显示加载更多并且将第二页的数据连接到原来的数据 一定要注意三种状态如何渲染页面以及对dataSource的修改,不然会有很多不明bug,我这里只是简单处理,具体可以依照自己的实际数据结构以及state的设计。

最后附上如何androidbuild签名的apk

  1. keytool -genkey -v -keystore release-key.keystore -alias key-alias -keyalg RSA -keysize 2048 -validity 10000keytool命令就不做详细介绍了,如果windows系统找不到,可以使用git的bash
  2. 证书生成之后复制到android/app目录下,记得修改.gitignore,不要把证书也提交了
  3. 修改配置文件android/gradle.properties加下:
RELEASE_STORE_FILE=release-key.keystore
RELEASE_KEY_ALIAS=key-alias
RELEASE_STORE_PASSWORD=*****// 生成证书的密码
RELEASE_KEY_PASSWORD=***** // 另一个密码

  1. 修改android/app/build.gradle:
def enableProguardInReleaseBuilds = true // true可以一定程度减少apk体积

android {
    ...
    defaultConfig {
        ...
    }
    signingConfigs {
        release {
            // 上文配置
            storeFile file(RELEASE_STORE_FILE)
            storePassword RELEASE_STORE_PASSWORD
            keyAlias RELEASE_KEY_ALIAS
            keyPassword RELEASE_KEY_PASSWORD
        }
    }
    ...
    buildTypes {
        release {
            ...
            signingConfig signingConfigs.release
        }
    }
    ...
}

最后 在android目录下运行./gradlew assembleRelease成功即可在android/app/build/outputs/apk/app-release.apk找到apk,运行./gradlew installRelease可以在设备上测试安装,注意如果是调试机请先卸载debug的apk不然会安装失败。

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • React-Native入门指南(三)

    在不断深入的过程中,发现React-Native布局和样式的坑还有很多,他没有像浏览器那样灵活和有规律可循,其中的规律需要我自己踩坑的时候发现。比如:不存在zI...

    疯狂的技术宅
  • WeChat 文章列表页面(一)

    本次的系列博文的知识点讲解和代码,主要是来自于 七月老师 的书籍《微信小程序开发:入门与实践》,由个人总结并编写,关于更多微信小程序开发中的各项技能,以及常见问...

    Nian糕
  • 使用 swiper 轮播插件遇到的问题及解决方法

    我只是记录一下我在使用过程中遇到的几个属性,详细API大家可以直接去官网查看:Swiper4.x使用方法

    德顺
  • vue-awesome-swiper实现轮播图片

    最近在学习Node.js (Express.js) + Vue.js (Element UI) 全栈开发王者荣耀手机端官网和管理后台时,学习到第三章 3.7-首...

    ccf19881030
  • swiper实现图片轮播功能

    swiper是一个相当强大的图片展示插件,下面我来介绍一下swiper的简单图片轮播功能。一般情况下我们所说的图片轮播就是在一个区域内有几张图片循环展示,有的可...

    OECOM
  • canvas实现有递增动画的环形进度条

    drawBaseCanvas:用来绘制底部灰色圆环。由于灰色圆环没有动画效果,所以一开始就绘制一个完整的灰色圆环即可。

    xing.org1^
  • css3-巧用选择器 “:target”

    xing.org1^
  • AngularJS 中使用Swiper制作滚动图不能滑动

    Swiper是目前较为流行的移动端触摸滑动插件,因为其简单好用易上手,受到很多前端开发者的欢迎。 ---- 今天在使用Swiper的时候遇到这个问题: 使用an...

    冷冷
  • react中使用swiper

    最近的react项目需要使用轮播图,自然而然的就想到了swiper,一直想通过npm安装的方式来使用,但是网上找了很多,资料很少,于是就暂时通过在index.h...

    OECOM
  • D3结合Swiper实现一个选中动画

    最近接到一个项目,其中有一个小需求我觉得可以稍微沉淀一下,首先是d3.js的简单应用,还有就是swiper的调试费了一定的时间

    玖柒的小窝
  • 原 Swiper实现图片预览效果

    作者:汪娇娇 日期:2018年3月10日 一、介绍 先用几张图来和大家描述一下什么是图片预览效果吧。 ? ? ? ? 图一:图片列表; 图二:点击列表中 “小...

    jojo
  • 移动端手势的七个事件库

    王小婷
  • swiper组件添加左右箭头

    前言:小程序官方swiper组件并未提供带左右箭头功能,但有些时候还是想把左右箭头添加上,今天连胜老师就给大家分享一下自己的实现方式。 思路很简单:在swip...

    连胜
  • vue封装Swiper实现图片轮播

    (1) Swiper是纯Javascript打造的滑动特效插件,面向手机、平板电脑等移动终端。

    赵帆同学GXUZF.COM
  • Swiper开篇

     swiper是一款轻量级以及免费的移动设备触控滑块的js框架,主要运用于移动端的操作,但也可以用于pc端页面效果制作,完全的开源免费,

    用户3159471
  • 手把手带你学习微信小程序 —— 八 (swiper组件 轮播图的实现)

    在日常的使用 app 的过程中,大家一定遇到过这样的情况,今天就带领大家手动写一个这样的轮播图

    Gorit
  • 微信小程序tab切换加联动

    注:数据绑定,特别是怎么动态绑定class,控制选中或者未选中状态,其中,绑定class或者style都和vue中的格式不太一样

    天天_哥
  • 小程序.文章页面

    轮播图每隔几秒钟图片会自动更换一张。在小程序中,我们不需要自己编写代码来实现这样的轮播图效果,小程序已经提供了一个现成的组件——swiper。

    云深无际
  • 面试简书(五)

    用PS打开图片,点击点击“文件”——“存储为Web所用格式”将图片存储为Web所用格式,点击“存储”。

    生南星

扫码关注云+社区

领取腾讯云代金券