Урок 10. Работа с RecyclerView на примере TweetsRecyclerView
Добавление разделителя информации о пользователе и списка твитов
Вначале вспомним, как должен выглядеть блок информации о пользователе.
Видим, что нам необходимо добавить View серого цвета после информации о пользователе и разместить под ней список RecyclerView. Мы бы могли поместить эту Viewпросто под последним TextView, но View должна занимать всю ширину. Это трудно сделать потому что у RelativeLayout установлено свойтво padding, которое делает отступ перед всеми вложенными элементами. Поэтому сделаем LinearLayout корневым элементом нашего layout, в свою очередь у него будет 3 вложенных элемента:
RelativeLayout с информацией о пользователе. Тот контейнер, который до этого момента являлся корневым.
View – разделитель информации.
RecyclerView – список твитов, который мы добавим чуть позже в этом уроке.
Наш layout после этих преобразований выглядит так:
Видим, что наш RelativeLayout практически не изменился. Единственное что мы изменили – поменяли значения атрибута android:layout_height c match_parent на wrap_content. Это необходимо, чтобы нашему разделителю хватило места на экране.
Мы просто сделали корневым элементом LinearLayout и добавили нашу View в качестве элемента, который находится под RelativeLayout.
Знакомство с элементом RecyclerView
RecyclerView представляет из себя элемент для отображения списка элементов. Как раз он и нужен нам для того, чтобы отобразить список твитов пользователя.
Напомним, что в одном из предыдущих практических занятий мы уже создали с вами tweet_item_view файл. В этом занятии мы просто свяжем его с java кодом, чтобы динамически добавлять в View информацию.
Подробный разбор возможностей RecyclerView уже был переведён в нашем блоге, поэтому рекомендую ознакомиться здесь.
Вначале надо добавить строку implementation 'com.android.support:recyclerview-v7:26.1.0' в наш build.gradle файле, чтобы иметь доступ в коде к RecyclerView.
build.gradle(Module:app)
1
2
3
4
dependencies {// остальные элементы выше не изменились
implementation 'com.android.support:recyclerview-v7:26.1.0'}
Далее добавим наш RecyclerView в layout файл. Не будем добавлять никаких зависимостей от соседних элементов, т.к. его родителем является LinearLayout. Просто поместим его после нашей View разделителя. И свяжем его с элементом в java коде.
activity_user_info.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<LinearLayout><!-- Остальные элементы сверху не изменились--><Viewandroid:id="@+id/delimeter_view"android:layout_width="match_parent"android:layout_height="6dp"android:layout_marginTop="@dimen/text_small_margin"android:background="@color/gray_mercury"/><android.support.v7.widget.RecyclerViewandroid:id="@+id/tweets_recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
UserInfoActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassUserInfoActivityextends AppCompatActivity {// остальные поля не изменилисьprivate RecyclerView tweetsRecyclerView;
@Override
protectedvoidonCreate(Bundle savedInstanceState){// остальной код выше не изменился
initRecyclerView();
loadUserInfo();}privatevoidinitRecyclerView(){
tweetsRecyclerView = findViewById(R.id.tweets_recycler_view);
tweetsRecyclerView.setLayoutManager(new LinearLayoutManager(this));}}
В Activity мы создали отдельный метод initRecyclerView(), который вызываем в методе onCreate(). LayoutManager отвечает за форму отображения элементов. В данном случае нам нужен обычный список, поэтому используем LinearLayoutManager(this).
Если вы сейчас запустите приложение, то увидите, что ничего визуально не изменилось. Это потому что список пуст. Чтобы в нём была какая-то информация его надо заполнить элементами. Для этого нам понадобится класс Adapter (переходник) – класс, который отвечает за связь элементов java кода с View-компонентами. Т.е. получая набор javaобъектов, мы должны подать его на вход в адаптер, который преобразует его уже в набор View-компонентов, которые и использует в дальнейшем RecyclerView.
Давайте создадим новый пакет adapter, где и разместим класс TweetAdapter. Вспоминаем, что надо нажать по пакету colibri.dev.com.colibritweet правой кнопкой и выбрать New -> Package. После этого вводим имя adapter.
После этого в этом пакете создаём новый java класс (New -> Java class), который называем TweetAdapter. Выглядеть резульат должен так:
А теперь подробно рассмотрим, что в этом классе и для чего предназначено.
Класс адаптера содержит поле tweetList – коллекцию java объектов, которые необходимо отобразить в списке. Для управления содержанием данной коллекции служат два метода:
setItems(Collection<Tweet> tweets) – для наполения коллекции необходимым содержимым;
clearItems() – для очистки коллекции (при необходимости обновить данные на экране).
Оба этих метода сожержат в себе вызов одного и того же метода notifyDataSetChanged(). Он преднозначен для того, чтобы дать адаптеру знать, что список элементов изменился и ему нужно позаботиться о том, чтобы перерисовать элементы на экране.
Главным связующим звеном между java объектами и View вляется внутренний класс TweetViewHolder. Имея доступ к View-компоненту, он принимает на вход java объект Tweet и непосредственно связывает их друг с другом, сопоставляя поля объекта с отображаемыми пользователю полями в методе bind(Tweet tweet).
Главным интерфейсом адаптеров, связываемых с RecyclerView, являюются два метода:
onCreateViewHolder(ViewGroup parent, int viewType) – метод вызывается для создания объекта ViewHolder, в конструктор которого необходимо передать View, с которой в дальнейщем будут связываться java объекты. Метод вызывается без нашего личного вмешательства, т.к. RecyclerView в себе инкапсулирует логику переиспользования элементов;
onBindViewHolder(TweetViewHolder holder, int position) – этот метод отвечает за связь java объекта и View. Метод также вызывается без нашего участия. Он будет вызываться чаще, чем метод onCreateViewHolder из-за того, что View компоненты будут переиспользоваться и в один и тот же визуальный элемент в процессе жизнедеятельности списка будут устанавливаться разные данные. Именно с этим связана следующая строка кода метода bind нашего TweetViewHolder:
Но из-за того, что наши элементы переиспользуются, то один раз скрыв изображение на одном элементе, оно будет скрыто на нём и при повторном использовании. Даже в том случае, если оно должно быть показано. Это нас не устраивает.
Также подробно необходимо остановиться на методе форматирования даты, использующегося при заполнении данными нашего компонента.
TweetAdapter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
publicclassTweetAdapterextends RecyclerView.Adapter<TweetAdapter.TweetViewHolder>{privatestaticfinal String TWITTER_RESPONSE_FORMAT="EEE MMM dd HH:mm:ss ZZZZZ yyyy";// Thu Oct 26 07:31:08 +0000 2017privatestaticfinal String MONTH_DAY_FORMAT ="MMM d";// Oct 26// остальной код выше не изменилсяprivate String getFormattedDate(String rawDate){
SimpleDateFormat utcFormat =new SimpleDateFormat(TWITTER_RESPONSE_FORMAT, Locale.ROOT);
SimpleDateFormat displayedFormat =new SimpleDateFormat(MONTH_DAY_FORMAT, Locale.getDefault());try{
Date date = utcFormat.parse(rawDate);return displayedFormat.format(date);}catch(ParseException e){thrownewRuntimeException(e);}}}
Т.к. различные сервисы предоставляют дату в самых различных форматах, то возникает необходимость создавать механизмы, которые бы преобразовывали дату одного формата в формат необходимый нам. Для этих целей у нас служит класс utils/DateFormatter:
Константа TWITTER_RESPONSE_FORMAT представляет собой формат, в котором мы получим дату от Twitter-сервиса. Такую дату отображать пользователю было бы странно. Нас больше устроил бы более простой вариант MONTH_DAY_FORMAT, когда мы отображаем только месяц и день.
Метод String getFormattedDate(String rawDate) принимает на вход дату в формате, в котором мы получим её от сервиса. На выходе же мы получаем готовую к использованию строку даты.
Для начала нам необходимо из строк шаблонов создать java объекты SimpleDateFormat, при помощи которых мы и будем производить преобразования. Т.к. напрямую строку даты одного формата в строку другого формата преобразовать мы не можем, то нам нужен переходный элемент:
TweetAdapter.java
1
Date date = utcFormat.parse(rawDate);
Данной операцией мы преобразовали строку, пришедшую нам от сервиса Twitter, в соответствии с шаблоном к обычном java объекту Date, которым теперь можно манипулировать каким угодно образом. Нам остаётся просто отформатировать вновь созданный объект даты в соответствии с необходимым нам шаблоном:
TweetAdapter.java
1
return clientFormat.format(date);
Добавление TweetAdapter к списку RecyclerView
Вот и всё, теперь осталось только сказать нашему RecyclerView, что за наполнение элементов в нём отвечает созданный нами адаптер. Достаточно добавить следующий код в UserInfoActivity:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
publicclassUserInfoActivityextends AppCompatActivity {// остальной код выше не изменилсяprivate TweetAdapter tweetAdapter;privatevoidinitRecyclerView(){// остальной код выше не изменился
tweetAdapter =new TweetAdapter();
mRecyclerView.setAdapter(tweetAdapter);}privatevoiddisplayTweets(Collection<Tweet> tweets){
mTweetAdapter.setItems(tweets);}}
Теперь давайте убедимся, что наш список действительно работает. Для этого:
создадим метод Collection<Tweet> getTweets, который будет возвращать нам коллекцию объектов заглушек.
создадим метод loadTweets(), где вызовём метод getTweets() и отобразим результат в экзмпляре TweetAdapter.
Сайт использует cookie-файлы для того, чтобы вам было удобнее им пользоваться. Для
продолжения работы с сайтом, вам необходимо принять использование cookie-файлов.
Добавление разделителя информации о пользователе и списка твитов
Вначале вспомним, как должен выглядеть блок информации о пользователе.
Видим, что нам необходимо добавить
View
серого цвета после информации о пользователе и разместить под ней списокRecyclerView
. Мы бы могли поместить этуView
просто под последнимTextView
, ноView
должна занимать всю ширину. Это трудно сделать потому что уRelativeLayout
установлено свойтвоpadding
, которое делает отступ перед всеми вложенными элементами. Поэтому сделаемLinearLayout
корневым элементом нашегоlayout
, в свою очередь у него будет 3 вложенных элемента:RelativeLayout
с информацией о пользователе. Тот контейнер, который до этого момента являлся корневым.View
– разделитель информации.RecyclerView
– список твитов, который мы добавим чуть позже в этом уроке.Наш
layout
после этих преобразований выглядит так:activity_user_info.xml
Видим, что наш
RelativeLayout
практически не изменился. Единственное что мы изменили – поменяли значения атрибутаandroid:layout_height
cmatch_parent
наwrap_content
. Это необходимо, чтобы нашему разделителю хватило места на экране.Мы просто сделали корневым элементом
LinearLayout
и добавили нашуView
в качестве элемента, который находится подRelativeLayout
.Знакомство с элементом
RecyclerView
RecyclerView
представляет из себя элемент для отображения списка элементов. Как раз он и нужен нам для того, чтобы отобразить список твитов пользователя.Напомним, что в одном из предыдущих практических занятий мы уже создали с вами
tweet_item_view
файл. В этом занятии мы просто свяжем его сjava
кодом, чтобы динамически добавлять вView
информацию.Подробный разбор возможностей
RecyclerView
уже был переведён в нашем блоге, поэтому рекомендую ознакомиться здесь.Вначале надо добавить строку
implementation 'com.android.support:recyclerview-v7:26.1.0'
в нашbuild.gradle
файле, чтобы иметь доступ в коде кRecyclerView
.build.gradle(Module:app)
Далее добавим наш
RecyclerView
вlayout
файл. Не будем добавлять никаких зависимостей от соседних элементов, т.к. его родителем являетсяLinearLayout
. Просто поместим его после нашейView
разделителя. И свяжем его с элементом вjava
коде.activity_user_info.xml
UserInfoActivity.java
В
Activity
мы создали отдельный методinitRecyclerView()
, который вызываем в методеonCreate()
.LayoutManager
отвечает за форму отображения элементов. В данном случае нам нужен обычный список, поэтому используемLinearLayoutManager(this)
.Если вы сейчас запустите приложение, то увидите, что ничего визуально не изменилось. Это потому что список пуст. Чтобы в нём была какая-то информация его надо заполнить элементами. Для этого нам понадобится класс
Adapter
(переходник) – класс, который отвечает за связь элементовjava
кода сView
-компонентами. Т.е. получая наборjava
объектов, мы должны подать его на вход в адаптер, который преобразует его уже в наборView
-компонентов, которые и использует в дальнейшемRecyclerView
.Давайте создадим новый пакет
adapter
, где и разместим классTweetAdapter
. Вспоминаем, что надо нажать по пакетуcolibri.dev.com.colibritweet
правой кнопкой и выбратьNew -> Package
. После этого вводим имяadapter
.После этого в этом пакете создаём новый
java
класс (New -> Java class
), который называемTweetAdapter
. Выглядеть резульат должен так:Работа с TweetAdapter
TweetAdapter.java
А теперь подробно рассмотрим, что в этом классе и для чего предназначено.
Класс адаптера содержит поле
tweetList
– коллекциюjava
объектов, которые необходимо отобразить в списке. Для управления содержанием данной коллекции служат два метода:setItems(Collection<Tweet> tweets)
– для наполения коллекции необходимым содержимым;clearItems()
– для очистки коллекции (при необходимости обновить данные на экране).Оба этих метода сожержат в себе вызов одного и того же метода
notifyDataSetChanged()
. Он преднозначен для того, чтобы дать адаптеру знать, что список элементов изменился и ему нужно позаботиться о том, чтобы перерисовать элементы на экране.Главным связующим звеном между
java
объектами иView
вляется внутренний классTweetViewHolder
. Имея доступ кView
-компоненту, он принимает на входjava
объектTweet
и непосредственно связывает их друг с другом, сопоставляя поля объекта с отображаемыми пользователю полями в методеbind(Tweet tweet)
.Главным интерфейсом адаптеров, связываемых с
RecyclerView
, являюются два метода:onCreateViewHolder(ViewGroup parent, int viewType)
– метод вызывается для создания объектаViewHolder
, в конструктор которого необходимо передатьView
, с которой в дальнейщем будут связыватьсяjava
объекты. Метод вызывается без нашего личного вмешательства, т.к.RecyclerView
в себе инкапсулирует логику переиспользования элементов;onBindViewHolder(TweetViewHolder holder, int position)
– этот метод отвечает за связьjava
объекта иView
. Метод также вызывается без нашего участия. Он будет вызываться чаще, чем методonCreateViewHolder
из-за того, чтоView
компоненты будут переиспользоваться и в один и тот же визуальный элемент в процессе жизнедеятельности списка будут устанавливаться разные данные. Именно с этим связана следующая строка кода методаbind
нашегоTweetViewHolder
:TweetAdapter.java
Если бы переиспользования элементов не было, то достаточно было бы написать:
Но из-за того, что наши элементы переиспользуются, то один раз скрыв изображение на одном элементе, оно будет скрыто на нём и при повторном использовании. Даже в том случае, если оно должно быть показано. Это нас не устраивает.
Также подробно необходимо остановиться на методе форматирования даты, использующегося при заполнении данными нашего компонента.
TweetAdapter.java
Т.к. различные сервисы предоставляют дату в самых различных форматах, то возникает необходимость создавать механизмы, которые бы преобразовывали дату одного формата в формат необходимый нам. Для этих целей у нас служит класс
utils/DateFormatter
:Константа
TWITTER_RESPONSE_FORMAT
представляет собой формат, в котором мы получим дату от Twitter-сервиса. Такую дату отображать пользователю было бы странно. Нас больше устроил бы более простой вариантMONTH_DAY_FORMAT
, когда мы отображаем только месяц и день.Метод
String getFormattedDate(String rawDate)
принимает на вход дату в формате, в котором мы получим её от сервиса. На выходе же мы получаем готовую к использованию строку даты.Для начала нам необходимо из строк шаблонов создать
java
объектыSimpleDateFormat
, при помощи которых мы и будем производить преобразования. Т.к. напрямую строку даты одного формата в строку другого формата преобразовать мы не можем, то нам нужен переходный элемент:TweetAdapter.java
Данной операцией мы преобразовали строку, пришедшую нам от сервиса Twitter, в соответствии с шаблоном к обычном
java
объектуDate
, которым теперь можно манипулировать каким угодно образом. Нам остаётся просто отформатировать вновь созданный объект даты в соответствии с необходимым нам шаблоном:TweetAdapter.java
Добавление TweetAdapter к списку RecyclerView
Вот и всё, теперь осталось только сказать нашему
RecyclerView
, что за наполнение элементов в нём отвечает созданный нами адаптер. Достаточно добавить следующий код вUserInfoActivity
:Теперь давайте убедимся, что наш список действительно работает. Для этого:
Collection<Tweet> getTweets
, который будет возвращать нам коллекцию объектов заглушек.loadTweets()
, где вызовём методgetTweets()
и отобразим результат в экзмпляреTweetAdapter
.TweetAdapter
Теперь давайте запустим наше приложение и посмотрим, что получилось.
Поздравляю! Сегодня мы прошли с вами непростую тему.
По иогу этого урока мы полностью подготовили наш экран списка твитов для их просмотра. Осталось только получить их.
На всякий случай отобразим полный листинг кода
UserInfoActivity
.UserInfoActivity.java