感觉这个是很有才华的博主,毕竟是可以在npm 包里面留后门的程序员
博主的gihtub关于这个项目的地址是:https://github.com/ikimiler/react-native-video-project
运行出来了项目我十分兴奋,因为项目很完整,先不去想复杂不复杂,但是看到这样的项目会很感恩开源的程序员
先看效果图






项目的界面大概是如上面的样子
布局很多类似,但是看到项目就会很开心
接下来我们分析项目

//index.js定义了入口为App.js,然后有数据处理这些
import React from 'react'
import { AppRegistry, StatusBar, View } from 'react-native';
import {} from './src/utils/ScreenUtils'
import { Provider } from 'react-redux'
import Store from './src/utils/ConfigRedux'
import CodePush from 'react-native-code-push'
import App from './App' class Root extends React.Component { render() {
return (
<Provider store={Store}>
<App />
</Provider>
);
}
} // var wrapper = CodePush({
// checkFrequency: CodePush.CheckFrequency.ON_APP_RESUME,
// installMode: CodePush.InstallMode.ON_NEXT_RESTART
// })(Root); AppRegistry.registerComponent('colavideoapp', () => Root);
//src/utils/ScreenUtils.js
//判断手机型号,缩放比例
import {Dimensions,PixelRatio,StatusBar,Platform} from 'react-native' // 设备的像素密度,例如:
// PixelRatio.get() === 1 mdpi Android 设备 (160 dpi)
// PixelRatio.get() === 1.5 hdpi Android 设备 (240 dpi)
// PixelRatio.get() === 2 iPhone 4, 4S,iPhone 5, 5c, 5s,iPhone 6,xhdpi Android 设备 (320 dpi)
// PixelRatio.get() === 3 iPhone 6 plus , xxhdpi Android 设备 (480 dpi) export const window = Dimensions.get("window")
export const screen= Dimensions.get("screen") const defaultWidth = 1080,defaultHeight = 1920,defaultRatio = 3; //px to dp
const w2 = defaultWidth / defaultRatio;
const h2 = defaultHeight / defaultRatio; //获取缩放比例
const scale = Math.min(window.height / h2, window.width / w2); function dp(number){
let size = Math.round(number * scale + 0.5) / defaultRatio;
return size
} // iPhoneX Xs
const X_WIDTH = 375;
const X_HEIGHT = 812; // iPhoneXR XsMax
const XR_WIDTH = 414;
const XR_HEIGHT = 896; // screen
const SCREEN_WIDTH = Dimensions.get('window').width;
const SCREEN_HEIGHT = Dimensions.get('window').height; //判断是否为iphoneX或Xs
function isIphoneX() {
return (
Platform.OS === 'ios' &&
((SCREEN_HEIGHT === X_HEIGHT && SCREEN_WIDTH === X_WIDTH) ||
(SCREEN_HEIGHT === X_WIDTH && SCREEN_WIDTH === X_HEIGHT))
)
} //判断是否为iphoneXR或XsMAX
function isIphoneXR() {
return (
Platform.OS === 'ios' &&
((SCREEN_HEIGHT === XR_HEIGHT && SCREEN_WIDTH === XR_WIDTH) ||
(SCREEN_HEIGHT === XR_WIDTH && SCREEN_WIDTH === XR_HEIGHT))
)
} global.dp = dp;
global.DEVICE = {
width:window.width,
height:window.height,
screenWidth: Platform.OS == 'ios'? window.width : screen.width,
screenHeight:Platform.OS == 'ios'? window.height : screen.height,
StatusBarHeight: StatusBar.currentHeight,
android:Platform.OS === 'android',
ios:Platform.OS == 'ios',
isIphoneX:isIphoneX() | isIphoneXR(),
}
//src/utils/ConfigRedux.js
//定义了redux的公共入口
import {createStore,combineReducers,applyMiddleware} from 'redux'
import promiseMiddleware from 'redux-promise-middleware' function reducer(state ={},action){
return {}
}
const reducers = combineReducers({
index:reducer
})
const store = createStore(reducers,applyMiddleware(promiseMiddleware)) export default store;
//src/views/MainTabNavigatorHeader.js
//设置的公共的搜索头部
import React from 'react'
import {
Text,
View,
Image,
TouchableOpacity
} from 'react-native'
import Header, { HeaderItem } from '../components/Header' export default class MainTabNavigatorHeader extends React.Component { _enterSearchPage = () => {
this.props.navigation.navigate('SearchPage')
} render() {
return (
<Header>
<HeaderItem onClick={() => this.props.navigation.navigate('PersonCenterPage')}>
<Image source={require('../../source/image/main_my.png')}></Image>
</HeaderItem>
<TouchableOpacity
onPress={this._enterSearchPage}
activeOpacity={1}
style={[{ flex: 1, height: 35, borderRadius: 5, backgroundColor: 'rgba(0,0,0,0.1)', justifyContent: 'center', alignItems: 'center' },this.props.centerStyle]}>
<Text>搜一搜,全都有</Text>
</TouchableOpacity>
{this.props.rightIcon && <HeaderItem onClick={() => this.props.onRightClick()}>
<Image source={this.props.rightIcon}></Image>
</HeaderItem>}
</Header>
);
}
}

//src/pages/OfflineVideoPlayer.js
//点击进去的视频页面
import React from 'react'
import { View, StyleSheet, ScrollView, Text, TouchableOpacity, Image, Share, ListView, NativeModules } from 'react-native'
import BaseComponent from '../components/BaseComponent'
import VideoWrapper from '../views/VideoWrapper'
import { writeHistoryVideo, queryCollectVideo, deleteCollectVideo, writeCollectVideo } from '../utils/DButils'
import Colors from '../utils/Colors'
import Toast from 'react-native-root-toast'
import DownloadManager from '../utils/DownloadManager'
import Loadding from '../components/Loadding' export default class OfflineVideoPlayer extends BaseComponent { state = {
data: null,
} initData(){
let item = this.props.navigation.state.params.data;
this.setState({data:item},() => this.update(this.LOAD_SUCCESS))
} _renderHeader() {
let item = this.state.data;
if(!item) return null; return (
<VideoWrapper
item={item}
navigation={this.props.navigation}
onProgress={options => this.progressOption = options}
onLoad={data => { }}
onEnd={() => { }} />
)
} renderComponent() {
let data = this.state.data;
let playCount = parseInt(data.playCount)
if (playCount > 10000) {
playCount = (playCount / 10000).toFixed(1) + '万'
}
let title = data.title + (data.index > 0 ? ` 第${data.index}集` : "") return (
<View style={{ flex: 1, backgroundColor: 'white' }}>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
<Text style={[styles.itemStyle, { fontSize: 18, color: 'black' }]}>{title}</Text>
<View style={styles.itemBetweenStyle}>
<Text>播放: {playCount}次</Text>
<Text style={[styles.buttonStyle, { backgroundColor: Colors.mainColor }]}>豆瓣: {data.imdbScore > 0 ? data.imdbScore : '6.0'}</Text>
</View>
<Text style={styles.itemStyle}>分类: {data.classifyTypeListValue}</Text>
<Text style={styles.itemStyle}>导演: {data.director}</Text>
<Text style={styles.itemStyle}>演员: {data.staring}</Text>
<Text style={styles.itemStyle}>简介: </Text>
<Text style={[styles.itemStyle, { marginTop: 10 }]}> {data.intro}</Text>
</ScrollView>
</View>
);
}
} var styles = StyleSheet.create({
itemBetweenStyle: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
minHeight: 40,
},
itemStyle: {
flexDirection: 'row',
alignItems: 'center',
minHeight: 30,
textAlignVertical: 'center'
},
buttonStyle: {
backgroundColor: '#EFF0EB',
borderRadius: 5,
paddingHorizontal: 10,
paddingVertical: 5,
textAlign: 'center',
textAlignVertical: 'center',
color: 'white'
},
bottomStyle: {
flexDirection: 'row',
height: 45,
alignItems: 'center'
},
bottomImageStyle: {
width: 20,
height: 20
}
})
//src/utils/DownloadManager.js
//点击下载的功能
import fs from 'react-native-fs'
import { queryDownloadVideoAll, writeDownloadVideo,deleteDownloadVideo } from '../utils/DButils'
import Toast from 'react-native-root-toast' const baseFile = DEVICE.android ? fs.ExternalStorageDirectoryPath + "/ColaApp" : fs.LibraryDirectoryPath + "/ColaApp";
fs.exists(baseFile).then(exists => {
if (!exists) {
fs.mkdir(baseFile)
}
}) class DownloadManager { /**
* 删除文件
* @param {*} data
*/
deleteCacheVideo(data) {
return new Promise(async function(resolve,reject) {
try {
let file = data.file;
let exitis = await fs.exists(file)
if (exitis) {
let lastIndex = file.lastIndexOf("/")
let dir = file.substring(0, lastIndex)
await fs.unlink(dir)
//同时删除数据库的记录
await deleteDownloadVideo(data)
resolve(true)
} else {
reject(false)
}
} catch (error) {
console.log('netlog-', error)
reject(false)
}
})
} checkSafeUrl(data) {
let url = data.url;
if (!url.startsWith("http://") && !url.startsWith("https://")) {
Toast.show("非法的下载链接")
return false;
} else if (url.indexOf('.m3u8') > -1) {
if (url.indexOf('?') > -1) {
data.url = url.substring(0, url.indexOf('?'))
}
return true;
}
Toast.show("暂不支持此格式视频")
return false;
} /**
* 正在进行中的任务
* {
* maxProgress: 100,
progress: 0,
status:0, // 0 运行中 -1 失败 2成功
toFile:toFile,
* }
*/
allRunningTask = new Map()
listeners = new Set(); addListener(listener = () => { }) {
this.listeners.add(listener)
} removeListener(listener = () => { }) {
if (this.listeners.has(listener)) {
this.listeners.delete(listener)
}
} /**
* 观察者模式,对外发送通知
*/
_updatelisteners() {
for (let listener of this.listeners) {
listener && listener();
}
} downLoad(data) {
let result = this.checkSafeUrl(data);
if (result) {
this.startDownloadM3U8(data)
}
} resetDownLoad(data) {
let result = this.checkSafeUrl(data);
if (result) {
if (this.allRunningTask.has(data.id)) {
this.allRunningTask.delete(data.id)
}
this.startDownloadM3U8(data)
}
} /**
* 开始下载m3u8文件
* @param {*} url
*/
async startDownloadM3U8(data) {
console.log('netlog-download', data.id)
//已经存在了,直接return
if (this.allRunningTask.has(data.id)) {
if (this.allRunningTask.get(data.id).status == 0) {
Toast.show("任务已经在下载队列中了,请不要重复下载")
return;
} else if (this.allRunningTask.get(data.id).status == 2) {
Toast.show("您已经下载过该视频,请不要重复下载")
return;
}
} //查询本地已经下载成功的视频
let videos = await queryDownloadVideoAll();
let keys = Object.keys(videos)
let localFile;
for (let i = 0; i < keys.length; i++) {
let obj = videos[keys[i]]
if (obj && obj.id == data.id) {
localFile = obj.file;
}
} if (localFile) {
let flag = await fs.exists(localFile)
if (flag) {
Toast.show("您已经下载过该视频,请不要重复下载")
return;
}
} Toast.show("开始下载...") //根据url获取到对应的本地目录
let url = data.url;
let urlSplits = url.split("/");
let scheme = urlSplits[0];
let baseUrl = urlSplits[2];
let path = urlSplits.slice(3, urlSplits.length - 1).join("/")
let fileName = urlSplits[urlSplits.length - 1] let toDirPath = baseFile + "/" + path;
await fs.mkdir(toDirPath)
let toFile = toDirPath + "/" + fileName; data.maxProgress = 100;
data.progress = 0;
data.status = 0;
data.toFile = toFile;
data.file = toFile;
//添加m3u8下载任务到缓存
this.allRunningTask.set(data.id, data) //开始下载m3u8文件
let task = fs.downloadFile({
fromUrl: url,
toFile: toFile,
connectionTimeout: 1000 * 60,
readTimeout: 1000 * 60,
begin: function (res) {
},
progress: function (res) {
},
}); let result = await task.promise
if (result.statusCode == 200) {
console.log('netlog-m3u8下载成功', toFile, url, result)
try {
//m3u8下载成功,开始逐步下载ts文件
await this.readM3U8File(data, url, toFile, toDirPath)
console.log('netlog-', '所有ts文件都下载成功了')
//标记下载成功
this.allRunningTask.get(data.id).status = 2;
//写入本地数据库
await writeDownloadVideo(data)
console.log('netlog-', '插入本地数据库成功了')
//删除内存中缓存
this.allRunningTask.delete(data.id)
Toast.show(data.title + "下载成功了,请到下载中心查看")
//通知出去
this._updatelisteners()
} catch (error) {
Toast.show("哎哟,下载出现了异常", error)
console.log('netlog-', '哎哟,下载出现了异常', error)
this.allRunningTask.get(data.id).status = -1;
//通知出去
this._updatelisteners()
}
} else {
console.log('哎哟,下载出现了异常')
Toast.show("哎哟,下载出现了异常")
this.allRunningTask.get(data.id).status = -1;
//通知出去
this._updatelisteners()
}
} /**
* 读取m3u8对应的内容,获取到对应的ts文件地址
* @param {*} m3u8Url
* @param {*} m3u8File
* @param {*} m3u8Dir
*/
async readM3U8File(data, m3u8Url, m3u8File, m3u8Dir) {
let result = await fs.readFile(m3u8File)
let lines = result.split('\n'); let tsUrls = [];
for (let line of lines) {
if (line.endsWith('.ts') || line.indexOf("ts") > -1) {
tsUrls.push(line)
}
}
//设置最大进度,默认为ts文件数为单位
this.allRunningTask.get(data.id).maxProgress = tsUrls.length;
//开始下载ts文件
await this.startDownloadTS(data, m3u8Url, tsUrls, 0, m3u8Dir);
} /**
* 开始下载ts文件
* @param {*} m3u8Url
* @param {*} tsUrls
* @param {*} index
* @param {*} m3u8Dir
*/
async startDownloadTS(data, m3u8Url, tsUrls, index, m3u8Dir) {
if (index >= tsUrls.length) {
return;
};
// if (index >= 10) {
// return;
// }; let url = tsUrls[index];
//如果ts文件中包含路径,当文件夹形式处理
if (url.lastIndexOf("/") > -1) {
let targetDir = m3u8Dir + "/" + url.substring(0, url.lastIndexOf('/'))
let exists = await fs.exists(targetDir)
if (!exists) {
await fs.mkdir(targetDir)
}
} let downloadUrl = m3u8Url.substring(0, m3u8Url.lastIndexOf("/") + 1) + url;
let toFile = m3u8Dir + "/" + url;
let result = await this.createDownloadTSPromise(downloadUrl, toFile)
console.log('netlog-ts下载成功了', toFile, downloadUrl, result) //刷新进度
this.allRunningTask.get(data.id).progress = index + 1;
//通知出去
this._updatelisteners()
await this.startDownloadTS(data, m3u8Url, tsUrls, index + 1, m3u8Dir)
} /**
* 创建ts下载任务
* @param {*} url
* @param {*} file
*/
createDownloadTSPromise(url, file) {
let task = fs.downloadFile({
fromUrl: url,
toFile: file,
connectionTimeout: 1000 * 60,
readTimeout: 1000 * 60,
begin: function (res) {
},
progress: function (res) {
},
});
return task.promise
} } const DownloadManagerInstance = new DownloadManager() export default DownloadManagerInstance;

//src/pages/QueryMoreVideoPage.js
//点击进入查看更多页面
import React from 'react'
import {
View,
Text,
Image,
StyleSheet,
ScrollView,
TouchableOpacity
} from 'react-native'
import BaseFlatListComponent from '../components/BaseFlatListComponent'
import Colors from '../utils/Colors'
import data from '../../data.json'
import config from '../../config.json' const itemWidth = Math.floor((DEVICE.width - 10) / 3);
const itemHeight = Math.floor(itemWidth * 1.3)
const finalStyle = { width: itemWidth, height: itemHeight } //this.props.id 0推荐 1电影 2电视剧 3动漫 4综艺
export default class QueryMoreVideoPage extends BaseFlatListComponent { pageSize = 18
numColumns = 3; paramsArray = []; contentContainerStyle = { flexDirection: 'row', flexWrap: 'wrap' } static navigationOptions = options => {
return {
title: options.navigation.state.params.title
}
} _initListState() {
return {
classData: null,
}
} componentDidMount() {
// let url = `/api/app/video/ver2/video/queryClassifyList/2/7?videoType=${this.props.navigation.state.params.id}`
// axios.get(url).then(res => {
// for (let i = 0; i < res.data.data.length; i++) {
// let childList = res.data.data[i].childList;
// this.paramsArray.push(""); //默认为全部
// childList = childList.splice(0, 0, { classifyName: res.data.data[i].classifyName, id: "", selected: true })
// }
// //flag 1 最多播放 2最近更新 3最多喜欢 5最高评分
// res.data.data.push({
// childList: [
// { classifyName: '最多播放', id: 1 ,selected:true},
// { classifyName: '最近更新', id: 2 ,selected:false},
// { classifyName: '最多喜欢', id: 3 ,selected:false},
// { classifyName: '最高评分', id: 5 ,selected:false},
// ]
// })
// this.paramsArray.push(1);
// this.setState({ classData: res.data.data }, () => super.componentDidMount())
// }).catch(error => {
// console.log('netlog-', error)
// super.componentDidMount()
// }) setTimeout(() => {
let res = {data:data.ClassTypes}
for (let i = 0; i < res.data.data.length; i++) {
let childList = res.data.data[i].childList;
this.paramsArray.push(""); //默认为全部
childList = childList.splice(0, 0, { classifyName: res.data.data[i].classifyName, id: "", selected: true })
}
//flag 1 最多播放 2最近更新 3最多喜欢 5最高评分
res.data.data.push({
childList: [
{ classifyName: '最多播放', id: 1 ,selected:true},
{ classifyName: '最近更新', id: 2 ,selected:false},
{ classifyName: '最多喜欢', id: 3 ,selected:false},
{ classifyName: '最高评分', id: 5 ,selected:false},
]
})
this.paramsArray.push(1);
this.setState({ classData: res.data.data }, () => super.componentDidMount())
}, config.delayed);
} getRequestAction(pageIndex, pageSize) {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve({data:data.ClassMoreData})
}, config.delayed);
})
} enterDetialPage = data => {
data.videoInfoId = data.id;
this.props.navigation.navigate("VideoInfoPage", { data })
} getTagName(obj) {
return obj.tagName == '无标签' ? null : obj.tagName
} _getTagBackgroundColor = tag => {
if (tag == "抢鲜") {
return "#573D1B"
} else if (tag == "1080P") {
return "#C47F14";
} else {
return "red"
}
} renderHeaderItem(item, index) {
return item.childList.map((child) => {
let bgColor = child.selected ? Colors.mainColor : 'white'
let tvColor = child.selected ? 'white' : 'black'
return (
<TouchableOpacity
style={{ padding: 5, borderRadius: 5, justifyContent: 'center', alignItems: 'center', marginRight: 10, backgroundColor: bgColor }}
onPress={() => {
this.paramsArray[index] = child.id;
let data = [...this.state.classData]
for (let i = 0; i < data[index].childList.length; i++) {
let z = data[index].childList[i]
z.selected = z == child;
}
this.setState({ classData: data }, () => {
this.onRefresh()
})
}}
activeOpacity={0.7}>
<Text style={{ color: tvColor }}>{child.classifyName}</Text>
</TouchableOpacity>
);
})
} renderFlatViewHeader = () => {
if (!this.state.classData) return null;
let views = this.state.classData.map((item, index) => {
return (
<ScrollView
showsHorizontalScrollIndicator={false}
horizontal={true}
contentContainerStyle={{ alignItems: 'center' }}
style={{marginTop:5, paddingLeft: 10 }}>
{this.renderHeaderItem(item, index)}
</ScrollView>
)
})
return (
<View>
{views}
</View>
)
} renderRow = (rowData, sectionID, rowID, highlightRow) => {
let obj = rowData;
let index = rowID + 1;
let style = index % 3 == 2 ? {
marginHorizontal: 5,
width: itemWidth,
alignItems: 'center',
marginTop: 10,
} : {
width: itemWidth,
alignItems: 'center',
marginTop: 10,
}
let tagName = obj.tagName == '无标签' ? null : obj.tagName
let tagBackgroundColor = this._getTagBackgroundColor(tagName)
let complete = obj.episodeState == 1;
let updateTag;
if (complete) {
if (obj.episodeUploadCount > 1) {
updateTag = "已完结";
}
} else {
updateTag = obj.episodeUploadCount > 1 ? obj.type != 4 ? `更新至${obj.episodeUploadCount}集` : `更新至${obj.episodeUploadCount}期` : null;
}
let image = obj.coverUrl ? { uri: obj.coverUrl } : require('../../source/image/nor.png')
let playCount = parseInt(obj.playCount)
if (playCount > 10000) {
playCount = (playCount / 10000).toFixed(1) + '万'
}
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.enterDetialPage(obj)}
style={style}>
<View style={finalStyle}>
<Image style={finalStyle} resizeMode="cover" source={image}></Image>
{tagName ? (
<View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}>
<Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text>
</View>
) : null}
{updateTag ? (
<View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text>
</View>
) : null}
</View>
<View style={{ paddingVertical: 5 }}>
<Text numberOfLines={1} style={{ textAlign: 'center' }}>{obj.title}</Text>
<Text numberOfLines={1} style={{ textAlign: 'center' }}>{playCount} 播放</Text>
</View>
</TouchableOpacity>
)
}
} ```js
//src/pages/VideoListPage.js
import React from 'react'
import {
View,
Text,
Image,
StyleSheet,
TouchableOpacity
} from 'react-native'
import BaseFlatListComponent from '../components/BaseFlatListComponent'
import data from '../../data.json'
import config from '../../config.json' const itemWidth = Math.floor((DEVICE.width - 10) / 3);
const itemHeight = Math.floor(itemWidth * 1.3)
const finalStyle = { width: itemWidth, height: itemHeight } export default class VideoListPage extends BaseFlatListComponent { pageSize = 18
numColumns = 3;
enbaleRefresh = false; contentContainerStyle = { flexDirection: 'row', flexWrap: 'wrap' } static navigationOptions = options => {
return {
title: options.navigation.state.params.title
}
} getRequestAction(pageIndex, pageSize) {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve({data:data.MoreData})
}, config.delayed);
})
} enterDetialPage = data => {
this.props.navigation.navigate("VideoInfoPage", { data})
} getTagName(obj) {
return obj.tagName == '无标签' ? null : obj.tagName
} _getTagBackgroundColor = tag => {
if(tag == "抢鲜"){
return "#573D1B"
}else if(tag == "1080P"){
return "#C47F14";
}else{
return "red"
}
} renderRow = (rowData, sectionID, rowID, highlightRow) => {
let obj = rowData;
let index = rowID + 1;
let style = index % 3 == 2 ? {
marginHorizontal: 5,
width: itemWidth,
alignItems: 'center',
marginTop: 10,
} : {
width: itemWidth,
alignItems: 'center',
marginTop: 10,
}
let tagName = obj.tagName == '无标签' ? null : obj.tagName
let tagBackgroundColor = this._getTagBackgroundColor(tagName)
let complete = obj.episodeState == 1;
let updateTag ;
if(complete){
if(obj.episodeUploadCount > 1){
updateTag = "已完结";
}
}else{
updateTag = obj.episodeUploadCount > 1 ? obj.type != 4 ? `更新至${obj.episodeUploadCount}集` : `更新至${obj.episodeUploadCount}期` : null;
}
let image = obj.coverUrl ? {uri : obj.coverUrl} : require('../../source/image/nor.png')
let playCount = parseInt(obj.playCount)
if(playCount > 10000){
playCount = (playCount / 10000).toFixed(1) + '万'
}
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.enterDetialPage(obj)}
style={style}>
<View style={finalStyle}>
<Image style={finalStyle} resizeMode="cover" source={image}></Image>
{tagName ? (
<View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}>
<Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text>
</View>
) : null}
{updateTag ? (
<View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text>
</View>
) : null}
</View>
<View style={{ paddingVertical: 5 }}>
<Text numberOfLines={1} style={{ textAlign: 'center' }}>{obj.title}</Text>
<Text numberOfLines={1} style={{ textAlign: 'center' }}>{playCount} 播放</Text>
</View>
</TouchableOpacity>
)
}
}

//视频详情页
//src/pages/VideoInfoPage.js
import React from 'react'
import { View, StyleSheet, ScrollView, Text, TouchableOpacity, Image, Share, ListView ,NativeModules,BackHandler} from 'react-native'
import BaseComponent from '../components/BaseComponent'
import VideoWrapper from '../views/VideoWrapper'
import { writeHistoryVideo, queryCollectVideo, deleteCollectVideo, writeCollectVideo } from '../utils/DButils'
import Colors from '../utils/Colors'
import Toast from 'react-native-root-toast'
import DownloadManager from '../utils/DownloadManager'
import Loadding from '../components/Loadding'
import data from '../../data.json'
import config from '../../config.json' export default class VideoInfo extends BaseComponent { videoItemIndex = 0;
params = {};
state = {
data: {},
totalVideoList: [],
isCollect: false,
downloadComponentShow: false,
} componentWillMount(){
this.subscription = BackHandler.addEventListener("hardwareBackPress",this.onBack)
} onBack = () => {
if(this.state.downloadComponentShow){
this.setState({downloadComponentShow:false})
return true;
}else{
return false;
}
} async componentWillUnmount() {
this.subscription && this.subscription.remove()
this.hideLoadding()
//事件回调
this.scrollTask && clearTimeout(this.scrollTask)
//存储播放记录
let data = this.state.data;
if (!data.title || !data.id || !data.coverUrl || !this.progressOption) return;
let id = data.id;
let name = data.title;
let level = this.videoItemIndex;
let progress = (this.progressOption.currentTime / this.progressOption.seekableDuration) * 100
let coverUrl = data.coverUrl;
let obj = { id, name, coverUrl, progress, level }
await writeHistoryVideo(obj)
this.props.navigation.state.params.onBack && this.props.navigation.state.params.onBack();
} initData() {
this.queryVideoCollect();
setTimeout(() => {
this.queryTotalVideoList(data.VideoInfoData.data);
}, config.delayed);
} queryVideoCollect() {
let data = this.props.navigation.state.params.data
queryCollectVideo(data).then(res => {
if (Object.keys(res).length) {
this.setState({ isCollect: true })
} else {
this.setState({ isCollect: false })
}
})
} addVideoCollect() {
let data = this.props.navigation.state.params.data
writeCollectVideo(data).then(res => {
this.queryVideoCollect();
})
} deleteVideoCollect() {
let data = this.props.navigation.state.params.data
deleteCollectVideo(data).then(res => {
this.queryVideoCollect();
})
} queryTotalVideoList(data) {
this.setState({ data, totalVideoList: data.videoList }, () => this.update(this.LOAD_SUCCESS, () => {
if (this.params.history) {
this.scrollTask = setTimeout(() => {
let videoItemIndex = this.params.level;
this.listview && this.listview.scrollTo(0, 55 * videoItemIndex)
}, this.params.level * 10);
}
}))
} _renderHeader() {
let item = null
//从播放历史进入
if (this.params.history) {
this.videoItemIndex = this.params.level;
}
if (this.state.totalVideoList.length) {
item = this.state.totalVideoList[this.videoItemIndex]
} return (
<VideoWrapper
ref={ref => this.videoWrapper = ref}
item={item}
navigation={this.props.navigation}
seek={this.params.progress}
onProgress={options => this.progressOption = options}
onLoad={data => {
if (this.params.history) {
this.params.history = false;
this.params.progress = 0;
}
}}
onEnd={() => {
let data = this.state.data;
if (data.videoList[this.videoItemIndex + 1]) {
this.videoItemIndex += 1;
this.forceUpdate(() => this.videoWrapper.startPlayVideo());
}
}} />
)
} dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
itemWidth = (DEVICE.width - 70) / 5; _renderVideoItems = () => {
let data = this.state.totalVideoList;
let dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
if (data.length > 1) {
return (
<View>
<Text style={{ color: 'black', fontSize: 16 }}>选集</Text>
<ListView
removeClippedSubviews={DEVICE.android ? true : false}
horizontal={true}
showsHorizontalScrollIndicator={false}
ref={ref => this.listview = ref}
contentContainerStyle={{ paddingVertical: 10 }}
initialListSize={this.params.history ? this.params.level + 10 : 10}
dataSource={dataSource.cloneWithRows(data)}
renderRow={(rowData, sectionID, rowID, highlightRow) => {
let index = parseInt(rowID)
let i = index + 1;
let color = this.videoItemIndex == index ? "#C47F14" : '#666666'
let text = this.state.data.type == 4 ? `第${i}期` : i;
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
if (this.videoItemIndex !== index) {
this.videoItemIndex = index;
this.forceUpdate()
}
}}
style={[styles.buttonStyle, {
marginRight: 10,
padding: 0,
minWidth: 45,
height: 45,
justifyContent: 'center',
alignItems: 'center'
}]}>
<Text style={{ color, fontSize: 15, fontWeight: 'bold' }}>{text}</Text>
</TouchableOpacity>
);
}}>
</ListView>
</View>
);
} else {
return null;
}
} renderComponent() {
let data = this.state.data;
let playCount = parseInt(data.playCount)
if (playCount > 10000) {
playCount = (playCount / 10000).toFixed(1) + '万'
}
return (
<View style={{ flex: 1, backgroundColor: 'white' }}>
<ScrollView style={{ flex: 1 }} contentContainerStyle={{ padding: 10 }}>
{this._renderVideoItems()} <Text style={[styles.itemStyle, { fontSize: 18, color: 'black' }]}>{data.title}</Text>
<View style={styles.itemBetweenStyle}>
<Text>播放: {playCount}次</Text>
<Text style={[styles.buttonStyle, { backgroundColor: Colors.mainColor }]}>豆瓣: {data.imdbScore > 0 ? data.imdbScore : '6.0'}</Text>
</View>
<Text style={styles.itemStyle}>分类: {data.classifyTypeList.join('/')}</Text>
<Text style={styles.itemStyle}>导演: {data.director}</Text>
<Text style={styles.itemStyle}>演员: {data.staring}</Text>
<Text style={styles.itemStyle}>简介: </Text>
<Text style={[styles.itemStyle, { marginTop: 10 }]}> {data.intro}</Text>
</ScrollView>
</View>
);
} /**
* 下载选集对应得component
*/
_renderOther2() {
if (!this.state.downloadComponentShow) return null; let data = this.state.totalVideoList;
let dataSource = new ListView.DataSource({ rowHasChanged: (r1, r2) => r1 !== r2 })
if (data.length > 1) {
return (
<View style={{ position: 'absolute', left: 0, right: 0, bottom: 0, top: 0, backgroundColor: 'white' }}>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', height: 45,paddingHorizontal:10 }}>
<Text style={{ color: 'black', fontSize: 16 }}>选集</Text>
<Text
style={{ color: 'black', fontSize: 16 }}
onPress={() => this.setState({downloadComponentShow:false})}
>关闭</Text>
</View>
<ListView
showsVerticalScrollIndicator={false}
initialListSize={data.length}
ref={ref => this.listview = ref}
contentContainerStyle={{ flexDirection: 'row', justifyContent: 'flex-start', flexWrap: 'wrap',paddingLeft:10 }}
dataSource={dataSource.cloneWithRows(data)}
renderRow={(rowData, sectionID, rowID, highlightRow) => {
let index = parseInt(rowID)
let i = index + 1;
let color = '#666666'
let text = this.state.data.type == 4 ? `第${i}期` : i;
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
this.downloadVideo(rowData,i)
}}
style={[styles.buttonStyle, {
width: Math.floor((DEVICE.width - 50) / 4),
height: 45,
marginRight:10,
justifyContent: 'center',
alignItems: 'center',
marginBottom:10,
}]}>
<Text style={{ color, fontSize: 15, fontWeight: 'bold' }}>{text}</Text>
</TouchableOpacity>
);
}}>
</ListView>
</View>
);
} else {
return null;
}
} _renderOther() {
let collectImg = this.state.isCollect ? require('../../source/image/shoucang.png') : require('../../source/image/icon_shoucang.png')
let collectText = this.state.isCollect ? "取消收藏" : " 收藏 "
return (
<View style={styles.bottomStyle}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
Share.share({
title: '来嘻哈影视,看免费高清大片',
message: '最新,最全,无广告,请上嘻哈影视 https://github.com/andmizi',
url: '最新,最全,无广告,请上嘻哈影视https://github.com/andmizi'
})
}}
style={{ flex: 1, alignItems: 'center' }}>
<Image style={styles.bottomImageStyle} resizeMode='contain' source={require('../../source/image/icon_share.png')}></Image>
<Text style={{ color: 'black' }}>分享</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
if (this.state.isCollect) {
this.deleteVideoCollect();
} else {
this.addVideoCollect()
}
}}
style={{ flex: 1, alignItems: 'center' }}>
<Image style={styles.bottomImageStyle} resizeMode='contain' source={collectImg}></Image>
<Text style={{ color: 'black' }}>{collectText}</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
let data = this.state.totalVideoList
if(data.length == 0) return;
if (data.length > 1) {
this.setState({ downloadComponentShow: true })
} else {
this.downloadVideo(data[0],-1)
}
}
}
style={{ flex: 1, alignItems: 'center' }}>
<Image style={styles.bottomImageStyle} resizeMode='contain' source={require('../../source/image/icon_down.png')}></Image>
<Text style={{ color: 'black' }}>缓存</Text>
</TouchableOpacity>
</View>
);
} showLoadding() {
this.hideLoadding()
this.loadding = Loadding.show();
} hideLoadding() {
this.loadding && Loadding.hide(this.loadding)
} /**
* 开始下载
* @param {*} data
*/
startDownloadVideo(data,index){
// let qualityMap = new Map();
// if (data.m3u8Format['1080P']) {
// qualityMap.set('1080P', data.m3u8Format['1080P'])
// }
// if (data.m3u8Format['720P']) {
// qualityMap.set('720P', data.m3u8Format['720P'])
// }
// if (data.m3u8Format['480P']) {
// qualityMap.set('480P', data.m3u8Format['480P'])
// }
// if (data.m3u8Format['360P']) {
// qualityMap.set('360P', data.m3u8Format['360P'])
// }
// if (data.m3u8Format['free'] && data.freeShow) {
// qualityMap.set('free', data.m3u8Format['free'])
// } // let playUrl = data.m3u8PlayUrl;
// //默认取第一个
// let arr = Array.from(qualityMap.keys());
// let videoQuality = arr[0]
// let url = playUrl + qualityMap.get(videoQuality); // console.log('netlog-',data.id,url)
this.hideLoadding() // properties: {
// id:"int",
// url: 'string', //以url为准
// title:'string', //视频名称
// index:'int', //集数
// coverUrl:'string', //视频封面
// file:'string', //本地存储路径,m3u8文件
// playCount:'string',//播放次数
// imdbScore:'int',//豆瓣评分
// director:'string',//导演
// staring:'string',//演员
// intro:'string', //简介
// type:'int',//类型 电影 电视剧 动漫 综艺
// }
let params = Object.assign({},this.state.data)
params.url = data;
params.index = index;
params.id = data.id;
params.classifyTypeListValue = params.classifyTypeList.join('/') DownloadManager.downLoad(params)
} /**
* 开始下载视频
* @param {*} data
*/
downloadVideo(data,index) {
this.showLoadding()
setTimeout(() => {
this.startDownloadVideo(config.videoUrl,index)
}, config.delayed);
}
} var styles = StyleSheet.create({
itemBetweenStyle: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
minHeight: 40,
},
itemStyle: {
flexDirection: 'row',
alignItems: 'center',
minHeight: 30,
textAlignVertical: 'center'
},
buttonStyle: {
backgroundColor: '#EFF0EB',
borderRadius: 5,
paddingHorizontal: 10,
paddingVertical: 5,
textAlign: 'center',
textAlignVertical: 'center',
color: 'white'
},
bottomStyle: {
flexDirection: 'row',
height: 45,
alignItems: 'center'
},
bottomImageStyle: {
width: 20,
height: 20
}
})
//src/pages/VIPPage.js
//vip页面
import React from 'react'
import { Text } from 'react-native'
import BaseComponent from '../components/BaseComponent';
import ScrollableTabView, { DefaultTabBar } from 'react-native-scrollable-tab-view'
import Colors from '../utils/Colors'
import VIPTabListPage from './VIPTabListPage' export default class VIPPage extends BaseComponent { state = {
data: []
} initData(pageIndex, pageSize) {
let url = "/api/app/video/ver2/video/queryColumnDataSmall/2/7?modelName=4"
axios.get(url).then(res => {
let data = res.data
if (data.success) {
if (data.data && data.data.length) {
this.setState({ data: data.data }, () => this.update(this.LOAD_SUCCESS))
} else {
this.update(this.LOAD_EMPTY)
}
} else {
this.update(this.LOAD_FAILED)
}
}).catch(error => {
console.log('netlog-', error)
this.update(this.LOAD_FAILED)
})
} _renderPages = () => {
return this.state.data.map(item => {
return (
<VIPTabListPage
navigation={this.props.navigation}
tabLabel={item.title}
id={item.columnId}>
</VIPTabListPage>
);
})
} renderComponent() {
return (
<ScrollableTabView
renderTabBar={() =>
<DefaultTabBar
tabStyle={{ backgroundColor: 'white', justifyContent: 'center', alignItems: 'center' }}
underlineStyle={{ backgroundColor: 'transparent', height: 0 }}
/>
}
locked={true}
tabBarPosition='top'
tabBarTextStyle={{
fontSize: DEVICE.ios_OS ? 17 : 20,
fontWeight: DEVICE.ios_OS ? '600' : '500',
}}
tabBarActiveTextColor={Colors.mainColor}
ref={(tabView) => { this.tabView = tabView }}> {this._renderPages()} </ScrollableTabView>
);
}
}
//src/pages/AboutPage.js
import React from 'react'
import {
Text,
View,
Image,
ScrollView
} from 'react-native' export default class AboutPage extends React.Component { render() {
return (
<View style={{ flex: 1, backgroundColor: 'white', justifyContent: 'center', alignItems: 'center', padding: 10 }}> <Text>影视爱好者,为广大网友提供免费的,高质量的影视作品</Text>
<Text style={{ marginTop: 5 }}>如有侵权,请联系告知~</Text> </View>
)
}
}
//src/pages/HelpPage.js
import React from 'react'
import {
Text,
View,
Image,
ScrollView
} from 'react-native' export default class HelpPage extends React.Component{ render(){
return (
<ScrollView
contentContainerStyle={{flex:1,padding:10,backgroundColor:'white'}}
showsVerticalScrollIndicator={false}>
<View style={{height:40,justifyContent:'center'}}>
<Text style={{color:'red'}}>1.无法播放视频</Text>
</View>
<Text>如果出现某些视频无法播放,包含一直缓冲,闪退,网络异常,请尝试多打开几次,如果还是无法播放,请联系我们的客服进行反馈。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}>
<Text style={{color:'red'}}>2.搜索不到想看的视频</Text>
</View>
<Text>由于版权的原因,某些视频暂时无法提供,请联系我们的客服进行反馈。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}>
<Text style={{color:'red'}}>3.界面展示异常</Text>
</View>
<Text>界面展示异常,不美观或适配遇到问题,请联系我们的客服进行反馈。</Text> <View style={{height:40,justifyContent:'center',marginTop:10}}>
<Text style={{color:'red'}}>4.图标加载不出来</Text>
</View>
<Text>Android:如果遇到启动页图片,返回按键图标加载不出来,请到设置-应用程序-嘻哈影视-存储-清空数据。</Text>
</ScrollView>
)
}
}
//src/pages/PersonCenterPage.js
import React from 'react'
import {
Text,
View,
Image,
TouchableOpacity,
StyleSheet,
ScrollView,
Share,
ImageBackground,
StatusBar,
Alert
} from 'react-native'
import BaseComponent from '../components/BaseComponent'
import SetingItem from '../views/SettingItem'
import { queryAllHistoryVideo, clearAllHistoryVideo } from '../utils/DButils'
import { HeaderItem, appBarPaddingTop } from '../components/Header'
import Toast from 'react-native-root-toast'
import Colors from '../utils/Colors' const itemWidth = Math.floor((DEVICE.width - 40) / 4);
const itemHeight = Math.floor(itemWidth * 1.1)
const finalStyle = { width: itemWidth, height: itemHeight } export default class PersonCenterPage extends BaseComponent { state = {
historyVideo: [],
} initData(){
queryAllHistoryVideo().then(res => {
let result = [];
for(let key in res){
result.push(res[key])
}
this.setState({historyVideo:result},() => this.update(this.LOAD_SUCCESS))
})
} _onBack = () => {
this.initData();
} enterDetialPage = data => {
data.videoInfoId = data.id;
data.title = data.name;
data.history = true;
let params = Object.assign({},data)
this.props.navigation.navigate("VideoInfoPage", { data:params, onBack: this._onBack })
} _clearAllHistoryVideo = () => {
clearAllHistoryVideo().then(res => {
this.setState({historyVideo:[]})
})
} renderComponent() {
console.log('netlog-item',this.state.historyVideo.length)
let historyVideoViews = []
for (let i = this.state.historyVideo.length - 1; i >= 0; i--) {
if(historyVideoViews.length >= 30) break;
let obj = this.state.historyVideo[i + ""];
console.log('netlog-item',obj)
let item = (
<TouchableOpacity
key={'history_' + i}
activeOpacity={0.7}
style={{ marginRight: 10 }}
onPress={() => this.enterDetialPage(obj)}>
<Image
style={[finalStyle]}
resizeMode="cover"
source={{ uri: obj.coverUrl }}></Image>
<Text
style={{ width: finalStyle.width, paddingVertical: 5, textAlign: 'center' }}
numberOfLines={1}>{obj.name}</Text>
<Text
style={{ width: finalStyle.width, textAlign: 'center' }}
numberOfLines={1}>观看至%{obj.progress}</Text>
</TouchableOpacity>
);
historyVideoViews.push(item)
} let imageheight = DEVICE.width / 1.7;
return (
<ScrollView
contentContainerStyle={{ paddingBottom: 50 }}
style={{ backgroundColor: "#F1F1F1" }}>
<ImageBackground
source={require('../../source/image/profile_bg.png')}
resizeMode='cover'
style={{ justifyContent: 'center', width: '100%', height: imageheight, alignItems: 'center', backgroundColor: 'white' }}>
{/* <Image source={require('../../source/image/profile_icon.png')}></Image> */}
<HeaderItem
onClick={() => this.props.navigation.goBack()}
style={{ position: 'absolute', left: 0, top: appBarPaddingTop }}>
<Image
resizeMode='contain'
style={{ width: 25, height: 25 }}
source={require('../../source/image/player_return.png')}></Image>
</HeaderItem>
</ImageBackground> <View style={{ flexDirection: 'row', paddingVertical: 10, backgroundColor: 'white' }}>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
Toast.show('正在努力开发中...')
}}
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/icon_mine_vip.png')}></Image>
<Text style={{ marginTop: 5, color: 'black' }}>神秘大片</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.props.navigation.navigate("DownloadPage")}
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/down.png')}></Image>
<Text style={{ marginTop: 5, color: 'black' }}>下载中心</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.props.navigation.navigate('MyCollectPage') }
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/shoucang.png')}></Image>
<Text style={{ marginTop: 5, color: 'black' }}>我的收藏</Text>
</TouchableOpacity>
<TouchableOpacity
activeOpacity={0.7}
onPress={() => {
Toast.show('正在努力开发中...')
}}
style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Image style={{ width: 25, height: 25 }} resizeMode='contain' source={require('../../source/image/more.png')}></Image>
<Text style={{ marginTop: 5, color: 'black' }}>更多功能</Text>
</TouchableOpacity>
</View> {historyVideoViews && historyVideoViews.length ? (
<View style={{ backgroundColor: 'white', marginTop: 10 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 10, paddingHorizontal: 10, justifyContent: 'space-between' }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ width: 3, height: 15, backgroundColor: "black" }}></View>
<Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>播放记录</Text>
</View>
<Text onPress={this._clearAllHistoryVideo}>清空记录</Text> </View>
<ScrollView
showsHorizontalScrollIndicator={false}
horizontal={true}
contentContainerStyle={{ paddingLeft: 10, paddingBottom: 20 }}>
{historyVideoViews}
</ScrollView>
</View>
) : null}
{/* 新手帮助页面 */}
<SetingItem style={{ marginTop: 10 }} onClick={() => { this.props.navigation.navigate('HelpPage') }} options={{ key: '新手帮助', value: '', hasArrow: true }}></SetingItem>
<SetingItem
onClick={() => {
Share.share({
title: '来嘻哈影视,看免费高清大片',
message: '最新,最全,无广告,请上嘻哈影视 https://github.com/andmizi',
url: '最新,最全,无广告,请上嘻哈影视https://github.com/andmizi'
})
}}
options={{ key: '分享给好友', value: '', hasArrow: true }}></SetingItem>
</ScrollView>
);
}
} const styles = StyleSheet.create({ })
//src/pages/SearchInfoPage.js
//看不出来是做的什么
import React from 'react'
import {
Text,
View,
Image,
TouchableOpacity,
StyleSheet
} from 'react-native'
import BaseFlatListComponent from '../components/BaseFlatListComponent'
import Colors from '../utils/Colors'
import data from '../../data.json'
import config from '../../config.json' export default class SearchInfoPage extends BaseFlatListComponent { enbaleRefresh = false; static navigationOptions = options => {
return {
title: options.navigation.state.params.key
}
} filterResponse(result) {
return result.data.data.map(item => {
item.title = item.title.replace(/{/g, "").replace(/}/g, "").replace(/,/g, "");
return item;
}) } getRequestAction(pageIndex, pageSize) {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve({data:data.ClassMoreData})
}, config.delayed);
})
} enterDetialPage = data => {
data.videoInfoId = data.id;
this.props.navigation.navigate("VideoInfoPage", { data })
} _getTagBackgroundColor = tag => {
if(tag == "抢鲜"){
return "#573D1B"
}else if(tag == "1080P"){
return "#C47F14";
}else{
return "red"
}
} renderRow = rowdata => {
let tagName = rowdata.tagName == '无标签' ? null : rowdata.tagName
let tagBackgroundColor = this._getTagBackgroundColor(tagName)
let complete = rowdata.episodeState == 1;
let updateTag;
if(complete){
if(rowdata.episodeUploadCount > 1){
updateTag = "已完结";
}
}else{
updateTag = rowdata.episodeUploadCount > 1 ? rowdata.type != 4 ? `更新至${rowdata.episodeUploadCount}集` : `更新至${rowdata.episodeUploadCount}期` : null;
}
let image = rowdata.coverUrl ? {uri : rowdata.coverUrl} : require('../../source/image/nor.png')
let playCount = parseInt(rowdata.playCount)
if(playCount > 10000){
playCount = (playCount / 10000).toFixed(1) + '万'
}
return (
<TouchableOpacity
activeOpacity={0.7}
onPress={() => this.enterDetialPage(rowdata)}
style={styles.itemStyle}>
<View style={{ width: 120, height: 80 }}>
<Image style={{width: 120, height: 80 }} resizeMode="cover" source={image}></Image>
{tagName ? (
<View style={{ position: 'absolute', borderRadius: 2, backgroundColor: tagBackgroundColor, top: 5, right: 5, paddingHorizontal: 5 }}>
<Text style={{ color: 'white', fontSize: 12 }}>{tagName}</Text>
</View>
) : null}
{updateTag ? (
<View style={{ position: 'absolute', width: '100%', bottom: 0, paddingVertical: 5, backgroundColor: 'rgba(0,0,0,0.3)', alignItems: 'center', justifyContent: 'center' }}>
<Text style={{ color: 'white', fontSize: 12 }}>{updateTag}</Text>
</View>
) : null}
</View>
<View style={{ flex: 1, height: 80, justifyContent: 'space-between', marginLeft: 10 }}>
<Text numberOfLines={1}>{rowdata.title}</Text>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between' }}>
<Text>播放{playCount}次</Text>
<Text style={styles.buttonStyle}>豆瓣: {rowdata.doubanScore > 0 ? rowdata.doubanScore : '6.0'}</Text>
</View>
</View>
</TouchableOpacity>
);
} } const styles = StyleSheet.create({
itemStyle: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
height: 100,
},
buttonStyle: {
backgroundColor: Colors.mainColor,
borderRadius: 5,
paddingHorizontal: 10,
paddingVertical: 5,
textAlign: 'center',
textAlignVertical: 'center',
color: 'white'
}
})

//src/pages/SearchPage.js
import React from 'react'
import {
Text,
View,
Image,
TouchableOpacity,
TextInput,
StyleSheet,
ScrollView
} from 'react-native'
import Header, { HeaderItem } from '../components/Header'
import BaseComponent from '../components/BaseComponent'
import { writeHistorySearchContent, queryAllHistorySearchContent, clearAllHistorySearchConten } from '../utils/DButils'
import Colors from '../utils/Colors'
import Toast from 'react-native-root-toast'
import data from '../../data.json'
import config from '../../config.json' const backIcon = require('../../source/icons/back_icon.png')
const itemWidth = (DEVICE.width - 20) / 2; export default class SearchPage extends BaseComponent { state = {
datas: data.HotSearchData.data,
historyContents: {},
content: '',
LOAD_STATE:this.LOAD_SUCCESS
} initData() {
this.queryHistoryVideo()
} /**
* 查询搜索历史记录
*/
queryHistoryVideo(){
queryAllHistorySearchContent().then(res => {
this.setState({historyContents:res})
})
} enterSearchInfo(key, flag) {
if (key) {
if (flag) {
writeHistorySearchContent(key).then(res => {
this.queryHistoryVideo();
}).catch(error => {
console.log("netlog-",error)
})
}
this.props.navigation.navigate('SearchInfoPage', { key })
}
} _clearAllHistorySearchContens = () => {
clearAllHistorySearchConten().then(res => {
this.setState({historyContents:{}})
})
} _renderHeader() {
return (
<Header>
<HeaderItem onClick={() => this.props.navigation.goBack()}>
<Image source={backIcon}></Image>
</HeaderItem>
<TextInput
autoFocus={true}
numberOfLines={1}
onChangeText={text => this.setState({ content: text })}
maxLength={20}
placeholder="搜一搜,全都有"
returnKeyType="search"
onSubmitEditing={e => this.enterSearchInfo(e.nativeEvent.text,true)}
underlineColorAndroid='transparent'
style={{ flex: 1, height: 35, padding: 0, borderRadius: 5, backgroundColor: 'rgba(0,0,0,0.1)', justifyContent: 'center', alignItems: 'center', textAlign: 'center' }}>
</TextInput>
<HeaderItem onClick={() => this.enterSearchInfo(this.state.content, true)}>
<Text style={{ fontSize: 15, color: 'black', fontWeight: 'bold' }}>搜索 </Text>
</HeaderItem>
</Header>
);
} renderComponent() {
let keys = []
keys = Object.keys(this.state.historyContents).reverse();
keys.splice(10, keys.length);
let showHistoryContents = keys.length > 0 return (
<ScrollView contentContainerStyle={{ padding: 10 }}>
{
showHistoryContents ? (
<View style={{ marginBottom: 15 }}>
<View style={{ flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginVertical: 10 }}>
<View style={{ flexDirection: 'row', alignItems: 'center' }}>
<View style={{ width: 3, height: 15, backgroundColor: 'black' }}></View>
<Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>历史搜索</Text>
</View>
<Text onPress={this._clearAllHistorySearchContens}>清空记录</Text>
</View>
<View style={{ flexDirection: 'row', flexWrap: 'wrap', alignItems: 'center', }}>
{
keys.map((key, index) => {
let item = this.state.historyContents[key]
return (
<Text
key={'search_children_' + index}
onPress={() => this.enterSearchInfo(item.name, true)}
numberOfLines={1}
style={{ fontSize: 15, margin: 5, padding: 5, color: 'white', backgroundColor: Colors.mainColor, borderRadius: 4 }}>{item.name}
</Text>
);
})
}
</View>
</View>
) : null
} <View style={{ flexDirection: 'row', alignItems: 'center', marginVertical: 10 }}>
<View style={{ width: 3, height: 15, backgroundColor: 'black' }}></View>
<Text style={{ color: 'black', fontSize: 15, marginLeft: 5 }}>热门搜索</Text>
</View>
<View style={styles.container}>
{
this.state.datas.map((item) => {
return (
<TouchableOpacity
key={'search_' + item.id}
style={{ width: itemWidth, marginVertical: 5, flexDirection: 'row', alignItems: 'center' }}
onPress={() => this.enterSearchInfo(item.keyword, false)}
activeOpacity={0.7}>
<Text style={{ fontSize: 15 }}>{item.orderNum} </Text>
<Text numberOfLines={1} style={{ fontSize: 15, marginLeft: 5 }}>{item.keyword}</Text>
</TouchableOpacity>
);
})
}
</View>
</ScrollView>
);
}
} const styles = StyleSheet.create({
container: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between'
}
})
//src/pages/VarietyPage.js
import React from 'react'
import { DeviceEventEmitter } from 'react-native'
import BaseFlatListComponent from '../components/BaseFlatListComponent'
import Banner from '../views/Banner'
import ListItem from '../views/ListItem'
import MainTabNavigatorHeader from '../views/MainTabNavigatorHeader'
import data from '../../data.json'
import config from '../../config.json' export default class VarietyPage extends BaseFlatListComponent { pageSize = 4; _renderHeader() {
return <MainTabNavigatorHeader
onRightClick={() => {
this.props.navigation.navigate('QueryMoreVideoPage', { id: 4, title: '综艺' })
}}
rightIcon={require('../../source/image/sx_icon.png')}
navigation={this.props.navigation} />
} getRequestAction(pageIndex, pageSize) {
return new Promise((resolve,reject) => {
setTimeout(() => {
resolve({data:data.VarietyPageData})
}, config.delayed);
})
} filterResponse(result) {
return result.data.data;
} renderFlatViewHeader = () => {
return <Banner id={4} navigation={this.props.navigation}></Banner>
} renderRow = rowData => {
return (
<ListItem navigation={this.props.navigation} data={rowData}></ListItem>
);
}
}

//初始首页

import React from 'react'
import { ScrollView, View, Text, Image, Alert, BackHandler, DeviceEventEmitter } from 'react-native'
import { StackNavigator, TabNavigator, NavigationActions, DrawerNavigator, DrawerItems } from 'react-navigation'
//
import { HeaderItem } from './src/components/Header'
import Toast from 'react-native-root-toast'
import SplashPage from './src/pages/SplashPage'
//推荐页面
import RecommendPage from './src/pages/RecommendPage'
//电影页面
import MoviePage from './src/pages/MoviePage'
//迷惑了不知道是做什么的了
import TVPage from './src/pages/TVPage'
//这个居然还是类似的组件,说明里面有优化空间
import CartoonPage from './src/pages/CartoonPage'
//封装的组件页面
import VarietyPage from './src/pages/VarietyPage'
//搜索页面
import SearchPage from './src/pages/SearchPage'
//不知道干啥的
import SearchInfoPage from './src/pages/SearchInfoPage'
//个人中心页面
import PersonCenterPage from './src/pages/PersonCenterPage'
//help页面
import HelpPage from './src/pages/HelpPage'
//关于页面
import AboutPage from './src/pages/AboutPage'
//vip页面
import VIPPage from './src/pages/VIPPage'
//视频详情页
import VideoInfoPage from './src/pages/VideoInfoPage'
//
import VideoListPage from './src/pages/VideoListPage'
//
import MyCollectPage from './src/pages/MyCollectPage'
//带你进入查询更多视频页面
import QueryMoreVideoPage from './src/pages/QueryMoreVideoPage'
//下载的方法
import DownloadPage from './src/pages/DownloadPage'
//进入的是单个的视频页面
import OfflineVideoPlayer from './src/pages/OfflineVideoPlayer'
//定义了根搜索
import MainTabNavigatorHeader from './src/views/MainTabNavigatorHeader' import Colors from './src/utils/Colors' const TabNav = TabNavigator({
Recommend: {
screen: RecommendPage,
navigationOptions: {
tabBarLabel: options => {
return <Text style={{ color: options.tintColor }}>推荐</Text>
},
tabBarIcon: options => {
let img = options.focused ? require('./source/image/main_choice_click.png') : require('./source/image/main_choice.png')
return <Image source={img}></Image>
},
tabBarOnPress: obj => {
DeviceEventEmitter.emit("Recommend");
obj.jumpToIndex(obj.scene.index)
},
}
},
Movie: {
screen: MoviePage,
navigationOptions: {
tabBarLabel: options => {
return <Text style={{ color: options.tintColor }}>电影</Text>
},
tabBarIcon: options => {
let img = options.focused ? require('./source/image/main_movie_click.png') : require('./source/image/main_movie.png')
return <Image source={img} ></Image>
},
tabBarOnPress: obj => {
DeviceEventEmitter.emit("Movie");
obj.jumpToIndex(obj.scene.index)
},
}
},
TV: {
screen: TVPage,
navigationOptions: {
tabBarLabel: options => {
return <Text style={{ color: options.tintColor }}>电视剧</Text>
},
tabBarIcon: options => {
let img = options.focused ? require('./source/image/main_tv_click.png') : require('./source/image/main_tv.png')
return <Image source={img} ></Image>
},
tabBarOnPress: obj => {
DeviceEventEmitter.emit("TV");
obj.jumpToIndex(obj.scene.index)
},
}
},
Cartoon: {
screen: CartoonPage,
navigationOptions: {
tabBarLabel: options => {
return <Text style={{ color: options.tintColor }}>动漫</Text>
},
tabBarIcon: options => {
let img = options.focused ? require('./source/image/icon_cartoon_nor_click.png') : require('./source/image/icon_cartoon_nor.png')
return <Image source={img} ></Image>
},
tabBarOnPress: obj => {
DeviceEventEmitter.emit("Cartoon");
obj.jumpToIndex(obj.scene.index)
},
}
},
Variety: {
screen: VarietyPage,
navigationOptions: {
tabBarLabel: options => {
return <Text style={{ color: options.tintColor }}>综艺</Text>
},
tabBarIcon: options => {
let img = options.focused ? require('./source/image/icon_variety_nor_click.png') : require('./source/image/icon_variety_nor.png')
return <Image source={img} ></Image>
},
tabBarOnPress: obj => {
DeviceEventEmitter.emit("Variety");
obj.jumpToIndex(obj.scene.index)
},
}
},
// VIP: {
// screen: VIPPage,
// navigationOptions: {
// tabBarLabel: options => {
// return <Text style={{ color: options.tintColor }}>神秘大片</Text>
// },
// tabBarIcon: options => {
// let img = options.focused ? require('./source/image/main_tv_click.png') : require('./source/image/main_tv.png')
// return <Image style={{ width: dp(55), height: dp(55) }} source={img} resizeMode="cover"></Image>
// },
// }
// }
}, {
tabBarPosition: 'bottom',
lazy: true,
swipeEnabled: false,
animationEnabled: false,
initialRouteName: "Recommend",
removeClippedSubviews: DEVICE.android ? true : false,
tabBarOptions: {
activeTintColor: Colors.mainColor,
inactiveTintColor: Colors.mainColor,
showIcon: true,
showLabel: true,
style: {
backgroundColor: 'white',
elevation: 5,
},
indicatorStyle: {
height: 0
}
}
}); const RootNav = StackNavigator({
Splash: {
screen: SplashPage,
navigationOptions: {
header: null
}
},
Root: {
screen: TabNav,
navigationOptions: function (options) {
return {
header: null,
headerLeft: null
}
}
},
VideoInfoPage: {
screen: VideoInfoPage,
navigationOptions: {
header: null
}
},
VideoListPage: {
screen: VideoListPage,
},
SearchPage: {
screen: SearchPage,
navigationOptions: {
header: null
}
},
SearchInfoPage: {
screen: SearchInfoPage,
},
PersonCenterPage: {
screen: PersonCenterPage,
navigationOptions: {
header: null
}
},
HelpPage: {
screen: HelpPage,
navigationOptions: {
title: "新手帮助"
}
},
AboutPage: {
screen: AboutPage,
navigationOptions: {
title: "关于我们"
}
},
MyCollectPage: {
screen: MyCollectPage,
navigationOptions: {
title: "我的收藏"
}
},
QueryMoreVideoPage: {
screen: QueryMoreVideoPage,
},
DownloadPage:{
screen:DownloadPage,
navigationOptions : {
title: "下载中心"
}
},
OfflineVideoPlayer:{
screen:OfflineVideoPlayer,
navigationOptions : {
header: null
}
},
}, {
initialRouteName: "Splash",
cardStyle: {
},
navigationOptions: function (options) {
return {
headerLeft: <HeaderItem onClick={() => options.navigation.goBack()}><Image source={require('./source/icons/back_icon.png')}></Image></HeaderItem>
}
}
}); const defaultStateAction = RootNav.router.getStateForAction;
RootNav.router.getStateForAction = (action, state) => {
if (DEVICE.android && state && action.type === NavigationActions.BACK && state.routes.length === 1) {
Alert.alert('提示', '确定要退出吗?', [{ text: '取消', onPress: () => { } },
{
text: '退出', onPress: () => {
BackHandler.exitApp();
}
}]);
const routes = [...state.routes];
return {
...state,
...state.routes,
index: routes.length - 1,
};
} else {
return defaultStateAction(action, state);
}
}; import { TestPage } from './src/TestPage' export default RootNav;

//src/views/Banner.js
import React from 'react'
import {
Image,
View,
Text,
TouchableOpacity,
ScrollView
} from 'react-native'
import BaseComponent from '../components/BaseComponent'
import Swiper from 'react-native-swiper'
import Colors from '../utils/Colors' import data from '../../data.json'
import config from '../../config.json' const finalStyle = { width: DEVICE.width, height: DEVICE.width * 0.5 }; //this.props.id 0推荐 1电影 2电视剧 3动漫 4综艺
export default class Banner extends BaseComponent { containerStyle = finalStyle; state = {
datas: [],
classDatas: []
} filterData(data) {
//过滤掉广告轮播
return data.filter(item => {
return item.targetType == 2 && item.videoInfoId != 0;
})
} initData() {
setTimeout(() => {
let result = this.props.id == 0 ? data.RecommendBannerData : (
this.props.id == 1 ? data.MovieBannerData : (
this.props.id == 2 ? data.TVBannerData : (
this.props.id == 3 ? data.CartoonBannerData : data.VarietyBannerData
)
)
)
this.setState({
datas: this.filterData(result.data)
}, () => this.update(this.LOAD_SUCCESS))
}, config.delayed);
} _enterVideoInfo = data => {
data.coverUrl = data.thumbnailUrl;
this.props.navigation.navigate("VideoInfoPage", { data })
} renderComponent() {
let items = [];
for (let i = 0; i < this.state.datas.length; i++) {
let obj = this.state.datas[i];
let image = obj.thumbnailUrl ? { uri: obj.thumbnailUrl } : require('../../source/image/nor.png')
let item = (
<TouchableOpacity
key={'banner' + i}
onPress={() => this._enterVideoInfo(obj)}
activeOpacity={1}>
<Image style={finalStyle} source={image} resizeMode="cover"></Image>
<View style={{ position: 'absolute', bottom: 0, paddingBottom: 25, paddingTop: 5, paddingLeft: 5, width: '100%', backgroundColor: 'rgba(0,0,0,0.3)' }}>
<Text
numberOfLines={1}
style={{ color: 'white', fontWeight: '400', fontSize: 15 }}>{obj.title}</Text>
</View>
</TouchableOpacity>
);
items.push(item)
}
return (
<Swiper
removeClippedSubviews={DEVICE.android ? true : false}
paginationStyle={{ bottom: 10, justifyContent: 'flex-end', paddingRight: 5 }}
style={finalStyle}
width={finalStyle.width}
height={finalStyle.height}
loop={true}
activeDotColor={Colors.mainColor}
dotColor="white"
autoplay={true}
showsPagination={true}>
{items}
</Swiper>
);
}
}

代码感觉很复杂啊,又不是很复杂,但是很难沉下心一句一句弄懂,只能似是而非,其实是不懂,下一篇喽~

最新文章

  1. jquery mobile在页面加载时添加加载中效果 document.ready 和window.onload执行顺序比较
  2. September 1st 2016 Week 36th Thursday
  3. Ms sql将首字母大写
  4. hdu 3123 GCC
  5. MySQL整数类型说明 int(11) vs int(20)
  6. jQuery 中使用 JSON
  7. 在CI中实现持续Web安全扫描
  8. JVM垃圾回收机制概述
  9. 7.Django
  10. how do I get the difference between two R named lists?
  11. Entity Framework 6 多对多增改操作指南
  12. CSS十大选择器
  13. ZooKeeper 数据结构 &amp; 命令
  14. WPF样式——经典博客
  15. Linux Shell脚本编程--Linux特殊符号大全
  16. C/C++ 智能指针简单剖析
  17. Python全栈 Web(边框、盒模型、背景)
  18. ARM的37个寄存器
  19. slabtop 监控实时内核片缓存信息
  20. 【计算几何】【凸包】bzoj2829 信用卡凸包

热门文章

  1. 【核心核心】5.Spring【DI】注解方式
  2. IDEA取消形参名显示
  3. 微信小程序——页面跳转传值
  4. 19-10-24-H
  5. 原生 js 实现摇一摇功能
  6. PAT甲级——A1029 Median
  7. 利用Python覆盖图像的某一部分,即改变图形一块区域(Region)的RGBA值
  8. Leetcode414Third Maximum Number第三大的数
  9. php获取数据转换成json格式
  10. vmware的Linux虚拟机ping不通外网的解决办法