Flutter 仿抖音 TikTok 上下滑動(dòng) 播放視頻UI框架盔沫,視頻播放使用 video_player
github:https://github.com/PangHaHa12138/TiktokVideo
實(shí)現(xiàn)功能:
1.上下滑動(dòng)自動(dòng)播放切換視頻惫霸,loading 封面圖占位
2.全屏播放橫豎屏切換
3.播放進(jìn)度條顯示
4.仿抖音評(píng)論彈窗
效果圖:
001.jpg
002.jpg
003.jpg
004.jpg
005.jpg
上代碼:
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:video_player/video_player.dart';
class VideoPage extends StatefulWidget {
const VideoPage({Key? key}) : super(key: key);
@override
State createState() => _VideoPageState();
}
class _VideoPageState extends State<VideoPage> {
late PageController _pageController;
int currentPageIndex = 0; //當(dāng)前播放索引
int currentIndex = 0; //當(dāng)前播放索引
List<VideoData> videoDataList = []; //視頻數(shù)據(jù)列表
List<VideoType> videoTypeList = []; //視頻分類數(shù)據(jù)列表
@override
void initState() {
loadData(false);
loadVideoType();
_pageController = PageController(initialPage: currentIndex);
_pageController.addListener(_onPageScroll);
super.initState();
}
void _onPageScroll() {
final pageIndex = _pageController.page?.round();
if (pageIndex != null && pageIndex != currentPageIndex) {
currentPageIndex = pageIndex;
print('=========> currentPageIndex: ${currentPageIndex}');
if (currentPageIndex == videoDataList.length - 2) {
loadData(true);
}
}
}
/// 視頻數(shù)據(jù) API請(qǐng)求
Future<void> loadData(bool isLoadMore) async {
// 延遲200ms 模擬網(wǎng)絡(luò)請(qǐng)求
await Future.delayed(const Duration(milliseconds: 200));
if (isLoadMore) {
print('=========> loadData');
List<VideoData> newVideoDataList = [];
newVideoDataList.clear();
newVideoDataList.addAll(videoDataList);
newVideoDataList.addAll(testVideoData);
setState(() {
videoDataList = newVideoDataList;
});
} else {
setState(() {
videoDataList = testVideoData;
});
}
}
/// 視頻類型 API請(qǐng)求
Future<void> loadVideoType() async {
// 延遲200ms 模擬網(wǎng)絡(luò)請(qǐng)求
await Future.delayed(const Duration(milliseconds: 200));
videoTypeList = testVideoType;
setState(() {});
}
@override
void dispose() {
_pageController.removeListener(_onPageScroll);
_pageController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
return Scaffold(
resizeToAvoidBottomInset: false, //很重要,不加鍵盤彈出視頻會(huì)被擠壓
body: Stack(
children: [
PageView.builder(
scrollDirection: Axis.vertical,
itemCount: videoDataList.length,
controller: _pageController,
onPageChanged: (currentPage) {
//頁面發(fā)生改變的回調(diào)
},
itemBuilder: (context, index) {
return VideoPlayerFullPage(
size: size,
videoData: videoDataList[index],
videoTypes: videoTypeList,
);
},
),
header(
context,
videoTypeList,
),
],
));
}
Widget header(BuildContext context, List<VideoType> videoTypes) {
var size = MediaQuery.of(context).size;
return Padding(
padding: const EdgeInsets.only(left: 15, top: 10, bottom: 10),
child: SafeArea(
child: Column(
children: [
Row(
children: [
IconButton(
icon: const Icon(
Icons.arrow_back_ios_new,
color: Colors.white,
),
onPressed: () {
if (Navigator.canPop(context)) {
Navigator.pop(context);
}
}),
GestureDetector(
onTap: () {
onSearchClick();
},
child: Container(
width: size.width - 100,
padding: const EdgeInsets.only(
left: 15, right: 15, top: 5, bottom: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: const Color(0x80444444),
),
child: Row(
children: const [
Icon(
Icons.search,
color: Colors.white,
),
SizedBox(
width: 5,
),
Text(
'搜索社群',
style: TextStyle(
color: Colors.white,
fontSize: 15,
),
),
],
),
),
),
],
),
const SizedBox(height: 10),
Wrap(
spacing: 8.0, // 主軸(水平)方向間距
runSpacing: 2.0, // 縱軸(垂直)方向間距
children: videoTypes.map((item) {
return GestureDetector(
onTap: () {
onVideoTypesClick(item);
},
child: Container(
padding: const EdgeInsets.only(
left: 12, right: 12, top: 4, bottom: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0), // 設(shè)置圓角
color: const Color(0xFF69DCE5), // 設(shè)置背景顏色
),
child: Text(
item.typeName,
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
textAlign: TextAlign.center,
),
),
);
}).toList(),
),
],
),
),
);
}
/// 頂部視頻類型 點(diǎn)擊
Future<void> onVideoTypesClick(VideoType videoType) async {
print('=====> 點(diǎn)擊了視頻類型');
}
/// 搜索點(diǎn)擊
Future<void> onSearchClick() async {
print('=====> 點(diǎn)擊了搜索');
}
}
class VideoPlayerFullPage extends StatefulWidget {
final List<VideoType> videoTypes; //視頻頂部分類
final VideoData? videoData;
const VideoPlayerFullPage({
Key? key,
required this.size,
required this.videoTypes,
required this.videoData,
}) : super(key: key);
final Size size;
@override
State createState() => _VideoPlayerFullPageState();
}
class _VideoPlayerFullPageState extends State<VideoPlayerFullPage> {
late VideoPlayerController videoController;
bool isInitPlaying = false;
bool isBuffering = false;
List<CommentData> comments = []; //評(píng)論數(shù)據(jù)列表
double videoWidth = 0;
double videoHeight = 0;
double _currentSliderValue = 0.0;
@override
void initState() {
videoController = VideoPlayerController.network(widget.videoData!.videoUrl)
..initialize().then((value) {
videoController.play();
videoController.setLooping(true);
setState(() {
_currentSliderValue = 0.0;
isInitPlaying = true;
videoWidth = videoController.value.size.width;
videoHeight = videoController.value.size.height;
});
});
videoController.addListener(videoListener);
super.initState();
}
void videoListener() {
setState(() {
isBuffering = videoController.value.isBuffering;
_currentSliderValue = videoController.value.position.inSeconds.toDouble();
});
}
@override
void dispose() {
videoController.removeListener(videoListener);
videoController.dispose();
super.dispose();
}
/// 底部視頻話題 點(diǎn)擊
Future<void> onVideoTagsClick(VideoTag videoTag) async {
print('=====> 點(diǎn)擊了視頻話題');
}
///點(diǎn)贊
Future<void> onLikeClick(VideoData videoData) async {
print('=====> 點(diǎn)擊了點(diǎn)贊');
}
///評(píng)論
Future<void> onCommentClick(BuildContext context, VideoData videoData) async {
print('=====> 點(diǎn)擊了評(píng)論');
// 延遲200ms 模擬網(wǎng)絡(luò)請(qǐng)求
await Future.delayed(const Duration(milliseconds: 200));
comments = testCommentData;
showCommentBottomSheet(context, comments, videoData);
}
///觀看人數(shù)
Future<void> onWatchClick(VideoData videoData) async {
print('=====> 點(diǎn)擊了觀看人數(shù)');
}
///分享
Future<void> onShareClick(VideoData videoData) async {
print('=====> 點(diǎn)擊了分享');
}
///加好友
Future<void> onAddFriendClick(VideoData videoData) async {
print('=====> 點(diǎn)擊了加好友');
}
///發(fā)布人名稱點(diǎn)擊
Future<void> onUserNameClick(VideoData videoData) async {
print('=====> 點(diǎn)擊了發(fā)布人名稱');
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.grey,
height: widget.size.height,
width: widget.size.width,
child: widget.videoData == null
? Center(
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: const Color(0x80444444),
),
child: Column(
children: const [
SizedBox(
height: 20,
),
Icon(
Icons.error_outline,
size: 50,
),
SizedBox(
height: 70,
),
Text(
'無數(shù)據(jù)',
style: TextStyle(fontSize: 20, color: Colors.white),
),
],
),
),
)
: GestureDetector(
onTap: () {
print('============>視頻點(diǎn)擊 ');
setState(() {
videoController.value.isPlaying
? videoController.pause()
: videoController.play();
});
},
child: Container(
height: widget.size.height,
width: widget.size.width,
decoration: const BoxDecoration(color: Colors.black),
child: Stack(
children: <Widget>[
videoWidth > videoHeight
? Center(
child: AspectRatio(
aspectRatio: videoController.value.aspectRatio,
child: VideoPlayer(videoController),
),
)
: AspectRatio(
aspectRatio: videoController.value.aspectRatio,
child: VideoPlayer(videoController),
),
Center(
child: !videoController.value.isPlaying && !isInitPlaying
? Image.network(
widget.videoData!.albumImg,
width: widget.size.width,
height: widget.size.height,
fit: BoxFit.cover,
)
: const SizedBox(),
),
Center(
child: Container(
decoration: const BoxDecoration(),
child: isPlaying(),
),
),
isBuffering || !videoController.value.isInitialized
? const Center(
child: SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: Color(0xFF69DCE5),
),
),
)
: const SizedBox(),
Padding(
padding:
const EdgeInsets.only(left: 0, top: 10, bottom: 10),
child: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
bottomPanel(
widget.size,
widget.videoData!,
),
rightPanel(
context,
widget.size,
widget.videoData!,
)
],
),
),
SizedBox(
height: 10,
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 3, // 軌道高度
trackShape:
const RoundedRectSliderTrackShape(), // 軌道形狀擦囊,可以自定義
activeTrackColor:
const Color(0xFF444444), // 激活的軌道顏色
inactiveTrackColor:
const Color(0x80444444), // 未激活的軌道顏色
thumbColor: const Color(0xFF999999), // 滑塊顏色
thumbShape: const RoundSliderThumbShape(
// 滑塊形狀男娄,可以自定義
enabledThumbRadius: 4 // 滑塊大小
),
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 10, // 設(shè)置滑塊的覆蓋層半徑
),
),
child: Slider(
value: _currentSliderValue,
min: 0.0,
max: videoController.value.duration.inSeconds
.toDouble(),
onChanged: (value) {
setState(() {
_currentSliderValue = value;
videoController.seekTo(
Duration(seconds: value.toInt()));
});
},
),
),
),
],
),
),
),
videoWidth > videoHeight
? GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => FullScreenVideoPage(
videoController: videoController)),
);
},
child: Padding(
padding:
const EdgeInsets.only(top: 500, left: 150),
child: SizedBox(
width: 110,
height: 40,
child: Container(
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(20.0),
color: const Color(0x80444444),
),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: const [
SizedBox(
width: 3,
),
Icon(
Icons.fullscreen,
color: Colors.white,
),
Text(
'全屏觀看',
style: TextStyle(
fontSize: 14,
color: Colors.white,
),
),
SizedBox(
width: 3,
),
],
)),
)))
: const SizedBox(),
],
),
),
),
);
}
Widget isPlaying() {
if (videoController.value.isInitialized) {
return videoController.value.isPlaying
? const SizedBox()
: Image.asset(
'assets/images/icon_play.png',
width: 80,
height: 80,
);
} else {
return const SizedBox();
}
}
String _formatDuration(Duration duration) {
return '${duration.inMinutes.remainder(60).toString().padLeft(2, '0')}:${(duration.inSeconds % 60).toString().padLeft(2, '0')}';
}
Widget bottomPanel(Size size, VideoData videoData) {
return Container(
width: size.width * 0.8,
height: size.height,
padding: const EdgeInsets.only(left: 15),
decoration: const BoxDecoration(),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
GestureDetector(
onTap: () {
onUserNameClick(videoData);
},
child: Text(
'@${videoData.userName}',
style: const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
),
),
const SizedBox(
height: 10,
),
Container(
margin: const EdgeInsets.only(right: 30),
child: Row(
children: [
videoData.type == "1"
? Container(
padding: const EdgeInsets.only(
left: 4, right: 4, top: 2, bottom: 2),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(3.0), // 設(shè)置圓角
color: const Color(0xFF8B452B), // 設(shè)置背景顏色
),
child: const Text(
'精',
style: TextStyle(
color: Color(0xFFF47947),
fontSize: 13,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
)
: const SizedBox(),
const SizedBox(
width: 10,
),
Text(
videoData.title,
style: const TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
Text(
' · ${videoData.time}',
style: const TextStyle(
color: Colors.grey,
fontSize: 13,
),
),
],
),
),
const SizedBox(
height: 5,
),
Container(
margin: const EdgeInsets.only(right: 30),
child: Text(
videoData.description,
style: const TextStyle(
color: Colors.white,
fontSize: 14,
),
),
),
const SizedBox(
height: 10,
),
Wrap(
spacing: 8.0, // 主軸(水平)方向間距
runSpacing: 2.0, // 縱軸(垂直)方向間距
children: videoData.videoTags.map((item) {
return GestureDetector(
onTap: () {
onVideoTagsClick(item);
},
child: Container(
padding: const EdgeInsets.only(
left: 6, right: 6, top: 3, bottom: 3),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0), // 設(shè)置圓角
color: const Color(0xFF69DCE5), // 設(shè)置背景顏色
),
child: Text(
'#${item.tagName}',
style: const TextStyle(
color: Colors.white,
fontSize: 12,
),
textAlign: TextAlign.center,
),
),
);
}).toList(),
),
const SizedBox(
height: 10,
),
],
),
);
}
Widget rightPanel(BuildContext context, Size size, VideoData videoData) {
return Expanded(
child: SizedBox(
height: size.height,
child: Column(
children: <Widget>[
Container(
height: size.height * 0.4,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
getProfile(videoData),
getLike(videoData, 25.0),
getComment(context, videoData, 25.0),
getWatch(videoData, 25.0),
getShare(videoData, 25.0),
const SizedBox(
height: 60,
),
],
))
],
),
),
);
}
Widget getLike(VideoData videoData, double size) {
return GestureDetector(
onTap: () {
onLikeClick(videoData);
},
child: Column(
children: <Widget>[
videoData.likeStatus == "1"
?
//已點(diǎn)贊
Image.asset(
'assets/images/icon_like.png',
width: size,
height: size,
)
//未點(diǎn)贊
: Image.asset(
'assets/images/icon_like.png',
width: size,
height: size,
),
const SizedBox(
height: 5,
),
Text(
videoData.likes,
style: const TextStyle(
color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700),
)
],
),
);
}
Widget getComment(BuildContext context, VideoData videoData, double size) {
return GestureDetector(
onTap: () {
onCommentClick(context, videoData);
},
child: Column(
children: <Widget>[
Image.asset(
'assets/images/icon_comment.png',
width: size,
height: size,
),
const SizedBox(
height: 5,
),
Text(
videoData.comments,
style: const TextStyle(
color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700),
)
],
),
);
}
Widget getWatch(VideoData videoData, double size) {
return GestureDetector(
onTap: () {
onWatchClick(videoData);
},
child: Column(
children: <Widget>[
Image.asset(
'assets/images/icon_watch.png',
width: size,
height: size,
),
const SizedBox(
height: 5,
),
Text(
videoData.watchers,
style: const TextStyle(
color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700),
)
],
),
);
}
Widget getShare(VideoData videoData, double size) {
return GestureDetector(
onTap: () {
onShareClick(videoData);
},
child: Column(
children: <Widget>[
Image.asset(
'assets/images/icon_share.png',
width: size,
height: size,
),
const SizedBox(
height: 5,
),
Text(
videoData.shares,
style: const TextStyle(
color: Colors.white, fontSize: 12, fontWeight: FontWeight.w700),
)
],
),
);
}
Widget getProfile(VideoData videoData) {
return GestureDetector(
onTap: () {
onAddFriendClick(videoData);
},
child: SizedBox(
width: 50,
height: 60,
child: Stack(
children: <Widget>[
Container(
width: 50,
height: 50,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(videoData.userAvatarUrl),
fit: BoxFit.cover)),
),
Positioned(
bottom: 3,
left: 18,
child: Container(
width: 20,
height: 20,
decoration: const BoxDecoration(
shape: BoxShape.circle, color: Color(0xFF69DCE5)),
child: const Center(
child: Icon(
Icons.add,
color: Colors.white,
size: 15,
)),
))
],
),
),
);
}
void showCommentBottomSheet(BuildContext context, List<CommentData> comments,
VideoData videoData) async {
await showModalBottomSheet(
context: context,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
),
enableDrag: true,
isScrollControlled: true,
builder: (_) => CommentBottomSheet(
commentData: comments,
videoData: videoData,
),
);
}
}
class CommentBottomSheet extends StatefulWidget {
final List<CommentData> commentData;
final VideoData videoData;
const CommentBottomSheet({
Key? key,
required this.commentData,
required this.videoData,
}) : super(key: key);
@override
State<CommentBottomSheet> createState() => _CommentsBottomSheetState();
}
class _CommentsBottomSheetState extends State<CommentBottomSheet> {
List<CommentData> comments = [];
VideoData? videoData;
TextEditingController textEditingController = TextEditingController();
FocusNode focusNode = FocusNode();
String hint = "寫評(píng)論";
@override
void initState() {
comments = widget.commentData;
videoData = widget.videoData;
super.initState();
}
/// 發(fā)送評(píng)論
onSendComment(String input) {
print('========> 發(fā)送評(píng)論:$input');
}
/// 點(diǎn)贊評(píng)論
onLikeComment(CommentData commentData) {
print('========> 點(diǎn)贊評(píng)論');
}
/// 回復(fù)評(píng)論
onReplayComment(CommentData commentData) {
print('========> 回復(fù)評(píng)論');
}
/// 回復(fù)評(píng)論中的回復(fù)
onReplayCommentReplay(CommentData commentData, CommentData replayComment) {
print('========> 回復(fù)評(píng)論中的回復(fù)');
}
/// 查看全部評(píng)論
onWatchAllComment(CommentData commentData) {
print('========> 查看全部評(píng)論');
for (int i = 0; i < comments.length; i++) {}
}
/// 底部輸入框 點(diǎn)贊
onBottomLike() {
print('========> 底部輸入框 點(diǎn)贊');
}
/// 底部輸入框 分享
onBottomShare() {
print('========> 底部輸入框 分享');
}
/// 底部輸入框 收藏
onBottomFavorite() {
print('========> 底部輸入框 收藏');
}
/// 底部輸入框 評(píng)論
onBottomComment() {
print('========> 底部輸入框 評(píng)論');
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 500,
child: Stack(
children: [
// 評(píng)論列表
Padding(
padding: const EdgeInsets.only(top: 60, bottom: 70),
child: ListView.builder(
shrinkWrap: true,
itemCount: comments.length,
itemBuilder: (BuildContext context, int index) {
return CommentItem(comments[index], comments, index);
},
),
),
Align(
alignment: Alignment.topCenter,
child: // 評(píng)論數(shù)
Container(
padding: const EdgeInsets.only(
top: 16, left: 16, right: 16, bottom: 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
color: Colors.white,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
'${comments.length}條評(píng)論',
style:
const TextStyle(fontSize: 15, color: Colors.grey),
),
],
),
const SizedBox(
height: 15,
),
Container(
margin: const EdgeInsets.only(left: 16, right: 16),
child: const Divider(
height: 1,
color: Colors.grey,
),
)
],
),
),
),
Align(
alignment: Alignment.bottomCenter,
child: // 輸入框和操作欄
Container(
color: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
margin: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom),
child: Row(
children: [
Flexible(
child: TextField(
controller: textEditingController,
focusNode: focusNode,
onSubmitted: submitComment,
onEditingComplete: () {
submitComment(textEditingController.text);
},
keyboardType: TextInputType.multiline,
maxLines: null,
textInputAction: TextInputAction.send,
decoration: InputDecoration(
hintText: hint,
filled: true,
isDense: true,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20),
borderSide: BorderSide.none,
),
),
),
),
const SizedBox(width: 4),
SizedBox(
width: 40,
child: GestureDetector(
onTap: () {
onBottomFavorite();
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.star_border,
color: Color(0xFF9F9F9F),
),
Text(
'${videoData?.favorites}',
style: const TextStyle(
color: Color(0xFF9F9F9F),
fontSize: 12,
),
),
],
)),
),
SizedBox(
width: 40,
child: GestureDetector(
onTap: () {
onBottomShare();
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.ios_share_outlined,
color: Color(0xFF9F9F9F),
),
Text(
'${videoData?.shares}',
style: const TextStyle(
color: Color(0xFF9F9F9F),
fontSize: 12,
),
),
],
)),
),
SizedBox(
width: 40,
child: GestureDetector(
onTap: () {
onBottomComment();
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.comment_outlined,
color: Color(0xFF9F9F9F),
),
Text(
'${videoData?.comments}',
style: const TextStyle(
color: Color(0xFF9F9F9F),
fontSize: 12,
),
),
],
)),
),
SizedBox(
width: 40,
child: GestureDetector(
onTap: () {
onBottomLike();
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(
Icons.thumb_up_alt_outlined,
color: Color(0xFF9F9F9F),
),
Text(
'${videoData?.likes}',
style: const TextStyle(
color: Color(0xFF9F9F9F),
fontSize: 12,
),
),
],
)),
),
],
),
),
),
],
),
);
}
void submitComment(String inputText) {
if (textEditingController.text.isEmpty) return;
onSendComment(textEditingController.text);
textEditingController.clear();
hint = '寫評(píng)論';
focusNode.unfocus();
}
Widget CommentItem(
CommentData commentData, List<CommentData> comments, int index) {
var size = MediaQuery.of(context).size;
return Container(
color: Colors.white,
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 45,
height: 45,
decoration: BoxDecoration(
border: Border.all(color: Colors.white),
shape: BoxShape.circle,
image: DecorationImage(
image: NetworkImage(commentData.userAvatarUrl),
fit: BoxFit.cover)),
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
commentData.userName,
style: const TextStyle(
fontSize: 18, color: Colors.black),
),
const SizedBox(height: 2),
Text(
commentData.time,
style: const TextStyle(
fontSize: 12, color: Colors.grey),
),
],
),
const SizedBox(width: 120),
// 點(diǎn)贊數(shù)量
GestureDetector(
onTap: () {
onLikeComment(commentData);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
commentData.likeStatus == "1"
? const Icon(
Icons.thumb_up,
color: Color(0xFF67DCE7),
)
: const Icon(
Icons.thumb_up_off_alt_outlined,
color: Colors.grey,
),
const SizedBox(width: 4),
Text(
commentData.likes,
style: TextStyle(
fontSize: 17,
color: commentData.likeStatus == "1"
? const Color(0xFF67DCE7)
: Colors.grey,
),
),
],
),
),
],
),
const SizedBox(height: 10),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
commentData.type == "1"
? Container(
padding: const EdgeInsets.only(
left: 4, right: 4, top: 2, bottom: 2),
decoration: BoxDecoration(
borderRadius:
BorderRadius.circular(3.0), // 設(shè)置圓角
color: const Color(0xFFFFF0EC), // 設(shè)置背景顏色
),
child: const Text(
'精',
style: TextStyle(
color: Color(0xFFED7F55),
fontSize: 13,
fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
)
: const SizedBox(),
const SizedBox(
width: 5,
),
GestureDetector(
onTap: () {
setState(() {
hint = "回復(fù) ${commentData.userName} : ";
});
FocusScope.of(context).requestFocus(focusNode);
onReplayComment(commentData);
},
child: SizedBox(
width: size.width - 148,
child: Text(
commentData.content,
style: const TextStyle(
fontSize: 17,
color: Colors.black,
),
),
),
),
const SizedBox(width: 30),
],
),
const SizedBox(height: 8),
// 回復(fù)內(nèi)容
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5.0), // 設(shè)置圓角
color: const Color(0xFFF3F3F3),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
...List.generate(
commentData.replayList.length,
(index) => ReplyItem(commentData,
commentData.replayList[index], size.width),
),
const SizedBox(
height: 5,
),
// 查看全部回復(fù)
GestureDetector(
onTap: () {
// 處理查看全部回復(fù)邏輯
onWatchAllComment(commentData);
},
child: Row(
children: [
Text(
'全部${commentData.replayList.length}條回復(fù)',
style: const TextStyle(
color: Colors.black, fontSize: 15),
),
const Icon(Icons.arrow_forward_ios_rounded,
size: 15, color: Colors.black),
],
)),
],
),
),
],
),
],
),
index == comments.length - 1
? Container(
margin: const EdgeInsets.only(top: 10),
child: const Text(
'- 沒有更多了哦 -',
style: TextStyle(
fontSize: 14,
color: Colors.grey,
fontWeight: FontWeight.bold),
),
)
: const SizedBox(),
],
),
);
}
Widget ReplyItem(
CommentData commentData, CommentData replayComment, double width) {
return GestureDetector(
onTap: () {
setState(() {
hint = "回復(fù) ${replayComment.userName} : ";
});
FocusScope.of(context).requestFocus(focusNode);
onReplayCommentReplay(commentData, replayComment);
},
child: SizedBox(
width: width - 120,
child: RichText(
text: TextSpan(
children: [
TextSpan(
text: replayComment.userName,
style: const TextStyle(
color: Color(0xFF67DCE7),
fontSize: 14,
),
),
const TextSpan(
text: ' 回復(fù) ',
style: TextStyle(
fontSize: 14,
color: Color(0xFF707070),
),
),
TextSpan(
text: replayComment.replayName,
style: const TextStyle(
color: Color(0xFF67DCE7),
fontSize: 14,
),
),
TextSpan(
text: ' : ${replayComment.replayContent}',
style: const TextStyle(
color: Color(0xFF707070),
fontSize: 14,
),
),
],
),
),
),
);
}
}
class FullScreenVideoPage extends StatefulWidget {
final VideoPlayerController videoController;
const FullScreenVideoPage({Key? key, required this.videoController})
: super(key: key);
@override
_FullScreenVideoPageState createState() => _FullScreenVideoPageState();
}
class _FullScreenVideoPageState extends State<FullScreenVideoPage> {
double _currentSliderValue = 0.0;
bool isBuffering = false;
bool isInitPlaying = false;
@override
void initState() {
super.initState();
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
]);
setState(() {
_currentSliderValue = 0.0;
isInitPlaying = true;
});
widget.videoController.addListener(videoListener);
}
void videoListener() {
setState(() {
isBuffering = widget.videoController.value.isBuffering;
_currentSliderValue =
widget.videoController.value.position.inSeconds.toDouble();
});
}
@override
void dispose() {
widget.videoController.removeListener(videoListener);
super.dispose();
}
Widget isPlaying() {
if (widget.videoController.value.isInitialized) {
return widget.videoController.value.isPlaying
? const SizedBox()
: Image.asset(
'assets/images/icon_play.png',
width: 80,
height: 80,
);
} else {
return const SizedBox();
}
}
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
backgroundColor: Colors.black,
body: GestureDetector(
onTap: () {
setState(() {
widget.videoController.value.isPlaying
? widget.videoController.pause()
: widget.videoController.play();
});
},
child: Stack(
children: [
VideoPlayer(widget.videoController),
Padding(
padding: const EdgeInsets.only(top: 25, right: 20),
child: IconButton(
icon: const Icon(
Icons.close,
size: 30,
),
color: Colors.white,
onPressed: () {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
Navigator.pop(context);
},
),
),
Center(
child: Container(
decoration: const BoxDecoration(),
child: isPlaying(),
),
),
isBuffering || !widget.videoController.value.isInitialized
? const Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Color(0xFF69DCE5),
),
),
)
: const SizedBox(),
Align(
alignment: Alignment.bottomCenter,
child: Container(
margin: const EdgeInsets.only(bottom: 10),
height: 10,
child: SliderTheme(
data: SliderTheme.of(context).copyWith(
trackHeight: 3, // 軌道高度
trackShape:
const RoundedRectSliderTrackShape(), // 軌道形狀嗤练,可以自定義
activeTrackColor: const Color(0xFF444444), // 激活的軌道顏色
inactiveTrackColor: const Color(0x80444444),
thumbColor: const Color(0xFF999999), // 未激活的軌道顏色
thumbShape: const RoundSliderThumbShape(
// 滑塊形狀扇调,可以自定義
enabledThumbRadius: 4 // 滑塊大小
),
overlayShape: const RoundSliderOverlayShape(
overlayRadius: 10, // 設(shè)置滑塊的覆蓋層半徑
),
),
child: Slider(
value: _currentSliderValue,
min: 0.0,
max: widget.videoController.value.duration.inSeconds
.toDouble(),
onChanged: (value) {
setState(() {
_currentSliderValue = value;
widget.videoController
.seekTo(Duration(seconds: value.toInt()));
});
},
),
),
),
),
],
),
),
),
onWillPop: () async {
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]);
Navigator.pop(context);
return false;
});
}
}
class VideoData {
final String id; // 唯一id
final String uid; // 發(fā)布人 uid
final String type; //type = 1 視頻加 精
final String videoUrl; //視頻地址
final String albumImg; //視頻第一幀封面
final String userName; //發(fā)布者名
final String userAvatarUrl; //發(fā)布者頭像
final String description; //視頻描述
final String title; //視頻標(biāo)題
final String likes; //視頻點(diǎn)贊數(shù)
final String likeStatus; //0未點(diǎn)贊 1 已點(diǎn)贊
final String comments; //視頻評(píng)論數(shù)
final String shares; //視頻分享數(shù)
final String watchers; //視頻觀看數(shù)
final String favorites; //視頻收藏?cái)?shù)
final String time; //視頻發(fā)布時(shí)間
final List<VideoTag> videoTags; //視頻關(guān)聯(lián)話題
VideoData({
required this.id,
required this.uid,
required this.type,
required this.videoUrl,
required this.albumImg,
required this.userName,
required this.userAvatarUrl,
required this.description,
required this.title,
required this.likes,
required this.likeStatus,
required this.comments,
required this.shares,
required this.watchers,
required this.favorites,
required this.time,
required this.videoTags,
});
}
class VideoTag {
final String tagId; //視頻關(guān)聯(lián)話題id
final String tagName; //視頻關(guān)聯(lián)話題名
VideoTag({
required this.tagId,
required this.tagName,
});
}
class VideoType {
final String typeId; //視頻分類id
final String typeName; //視頻分類名
VideoType({
required this.typeId,
required this.typeName,
});
}
class CommentData {
final String id; // 唯一id
final String uid; // 評(píng)論用戶uid
final String userName; // 評(píng)論用戶uid
final String userAvatarUrl; // 評(píng)論用戶uid
final String time; // 發(fā)布評(píng)論時(shí)間
final String type; //type = 1 評(píng)論加 精
final String content; //評(píng)論文案
final String likes; //評(píng)論點(diǎn)贊數(shù)
final String likeStatus; //0未點(diǎn)贊 1 已點(diǎn)贊
final String replayName; //被回復(fù)者
final String replayUid; //被回復(fù)者 uid
final String replayContent; //回復(fù)內(nèi)容
final List<CommentData> replayList;
CommentData({
required this.id,
required this.uid,
required this.userName,
required this.userAvatarUrl,
required this.time,
required this.type,
required this.content,
required this.likes,
required this.likeStatus,
required this.replayName,
required this.replayUid,
required this.replayContent,
required this.replayList,
});
}
/// 測(cè)試數(shù)據(jù)
List<CommentData> testCommentData = <CommentData>[
CommentData(
id: "2524525",
uid: "5254453",
userName: "晴子",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "1",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [
CommentData(
id: "2545",
uid: "11541",
userName: "用戶1",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "5383",
uid: "57225",
userName: "用戶2",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "42458",
uid: "245454",
userName: "用戶3",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
],
),
CommentData(
id: "56535",
uid: "52482",
userName: "蝦仁",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [
CommentData(
id: "5353",
uid: "24535",
userName: "用戶4",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "5355",
uid: "35434",
userName: "用戶5",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "5452",
uid: "35572",
userName: "用戶6",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
],
),
CommentData(
id: "87886",
uid: "6765",
userName: "晴子",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [
CommentData(
id: "8768",
uid: "68737",
userName: "用戶7",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "68727",
uid: "68778",
userName: "用戶8",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
CommentData(
id: "12821",
uid: "8755",
userName: "用戶9",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
time: "2023/01/17 14:30:22",
type: "1",
content: "有情趣又熱愛生活的人真好有情趣又熱愛生活",
likes: "100",
likeStatus: "0",
replayName: "蝦仁",
replayUid: "11111",
replayContent: "奔馳發(fā)布全新旅行車更適合大家出行要不要試駕",
replayList: [],
),
],
),
];
List<VideoType> testVideoType = <VideoType>[
VideoType(typeId: "1111", typeName: "熱門"),
VideoType(typeId: "1111", typeName: "分類一"),
VideoType(typeId: "1111", typeName: "分類二"),
VideoType(typeId: "1111", typeName: "分類三"),
VideoType(typeId: "1111", typeName: "分類四"),
];
List<VideoData> testVideoData = <VideoData>[
VideoData(
id: "111",
uid: "1233",
type: "1",
videoUrl: "https://static.ybhospital.net/test-video-2.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽矿咕,花開進(jìn)度80%,準(zhǔn)備和愛車路過全世界狼钮,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "130",
likeStatus: "1",
comments: "186",
shares: "135",
watchers: "328",
favorites: "636",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "111",
uid: "1233",
type: "1",
videoUrl: "https://static.ybhospital.net/test-video-3.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽碳柱,花開進(jìn)度80%,準(zhǔn)備和愛車路過全世界熬芜,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "130",
likeStatus: "1",
comments: "165",
shares: "135",
watchers: "320",
favorites: "105",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "111",
uid: "1233",
type: "1",
videoUrl: "https://static.ybhospital.net/test-video-4.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽莲镣,花開進(jìn)度80%,準(zhǔn)備和愛車路過全世界涎拉,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "150",
likeStatus: "1",
comments: "185",
shares: "136",
watchers: "280",
favorites: "500",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "111",
uid: "1233",
type: "1",
videoUrl: "https://static.ybhospital.net/test-video-5.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽瑞侮,花開進(jìn)度80%的圆,準(zhǔn)備和愛車路過全世界,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "365",
likeStatus: "1",
comments: "425",
shares: "253",
watchers: "854",
favorites: "524",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "111",
uid: "1233",
type: "1",
videoUrl: "https://static.ybhospital.net/test-video-6.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽半火,花開進(jìn)度80%越妈,準(zhǔn)備和愛車路過全世界,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "352",
likeStatus: "1",
comments: "585",
shares: "425",
watchers: "825",
favorites: "245",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "2525",
uid: "35435",
type: "1",
videoUrl: "https://media.w3.org/2010/05/sintel/trailer.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽钮糖,花開進(jìn)度80%梅掠,準(zhǔn)備和愛車路過全世界,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "252",
likeStatus: "1",
comments: "424",
shares: "245",
watchers: "453",
favorites: "523",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
VideoTag(tagId: "1111", tagName: "南京車友圈"),
VideoTag(tagId: "1111", tagName: "活動(dòng)名稱"),
]),
VideoData(
id: "2525",
uid: "35435",
type: "1",
videoUrl: "https://jomin-web.web.app/resource/video/video_iu.mp4",
albumImg:
"https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fc-ssl.duitang.com%2Fuploads%2Fblog%2F202107%2F05%2F20210705100427_ee4b8.thumb.1000_0.jpg&refer=http%3A%2F%2Fc-ssl.duitang.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1628415177&t=87962cc902fb5925da0a4d60d4c48ca9",
userName: "發(fā)布人名稱",
userAvatarUrl:
"https://p16-tiktokcdn-com.akamaized.net/aweme/720x720/tiktok-obj/1663771856684033.jpeg",
description: "春日的暖陽店归,花開進(jìn)度80%阎抒,準(zhǔn)備和愛車路過全世界,感受獨(dú)具魅力的嶺南文化!",
title: "視頻標(biāo)題",
likes: "252",
likeStatus: "1",
comments: "424",
shares: "245",
watchers: "453",
favorites: "523",
time: "2023年12月16日",
videoTags: [
VideoTag(tagId: "1111", tagName: "今天去哪玩"),
]),
];