I got the following exception when trying to change the background of pages inside a ViewPager in the onPageScrolled method. I have edited the question in order to make it more clear.
android.content.res.Resources$NotFoundException: Resource ID #0x0
at android.content.res.Resources.getValue(Resources.java:1245)
at android.content.res.Resources.getColor(Resources.java:899)
at android.support.v4.content.ContextCompat.getColor(ContextCompat.java:413)
at com.noel.material_onboarding.OnboardingActivity.color(OnboardingActivity.java:113)
at com.noel.material_onboarding.OnboardingActivity.access$200(OnboardingActivity.java:29)
at com.noel.material_onboarding.OnboardingActivity$1.onPageScrolled(OnboardingActivity.java:86)
First I create the slider objects, this includes setting up the background color:
addSlide(new SlideFragmentBuilder()
.description("This is a test")
.backgroundColor(R.color.colorPrimary)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 2")
.backgroundColor(R.color.green)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 3")
.backgroundColor(R.color.orange)
.build());
addSlide(new SlideFragmentBuilder()
.description("This is a test 4")
.backgroundColor(R.color.orange)
.build());
Here's a link to the SlideFragmentBuilder on github and the Fragment class itself
Here's my onPageScrolled method:
mViewPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int colorUpdate = (Integer) evaluator.evaluate(positionOffset, color(mOnboardingAdapter.getItem(position).backgroundColor()), color(mOnboardingAdapter.getItem(position + 1).backgroundColor()));
mViewPager.setBackgroundColor(colorUpdate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(colorUpdate);
}
}
#Override
public void onPageSelected(int position) {
btnFinish.setVisibility(position == mOnboardingAdapter.getLastItemPosition() ? View.VISIBLE : View.GONE);
btnNext.setVisibility(position == mOnboardingAdapter.getLastItemPosition() ? View.GONE : View.VISIBLE);
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
The color() method that is used
private int color(#ColorRes int color){
return ContextCompat.getColor(this, color);
}
Basically, I just need the background of one page to fade in as the user swipes to another page.
Ok, so I went through the docs and found an important thing I was missing out on:
int: Position index of the first page currently being displayed. Page position+1 will be visible if positionOffset is nonzero.
Basically the app was crushing on the second screen after the positionOffset went back to zero. See this is how it works:
On the first screen the positionOffset is zero and the position of the page is also zero, however position + 1 is not available since the positionOffset is zero. I solved this by adding the following statement to check whether the Offset is zero or not:
positionOffset != 0.0 ? position + 1 : position
This is how the onPageScrolled method looks like:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
int colorUpdate = (Integer) evaluator.evaluate(positionOffset, color(mOnboardingAdapter.getItem(position).backgroundColor()), color(mOnboardingAdapter.getItem(positionOffset != 0.0 ? position + 1 : position).backgroundColor()));
mViewPager.setBackgroundColor(colorUpdate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(colorUpdate);
}
}
try to use this
Color.parseColor(mOnboardingAdapter.getItem(position + 1));
and don't forgot to remove integer cast
(Integer)
And i think that
mOnboardingAdapter.getItem(position + 1)
is a null value (not initialized)
Related
I have a method that will check if the last element in a RecyclerView is completely visible by the user, so far I have this code
The problem is how to check if the RecyclerView has reached it's bottom ?
PS I have items dividers
public void scroll_btn_visibility_controller(){
if(/**last item is visible to user*/){
//This is the Bottom of the RecyclerView
Scroll_Top_Btn.setVisibility(View.VISIBLE);
}
else(/**last item is not visible to user*/){
Scroll_Top_Btn.setVisibility(View.INVISIBLE);
}
}
UPDATE : This is one of the attempts I tried
boolean isLastVisible() {
LinearLayoutManager layoutManager = ((LinearLayoutManager)rv.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = disp_adapter.getItemCount();
return (pos >= numItems);
}
public void scroll_btn_visibility_controller(){
if(isLastVisible()){
Scroll_Top.setVisibility(View.VISIBLE);
}
else{
Scroll_Top.setVisibility(View.INVISIBLE);
}
}
so far no success I think there is something wrong within these lines :
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = disp_adapter.getItemCount();
You can create a callback in your adapter which will send a message to your activity/fragment every time when the last item is visible.
For example, you can implement this idea in onBindViewHolder method
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
if(position==(getItemCount()-1)){
// here goes some code
// callback.sendMessage(Message);
}
//do the rest of your stuff
}
UPDATE
Well, I know it's been a while but today I ran into the same problem, and I came up with a solution that works perfectly. So, I'll just leave it here if anybody ever needs it:
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
LinearLayoutManager layoutManager=LinearLayoutManager.class.cast(recyclerView.getLayoutManager());
int totalItemCount = layoutManager.getItemCount();
int lastVisible = layoutManager.findLastVisibleItemPosition();
boolean endHasBeenReached = lastVisible + 5 >= totalItemCount;
if (totalItemCount > 0 && endHasBeenReached) {
//you have reached to the bottom of your recycler view
}
}
});
You should use your code with following change:
boolean isLastVisible() {
LinearLayoutManager layoutManager =((LinearLayoutManager) rv.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = rv.getAdapter().getItemCount();
return (pos >= numItems - 1);
}
Be careful, findLastCompletelyVisibleItemPosition() returns the position which start at 0. So, you should minus 1 after numItems.
Assuming you're using LinearLayoutManager, this method should do the trick:
boolean isLastVisible() {
LinearLayoutManager layoutManager = ((LinearLayoutManager)mRecyclerView.getLayoutManager());
int pos = layoutManager.findLastCompletelyVisibleItemPosition();
int numItems = mRecyclerView.getAdapter().getItemCount();
return (pos >= numItems);
}
try working with onScrollStateChanged it will solve your issue
Try this solution as it depends on how you want to implement the chat.
In your onCreate() method add the call to post to your recyclerview and implement the runable method, this to guarantee that the element has already been loaded and then execute the scrollToPosition method adding the last element of your list as a parameter.
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.scrollToPosition(yourList().size()-1);
}
});
How can I improve this code? I'd like to show a FAB only if fragment with position 0 is displayed, hiding it with scale effect when user swipe to other view pager fragments. The value:
(1 - positionOffset)
never reach exactly 0, so I've add setScale(0). This way before complete disappearing the FAB needs to wait that the new position (e.g. 1) is set. I'd like to have a more fluid behaviour, can you help me?
viewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
if (fab != null && position == 0) {
fab.setScaleX(1 - positionOffset);
fab.setScaleY(1 - positionOffset);
} else {
fab.setScaleX(0);
fab.setScaleX(0);
}
}
});
I'm interested in what is the right and first possible moment to get size of first item of RecyclerView?
I've tried to use:
recyclerView.setLayoutManager(new GridLayoutManager(context, 2));
recyclerView.setAdapter(new MyDymmyGridRecyclerAdapter(context));
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
recyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View firstRecyclerViewItem = recyclerView.getLayoutManager().findViewByPosition(0);
// firstRecyclerViewItem is null here
}
});
but it returns null at this moment.
If you're using OnGlobalLayoutListener you should remember onGlobalLayout can be called multiple times. Some of those calls can happen even before Layout is ready (and by ready I mean the moment when you can get dimensions of a View by calling view.getHeight() or view.getWidth()). So the proper way of implementing your approach would be:
recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int width = recyclerView.getWidth();
int height = recyclerView.getHeight();
if (width > 0 && height > 0) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN) {
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
} else {
recyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
}
}
View firstRecyclerViewItem = recyclerView.getLayoutManager().findViewByPosition(0);
}
});
Apart from that you still need to be sure that at the time of findViewByPosition(0) call:
Your RecyclerView's Adapter has at least one data element.
View at position 0 is currently visible in RecyclerView
Tell me if that fixes your issue, if not there is still another way of doing what you need.
In my extended RecyclerView I override onChildAttachedToWindow like this
#Override
public void onChildAttachedToWindow(View child) {
super.onChildAttachedToWindow(child);
if (!mIsChildHeightSet) {
// only need height of one child as they are all the same height
child.measure(0, 0);
// do stuff with child.getMeasuredHeight()
mIsChildHeightSet = true;
}
}
I had this type of problem. I want to perform click by default on the first visible position of the recylerview. I wrote the code for that on onResume but it did not work. I solved my problem by writting the code in onWindowFocusChanged method
#Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if(isCalledForTheFirstTime)
{
LinearLayoutManager manager= (LinearLayoutManager) rcViewHeader.getLayoutManager();
int pos= manager.findFirstCompletelyVisibleItemPosition();
View view=manager.findViewByPosition(pos);
if(view!=null)
{
view.performClick();
}
// change the vaule so that it would not be call in case a pop up appear or disappear
isCalledForTheFirstTime=false;
}
}
I'm working on a Circular ViewPager, and i've implemented this exactly solution (https://stackoverflow.com/a/12965787/1083564).
The only thing is missing, is the fact that i need to smoothScroll when i'm using the setCurrentItem(int i, bol b) method, that instantly goes to the pixel limit, without using the smoothScroll.
I already have the access to use this method, using the following code:
package android.support.v4.view;
import android.content.Context;
import android.util.AttributeSet;
public class MyViewPager extends ViewPager {
public MyViewPager(Context context) {
super(context);
}
public MyViewPager(Context context, AttributeSet attr) {
super(context, attr);
}
public void smoothScrollTo(int x, int y, int velocity) {
super.smoothScrollTo(x, y, velocity);
}
}
But i couldn't figure it out where and how to use it. I have the number of pixels that i need to run smoothly by using this code inside the setOnPageChangeListener on my ViewPager:
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
Log.d( "viewpager", positionOffsetPixels+"");
}
Before it goes to 0, instantly, because of the setCurrentItem, i have the value of pixels left to reach 0 (to the left) or x (to the right, depending of screen). I dont know how can i get this x number too.
PS: I think this solution is the exatcly one used by IMDB app. You can see this scrolling from the first to the last but one, without remove your finger (use 2 fingers to do it). You will see that the "white limit" will show from the left side of the ViewPager. The only difference is that they know how to smooth scroll after using the setCurrentItem.
If you need some more information, please, ask! Thanks!
Issue: When you detect circular scrolling has to be perfomed, calling setCurrentItem immediately will cause the ViewPager to scroll to the real fragment immediately without smooth scrolling as it is set to false.
Solution: Instead allow the ViewPager to scroll to the fake fragment smoothly as it does for other fragments and then scroll to the real fragment after some delay with smooth scrolling set to false. User will not notice the change.
When we are performing circular scrolling, call setCurrentItem in a runnable with some delay. Use onPageSelected to know the index of the page selected.
public void onPageSelected(int position) {
// Consider eg. : C' A B C A'
boolean circularScroll = false;
if(position == 0) {
// Position 0 is C', we need to scroll to real C which is at index 3.
position = mPager.getAdapter().getCount() - 2;
circularScroll = true;
}
int lastIndex = mPager.getAdapter().getCount() - 1;
if(position == lastIndex) {
// Last index is A', we need to scroll to real A, which is at index 1.
position = 1;
circularScroll = true;
}
if(circularScroll) {
final int realPosition = position;
mPager.postDelayed(new Runnable() {
#Override
public void run() {
mPager.setCurrentItem(realPosition, false);
}
}, 500L);
}
}
When you set the second parameter of the setCurrentItem to true it should smooth scroll
#Override
public void onPageScrollStateChanged (int state) {
if (state == ViewPager.SCROLL_STATE_IDLE) {
int curr = viewPager.getCurrentItem();
int lastReal = viewPager.getAdapter().getCount() - 2;
if (curr == 0) {
viewPager.setCurrentItem(lastReal, true);
} else if (curr > lastReal) {
viewPager.setCurrentItem(1, true);
}
}
}
I'm trying to smoothly scroll to last element of a list after adding an element to the arrayadapter associated with the listview.
The problem is that it just scrolls to a random position
arrayadapter.add(item);
//DOES NOT WORK CORRECTLY:
listview.smoothScrollToPosition(arrayadapter.getCount()-1);
//WORKS JUST FINE:
listview.setSelection(arrayadapter.getCount()-1);
You probably want to tell the ListView to post the scroll when the UI thread can handle it (which is why yours it not scrolling properly). SmoothScroll needs to do a lot of work, as opposed to just go to a position ignoring velocity/time/etc. (required for an "animation").
Therefore you should do something like:
getListView().post(new Runnable() {
#Override
public void run() {
getListView().smoothScrollToPosition(pos);
}
});
(Copied from my answer: smoothScrollToPositionFromTop() is not always working like it should)
This is a known bug. See https://code.google.com/p/android/issues/detail?id=36062
However, I implemented this workaround that deals with all edge cases that might occur:
First call smothScrollToPositionFromTop(position) and then, when scrolling has finished, call setSelection(position). The latter call corrects the incomplete scrolling by jumping directly to the desired position. Doing so the user still has the impression that it is being animation-scrolled to this position.
I implemented this workaround within two helper methods:
smoothScrollToPosition()
public static void smoothScrollToPosition(final AbsListView view, final int position) {
View child = getChildAtPosition(view, position);
// There's no need to scroll if child is already at top or view is already scrolled to its end
if ((child != null) && ((child.getTop() == 0) || ((child.getTop() > 0) && !view.canScrollVertically(1)))) {
return;
}
view.setOnScrollListener(new AbsListView.OnScrollListener() {
#Override
public void onScrollStateChanged(final AbsListView view, final int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
view.setOnScrollListener(null);
// Fix for scrolling bug
new Handler().post(new Runnable() {
#Override
public void run() {
view.setSelection(position);
}
});
}
}
#Override
public void onScroll(final AbsListView view, final int firstVisibleItem, final int visibleItemCount,
final int totalItemCount) { }
});
// Perform scrolling to position
new Handler().post(new Runnable() {
#Override
public void run() {
view.smoothScrollToPositionFromTop(position, 0);
}
});
}
getChildAtPosition()
public static View getChildAtPosition(final AdapterView view, final int position) {
final int index = position - view.getFirstVisiblePosition();
if ((index >= 0) && (index < view.getChildCount())) {
return view.getChildAt(index);
} else {
return null;
}
}
You should use setSelection() method.
Use LayoutManager to smooth scroll
layoutManager.smoothScrollToPosition(recyclerView, new RecyclerView.State(), position);
final Handler handler = new Handler();
//100ms wait to scroll to item after applying changes
handler.postDelayed(new Runnable() {
#Override
public void run() {
listView.smoothScrollToPosition(selectedPosition);
}}, 100);
The set selection method mentioned by Lars works, but the animation was too jumpy for our purposes as it skips whatever was left. Another solution is to recall the method repeatedly until the first visible position is your index. This is best done quickly and with a limit as it will fight the user scrolling the view otherwise.
private void DeterminedScrollTo(Android.Widget.ListView listView, int index, int attempts = 0) {
if (listView.FirstVisiblePosition != index && attempts < 10) {
attempts++;
listView.SmoothScrollToPositionFromTop (index, 1, 100);
listView.PostDelayed (() => {
DeterminedScrollTo (listView, index, attempts);
}, 100);
}
}
Solution is in C# via. Xamarin but should translate easily to Java.
Do you call arrayadapter.notifyDataSetChanged() after you called arrayadapter.add()? Also to be sure, smoothScrollToPosition and setSelection are methods available in ListView not arrayadapter as you have mentioned above.
In any case see if this helps:
smoothScrollToPosition after notifyDataSetChanged not working in android
Solution in kotlin:
fun AbsListView.scrollToIndex(index: Int, duration: Int = 150) {
smoothScrollToPositionFromTop(index, 0, duration)
postDelayed({
setSelection(index)
post { smoothScrollToPositionFromTop(index, 0, duration) }
}, duration.toLong())
}
PS: Looks like its quite messed up on Android SDK side so this is kind of best you can get, if you don't want to calculate your view offset manually. Maybe best easy way is to set duration to 0 for long list to avoid any visible jump.
I had some issues when calling just setSelection in some positions in GridView so this really seems to me as solution instead of using that.
use this in android java, it work for me:
private void DeterminedScrollTo(int index, int attempts) {
if (listView.getFirstVisiblePosition() != index && attempts < 10) {
attempts++;
if (listView.canScrollVertically(pos))
listView.smoothScrollToPositionFromTop(index, 1, 200);
int finalAttempts = attempts;
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
#Override
public void run() {
DeterminedScrollTo(index, finalAttempts);
}
}, 200);
}
}