React Native 项目实战 -- DoubanProject
引言:本文是我研究react-native时写的一个简单的demo,代码里有详细的注释,好废话不多说,直接上代码。
1.项目目录
2.index.android.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | /** * index.android.js 入口文件 * Sample React Native App * https://github.com/facebook/react-native * @flow */ // icon={require("image!book")} // icon={require("image!movie")} import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, StatusBar } from 'react-native' ; // 导入导航器 var Navigation = require( "./android_views/common/navigation" ); // 导入BookList var BookList = require( "./android_views/book/book_list" ); // 导入MovieList var MovieList = require( "./android_views/movie/movie_list" ); // tab组件 import TabNavigator from 'react-native-tab-navigator' ; // 隐藏状态栏 StatusBar.setHidden( true ); // TabNavigator管理两个模块:图书、电影 var DoubanProject = React.createClass({ getInitialState: function () { return { selectedTab: "图书" }; }, render: function () { return ( <TabNavigator> <TabNavigator.Item // 标题 title= "图书" // 设置选中的位置 selected={ this .state.selectedTab== "图书" } // 点击Event onPress={() => { this .setState({ selectedTab: "图书" }) }} //图标 renderIcon={() => <Image style={styles.icon} source={require( "./res/images/book.png" )} />} //选中时图标 renderSelectedIcon={() => <Image style={[styles.icon,{tintColor: '#2f8fe6' }]} source={require( "./res/images/book.png" )} />}> <Navigation component={BookList}/> </TabNavigator.Item> <TabNavigator.Item // 标题 title= "电影" // 设置选中的位置 selected={ this .state.selectedTab== "电影" } // 点击Event onPress={() => { this .setState({ selectedTab: "电影" }) }} //图标 renderIcon={() => <Image style={styles.icon} source={require( "./res/images/movie.png" )} />} //选中时图标 renderSelectedIcon={() => <Image style={[styles.icon,{tintColor: '#2f8fe6' }]} source={require( "./res/images/movie.png" )} />}> <Navigation component={MovieList}/> </TabNavigator.Item> </TabNavigator> ) } }); const styles = StyleSheet.create({ icon: { width: 22, height: 22 } }); AppRegistry.registerComponent( 'DoubanProject' , () => DoubanProject); |
3.service.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* 1.接口 API 基于豆瓣开放API的图书、电影 */ var BaseURL = "https://api.douban.com/v2/" ; var Douban_APIS = { /* 图书搜索 image 图书缩略图 title 图书名称 publisher 出版社 author 作者 price 价格 pages 图书总页数 */ book_search: BaseURL + "book/search" , /* 图书详情 image 图书缩略图 title 图书名称 publisher 出版社 author 作者 price 价格 pages 图书总页数 summary 图书简介 author_intro 作者简介 */ book_detail_id: BaseURL + "book/" , /* 电影搜索 images.medium 电影图像 title 电影名称 casts 电影演员 数据需要再处理 rating.average 电影评分 year 电影上映时间 genres 电影标签 alt 电影详情url */ movie_search: BaseURL + "movie/search" , } // 导出 module.exports = Douban_APIS; |
4.util.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | /* 2.定义工具类 实现功能:定义多个属性,在项目中会使用的一些功能。包括:获取屏幕尺寸、loading组件、GET请求方法 包含组件: 外部引用: GET请求方法需要从外部引入url、请求成功的回调方法、请求失败的回调方法。 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Dimensions, // 用于获取设备屏幕的宽高 ActivityIndicator // loading组件 } from 'react-native' ; // 定义对象,将提供的功能作为属性存放 var Util = { // 屏幕尺寸 windowSize: { width: Dimensions.get( "window" ).width, height: Dimensions.get( "window" ).height }, // 基于fetch的get方法 只负责下载数据,下载后的处理操作在回调方法中实现 // successCallback 数据下载成功的回调方法,在组件中实现 // failCallback 数据下载失败的回调方法,在组件中实现 getRequest: function (url, successCallback, failCallback) { fetch(url) .then((response) => response.json()) .then((responseData) => successCallback(responseData)) . catch ((error) => failCallback(error)); }, // loading效果 loading: <ActivityIndicator style={{marginTop:200}} /> } // 导出 module.exports = Util; |
5.searchBar.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 | /* 3.实现功能:封装搜索栏组件,包括文本输入框和搜索按钮 包含组件: 外部传入: 输入框和按钮的属性设置由外部引入。例如:placeholder、onPress、onChangeText 使用...this.props将外部传入的属性设置给TextInput和TouchableOpacity 注意:指定高度、边框颜色、边框线框 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, TextInput, TouchableOpacity } from 'react-native' ; // 定义组件 var SearchBar = React.createClass({ render: function () { return ( <View style={styles.container}> <View style={styles.inputContainer}> <TextInput style={styles.input} {... this .props} /> </View> <TouchableOpacity style={styles.btn} {... this .props}> <Text style={styles.search}>搜索</Text> </TouchableOpacity> </View> ); } }); var styles = StyleSheet.create({ container: { flexDirection: "row" , justifyContent: "flex-end" , alignItems: "center" , height: 44, marginTop: 10 }, inputContainer: { flex: 1, marginLeft: 5 }, input: { flex: 1, height: 44, borderWidth: 1, borderRadius: 4, borderColor: "#CCC" , paddingLeft: 5 }, btn: { width: 55, height: 44, marginLeft: 5, marginRight: 5, backgroundColor: "#23BEFF" , borderRadius: 4, justifyContent: "center" , alignItems: "center" }, search: { flex: 1, color: "#fff" , fontSize: 15, fontWeight: "bold" , textAlign: "center" , lineHeight: 34 } }); module.exports = SearchBar; |
6.left_icon.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | /* 4.实现功能:封装返回按钮图标,不使用图片 包含组件: 外部传入: */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View } from 'react-native' ; // 定义组件 返回图标 var Icon = React.createClass({ render: function () { return ( <View> <View style={styles.go}></View> </View> ); } }); var styles = StyleSheet.create({ go: { width: 15, height: 15, borderLeftWidth: 2, borderBottomWidth: 2, borderColor: "#fff" , marginLeft: 10, transform: [{rotate: "45deg" }] // 将一个矩形框旋转了45度 } }); module.exports = Icon; |
7.header.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | /* 5.实现功能:封装header,在头部展示标题和返回按钮 包含组件: 外部传入: navigator 点击返回按钮返回上一级页面 initObj(backName、barTitle) 返回按钮的名称、标题 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, TouchableOpacity } from 'react-native' ; // 导入左侧按钮 var Icon = require( "./left_icon" ); var Header = React.createClass({ render: function () { // 获取obj对象,包括:backName(按钮名称)、barTitle var headerContent = this .props.initObj; return ( <View style={styles.header}> <TouchableOpacity style={styles.left_btn} onPress={ this ._pop}> <Icon /> <Text style={styles.btn_next}>{headerContent.backName}</Text> </TouchableOpacity> <View style={styles.title_container}> <Text style={styles.title} numberOfLines={1}>{headerContent.barTitle}</Text> </View> </View> ); }, // 返回按钮事件处理方法 _pop: function () { this .props.navigator.pop(); } }); var styles = StyleSheet.create({ header: { height: 44, backgroundColor: "#3497FF" , flexDirection: "row" , justifyContent: "center" , alignItems: "center" }, left_btn: { flexDirection: "row" , justifyContent: "center" , alignItems: "center" }, btn_text: { color: "#fff" , fontSize: 17, fontWeight: "bold" }, title_container: { flex: 1, justifyContent: "center" , alignItems: "center" }, title: { color: "#fff" , fontSize: 18, fontWeight: "bold" , lineHeight: 18, width: 200 } }); module.exports = Header; |
8.navigation.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | /* 6.实现功能:封装导航器初始化设置 包含组件:Navigator 外部传入: component 需要展示的页面组件 route对象 必须添加component属性; 如果需要传值可以添加passProps属性 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View } from 'react-native' ; // npm install react-native-deprecated-custom-components --save import CustomerComponents, { Navigator } from 'react-native-deprecated-custom-components' ; var Navigation = React.createClass({ render: function () { // 创建route对象,约定格式 var rootRoute = { component: this .props.component, passProps: { } }; return ( <Navigator initialRoute={rootRoute} configureScene={() => { return Navigator.SceneConfigs.PushFromRight}} renderScene={(route,navigator) => { var Component = route.component; return ( <View style={{flex:1}}> <Component navigator={navigator} route={route} {...route.passProps}/> </View> ); }}/> ); } }); module.exports = Navigation; |
9.customWebView.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | /* 7.实现功能:封装WebView,根据传入的url展示网页信息 包含组件:Header、WebView 外部传入: 给Header设置:navigator、initObj(backName、title) 给WebView设置:source(url) */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, WebView } from 'react-native' ; var Header = require( "./header" ); var CustomWebView = React.createClass({ render: function () { return ( <View style={{backgroundColor: "white" ,flex:1}}> <Header navigator={ this .props.navigator} initObj={{ backName: this .props.backName, barTitle: this .props.title }}/> <WebView startInLoadingState={ true } contentInset={{top:-44,bottom:-120}} source={{uri: this .props.url}}/> </View> ); } }); module.exports = CustomWebView; |
10.book_item.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | /* 8.图书列表 item 实现功能:展示图书信息,点击item进入图书详情页面 包含组件:基本组件 外部传入: book 图书对象 onPress事件处理方法 通过...this.props绑定,需要设置参数,即图书id 需要使用的字段: image 图书缩略图 title 图书名称 publisher 出版社 author 作者 price 价格 pages 图书总页数 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native' ; var BookItem = React.createClass({ render: function () { var book = this .props.book; return ( <TouchableOpacity style={styles.item} {... this .props}> { /* 图书图像 */ } <View style={styles.imageContainer}> <Image style={styles.image} source={{uri:book.image}}/> </View> { /* 图书信息 */ } <View style={styles.contentContainer}> <View style={styles.textContainer}> <Text numberOfLines={1}>{book.title}</Text> </View> <View style={styles.textContainer}> <Text style={styles.publisher_author} numberOfLines={1}>{book.publisher}</Text> </View> <View style={styles.textContainer}> <Text style={styles.publisher_author} numberOfLines={1}>{book.author}</Text> </View> <View style={{flexDirection: "row" ,flex:1,alignItems: "center" }}> <Text style={styles.price}>{book.price}</Text> <Text style={styles.pages}>{book.pages}页</Text> </View> </View> </TouchableOpacity> ) } }); var styles = StyleSheet.create({ item: { flexDirection: "row" , height: 120, padding: 10 }, imageContainer: { justifyContent: "center" , alignItems: "center" }, image: { width: 80, height: 100 }, contentContainer: { flex: 1, marginLeft: 15 }, textContainer: { flex: 1, justifyContent: "center" }, publisher_author: { color: "#A3A3A3" , fontSize: 13 }, price: { color: "#2BB2A3" , fontSize: 16 }, pages: { marginLeft: 10, color: "#A7A0A0" } }); module.exports = BookItem; |
11.book_list.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | /* 9.图书列表模块:搜索栏、图书列表 图书列表的内容:通过调用图书搜索接口获得多条图书数据 图书列表Item是单独封装的 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, TouchableOpacity, ListView, ScrollView } from 'react-native' ; // 从common模块导入内容 var Util = require( "./../common/util" ); var SearchBar = require( "./../common/searchBar" ); var ServiceURL = require( "./../common/service" ); var BookItem = require( "./book_item" ); var BookDetail = require( "./book_detail" ); var BookList = React.createClass({ getInitialState: function () { var ds = new ListView.DataSource({ rowHasChanged: (oldRow, newRow) => oldRow !== newRow }); return { // dataSource dataSource: ds, // 网络请求状态标识 show: false , // 搜索关键字 // 作用:1.搜索接口需要设置搜索内容 2.点击搜索按钮时,修改关键字内容,重新请求数据,重新渲染 keywords: "react" }; }, getData: function () { // 开启loading,每次搜索时都需要重新下载显示数据 this .setState({ show: false }); // 请求数据 var that = this ; var url = ServiceURL.book_search + "?count=20&q=" + this .state.keywords; Util.getRequest(url, function (data) { // 请求成功回调函数 /* 如果没有相关书籍,使用alert提示 https://api.douban.com/v2/book/search?count=20&q=react {"count":0,"start":0,"total":0,"books":[]} */ if (!data.books || data.books.length == 0) { return alert( "未查询到相关书籍" ); } // 设置下载状态和数据源 var ds = new ListView.DataSource({ rowHasChanged: (oldRow, newRow) => oldRow !== newRow }); that.setState({ show: true , dataSource: ds.cloneWithRows(data.books) }); }, function (error) { // 请求失败回调函数 alert(error); }) }, // TextInput的onChangeText事件处理方法 _changeText: function (text) { this .setState({ keywords: text }); }, _searchPress: function () { this .getData(); }, _showDetail: function (bookID){ var detailRoute = { component: BookDetail, passProps:{ bookID: bookID } } this .props.navigator.push(detailRoute); }, // 布局 render: function () { return ( <ScrollView> <SearchBar placeholder= "请输入图书的名称" onPress={ this ._searchPress} onChangeText={ this ._changeText}/> { // 请求数据时显示loading,数据请求成功后显示ListView this .state.show ? <ListView dataSource={ this .state.dataSource} initialListSize={10} renderRow={ this ._renderRow} renderSeparator={ this ._renderSeparator}/> : Util.loading } </ScrollView> ); }, componentDidMount: function () { // 请求数据 this .getData(); }, _renderRow: function (book) { return <BookItem book={book} onPress={ this ._showDetail.bind( this ,book.id)}/> }, // 分割线 _renderSeparator: function (sectionID: number, rowID: number) { var style = { height: 1, backgroundColor: "#CCCCCC" } return <View style={style} key={sectionID+rowID} /> } }); var styles = StyleSheet.create({ // }); module.exports = BookList; |
12.book_detail.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | /* 10.图书详情 实现功能:展示图书详情,包括:图书信息、图书简介、作者简介 包含组件:基本组件、BookItem(图书信息使用BookItem展示) 外部传入: 需要使用的字段: image 图书缩略图 title 图书名称 publisher 出版社 author 作者 price 价格 pages 图书总页数 summary 图书简介 author_intro 作者简介 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, ScrollView } from 'react-native' ; // 引入 var ServiceURL = require( "./../common/service" ); var Util = require( "./../common/util" ); var Header = require( "./../common/header" ); var BookItem = require( "./book_item" ); // 创建组件类 var BookDetail = React.createClass({ getInitialState: function () { return { bookData: null // 图书对象详情信息 } }, getData: function (){ // 获取图书信息 var that = this ; var url = ServiceURL.book_detail_id + this .props.bookID; Util.getRequest(url, function (data){ that.setState({ bookData:data }); }, function (error){ alert(error); }); }, render: function (){ return ( <ScrollView style={styles.container}> { this .state.bookData ? <View> <Header initObj={{backName: "图书" ,barTitle: this .state.bookData.title}} navigator={ this .props.navigator}/> <BookItem book={ this .state.bookData}/> <View> <Text style={styles.title}>图书简介</Text> <Text style={styles.text}>{ this .state.bookData.summary}</Text> </View> <View style={{marginTop:10}}> <Text style={styles.title}>作者简介</Text> <Text style={styles.text}>{ this .state.bookData.author_intro}</Text> </View> <View style={{height:55}}></View> </View> : Util.loading } </ScrollView> ); }, // 组件挂载以后,进行网络请求 componentDidMount: function (){ // 请求图书详情 this .getData(); } }); var styles = StyleSheet.create({ container:{ flex:1, backgroundColor: "white" }, title:{ fontSize:16, marginTop:10, marginLeft:10, marginBottom:10, fontWeight: "bold" }, text:{ marginLeft:10, marginRight:10, color: "#000D22" } }); module.exports = BookDetail; |
13.movie_item.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | /* 11.电影列表item 实现功能:展示电影信息,点击item进入电影详情页面 包含组件:基本组件 外部传入: movie 电影对象 onPress 通过...this.props绑定,需要设置参数:电影名称、电影详情页面url 需要使用的字段: images.medium 电影图像 title 电影名称 casts 电影演员 数据需要再处理 rating.average 电影评分 year 电影上映时间 genres 电影标签 alt 电影详情url */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, TouchableOpacity } from 'react-native' ; var MovieItem = React.createClass({ render: function () { var movie = this .props.movie; // 提取演员姓名 // 原始数据结构:数组元素是描述演员的对象,对象中包含演员名字 // 需要遍历数组,把每个演员的名字存在一个新的数组中 var actors = []; for ( var i in movie.casts){ actors.push(movie.casts[i].name); } return ( <TouchableOpacity style={styles.item} {... this .props}> <View style={styles.imageContainer}> <Image style={styles.image} resizeMode= "contain" source={{uri:movie.images.medium}}/> </View> <View style={styles.contentContainer}> <View style={styles.textContainer}> <Text style={styles.text} numberOfLines={1}>名称:{movie.title}</Text> </View> <View style={styles.textContainer}> <Text style={styles.text} numberOfLines={1}>演员:{actors}</Text> </View> <View style={styles.textContainer}> <Text style={styles.text} numberOfLines={1}>评分:{movie.rating.average}</Text> </View> <View style={styles.textContainer}> <Text style={styles.text} numberOfLines={1}>时间:{movie.year}</Text> </View> <View style={styles.textContainer}> <Text style={styles.text} numberOfLines={1}>标签{movie.genres}</Text> </View> </View> </TouchableOpacity> ); } }); var styles = StyleSheet.create({ item:{ flexDirection: "row" , height:120, padding:10 }, imageContainer:{ justifyContent: "center" , alignItems: "center" }, image:{ width:80, height:110 }, contentContainer:{ flex:1, marginLeft:15 }, textContainer:{ flex:1, justifyContent: "center" }, text:{ color: "black" } }); module.exports = MovieItem; |
14.movie_list.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | /* 12.电影列表模块:搜索框、电影列表 电影列表的内容:通过调用电影搜索接口获得多条电影数据 电影列表Item是单独封装的 */ import React, { Component } from 'react' ; import { AppRegistry, StyleSheet, Text, View, Image, TouchableOpacity, ListView, ScrollView } from 'react-native' ; var SearchBar = require( "./../common/searchBar" ); var Util = require( "./../common/util" ); var ServiceURL = require( "./../common/service" ); var MovieItem = require( "./movie_item" ); var MovieWebView = require( "./../common/customWebView" ); var MovieList = React.createClass({ getInitialState: function () { var ds = new ListView.DataSource({ rowHasChanged:(oldRow,newRow) => oldRow!==newRow }); return { dataSource: ds, show: false , keywords: "哈利波特" }; }, _changeText: function (text){ this .setState({ keywords:text }); }, _searchPress: function (){ this .getData(); }, _showDetail: function (title,url){ var detailRoute = { component:MovieWebView, passProps:{ backName: "电影" , title:title, url:url } }; // 推出 this .props.navigator.push(detailRoute); }, getData: function (){ this .setState({ show: false }); var that = this ; var url = ServiceURL.movie_search + "?count=20&q=" + this .state.keywords; /* https://api.douban.com/v2/movie/search?count=20&q=哈利波特 {"count":0,"start":0,"total":0,"books":[]} */ Util.getRequest(url, function (data){ if (!data.subjects||data.subjects.length==0){ return alert( "未找到相关电影" ); } var ds = new ListView.DataSource({ rowHasChanged:(oldRow,newRow) => oldRow!==newRow }); var movies = data.subjects; that.setState({ show: true , dataSource:ds.cloneWithRows(movies) }); }, function (error){ alert(error); }); }, render: function (){ return ( <ScrollView> <SearchBar placeholder= "请输入电影的名称" onPress={ this ._searchPress} onChangeText={ this ._changeText}/> { this .state.show ? <ListView dataSource={ this .state.dataSource} initialListSize={10} renderRow={ this ._renderRow} renderSeparator={ this ._renderSeparator}/> : Util.loading } </ScrollView> ); }, componentDidMount: function (){ // 请求数据 this .getData(); }, _renderRow: function (movie){ return <MovieItem movie={movie} onPress={ this ._showDetail.bind( this , movie.title, movie.alt)}/>; }, // 分割线 _renderSeparator: function (sectionID:number,rowID:number){ var style = { height: 1, backgroundColor: "#CCCCCC" }; return <View style={style} key={sectionID+rowID}></View> } }); var styles = StyleSheet.create({ }); module.exports = MovieList; |
15.效果图
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】凌霞软件回馈社区,携手博客园推出1Panel与Halo联合会员
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从零实现富文本编辑器#3-基于Delta的线性数据结构模型
· 记一次 .NET某旅行社酒店管理系统 卡死分析
· 长文讲解 MCP 和案例实战
· Hangfire Redis 实现秒级定时任务,使用 CQRS 实现动态执行代码
· Android编译时动态插入代码原理与实践
· 使用TypeScript开发微信小程序(云开发)-入门篇
· 没几个人需要了解的JDK知识,我却花了3天时间研究
· 在SqlSugar的开发框架中增加对低代码EAV模型(实体-属性-值)的WebAPI实现支持
· C#高性能开发之类型系统:从 C# 7.0 到 C# 14 的类型系统演进全景
· .NET Core中的配置Configuration实战