OnScrollChangedListener в Android 2.x

Заметил особенность в отлове события OnScrollChanged на старых девайсах с Android 2.x (проблема также может относиться к 3.x; нет возможности проверить). Суть заключается в том, что scroll-слушатель не работает, если добавлять его до того, как layout впервые будет пересчитан. Например, если делать это в onCreate:

public class MainActivity : AppCompatActivity()
{
	override fun onCreate(savedInstanceState: Bundle?) {
		super.onCreate(savedInstanceState)
		setContentView(R.layout.activity_main)

		println(myView.getViewTreeObserver().isAlive()) // true

		myView.getViewTreeObserver().addOnScrollChangedListener {
			// will never fire
		}
	}
}

То же самое относится к методам onStart, onResume, onPostResume, onAttachedToWindow.

Таким образом, если вы поддерживаете API<15 (ICS), то не добавляйте OnScrollChangedListener в указанных методах. Причину такого поведения назвать затрудняюсь. В документации об условиях добавления scroll-слушателя ничего не сказано. Единственное – Observer должен быть Alive, что выполняется, как видно из примера выше.

Когда же добавлять слушатель?

1. Либо по какому-то пользовательскому событию, когда layout уже построен.

2. Либо при первом срабатывании OnGlobalLayoutListener:

val layoutListener = object : ViewTreeObserver.OnGlobalLayoutListener {
	override fun onGlobalLayout() {
		myView.getViewTreeObserver().removeGlobalOnLayoutListener(this)
		myView.getViewTreeObserver().addOnScrollChangedListener(scrollListener)
	}
}

myView.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener)

3. Либо, что иногда более удобно, в onWindowFocusChanged:

public class MainActivity : AppCompatActivity()
{
	private var isScrollListenerAdded = false

	override fun onCreate(savedInstanceState: Bundle?) {
		// ...
	}

	override fun onWindowFocusChanged(hasFocus: Boolean) {
		super.onWindowFocusChanged(hasFocus)
		if (hasFocus && !isScrollListenerAdded) {
			myView.getViewTreeObserver().addOnScrollChangedListener(scrollListener)
			isScrollListenerAdded = true
		}
	}

	override fun onPause() {
		myView.getViewTreeObserver().removeOnScrollChangedListener(scrollListener)
		isScrollListenerAdded = false
		super.onPause()
	}
}

Стоит отметить, что onWindowFocusChanged(false) не срабатывает в некоторых случаях (например, при смене ориентации он вызван не будет, зато повторно сработает onWindowFocusChanged(true)), поэтому удаление слушателя нужно производить в onPause или onStop.

PS

Кстати, на данный момент (сентябрь 2015), согласно статистике Google Play, доля устройств с API<15 в категории «Книги и справочники» – чуть более 7%.

Android 5.x	18.05%
Android 4.x	74.72%
Android 3.x	0.31%
Android 2.x	6.75%