Статьи

Пользовательские макеты Android с вашим собственным ArrayAdapter

Android предоставляет серию различных макетов для удовлетворения потребностей ваших приложений. Один из самых быстрых и простых способов отображения информации для пользователей — через компонент ListView . Этот компонент создает простую прокручиваемую область, которая может отображать уникальные наборы информации. Отличительной особенностью ListView Например, для отображения изображения слева и заголовка справа для каждой записи. Это возможно путем создания и использования пользовательских ArrayAdapter

В этом руководстве вы узнаете, как получить ListViewадаптера ArrayAdapter . Разобравшись с этими темами, вы сможете создавать удивительно выглядящие прокручиваемые списки информации.

Компоненты Android ArrayAdapter

Есть несколько взаимосвязанных небольших частей для создания вашего окончательного настроенного ListView Вам понадобится следующее:

  • Источник данных, как правило, в виде списка объектов
  • Настраиваемый объект ArrayAdapter
  • ListViewArrayAdapter

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

Android ArrayAdapter Обзор

Индивидуальная аренда ListView Showcase

В качестве примера вы создадите персонализированный ListView Каждое свойство будет отображать такую ​​информацию, как адрес, рекомендуемое изображение, цена и краткое описание. Вот что вы можете увидеть в конце.

Пример приложения

Идея состоит в том, чтобы научиться настраивать макет, а не копировать его точно. Как только вы поймете, как работают принципы, вы можете создать макет, который работает для вас.

Окончательный код этого проекта находится на GitHub .

Создание вашего проекта

Создайте новый проект в Android Studio с поддержкой Android 4.0.3 (API 15 — Ice Cream Sandwich) и выше, который охватит большую часть рынка Android. Добавьте пустое действие и назовите проект Rental Properties .

Как только Android Studio завершит настройку вашего проекта, вы должны увидеть простой класс MainActivityactivity_main.xml . Это все, что вам нужно для начала.

Определение исходных данных

Прежде чем вы сможете создать ListView Поскольку каждое свойство имеет набор информации, начните с создания класса для установки и получения информации. Создайте новый класс Property

 //Base class to hold information about the property
public class Property {

    //property basics
    private int streetNumber;
    private String streetName;
    private String suburb;
    private String state;
    private String description;
    private String image;
    private Double price;
    private int bedrooms;
    private int bathrooms;
    private int carspots;

    //constructor
    public Property(int streetNumber, String streetName, String suburb, String state, String description, Double price, String image, int bedrooms, int bathrooms, int carspots){

        this.streetNumber = streetNumber;
        this.streetName = streetName;
        this.suburb = suburb;
        this.state = state;
        this.description = description;
        this.price = price;
        this.image = image;
        this.bedrooms = bedrooms;
        this.bathrooms = bathrooms;
        this.carspots = carspots;
    }

    //getters
    public int getStreetNumber() { return streetNumber; }
    public String getStreetName() {return streetName; }
    public String getSuburb() {return suburb; }
    public String getState() {return state; }
    public String getDescription() {return description; }
    public Double getPrice() {return price; }
    public String getImage() { return image; }
    public int getBedrooms(){ return bedrooms; }
    public int getBathrooms(){ return bathrooms; }
    public int getCarspots(){ return carspots; }
}

Это создает структуру для класса путем добавления личных полей для хранения информации, такой как адрес улицы, штат, описание и т. Д.

Продолжите, создав метод конструктора Property

Наконец, определите функции get

Создание файла макета XML

Теперь, когда у вас есть класс, вам нужно определить файл макета XML, используемый настраиваемым ArrayAdapter

Откройте папку проектов верхнего уровня res и найдите подкаталог макета . Создайте новый файл макета с именем ‘property_layout’ и добавьте RelativeLayout в качестве корневого тега (это характерно для моего дизайна).

Дизайн макета разделен на три раздела:

  • Левый раздел, содержащий изображение.
  • Правильный раздел, который содержит адрес и описание.
  • Нижний раздел, который содержит информацию о свойствах, таких как количество спален, ванных комнат, места для автомобилей и цену.

При визуализации он должен выглядеть следующим образом.

Макет приложения

Скопируйте следующее в ваш новый макет. Здесь вы создали вложенный макет, где каждый из разделов / элементов имеет идентификаторы, чтобы вы могли размещать и заполнять их. Не стесняйтесь настраивать поля, позиции и размеры элементов по своему усмотрению.

 <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:paddingTop="10dp"
    android:paddingBottom="10dp">

    <ImageView
        android:id="@+id/image"
        android:layout_width="100dp"
        android:layout_height="100dp"
        android:layout_marginRight="10dp"
        android:contentDescription="Property Image" />

    <LinearLayout
        android:id="@+id/infoSection"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/image"
        android:orientation="vertical">

        <TextView
            android:id="@+id/address"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:text="Street Address"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/description"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:text="Description"
            android:textSize="15sp" />
    </LinearLayout>

    <RelativeLayout
        android:id="@+id/pricingSection"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/infoSection"
        android:orientation="vertical">

        <TextView
            android:id="@+id/price"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginBottom="10dp"
            android:text="Price" />

        <TextView
            android:id="@+id/bedroom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/price"
            android:layout_marginRight="15dp"
            android:text="Bed:" />

        <TextView
            android:id="@+id/bathroom"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/price"
            android:layout_marginRight="15dp"
            android:layout_toRightOf="@id/bedroom"
            android:text="Bath:" />

        <TextView
            android:id="@+id/carspot"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@id/price"
            android:layout_marginRight="15dp"
            android:layout_toRightOf="@id/bathroom"
            android:text="Car:" />

    </RelativeLayout>

</RelativeLayout>

И замените TextViewactivity_main.xml на:

 <ListView
       android:layout_width="match_parent"
       android:layout_height="match_parent"
       android:id="@+id/customListView"></ListView>

Добавление изображений

Большинство компонентов пользовательского интерфейса — это просто элементы TextViewImageView

Каждое из свойств имеет соответствующее изображение, нарисованное при визуализации представления. В этом примере я использовал имя property_image_X Поскольку вы добавите 4 свойства, вам понадобятся 4 изображения, вы можете найти изображения, которые я использовал здесь .

Если вы ранее не добавляли изображения в проект Android Studio, скопируйте все изображения, которые вы хотите добавить в свой проект, и выберите « Вставить» в папку для рисования .

Добавление изображений

Выделение изображений и перетаскивание их в папку для рисования не будет работать.

Заполнение данных для использования

Поскольку у вас настроен базовый класс Property

После открытия класса MainActivityArrayList

 private ArrayList<Property> rentalProperties = new ArrayList<>();

Теперь onCreateMainActivity//create property elements
rentalProperties.add(
new Property(10, "Smith Street", "Sydney", "NSW", "A large 3 bedroom apartment right in the heart of Sydney! A rare find, with 3 bedrooms and a secured car park.", 450.00, "property_image_1", 3, 1, 1));

rentalProperties.add(
new Property(66, "King Street", "Sydney", "NSW", "A fully furnished studio apartment overlooking the harbour. Minutes from the CBD and next to transport, this is a perfect set-up for city living.", 320.00, "property_image_2", 1, 1, 1));

rentalProperties.add(
new Property(1, "Liverpool Road", "Liverpool", "NSW", "A standard 3 bedroom house in the suburbs. With room for several cars and right next to shops this is perfect for new families.", 360.00, "property_image_3", 3, 2, 2));

rentalProperties.add(
new Property(567, "Sunny Street", "Gold Coast", "QLD", "Come and see this amazing studio appartment in the heart of the gold coast, featuring stunning waterfront views.", 360.00, "property_image_4" , 1, 1, 1));
Настройте это, если хотите, но обратите внимание, что все эти поля являются обязательными и должны быть определены в соответствии с ожиданиями конструктора (т.е. сначала номер улицы, затем название улицы, затем пригород и т. Д.).

 ArrayAdapter

В реальном приложении вы извлекаете эти данные из веб-источника и создаете объекты на лету. Для этого урока проще определить значения в коде.

Создание пользовательского класса ArrayAdapter

Стандартный Android ArrayAdapter Хотя это может быть хорошо для простых списков, вам нужна возможность предоставить свои собственные макеты и заполнить их соответствующим образом. Вы делаете это, создавая свой собственный класс MainActivty

Для простоты я создаю новый класс внутри класса //custom ArrayAdapter
class propertyArrayAdapter extends ArrayAdapter<Property>{

private Context context;
private List<Property> rentalProperties;

//constructor, call on creation
public propertyArrayAdapter(Context context, int resource, ArrayList<Property> objects) {
super(context, resource, objects);

this.context = context;
this.rentalProperties = objects;
}

//called when rendering the list
public View getView(int position, View convertView, ViewGroup parent) {

//get the property we are displaying
Property property = rentalProperties.get(position);

//get the inflater and inflate the XML layout for each item
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.property_layout, null);

TextView description = (TextView) view.findViewById(R.id.description);
TextView address = (TextView) view.findViewById(R.id.address);
TextView bedroom = (TextView) view.findViewById(R.id.bedroom);
TextView bathroom = (TextView) view.findViewById(R.id.bathroom);
TextView carspot = (TextView) view.findViewById(R.id.carspot);
TextView price = (TextView) view.findViewById(R.id.price);
ImageView image = (ImageView) view.findViewById(R.id.image);

//set address and description
String completeAddress = property.getStreetNumber() + " " + property.getStreetName() + ", " + property.getSuburb() + ", " + property.getState();
address.setText(completeAddress);

//display trimmed excerpt for description
int descriptionLength = property.getDescription().length();
if(descriptionLength >= 100){
String descriptionTrim = property.getDescription().substring(0, 100) + "...";
description.setText(descriptionTrim);
}else{
description.setText(property.getDescription());
}

//set price and rental attributes
price.setText("$" + String.valueOf(property.getPrice()));
bedroom.setText("Bed: " + String.valueOf(property.getBedrooms()));
bathroom.setText("Bath: " + String.valueOf(property.getBathrooms()));
carspot.setText("Car: " + String.valueOf(property.getCarspots()));

//get the image associated with this property
int imageID = context.getResources().getIdentifier(property.getImage(), "drawable", context.getPackageName());
image.setImageResource(imageID);

return view;
}
}

 PropertyArrayAdapter

Выше вы создали новый адаптер с именем ArrayAdapterprivate Context context;
private List<Property> rentalProperties;

Вы добавили два приватных поля вверху, потому что вам нужны методы внутри класса для доступа к контексту и списку свойств.

 ArrayAdapter

ArrayAdapter Constructor

При расширении contextresourceobjects //constructor, call on creation
public propertyArrayAdapter(Context context, int resource, ArrayList<Property> objects) {
super(context, resource, objects);

this.context = context;
this.rentalProperties = objects;
}

 ListView

Этот конструктор передает весь список свойств массива, а также контекст. Оба важны и присваиваются классам приватных переменных для использования.

Визуализация представления с помощью метода getView

Каждый раз, когда getView//called when rendering the list
public View getView(int position, View convertView, ViewGroup parent) {
//get the property we are displaying
Property property = rentalProperties.get(position);

//get the inflater and inflate the XML layout for each item
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.property_layout, null);
}
Поскольку вы хотите отобразить пользовательский макет, вам нужно определить этот метод.

 position

Поскольку у вас есть доступ к context Теперь у вас есть этот объект, вы можете получить адрес, описание и все другие поля, используя методы getter класса.

Загрузка нашего XML-файла макета

Следующим шагом будет использование ранее сохраненного LAYOUT_INFLATER_SERVICELayoutInflater Создайте новый объект detail_layout//get the inflater and inflate the XML layout for each item
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
View view = inflater.inflate(R.layout.property_layout, null);

 streetNumber

Заполнение файла макета и его возврат

Поскольку теперь у вас есть доступ к XML-файлу, вы можете найти элементы по их идентификатору.

Получить все элементы, а затем заполнить их содержимое. Вы создаете поле адреса путем объединения streetNamestatedescription Другие элементы, такие как property_image_1

Актив изображения обрабатывается по-разному. При создании каждому из свойств присваивалось соответствующее имя изображения, например, getResources Вы используете метод getIdentifierImageViewdrawable . Это даст идентификатор ресурса для заполнения TextView description = (TextView) view.findViewById(R.id.description);
TextView address = (TextView) view.findViewById(R.id.address);
TextView bedroom = (TextView) view.findViewById(R.id.bedroom);
TextView bathroom = (TextView) view.findViewById(R.id.bathroom);
TextView carspot = (TextView) view.findViewById(R.id.carspot);
TextView price = (TextView) view.findViewById(R.id.price);
ImageView image = (ImageView) view.findViewById(R.id.image);

//set address and description
String completeAddress = property.getStreetNumber() + " " + property.getStreetName() + ", " + property.getSuburb() + ", " + property.getState();
address.setText(completeAddress);

//display trimmed excerpt for description
int descriptionLength = property.getDescription().length();
if(descriptionLength >= 100){
String descriptionTrim = property.getDescription().substring(0, 100) + "...";
description.setText(descriptionTrim);
}else{
description.setText(property.getDescription());
}

//set price and rental attributes
price.setText("$" + String.valueOf(property.getPrice()));
bedroom.setText("Bed: " + String.valueOf(property.getBedrooms()));
bathroom.setText("Bath: " + String.valueOf(property.getBathrooms()));
carspot.setText("Car: " + String.valueOf(property.getCarspots()));

//get the image associated with this property
int imageID = context.getResources().getIdentifier(property.getImage(), "drawable", context.getPackageName());
image.setImageResource(imageID);

//return the view, very important
return view;

 ArrayAdapter

Этот процесс может быть настолько простым или сложным, насколько вам нужно. В нашем примере мы показываем довольно много информации и гарантируем, что она хороша для конечного пользователя. Вы можете сократить это, чтобы отобразить несколько предложений или расширить его, чтобы обеспечить больше функциональности.

Использование Custom ArrayAdapter

Теперь, когда у вас есть новый настроенный класс onCreateMainActivity//create our new array adapter
ArrayAdapter<Property> adapter = new propertyArrayAdapter(this, 0, rentalProperties);

Ниже, где вы добавили данные арендной собственности, создайте новый адаптер:

 context

Три свойства, передаваемые в этот адаптер:

  • Текущий thisMainActivity0
  • Значение 0 ArrayList
  • ListView

Следующим шагом является подключение этого нового адаптера к ListView Сделайте это, найдя setAdapter//Find list view and bind it with the custom adapter
ListView listView = (ListView) findViewById(R.id.customListView);
listView.setAdapter(adapter);

 ArrayAdapter

Вот и все!

Когда вы запускаете приложение, вы должны увидеть новый настроенный макет, определенный в XML-файле и настроенный внутри ListView

Бонус — динамическая загрузка макетов

В этом примере вы отобразили все свойства, используя property_layout.xml . Это было для простоты, так как обычно вы хотите, чтобы все элементы списка выглядели одинаково.

Когда каждое представление отображается внутри Property Для простоты вы можете загрузить новый макет для выбранного элемента (например, рекомендуемого элемента) или измененную версию.

Вернитесь к примеру и измените его, чтобы выбранные свойства имели разный макет.

Измените класс featured

 private Boolean featured;

Добавьте функцию получения:

 public Boolean getFeatured(){return featured; }

Вам также необходимо добавить его в конструктор, чтобы убедиться, что переданные данные содержат эту дополнительную информацию.

 this.featured = featured;

Например, установите свойство id 1, чтобы оно было добавлено, а остальные оставьте как есть.

Скопируйте файл property_layout.xml и переименуйте его в property_layout_alt.xml . Внесите любые изменения, которые вы хотите, я выделил адрес и цену на красном фоне.

Внутри класса MainActivitygetView

 //conditionally inflate either standard or special template
View view;
if(property.getFeatured() == true){
    view = inflater.inflate(R.layout.property_layout_alt, null);
}else{
    view = inflater.inflate(R.layout.property_layout, null);
}

Вы проверяете, есть ли он, и загружаете ли он новый файл property_layout_alt.xml (который содержит новые визуальные изменения)

При запуске вы должны увидеть что-то вроде этого, с выделенными свойствами, используя альтернативный макет

Динамический Макет

Бонус — обработка событий кликов для вашего ArrayAdapter

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

Вернитесь к методу onCreateMainActivity

 //add event listener so we can handle clicks
AdapterView.OnItemClickListener adapterViewListener = new AdapterView.OnItemClickListener() {

    //on click
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

        Property property = rentalProperties.get(position);

        Intent intent = new Intent(MainActivity.this, DetailActivity.class);
        intent.putExtra("streetNumber", property.getStreetNumber());
        intent.putExtra("streetName", property.getStreetName());
        intent.putExtra("suburb", property.getSuburb());
        intent.putExtra("state", property.getState());
        intent.putExtra("image", property.getImage());
        intent.putExtra("bedrooms", property.getBedrooms());
        intent.putExtra("bathrooms", property.getBathrooms());
        intent.putExtra("carspots", property.getCarspots());
        intent.putExtra("description", property.getDescription());

        startActivity(intent);
    }
};
//set the listener to the list view
listView.setOnItemClickListener(adapterViewListener);

Вы создаете новый объект onItemClickListeneronItemClick

В этом примере вы получаете свойство из списка арендуемых свойств, а затем устанавливаете дополнительные свойства Intent через объект Intent После того, как вы настроили дополнительные функции, вы можете начать следующее действие, Детальное задание.

Когда пользователь щелкает элемент, он переходит в детальное действие. Чтобы добавить действие в файл манифеста проекта:

 <activity
  android:name=".DetailActivity"                      
  android:name=".DetailActivity"
  android:parentActivityName=".MainActivity"                  android:label="Rental Detail">
</activity>

В следующем упражнении вы можете получить дополнительные функции, переданные на этапе намерения, создавая ваше детальное действие.

Вот класс DetailActivity

 public class DetailActivity extends AppCompatActivity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.detail_layout);

        //set the back (up) button
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        //find all our view components
        ImageView imageView = (ImageView) findViewById(R.id.image);
        TextView addressTV = (TextView) findViewById(R.id.address);
        TextView descriptionTV = (TextView) findViewById(R.id.description);
        TextView priceTV = (TextView) findViewById(R.id.price);
        TextView bedroomsTV = (TextView) findViewById(R.id.bedrooms);
        TextView bathroomsTV = (TextView) findViewById(R.id.bathrooms);
        TextView carspotsTV = (TextView) findViewById(R.id.carspots);


        //collect our intent and populate our layout
        Intent intent = getIntent();

        Integer streetNumber = intent.getIntExtra("streetNumber", 0);
        String streetName = intent.getStringExtra("streetName");
        String suburb = intent.getStringExtra("suburb");
        String state = intent.getStringExtra("state");
        String description = intent.getStringExtra("description");
        Double price = intent.getDoubleExtra("price", 0.0);
        Integer bedrooms = intent.getIntExtra("bedrooms", 0);
        Integer bathrooms = intent.getIntExtra("bathrooms", 0);
        Integer carspots = intent.getIntExtra("carspots", 0);
        String image = intent.getStringExtra("image");
        Integer imageID = this.getResources().getIdentifier(image, "drawable", this.getPackageName());
        String address = streetNumber + " " + streetName + ", " + suburb + ", " + state;

        //set elements
        imageView.setImageResource(imageID);
        addressTV.setText(address);
        descriptionTV.setText(description);
        priceTV.setText('$' + Double.toString(price));
        bathroomsTV.setText("Bathrooms: " + bathrooms);
        bedroomsTV.setText("Bedrooms: " + bedrooms);
        carspotsTV.setText("Car Spots: " + carspots);

        //set the title of this activity to be the street name
        getSupportActionBar().setTitle(address);
    }
}

И макет связанной деятельности:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="10dp">

    <ScrollView
        android:id="@+id/scrollView"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:orientation="vertical"
            android:layout_height="wrap_content">

            <ImageView
                android:id="@+id/image"
                android:layout_width="match_parent"
                android:contentDescription="Property Image"
                android:layout_height="wrap_content"
                android:adjustViewBounds="true"
                android:layout_marginBottom="10dp"
                android:background="#cc0033"/>

            <TextView
                android:id="@+id/address"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Address goes here"
                android:layout_marginBottom="10dp"
                android:textSize="18sp"/>

            <TextView
                android:id="@+id/description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textSize="15sp"
                android:text="Description here"
                android:layout_marginBottom="15dp"/>

            <TextView
                android:id="@+id/price"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:text="price"/>

            <TextView
                android:id="@+id/bedrooms"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Bedrooms"
                android:layout_marginBottom="5dp"/>
            <TextView
                android:id="@+id/bathrooms"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Bathrooms"
                android:layout_marginBottom="5dp"/>
            <TextView
                android:id="@+id/carspots"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="Car Spots"
                android:layout_marginBottom="5dp"/>

        </LinearLayout>

    </ScrollView>

</LinearLayout>

Куда отсюда?

Теперь, когда вы знаете основы настройки класса ArrayAdapter, вы можете создавать отличные интерфейсы для своих списков. ArrayAdapter не ограничивается компонентом ListView, его также можно использовать с компонентами GridView или Spinner.

Создание богатых, визуально впечатляющих списков — отличный способ заинтересовать ваших пользователей. Вы могли бы расширить эти идеи, добавив анимацию прокрутки или выполнив различные функции для каждого элемента (например, зарегистрировав одно действие, которое срабатывает при нажатии, а другое — при пролистывании).

Любые комментарии или вопросы, пожалуйста, дайте мне знать в комментариях ниже.