@Sh1d0w 的答案很好,但对于那些不太了解颤振本身的人来说,它缺乏内容。我不得不说谢谢 Sh1d0w,因为你给了我基础来创建我自己的代码来使用 youtube url 获取数据。
我使用 SearchDelegate 结合了他的答案以显示结果,我仍然需要添加代码以显示下一个/上一个页面结果(youtube 仅按页面提供 50 个结果)但要回答您的问题,这里是代码.
最后,您将能够获得以下信息:
发布时间
频道 ID
标题
描述
缩略图(默认、高、中)
直播内容
视频ID
如果您想要 json 中的任何其他数据,您需要在 Class Snippet 中添加您想要的内容,以便稍后返回和使用。
由于我使用的是在 snapshot.data 对象中提供数据的 futureBuilder,因此您需要使用这种声明来获取每个属性:
snapshot.data[index].title
snapshot.data[index].description
snapshot.data[index].thumbnails.high.url
**在代码中您会看到 appTheme.text 等内容,它们只是颜色的变量,请根据您的颜色更改它们。
** sizeBuild() 或 fontSizeBuild() 之类的函数是我为我的案例创建的函数,您只需删除这些行并根据需要编写任意数字
common_youtubeApi.dart
import 'dart:convert';
class YoutubeResponse{
//!-1st level parameters of youtube api for playlist
//!-https://developers.google.com/youtube/v3/docs/playlistItems/list#response
String kind;
String etag;
String nextPageToken;
String prevPageToken;
String regionCode;
List<Item> items;
//change the default values for the obtained values from url
YoutubeResponse({
this.kind,
this.etag,
this.nextPageToken,
this.prevPageToken,
this.regionCode,
this.items
});
//Json decode and make a dart object called Map
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'nextPageToken': nextPageToken,
'prevPageToken': prevPageToken,
'regionCode': regionCode,
'items': items,
};
factory YoutubeResponse.fromJSON(Map<String, dynamic> YoutubeResponseJson){
var list = YoutubeResponseJson['items'] as List;
List<Item> itemsList = list.map((i)=> Item.fromJSON(i)).toList();
return new YoutubeResponse(
kind: YoutubeResponseJson['kind'],
etag: YoutubeResponseJson['etag'],
nextPageToken: YoutubeResponseJson['nextPageToken'],
prevPageToken: YoutubeResponseJson['prevPageToken'],
regionCode: YoutubeResponseJson['regionCode'],
// mPageInfo: pageInfo.fromJSON(YoutubeResponseJson['pageInfo']),
items: itemsList
);
}
}
//---------Create an single video item
class Item{
String kind;
String etag;
String id;
Snippet snippet;
Item({
this.kind, this.etag, this.id, this.snippet
});
Map<String, dynamic> toJson() => {
'kind': kind,
'etag': etag,
'id': id,
'snippet': snippet,
};
factory Item.fromJSON(Map<String, dynamic> ItemJson) {
return Item(
kind: ItemJson['kind'],
etag: ItemJson['etag'],
id: ItemJson['id'],
snippet: Snippet.fromJSON(ItemJson['snippet']),
);
}
}
class Snippet {
String publishedAt;
String channelId;
String title;
String description;
Thumbnails thumbnails;
String channelTitle;
String liveBroadcastContent;
String videoId;
Snippet(
{this.publishedAt,
this.channelId,
this.title,
this.description,
this.thumbnails,
this.channelTitle,
this.liveBroadcastContent,
this.videoId,
});
Map<String, dynamic> toJson() => {
'publishedAt': publishedAt,
'channelId': channelId,
'title': title,
'description': description,
'thumbnails': thumbnails,
'channelTitle': channelTitle,
'liveBroadcastContent': liveBroadcastContent,
'videoId': videoId,
};
factory Snippet.fromJSON(Map<String, dynamic> snippetJson) {
return Snippet(
publishedAt: snippetJson['publishedAt'],
channelId: snippetJson['channelId'],
title: snippetJson['title'],
description: snippetJson['description'],
thumbnails: Thumbnails.fromJSON(snippetJson['thumbnails']) ,
channelTitle: snippetJson['channelTitle'],
liveBroadcastContent: snippetJson['liveBroadcastContent'],
videoId: snippetJson['resourceId']['videoId'],
);
}
}
class Medium {
int height;
int width;
String url;
Medium({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Medium.fromJSON(Map<String, dynamic> MediumJson) {
return Medium(
height: MediumJson['height'],
width: MediumJson['width'],
url: MediumJson['url'],
);
}
}
class High {
int height;
int width;
String url;
High({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory High.fromJSON(Map<String, dynamic> HighJson) {
return High(
height: HighJson['height'],
width: HighJson['width'],
url: HighJson['url'],
);
}
}
class Default {
int height;
int width;
String url;
Default({this.height, this.width, this.url});
Map<String, dynamic> toJson() => {
'height': height,
'width': width,
'url': url,
};
factory Default.fromJSON(Map<String, dynamic> defaultJson) {
return Default(
height: defaultJson['height'],
width: defaultJson['width'],
url: defaultJson['url'],
);
}
}
class Thumbnails {
Default mDefault;
Medium medium;
High high;
Thumbnails({this.mDefault, this.medium, this.high});
var data = JsonEncoder().convert("");
Map<String, dynamic> toJson() => {
'default': mDefault,
'medium': medium,
'high': high,
};
factory Thumbnails.fromJSON(Map<String, dynamic> thumbnailsJson) {
return Thumbnails(
mDefault: Default.fromJSON(thumbnailsJson['default']),
medium: Medium.fromJSON(thumbnailsJson['medium']),
high: High.fromJSON(thumbnailsJson['high']),
);
}
}
searchList.dart
要导入的文件:
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:denApp/keys/app_keys.dart'; //put your keys always in a different file with gitignore
import 'package:url_launcher/url_launcher.dart'; //for links in each item
import 'package:denApp/Widgets/common_youtubeAPI.dart'; //here are the models of each data requested in the json to be used here
class DataSearch extends SearchDelegate<List>{
var nextPageToken;
var prevPageToken;
Future<void> _launched;
//ajax/http request for data
Future<List<Snippet>> getVideos(http.Client client) async {
YoutubeResponse parsedResponse;
final response = await client
.get('https://www.googleapis.com/youtube/v3/playlistItems?part=snippet&playlistId=$youtubeTrickPlaylist&key=$youtubeAPIKEY&maxResults=50');
List<Snippet> videos = [];
parsedResponse = YoutubeResponse.fromJSON(json.decode(response.body));
for(var i=0; i < parsedResponse.items.length; i++ ){
videos.add(parsedResponse.items[i].snippet);
}
this.nextPageToken = parsedResponse.nextPageToken;
this.prevPageToken = parsedResponse.prevPageToken;
print(this.nextPageToken);
print(this.prevPageToken);
return videos;
}
//We use the launcher plugin to manage the click to go to the website, please visit the plugin web for info how to use it
Future<void> _launchInBrowser(String url) async {
print(url);
if (await canLaunch(url)) {
await launch(
url,
forceSafariVC: false,
forceWebView: false,
headers: <String, String>{'my_header_key': 'my_header_value'},
);
} else {
throw 'Could not launch $url';
}
}
//------------------------------------------------------
//--This part is to edit the colors and design of the searchDelegate widget, I have a separated file with the themes, colors which I call using appTheme, so you need to change all those variables with your own colors.
@override
ThemeData appBarTheme(BuildContext context) {
return ThemeData(
primaryColor: appTheme.text,
backgroundColor: appTheme.dark,
bottomAppBarColor: appTheme.dark,
canvasColor: appTheme.dark,
);
}
//-------------------------------------------------------------
//---Here we define how it will works the SearchDelegate and its icons/functions-----------
@override
List<Widget> buildActions(BuildContext context) {
// ----This is the icon which will delete whatever you write in your searchbar
return [IconButton(icon: Icon(Icons.clear), onPressed: () {
query="";
},)];
}
@override
Widget buildLeading(BuildContext context) {
// ---- This is the icon which will allow you to close the search delegate going to the back page.
return IconButton(icon: Icon(Icons.arrow_back), onPressed: (){
close(context, null);
});
}
@override
Widget buildResults(BuildContext context) {
// nothing here
throw UnimplementedError();
}
@override
Widget buildSuggestions(BuildContext context) {
// This is actually the place where you will do all the work when you receive the data from your future request. Since we are going to build something after we wait for the data, we need to use futureBuilder instead of builder only. In the future propiety you will put the function which will do the request to the api
return FutureBuilder<List<Snippet>>(
future: getVideos(http.Client()),
builder: (context, snapshot) {
//during the build you need to check the connection state so depending on what is happening you will show something , like a loader if it is waiting state, etc
switch (snapshot.connectionState){
case ConnectionState.none:
return Text('none', style: TextStyle(color:Colors.black));
case ConnectionState.active:
return Text('active', style: TextStyle(color:Colors.black));
case ConnectionState.waiting:
return Center(
child: CircularProgressIndicator(),
);
default:
//after checking connection we need to check if we do not got null data from the request
if(snapshot.data != null){
var dt = snapshot.data.where((p)=> p.title.toLowerCase().contains(query.toLowerCase())).toList();
return dt.isEmpty? Center(child:Text("There was no results for your query", style: TextStyle(color: appTheme.text))) : ListView.builder(
itemCount: dt.length,
padding: EdgeInsets.zero,
itemBuilder: (context, index) {
if(query.isEmpty){
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
// color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 5),
),
Text(snapshot.data[index].description,
maxLines: 3,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}else{
return Card(
margin: (index == (dt.length - 1)) ? EdgeInsets.only(top: sizeBuild(context, "height", 5), bottom: sizeBuild(context, "height", 15)) : EdgeInsets.only(top: sizeBuild(context, "height", 5)),
color: appTheme.bgLight.withOpacity(.1),
child: InkWell(
splashColor: appTheme.linkB.withAlpha(30),
onTap: () {
_launched = _launchInBrowser('https://www.youtube.com/watch?v=${snapshot.data[index].videoId}');
},
child: Container(
child: Row(
children: <Widget>[
Container(
width:sizeBuild(context, "width", 140),
child: Image.network("${snapshot.data[index].thumbnails.high.url}", fit: BoxFit.cover),
),
Container(
margin: EdgeInsets.symmetric(horizontal: sizeBuild(context, "width",10)),
width:sizeBuild(context, "width", 280),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(snapshot.data[index].title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text,
fontWeight: FontWeight.bold,
fontSize: fontSizeBuild(context, 'body')
),
),
SizedBox(
height: sizeBuild(context, "height", 20),
),
Text(snapshot.data[index].description,
maxLines: 5,
overflow: TextOverflow.ellipsis,
style: TextStyle (
color: appTheme.text.withOpacity(.7),
fontSize: fontSizeBuild(context, 'small')
),
),
],
),
),
],
),
),
),
);
}
},
);
}else{
//this is the widget to give if there is no data
return Center(child: Text("There was no data from the server", style: TextStyle(color:appTheme.text)));
}
}
},
);
}
}