- Пример использования ScrollView в Android
- Обзор Scrollview в Android
- Советы по использованию Scrollview
- Пример использования Scrollview в Android
- На SDK надейся и сам не плошай: Проблема вложенных скроллов в BottomSheetBehavior
- Первая встреча
- Пробуем отлавливать события
- Ищем перехватчика
- BottomSheetBehavior
- Исправление
- Java Reflection API
- Копипаст
- Заключение
- Android fillViewport property of Scroll View | Tips Tricks
- Step 1. Insert long string
- Step 2. Main XML file
- long text and fillViewport = “false”
- long text and fillViewport = “true”
- short text and fillViewport = “false”
- short text and fillViewport = “true”
- One important thing
Пример использования ScrollView в Android
В этой статье мы рассмотрим пример использования ScrollView в простом Android приложении.
ScrollView — служит для упаковки иерархии объектов View в контейнер, который можно прокручивать (скролить).
Обзор Scrollview в Android
Контейнер Scrollview представляет собой особый тип FrameLayout. Его особенность состоит в том, что он позволяет пользователям просматривать контент, который занимает больше места, чем физически есть на дисплее. Иными словами — просматривать контент, который не вмещается на экране без прокрутки.
Scrollview может содержать только один дочерний элемент или контейнер ViewGroup .
Советы по использованию Scrollview
- Никогда не используйте view-компонент ListView или GridView вместе со ScrollView. ListView предназначен для отображения списка элементов и оптимизирован для работы с большим объемом данных. Кроме того, он содержит встроенные возможности для прокрутки содержимого.
- Контейнер Scrollview поддерживает только вертикальную прокрутку. Если Вы хотите добавить горизонтальную прокрутку, то нам понадобится другой наследник FrameLayout — контейнер HorizontalScrollView.
- В xml разметке контейнер ScrollView имеет полезное свойство android:fillViewport , которое определяет, должен ли Scrollview растягивать свое содержание, чтобы заполнить доступное место на экране. Также доступен метод для установки этого значения в коде: setFillViewport(boolean) .
Пример использования Scrollview в Android
Теперь создадим макет, в котором будет виджет ImageView с картинкой и два TextView: для заголовка и текста. Все это обернем в вертикальный LinearLayout и поместим в корневой ScrollView (помним, что Scrollview не может содержать больше одного дочернего элемента):
Источник
На SDK надейся и сам не плошай: Проблема вложенных скроллов в BottomSheetBehavior
Наверное, каждый, любуясь красивыми и магически плавно съезжающими в разные стороны окошками, тулбарами и остальными вьюхами, задумывался как это работает, наверное, даже что-то читал про CoordianatorLayout, про различные Behaivor, которые позволяют создавать буквально волшебство на андроидовских вьюхах. Конечно, можно писать кастомные вьюхи, с нужным поведением, которое может быть ограничено только твоим воображением или твоими знаниями в Android разработке. Но помимо этого, есть ещё одно ограничение — время, не будешь же ты писать кастомную вьюху на хакатоне, в горящем по срокам проекте, а написанные заранее решения с кастомными вьюхами могут быть освоены не за самый короткий срок членами команды. Именно тогда приходит самое простое и самое логичное решение проблемы — не выпендриваться, использовать стандартные инструменты Android, там же смышлённые ребята сидят, всё будет в шоколаде (ну или в других сладостях андроида).
Но не всё так просто, тут и начинается моё близкое знакомство с магией таких ребят, как CoordinatorLayout, BottomSheetBehavior, а точнее с багом, который выпустили из виду разработчики, когда их писали. В статье будет описан процесс выявления бага, связанного с вложенной прокруткой внутри view-компонентов с поведением BottomSheetBehavior, а также способы его решения.
Первая встреча
Передо мной стояла задача сделать несложный функционал с вложенной прокруткой, состоящей из горизонтального RecyclerView, ScrollView и CoordinatorLayout, именно он позволяет, ориентируясь на Behavior-ы вложенных вьюх, вытворять такие фигуры высшего пилотажа, как плавные произвольные сдвиги различных вью.
Разложим по полочкам: делаем корневой CoordinatorLayout, внутри которого, находится RecyclerView, с прописанным поведением из библиотеки android.material BottomSheetBehavior, что позволит нашему ресайклеру, выезжать снизу корневого вью или обратно туда сворачиваться. Внутри item-ов каждого, находится TextView — заголовок item-а, и NestedScrollView, с различным наполнением (допустим тот же TextView).
Почему NestedScrollView? Потому что он, согласно официальной документации Android, выполняет ту же функцию, что ScrollView, но помимо этого поддерживает вложенную прокрутку, как со стороны родительской, так и со стороны дочерней вьюхи, по умолчанию.
Накидали вьюхи на layout, написали своих наследников RecyclerView.Adapter и RecyclerView.ViewHolder, сделали модели данных, заглушки, кажется всё готово.
Итак, камера, мотор, и… Всё работает? Шторка выезжает и сворачивается, NestedScrollView крутится, в чём подвох? Перелистываем recycler на другой item, пробуем скроллить… Не работает… Прокрутим ещё на несколько item-ов дальше, снова работает, и так продолжается с точной периодичностью в несколько item-ов. Кто знаком с идеей работы RecyclerView, уже знает в чём дело — дело в грязных View, которые Recycler переиспользует, чтобы экономить ресурсы смартфона. Но это не совсем то, из-за чего может не скроллится NestedScrollView, начинаем расследование.
Пробуем отлавливать события
Первое что, пришло мне в голову — посмотреть какие события приходят в рабочем item-е и в нерабочем. Попробуем отлавливать события onScrollChange:
В первом item всё ок, а вот в остальных тишина… Тогда пробуем, кое-что поинтереснее, а именно перехватывать касания:
Картина в логах поинтереснее, мы видим, что события касаний логируются, как в рабочем, так и в нерабочем примере, однако если повнимательнее посмотреть, то можно увидеть, что в рабочем примере событий больше, чем в нерабочем, следовательно можно придти к выводу, что кто-то или что-то перехватывает наши события, которые так нужны, чтобы сделать скролл.
Ищем перехватчика
Как мы знаем, все события поступают не сразу в дочернюю вьюху, а сначала проходят через все родительские. Что от нас требуется? Найти ту вьюху, в которой появляются пропадающие в NestedScrollView события OnTouch. Пробуем слушать события в корневой вьюхе item-а:
Без изменений. Идём выше, пробуем перехватить в самом recycler, картина не меняется, однозначно приходят не все события. Проверяем самого главного парня: CoordinatorLayout, и тут есть пробитие — события приходящие из работающего item-а, такие же как из неработающего.
Но как известно, сам по себе CoordinatorLayout, мало чего умеет, он может только отлавливать поведение дочерних компонентов, нет поведения — CoordinatorLayout, грубо говоря, равен FrameLayout. И тут мы вспоминаем, что как раз назначили RecyclerView поведение из библиотеки material — BottomSheetBehavior. Следовало быть, он тот, кого мы и искали.
BottomSheetBehavior
Что из себя представляет BottomSheetBehavior в двух словах — java класс, наследуемый от CoordinatorLayout.Behavior, предоставляющий инструкции по поведению дочерней вьюхи внутри родительской (CoordinatorLayout). Чтобы разобраться, что и как вызывается, было бы неплохо залогировать все методы этого класса. Как это сделать? Наследуем от BottomSheetBehavior собственный класс TestBehavior, переопределяем, все методы, оставляя внутри логи и вызовы методов супер-класса. Подставляем в RecyclerView новое поведение.
Запускаем и смотрим логи. В рабочем item-е приходят следующие события: onInterceptTouchEvent, onStartNestedScroll, onNestedPreScroll, onNestedScroll, onStopNestedScroll. В нерабочем: onInterceptTouchEvent, onStartNestedScroll, onNestedPreScroll, onStopNestedScroll. Судя по всему, именно в методе onNestedPreScroll происходит заглушение событий. Переходим к методу onNestedPreScroll в классе BottomSheetBehavior и ставим точку остановки, запускаем отладчик, и видим, что условие при котором происходит выход из метода:
Итак, что за переменная nestedScrollingChildRef? Слабая ссылка на дочерний View, соответственно в условии осуществляется проверка на соответствие View, которым управляет CoordinatorLayout и View, для которого поступило событие.
Смотрим инициализацию и понимаем, что объект инициализируется только при единственном вызове метода onLayoutChild:
И тут вспоминаем про Recycler, про то, что при каждом пролистывании обновляется просматриваемый item, следственно отображается другое View, а ссылка внутри BottomSheetBehavior, все ещё ссылается на первую вьюху, именно поэтому, когда первая View переиспользуется, то скролл происходит, когда используется другая — нет. Причина найдена, пробуем исправить проблему.
Исправление
Что нужно, чтобы всё начало работать? Нужно как-то обновлять ссылку на текущую view в Recycler-е, например сделать метод, который будет обновлять значение ссылки, но так как BottomSheetBehavior это класс, который недоступен для редактирования и поле является приватным, у нас есть два выбора: написать собственную реализацию этого класса (например: скопировав полностью класс, добавив свой метод) или использовать Java Reflection API. Оба варианта выглядят неочень, но выкручиваться надо.
Java Reflection API
Решение с Java Reflection API, через того же наследника TestBeahvior, получаем приватное поле в Runtime и меняем его значение, единственное, что надо учесть, менять поле лучше всего в методе onStartNestedScroll, перед проверкой в onNestedPreScroll.
Копипаст
Тут всё просто, копируем класс BottomSheetBehavior, дописываем private метод refreshNestedScrollingChildRef, который будет обновлять содержимое объекта ссылки, вызываем этот метод в методе onStartNestedScroll. Готово, всё работает.
Заключение
Разработчики системы Android предоставляют множество классных и хорошо работающих инструментов, однако это не значит, что все они будут работать идеально. Всегда можно найти кейс при котором, стандартный инструмент не будет работать, как и в моём случае.
Источник
Android fillViewport property of Scroll View | Tips Tricks
We will work with Android ScrollView’s fillViewport property in this tutorial.
In this post, I will cover the important information regarding scrollview’s fillViewport property.
I tried to get some view from android developer’s official site but they have written just one line : “Defines whether the scrollview should stretch its content to fill the viewport.”
Obviously I did not get enough idea from above single line about what actually fillViewport does for scroll view.
So I did a lot of research on internet to dig more information about this mysterious topic.
Main aim of this property is to take care about User Interaction when the total height of scrollview is less than the height of the device’s screen size.
Let us understand this with the help of the example.
So create a new project in the android studio.
Step 1. Insert long string
In the res->values->strings.xml file, add the below line
Above is the long text which will give plenty of height to our scroll view.
Now, full source code for strings.xml file is looking like the below file
First string holds the app name.
Second is the short text and third one is the long text. We will see the main usage of the fillViewport with the help of the short and long text.
Step 2. Main XML file
In the activity_main.xml file, add the below source code
The root tag of this layout is the linearlayout.
Then it’s child is the scroll view. Here, our main code starts.
Inside this scroll view, there is one child which is linearlayout with vertical orientation.
First child of this linearlayout is the text view. This textview will contain small or long texts.
Now above code will make below preview with
long text and fillViewport = “false”
Now in the code of activity_main.xml file, set the fillViewport = true.
So now we have below case
long text and fillViewport = “true”
From the above two cases, we can conclude that if the height of the scroll view is more than the height of the device’s screen size then the value of fillViewport in the scroll view does not matter.
So we are left with two cases. 1. short text with fillViewport = false and 2. short text with fillViewport = true
Now in the source code of activity_main.xml file, make two changes.
Change the text from long text to short text and set the fillViewport as false.
Now the code block of activity_main.xml file is as the below
So our new case as :
short text and fillViewport = “false”
Output for this case is as the following image
Here is the problem exists. We have scroll view with match_parent height but still, two buttons which want to keep at the bottom, are right below the small text.
It does mean that the height of scroll view is considered as wrap_content even if the value of android:layout_height is match_parent.
fillViewport will help us in this situation. In the code of activity_main.xml file, just change the fillViewport to true.
So the case is now
short text and fillViewport = “true”
See the following image which is the output of above case
And you can see the magic of fillViewport in the above image. Height of the scroll view is now full and both the buttons are at the bottom.
One important thing
Look at the below code
Here is the linearlayout with horizontal orientation.
Two buttons are the children of this linearlayout. We want these two buttons at the bottom.
So make sure below things to use fillViewport properly.
- android:layout_height for linearlayout must be “match_parent“
- android:gravity in linearlayout must be “bottom“
- fillviewport in must be “true“
Thus, we have seen that fillView port allows scroll view to extend it’s height equals to the full height of device screen’s height in the cases when the child of scroll view has less height.
Источник