суббота, 5 мая 2012 г.

Галерея

В этом уроке расскажу как сделать галерею в приложениях android. Начнем с простой галереи и затем немного ее украсим.



1. Для работы с галерей используется класс Gallery. Напишем его в разметке xml:
  1. <Gallery  
  2.  android:id="@+id/gallery"  
  3.  android:layout_width="fill_parent"  
  4.  android:layout_height="wrap_content" 
  5. />
У меня в разметке еще есть текстовое поле (TextView) c id «selected», в который я буду выводить номер выделенного изображения в галереи.

2. Так же как и в списках (ListView) или в таблицах (GridView) для работы с галерей используется адаптер — класс унаследованный от BaseAdapter. Про него я уже писала в статьях Списки и Игра для тренировки памяти. При наследовании от класса BaseAdapter необходимо обязательно описать 4 метода:
public int getCount() - кол-во элементов (в данном случае в галерее)
public Object getItem(int position) — возвращает значение элемента с номером position
public long getItemId(int position) — возвращает ID элемента с номером position
public View getView(int position, View convertView, ViewGroup parent) — возвращает View, который будет является элементом с номером position.

3. Для представления галереи в android есть предопределенный стиль, который рисует для каждого элемента серую рамку (для выделенного — светло-серую). Называется стиль galleryItemBackground. Как мы знаем, чтобы задать фон или стиль в программе используется идентификатор типа R.drawable.image, причем image должен лежать в директории /res/drawable нашего проекта. Но стиль galleryItemBackground является системным и для того чтобы получить идентификатор создадим в /res/values файл (например attrs.xml) и напишем туда
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.   <declare-styleable name="MyGallery">
  4.     <attr name="android:galleryItemBackground" />
  5.   </declare-styleable>
  6. </resources>

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

4. Теперь напишем собственно класс GalleryOne унаследованный от Activity
  1. public class GalleryOne extends Activity {
  2.  
  3.   Gallery mGallery;
  4.   TextView mTextView;
  5.   ImageAdapter mImageAdapter;
  6.  
  7.   /** Called when the activity is first created. */
  8.   @Override
  9.   public void onCreate(Bundle savedInstanceState) {
  10.     super.onCreate(savedInstanceState);
  11.     setContentView(R.layout.gallery);
  12.  
  13.     // Текстовое поле, в которое выводится номер выделенного элемента
  14.     mTextView = (TextView) findViewById(R.id.selected);
  15.  
  16.     mGallery = (Gallery) findViewById(R.id.gallery);
  17.  
  18.     // Устанавливаем адаптер
  19.     mImageAdapter = new ImageAdapter(this);  
  20.     mGallery.setAdapter(mImageAdapter);
  21.  
  22.     // Выделяем элемент по середине
  23.     mGallery.setSelection(mImageAdapter.getCount() / 2);
  24.  
  25.     // Устанавливаем действия, которые будут выполнены при выделении элемента
  26.     mGallery.setOnItemSelectedListener(new OnItemSelectedListener() {
  27.       @Override
  28.       public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
  29.         mTextView.setText(String.valueOf(position));
  30.       }
  31.  
  32.       @Override
  33.       public void onNothingSelected(AdapterView<?> arg0) {
  34.         mTextView.setText("");
  35.       }
  36.     });
  37.   }
  38.  
  39.   // Класс адаптера
  40.   public class ImageAdapter extends BaseAdapter {
  41.     int mGalleryItemBackground;
  42.     private Context mContext;
  43.  
  44.     // Массив изображений
  45.     private int[] mImageIds = {  
  46.       R.drawable.s01, R.drawable.s02, R.drawable.s03, R.drawable.s04
  47.       R.drawable.s05, R.drawable.s06, R.drawable.s07, R.drawable.s08,
  48.       R.drawable.s09, R.drawable.s10};
  49.  
  50.     public ImageAdapter(Context c) {
  51.       mContext = c;
  52.       TypedArray attr = mContext.obtainStyledAttributes(R.styleable.MyGallery);
  53.       mGalleryItemBackground = attr.getResourceId(R.styleable.MyGallery_android_galleryItemBackground0);
  54.       attr.recycle();
  55.     }
  56.  
  57.     public int getCount() {
  58.       return mImageIds.length;
  59.     }
  60.  
  61.     public Object getItem(int position) {
  62.       return position;
  63.     }
  64.  
  65.     public long getItemId(int position) {
  66.       return position;
  67.     }
  68.  
  69.     public View getView(int position, View convertView, ViewGroup parent) {
  70.       ImageView imageView = new ImageView(mContext);
  71.       imageView.setImageResource(mImageIds[position]);
  72.       // Позиционирование по центру
  73.       imageView.setScaleType(ImageView.ScaleType.CENTER);
  74.       // Размер по содержимому
  75.       imageView.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  76.       imageView.setBackgroundResource(mGalleryItemBackground);
  77.       return imageView;
  78.     }
  79.   }
  80. }

Основные места прокомментированы, так что должно быть все понятно. Более подробно расскажу про использование стиля galleryItemBackground. Для работы с такими стилями используется класс TypedArray, который является массивом. Инициализируется экземпляр класса с помощью obtainStyledAttributes() или obtainAttributes(). Метод obtainStyledAttributes() возвращает атрибуты прочитанные из ресурса styleable (то есть все элементы attr из attrs.xml).
Метод getResourceId(int index, int defValue) — возвращает идентификатор ресурса.
После работы с экземпляром класса TypedArray, его обязательно надо очистить с помощью метода recycle();

Таким образом мы получаем id ресурса для предопределенного стиля galleryItemBackground, который можем передать в метод imageView.setBackgroundResource().

5. Запускаем.


6. Если посмотреть исходники андроида, то можно найти, что galleryItemBackground, это не какой-то неизвестный кот в мешке, а вполне понятный файл ресурса drawable и выглядит он приблизительно так:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3.   <item android:drawable="@drawable/gallery_selected_default" 
  4.     android:state_selected="true"
  5.     android:state_window_focused="false" />
  6.  
  7.   <item android:drawable="@drawable/gallery_unselected_default" 
  8.     android:state_selected="false"
  9.     android:state_window_focused="false" />
  10.  
  11.   <item android:drawable="@drawable/gallery_selected_pressed" 
  12.     android:state_selected="true"
  13.     android:state_pressed="true" />
  14.  
  15.   <item android:drawable="@drawable/gallery_selected_focused" 
  16.     android:state_selected="true"
  17.     android:state_focused="true" />
  18.  
  19.   <item android:drawable="@drawable/gallery_selected_default" 
  20.     android:state_selected="true" />
  21.  
  22.   <item android:drawable="@drawable/gallery_unselected_pressed" 
  23.     android:state_selected="false"
  24.     android:state_pressed="true" />
  25.  
  26.   <item android:drawable="@drawable/gallery_unselected_default" />
  27. </selector>

selector определяет drawable, значение которого меняется в зависимости от состояния. Поэтому если мы хотим сделать собственный стиль, можно скопировать этот код в файл в директории /res/drawable (у меня это bg.xml) и заменить «@drawable/...» на собственные изображения. Мне обрабатывать столько состояний объекта ни к чему, я хочу чтобы выделенный элемент (или элемент в фокусе) был в рамочке, а все остальные никак не выделялись. Напишем в bg.xml такое:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <item android:state_selected="true" android:drawable="@drawable/selected" />
  4.     <item android:state_focused="true" android:drawable="@drawable/selected" />
  5.     <item android:drawable="@android:color/transparent" />
  6. </selector>

7. Теперь надо немного изменить наш класс. Прежде всего уберем все что связано с TypedArray из конструктора ImageAdapter и в методе getView() изменим строчку imageView.setBackgroundResource(R.drawable.bg);

8. Запустим


Рамочка нарисовалась, но картинки налезают друг на друга. Определим для галереи атрибут spacing (расстояние между элементами). Это можно сделать в gallery.xml или в самом коде. А также поставим для каждого элемента атрибут padding (расстояние между границей элемента и его содержимым). Итого получилось такое: (в комментариях показано какие строчки добавились, а какие надо удалить)
  1. public class GalleryOne extends Activity {
  2.  
  3.   ...
  4.   public void onCreate(Bundle savedInstanceState) {
  5.  
  6.     ...
  7.     // Выделяем элемент по середине
  8.     mGallery.setSelection(mImageAdapter.getCount() / 2);
  9.     mGallery.setSpacing(2); /* NEW */
  10.  
  11.     ...  
  12.     // Класс адаптера
  13.     public class ImageAdapter extends BaseAdapter {
  14.       // int mGalleryItemBackground; /* DELETE */
  15.       private Context mContext;
  16.  
  17.      ...
  18.       public ImageAdapter(Context c) {
  19.       mContext = c;
  20.       /* DELETE */
  21.       //TypedArray attr = mContext.obtainStyledAttributes(R.styleable.MyGallery);
  22.       // mGalleryItemBackground = attr.getResourceId(R.styleable.MyGallery_android_galleryItemBackground, 0);
  23.       // attr.recycle();
  24.     }
  25.  
  26.     ...
  27.     public View getView(int position, View convertView, ViewGroup parent) {
  28.       ...
  29.       imageView.setBackgroundResource(R.drawable.bg); /* CHANGED */
  30.       imageView.setPadding(102102); /* NEW */
  31.  
  32.       return imageView;
  33.     }
  34.   }
  35. }

9. Запускаем


10. Менять в зависимости от состояния можно не только фон, но и само изображение. Для каждой картинки создадим ее черно-белую копию (если изначальный файл s01.png, то копия будет s01_gray.png). Также создадим для каждой картинки файл (например d01.xml) в директории /res/drawable.
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  3.     <item android:state_selected="true" android:drawable="@drawable/s01" />
  4.     <item android:drawable="@drawable/s01_gray" />
  5. </selector>

То есть для выбранного элемента будет использоваться картинка s01.png, а для невыбранного s01_gray.png

11. В коде нам необходимо заменить массив ресурсов на
private int[] mImageIds = {
R.drawable.d01, R.drawable.d02, R.drawable.d03, R.drawable.d04,
R.drawable.d05, R.drawable.d06, R.drawable.d07, R.drawable.d08,
R.drawable.d09, R.drawable.d10};


Вместо изображений s01, s02... s10, будут браться файлы drawable d01, d02... d10. И уберем добавление фона изображения, то есть строчку imageView.setBackgroundResource(R.drawable.bg);

12. Запускаем


13. Метод getView() адаптера возвращает не ImageView, а просто View. А значит элементом галереи необязательно должно быть изображение. Там может быть TextView (текстовое поле), Button (кнопка) или даже LinearLayout содержащий несколько элементов. В статье про списки мы уже такое делали. Там мы делали это с помощью класса LayoutInflater, сейчас посмотрим как сделать это без него. Добавим к нашим мордочкам текст. Для этого после массива с картинками, определим массив с строками
private String[] mStrings = {"Smiling", "In love", "Sad", "Crying", "Scared",
"Winking", "Laughting", "Surprised", "Upset", "Angry"};


И допишим метод getView ()
  1. public View getView(int position, View convertView, ViewGroup parent) {
  2.   ImageView imageView = new ImageView(mContext);
  3.   imageView.setImageResource(mImageIds[position]);
  4.   imageView.setScaleType(ImageView.ScaleType.CENTER);
  5.   imageView.setPadding(102102);
  6.   imageView.setLayoutParams(new Gallery.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
  7.  
  8.   TextView textView = new TextView(mContext);
  9.   textView.setText(mStrings[position]);
  10.   textView.setGravity(Gravity.CENTER);
  11.   textView.setTypeface(Typeface.MONOSPACE);
  12.   textView.setTextSize(10);
  13.   textView.setTextColor(getResources().getColorStateList(R.color.text));
  14.  
  15.   LinearLayout layout = new LinearLayout(getApplicationContext());
  16.   layout.setOrientation(LinearLayout.VERTICAL);
  17.   layout.addView(imageView);
  18.   layout.addView(textView);
  19.  
  20.   return layout;
  21. }

14. Вначале мы определяем изображение imageView (оно ничем не отличается оттого что у нас уже было). Затем определяем textView, устанавливаем ему значение из массива mStrings. Располагаем текст по центру, шрифт моноширинный 10pt. Цвет текста также должен меняться в зависимости от состояния элемента, поэтому добавим в /res/color файл text.xml
  1. <selector xmlns:android="http://schemas.android.com/apk/res/android">
  2.     <item android:state_selected="true" android:color="#FFAA25" />
  3.     <item android:state_focused="true" android:color="#FFAA25" />
  4.     <item android:color="#919191" />
  5. </selector>

И наконец создаем вертикальный LinearLayout и добавляем в него картинку и текст.
15. Запускаем


Мы посмотрели как создать галерею в android и несколько способов ее представления. В папке исходников лежат все 4 примера галерей.

Исходники:
GalleryReview.zip

Комментариев нет:

Отправить комментарий