Добавление разделителя информации о пользователе и списка твитов
Вначале вспомним, как должен выглядеть блок информации о пользователе.
Видим, что нам необходимо добавить 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 (рус. переиспользующий компонент) представляет из себя компонент для отображения элементов списка. Как раз он и нужен нам для того, чтобы отобразить список твитов пользователя.
Напомним, что в 7 уроке мы уже создали с вами файл tweet_item_view. В этом занятии мы просто свяжем его с java кодом, чтобы динамически добавлять во View информацию.
Данная тема довольна сложна для понимая с первого раза, поэтому помимо пошагового разбора работы с RecyclerView в данном уроке рекомендуем ознакомиться с полезными материалами прикреплёнными в завершении урока для закрепления материала.
Вначале надо добавить строку implementation 'com.android.support:recyclerview-v7:26.1.0' в файл build.gradle, чтобы иметь доступ в коде к RecyclerView.
build.gradle(Module:app)
dependencies {
// остальные элементы выше не изменились
implementation 'com.android.support:recyclerview-v7:26.1.0'
}
Далее добавим наш RecyclerView в layout файл. Не будем добавлять никаких зависимостей от соседних элементов, т.к. его родителем является LinearLayout. Просто поместим его после нашей View разделителя. И свяжем его с элементом в java коде.
activity_user_info.xml
<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.RecyclerView
android:id="@+id/tweets_recycler_view"android:layout_width="match_parent"android:layout_height="match_parent"/>
</LinearLayout>
UserInfoActivity.java
publicclassUserInfoActivityextendsAppCompatActivity {
// остальные поля не изменилисьprivateRecyclerView tweetsRecyclerView;
@OverrideprotectedvoidonCreate(BundlesavedInstanceState) {
// остальной код выше не изменился
initRecyclerView();
}
privatevoidinitRecyclerView() {
tweetsRecyclerView = findViewById(R.id.tweets_recycler_view);
}
}
В Activity мы создали отдельный метод initRecyclerView(), который вызываем в методе onCreate().
Для работы с RecyclerView необходим LayoutManager, а также Adapter. LayoutManager отвечает за форму отображения элементов: обычная линейная, в виде сетки, в виде шахматной сетки. В данном случае нам нужен обычный линейный список, поэтому используем LinearLayoutManager:
UserInfoActivity.java
publicclassUserInfoActivityextendsAppCompatActivity {
// остальной код выше не изменилсяprivatevoidinitRecyclerView() {
// остальной код не изменился
tweetsRecyclerView.setLayoutManager(newLinearLayoutManager(this));
}
}
Если вы сейчас запустите приложение, то увидите, что ничего визуально не изменилось. Это потому что список пуст. Чтобы в нём была какая-то информация его надо заполнить элементами. Для этого нам понадобится класс Adapter (переходник) – класс, который отвечает за связь элементов java кода с View-компонентами. Т.е. получая набор java объектов, мы должны подать его на вход в адаптер, который преобразует его уже в набор View-компонентов, которые и использует в дальнейшем RecyclerView.
Давайте создадим новый пакет adapter, где и разместим класс TweetAdapter. Вспоминаем, что надо нажать по пакету colibri.dev.com.colibritweet правой кнопкой и выбрать New -> Package. После этого вводим имя adapter.
После этого в этом пакете создаём новый java класс (New -> Java class), который называем TweetAdapter. Выглядеть результат должен так:
Работа с TweetAdapter
Adapter, используемый при работе с RecyclerView обязан определить объект типа ViewHolder, который предоставляет доступ ко всем View-компонентам в каждой строке списка. Давайте модифицируем наш адаптер, добавив необходимые для RecyclerView зависимости:
TweetAdapter.java
packagecolibri.dev.com.colibritweet.adapter;
importandroid.support.v7.widget.RecyclerView;
importandroid.view.View;
importandroid.widget.ImageView;
importandroid.widget.TextView;
importcolibri.dev.com.colibritweet.R;
// Унаследовали наш адаптер от RecyclerView.Adapter// Здесь же указали наш собственный ViewHolder, который предоставит нам доступ к View-компонентамpublicclassTweetAdapterextendsRecyclerView.Adapter<TweetAdapter.TweetViewHolder> {
// Предоставляет прямую ссылку на каждый View-компонент// Используется для кэширования View-компонентов и последующего быстрого доступа к нимclassTweetViewHolderextendsRecyclerView.ViewHolder {
// Ваш ViewHolder должен содержать переменные для всех// View-компонентов, которым вы хотите задавать какие-либо свойства// в процессе работы пользователя со спискомprivateImageView userImageView;
privateTextView nameTextView;
privateTextView nickTextView;
privateTextView creationDateTextView;
privateTextView contentTextView;
privateImageView tweetImageView;
privateTextView retweetsTextView;
privateTextView likesTextView;
// Мы также создали конструктор, который принимает на вход View-компонент строкИ// и ищет все дочерние компонентыpublicTweetViewHolder(ViewitemView) {
super(itemView);
userImageView = itemView.findViewById(R.id.profile_image_view);
nameTextView = itemView.findViewById(R.id.author_name_text_view);
nickTextView = itemView.findViewById(R.id.author_nick_text_view);
creationDateTextView = itemView.findViewById(R.id.creation_date_text_view);
contentTextView = itemView.findViewById(R.id.tweet_content_text_view);
tweetImageView = itemView.findViewById(R.id.tweet_image_view);
retweetsTextView = itemView.findViewById(R.id.retweets_text_view);
likesTextView = itemView.findViewById(R.id.likes_text_view);
}
}
}
Теперь, когда мы определили базовый адаптер и ViewHolder, нам нужно заполнить наш адаптер. Сначала давайте создадим переменную для списка твитов:
TweetAdapter.java
packagecolibri.dev.com.colibritweet.adapter;
importjava.util.ArrayList;
importjava.util.List;
importcolibri.dev.com.colibritweet.pojo.Tweet;
publicclassTweetAdapterextendsRecyclerView.Adapter<TweetAdapter.TweetViewHolder> {
privateList<Tweet> tweetList =newArrayList<>();
// остальной код не изменился
}
Для того, чтобы работа с нашим адаптером была гибкой, нам нужны какие-то рычаги для управления наполнением адаптера. Давайте добавим два метода для этого. Один будет служить для наполнения коллекции, другой – для очистки, если нам понадобится обновить информацию на экране:
TweetAdapter.java
importjava.util.Collection;
publicclassTweetAdapterextendsRecyclerView.Adapter<TweetAdapter.TweetViewHolder> {
// остальной код выше не изменилсяpublicvoidsetItems(Collection<Tweet>tweets) {
tweetList.addAll(tweets);
notifyDataSetChanged();
}
publicvoidclearItems() {
tweetList.clear();
notifyDataSetChanged();
}
// остальной код ниже не изменился
}
Оба этих метода содержат в себе вызов одного и того же метода notifyDataSetChanged(). Он предназначен для того, чтобы дать адаптеру знать, что список элементов изменился и ему нужно позаботиться о том, чтобы перерисовать элементы на экране.
Каждый адаптер должен реализовывать 3 главных метода:
onCreateViewHolder(ViewGroup parent, int viewType) – метод вызывается для создания объекта ViewHolder, в конструктор которого необходимо передать созданный View-компонент, с которым в дальнейшем будут связываться java объекты. Метод вызывается без нашего личного вмешательства, т.к. RecyclerView в себе инкапсулирует логику переиспользования элементов;
onBindViewHolder(TweetViewHolder holder, int position) – этот метод отвечает за связь java объекта и View. Метод также вызывается без нашего участия. Он будет вызываться чаще, чем метод onCreateViewHolder из-за того, что View компоненты будут переиспользоваться и в один и тот же визуальный элемент в процессе жизнедеятельности списка будут устанавливаться разные данные;
getItemCount() – сообщает количество элементов в списке.
Давайте реализуем их:
TweetAdapter.java
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
publicclassTweetAdapterextendsRecyclerView.Adapter<TweetAdapter.TweetViewHolder> {
// остальной код выше не изменился@OverridepublicTweetViewHolderonCreateViewHolder(ViewGroupparent, intviewType) {
View view =LayoutInflater.from(parent.getContext())
.inflate(R.layout.tweet_item_view, parent, false);
returnnewTweetViewHolder(view);
}
@OverridepublicvoidonBindViewHolder(TweetViewHolderholder, intposition) {
}
@OverridepublicintgetItemCount() {
return tweetList.size();
}
// остальной код ниже не изменился
}
Отлично, теперь осталось добавить код, который будет заполнять данными наши View-компоненты в TweetViewHolder:
Всё, что делает данный код – используя ранее созданные View-компоненты, он заполняет их новыми данными. Таким образом происходит переиспользование View-компонентов без лишнего создания новых. Именно с этим связана следующая строка кода метода bind нашего TweetViewHolder:
Если бы переиспользования элементов не было, то достаточно было бы написать строку:
TweetAdapter.java
if (tweetPhotoUrl ==null) {
tweetImageView.setVisibility(GONE);
}
Но из-за того, что наши элементы переиспользуются, то один раз скрыв изображение на одном элементе, оно будет скрыто на нём и при повторном использовании. Даже в том случае, если оно должно быть показано. Это нас не устраивает.
Также давайте подробнее остановимся на методе форматирования даты, использующегося при заполнении данными нашего компонента:
TweetAdapter.java
publicclassTweetAdapterextendsRecyclerView.Adapter<TweetAdapter.TweetViewHolder> {
privatestaticfinalStringTWITTER_RESPONSE_FORMAT="EEE MMM dd HH:mm:ss ZZZZZ yyyy"; // Thu Oct 26 07:31:08 +0000 2017privatestaticfinalStringMONTH_DAY_FORMAT="MMM d"; // Oct 26// остальной код выше не изменилсяprivateStringgetFormattedDate(StringrawDate) {
SimpleDateFormat utcFormat =newSimpleDateFormat(TWITTER_RESPONSE_FORMAT, Locale.ROOT);
SimpleDateFormat displayedFormat =newSimpleDateFormat(MONTH_DAY_FORMAT, Locale.getDefault());
try {
Date date = utcFormat.parse(rawDate);
return displayedFormat.format(date);
} catch (ParseException e) {
thrownewRuntimeException(e);
}
}
}
Т.к. различные сервисы предоставляют дату в самых различных форматах, то возникает необходимость создавать механизмы, которые бы преобразовывали дату одного формата в формат необходимый нам.
Константа TWITTER_RESPONSE_FORMAT представляет собой формат, в котором мы получим дату от Twitter-сервиса. Такую дату отображать пользователю было бы странно. Нас больше устроил бы более простой вариант MONTH_DAY_FORMAT, когда мы отображаем только месяц и день.
Метод String getFormattedDate(String rawDate) принимает на вход дату в формате, в котором мы получим её от сервиса. На выходе же мы получаем готовую к использованию строку даты.
Для начала нам необходимо из строк шаблонов создать java объекты SimpleDateFormat, при помощи которых мы и будем производить преобразования. Т.к. напрямую строку даты одного формата в строку другого формата преобразовать мы не можем, то нам нужен переходный элемент:
TweetAdapter.java
Date date = utcFormat.parse(rawDate);
Данной операцией мы преобразовали строку, пришедшую нам от сервиса Twitter, в соответствии с шаблоном к обычном java объекту Date, которым теперь можно манипулировать каким угодно образом. Нам остаётся просто отформатировать вновь созданный объект даты в соответствии с необходимым нам шаблоном:
TweetAdapter.java
return clientFormat.format(date);
Добавление TweetAdapter к списку RecyclerView
Вот и всё, теперь осталось только сказать нашему RecyclerView, что за наполнение элементов в нём отвечает созданный нами адаптер. Достаточно добавить следующий код в UserInfoActivity:
UserInfoActivity.java
publicclassUserInfoActivityextendsAppCompatActivity {
// остальной код выше не изменилсяprivateTweetAdapter tweetAdapter;
privatevoidinitRecyclerView() {
// остальной код выше не изменился
tweetAdapter =newTweetAdapter();
tweetsRecyclerView.setAdapter(tweetAdapter);
}
}
Теперь давайте убедимся, что наш список действительно работает. Для этого:
создадим метод Collection<Tweet> getTweets(), который будет возвращать нам коллекцию объектов-заглушек.
создадим метод loadTweets(), где вызовём метод getTweets() и отобразим результат в экземпляре TweetAdapter.
UserInfoActivity.java
publicclassUserInfoActivityextendsAppCompatActivity {
@OverrideprotectedvoidonCreate(BundlesavedInstanceState) {
// остальной код выше не изменился
loadTweets();
}
privatevoidloadTweets() {
Collection<Tweet> tweets = getTweets();
tweetAdapter.setItems(tweets);
}
privateCollection<Tweet>getTweets() {
returnArrays.asList(
newTweet(getUser(), 1L, "Thu Dec 13 07:31:08 +0000 2017", "Очень длинное описание твита 1",
4L, 4L, "https://www.w3schools.com/w3css/img_fjords.jpg"),
newTweet(getUser(), 2L, "Thu Dec 12 07:31:08 +0000 2017", "Очень длинное описание твита 2",
5L, 5L, "https://www.w3schools.com/w3images/lights.jpg"),
newTweet(getUser(), 3L, "Thu Dec 11 07:31:08 +0000 2017", "Очень длинное описание твита 3",
6L, 6L, "https://www.w3schools.com/css/img_mountains.jpg")
);
}
}
Теперь давайте запустим наше приложение и посмотрим, что получилось.
Отличная работа. Подытожим всё вышесказанное схемой, которая используется в официальной документации для отображения принципа работы c RecyclerView:
В этой схеме чётко разграничены 3 слоя: View-слой – это непосредственно сам RecyclerView, а также его «помощник» LayoutManager, который отвечает за форму отображения элементов. Dataset – это данные, которые необходимо отобразить в списке. Могут быть представлены в любом виде: как в нашем случае – методом-заглушкой или реально запрашиваемыми с сервера данными, как мы это сделаем в следующих уроках.
* Adapter представляет собой натуральный переходник между сырыми данными и пользовательским интерфейсом, готовым к их отображению.
Вот и всё! Сегодня мы с вами приблизились к пониманию очень непростой темы.
По итогу этого урока мы полностью подготовили наш экран списка твитов для их просмотра. Осталось только получить их.
На всякий случай отобразим полный листинг кода UserInfoActivity и TweetAdapter:
Сайт использует cookie-файлы для того, чтобы вам было удобнее им пользоваться. Для
продолжения работы с сайтом, вам необходимо принять использование cookie-файлов.
Код начала урока:
gitzip
Структура урока:
Добавление разделителя информации о пользователе и списка твитов
Вначале вспомним, как должен выглядеть блок информации о пользователе.
Видим, что нам необходимо добавить
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
(рус. переиспользующий компонент) представляет из себя компонент для отображения элементов списка. Как раз он и нужен нам для того, чтобы отобразить список твитов пользователя.Напомним, что в 7 уроке мы уже создали с вами файл
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()
.Для работы с
RecyclerView
необходимLayoutManager
, а такжеAdapter
.LayoutManager
отвечает за форму отображения элементов: обычная линейная, в виде сетки, в виде шахматной сетки. В данном случае нам нужен обычный линейный список, поэтому используемLinearLayoutManager
:UserInfoActivity.java
Если вы сейчас запустите приложение, то увидите, что ничего визуально не изменилось. Это потому что список пуст. Чтобы в нём была какая-то информация его надо заполнить элементами. Для этого нам понадобится класс
Adapter
(переходник) – класс, который отвечает за связь элементовjava
кода сView
-компонентами. Т.е. получая наборjava
объектов, мы должны подать его на вход в адаптер, который преобразует его уже в наборView
-компонентов, которые и использует в дальнейшемRecyclerView
.Давайте создадим новый пакет
adapter
, где и разместим классTweetAdapter
. Вспоминаем, что надо нажать по пакетуcolibri.dev.com.colibritweet
правой кнопкой и выбратьNew -> Package
. После этого вводим имяadapter
.После этого в этом пакете создаём новый
java
класс (New -> Java class
), который называемTweetAdapter
. Выглядеть результат должен так:Работа с TweetAdapter
Adapter
, используемый при работе сRecyclerView
обязан определить объект типаViewHolder
, который предоставляет доступ ко всемView
-компонентам в каждой строке списка. Давайте модифицируем наш адаптер, добавив необходимые дляRecyclerView
зависимости:TweetAdapter.java
Теперь, когда мы определили базовый адаптер и
ViewHolder
, нам нужно заполнить наш адаптер. Сначала давайте создадим переменную для списка твитов:TweetAdapter.java
Для того, чтобы работа с нашим адаптером была гибкой, нам нужны какие-то рычаги для управления наполнением адаптера. Давайте добавим два метода для этого. Один будет служить для наполнения коллекции, другой – для очистки, если нам понадобится обновить информацию на экране:
TweetAdapter.java
Оба этих метода содержат в себе вызов одного и того же метода
notifyDataSetChanged()
. Он предназначен для того, чтобы дать адаптеру знать, что список элементов изменился и ему нужно позаботиться о том, чтобы перерисовать элементы на экране.Каждый адаптер должен реализовывать 3 главных метода:
onCreateViewHolder(ViewGroup parent, int viewType)
– метод вызывается для создания объектаViewHolder
, в конструктор которого необходимо передать созданныйView
-компонент, с которым в дальнейшем будут связыватьсяjava
объекты. Метод вызывается без нашего личного вмешательства, т.к.RecyclerView
в себе инкапсулирует логику переиспользования элементов;onBindViewHolder(TweetViewHolder holder, int position)
– этот метод отвечает за связьjava
объекта иView
. Метод также вызывается без нашего участия. Он будет вызываться чаще, чем методonCreateViewHolder
из-за того, чтоView
компоненты будут переиспользоваться и в один и тот же визуальный элемент в процессе жизнедеятельности списка будут устанавливаться разные данные;getItemCount()
– сообщает количество элементов в списке.Давайте реализуем их:
TweetAdapter.java
Отлично, теперь осталось добавить код, который будет заполнять данными наши
View
-компоненты вTweetViewHolder
:TweetAdapter.java
Всё, что делает данный код – используя ранее созданные
View
-компоненты, он заполняет их новыми данными. Таким образом происходит переиспользованиеView
-компонентов без лишнего создания новых. Именно с этим связана следующая строка кода методаbind
нашегоTweetViewHolder
:TweetAdapter.java
Если бы переиспользования элементов не было, то достаточно было бы написать строку:
TweetAdapter.java
Но из-за того, что наши элементы переиспользуются, то один раз скрыв изображение на одном элементе, оно будет скрыто на нём и при повторном использовании. Даже в том случае, если оно должно быть показано. Это нас не устраивает.
Также давайте подробнее остановимся на методе форматирования даты, использующегося при заполнении данными нашего компонента:
TweetAdapter.java
Т.к. различные сервисы предоставляют дату в самых различных форматах, то возникает необходимость создавать механизмы, которые бы преобразовывали дату одного формата в формат необходимый нам.
Константа
TWITTER_RESPONSE_FORMAT
представляет собой формат, в котором мы получим дату от Twitter-сервиса. Такую дату отображать пользователю было бы странно. Нас больше устроил бы более простой вариантMONTH_DAY_FORMAT
, когда мы отображаем только месяц и день.Метод
String getFormattedDate(String rawDate)
принимает на вход дату в формате, в котором мы получим её от сервиса. На выходе же мы получаем готовую к использованию строку даты.Для начала нам необходимо из строк шаблонов создать
java
объектыSimpleDateFormat
, при помощи которых мы и будем производить преобразования. Т.к. напрямую строку даты одного формата в строку другого формата преобразовать мы не можем, то нам нужен переходный элемент:TweetAdapter.java
Данной операцией мы преобразовали строку, пришедшую нам от сервиса Twitter, в соответствии с шаблоном к обычном
java
объектуDate
, которым теперь можно манипулировать каким угодно образом. Нам остаётся просто отформатировать вновь созданный объект даты в соответствии с необходимым нам шаблоном:TweetAdapter.java
Добавление TweetAdapter к списку RecyclerView
Вот и всё, теперь осталось только сказать нашему
RecyclerView
, что за наполнение элементов в нём отвечает созданный нами адаптер. Достаточно добавить следующий код вUserInfoActivity
:UserInfoActivity.java
Теперь давайте убедимся, что наш список действительно работает. Для этого:
Collection<Tweet> getTweets()
, который будет возвращать нам коллекцию объектов-заглушек.loadTweets()
, где вызовём методgetTweets()
и отобразим результат в экземпляреTweetAdapter
.UserInfoActivity.java
Теперь давайте запустим наше приложение и посмотрим, что получилось.
Отличная работа. Подытожим всё вышесказанное схемой, которая используется в официальной документации для отображения принципа работы c
RecyclerView
:В этой схеме чётко разграничены 3 слоя:
View
-слой – это непосредственно самRecyclerView
, а также его «помощник»LayoutManager
, который отвечает за форму отображения элементов.Dataset
– это данные, которые необходимо отобразить в списке. Могут быть представлены в любом виде: как в нашем случае – методом-заглушкой или реально запрашиваемыми с сервера данными, как мы это сделаем в следующих уроках.*
Adapter
представляет собой натуральный переходник между сырыми данными и пользовательским интерфейсом, готовым к их отображению.Вот и всё! Сегодня мы с вами приблизились к пониманию очень непростой темы.
По итогу этого урока мы полностью подготовили наш экран списка твитов для их просмотра. Осталось только получить их.
На всякий случай отобразим полный листинг кода
UserInfoActivity
иTweetAdapter
:UserInfoActivity.java
TweetAdapter.java
Полезные материалы:
Полный листинг изменений кода:
Code diff