Intro
In this post we will show you how you can get the visibility of a view element in Android when you scroll the screen, how to determine how much of a view is visible on the screen at given time, to follow the changes in visibility of the given view. This could be useful if you want to trigger some action when scrolling and you need the view will be fully visible, or maybe more than 50% visible, depending on your specific needs. This is especially useful if you are implementing MRAID 3.0 (Mobile Rich Media Ad Interface) exposure change. You can find this implementation on page 60 in the MRAID 3.0 documentation here.
In the example we will be using three TextViews, and we will measure the visibility on screen of the middle element. We will be using Kotlin in this example.
The example
First we will create a new project in Android Studio with an empty activity and we will name the project VisibilityExposureChange. We will start with the layout. Our root layout element will be a ScrollView. Because a ScrollView can only have one direct child, we will add a Constraint layout inside the ScrollView that we will use as a frame. Inside the constraint layout we will add two TextViews and between them a WebView. We will set some dummy text in the TextViews. When we are done, our layout will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
<ScrollView android:id="@+id/scrollView" android:layout_width="match_parent" android:layout_height="match_parent" xmlns:android="http://schemas.android.com/apk/res/android"> <androidx.constraintlayout.widget.ConstraintLayout xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/scrollViewRootChild" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <TextView android:id="@+id/dummyTextOne" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dummy_text" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/measuredView" android:layout_width="match_parent" android:layout_height="400dp" android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/dummyTextTwo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/dummy_text" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@id/measuredView" app:layout_constraintBottom_toBottomOf="parent"/> </androidx.constraintlayout.widget.ConstraintLayout> </ScrollView> |
Now we go to the main activity. In order to measure the visibility of the view we will need two methods onScrollTouchListener() and getViewVisibilityOnScrollStopped().
In the onScrollTouchListener() method we will handle the user’s action as a MotionEvent and we will get A MotionEvent is an object that is used to report movement events (mouse, pen, finger, trackball). Motion events may hold either absolute or relative movements and other data, depending on the type of device.
Based on the user action we will get the ScrollView location on the screen using the getLocationOnScreen method of the View class and the Scroll view height. After that we will get our measured view as a child of the constraint layout element, and we will get the location and the height of our measured view. With this data as parameters we will call our second method to get the view’s visibility when the scrolling is stopped. The code for this method will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
private fun onScrollTouchListener() { scrollView.setOnTouchListener(View.OnTouchListener { _, event -> val action = event.action if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { Handler().postDelayed({ val childCount: Int = scrollViewRootChild.childCount //Scroll view location on screen val scrollViewLocation = intArrayOf(0, 0) scrollView.getLocationOnScreen(scrollViewLocation) //Scroll view height val scrollViewHeight: Int = scrollView.height for (i in 0 until childCount) { val child: View = scrollViewRootChild.getChildAt(i) if (child.id == measuredView.id && child.visibility == View.VISIBLE) { val viewLocation = IntArray(2) child.getLocationOnScreen(viewLocation) val viewHeight: Int = measuredView.height getViewVisibilityOnScrollStopped( scrollViewLocation, scrollViewHeight, viewLocation, viewHeight ) } } }, 150) } false }) } |
In our second method getViewVisibilityOnScrollStopped() we will use four parameters – location of the scroll view on screen, the height of the scroll view, the location of our measured view on screen and the height of our measured view on screen. In this method we will check the visibility of our measured view using the top and bottom of the measured view. If the bottom and the top of the view are both inside the scroll view, the visibility will be 100%, and if the view’s bottom is outside of the scroll view we will calculate the visible part by subtracting the measured view’s top from scroll view’s bottom. The code for this method will look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
private fun getViewVisibilityOnScrollStopped( scrollViewLocation: IntArray, //location of scroll view on screen scrollViewHeight: Int, // height of scroll view viewLocation: IntArray, // location of view on screen, you can use the method of view class's getLocationOnScreen method. viewHeight: Int // height of view ) { var visiblePercent: Float val viewBottom = viewHeight + viewLocation[1] //Get the bottom of view. if (viewLocation[1] >= scrollViewLocation[1]) { //if view's top is inside the scroll view. visiblePercent = 100f val scrollBottom = scrollViewHeight + scrollViewLocation[1] //Get the bottom of scroll view if (viewBottom >= scrollBottom) { //If view's bottom is outside from scroll view val visiblePart = scrollBottom - viewLocation[1] //Find the visible part of view by subtracting view's top from scrollview's bottom visiblePercent = visiblePart.toFloat() / viewHeight * 100 } Toast.makeText( this, "Visibility of the view: " + visiblePercent.toInt() + "%", Toast.LENGTH_SHORT ).show() } else { //if view's top is outside the scroll view. if (viewBottom > scrollViewLocation[1]) { //if view's bottom is outside the scroll view val visiblePart = viewBottom - scrollViewLocation[1] //Find the visible part of view by subtracting scroll view's top from view's bottom visiblePercent = visiblePart.toFloat() / viewHeight * 100 Toast.makeText( this, "Visibility of the view: " + visiblePercent.toInt() + "%", Toast.LENGTH_SHORT ).show() } } } |
The final result:
You can find the whole project here.
Happy coding!