HorizontalScrollView: auto-scroll to end when new Views are added?
I have a HorizontalScrollView containing a LinearLayout. On screen I have a Button that will add new Views to the LinearLayout at runtime, and I'd like the scroll view to scroll to the end of the list when a new View is added.
I almost have it working - except that it always scrolls one view short of the last view. It seems like it's scrolling without first calculating the inclusion of the new view.
In my app I am using a custom View object, but I made a small test application that uses ImageView and has the same symptom. I tried various things like requestLayout() on both the Layout and ScrollView, I tried scrollTo(Integer.MAX_VALUE) and it scrolled into the netherverse :) Am I violating a UI thread issue or something?
- Rick
======
public class Main extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button b = (Button) findViewById(R.id.addButton);
b.setOnClickListener(new AddListener());
add();
}
private void add() {
LinearLayout l = (LinearLayout) findViewById(R.id.list);
HorizontalScrollView s =
(HorizontalScrollView) findViewById(R.id.scroller);
ImageView i = new ImageView(getApplicationContext());
i.setImageResource(android.R.drawable.btn_star_big_on);
l.addView(i);
s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
private class AddListener implements View.OnClickListener {
@Override
public void onClick(View v) {
add();
}
}
}
Layout XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<HorizontalScrollView
android:id="@+id/scroller"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:scrollbarSize="50px">
<LinearLayout
android:id="@+id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="4px"/>
</HorizontalScrollView>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center">
<Button
android:id="@+id/addButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="80px"
android:paddingRight="80px"
android:paddingTop="40px"
android:paddingBottom="40px"
android:text="Add"/>
</LinearLayout>
</LinearLayout>
Solution 1:
I think there's a timing issue. Layout isn't done when a view is added. It is requested and done a short time later. When you call fullScroll immediately after adding the view, the width of the linearlayout hasn't had a chance to expand.
Try replacing:
s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
with:
s.postDelayed(new Runnable() {
public void run() {
s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
}, 100L);
The short delay should give the system enough time to settle.
P.S. It might be sufficient to simply delay the scrolling until after the current iteration of the UI loop. I have not tested this theory, but if it's right, it would be sufficient to do the following:
s.post(new Runnable() {
public void run() {
s.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
}
});
I like this better because introducing an arbitrary delay seems hacky to me (even though it was my suggestion).
Solution 2:
I agree with @monxalo and @granko87 that the better approach is with a listener instead of making an assumption that the layout will be complete if we let some arbitrary amount of time pass.
In case, like me, you don't need to use a custom HorizontalScrollView subclass you can just add an OnLayoutChangeListener to keep things simple:
mTagsScroller.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
mTagsScroller.removeOnLayoutChangeListener(this);
mTagsScroller.fullScroll(View.FOCUS_RIGHT);
}
});