Диалоговое окно Android с «иконифицированным» меню

    Некоторое время назад меня увлекла идея разработки приложений под платформу Android. Дабы не заниматься изучением платформы на простых hello-world программках решил сделать что-то такое, что позволило бы освоиться с UI частью фреймворка, работой с БД, сетью и социальными сервисами.
    Идея была придумана до одурения простая и я бы даже сказал, тупая. И вот когда я начал что-то делать то тут резко захотелось мне сделать красивое диалоговое окно с выбором пункта меню с иконками. Такой диалог присутствует в стандартном Андроиде, например, долгий тап на рабочем столе открывает диалог выбора добавляемого контента (виджет, обоины и т.д.). Итак, добро пожаловать под кат…


    Как было упомянуто выше передо мной стояла задача сделать диалоговое окно типа такого:

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

    Список (провайдер) возможных типов аккаунтов


    1. public final class AccountTypesProvider {
    2.   public static List<AccountType> accountTypes = Collections.unmodifiableList(Arrays.asList(
    3.       new AccountType(AccountType.TWITTER_ACCOUNT, "Twitter", R.drawable.twitter_icon_big),
    4.       new AccountType(AccountType.FACEBOOK_ACCOUNT, "Facebook", R.drawable.facebook_icon_big),
    5.       new AccountType(AccountType.BUZZ_ACCOUNT, "Google Buzz", R.drawable.buzz_icon_big),
    6.       new AccountType(AccountType.LINKEDIN_ACCOUNT, "LinkedIn", R.drawable.linkedin_icon_big),
    7.       new AccountType(AccountType.VKONTAKTE_ACOUNT, "ВКонтакте", R.drawable.vkontakte_icon_big)
    8.   ));
    9. }
    * This source code was highlighted with Source Code Highlighter.

    Это простая обертка вокруг списка возможных типов аккаунтов. Тип аккаунта здесь простой POJO класс, который состоит из идентификатора (константа), названия сервиса и идентификатора ресурса иконки для данного типа сервиса.

    ListAdapter для отображения списка типов аккаунтов в заданном layout'е


    1. public final class AccountsTypesListAdapter extends ArrayAdapter<AccountType> {
    2.   private Activity context;
    3.   private List<AccountType> accountTypes;
    4.   
    5.   public AccountsTypesListAdapter(Activity context, List<AccountType> accountTypes) {
    6.     super(context, R.layout.select_account_item, accountTypes);
    7.     
    8.     this.context = context;
    9.     this.accountTypes = accountTypes;
    10.   }
    11.   
    12.   @Override
    13.   public View getView(int position, View convertView, ViewGroup parent) {
    14.     LayoutInflater inflater = context.getLayoutInflater();
    15.     View row = inflater.inflate(R.layout.select_account_item, parent, false);
    16.     
    17.     TextView label = (TextView) row.findViewById(R.id.text_item);
    18.     label.setText(accountTypes.get(position).title);
    19.     
    20.     ImageView icon = (ImageView) row.findViewById(R.id.icon_item);
    21.     icon.setImageResource(accountTypes.get(position).bigIconId);
    22.     
    23.     return row;
    24.   }
    25. }
    * This source code was highlighted with Source Code Highlighter.


    Для начала нужно передать в конструктор список типов, с которым будет работать адаптер списка. Привязка конкретного item'а к лэйауту происходит в переопределенном методе getView(). В нем загружается лэйаут из указанного ресурса, извлекаются виджеты и в них записываются данные об конкретном элементе списка. Кстати, индекс этого элемента автоматически доступен через параметр position.

    Данный лист-адаптер работает с таким вот лэйаутом


    1. <?xml version="1.0" encoding="utf-8"?>
    2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    3.   android:orientation="horizontal" android:layout_width="fill_parent"
    4.   android:layout_height="fill_parent" android:padding="10px">
    5.   
    6.   <ImageView android:id="@+id/icon_item" android:layout_width="wrap_content"
    7.     android:layout_height="fill_parent"/>
    8.     <TextView android:id="@+id/text_item" android:layout_width="wrap_content"
    9.       android:layout_height="fill_parent" android:paddingLeft="10px"
    10.       android:paddingTop="5px" android:textStyle="bold"
    11.       android:textColor="#000000"/>
    12. </LinearLayout>
    * This source code was highlighted with Source Code Highlighter.


    Все, что теперь осталось — это привязать разработанный лист-адаптер к конкретному диалоговому окну
    1. public static void showSelectAccountTypeDialog(Activity context, String title, OnClickListener dialogListener) {
    2.     AlertDialog.Builder builder = new AlertDialog.Builder(context);
    3.     builder.setTitle(title);
    4.     builder.setAdapter(new AccountsTypesListAdapter(context, AccountTypesProvider.accountTypes), dialogListener);
    5.     builder.create().show();
    6.   }
    * This source code was highlighted with Source Code Highlighter.


    и вызвать в нужном месте activity диалог
    1. private void displaySelectAccountTypeDialog() {
    2.     ApplicationDialogs.showSelectAccountTypeDialog(this, "Select network", new OnClickListener() {
    3.       @Override
    4.       public void onClick(DialogInterface dialogInterface, int selectedItemId) {
    5.         setupAccount(selectedItemId);
    6.       }
    7.     });
    8.   }
    * This source code was highlighted with Source Code Highlighter.


    Диалог после своего завершения вернет индекс выбранного item'а параметром selectedItemId в листенер, который указан при вызове диалога. В данном простом случае этот индекс будет совпадать с ID типа аккаунта (item'а в списке), поэтому никаких дополнительных преобразований или извлечений не требуется. Для моей задачи этого индекса более чем достаточно.

    В итоге у меня получился такой вот симпатичный диалог


    Может я в данном топике просто описал прописные истины, может нет. Решать, как говорится, Вам, Хабраюзеры…

    Спасибо и удачи всем в увлекательном процессе разработки под Android-платформу ;)
    Ads
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More

    Comments 38

    • UFO just landed and posted this here
        +3
        Спасибо за статью! побольше бы статей по разработке
          +2
          Пожалуйста! Действительно, статей по разработке под Андроид маловато.
            0
            Спасибо за то что поделились опытом! Очень полезно.
              0
              Перенесите, пожалуйста, в Android.
                +3
                Перенес
                  –3
                  Будем рады, если опубликуешь статью на нашем форуме — androidforums.ru/Stati-ishodniki-sovety-f9.html
                    –1
                    Хорошо
                    +2
                    • 1. Размеры нужно писать в dip, иначе ваш пример будет по разному выглядеть на различных устройствах.
                    • 2. Вариант подходит только для небольшого количество пунктов, которые не будут прокручиваться, т.к. View row = inflater.inflate(R.layout.select_account_item, parent, false); довольно затратная операция. А также в методе getView не переиспользуются view, и они каждый раз создаются заново.
                      +1
                      1. Согласен
                      2. Согласен

                      Учту, обязательно, Ваши пожелания
                      –1
                      посоветуйте, плиз, книгу по яве.
                      я искал на озоне: по адроиду там старые книги, а по яве — сами знаете какой зоопарк, не знаю что выбрать.
                        0
                        Философия Java Брюса Эккеля
                          –1
                          по Android сейчас на русском книга вышла, вполне неплохая, но для новичков
                            0
                            я на английском спокойно читаю, так что предлагаю расширить список.
                              0
                              ну тогда pro android 2, professional android 2 development — две лучшие книги, сам я читал только первую, но первое издание. мне очень понравилось. читается просто и понятно
                          +1
                          У тебя адаптер с твоем случае медленно работает. Самое быстрое сделать так:

                          public final class AccountsTypesListAdapter extends ArrayAdapter<AccountType> {
                          	private Activity context;
                          	private List<AccountType> accountTypes;
                          	private LayoutInflater mInflater;
                          
                          	public AccountsTypesListAdapter(Activity context, List<AccountType> accountTypes) {
                          		super(context, R.layout.select_account_item, accountTypes);
                          
                          		this.context = context;
                          		this.accountTypes = accountTypes;
                          		mInflater = context.getLayoutInflater();
                          	}
                          
                          	@Override
                          	public View getView(int position, View convertView, ViewGroup parent) {
                          		ItemViewHolder itemViewHolder = null;
                          		if (convertView == null) {
                          			convertView = mInflater.inflate(R.layout.select_account_item, parent, false);
                          			itemViewHolder = new ItemViewHolder();
                          			itemViewHolder.tvLabel = (TextView) convertView.findViewById(R.id.text_item);
                          			itemViewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.icon_item);
                          			convertView.setTag(itemViewHolder);
                          		} else {
                          			itemViewHolder = (ItemViewHolder) convertView.getTag();
                          		}
                          
                          		itemViewHolder.tvLabel.setText(accountTypes.get(position).title);
                          		itemViewHolder.ivIcon.setImageResource(accountTypes.get(position).bigIconId);
                          
                          		return convertView;
                          	}
                          
                          	private class ItemViewHolder {
                          		public TextView tvLabel;
                          		public ImageView ivIcon;
                          	}
                          }
                          
                            0
                            Ух ты, интересно
                              0
                              посмотри презентации с последнего google i/o — там в pdf есть code.google.com/intl/ru-RU/events/io/2010/sessions.html#Android

                              конкретно по listview и адаптерам тут — dl.google.com/googleio/2010/android-world-of-listview-android.pdf
                                0
                                особенно посмотри по listview 17 слайд, там разница производительности твоего метода и моего ))
                                  0
                                  Спасибо за совет. Переписал адаптер — реально намного быстрее открывается теперь диалог
                                    0
                                    ну разница сильно заметна обычно если много в списке элементов, неужели на таком малом количестве тоже быстрей?
                                      0
                                      субъективно и в эмуляторе: при первом открытии диалога скорость открытия такая же, при последующих — заметно на глаз, что немного быстрее
                                  0
                                  Может кто-нибудь знает где можно скачать все(или несколько) эти видео разом и желательно на большой скорости?
                                0
                                Извиняюсь за не слишком интеллектуальный вопрос, но я хотел бы прояснить для себя принцип работы данного адаптера, т.к. я тоже изучаю Андроида.

                                При скроллинге списка, в отображаемую область попадают новые элементы списка, для каждого заново отображаемого элемента вызывается метод getView, который должен возвращать представление (View) для данного элемента. Если элемент уже был на экране, то в метод передается существующий View. Т.к. inflate требует значительных затрат, то лучше его не вызывать, если есть этот существующий View. Дочерние View'ы тоже сохраняются, и их так же лучше не пересоздавать, но то, что в layout-е непосредственно не описано, не сохраняется, поэтому изображение и текст придется переприсваивать заново.

                                Все я верно понял или что-то упустил?
                                  0
                                  не обязательно вид от данного элемента, вообще ранее созданный и не используемый более вид. Быстро прокручиваете вниз — в этот метод будут приходить виды, скрывшиеся вверху.
                                0
                                По android sdk мне вполне хватает двух книг:

                                + официальная документация официального сайта + google-группа по андроиду.
                                А по Java я бы посоветовал Thinking in Java (Философия Java) Брюса Эккеля

                                P.S. Кстати, я бы не сказал, что указанные выше книги по андроиду устаревшие. 2-я версия платформы какникак описывается
                                  0
                                  спасибо за книжки
                                  0
                                  Сам новичок в Android, поэтому не буду давать советов, а выскажу лишь несколько наблюдений и мыслей вслух)

                                  1. AccountsTypesListAdapter можно было наследовать сразу от BaseAdapter. В приложении с несколькими подобными меню было бы удобней.
                                  2. Может быть, есть смысл создавать вьюшник элемента списка не из xml, а в рантайме? Это быстрее и надёжнее.
                                  3. Данный адаптер можно использовать не только в AlertDialog, но и в любом одноуровневом ListView.

                                  З.Ы. Автор — молодец.
                                    0
                                    1. А разница?
                                    2. Так в дальнейшем проще стилизовать и реюзать вид. А чем быстрее и надёжнее?
                                      0
                                      1. Разница в . Мне кажется лучше оставить здесь пространство для использования разного контента, написать более общий адаптер. Впрочем, это придирки.
                                      2. Про стилизовать и реюзать — согласен. Но, во-первых, Inflator чертовски долго работает, как уже было сказано выше, а во-вторых, xml — потенциальный источник багов.
                                        0
                                        1. Альтернатива — только курсор-адаптер, а там это делалось бы через SimpleCursorAdapter.ViewBinder.
                                        2. Скорость — да, в таком простом случае было бы не сложнее создать кодом и выиграть в скорости, а баги какие возможны?
                                          0
                                          1. Т.е.? Вы о чём? Адаптер можно написать любой. Я за более общий.
                                          2. Не надо забывать, что xml — отдельный файл. С ним может произойти всё, что угодно от повреждения до подмены содержания. Плюс при использовании собственных вьюшников в xml могут повылезать проблемы с загрузкой классов. Гляньте в исходники Inflator и посмотрите, откуда у него exception'ы «растут».
                                    0
                                    Общее пожелание:
                                    Не мельчите со шрифтом. Все у кого глазки плохо видят вам будут благодарны.
                                      0
                                      со шрифтом исходников?
                                        0
                                        я думаю со шрифтом вот тут

                                          0
                                          Верно вам сказали.
                                          В вашем замечательном диалоговом окне.
                                        0
                                        Интересно, спасибо! Судя по комментариям, на Хбаре очень много людей серьезно занимающихся разработкой под Android. Хотелось бы обратиться с просьбой писать побольше статей таких как эта и эта, т.к. очень мало статей на русском для начинающих. Заранее спасибо!
                                          0
                                          Оно же, только немного более в общем виде:
                                          dev.androidteam.ru/snippets/dialog/items-with-images-in-dialog

                                          Only users with full accounts can post comments. Log in, please.