22

I have an HorizontalScrollView which contains a RelativeLayout. This layout is empty in the XML, and is populated from java in the onCreate.

I would like this scroll view to be initially somewhere in the middle of the RelativeLayout, which is way larger than the screen.

I tried mHorizScrollView.scrollTo(offsetX, 0); which doesn't work. I don't know what's wrong with this.

I could post the code, but it is not really relevant. What matters is that everything is done programatically (has to :s), and that the initial position of the HorizontalScrollView has to be set programmatically.

Thanks for reading. Please tell me if you need more details or if this is not clear enough.

Benoit Duffez
  • 11,839
  • 12
  • 77
  • 125

9 Answers9

30

I found that if you extend the HorizontalScrollView and override the onLayout, you can cleanly scroll, after the super call to onLayout:

MyScrollView extends HorizontalScrollView {
    protected void onLayout (boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        this.scrollTo(wherever, 0);
    }
}

EDIT: For scrolling to start or end you can use:

this.fullScroll(HorizontalScrollView.FOCUS_RIGHT/FOCUS_LEFT);

instead of

this.scrollTo(wherever, 0);
Amt87
  • 5,493
  • 4
  • 32
  • 52
HalR
  • 11,411
  • 5
  • 48
  • 80
27
public void autoSmoothScroll() {

        final HorizontalScrollView hsv = (HorizontalScrollView) view.findViewById(R.id.horiscroll);
        hsv.postDelayed(new Runnable() {
            @Override
            public void run() {
                //hsv.fullScroll(HorizontalScrollView.FOCUS_RIGHT);
                hsv.smoothScrollBy(500, 0);
            }
        },100);
    }
tekkavi
  • 904
  • 10
  • 19
  • I want to scroll to a view in horizontal scroll view. This code works for me. I used `hsv.post()` instead of `hsv.postDelayed()` – Minh Nguyen Jul 02 '18 at 15:52
  • this is also good to scroll smoothly hsv.arrowScroll(HorizontalScrollView.FOCUS_LEFT/RIGHT); – CanCoder Sep 16 '19 at 01:33
16

To test whether it's a timing issue (which I think it is), instead of calling scrollTo() in onStart, call postDelayed() with a Runnable that calls scrollTo, with a delay of 30 or so.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521
  • 1
    This confirmed that it is, indeed, a timing issue. Is there a cleaner way to do this, than a postDelayed Runnable ? – Benoit Duffez Jan 26 '11 at 08:24
  • 1
    You could probably subclass HorizontalScrollView, add a field that indicates that scrolling is necessary, and then do the scrolling at the end of the next call to onLayout() (or perhaps on computeScroll() or some other method). I think using postDelayed is just easier. – Ted Hopp Jan 28 '11 at 05:09
  • I found something curious, i think with 'scroll.smoothScrollTo' you need more delay then with 'scroll.scrollTo'. – neteinstein Aug 26 '11 at 15:36
  • I would be very careful using delays as a solution as you can't say if the delay set on your device/emulator will fit the performance of every other device. I think the better and cleaner solution is extending HorizontalScrollView as you can see in HaIR's answer – Muzikant Aug 02 '12 at 12:22
  • @Muzikant - I think HaIR's answer offers a better solution than my own. – Ted Hopp Aug 02 '12 at 15:04
9

A better approach would be using the ViewTreeObserver to observe layouts.

View interestedInView;
onCreate(){
  //Code

  //Observe for a layout change
  ViewTreeObserver viewTreeObserver = interestedInView.getViewTreeObserver();
  if (viewTreeObserver.isAlive()) {
    viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
      @Override
      public void onGlobalLayout() {
        //REMOVE THE VIEWTREE OBSERVER
        //interestedInView is ready for size and position queries because it has been laid out
      }
    });
  }
}
Vikram Bodicherla
  • 7,133
  • 4
  • 28
  • 34
  • Thanks for help its working for me, but I have question does it require more processing then postdelayed(new Runnable() ....) – MGD Feb 21 '12 at 21:33
  • 1
    Well, if you are taking run-time overhead, the answer is yes, because you are adding a new listener and removing it then. But it is so very minimal that it wont count. And it's worth the robustness of the approach. – Vikram Bodicherla Feb 22 '12 at 01:23
  • Now I am using ViewTreeObserver and its working for me, please tell me if its less efficient or what ??? – MGD Feb 23 '12 at 09:36
  • @MGD Not really. You are only attaching a listener and then removing it. So its equally efficient and more importantly, very robust. – Vikram Bodicherla Feb 24 '12 at 02:47
  • thanks for the comment Vikram yet I am having lil problem that I have posted http://stackoverflow.com/questions/9414035/horizontal-scrollview-full-scroll-focus-right-with-viewtreeobserver please consider this question if you could help :) – MGD Feb 24 '12 at 09:18
1

The problem is that the view doesn't have a size yet. The Clean way to do this is to implement the onSizeChanged of the ScrollView to send a message to a Handler in your activity to in order to notify the activity that the view has a size and that scrolling is possible.

  • This looked really good but it doesn't work. I created a class that extends HorizontalScrollView, and that implements onSizeChanged. When this callback is called, it calls a method in the activity that does the scrolling. However, the scroll is not working. It only works if it is post delayed. – Benoit Duffez May 12 '11 at 12:41
1

Use view.post and write scrollTo(x,y) inside run method. This the way to invalidate view in post method as per android API document.

Piyush
  • 11
  • 1
  • I just replaced my postDelayed with a .post and it works like a charm. Just as robust as a listener as it is triggered _after_ the view has bee attached. I use this to access the dimensions of a view as this is only available after it has been attached to the screen an measured. – slott May 25 '12 at 09:51
0

The scrollTo function should work. What is the layout_width of the HSV, and the view inside of it?

Jason LeBrun
  • 13,037
  • 3
  • 46
  • 42
  • 1
    I think this is a timing issue. The views inside the HSV are added at runtime (from onCreate) and the layout_width of the HSV is fill_parent. The mHSV.scrollTo() is called from onStart. – Benoit Duffez Jan 25 '11 at 23:30
0

The onSizeChanged function should work. What you fail to understand is that the onSizeChanged should send a message to a Handler of your activity and not any callback function, because a handler will execute code in the UI thread. You cannot do UI operations outside it. Have a look at handlers.

  • 1
    onSizeChanged must be called in the UI thread because when I called the scrollTo from onSizeChanged, it did not complain. – Benoit Duffez May 13 '11 at 08:31
0

Use Kotlin extension function.

inline fun <T : View> T.afterMeasured(crossinline f: T.() -> Unit) {
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
    override fun onGlobalLayout() {
        if (measuredWidth > 0 && measuredHeight > 0) {
            viewTreeObserver.removeOnGlobalLayoutListener(this)
            f()
        }
    }
})

}

Usage:

mHorizontalScrollView.afterMeasured {
        var scrollTo = 0
        val count = (layout.svList.getChildAt(0) as LinearLayout)
            .childCount

        if(index < count){
            val child = (layout.svList.getChildAt(0) as LinearLayout)
                .getChildAt(index)

            scrollTo = child.width

        }

        Log.d(TAG, "scrollToComment: $scrollTo")

        mHorizontalScrollView.scrollTo(scrollTo, 0)
    }
VIVEK CHOUDHARY
  • 468
  • 5
  • 8