Flutter - PageView animatedToPage will load middle page. How to avoid it? - flutter

I have a PageView, I want scroll pageView programmatically, so I have two choices:
use animateToPage
use jumpToPage
now, I need smooth transition effect, so I have to use first api. But, I have a problem: animateToPage will load middle pages which don't need to be shown at this time when I scroll from the first page to the last page.(jumpToPage don't have this problem, but I need animation).
How to avoid it?

We can achieve that by
Swap lastPage Widget to next position of current page
Animate to next page
Jump to real lastPage index
Refresh swapped Index to its previous value
In this example, I used fixed PageView children count, which is 8.
Demo
Comparison
Combine to 8th page Button
as CopsOnRoad suggested, this button will trigger Scroll animation to last page (in this case 8th page). Firstly, we
jumpToPage(6), and then animateToPage(7, ..).
This method works, but adversely, user will notice sudden change of current page to 7th page.
Flash Jump to 8th page Button
Unlike like first method, this button will avoid displaying 7th page unnecessarily
Syntax Explanation
this is the main function
void flashToEight() async {
int pageCurrent = pageController.page.round();
int pageTarget = 7;
if (pageCurrent == pageTarget){
return;
}
swapChildren(pageCurrent, pageTarget); // Step # 1
await quickJump(pageCurrent, pageTarget); // Step # 2 and # 3
WidgetsBinding.instance.addPostFrameCallback(refreshChildren); // Step # 4
}
detailed look
// Step # 1
void swapChildren(int pageCurrent, int pageTarget) {
List<Widget> newVisiblePageViews = [];
newVisiblePageViews.addAll(pageViews);
if (pageTarget > pageCurrent) {
newVisiblePageViews[pageCurrent + 1] = visiblePageViews[pageTarget];
} else if (pageTarget < pageCurrent) {
newVisiblePageViews[pageCurrent - 1] = visiblePageViews[pageTarget];
}
setState(() {
visiblePageViews = newVisiblePageViews;
});
}
// Step # 2 and # 3
Future quickJump(int pageCurrent, int pageTarget) async {
int quickJumpTarget;
if (pageTarget > pageCurrent) {
quickJumpTarget = pageCurrent + 1;
} else if (pageTarget < pageCurrent) {
quickJumpTarget = pageCurrent - 1;
}
await pageController.animateToPage(
quickJumpTarget,
curve: Curves.easeIn,
duration: Duration(seconds: 1),
);
pageController.jumpToPage(pageTarget);
}
// Step # 4
List<Widget> createPageContents() {
return <Widget>[
PageContent(1),
PageContent(2),
PageContent(3),
PageContent(4),
PageContent(5),
PageContent(6),
PageContent(7),
PageContent(8),
];
}
void refreshChildren(Duration duration) {
setState(() {
visiblePageViews = createPageContents();
});
}
Full Working-Example Repository
You may look into full source code and build locally. Github

Related

Is there an automatic function for changing the index of page in PaginatedDataTable?

I've created PaginatedDataTable in flutter as below
Everything works fine but I want to show this in Television and need to change Page by automatic, is there any way to do this?
I found the solution by using PaginatorController as below
void startTimer(PaginatorController paginatorController, int duration) {
Timer.periodic(Duration(seconds: duration), (Timer t) {
if (paginatorController.isAttached) {
// Check if still less than row count
if (paginatorController.currentRowIndex +
paginatorController.rowsPerPage <
paginatorController.rowCount) {
paginatorController.goToNextPage();
} else {
// If last page go back to first page
paginatorController.goToFirstPage();
}
}
});
}

Ionic 3 - How to prevent the user from clicking slide pager from slide 0 to slide 2 if slide 1 is not answered

I am working on a Ionic 3 questionnaire application where the user cannot go from one slide to another until the question is answered on a slide. I got everything working except pager (this dots) is allowing the user to go from one slide to another even though question# 2 is not answered.
I added the following logic but its not working as expected. Its still allowing me to jump from slide 0 to slide 2. Adding this.slideTo(this.currentIndex) changed the dot to be highlighted for slide 0 however its showing the contents of Slide# 2.
onSlideWillChange(event) {
let answerNotSelected: boolean = false;
for (let i: number = 0; i < this.slides.getActiveIndex(); i++) {
answerNotSelected = this.questionnaire.questions[i].selectedAnswer === undefined;
if (answerNotSelected) {
console.log('Returning from newQuestionIndex ' + this.slides.getActiveIndex() + ' to current slide:' + this.currentQuestionIndex);
this.slideTo(this.currentQuestionIndex);
}
} }
You could just lock the slides, and only enable back when the question has an answer. That way, the pager won't change the current slide (It would just be there to give the user some feedback about how many other questions are there in the questionnaire):
import { Component, ViewChild } from '#angular/core';
import { NavController, Content, Slides } from 'ionic-angular';
#Component({
selector: 'page-home',
templateUrl: 'app/home.page.html'
})
export class HomePage {
#ViewChild(Slides) slides: Slides;
ngOnInit() {
this.slides.lockSwipes(true); // Lock the slides
}
public showPrevious(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slidePrev(500); // Move the the previous
this.slides.lockSwipes(true); // Lock the slides again
}
public showNext(): void {
this.slides.lockSwipes(false); // Unlock the slides
this.slides.slideNext(500); // Move the the next
this.slides.lockSwipes(true); // Lock the slides again
}
}
Please take a look at this working plunker for a demo.
I had the same problem - for anyone in the future I solved it in a albeit slightly hacky way (since _paginationContainer is an internal variable). You could probably also as easily just find elements in the DOM.
I called this on
ionSlideWillChange
, but it could probably be called on any of the swipe events.
ion-slide is very poorly documented, but it is based off of http://idangero.us/swiper/api/ and has all the same methods so you can look here for better documentation.
lockAll() {
var current = this.slider.getActiveIndex();
let array = this.slider._paginationContainer.childNodes
for (let index = 0; index < array.length; index++) {
var button : any = array[index];
if (this.inRange(index, current -1, current +1)) {
button.disabled = false;
}
else {
button.disabled = true
}
}
}
inRange(x, min, max) {
return ((x-min)*(x-max) <= 0);
}

RxJs Observable with infinite scroll OR how to combine Observables

I have a table which uses infinite scroll to load more results and append them, when the user reaches the bottom of the page.
At the moment I have the following code:
var currentPage = 0;
var tableContent = Rx.Observable.empty();
function getHTTPDataPageObservable(pageNumber) {
return Rx.Observable.fromPromise($http(...));
}
function init() {
reset();
}
function reset() {
currentPage = 0;
tableContent = Rx.Observable.empty();
appendNextPage();
}
function appendNextPage() {
if(currentPage == 0) {
tableContent = getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; });
} else {
tableContent = tableContent.combineLatest(
getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; }),
function(o1, o2) {
return o1.concat(o2);
}
)
}
}
There's one major problem:
Everytime appendNextPage is called, I get a completely new Observable which then triggers all prior HTTP calls again and again.
A minor problem is, that this code is ugly and it looks like it's too much for such a simple use case.
Questions:
How to solve this problem in a nice way?
Is is possible to combine those Observables in a different way, without triggering the whole stack again and again?
You didn't include it but I'll assume that you have some way of detecting when the user reaches the bottom of the page. An event that you can use to trigger new loads. For the sake of this answer I'll say that you have defined it somewhere as:
const nextPage = fromEvent(page, 'nextpage');
What you really want to be doing is trying to map this to a stream of one directional flow rather than sort of using the stream as a mutable object. Thus:
const pageStream = nextPage.pipe(
//Always trigger the first page to load
startWith(0),
//Load these pages asynchronously, but keep them in order
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
//One option of how to join the pages together
scan((pages, p) => ([...pages, p]), [])
)
;
If you need reset functionality I would suggest that you also consider wrapping that whole stream to trigger the reset.
resetPages.pipe(
// Used for the "first" reset when the page first loads
startWith(0),
//Anytime there is a reset, restart the internal stream.
switchMapTo(
nextPage.pipe(
startWith(0),
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
scan((pages, p) => ([...pages, p]), [])
)
).subscribe(x => /*Render page content*/);
As you can see, by refactoring to nest the logic into streams we can remove the global state that was floating around before
You can use Subject and separate the problem you are solving into 2 observables. One is for scrolling events , and the other is for retrieving data. For example:
let scrollingSubject = new Rx.Subject();
let dataSubject = new Rx.Subject();
//store the data that has been received back from server to check if a page has been
// received previously
let dataList = [];
scrollingSubject.subscribe(function(page) {
dataSubject.onNext({
pageNumber: page,
pageData: [page + 10] // the data from the server
});
});
dataSubject.subscribe(function(data) {
console.log('Received data for page ' + data.pageNumber);
dataList.push(data);
});
//scroll to page 1
scrollingSubject.onNext(1);
//scroll to page 2
scrollingSubject.onNext(2);
//scroll to page 3
scrollingSubject.onNext(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>

Same Button in different line robotium

I'm writing robotium scripts for my tablet application. In line number 1 and 2 (on clicking line number 1 the next listview will appear with line number 2), there were same button "Go next" with same id. How do i click the "Go next" button in line number 2.
Thanks for the help.,
Dhivya.
Methods which should help you are as follows:
getView(int id, int index) // returns View
getView(String id, int index) // returns View
clickOnView(View view)
clickOnText(String text, int match) // match seems to be an index in this case
Try this:
int count = 1;
ArrayList<Button> bottoni = solo.getCurrentViews(Button.class);
for (Button button : bottoni) {
if(button.getId() == solo.getView("com.xxx.yyyyyyyy:id/idButton").getId())
{
if(count==2){
solo.clickOnView(button);
break;
}
count++;
}
}

ProgressMessageBox Is Not Updating

In GXT (from Sencha) I am attempting to use a ProgressMessageBox to show the user that work is being done, but I am not seeing the message box until after all of the work is complete. Here is the code:
final ProgressMessageBox messageBox = new ProgressMessageBox("Task Description",
"Executing Task...");
messageBox.setProgressText("Calculating...");
messageBox.setPredefinedButtons();
messageBox.show();
for (int i = 0; i < 5; ++i) {
for (long l = 0l; l < 10000000000l; ++l) {
if (l == 12345l) {
MyUtil.info(60, "" + System.currentTimeMillis() / 1000);
}
}
messageBox.updateProgress((double)(i + 1) / 5, "{0}% Complete");
}
This code is in the SelectionHandler of a menu item. Obviously I am not actually just looping a few billion times in the "real" code, but when I perform the work that I want to execute I get the same result ... the message box is shown after all the work has been completed. I see the "info" messages (MyUtil#info is simply a wrapper around the GXT Info capability, which causes an info message to be displayed for the specified number of seconds) ... and on my machine, running the code shown, each message has a value that is about seven seconds greater than the previous message (but they all show up at the same time as the message box).
Is there something that I need to do after calling ProgressMessageBox#updateProgress to force the screen or message box to refresh?
I was able to get this to work by putting the task into a Scheduler#execute method. The example in the Sencha Explorer Demo uses a Timer, but since I don't know how long the execution will take I chose to use the Scheduler approach. Here is the relevant final code:
...
menuItem.addSelectionHandler(new SelectionHandler<Item>() {
#Override
public void onSelection(final SelectionEvent<Item> selectionEvent) {
final ProgressMessageBox messageBox = new ProgressMessageBox("Size All Columns", //
"Resizing Columns...");
messageBox.setProgressText("Calculating...");
// messageBox.setPredefinedButtons();
messageBox.show();
resizeNextColumn(messageBox,
_selectionModel instanceof CheckBoxSelectionModel ? 1 : 0,
_grid.getColumnModel().getColumnCount() - 1);
}
});
...
private void resizeNextColumn(final ProgressMessageBox messageBox,
final int columnIndex,
final int lastColumnIndex) {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
#Override
public void execute() {
resizeColumnToFit(columnIndex);
if (columnIndex == lastColumnIndex) {
messageBox.hide();
_grid.getView().refresh(true);
return;
}
messageBox.updateProgress((double)columnIndex / (lastColumnIndex + 1),
"{0}% Complete");
resizeNextColumn(messageBox, columnIndex + 1, lastColumnIndex);
}
});
}
One thing that didn't appear to work is the messageBox.setPredefinedButtons() call ... when I use this the progress bar in the message box doesn't update at all. I really didn't want an "OK" button on the dialog, but for now it'll have to do. I'm using 3.1.0 of GXT.