I build a recyclerview like so:
recyclerView = (RecyclerView) view.findViewById(R.id.recyclerview);
recyclerView.setLayoutManager(new LinearLayoutManager(getActivity()));
currentResults = new ArrayList<Stuff>();
stuffAdapter = new StuffAdapter(currentResults, getActivity());
recyclerView.setAdapter(stuffAdapter);
then when the user searches for things and results are returned, they're added to the adapter in the normal way
currentResults.clear();
currentResults.addAll(searchResponse.results);
restaurantAdapter.notifyDataSetChanged();
this is all very standard. the results are obtained via a searchview widget in an appcompat toolbar and a retrofit callback.
the problem is that there is one particular point where the user can close the keyboard, and the UI adjusts to fit, and then the whole thing crashes with
java.lang.UnsupportedOperationException: RecyclerView does not support scrolling to an absolute position. at
android.support.v7.widget.RecyclerView.scrollTo(RecyclerView.java:941) at
android.view.View.setScrollX(View.java) at
android.animation.PropertyValuesHolder.nCallIntMethod(Native Method) at
android.animation.PropertyValuesHolder.access$200(PropertyValuesHolder.java)
at android.animation.PropertyValuesHolder$IntPropertyValuesHolder.setAnimatedValue(PropertyValuesH
Nobody is calling scrollto. I don't get why it's throwing this. Any insight would be appreciated.
Reading from another SO Question :
java.lang.UnsupportedOperationException: RecyclerView does not support scrolling to an absolute position
The problem is with some specific devices only, because of there Implementation of ICS. As the suggested work around, you can make a CustomRecyclerView that extend RecyclerView and override scrollTo that catch the error and stop the application to crash.
#Override
public void scrollTo(int x, int y) {
Log.e(TAG, "CustomRecyclerView does not support scrolling to an absolute position.");
// Either don't call super here or call just for some phones, or try catch it. From default implementation we have removed the Runtime Exception trown
}
Related
I am working on an Android app that runs on only one devicerunning KitKat.
The smooth scrolling feature for a RecylerView I used that was working on other physical tablets and genymotion has unfortunately stopped working on the one device it needs to work on.
Instead of scrolling to a certain position it passes over the target position and scrolls all the way to the bottom and looks really bad.
I am able to track down the error to the abstract SmoothScroller in the RecyclerView class.
if (getChildPosition(mTargetView) == mTargetPosition) {
onTargetFound(mTargetView, recyclerView.mState, mRecyclingAction);
mRecyclingAction.runIfNecessary(recyclerView);
stop();
} else {
Log.e(TAG, "Passed over target position while smooth scrolling.");
mTargetView = null;
}
I was using a SnappingLinearLayoutManager that I found online, but swapped it out with the normal LinearLayoutManager from Android, and still am having the same problem.
The list is 7 items long (user can see 4 at a time) and I scroll to the 5th item (position 4) item.
When I scroll to the 3rd I don't receive this error.
Also after I scroll the list up and down once, the error stops happening.
EDIT:
I am able to use layoutManager.scrollToPositionWithOffset(); But I am trying to do this with the smooth scroll animation.
Here is some of my code and details:
private void setupMainRecyclerViewWithAdapter() {
mainLayoutManager = new SnappingLinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mainListRecyclerView.setLayoutManager(mainLayoutManager);
settingsMainListAdapter = new SettingsListAdapter(SettingsActivity.this,
settingsPresenter.getSettingsItems(),
settingsPresenter);
mainListRecyclerView.setAdapter(settingsMainListAdapter);
mainListRecyclerView.addItemDecoration(new BottomOffsetDecoration(EXTRA_VERTICAL_SCROLLING_SPACE));
}
#Override
public void scrollMainList(boolean listAtTop) {
if(listAtTop) {
mainListRecyclerView.smoothScrollToPosition(4);
moveMainMoreButtonAboveList();
} else {
mainListRecyclerView.smoothScrollToPosition(0);
moveMainMoreButtonBelowList();
}
}
If you call recyclerView.smoothScrollToPosition(pos) will be called immediately on the UI thread and if recyclerView's Adapter is too much busy to generating view items then the calling of smoothScrollToPosition will be missed then because recyclerView has no data to smooth scroll. So it's better to do that in a background thread by recyclerView.post(). By calling this it goes into the Main thread queue and gets executed after the other pending tasks are finished.
Therefore you should do something like this which worked for my case:
recyclerView.post(new Runnable() {
#Override
public void run() {
recyclerView.smoothScrollToPosition(pos);
}
});
Well, I realize it's too late, however I tried some different solutions and found one...
in custom LinearSmoothScroller I override updateActionForInterimTarget
#Override
protected void updateActionForInterimTarget(Action action) {
action.jumpTo(position);
}
It's appears not very smooth, but not instant in contrast with scrollToPositionWithOffset.
Just add one line for smooth scroll
recyclerView.setNestedScrollingEnabled(false);
it will work fine
Take a look at hasPendingAdapterUpdates(). You can use this along with a delay() for coroutines or Thread.sleep() to enable the backing data to be available before doing the scroll.
The scroll state of an item on a view pager is not getting saved and restored on android KitKat and older devices (I've tested API 15,17,19,22,23 at this point)
The scroll view is in a fragment inside another fragment with a view pager.
The view pager adapter is a FragmentStatePagerAdapter
When the pager adapter saves the state, it calls android.support.v4.app.FragmentManager#saveFragmentInstanceState.
From there we end up in android.support.v4.app.FragmentManagerImpl#saveFragmentViewState but a call to ScrollView#saveHierarchyState doesn't add a saved state to the bundle like it does on newer devices. Specifically the newer devices add an instance of android.widget.HorizontalScrollView.SavedState (That's what the debugger says anyhow, the code suggests it would be a android.widget.ScrollView.SavedState whereas the older API devices add android.view.AbsSavedState#EMPTY_STATE
void saveFragmentViewState(Fragment f) { //f is my fragment
if (f.mInnerView == null) {
return;
}
if (mStateArray == null) {
mStateArray = new SparseArray<Parcelable>();
} else {
mStateArray.clear();
}
// f.mInnerView is a ScrollView
f.mInnerView.saveHierarchyState(mStateArray); //should save state here
if (mStateArray.size() > 0) {
f.mSavedViewState = mStateArray;
mStateArray = null;
}
}
Further investigation of what should be happening lead me to android.widget.ScrollView#onSaveInstanceState which actually saves the scroll position into android.widget.ScrollView.SavedState#scrollPosition
When I diffed ScrollView.java between android API 23 and 19, I found that newer versions had indeed added code which saves and restores this state.
I discovered that the support library widget, NestedScrollView, was the easiest solution as it saves scroll state properly. As the linked documentation states, it doesn't actually need to be nested as the same class can be the parent or nested child. One important change I needed to make was to include the scrollbars which aren't there by default for some reason with this class.
<android.support.v4.widget.NestedScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/my_scroller"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="#style/AppTheme"
android:scrollbars="vertical"
tools:context="com.example.myapplication.FragmentOne">
We've been looking around for the implementation of the new material design swipe down to refresh on RecyclerViews with the loading circle coming down from bottom view same like new gmail app.i found so many things. But not getting new gmail app like example or demo .i already implement refreshlayout and recycleview.
But when scroll down to recycleview,how to put loading circle at the last of the record in recycleview. I want to get some idea to put in onscroll of recycleview.
recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(int arg0) {
}
#Override
public void onScrolled(int arg0, int arg1) {
//add some data but not to show loader.
getDataFromDB();
}
});
hi If you wan't to develop such a kind of Layout then please follow this url, i was used it it's an awesome.
https://github.com/stormzhang/SwipeRefreshLayoutDemo
You should wrap all your layouts with
pull to refresh component for that.
after swipeRefreshLayout.setOnRefreshListener and set swipeRefreshLayout.setRefreshing(true); when you send API call
hear is awesome demo for both Scroll down to load data using recycleview and Swipe to RefreshLayout.
EDIT: tl;dr: WebView appears as white box, even though I appear to be setting it up correctly, and indeed it does work the first two times, but fails subsequently)
EDIT: Video showing the problem in action...
I have the following bit of code which inflates a view (Which contains a WebView) from the xml which defines it:
private void createCard(ViewGroup cvFrame, Card card) {
//... setup vairables...
cvFrame.clearDisappearingChildren();
cvFrame.clearAnimation();
try {
View cv = LayoutInflater.from(getBaseContext()).inflate(R.layout.card_back_view,
cvFrame, true);
cv.setBackgroundDrawable(Drawable.createFromStream(mngr.open(deckName + "_Card_back.png"), deckName));
TextView suit = (TextView)cv.findViewWithTag("card_back_suit");
//...setup text view for suit, this code works fine every time...
WebView title = (WebView)cv.findViewWithTag("card_back_title");
//This WebView doesn't appear to be the one which actually appears on screen (I can change settings till I'm blue in the face, with no effect)
if (title != null) {
title.setBackgroundColor(0x00000000);
title.loadData(titleText, "text/html", "UTF-8");
} else {
Log.e("CardView", "Error can't find title WebView");
}
} catch (IOException e) {
Log.e("CardView", "Error making cards: ", e);
}
}
When this method is called as part of the onCreate method in my Activity, the WebView contains the correct code, and is suitably transparent.
I have a gesture listener which replaces the contents of the ViewGroup with different content (It animates the top card off to the left, replaces the contents of the top card with card 2, puts the top card back, then replaces card 2 with card 3)
//Gesture listener event
ViewGroup cvFrame = (ViewGroup)findViewById(R.id.firstCard);
cardLoc++
cvFrame.startAnimation(slideLeft);
(onAnimationEnd code)
public void onAnimationEnd(Animation animation) {
if (animation == slideLeft) {
ViewGroup cvFrameOldFront = (ViewGroup)findViewById(R.id.firstCard);
ViewGroup cvFrameNewFront = (ViewGroup)findViewById(R.id.secondCard);
createCard(cvFrameOldFront, cards.get((cardLoc)%cards.size()));
createCard(cvFrameNewFront, cards.get((cardLoc+1)%cards.size()));
TranslateAnimation slideBack = new TranslateAnimation(0,0,0,0);
slideBack.setDuration(1);
slideBack.setFillAfter(true);
cvFrameOldFront.startAnimation(slideBack);
}
}
When the animation has happened and I replace the contents of the cards, the TextView suit is replaced fine and the code definitely passes through the code to replace the WebView contents, but for some reason I end up with a white rectangle the size and shape of the WebView, no content, no transparency.
If I change the WebView to a TextView, it's contents is replaced fine, so it's an issue that occurs only with the WebView control :S
Can anyone tell me why / suggest a fix?
It turns out the WebView doesn't get cleared down when using the LayoutInflater to replace the contents of a ViewGroup. The other controls all seem to get removed (or at least the findViewWithTag() returns the right reference for every other control). I've just added in the line cvFrame.removeAllViews() immediately before the LayoutInflater does it's stuff and that fixed the issue.
If anyone has any better explanation for this I'll throw the points their way otherwise they will just go into the ether...
By calling findViewById, you are getting a reference on the previously loaded webview do you ?
so the loadData call that fails is the second one you make on a single webview instance.
you may want to check this :
Android WebView - 1st LoadData() works fine, subsequent calls do not update display
It appears that loadData() won't load data twice... you may want to try WebView.loadDataWithBaseUri()
Hope that helps.
I had a similar problem loading several WebViews content.
It was because of a misusing of the pauseTimers function
The situation was : the first webView weren't needed anymore, conscientiously I wanted to pause it before to release it. Calling onPause() and pauseTimers()
pauseTimers being common to any web views, it broke every use of webviews occuring after that, there were displaying only white rectangles.
Maybe its not your problem here, but it's worth checking your not calling WebView.pauseTimers() somewhere.
To confirm your answer, the source code for LayoutInflater.inflate(int resource, ViewGroup root, boolean attachToRoot) does in fact internally calls root.addView() which attaches the newly inflated view at the end of the root's children instead of replacing them.
So the mystery now is why did your call to findViewWithTag() is returning the expected objects for your other widgets (which would be the top, most recently created instances), but for your WebView it was returning something else.
Is it possible that there is another object in your layout XML which shares the same "card_back_title" tag?
I was also wondering why you didn't use the more common findViewById() instead, but I am not sure whether it would make a difference.
I have a ListView with about 100 entries. When the user does the "fling" from bottom to top it starts scrolling and keeps on scrolling even when the finger does not touch the display any more.
Is there a way to stop the scrolling animation at this point?
and we lookup the android source code (AbsListView), give it a ACTION_CANCEL touchEvent, can stop the fling. it is easy.
listView.dispatchTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(), SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
I didn't try the solution of Pompe de velo but since smoothScrollToPosition() is not available for API level less than 8 this didnt work for me.
I agree, changing default behaviour is not a good Idea, but sometimes you need to. So here is my (dirty) solution which uses reflection. This is by far not the recommended way since it's a hack but it works for me. There might be a better solution but I didn't found it.
class StopListFling {
private static Field mFlingEndField = null;
private static Method mFlingEndMethod = null;
static {
try {
mFlingEndField = AbsListView.class.getDeclaredField("mFlingRunnable");
mFlingEndField.setAccessible(true);
mFlingEndMethod = mFlingEndField.getType().getDeclaredMethod("endFling");
mFlingEndMethod.setAccessible(true);
} catch (Exception e) {
mFlingEndMethod = null;
}
}
public static void stop(ListView list) {
if (mFlingEndMethod != null) {
try {
mFlingEndMethod.invoke(mFlingEndField.get(list));
} catch (Exception e) {
}
}
}
}
Well there surely is a way to do it. But the point is more whether or not it is advisable to do it, in my opinion.
The list is a standard Android control that behaves constistently across all applications. So I would be surprised if I found a list that did not behave the same in your application. You can stop the fling by putting your finger back on the screen at any time.
That said, if you want to do extra work, you could subclass the list view and override its on touch method. Best way to know what to do is to get the source code of ListView (ListView in Android 1.6).
You can prevent flinging for ListViews in API 8 by overriding onTouchEvent and calling smoothScrollBy.
#Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_UP:
this.smoothScrollBy(0, 0);
break;
}
return super.onTouchEvent(ev);
}
This takes over from the fling scrolling and scrolls 0px instead.
My opinion is that you shouldn't modify this behaviour, since the fling behaviour is what the user expects.
However, to your question. I haven't tried this but in theory it should work.
Implement an OnScrollListener to your ListView and use the onScrollStateChanged() method to check if the current state is SCROLL_STATE_FLING. After you've determined that the scrolling perfoms by a fling you can get your ListView's first visible position by using the getFirstVisiblePosition() method and from there you can use smoothScrollToPosition() where you put in your getFirstVisiblePosition() value as an argument.
if you what disable default animation from list view just need set id for root (main) layout in xml and call void onClickListener in class for root layout