I have a ViewPager which i populate with 4 items. I need it to be scrollable until the 3rd item (inclusive) but when the user tries to move to the 4th one it should just not let him.
When he validates the 3 pages, only then i need to allow him to move to the 4th.
Don't ask why, it's complicated, but I can't instantiate and set the 4th page to the adapter only AFTER the first 3 are validated. That would solve my problem, indeed. I need to have it already available in the list.
Any suggestions?
I tried playing around with onPageScrolled() to see if I can stop it from moving forward even a pixel, but I wasn't able to figure it out.
#Override
public int getCount() {
if(!isAccess) {
return 3;
} else {
return 4;
}
}
Related
I'm making an API call getData(forPage: Int): Response which returns a page-worth of data (10 items max) and thereIsMoreData: Boolean.
The recyclerView is implemented that by scrolling, the scroll listener automatically fetches more data using that API call:
val scrollListener = object : MyScrollListener() {
override fun loadMoreItems() {
apiFunctionForLoading(currentPage + 1)
}
}
The problem is that with longer screen devices that have more space for items (let's say 20), the RV receives 10 items and then doesn't allow scrolling, because there's no more items to scroll to. Without scrolling, more data cannot be loaded.
My naive solution:
load first set of data
if thereIsMoreData == true I load another page of data
now I have more data than the screen can display at once hence allowing scroll
Is there a more ellegant solution?
Android has this Paging Library now which is about displaying chunks of data and fetching more when needed. I haven't used it and it looks like it might be a bit of work, but maybe it's worth a look?
Codepath has a tutorial on using it and I think their stuff is pretty good and easy to follow, so maybe check that out too. They also have this older tutorial that's closer to what you're doing (handling it yourself) so there's that too.
I guess in general, you'd want your adapter to return an "infinite" number for getItemCount() (like Integer.MAX_VALUE). And then in your onBindViewHolder(holder, position) method you'd either set the item at position, or if you don't have that item yet you load in the next page until you get it.
That way your initial page will always have the right amount of content, because it will be full of ViewHolders that have asked for data - if there's more than 10, then item 11 will have triggered the API call. But actually handling the callback and all the updating is the tricky part! If you have that working already then great, but it's what the Paging library was built to take care of for you (or at least make it easier!)
An elegant way would be to check whether the view can actually scroll down:
recyclerView.canScrollVertically(1)
1 means downwards -> returns true if it is possible tro scroll down.
So if it returns false, your page is not fully filled yet.
I'm using a ViewPager2 in my Android app to let the user scroll left and right through (currently) 2 pages. It seems fine, except for one little detail.
I'm using setOffscreenPageLimit(2) because I want both pages to be ready for scrolling straight away. However, it doesn't seem to do anything. When I scroll from page 1 to page 2 for the first time, the second page's layout doesn't appear until it's fully selected (i.e. the transition animation is finished). This looks quite jarring, and defeats the point of smooth scrolling with a ViewPager2 at all.
After page 2 loads for the first time, scrolling back and forth works properly with both pages being retained allowing a smooth transition.
Can anyone replicate this? This used to work just fine with ViewPager.
Here's how I set up my ViewPager2:
_viewPager2FragmentHolder = getActivity().findViewById(R.id.viewPager2TargetFragmentHolder);
_viewPager2FragmentHolder.setOffscreenPageLimit(2);
_viewPager2FragmentHolder.setAdapter(new TargetFragmentStateAdapter(this));
...and here's my adapter class:
public class TargetFragmentStateAdapter extends FragmentStateAdapter
{
public TargetFragmentStateAdapter(#NonNull Fragment fragment)
{
super(fragment);
}
#NonNull
#Override
public Fragment createFragment(int position)
{
switch (position)
{
case 0:
return new TargetCoordinatesFragment();
case 1:
return new TargetDirectionsFragment();
default:
return new Fragment();
}
}
#Override
public int getItemCount()
{
return 2;
}
}
From the official documentation: https://developer.android.com/reference/androidx/viewpager2/widget/ViewPager2#setOffscreenPageLimit(int)
Set the number of pages that should be retained to either side of the currently visible page(s). Pages beyond this limit will be recreated from the adapter when needed.
Therefore, you should set it to 1 to get what you want.
If offscreen pages seems to be 'loaded' only when selected, it means either the adapter or the pageSelectionCallback is doing somethiong wrong.
I have a list of questions with answer choices(let us assume 5 questions). Currently, all 5 questions are loaded at once in the RecyclerView, so a student can answer all the questions in the view. I would like to change this such that the first question loads and only after the student has answered and clicks the submit button, will the second load and so on. In other words, submit the answer to question 1, the view clears and shows question 2 and so on. An example of an app that I have seen achieve that is Duolingo. You only see the next question after you have submitted the current one displayed. How do I achieve this?
If you need to show only one question at a time, you don't need to use a RecyclerView at all. Just use text views and reuse them.
If you want to display old questions when a new question is unlocked, you may handle it in the getItemCount() method of your RecyclerView's adapter.
First sort the questions in order and use:
#Override
public int getItemCount() {
return currentQuestionIndex + 1;
}
After that, when the next button is clicked, increment the current question's index and call notifyDataSetChanged() in your RecyclerView.
If you want to display only one question and use RecyclerView anyway, return 1 from the getItemCount method. Then in the onBindViewHolder method instead of using the position parameter to get item from the list, use list.get(currentQuestionIndex);
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
Question question = questionList.get(currentQuestionIndex);
//other code
}
I have an enhanced loop, which will dynamically inflate however many layouts relevant to the number of values held in my array.
This works perfectly however, there is a method being called on each iteration, which also works but there is a big bug that I need help resolving.
Imagine there are 5 items in my array, therefore 5 layouts are inflated, in these layouts there is a little scratchcard type section on the layout.
Now if the user is on page 1, uses the scratchcard, then moves on to page 2, uses the scratchcard etc etc, it works fine.
But if the user is on page 1 and then goes to say, page 5 and then back to page 1 (basically in a random order), the scratchcard doesn't work.
From my understanding, the reason for this is that the method is being called an implemented on each iteration and the view is losing its state if the user scrolls back or scrolls in random orders.
Therefore I need a way to save the created view state in my viewpager.
Is this possible for my scenario? I have tried my best to find a solution, but cannot find something that feels relevant to my question.
Here is a snippet of the code in question. Thanks for any guidance or suggestions!
for (String x : array1) {
//loop out the number of layouts relative to the number of questions held in x
View current_layout = LayoutInflater.from(getActivity()).inflate(R.layout.question_fragment, null);
//use the pageAdapter to add the layout to the users view
pagerAdapter.addView(current_layout);
//call method to add functionality to the scratchcard
isCorrect(current_layout);
}
public void isCorrect(View current_layout) {
ScratchoffController controller1 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view1), current_layout.findViewById(R.id.scratch_view_behind1));
ScratchoffController controller2 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view2), current_layout.findViewById(R.id.scratch_view_behind2));
ScratchoffController controller3 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view3), current_layout.findViewById(R.id.scratch_view_behind3));
ScratchoffController controller4 = new ScratchoffController(getActivity())
.setThresholdPercent(0.40d)
.setTouchRadiusDip(getActivity(), 30)
.setFadeOnClear(true)
.setClearOnThresholdReached(true)
.setCompletionCallback(() -> {
})
.attach(current_layout.findViewById(R.id.scratch_view4), current_layout.findViewById(R.id.scratch_view_behind4));
}
I ussually use ViewPager with Fragments and what you mention has happend to me when I try to keep references to the Fragment instances (in my case) outside of the viewpager.
This happens because the viewpager may create new instances of the Fragment it contains when you re-vist the tab in the way you mention. When this happens, the instance reference you hold outside of the viewpager is not anymore what the viewpager is showing.
In your case , according to this question, you have to oveeride instatiateItem and destroyItem. I think you can use these methods to save state restore state, and also you could update any external reference when instantiateItem is called.
I have a 2 pane layout. On the left is a ListView, on the rights some content linked with the list. The content is scrollable.
If content moves forward, I want the list to go forward only if this item is not yet displayed. This prevents that the list has to update on every content change. I also want the list to go ahead one full page.
Is there something like
listView.isThisElementCurrentlyShown(int nr)
and
listView.moveAheadOnePage(int direction)
?
Thanks in advance.
Here is an example of isThisElementCurrentlyShown(Element e):
public boolean isThisElementCurrentlyShown(Element e){
ListView lv = getListView();
int start = lv.getFirstVisiblePosition();
for(int i=start, j=lv.getLastVisiblePosition();i<=j;i++){
if(e==lv.getItemAtPosition(i)){
return true;
}
}
return false;
}
This will tell you whether the element is visible or not.
For the moveAheadOnePage you should be able to use listview's built in functions getFirstVisiblePosition() and getLastVisiblePosition() to calculate the number of rows visible on that device, then advance the listview ahead that many positions. Something like this pseudocode (you will have to write this one):
public void moveAheadOnePage(int direction){//direction: 0-up, 1-down
int numVisibleRows = getLastVisiblePosition() - getFirstVisiblePosition();
this.setSelected(currentSelection + numVisibleRows) // +/- depending on direction
}
Mind you these are both expensive calls to make while scrolling through a listview, so you will definitely have to implement wisely~
EDIT: Updated code to include return false