I have the following code which recording the scroll Y position in my ListView whenever my activity is paused.
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
int scroll = mListView.getScrollY();
System.out.println (" scrollY" + scroll);
}
But regardless where I scroll my ListView vertically, the value of getScrollY() always return 0. Can anyone please help me?
Thank you.
ListView.getScrollY() returns 0, because you are not scrolling the ListView itself but the Views inside it. Calling 'getScrollY()' on any of the 'View's that you scroll would return actual values (only to some extent, since they get recycled).
In order to save your position, please try the following:
#Override
protected void onSaveInstanceState (Bundle outState) {
super.onSaveInstanceState(outState);
int scrollPos = mListView.getSelectedItemPosition();
outState.putInt("MyPosition", scrollPos);
}
#Override
public void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
int scrollPos = inState.getInt("MyPosition");
mListView.setSelection(scrollPos);
}
Good Luck!
Related
I have a layout that has several cardView, that have recyclerViews in them
in the beginning the first recyclerview is empty, so the card doesn't show.
later on, the recyclerview gets populated, so I want it to have a nice animation to it.
I'm trying to move the other cards down, and then fade in the card.
to do that I set the database of the recyclerView, and then I attach a OnPreDrawListener to the cardview around it, so I can get the height of the view, then I set the view to GONE and run a transationY animation on the card below it.
Thing is that when I call getMeasuredHeight I get 0.
It's almost as the notifyDatasetChanged() only happens in the next frame, so the view didn't get it's new height yet.
here is my code:
private void runSearchAnimation(List<Route> searchResult) {
if(rvSearchResults.getMeasuredHeight() == 0) {
Log.i(TAG, "height is 0");
resultsAdapter.setDatabase(searchResult);
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Log.i(TAG, "cvSearchResults.getMeasuredHeight() = " + cvSearchResults.getMeasuredHeight());
cvSearchResults.setVisibility(View.GONE);
ObjectAnimator contentAnim = ObjectAnimator.ofFloat(cvRecentSearches,
"translationY", cvRecentSearches.getTranslationY(), cvSearchResults.getMeasuredHeight());
contentAnim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationStart(Animator animation) {
Log.i(TAG, "animation started");
}
#Override
public void onAnimationEnd(Animator animation) {
Log.i(TAG, "animation ended");
cvSearchResults.setVisibility(View.VISIBLE);
}
});
contentAnim.setDuration(300);
contentAnim.start();
return true;
}
});
}
}
Does anyone have an idea on how to solve this?
I believe that RecyclerView supports such animations via RecyclerView.ItemAnimator.
See the docs.
so after some thinking I came up with this easy solution:
cvSearchResults.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(cvSearchResults.getMeasuredHeight() != 0) {
cvSearchResults.getViewTreeObserver().removeOnPreDrawListener(this);
Since I will keep getting my onPreDraw method called until I will remove the listener, I remove it only when I know the view has been measured with the recyclerview data I gave it
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 have a ListView with a custom child view as its row layout. My requirement is to invalidate the row's child view only when the ListView is scrolled. Right now I achieve this by calling ChildView.invalidate() from the child view's onDraw() method and things works very well as I expected, but this approach also invalidates the child view even when the ListView is not scrolled, so I observe that it consumes a lot of CPU when the app is running. I am looking for an inexpensive solution for this.
Is there any call back occured when the ListView is scrolled? I could not see anything.
Please answer, Thank you.
protected void onDraw (Canvas canvas) {
drawBackground(canvas);
drawContent(canvas);
this.invalidate();//Recursive calling...? But no error or warnings issued.
//super.onDraw(canvas);
}
#pskink Thanks It works! but did not solve the issue of "do it with an inexpensive approach". Please refer the comments in the code and answer what to do with arguments of onScroll() to only invalidate views which are visible on the screen. View.getChildAt(int).invalidate does not work
import android.app.ListActivity;
import android.widget.ListView;
import android.widget.AbsListView;
public class MyListActivity extends ListActivity implements ListView.OnScrollListener {
...
#Override
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.list_activity_layout);
getListView().setOnScrollListener(this);
}
....
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.e("MyListActivity", "onScrollStateChanged()");
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
Log.e("MyListActivity", "onScroll()");
Log.e("view.getId():", "" + view.getId());
//view.invalidateViews();//Calling this method does the job, but it seems that It might re-measure
// and redraw each and every views of each and every rows in the ListView..!
// So, still it is an expensive approach. is it?, but better than doing
// recursive calling of invalidate(); am I right?
// I am not satisfied with this approach.
// At least it should be possible to invalidate the views which are visible on the screen, as we expect from -
// the arguments of this method.
// Let me attempt to do it, shown below..
if(visibleItemCount != 0){
int lastVisibleItemCount = firstVisibleItem + visibleItemCount;
for(int i=firstVisibleItem; i<lastVisibleItemCount; i++){
if(view.getChildAt(i) != null){
view.getChildAt(i).invalidate();// Not works.
}
Log.e("view.getItemIdAtPosition(i):", "" + view.getItemIdAtPosition(i));
}
}
}
}
#pskink Here is the exact solution I was looking for. It only invalidates a single child view, which is in visibility range. It works without any issue.
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//view.invalidateViews();//It does.., but still being expensive...!
//The following only invalidates a single child view of the Row view in the visibility range.
//Initializing i=0 may seem unnecessary, but it resolves many unexpected behavior of
//the ListView when it is initialized to i=firstVisibleItem.
if(visibleItemCount != 0){
int lastVisibleItemCount = firstVisibleItem + visibleItemCount;;
for(int i=0; i<lastVisibleItemCount; i++){
mRowView = mListView.getChildAt(i);//returns the row view.
if(mRowView != null){
mChildView = (CustomChildView) mRowView.findViewById(R.id.custom_child_view);//Single child of the Row View.
if(mChildView != null){
mChildView.invalidate();//only invalidates the required Child view of the row view.
}
}
Log.e("Row view pos: ", "" + i);
}
}
}
In my Android Layout, I have a TextView. This TextView is displaying a rather large spannable text and it is able to scroll. Now when the phone is rotated, the View is destroyed and created and I have to setText() the TextView again, resetting the scroll position to the beginning.
I know I can use getScrolly() and scrollTo() to scroll to pixel positions, but due to the change in View widths, lines become longer and a line that was at pixel pos 400 might now be at 250. So this is not very helpful.
I need a way to find the first visible line in a TextView in onDestroy() and then a way to make the TextView scroll to this specific piece of text after the rotation.
Any ideas?
This is an old question, but I landed here when searching for a solution to the same problem, so here is what I came up with. I combined ideas from answers to these three questions:
Scroll TextView to text position
Dynamically Modifying Contextual/Long-Press Menu in EditText Based on Position of Long Press
ScrollView .scrollTo not working? Saving ScrollView position on rotation
I tried to extract only the relevant code from my app, so please forgive any errors. Also note that if you rotate to landscape and back, it may not end in the same position you started. For example, say "Peter" is the first visible word in portrait. When you rotate to landscape, "Peter" is the last word on its line, and the first is "Larry". When you rotate back, "Larry" will be visible.
private static float scrollSpot;
private ScrollView scrollView;
private TextView textView;
protected void onCreate(Bundle savedInstanceState) {
textView = new TextView(this);
textView.setText("Long text here...");
scrollView = new ScrollView(this);
scrollView.addView(textView);
// You may want to wrap this in an if statement that prevents it from
// running at certain times, such as the first time you launch the
// activity with a new intent.
scrollView.post(new Runnable() {
public void run() {
setScrollSpot(scrollSpot);
}
});
// more stuff here, including adding scrollView to your main layout
}
protected void onDestroy() {
scrollSpot = getScrollSpot();
}
/**
* #return an encoded float, where the integer portion is the offset of the
* first character of the first fully visible line, and the decimal
* portion is the percentage of a line that is visible above it.
*/
private float getScrollSpot() {
int y = scrollView.getScrollY();
Layout layout = textView.getLayout();
int topPadding = -layout.getTopPadding();
if (y <= topPadding) {
return (float) (topPadding - y) / textView.getLineHeight();
}
int line = layout.getLineForVertical(y - 1) + 1;
int offset = layout.getLineStart(line);
int above = layout.getLineTop(line) - y;
return offset + (float) above / textView.getLineHeight();
}
private void setScrollSpot(float spot) {
int offset = (int) spot;
int above = (int) ((spot - offset) * textView.getLineHeight());
Layout layout = textView.getLayout();
int line = layout.getLineForOffset(offset);
int y = (line == 0 ? -layout.getTopPadding() : layout.getLineTop(line))
- above;
scrollView.scrollTo(0, y);
}
TextView can save and restore its state for you. If you aren't able to use that, you can disable that and explicitly call the methods:
http://developer.android.com/reference/android/widget/TextView.SavedState.html
http://developer.android.com/reference/android/widget/TextView.html#onSaveInstanceState()
http://developer.android.com/reference/android/widget/TextView.html#onRestoreInstanceState(android.os.Parcelable)
The best answer, I got by searching.
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
final ScrollView scrollView = (ScrollView) findViewById(R.id.Trial_C_ScrollViewContainer);
outState.putFloatArray(ScrollViewContainerScrollPercentage,
new float[]{
(float) scrollView.getScrollX()/scrollView.getChildAt(0).getWidth(),
(float) scrollView.getScrollY()/scrollView.getChildAt(0).getHeight() });
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
final float[] scrollPercentage = savedInstanceState.getFloatArray(ScrollViewContainerScrollPercentage);
final ScrollView scrollView = (ScrollView) findViewById(R.id.Trial_C_ScrollViewContainer);
scrollView.post(new Runnable() {
public void run() {
scrollView.scrollTo(
Math.round(scrollPercentage[0]*scrollView.getChildAt(0).getWidth()),
Math.round(scrollPercentage[1]*scrollView.getChildAt(0).getHeight()));
}
});
}
I have created one custom view that contains the horizontalscrollview. Now when i changes the orientation its scroll state change to 0 every time.
I got the previous scroll state by using onSaveInstanceState(Bundle savedInstanceState) and onRestoreInstanceState(Bundle savedInstanceState) . in onRestoreInstanceState i am using following method to get reposition the scroll,
hoz_scroll.scrollTo(hoz_x_pos, hoz_scroll.getScrollY());
But there is not effect at all.
Help appreciated. Thanks.
If you have to call onCreate every time orientation change the position resets.
You can avoid it by adding orientation changed to manifest but not sure if current scroll state is intact.
I have researched it couple of days ago.If your interface has stadar dimentions on every orientation then you might find an equation by sampling many scroll values.
Create a map with the getScrollY() with values on landscape and portait that displays the same text. More values are better. Then use Polynomial interpolation to create a polynomial equation using matlab or papper.
More values -> more accuracy
http://en.wikipedia.org/wiki/Polynomial_interpolation
also scrolling to a position must be done like this
hoz_scroll.post(new Runnable() {
public void run() {
scroller.scrollTo(hoz_x_pos,hoz_scroll.getScrollY());
}
});
I believe you need to set
android:configChanges="orientation"
in your activity's element in AndroidManifest.xml.
Then you need to override public void onConfigurationChanged(Configuration config) in your activity.
Put this code in your Activity to not have it scroll when orientation changes. You don't even need to code.
#Override
public void onConfigurationChanged(Configuration config){
super.onConfigurationChanged(config);
}
here is a working solution:
private ScrollView scroll;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_content);
TextView title=(TextView) findViewById(R.id.textView1);
TextView content=(TextView) findViewById(R.id.textView2);
title.setText(getIntent().getStringExtra("title"));
content.setText(getIntent().getStringExtra("content"));
scroll = (ScrollView) findViewById(R.id.scrollView1);
if(savedInstanceState!=null){
final int y=savedInstanceState.getInt("position");
scroll.post(new Runnable() {
public void run() {
scroll.scrollTo(0,y);
}
});
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("position", scroll.getScrollY());
}