Una aplicación Douban simple
efecto de vídeo
berro
Breve descripción de las funciones
Función
- lista Top250
- detalles de la pelicula
- Lista de películas favoritas
- Búsqueda borrosa
- buscar Historia
- lista de búsqueda
- limpiar historial de búsqueda
- Desliza hacia los lados para eliminar videos favoritos
biblioteca de terceros
nombre | Observación |
---|---|
Dio | biblioteca de red |
proveedor | administración del Estado |
sqlfite | base de datos |
tostadas | Consejos para tostadas |
flutter_swiper | Bandera |
pk_esqueleto | Pantalla de esqueleto |
Breve descripción de la interfaz.
Dado que muchas interfaces de Douban están actualmente restringidas, este proyecto utiliza la dirección de la interfaz organizada por alguien en Github. Hay un total de cuatro interfaces que se pueden usar. Entre ellas, las dos interfaces relacionadas con la búsqueda solo se pueden acceder una vez dentro de los 30 segundos. Aparece code:429,访问频繁异常
, las dos interfaces restantes son ilimitadas
- Obtenga la lista de las 250 mejores películas:
`https://api.wmdb.tv/api/v1/top?type=Imdb&skip=0&limit=50&lang=Cn`
- Buscar por ID de Douban
https://api.wmdb.tv/movie/api?id=doubanid
- Buscar por palabras clave de video
https://api.wmdb.tv/api/v1/movie/search?q=英雄本色&limit=10&skip=0&lang=Cn&year=2002
- Generación de carteles de cine.
https://api.wmdb.tv/movie/api/generateimage?doubanId=1306123&lang=En
barra de navegación inferior
El color de fondo de cada uno BottomNavigationBarItem
es diferente, y cada inicialización se obtiene aleatoriamente
representaciones
lograr
Inicializar BottomNavigationBarItem
Los colores de fondo de los tres elementos se obtienen aleatoriamente
///底部导航栏数据
final pageList = [const HomePage(),const SearchPage(),const MinePage()];
final navList = [
BottomNavigationBarItem(
icon: const Icon(Icons.home),
label: '首页',
backgroundColor: Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))
),
BottomNavigationBarItem(
icon: const Icon(Icons.search),
label: '搜索',
backgroundColor: Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))
),
BottomNavigationBarItem(
icon: const Icon(Icons.person),
label: '我的',
backgroundColor: Color.fromARGB(255, Random().nextInt(255), Random().nextInt(255), Random().nextInt(255))
)
];
bottomNavigationBar
Definir el número de página actual del registro
var _currentPage = 0;
- ítems: correspondientes a los subítems de Navegación
- currentIndex: el número de serie de la página actual
- tipo: el método de visualización de la barra de navegación inferior
- onTap: haga clic en evento
bottomNavigationBar: BottomNavigationBar(
items: navList,
currentIndex: _currentPage,
type: BottomNavigationBarType.shifting,
onTap: (index){
_changeIndex(index);
},
)
Para cambiar de página, haga un juicio simple para evitar la ejecución repetida de hacer clic en la página actual
void _changeIndex(int index){
if(index != _currentPage){
setState(() {
_currentPage = index;
});
}
}
Cambiar de página hace que se vuelvan a dibujar las subpáginas bottomNavigationBar
Al usar IndexedStack
, no se recargará cuando se cambien las subpáginas
body: IndexedStack(
index: _currentPage,
children: pageList,
)
lista Top250
Al FutureBuilder
ejecutar el método de solicitud de red y luego evaluar si se devuelven los datos, waiting
mostrarlos en el estado 骨架屏loading
, notificar al proveedor que actualice después de que se devuelvan los datos y luego actualice localmente.电影ListView
representaciones
lograr
Árbol de widgets
El segundo Widget es solo una descripción simple a través de Columna y Fila, sin dibujar completamente todo el árbol de Widget
FuturoConstructor
Entre ellos, getTopList() es una solicitud de red para obtener la lista Top 250. Para el paquete de red de contenido específico, consulte el paquete Dio
Obtener y analizar los datos de la lista Top250
///电影榜单前250名接口数据解析
Future<List<ITopEntity>?> getTopList(int skip,int limit) async {
var url = HttpPath.getTop250(skip, limit);
final result = await HttpUtil.getInstance().get(url, null);
return jsonConvert.convertListNotNull<ITopEntity>(result);
}
Construir FutureBuilder
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(10.0),
color: Colors.white,
child: FutureBuilder(
future: getTopList(skip, limit),
builder: _buildFutureBuild
),
);
}
Luego FutureBuilder
monitoree cada estado, donde waiting
el estado devuelve el componente de pantalla de esqueleto, y luego comience a construir después de que se devuelvan los datosListView
///FutureBuild+骨架屏
///FutureBuild会执行两次,第一次状态为waiting,第二次为done(在正常情况下)
Widget _buildFutureBuild(BuildContext context, AsyncSnapshot<List<ITopEntity>?> snapshot){
switch(snapshot.connectionState){
case ConnectionState.none:
return const Text('未开始网络请求...');
case ConnectionState.active:
return const Text('开始网络请求...');
case ConnectionState.waiting:
return PKCardPageSkeleton(totalLines: 6);
case ConnectionState.done:
if(snapshot.hasError) {
return const Text('请求过于频繁,请稍后再试...');
}else{
if(snapshot.data == null) {
return const Center(child: Text('请求过于频繁,请稍后再试...'));
} else {
Widget widget = _buildMovie(snapshot.data!);
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_loadMovieList(snapshot.data!);
});
return widget;
}
}
}
}
anormal
Cuando estoy FutureBuilder
construyendo un ListView, cuando regresan los datos, necesito pasar Provider
los datos y puedo comenzar a actualizar, pero mi ListView actual está en construcción, por lo que aparecerá la siguiente advertencia, pero no afectará la ejecución del código. pasar la actualización de datos se usa 帧回调
para monitorear, lo que significa que se ejecuta secuencialmente. Esta devolución de llamada solo se ejecutará después de que se complete la construcción de mi diseño, y esta devolución de llamada solo se ejecutará una vez.
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
_loadMovieList(snapshot.data!);
});
// The following assertion was thrown while dispatching notifications for MovieProvider:
// setState() or markNeedsBuild() called during build.
//
// This _InheritedProviderScope<MovieProvider> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
// The widget on which setState() or markNeedsBuild() was called was: _InheritedProviderScope<MovieProvider>
// value: Instance of 'MovieProvider'
// listening to value
// The widget which was currently being built when the offending call was made was: FutureBuilder<List<ITopEntity>?>
// dirty
// state: _FutureBuilderState<List<ITopEntity>?>#aba8e
ListView pull up cargando
- Definir el controlador de carga pull-up
final ScrollController _scrollController = ScrollController();
- Agregar oyente de pull-up
///这个判断相当于屏幕底部高度和手指滑动到底部高度一致时执行上拉操作
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if(_scrollController.position.pixels == _scrollController.position.maxScrollExtent){
skip += limit;
_loadMovieData();
}
});
}
Úselo provider
para la administración del estado, luego agregue los datos adquiridos y realice una actualización parcial
///上拉加载
Future _loadMovieData() async{
await getTopList(skip,limit).then((value){
final movieProvider = context.read<MovieProvider>();
if(value != null){
movieProvider.setList = value;
}
});
}
- Construya el ListView
///电影ListView
///电影Provider修改作用域
Widget _buildMovie(List<ITopEntity> entityList){
return Consumer<MovieProvider>(
builder: (ctx,movieProvider,child){
return ListView.builder(
itemCount: movieProvider.getList.length,
controller: _scrollController,
itemBuilder:(context,index){
return createMovieItem(movieProvider.getList[index],index);
});
},
);
}
detalles de la pelicula
representaciones
lograr
Todavía use FutureBuilder para implementar solicitudes de datos y luego complete después de que se devuelvan los datos
desenfoque gaussiano
La imagen de fondo son los datos después de la devolución de llamada y luego se desdibujan. La relación de desenfoque se puede modificar pasando sigmaX
y valorsigmaY
ImageFiltered(
imageFilter: ImageFilter.blur(sigmaX:10,sigmaY: 10),
child: Image.network(
snapshot.data?.data[0].poster ?? '',
fit: BoxFit.fill,
width: double.infinity,
height: double.infinity,),
)
Adquisición de datos de red
Realice una solicitud de red pasándola doubanId
como un parámetro, y luego analice y devuelva
///电影详情数据解析
Future<IMovieDetailEntity> getMovieDetail(String doubanid) async{
var url = HttpPath.getMovieDetail(doubanid);
final result = await HttpUtil.getInstance().get(url, null);
return IMovieDetailEntity.fromJson(result);
}
detalles de la pelicula
El póster de la película y la descripción de la información de la película envueltos por el fondo blanco a continuación se muestran usando Stack
y , y el componente deslizante se usa para envolver.Position
SingleChildScrollView
///电影海报和电影描述重叠
Widget buildMovieDetail(BuildContext context,IMovieDetailEntity? entity){
if(entity == null) return const Text('请求过于频繁,请稍后再试...');
final height = MediaQuery.of(context).size.height;
final width = MediaQuery.of(context).size.width;
return SingleChildScrollView(
scrollDirection: Axis.vertical,
physics: const BouncingScrollPhysics(),
reverse: false,
child: Container(
width: width,
height: height,
padding: const EdgeInsets.only(top: 20.0),
//color: Colors.grey,
alignment: Alignment.topCenter,
child: Stack(
children: [
_buildMovieDescribe(entity),
Positioned(
top: 0,
left: 0,
right: 0,
child: buildMovieImage(entity.data[0].poster,250.0))
],
),
),
);
}
El contenido específico Column
se muestra verticalmente a través del diseño. Debido a razones de espacio, el contenido de visualización del subwidget específico no se presentará.
///电影描述
Widget _buildMovieDescribe(IMovieDetailEntity entity){
return Container(
width: double.infinity,
height: double.infinity,
margin: const EdgeInsets.only(top: 180.0,left: 15.0,right: 15.0,bottom: 20.0),
decoration: const BoxDecoration(
shape: BoxShape.rectangle,
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
child: Padding(
padding: const EdgeInsets.only(top: 100.0,left: 10,right: 10,bottom: 0),
child:Column(
children: [
getText(entity.data[0].name,textSize: 20.0,fontWeight: FontWeight.bold),
const SizedBox(height: 5.0),
getText(entity.data.length > 1 ? entity.data[1].name : 'unknown',textSize: 16.0,color: Colors.grey),
const SizedBox(height: 20.0),
_buildYearCountryWidget(entity),
const SizedBox(height: 10.0),
_buildGenreWidget(entity),
const SizedBox(height: 20.0),
_buildCollection(entity),
const SizedBox(height: 20.0),
getText(entity.data[0].description,textSize: 12,color: Colors.black,maxLine: 10),
const SizedBox(height: 10.0),
getText(entity.data.length > 1 ? entity.data[1].description : 'unknown...',textSize: 12,color: Colors.black,maxLine: 10),
const SizedBox(height: 20.0),
splitLine,
const SizedBox(height: 20.0),
_buildActorRowWidget('作者:',entity.writer,0),
const SizedBox(height: 10.0),
_buildActorRowWidget('导演:',entity.director,1),
const SizedBox(height: 10.0),
_buildActorRowWidget('演员:',entity.actor,2),
const SizedBox(height: 10.0),
_buildMovieDateWidget('日期:',0,1,entity.dateReleased.isNotEmpty ? entity.dateReleased: '未知'),
const SizedBox(height: 10.0),
_buildMovieDateWidget('片长:',entity.duration,2)
],
),
)
);
}
Búsqueda borrosa
representaciones
lograr
El contenido de la búsqueda y el campo de datos devuelto se distinguen por el resaltado de texto enriquecido
Widget _buildSearchListView(String key) {
if(key.isNotEmpty && key == _lastSearch){
return defaultContent!;
}else{
_lastSearch = key;
}
///模糊搜索列表,添加富文本高亮显示
Widget displayContent = const Center(child: CircularProgressIndicator());
List<ITopEntity> resultList = [];
getSearchList(key)
.then((value) => {
if(value != null){
resultList = value
}})
.catchError(
(e) => {
displayContent = const Center(child: Text('加载失败!'))})
.whenComplete(() => {
displayContent = ListView.builder(
itemCount: resultList.length,
itemBuilder: (context, index) {
return ListTile(
leading: const Icon(Icons.search, size: 20),
title: Transform(
transform: Matrix4.translationValues(-16, 0.0, 0.0),
child: RichText(
overflow: TextOverflow.ellipsis,
text: TextSpan(
text: resultList[index].data[0].name.substring(0, key.length),
style: const TextStyle(
color: Colors.black,
fontWeight: FontWeight.bold),
children: [
TextSpan(
text: resultList[index].data[0].name.substring(key.length),
style: const TextStyle(color: Colors.grey),
)
]),
),
),
onTap: () {
exeSearchInsert(SearchRecord(resultList[index].data[0].name.toString()), context);
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SearchResultPage(resultList[index].data[0].name)));
},
);
}),
setState(() {
if (resultList.isEmpty) {
displayContent = const Center(child: Text('没有搜索到相关内容!'));
}else{
defaultContent = displayContent;
}
}),
});
return displayContent;
}
buscar Historia
representaciones
lograr
Almacenamiento local a través sqlite
de la base de datos. Para obtener más información, consulte el uso y el empaquetado de datos de SQLite . Aquí, solo se introducen la inserción de datos, la limpieza de registros y la construcción de diseños.
Búsqueda de historial
Título del historial, dándole GestureDetector
un evento de clic y manejando la operación de compensación en el evento de clic
Widget _buildHistory(){
return Column(
children: [
GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
getText('历史记录',color: Colors.black,textSize: 16.0,fontWeight: FontWeight.bold),
const Icon(Icons.delete_forever,color: Colors.grey)
]
),
onTap: (){
///清空搜索记录
exeSearchClear(context);
},
),
Flexible(child: _buildHistoryList())
],
);
}
lista de búsqueda
Al GridView
crear una lista con 4 columnas por fila, porque los datos cambiarán con el cambio de la búsqueda, a través de la provider
gestión de estado, cuando se inserten los datos, se le notificará al oyente para realizar una actualización parcial.
parámetro | Observación |
---|---|
número de ejes cruzados | Número de columnas |
espaciado del eje principal | Distancia entre husillos |
crossAxisSpacing | Espaciado entre ejes transversales |
childAspectRatio | relación de aspecto del elemento |
///构建搜索记录List列表
Widget _buildHistoryList() {
return Container(
margin: const EdgeInsets.only(top: 10.0),
child: Consumer<SearchProvider>(
builder: (ctx, searchProvider, child) {
return GridView.builder(
itemCount: searchProvider.searchList.length,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 4,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 3),
itemBuilder: (ctx, index) {
return _buildHistoryItem(searchProvider.searchList[index].searchKey);
});
}),
);
}
///搜索记录item
Widget _buildHistoryItem(String key){
return GestureDetector(
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(5.0),
decoration: const BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.all(Radius.circular(10.0))
),
child: getText(key,color: Colors.white),
),
onTap: (){
exeSearchInsert(SearchRecord(key),context);
Navigator.of(context).push(MaterialPageRoute(builder: (context) => SearchResultPage(key)));
},
);
}
insertar registro
Primero juzgue si el mismo contenido existe en la base de datos, si existe, no se procesará, de lo contrario, se insertará en la base de datos y luego se provider
modificará la fuente de datos.
exeSearchInsert(SearchRecord(_searchContent.toString()), context);
exeSearchInsert(SearchRecord entity,BuildContext context) async {
bool isExist = await SearchDao.getInstance().isExist(entity.searchKey);
if(!isExist){
final provider = context.read<SearchProvider>();
provider.addSingleBean = entity;
await SearchDao.getInstance().insert(entity);
}
}
borrar registro
También se divide en dos pasos, uno es borrar la base de datos y el otro es provider
la fuente de datos en poder de la situación.
///清空搜索记录
exeSearchClear(BuildContext context) async{
final provider = context.read<SearchProvider>();
if(provider.searchList.isEmpty){
showFailedToast('记录为空,无需清空!');
return;
}
int count = await SearchDao.getInstance().deleteAll();
if(count > 0){
provider.clear();
showSuccessToast('清空完成!');
}else{
showFailedToast('清空失败!');
}
}
Resultados de la búsqueda
representaciones
lograr
obtener datos de red
///电影搜索列表解析
Future<List<ITopEntity>?> getSearchList(String key) async {
var url = HttpPath.getSearchContent(key);
final result = await HttpUtil.getInstance().get(url, null);
return jsonConvert.convertListNotNull<ITopEntity>(result);
}
lista de resultados de búsqueda
También se utiliza FutureBuilder
, pase la solicitud de red anterior en sus future
parámetros, luego builder
controle el estado de los datos y cree una lista de datos
///构建搜索结果list列表
Widget _buildMovieList(List<ITopEntity>? entityList){
if(entityList == null || entityList.length == 0) return const Center(child: Text('没有搜索到相关内容!'));
return ListView.builder(
itemCount: entityList.length,
itemBuilder: (BuildContext context, int index){
return Container(
padding: const EdgeInsets.all(10.0),
child: MovieItem(
MovieFavorite(
entityList[index].doubanId,
entityList[index].data[0].poster,
entityList[index].data[0].name,
entityList[index].data[0].country,
entityList[index].data[0].language,
entityList[index].data[0].genre,
entityList[index].data[0].description
)
),
);
});
}
Elemento de película
Dado que el elemento de la película aquí es coherente con el elemento de la lista de favoritos, se encapsula en un StatelessWidget
widget sin estado
class MovieItem extends StatelessWidget {
final MovieFavorite entity;
const MovieItem(this.entity,{
Key? key}) : super(key: key);
///构建电影item右边部分
Widget _buildItemRight(MovieFavorite entity,double height){
return Container(
height: height,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 10.0),
getText(entity.movieName,textSize: 16.0,fontWeight: FontWeight.bold,color: Colors.black),
const SizedBox(height: 10.0),
getText(entity.movieCountry,textSize: 12.0),
const SizedBox(height: 5.0),
getText(entity.movieLanguage,textSize: 12.0),
const SizedBox(height: 5.0),
getText(entity.movieGenre.isNotEmpty ? entity.movieGenre : '未知',textSize: 12.0),
const SizedBox(height: 5.0),
Expanded(child: getText(entity.movieDescription,textSize: 12.0,color: Colors.grey,maxLine: 5)),
const SizedBox(height: 10.0),
],
),
);
}
///构建每一个搜索列表item
Widget _buildMovieItem(MovieFavorite entity,BuildContext context){
return GestureDetector(
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
buildMovieImage(entity.moviePoster, 200),
const SizedBox(width: 10.0),
Expanded(child: _buildItemRight(entity,200))
],
),
onTap: (){
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MovieDetailPage(entity.doubanId, entity.movieName)));
},
);
}
Widget build(BuildContext context) {
return Container(
child: _buildMovieItem(entity,context),
);
}
}
colección de videos
representaciones
lograr
Dado que el elemento de la película favorita es el mismo que el elemento de la película del resultado de la búsqueda, se encapsula en un StatelessWidget
widget sin estado, como se muestra arriba, por lo que no es redundante aquí.
Agregar a los favoritos
Solo cuando la película agregada no tiene una instancia correspondiente en la base de datos, se puede agregar y luego provider
se notifica la modificación de datos mediante Toast para solicitar
exeFavoriteInsert(
MovieFavorite(
entity.doubanId,
entity.data[0].poster,
entity.data[0].name,
entity.data[0].country,
entity.data[0].language,
entity.data[0].genre,
entity.data[0].description),
context
);
exeFavoriteInsert(MovieFavorite entity,BuildContext context) async {
bool isExist = await FavoriteDao.getInstance().isExist(entity.doubanId);
if(!isExist){
int flag = await FavoriteDao.getInstance().insert(entity);
final favoriteProvide = context.read<FavoriteProvider>();
final List<MovieFavorite> list = [entity];
favoriteProvide.favoriteList = list;
if (flag > 0) {
showSuccessToast('收藏成功!');
} else {
showFailedToast('收藏失败!');
}
}else{
showFailedToast('此影片已被收藏,请勿重复添加!');
}
}
Deslizar para eliminar
representaciones
lograr
Deslizamiento lateral descartable
La eliminación del deslizamiento lateral se Dismissible
realiza deslizando de izquierda a derecha sin eliminar, solo se muestra un fragmento de texto con fondo azul, el contenido se background
proporciona a la derecha y se devuelve uno Widget
; al deslizar de derecha a izquierda se mostrará el texto de fondo rojo, siempre que secondaryBackground
por y mostrar el cuadro de solicitud de eliminación. El punto clave es confirmDismiss
el método. Lo que necesita devolver es Future<bool?>
el valor de tipo. Si devuelve ture
, se eliminará de la lista actual. Esta eliminación es solo estática. El elemento se elimina de la lista actual. La eliminación específica la lógica debe manejarse por sí misma, y el false
elemento no se eliminará si regresa
Widget _buildFavoriteList(){
return Container(
margin: const EdgeInsets.all(10.0),
child: Consumer<FavoriteProvider>(
builder: (context,favoriteProvider,child){
if(favoriteProvider.favoriteList.isEmpty){
return const Center(child: Text('暂未收藏任何影片!'));
}else{
return ListView.builder(
itemCount: favoriteProvider.favoriteList.length,
itemBuilder: (BuildContext context, int index){
return Dismissible(
key: Key(favoriteProvider.favoriteList[index].movieName),
background: Container(
alignment: Alignment.center,
color: Colors.blue,
child: ListTile(
leading: const Icon(Icons.tag_faces_rounded,color: Colors.white,),
title: getText('就不让你删,气死你!!!',color: Colors.white))
),///从右到左的背景颜色
secondaryBackground: Container(
alignment: Alignment.center,
color: Colors.red,
child: ListTile(
leading: const Icon(Icons.delete_forever,color: Colors.white),
title: getText('删就删咯,反正不爱了...',color: Colors.white))),///从左到右的背景颜色
confirmDismiss: (direction) async{
switch(direction){
case DismissDirection.endToStart:
return await _showDeleteDialog(context, favoriteProvider.favoriteList[index],index,favoriteProvider) == true;
case DismissDirection.vertical:
case DismissDirection.horizontal:
case DismissDirection.startToEnd:
case DismissDirection.up:
case DismissDirection.down:
case DismissDirection.none:
break;
}
return false;
},
child: Container(
padding: const EdgeInsets.all(10.0),
child: MovieItem(favoriteProvider.favoriteList[index]),
),
);
});
}
},
)
);
}
Dismissible
Las direcciones de deslizamiento son las siguientes, incluido el modo
enum DismissDirection {
/// The [Dismissible] can be dismissed by dragging either up or down.
vertical,
/// The [Dismissible] can be dismissed by dragging either left or right.
horizontal,
/// The [Dismissible] can be dismissed by dragging in the reverse of the
/// reading direction (e.g., from right to left in left-to-right languages).
endToStart,
/// The [Dismissible] can be dismissed by dragging in the reading direction
/// (e.g., from left to right in left-to-right languages).
startToEnd,
/// The [Dismissible] can be dismissed by dragging up only.
up,
/// The [Dismissible] can be dismissed by dragging down only.
down,
/// The [Dismissible] cannot be dismissed by dragging.
none
}
Eliminar cuadro de solicitud
Deslice hacia atrás de derecha a izquierda true
, lo que significa eliminar el elemento, como se explicó anteriormente, si la función devuelve lo mismo, true
elimínelo y viceversa
return await _showDeleteDialog(context, favoriteProvider.favoriteList[index],index,favoriteProvider) == true;
Al AlertDialog
implementar el cuadro de diálogo, un título, un contenido, un botón Aceptar y un botón Cancelar, los botones manejan diferentes lógicas, que son claramente visibles, pero el valor devuelto no se refleja en el código bool
. Mirando el código fuente, se puede ver que eliminando la parte superior actual del montón del montón context
, además del contexto, hay otro bool
valor, que es dialog
el valor devuelto
/// A dialog box might be closed with a result:
///
/// ```dart
/// void _accept() {
/// Navigator.pop(context, true); // dialog returns true
/// }
/// ```
///删除提示框
Future<bool?> _showDeleteDialog(BuildContext context, MovieFavorite bean,int index,FavoriteProvider provider) {
return showDialog<bool>(
context: context,
barrierDismissible: true,
builder: (BuildContext context) {
return AlertDialog(
title: getText('系统提示',textAlign: TextAlign.center,fontWeight: FontWeight.bold,textSize: 20),
content: getText('是否要从收藏列表中删除影片《${
bean.movieName}》?',textSize: 14,maxLine: 2),
actions: <Widget>[
TextButton(
child: getText('确定',color: Colors.red),
onPressed: () async{
await FavoriteDao.getInstance().delete(bean.doubanId);
provider.removeItem = index;
showSuccessToast('删除成功!');
Navigator.pop(context,true);
},
),
TextButton(
child: getText('取消',color: Colors.blue),
onPressed: () {
showSuccessToast('取消删除!');
Navigator.pop(context,false);
},
),
],
);
},
);
}