file1 : pageViewWidg.dart
class PageViewWidg extends StatefulWidget {
final String videoUrl;
final String videoUrl2;
final String avatarImg;
PageViewWidg({
super.key,
required this.videoUrl,
required this.avatarImg,
required this.videoUrl2,
});
#override
State<PageViewWidg> createState() => _PageViewWidgState();
}
class _PageViewWidgState extends State<PageViewWidg> {
late VideoPlayerController controller;
#override
void initState() {
controller = VideoPlayerController.asset(widget.videoUrl)..initialize().then((_) {
// Ensure the first frame is shown after the video is initialized, even before the play button has been pressed.
setState(() {});
});
super.initState();
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
// build Profile
buildProfuile(String profilePhoto){
return SizedBox(
width: 60,
height:60,
child: Stack(
children: [
Positioned(child: Container(
width: 50,
height: 50,
padding: const EdgeInsets.all(1),
decoration: BoxDecoration(
color:Colors.white,
borderRadius: BorderRadius.circular(25),
),
child:ClipRRect(
borderRadius: BorderRadius.circular(25),
child: Image(image: NetworkImage(profilePhoto),fit: BoxFit.cover),
),
),
),
],
),
);
}
// build Music Album
buildMusicAlbum(String profilePhoto){
return SizedBox(
width: 60,
height:60,
child:Column(
children: [
Container(
padding: const EdgeInsets.all(8),
width: 50,
height:50,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [ Colors.grey,Colors.white]
),
borderRadius: BorderRadius.circular(25),
),
child:ClipRRect(
borderRadius: BorderRadius.circular(15),
child: Image(image: NetworkImage(profilePhoto),fit: BoxFit.cover,),
)
)
],
)
);
}
#override
Widget build(BuildContext context) {
return Expanded(
child: InkWell(
onTap:(){
Navigator.of(context).push(
MaterialPageRoute(builder: (_) => ExpandPageView(avatarImg: widget.avatarImg,imgUrl: widget.videoUrl,controller: controller,),
));
},
child: Stack(
children: [
// AspectRatio(
// aspectRatio: controller.value.aspectRatio,
// child: VideoPlayer(controller),
// ),
controller.value.isInitialized
?
VideoPlayer(controller)
:
Container(),
Center(
child: InkWell(
onTap:(){
controller.value.isPlaying
?
setState((){
controller.pause();
})
:
setState((){
controller.play();
});
},
child:
Image.asset(
controller.value.isPlaying
?
'assets/images/pause_icon.png'
:
'assets/images/play_icon.png'
,
height:50,
)
),
),
file2 : home_screen.dart
class HomeSceen extends StatefulWidget {
const HomeSceen({super.key});
#override
State<HomeSceen> createState() => _HomeSceenState();
}
class _HomeSceenState extends State<HomeSceen> {
List<PageViewWidg> videos=[
PageViewWidg(
videoUrl: 'assets/videos/video.mp4',
videoUrl2: 'assets/videos/video.mp4',
avatarImg: 'assets/images/pic2.jpg',
),
PageViewWidg(
videoUrl: 'assets/videos/video2.mp4',
videoUrl2: 'assets/videos/video2.mp4',
avatarImg: 'assets/images/pic1.jfif',
),
PageViewWidg(
videoUrl: 'assets/videos/video3.mp4',
videoUrl2: 'assets/videos/video3.mp4',
avatarImg: 'assets/images/pic2.jpg',
),
PageViewWidg(
videoUrl: 'assets/videos/video4.mp4',
videoUrl2: 'assets/videos/video2.mp4',
avatarImg: 'assets/images/pic1.jfif',
),
PageViewWidg(
videoUrl: 'assets/videos/video2.mp4',
videoUrl2: 'assets/videos/video2.mp4',
avatarImg: 'assets/images/pic2.jpg',
),
PageViewWidg(
videoUrl: 'assets/videos/video2.mp4',
videoUrl2: 'assets/videos/video2.mp4',
avatarImg: 'assets/images/pic2.jpg',
),
];
final PageController pagecontroller=PageController(
initialPage: 0,
);
//
#override
Widget build(BuildContext context) {
// final size=MediaQuery.of(context).size;
bool like=true;
return Scaffold(
body:SafeArea(
child: PageView.builder(
itemCount: videos.length,
controller:pagecontroller,
scrollDirection: Axis.vertical,
itemBuilder: (context,index){
return Stack(
alignment: AlignmentDirectional.centerEnd,
children:[
Column(
children: [
videos[index],
// Divider(height:2,color:Color.fromARGB(255, 76, 72, 72)),
videos[index+1],
],
),
I tried to add two controller and every controller of video,and it's not work for me.
VideoPlayerController _controller1;
VideoPlayerController _controller2;
#override
void initState() {
super.initState();
_controller1 = VideoPlayerController.assets(videoUrl1)
..initialize().then((_) {
setState(() {});
});
_controller2 = VideoPlayerController.assets(videoUrl2)
..initialize().then((_) {
setState(() {});
});
}
what I need now is how to control two videos when video is start the second will stop and
change their icon or imageAssets like if the icon is paused it changes to the play icon and the second video also if the first video has a pause icon another video will be change to start video.
here is the answer that I found finally.
late VideoPlayerController _controller1;
late VideoPlayerController _controller2;
bool _isPaused1 = true;
bool _isPaused2 = true;
#override
void initState() {
super.initState();
_controller1 =VideoPlayerController.network(videos[3]);
_controller1.addListener(() {
setState(() {});
});
// _controller1.setLooping(true);
_controller1.initialize().then((_) => setState(() {}));
_controller2 =VideoPlayerController.network(videos[5]);
_controller2.addListener(() {
setState(() {});
});
// _controller2.setLooping(true);
_controller2.initialize().then((_) => setState(() {}));
}
#override
void dispose() {
_controller1.dispose();
_controller2.dispose();
super.dispose();
}
_playPauseToggle1() {
setState(() {
if (_isPaused1) {
_controller1.play();
_controller2.pause(); // if _controller1 is playing, _controller2 should be paused
_isPaused2 = true;
} else {
_controller1.pause();
}
_isPaused1 = !_isPaused1;
});
}
_playPauseToggle2() {
setState(() {
if (_isPaused2) {
_controller2.play();
_controller1.pause(); // if _controller2 is playing, _controller1 should be paused
_isPaused1 = true;
} else {
_controller2.pause();
}
_isPaused2 = !_isPaused2;
});
}
VideoPlayer(_controller1),
Center(
child: InkWell(
onTap: _playPauseToggle1,
// ignore: dead_code
child:_isPaused1?
Image.asset('assets/images/play_icon.png',height:50,color: Colors.grey,)
:
Image.asset('assets/images/pause_icon.png',height:50,color: Colors.grey,)
),
),
VideoPlayer(_controller2),
Center(
child: InkWell(
onTap: _playPauseToggle2,
// ignore: dead_code
child:_isPaused2 ?
Image.asset('assets/images/play_icon.png',height:50,color: Colors.grey,)
:
Image.asset('assets/images/pause_icon.png',height:50,color: Colors.grey,)
),
),
and depends on the video if is starting I change the icon otherwise I change the icon to another state.
and I hope it will be helpful for some who looking for like this question.
Related
Animation library is giving me this error about lifecycle state-- 'package:flutter/src/widgets/framework.dart': Failed assertion: line 4519 pos 12: '_lifecycleState != _ElementLifecycle.defunct': is not true. is was getting this error when i used animation controller.
After the error my flutter application crashed and i have to reload everytime.
// ignore_for_file: depend_on_referenced_packages, sized_box_for_whitespace, avoid_print
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:habbit_app/controllers/timer_tab_controller.dart';
import 'package:habbit_app/screens/timer/set_timer_component.dart';
import 'package:flutter_ringtone_player/flutter_ringtone_player.dart';
import 'package:habbit_app/widgets/text_widget/label_text.dart';
class TimerTab extends StatefulWidget {
const TimerTab({super.key});
#override
State<TimerTab> createState() => _TimerTabState();
}
class _TimerTabState extends State<TimerTab> with TickerProviderStateMixin {
AnimationController? animationController;
TimerTabController tabController =
Get.put(TimerTabController(), permanent: false);
late TabController _controller;
bool isPlaying = false;
// int get countText2 {
// int count = tabController.totalSeconds;
// return;
// }
String get countText {
Duration count =
animationController!.duration! * animationController!.value;
return animationController!.isDismissed
? '${animationController!.duration!.inHours}:${(animationController!.duration!.inMinutes % 60).toString().padLeft(2, '0')}:${(animationController!.duration!.inSeconds % 60).toString().padLeft(2, '0')}'
: '${count.inHours}:${(count.inMinutes % 60).toString().padLeft(2, '0')}:${(count.inSeconds % 60).toString().padLeft(2, '0')}';
}
#override
void dispose() {
animationController!.dispose();
super.dispose();
}
double progress = 1.0;
void notifiy() {
if (countText == "0:00:00") {
FlutterRingtonePlayer.playNotification();
}
}
#override
void initState() {
// ignore: todo
// TODO: implement initState
TimerTabController tabController =
Get.put(TimerTabController(), permanent: false);
super.initState();
_controller = TabController(length: 3, vsync: this);
_controller.addListener(() {
tabController.tabIndex.value = _controller.index;
// print(tabController.tabIndex.value);
});
super.initState();
animationController = AnimationController(
vsync: this, duration: Duration(seconds: tabController.totalSeconds));
animationController!.addListener(() {
notifiy();
if (animationController!.isAnimating) {
setState(() {
progress = animationController!.value;
});
} else {
setState(() {
progress = 1.0;
isPlaying = false;
});
}
});
}
#override
Widget build(BuildContext context) {
var color = Theme.of(context);
return Obx(
() => tabController.isFirst.value
? SetTimerComponent(
changeAnimation: () {
animationController!.duration =
Duration(seconds: tabController.totalSeconds);
tabController.changeFirst();
},
)
: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// start timer screen
Container(
height: 350,
width: MediaQuery.of(context).size.width,
// color: color.cardColor,
child: Column(children: [
Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 300,
height: 300,
child: CircularProgressIndicator(
color: color.primaryColor,
backgroundColor:
color.disabledColor.withOpacity(0.6),
value: progress,
strokeWidth: 10,
)),
GestureDetector(
onTap: () {
if (animationController!.isDismissed) {
showModalBottomSheet(
context: context,
builder: (context) => Container(
height: 300,
child: CupertinoTimerPicker(
initialTimerDuration:
animationController!.duration!,
backgroundColor:
color.backgroundColor,
onTimerDurationChanged: (time) {
setState(() {
animationController!.duration =
time;
});
},
),
));
}
},
child: AnimatedBuilder(
animation: animationController!,
builder: ( context, child) => Text(
countText,
style: TextStyle(
fontSize: 60,
fontWeight: FontWeight.bold,
color: color.primaryColor),
),
),
),
],
),
]),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () {
print(tabController.totalSeconds);
if (animationController!.isAnimating) {
animationController!.stop();
setState(() {
isPlaying = false;
});
} else {
animationController!.reverse(
from: animationController!.value == 0
? 1.0
: animationController!.value);
setState(() {
isPlaying = true;
});
}
},
child: Container(
height: 35,
width: 90,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(30)),
color: color.primaryColor),
child: Center(
child: LabelText(
text: isPlaying == true ? "PAUSE" : "RESUME",
isBold: true,
isColor: true,
color: Colors.white,
)),
),
),
GestureDetector(
onTap: () {
animationController!.reset();
tabController.currentvalueHour.value = 0;
tabController.currentvalueMin.value = 0;
tabController.currentvalueSec.value = 0;
setState(() {
isPlaying = false;
});
tabController.changeFirst2();
},
child: Container(
height: 35,
width: 90,
decoration: BoxDecoration(
borderRadius:
const BorderRadius.all(Radius.circular(30)),
color: color.disabledColor.withOpacity(0.5)),
child: const Center(
child: LabelText(
text: "DELETE",
isColor: true,
color: Colors.white,
isBold: true,
)),
),
)
],
)
],
),
);
}
}
I would like to, on long press on a item in the list, animate in a checkbox on the leading end of the list item for every list item like in this video.
I have tried with implicit animations, here under is my list item code.
class InspectionItemWidget extends StatefulWidget {
final Inspection inspection;
final Function(Inspection, bool) selected;
const InspectionItemWidget(
{Key? key, required this.inspection, required this.selected})
: super(key: key);
#override
State<InspectionItemWidget> createState() => _InspectionItemWidgetState();
}
class _InspectionItemWidgetState extends State<InspectionItemWidget> {
File? _imageFile;
final _duration = const Duration(milliseconds: 400);
late double _selectWidth;
late double _opacity;
late bool _selected;
#override
void initState() {
super.initState();
_selectWidth = 0.0;
_opacity = 0.0;
_selected = false;
di<EventBus>().on<InspectionSelectEvent>().listen((event) {
if (mounted) {
setState(() {
if (event.selectMode) {
_selectWidth = 60.0;
_opacity = 1.0;
} else {
_selectWidth = 0.0;
_opacity = 0.0;
}
});
}
});
}
#override
Widget build(BuildContext context) {
AppLocalizations loc = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.fromLTRB(16, 2, 16, 8),
child: SizedBox(
height: 80,
child: PhysicalModel(
color: Colors.white,
elevation: 8,
child: Row(
children: [
_buildSelectBox(),
_buildImage(),
Padding(
padding: const EdgeInsets.only(left: 8),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(getStatusName(widget.inspection.status, loc),
style: TextStyle(color: colorMedium)),
Text(getTypeName(widget.inspection.type, loc),
style: const TextStyle(fontWeight: FontWeight.bold)),
Text(formatDateTime(widget.inspection.created)),
Text(widget.inspection.description)
],
),
)
],
),
),
),
);
}
Widget _buildSelectBox() {
return AnimatedContainer(
duration: _duration,
width: _selectWidth,
child: AnimatedOpacity(
opacity: _opacity,
duration: _duration,
child: Checkbox(
value: _selected,
onChanged: (_) {
setState(() {
_selected = !_selected;
});
widget.selected(widget.inspection, _selected);
}),
),
);
}
Widget _buildImage() {
if (widget.inspection.imageName.isNotEmpty) {
if (_imageFile != null) {
return SizedBox(
width: 80,
height: 80,
child: Padding(
padding: const EdgeInsets.all(4),
child: Image.file(_imageFile!,
width: 72, height: 72, fit: BoxFit.fill)),
);
} else {
return FutureBuilder<void>(
future: _loadImage(widget.inspection.imageName),
builder: (context, snapshot) {
return SizedBox(
width: 80,
height: 80,
child: Padding(
padding: const EdgeInsets.all(4),
child: snapshot.connectionState == ConnectionState.done
? Image.file(_imageFile!,
width: 72, height: 72, fit: BoxFit.fill)
: Container(),
),
);
});
}
}
return const SizedBox(
width: 80,
height: 80,
child: Padding(
padding: EdgeInsets.all(4),
child: Icon(Icons.no_photography, size: 72),
));
}
Future<void> _loadImage(String name) async {
Directory appDocumentsDirectory = await getApplicationDocumentsDirectory();
String appDocumentsPath = appDocumentsDirectory.path;
String filePath = '$appDocumentsPath/images/$name';
_imageFile = File(filePath);
}
}
But the animation is not happening?
Also, I am using a event bus to start the animation for every item, is there a better way for this? A way to signal every item to start the animation?
You can achieve this in multiple ways. Here's one simple solution using AnimatedCrossFade.
For time being, I did it for just one item (other items won't show an unchecked checkbox, but you could do it easily with any state management solution of your choice).
Basic Example
class AnimationDemo extends StatefulWidget {
#override
AnimationDemoState createState() => AnimationDemoState();
}
class AnimationDemoState extends State<AnimationDemo> {
bool isAllSelected = false;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, index) {
return Item(
(index + 1).toString(),
isAllSelected: isAllSelected,
onLongPress: () {
setState(() {
isAllSelected = !isAllSelected;
});
},
);
},
itemCount: 20,
);
}
}
class Item extends StatefulWidget {
final String title;
final bool isAllSelected;
final VoidCallback onLongPress;
const Item(this.title,
{required this.isAllSelected, required this.onLongPress});
#override
ItemState createState() => ItemState();
}
class ItemState extends State<Item> with TickerProviderStateMixin {
bool isSelected = false;
//todo: instead of this, use a global variable with any state management solution to show unchecked boxes for others
#override
Widget build(BuildContext context) {
return ListTile(
onLongPress: () {
setState(() {
isSelected = !isSelected;
});
},
title: Text(widget.title),
minLeadingWidth: 0,
leading: AnimatedCrossFade(
alignment: Alignment.bottomLeft,
reverseDuration: const Duration(milliseconds: 200),
secondCurve: Curves.linearToEaseOut,
duration: const Duration(milliseconds: 200),
firstChild: Checkbox(value: true, onChanged: (_) {}),
secondChild: const SizedBox(),
crossFadeState:
isSelected ? CrossFadeState.showFirst : CrossFadeState.showSecond,
),
);
}
}
Output
I am making a quiz app and at first everything works fine, but when I do a quiz the first time, it does the correct or incorrect answer check perfectly.
But when I go back to quiz without restarting the app just navigating from one page to another the PageView does not reset its state again.
Before taking the quiz
enter image description here
After I do the quiz and I want to do it again without restart the app, I get the checked answers.
enter image description here
How to return the PageView to its initial state without restart the app
Here is my code:
import 'package:flutter/material.dart';
import 'package:quizapp/src/models/quiz_model.dart';
import 'package:quizapp/src/screens/result_screen.dart';
class QuizScreen extends StatefulWidget {
const QuizScreen({Key? key}) : super(key: key);
#override
State<QuizScreen> createState() => _QuizScreenState();
}
class _QuizScreenState extends State<QuizScreen> {
int _questionNumber = 1;
late PageController _controller;
int _score = 0;
#override
void initState() {
_controller = PageController(initialPage: 0);
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: PageView.builder(
physics: const NeverScrollableScrollPhysics(),
controller: _controller,
itemCount: questions.length,
itemBuilder: (context, index) {
final _question = questions[index];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 16,
),
Text(
_question.text,
style: const TextStyle(fontSize: 22),
),
const SizedBox(
height: 16,
),
Expanded(
child: SingleChildScrollView(
child: Column(
children: _question.options
.map((option) => GestureDetector(
onTap: () {
Future.delayed(
const Duration(milliseconds: 250),
() {
if (_questionNumber <
questions.length) {
_controller.nextPage(
duration: const Duration(
milliseconds: 250),
curve: Curves.easeInExpo);
setState(() {
if (option.isCorrect == true) {
_score++;
}
});
setState(() {
_questionNumber++;
// _isLocked = false;
});
} else {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) =>
ResultScreen(
score: _score),
));
}
});
if (_question.isLocked) {
return;
} else {
setState(() {
_question.isLocked = true;
_question.selectedOption = option;
});
}
},
child: Container(
height: 50,
padding: const EdgeInsets.all(12),
margin: const EdgeInsets.symmetric(
vertical: 8),
decoration: BoxDecoration(
color: const Color(0xFF6949FD),
borderRadius:
BorderRadius.circular(16),
border: Border.all(
color: getColorForOption(
option, _question))),
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
option.text,
style: const TextStyle(
fontSize: 18,
color: Colors.white),
),
const SizedBox(width: 10),
getIconForOption(option, _question)
],
),
),
))
.toList(),
)))
]);
},
)),
),
],
),
));
}
Color getColorForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect ? Colors.green : Colors.red;
} else if (option.isCorrect) {
return Colors.green;
}
}
return const Color(0xFF6949FD);
}
Widget getIconForOption(Option option, Question _question) {
final isSelected = option == _question.selectedOption;
if (_question.isLocked) {
if (isSelected) {
return option.isCorrect
? const Icon(Icons.check_circle, color: Colors.green)
: const Icon(Icons.cancel, color: Colors.red);
} else if (option.isCorrect) {
return const Icon(Icons.check_circle, color: Colors.green);
}
}
return const SizedBox.shrink();
}
}
An easier way is to restart the app when you go back or press a button. You can wrap Scaffold() with WillPopScope() to restart when you back. You can use this package to restart.
If you need to save the score, you can save it in local storage. Another easy package for this is get_storage.
dependencies:
flutter_phoenix: ^1.1.0
runApp(Phoenix(child: const MyApp()));
WillPopScope(
onWillPop: () async {
Phoenix.rebirth(context);
},
child: Scaffold())
I uses Listview.builder. it detect scroll end to many time that is why API call to many times and add duplicate Data in Listview.
Code:-
ListView.builder(
controller: _scrollController
..addListener(() async {
if (_scrollController
.position.pixels -
10 ==
_scrollController.position
.maxScrollExtent -
10 &&
!state.isPaginationLoading) {
print("Scroll End TEst Screen");
await ctx
.read<ProfileCubit>()
.getProfiles(
context, true, null);
}
Dont put logic code inside build. In your case _scrollController will addListener every times widget build called, cause multiple handle will trigger.
Advice for you is create and put handle logic to a function, put addListener/removeListener in initState/dispose because they was called only once.
With your problem, you can create a variale to check api was called yet and prevent other call.
class AppState extends State<App> {
var scroll = ScrollController();
var preventCall = false;
#override
initState() {
scroll.addListener(onScroll);
super.initState();
}
#override
void dispose() {
scroll.removeListener(onScroll);
super.dispose();
}
Future yourFuture() async {}
void onScroll() {
var position = scroll.position.pixels;
if (position >= scroll.position.maxScrollExtent - 10) {
if (!preventCall) {
yourFuture().then((_) => preventCall = false);
preventCall = true;
}
}
}
#override
Widget build(BuildContext context) {
return ...
}
}
You can add a condition to check if API call is happening or not and based on it you can you can call the API. You would also need to handle pagination logic if all info is loaded.
you can always check if you reached at the limit of max Limit of your scroll controller then you can call API
condition is like
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
you may be like full example
import 'dart:convert';
import 'dart:developer';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/http.dart' as http;
class Paggination2HomeScreen extends StatefulWidget {
const Paggination2HomeScreen({Key? key}) : super(key: key);
#override
State<Paggination2HomeScreen> createState() => _Paggination2HomeScreenState();
}
class _Paggination2HomeScreenState extends State<Paggination2HomeScreen> {
final String _baseUrl = "https://jsonplaceholder.typicode.com/photos";
int _page = 1;
final int _limit = 10;
bool _hasNextPage = true;
bool _isFirstLoadRunning = false;
bool _isLoadMoreRunning = false;
List _posts = [];
void _firstLoad() async {
setState(() {
_isFirstLoadRunning = true;
});
try {
final res = await http.get(
Uri.parse("$_baseUrl?_page=$_page&_limit=$_limit"),
);
if (res.statusCode == 200) {
setState(() {
_posts = jsonDecode(res.body);
});
log('receivedData : ${jsonDecode(res.body)}');
}
} catch (e) {
if (kDebugMode) {
log('Something went wrong');
}
}
setState(() {
_isFirstLoadRunning = false;
});
}
void _loadMore() async {
if (_hasNextPage == true &&
_isFirstLoadRunning == false &&
_isLoadMoreRunning == false) {
setState(() {
_isLoadMoreRunning = true;
});
_page++;
try {
final res = await http.get(
Uri.parse('$_baseUrl?_page=$_page&_limit=$_limit'),
);
log('url : $_baseUrl?_page=$_page&_limit=$_limit');
if (res.statusCode == 200) {
final List fetchedPost = jsonDecode(res.body);
if (fetchedPost.isNotEmpty) {
setState(() {
_posts.addAll(fetchedPost);
});
} else {
setState(() {
_hasNextPage = false;
});
}
}
} catch (e) {
if (kDebugMode) {
log('something went wrong');
}
}
setState(() {
_isLoadMoreRunning = false;
});
}
}
// the controller for listyView
late ScrollController _controller;
#override
void initState() {
super.initState();
_firstLoad();
_controller = ScrollController();
}
#override
void dispose() {
super.dispose();
_controller.removeListener(_loadMore);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.orangeAccent,
elevation: 0.0,
centerTitle: true,
title: Text(
'Paggination',
style: TextStyle(
color: Colors.black.withOpacity(0.52),
fontSize: 20,
),
),
systemOverlayStyle: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
),
),
body: _isFirstLoadRunning
? Center(
child: CircularProgressIndicator(
color: Colors.black.withOpacity(0.25),
),
)
: Column(
children: [
Expanded(
child: ListView.builder(
controller: _controller
..addListener(() async {
if (_controller.position.pixels >
_controller.position.maxScrollExtent) {
_loadMore();
}
}),
physics: const BouncingScrollPhysics(),
itemCount: _posts.length,
itemBuilder: (context, index) => Container(
padding: const EdgeInsets.all(10),
margin: const EdgeInsets.symmetric(
horizontal: 10,
vertical: 5,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.065),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: Colors.black.withOpacity(0.2),
width: 0.5,
),
),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'ID : ${_posts.elementAt(index)['id']}',
),
Text(
'AlbumID : ${_posts.elementAt(index)['albumId']}',
),
Text(
'Title : ${_posts.elementAt(index)['title']}',
textAlign: TextAlign.justify,
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['url'],
),
),
SizedBox(
height: MediaQuery.of(context).size.width * 0.4,
width: MediaQuery.of(context).size.width * 0.4,
child: Image.network(
_posts.elementAt(index)['thumbnailUrl'],
),
),
],
),
],
),
),
),
),
// when the _loadMore running
if (_isLoadMoreRunning == true)
Container(
color: Colors.transparent,
padding: const EdgeInsets.only(top: 10, bottom: 20),
child: const Center(
child: CircularProgressIndicator(
color: Colors.amberAccent,
strokeWidth: 3,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation<Color>(Colors.blue),
),
),
),
if (_hasNextPage == false)
SafeArea(
child: Container(
width: double.maxFinite,
padding: const EdgeInsets.only(top: 20, bottom: 20),
color: Colors.orangeAccent,
child: const Text('you get all'),
),
)
],
),
);
}
}
I have a Container A wrapped inside GestureDetector, which tap down to change background color to yellow, tap up to restore color to red. When the Container A is tapped, the color will be changed like this (red -> yellow -> red). But if this container A is wrapped inside a PageView with another empty Container B, tapping the Container A will only show the tap up color(red), the tap down color(yellow) will only be shown if I onLongPress the Container A, which is not expected.
And I tried to remove Container B from the PageView, then the color changing of Container A will be shown correctly(red -> yellow -> red).
Please help me to figure out, thanks. Here is the sample code.
class PageViewDemo extends StatefulWidget {
#override
_PageViewDemoState createState() => _PageViewDemoState();
}
class _PageViewDemoState extends State<PageViewDemo>
with SingleTickerProviderStateMixin {
PageController _pageController;
TabController _tabController;
static var pageOneColor;
static var isTapped;
#override
void initState() {
super.initState();
pageOneColor = Colors.red;
isTapped = false;
_pageController = PageController();
_tabController = TabController(vsync: this, length: 2);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(
padding: const EdgeInsets.only(
top: 40.0,
),
child: TabPageSelector(
controller: _tabController,
color: Colors.transparent,
selectedColor: Colors.blue,
),
),
Container(
width: MediaQuery.of(context).size.width,
height: 400,
child: PageView(
controller: _pageController,
scrollDirection: Axis.horizontal,
children: <Widget>[
Container(
color: Colors.greenAccent,
child: Container(
padding: EdgeInsets.all(50),
decoration: BoxDecoration(
border: Border.all(
width: 3,
color: Colors.black,
),
),
height: 100,
width: 100,
child: RawGestureDetector(
gestures: <Type, GestureRecognizerFactory>{
CustomButtonGestureRecognizer:
GestureRecognizerFactoryWithHandlers<
CustomButtonGestureRecognizer>(
() => CustomButtonGestureRecognizer(
onTapDown: _onTapDown,
onTapUp: _onTapUp,
onTapCancel: _onTapCancel,
),
(CustomButtonGestureRecognizer instance) {},
),
},
child: Container(
color: pageOneColor,
child: Center(
child: Text('Container A'),
),
),
),
// child: GestureDetector(
// onTapDown: (detail) {
// /// TapDown Color isn't shown if 'Container B' is added to PageView
// print(detail);
// setState(() {
// isTapped = true;
// pageOneColor = Colors.yellow;
// });
// },
// onTapUp: (detail) {
// print(detail);
// setState(() {
// isTapped = false;
// pageOneColor = Colors.red;
// });
// },
// child: Container(
// color: pageOneColor,
// child: Center(
// child: Text('Container A'),
// ),
// ),
// ),
),
),
Container(
/// remove Container B will see the correct color changing of Container A
color: Colors.grey,
child: Center(
child: Text('Container B'),
),
),
],
onPageChanged: (int pageId) {
setState(() {
debugPrint('pageId:$pageId');
_tabController.index = pageId;
});
},
),
),
],
),
);
}
_onTapDown(TapDownDetails details) {
print(details);
setState(() {
isTapped = true;
pageOneColor = Colors.yellow;
});
}
_onTapUp(TapUpDetails details) {
print(details);
setState(() {
isTapped = false;
pageOneColor = Colors.red;
});
}
_onTapCancel() {
print('tap cancelled');
setState(() {
isTapped = false;
pageOneColor = Colors.red;
});
}
}
class CustomButtonGestureRecognizer extends OneSequenceGestureRecognizer {
final Function onTapDown;
final Function onTapUp;
final Function onTapCancel;
CustomButtonGestureRecognizer(
{#required this.onTapDown,
#required this.onTapUp,
#required this.onTapCancel});
#override
void addPointer(PointerEvent event) {
if (event is PointerDownEvent) {
onTapDown(TapDownDetails());
startTrackingPointer(event.pointer);
resolve(GestureDisposition.accepted);
} else {
stopTrackingPointer(event.pointer);
}
}
#override
void handleEvent(PointerEvent event) {
if (event is PointerDownEvent) {
print('tap down detected');
onTapDown(TapDownDetails());
}
if (event is PointerUpEvent) {
print('tap up detected');
onTapUp(TapUpDetails());
stopTrackingPointer(event.pointer);
}
if (event is PointerCancelEvent) {
print('tap cancel detected');
onTapCancel();
stopTrackingPointer(event.pointer);
}
}
#override
String get debugDescription => 'customButtonTap';
#override
void didStopTrackingLastPointer(int pointer) {}
}
The issue here seems that you're trying to use multiple GestureDetector and it interferes with each other. What you can do here is implement a custom GestureRecognizer in a RawGestureDetector. You can follow this guide fore more details.