In Flutter I canĀ“t assing the same PageController to many PageView. So in need to use two or more PageControllers.
I need to synchronize my ViewPages so that when I slide one another it also slides
How can I sync two or more PageController or PageView?
What I want is every PageView can control others PageView make them all sync no matter which slide.
So if a have A, B and C PageView and I slide A then B and C slides as well... if I slide B then A and C slides... and so on.
You can reference https://github.com/braulio94/menu_flutter
The trick is use NotificationListener to listen ScrollUpdateNotification
and compare two page
_backgroundPageController.page != _pageController.page
code snippet at line 183
new NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (notification.depth == 0 &&
notification is ScrollUpdateNotification) {
selectedIndex.value = _pageController.page;
if (_backgroundPageController.page != _pageController.page) {
_backgroundPageController.position
// ignore: deprecated_member_use
.jumpToWithoutSettling(_pageController.position.pixels /
_kViewportFraction);
}
setState(() {});
}
return false;
}
full code
https://github.com/braulio94/menu_flutter/blob/master/lib/screens/pager.dart
To sync two pageviews (in case you want to use different viewfractions for example)
create two controllers, upper with view fraction 0,7 and bottom with 1.0
Now to sync add listener to upper and get current scroll offset value.
#override
void initState() {
super.initState();
_controller_upper = new PageController(viewportFraction: 0.7);
_controller_bottom = new PageController();
_controller.addListener(_onScroll);
}
void _onScroll() {
scrollValue = _controller.page;
//page returns value between 0 to 1 for 1st page, 1 to 2 for second and s on, need to multiply it by screen width
_controller_bottom.jumpTo(scrollValue*MediaQuery.of(context).size.width);
}
There is another solution. When you create controllers you can add listeners for each of. In every listener you should describe logic for another controller.
To be more clear I will describe in more detail. If necessary, you can combine the logic into one method, but it will be less clear.
int _previousPage = 0;
bool _isController1 = false;
bool _isController2 = false;
void resetMoveInfo(){
_isController1 = false;
_isController2 = false;
}
void _onController1Scroll() {
if (_isController2)
return;
_isController1 = true;
if (_controller1.page.toInt() == _controller1.page) {
_previousPage = _controller1.page.toInt();
resetMoveInfo();
}
_controller2.position
// ignore: deprecated_member_use
.jumpToWithoutSettling(_controller1.position.pixels * _viewPortFraction);
}
void _onController2Scroll() {
if (_isController1)
return;
_isController2 = true;
if (_controller2.page.toInt() == _controller2.page) {
_previousPage = _controller2.page.toInt();
resetMoveInfo();
}
_controller1.position
// ignore: deprecated_member_use
.jumpToWithoutSettling(_controller2.position.pixels / _viewPortFraction);
}
When you initialize your controller you should add listener for each of them
_controller1 = PageController(
initialPage: _previousPage,
keepPage: false,
viewportFraction: 1)
..addListener(_onController1Scroll);
Related
I have SpriteComponent named Obstacle. I set the priority value to 0 in the super method. But the Obstacle component is regenerated with the update method. It disappears when it exits the screen. When it reoccurs, the priorty value becomes invalid. I am sharing sample screenshots.
first img
second img
The player stays behind the obstacle. GameOverPanel always at the back.
#override
Future<void> onLoad() async {
add(ScreenHitbox());
add(gameRef.homeBackgroundParallaxComponent);
add(gameRef.homeBaseParallax);
add(gameRef.obstacleManager..priority = 0);
add(gameRef.playerManager..priority = 1);
add(gameRef.gameOverPanel..priority = 2);
}
#override
void update(double dt) {
super.update(dt);
gameRef.obstacleManager.updateObstacle(GameConst.gameSpeed * dt);
gameRef.playerManager.changePositionDown(value: GameConst.gravity * dt);
}
Game Over Panel
class GameOverPanel extends Component with HasGameRef<HopyBirdGame> {
GameOverPanel() : super(priority: 4);
#override
Future<void> onLoad() async {
add(GameOverText());
}
#override
void renderTree(Canvas canvas) {
if (gameRef.gameOver) {
super.renderTree(canvas);
}
}
}
class GameOverText extends SpriteComponent with HasGameRef<HopyBirdGame> {
GameOverText() : super(size: Vector2(300, 80), anchor: Anchor.center);
#override
Future<void>? onLoad() async {
sprite = await gameRef.loadSprite(Assets.gameOver.path);
position = Vector2(gameRef.screenWidth / 2, gameRef.screenHeight / 2);
return super.onLoad();
}
}
-Obstacle
class Obstacle extends SpriteComponent
with HasGameRef<HopyBirdGame>, CollisionCallbacks {
final ObstacleType obstacleType;
Obstacle({
required this.obstacleType,
super.position,
super.priority,
}) : super(
size: Vector2(GameConst.obstacleWidth, GameConst.obstacleHeight),
anchor: Anchor.center,
);
Obstacle.empty({this.obstacleType = ObstacleType.empty});
#override
Future<void> onLoad() async {
switch (obstacleType) {
case ObstacleType.up:
sprite = await gameRef.loadSprite(
Assets.redPipeUp.path,
);
break;
case ObstacleType.down:
sprite = await gameRef.loadSprite(
Assets.redPipeDown.path,
);
break;
case ObstacleType.empty:
return;
}
add(RectangleHitbox());
}
}
class Obstacles extends Component with HasGameRef<HopyBirdGame> {
final Obstacle upObstacle;
final Obstacle downObstacle;
bool isCreateNextObstacles = true;
Obstacles({
required this.upObstacle,
required this.downObstacle,
super.priority,
});
#override
Future<void>? onLoad() {
upObstacle.priority = 1;
downObstacle.priority = 1;
createChildren();
return super.onLoad();
}
void createChildren() {
final children = [upObstacle, downObstacle];
addAll(children);
}
}
Obstacle Manager
class ObstacleManager extends Component with HasGameRef<HopyBirdGame> {
ObstacleManager() : super(priority: 1);
ListQueue<Obstacles> history = ListQueue();
#override
Future<void> onLoad() async {
createObstacles();
}
void updateObstacle(double value) {
if (!gameRef.gameOver) {
if (history.isNotEmpty) {
history.last.upObstacle.position.x -= value;
history.last.downObstacle.position.x -= value;
if (history.last.upObstacle.position.x <= gameRef.screenWidth / 2) {
createObstacles();
final isThereAnyObstaclesOutsideScreen = history.any(
(element) =>
element.downObstacle.position.x <=
-(GameConst.obstacleWidth / 2),
);
if (isThereAnyObstaclesOutsideScreen) {
final obstaclesOutsideScreen = history
.lastWhere((element) => element.downObstacle.position.x <= 0);
obstaclesOutsideScreen.removeFromParent();
gameRef.remove(obstaclesOutsideScreen);
}
}
if (history.length >= 2) {
history.elementAt(history.length - 2).upObstacle.position.x -= value;
history.elementAt(history.length - 2).downObstacle.position.x -=
value;
}
}
}
}
void createObstacles() {
final obstaclesGap =
gameRef.screenHeight - gameRef.difficulty.spaceForPlayers;
final gapAboveTheObstacleOnScreen =
((2 * GameConst.obstacleHeight) - obstaclesGap) / 2;
final randomNumber = _randomGenerator(
min: -gameRef.difficulty.distanceAxisYToGoUpAndDown,
max: gameRef.difficulty.distanceAxisYToGoUpAndDown,
);
final axisYOfUpObstacle = (GameConst.obstacleHeight / 2) -
gapAboveTheObstacleOnScreen +
randomNumber;
final axisYOfDownObstacle = axisYOfUpObstacle +
GameConst.obstacleHeight +
gameRef.difficulty.spaceForPlayers;
final obstacles = Obstacles(
upObstacle: Obstacle(
obstacleType: ObstacleType.up,
position: Vector2(
gameRef.screenWidth + (GameConst.obstacleWidth / 2),
axisYOfUpObstacle,
),
),
downObstacle: Obstacle(
obstacleType: ObstacleType.down,
position: Vector2(
gameRef.screenWidth + (GameConst.obstacleWidth / 2),
axisYOfDownObstacle,
),
),
);
history.add(obstacles);
gameRef.add(obstacles);
}
double _randomGenerator({required double min, required double max}) {
final gap = (max - min).toInt();
return min + Random().nextInt(gap);
}
}
EDIT: The same problem applies in game over panel.
Game Over Panel
class GameOverPanel extends Component with HasGameRef<HopyBirdGame> {
GameOverPanel() : super();
#override
Future<void> onLoad() async {
add(GameOverText());
}
#override
void renderTree(Canvas canvas) {
if (gameRef.gameOver) {
super.renderTree(canvas);
}
}
}
class GameOverText extends SpriteComponent with HasGameRef<HopyBirdGame> {
GameOverText()
: super(
size: Vector2(300, 80),
anchor: Anchor.center,
);
#override
Future<void>? onLoad() async {
sprite = await gameRef.loadSprite(Assets.gameOver.path);
position = Vector2(gameRef.screenWidth / 2, gameRef.screenHeight / 2);
return super.onLoad();
}
}
Similarly, the first assignment is made in the Flame Game class.
I'm guessing (since the code for the ObstacleManager isn't present) that you are adding the obstacles directly to the game from the ObstacleManager to the component tree and that you aren't adding the priority to the actual Obstacle components.
If this is not the case, please update the question with the code for the ObstacleManager.
EDIT: After code upload.
It doesn't help that you are giving priorities to the manager classes when the components are not added as children to them, but to the gameRef. Since the player manager class isn't included in the question you either have to set the Obstacles to priority -1, or you have to give the player priority 1 (and obstacles will default to 0).
gameRef.add(obstacles..priority = -1);
This will make the obstacles be behind the background, so you have to also change the background parallaxes:
add(gameRef.homeBackgroundParallaxComponent..priority = -3);
add(gameRef.homeBaseParallax..priority = -2);
So it will be better to just set the player priority to 1 where you are adding that one to the game, because then you don't have to change all the other classes.
From Here: https://api.flutter.dev/flutter/widgets/GestureDetector/onPanEnd.html
there is velocity concept.
I want to remove after drag by some velocity.
How to remove following moving or make velocity to 0?
I want to very not flexible interactive viewer.
EDIT:
STICKY TRANSFORMCONTROLLER CODE:
class CustomSecondTransformationController extends TransformationController {
CustomSecondTransformationController([Matrix4? value])
: super(value ?? Matrix4.identity());
set before(Offset value) {
_before = value;
}
Offset toScene(Offset viewportPoint) {
Offset vp = viewportPoint;
final Matrix4 inverseMatrix = Matrix4.inverted(value);
final Vector3 untransformed = inverseMatrix.transform3(Vector3(
vp.dx,
vp.dy,
0,
));
return Offset(untransformed.x / 3, untransformed.y / 3);
}
I am using a list to store my items and changing the items indexes if swiped left or right but now when I am trying to swipe it does not stop I want the swipe to go from 1->2 or 2->3 or 3->2 but instead it goes 1->4 or 4->1.
This code below is the part of my program where I am swiping and calling the functions (where I increased the sensitivity to 15).
return GestureDetector(
onHorizontalDragUpdate: (dis) {
if (dis.delta.dx > 15) {
prevPage();
}
else if (dis.delta.dx < -15) { //User swiped from right to left
nextPage();
}
},
And these are the functions which it is calling.
void nextPage() {
if(currPage !=4) {
setState(() {
currPage += 1;
});
}
}
void prevPage() {
if(currPage != 1) {
setState(() {
currPage -= 1;
});
}
}
How can I fix this situation?
Thanks a lot.
I am building an application with swipeable cards in a deck.
Swiping works perfectly fine at any speed so long as I am swiping sort of at least 50% of the distance of the screen.
People don't tend to do this however, it is a better user experience to just swipe a very short distance quickly left or right and have the application detect the intent and complete the scroll action.
My code is as follows:
void _onHorizontalDragStart(DragStartDetails details) {
startDrag = details.globalPosition;
startDragPercentScroll = scrollPercent;
}
void _onHorizontalDragUpdate(DragUpdateDetails details) {
final currentDrag = details.globalPosition;
final dragDistance = currentDrag.dx - startDrag.dx;
final singleCardDragPercent = dragDistance / context.size.width;
setState(() {
scrollPercent = (startDragPercentScroll + (-singleCardDragPercent / widget.cards.length)).clamp(0.0, 1.0 - (1 / widget.cards.length));
});
}
void _onHorizontalDragEnd(DragEndDetails details) {
finishScrollStart = scrollPercent;
finishScrollEnd = (scrollPercent * widget.cards.length).round() / widget.cards.length;
finishScrollController.forward(from: 0.0);
setState(() {
startDrag = null;
startDragPercentScroll = null;
});
}
I am certain there is just something wrong with my math there somewhere that is preventing short, quick swipes from working.
What happens instead is the card moves a short distance and then snaps back to the same card.
I have a listview that gets additional views added on request, that's maintained by a BaseAdapter. How can I maintain scroll position after the update?
I know this has been asked a few times, but each time, the same solution has been put forward, which I have tried, which is to call adapter.notifyDataSetChanged(); after updating the ArrayList that contains the list contents.
How can I ensure that scroll position is maintained?
Implement an OnScrollListener in your activity class and then use the following code:
int currentFirstVisibleItem, currentVisibleItemCount, currentTotalItemCount;
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
this.currentFirstVisibleItem = firstVisibleItem;
this.currentVisibleItemCount = visibleItemCount;
this.currentTotalItemCount = totalItemCount;
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.currentScrollState = scrollState;
this.isScrollCompleted();
}
private void isScrollCompleted() {
if (currentFirstVisibleItem + currentVisibleItemCount >= currentTotalItemCount) {
if (this.currentVisibleItemCount > 0
&& this.currentScrollState == SCROLL_STATE_IDLE) {
//Do your work
}
}
}
If you are using AsyncTask for updating your data, then you can include the following in your PostExecute() in order to maintain the Scroll position:
list.setAdapter(adapter);
list.setSelectionFromTop(currentFirstVisibleItem, 0);
I hope this helps.
The approach I take is to call ListView.setSelection(position) to scroll to the desired position after the update.
Depending on where you're calling it from, you might need to call requestFocusFromTouch before calling setSelection in order to ensure the item gets positioned appropriately.
This code will help you out....
// get position of listview
int index = listView.getFirstVisiblePosition();
View v = listView.getChildAt(0);
int top = (v == null) ? 0 : v.getTop();
// notify dataset changed or re-assign adapter here
//re-assign the position of listview after setting the adapter again
listView.setSelectionFromTop(index, top);
Cheers :)
You can try this:
//Get the top position from the first visible element
int fVisible = list.getFirstVisiblePosition();
View vFirst = list.getChildAt(0);
int pos = 0;
if (vFirst != null) pos = vFirst.getTop();
//Restore the position
list.setSelectionFromTop(fVisible, pos);
You can do as below. If you want to load more data with scroll to bottom it will have some change.
int currentFirstVisibleItem, currentScrollState;
int countChange = 0;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
Log.d("Scroll", "Scrolling");
this.currentFirstVisibleItem = firstVisibleItem;
countChange = 0;//during touching you can decide not to refresh by scroll
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.d("Scroll", "Changed");
this.currentScrollState = scrollState;
if (scrollState == SCROLL_STATE_TOUCH_SCROLL) {
countChange = 0;
}
if (++countChange > 1) {
//scroll to top and release touch change=2. Simple scroll and release touch change=1
this.isScrollCompleted();
countChange = 0;
}
}
private void isScrollCompleted() {
Log.d("Load", "Reload");
if (currentFirstVisibleItem == 0) {
//Your loading go here
}
}