Урок 12. Практика. UsersAdapter. Обработка клика по элементу списка

Код начала урока:

gitzip

Структура урока:

Создание SearchUsersActivity

Вначале нам надо создать новую ActivitySearchUsersActivity.

Мы ещё не создавали пакет activity. Давайте создадим его, как мы уже делали в прошлых уроках.

Давайте переместим файл UserInfoActivity в пакет activity. Для этого левой кнопкой нажимаем на UserInfoActivity и перетягиваем в пакет activity.

У вас появится диалог подтверждения перемещения файла, изменения его пакета во всех местах использования:

PackageRefactoring.png

Нажимаем Refactor. Видим, что файл переместился. Двигаемся дальше.

Давайте в пакете activity создадим новый Java файл, который назовём SearchUsersActivity.

После этого давайте создадим layout для нашей Activity. Для этого нажмём правой кнопкой по папке layout и выберем New -> Layout resource file.

NewLayout.png

Введём имя activity_search_users, корневым элементом выберем контейнер FrameLayout. Сразу же добавим элемент RecyclerView в него:

activity_search_users.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/users_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</FrameLayout>

Давайте свяжем наш layout и нашу Activity:

SearchUsersActivity.java

package colibri.dev.com.colibritweet.activity;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import colibri.dev.com.colibritweet.R;

public class SearchUsersActivity extends AppCompatActivity {

    private RecyclerView usersRecyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search_users);
        initRecyclerView();
    }

    private void initRecyclerView() {
        usersRecyclerView = findViewById(R.id.users_recycler_view);
        usersRecyclerView.setLayoutManager(new LinearLayoutManager(this));
    }
}

Также не забываем добавить нашу Activity в AndroidManifest файл:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="colibri.dev.com.colibritweet">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:name=".TwitterApp"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".activity.UserInfoActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name=".activity.SearchUsersActivity"/>
    </application>

</manifest>

Для тестирования давайте сделаем новую Activity SearchUsersActivity стартовой, чтобы мы могли видеть вносимые нами изменения на экране. Для этого в файле AndroidManifest перенесём блок <intent-filter> в новую Activity:

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="colibri.dev.com.colibritweet">

    // остальной код выше не изменился

    <activity android:name=".activity.UserInfoActivity"/>
    <activity android:name=".activity.SearchUsersActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
    </activity>

    // остальной код ниже не изменился

</manifest>

Создание UsersAdapter

В предыдущем уроке мы познакомились с элементом RecyclerView и адаптером, при помощи которого можно отобразить элементы списка. В этом уроке предлагаем вам самостоятельно создать адаптер для списка экрана SearchUsersActivity. Наш вариант реализации приведён ниже.

Для тестирования вашего решения предлагаем создать метод getUsers(), возвращающий объекты-заглушки:

SearchUsersActivity.java

public class SearchUsersActivity extends AppCompatActivity {

    // остальной код выше не изменился

    private Collection<User> getUsers() {
        return Arrays.asList(
                new User(
                        929257819349700608L,
                        "http://i.imgur.com/DvpvklR.png",
                        "DevColibri",
                        "@devcolibri",
                        "Sample description",
                        "USA",
                        42,
                        42
                ),

                new User(
                        44196397L,
                        "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0_400x400.jpg",
                        "Elon Musk",
                        "@elonmusk",
                        "Hat Salesman",
                        "Boring",
                        14,
                        13
                )
        );
    }

    // остальной код ниже не изменился
}

Напоминаем, что мы уже создавали файл user_item_view. Поэтому его и используем в нашем адаптере UsersAdapter.

Обработка клика по элементу списка

Но на этом экране список немного отличается от предыдущего: здесь нам надо обработать нажатие на элемент списка и перейти на экран информации о пользователе.

Теперь о реализации. Механизм прост: физически на экране пользователь видит список из множества View-компонентов. Android обо всём позаботился до нас и всё, что необходимо сделать нам – это использовать готовый интерфейс-слушатель View.OnClickListener, который предоставляет единственный метод onClick(View v). Использовать его довольно просто (помимо представленного здесь материала также можно ознакомиться с этим):

ClickListener sample

myView.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        // необходимое действие
    }
});

Таким образом, установив такой слушатель на View-компонент, мы можем реагировать на события нажатия на него любым необходимым нам способом. Отлично, момент, в который происходит нажатие на компонент, мы перехватываем.

Но теперь нам нужно получить данные пользователя, которые связаны, с этим компонентом. А мы, конечно же, помним, где осуществляется связь между View-компонентами и данными – в адаптере. Т.е. именно в адаптере мы можем узнать раньше всего, что произошло нажатие по какому-либо элементу и что необходимо связать его с реальными данными.

На этом зона ответственности адаптера заканчивается. За то, что делать с информацией, полученной при нажатии на элемент должна отвечать Activity. Поэтому нам необходим «мост», некий интерфейс адаптера, который бы помог нам реагировать на нажатия по элементу списка на более высоком уровне – оперируя данными.

Помните интерфейс, который предоставляет View? С единственным методом, который сообщает нам, какой элемент был нажат. Нам нужен такой же, только сообщать он нам будет не о нажатом View, а о нажатом пользователе:

UsersAdapter.java

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {

    // код выше не изменился

    public interface OnUserClickListener {
        void onUserClick(User user);
    }
}

Отлично, теперь наш адаптер предоставляет интерфейс, который должен гарантировать вызов метода onUserClick(User user) при нажатии на элемент списка. Поэтому Activity смело может использовать его для реакции на данное событие.

Т.е. мы используем обычную реализацию паттерна проектирования наблюдатель.

Создание наблюдателя за кликом в SearchUsersActivity

В данном случае нам необходимо создать внутри SearchUsersActivity наблюдателя за кликом по элементу пользователя. Наблюдатель мы реализуем, создавая новый экземпляр интерфейса OnUserClickListener. Но для начала давайте добавим этот интерфейс в наш SearchUsersAdapter, чтобы мы могли передать слушателя из Activity в Adapter:

UsersAdapter.java

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {

    private OnUserClickListener onUserClickListener;

    public UsersAdapter(OnUserClickListener onUserClickListener) {
        this.onUserClickListener = onUserClickListener;
    }

    // остальной код ниже не изменился
}



SearchUsersActivity.java

public class SearchUsersActivity extends AppCompatActivity {

    private void initRecyclerView() {
        UsersAdapter.OnUserClickListener onUserClickListener = new UsersAdapter.OnUserClickListener() {
            @Override
            public void onUserClick(User user) {
                Toast.makeText(SearchUsersActivity.this, "user " + user.getName(), Toast.LENGTH_SHORT).show();
            }
        };
        usersAdapter = new UsersAdapter(onUserClickListener);
        usersRecyclerView.setAdapter(usersAdapter);
    }
}

Пока по клику на элемент будем просто выводить имя пользователя, чтобы проверить, что всё работает правильно.

Уведомление наблюдателя о событии в UsersAdapter

Теперь осталось гарантировать, что метод onUserClick(User user) будет вызываться при каждом нажатии на элемент списка. Поэтому вызывать мы его будем в методе onClick(View v), который срабатывает именно тогда, когда нам и нужно. Отличным местом для добавления слушателя для View-компонента является место его создания в адаптере:

UsersAdapter.java

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {

    // остальной код выше не изменился

    class UserViewHolder extends RecyclerView.ViewHolder {

        public UserViewHolder(View itemView) {

            // остальной код выше не изменился

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    User user = userList.get(getLayoutPosition());
                    onUserClickListener.onUserClick(user);
                }
            });
        }

        // остальной код ниже не изменился
    }
}

Метод getLayoutPosition() вернёт нам позицию, по которой произошло нажатие, что поможет нам получить по порядковому номеру элемент данных. Эти данные мы и отправляем на уровень выше в методе onUserClick(User user), с которыми уже работает Activity.

Давайте запустим приложение, проверим что при клике на элемент отображается корректное имя:

ElonMuskPopup.png

Видим, что снизу экрана вывелся Toast с именем Elon Musk. Отлично, значит всё работает корректно.

Переход на экран UserInfoActivity

Отличная работа. Но на этом экране список немного отличается от предыдущего: здесь по нажатию на элемент списка необходимо выполнить переход на детальную информацию о пользователе. Проще говоря, по нажатию на элемент списка нам необходимо узнать id пользователя, на которого мы нажали, и использовать его для перехода на детальную информацию о нём. Делать мы это будем при помощи объекта Intent (рус. намерение), при помощи которого мы сообщаем Android системе о том, что мы изъявляем желание попасть на другой экран и передать туда некоторые данные. Подробнее об этом механизме вы можете узнать из наших видеоуроков (этом и этом), а также из официальной документации.

Также давайте создадим константу USER_ID в Activity UserInfoActivity, чтобы мы могли ссылаться на неё при получении данных на экране:

UserInfoActivity.java

public class UserInfoActivity extends AppCompatActivity {

    public static final String USER_ID = "userId";

    // остальной код ниже не изменился
}

Таким образом код перехода на экран детальной информации о пользователе выглядит следующим образом:

SearchUsersActivity.java

Intent intent = new Intent(SearchUsersActivity.this, UserInfoActivity.class);
intent.putExtra(UserInfoActivity.USER_ID, userId);
startActivity(intent);

Мы могли бы просто передать просто «сырую» строку в объекте Intent:

SearchUsersActivity.java

intent.putExtra("userId", userId);

Однако в таком случае при получении объекта Intent следующим экраном придётся снова писать данную строку. Это повышает шанс ошибки или опечатки при написании кода. Создание же константы USER_ID просто избавляет нас от необходимости задумываться о том, что скрывается внутри неё. Android Studio просто не позволит нам ошибиться.

Полный листинг кода:

SearchUsersActivity.java

package colibri.dev.com.colibritweet.activity;

import java.util.Arrays;
import java.util.Collection;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import colibri.dev.com.colibritweet.R;
import colibri.dev.com.colibritweet.adapter.UsersAdapter;
import colibri.dev.com.colibritweet.pojo.User;

public class SearchUsersActivity extends AppCompatActivity {
    private RecyclerView usersRecyclerView;
    private UsersAdapter usersAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_search_users);
        initRecyclerView();

        searchUsers();
    }

    private void searchUsers() {
        Collection<User> users = getUsers();
        usersAdapter.setItems(users);
    }

    private Collection<User> getUsers() {
        return Arrays.asList(
                new User(
                        929257819349700608L,
                        "http://i.imgur.com/DvpvklR.png",
                        "DevColibri",
                        "@devcolibri",
                        "Sample description",
                        "USA",
                        42,
                        42
                ),

                new User(
                        44196397L,
                        "https://pbs.twimg.com/profile_images/782474226020200448/zDo-gAo0_400x400.jpg",
                        "Elon Musk",
                        "@elonmusk",
                        "Hat Salesman",
                        "Boring",
                        14,
                        13
                )
        );
    }

    private void initRecyclerView() {
        usersRecyclerView = findViewById(R.id.users_recycler_view);
        usersRecyclerView.setLayoutManager(new LinearLayoutManager(this));

        UsersAdapter.OnUserClickListener onUserClickListener = new UsersAdapter.OnUserClickListener() {
            @Override
            public void onUserClick(User user) {
                Intent intent = new Intent(SearchUsersActivity.this, UserInfoActivity.class);
                intent.putExtra(UserInfoActivity.USER_ID, user.getId());
                startActivity(intent);
            }
        };
        usersAdapter = new UsersAdapter(onUserClickListener);
        usersRecyclerView.setAdapter(usersAdapter);
    }
}

UsersAdapter.java

package colibri.dev.com.colibritweet.adapter;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import com.squareup.picasso.Picasso;

import colibri.dev.com.colibritweet.R;
import colibri.dev.com.colibritweet.pojo.User;

public class UsersAdapter extends RecyclerView.Adapter<UsersAdapter.UserViewHolder> {
    private List<User> userList = new ArrayList<>();
    private OnUserClickListener onUserClickListener;

    public UsersAdapter(OnUserClickListener onUserClickListener) {
        this.onUserClickListener = onUserClickListener;
    }

    @Override
    public UserViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.user_item_view, parent, false);
        return new UserViewHolder(view);
    }

    @Override
    public void onBindViewHolder(UserViewHolder holder, int position) {
        User user = userList.get(position);
        holder.bind(user);
    }

    public void setItems(Collection<User> users) {
        userList.addAll(users);
        notifyDataSetChanged();
    }

    public void clearItems() {
        userList.clear();
        notifyDataSetChanged();
    }

    @Override
    public int getItemCount() {
        return userList.size();
    }

    class UserViewHolder extends RecyclerView.ViewHolder {
        private ImageView userImageView;
        private TextView nameTextView;
        private TextView nickTextView;

        public UserViewHolder(View itemView) {
            super(itemView);
            userImageView = itemView.findViewById(R.id.profile_image_view);
            nameTextView = itemView.findViewById(R.id.user_name_text_view);
            nickTextView = itemView.findViewById(R.id.user_nick_text_view);

            itemView.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    User user = userList.get(getLayoutPosition());
                    onUserClickListener.onUserClick(user);
                }
            });
        }

        public void bind(User user) {
            nameTextView.setText(user.getName());
            nickTextView.setText(user.getNick());
            Picasso.with(itemView.getContext()).load(user.getImageUrl()).into(userImageView);
        }
    }

    public interface OnUserClickListener {
        void onUserClick(User user);
    }
}

Запустим приложение. Видим, что по клику на элемент списка мы переходим на UserInfoActivity.

Таким образом, в ходе данного урока мы рассмотрели принцип работы со слушателями существующих компонентов системы, а также научились создавать свои собственные.

После того, как вы закончите тестирование, не забудьте в манифесте приложения снова сделать Activity UserInfoActivity стартовой.

Полезные материалы:

Полный листинг изменений кода:

Code diff

УВИДЕТЬ ВСЕ Добавить заметку
ВЫ
Добавить ваш комментарий