пятница, 29 марта 2013 г.

Галерея с помощью ViewFlipper

Галерея с помощью ViewFlipper
Часто при разработке галереи в мобильных приложениях разработчик сталкивается с проблемой нехватки памяти при работе с большим количеством изображений. В этой статье мы разработаем галерею, которая поможет предотвратить появление сообщений вида OutOfMemoryError или OutOfMemoryException.

Особенности галереи

  • При большом количестве изображений приложение будет работать стабильно без OutOfMemoryError;
  • Анимированные переходы между изображениями;
  • Возможность зациклить галерею.

Подготовка ресурсов

Исходный код strings.xml
<resources>
    <string name="app_name">ViewFlipperExample</string>
    <string name="str_loop">Looped</string>
    <string name="str_pages">%1$s/%2$s</string>
</resources>

Отсюда можно скачать необходимые изображения для галереи.
Так же создадим файлы анимации для переходов между картинками.
Исходный код: anim/go_next_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
        android:fromXDelta="100%p"
        android:toXDelta="0"
        android:duration="400"/>
<alpha
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="400" />
</set>

Исходный код: anim/go_next_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
        android:fromXDelta="0"
        android:toXDelta="-100%p"
        android:duration="400"/>
    <alpha   
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="400" />
</set>

Исходный код: anim/go_prev_in.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
      android:fromXDelta="-100%p"
        android:toXDelta="0"
        android:duration="400"/>
    <alpha
      android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="400" />
</set>

Исходный код: anim/go_prev_out.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
    <translate
      android:fromXDelta="0"
        android:toXDelta="100%p"
        android:duration="400"/>
    <alpha
        android:fromAlpha="1.0"
        android:toAlpha="1.0"
        android:duration="400" />
</set>

И сам макет приложения. Он будет состоять из ViewFlipper, TextView для отображения текущей страницы и CheckBox для возможности зациклить галерею.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <ViewFlipper
        android:id="@+id/gallery"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1" />

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

        <CheckBox
            android:id="@+id/checkBoxLoop"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@string/str_loop" />

        <TextView
            android:id="@+id/txtPages"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:gravity="center"/>
    </LinearLayout>

</LinearLayout>

Исходный код gallery_item.xml:
<ImageView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/gallery_item"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:scaleType="centerInside" >
</ImageView>

Разработка

Идея галереи заключается в том, чтобы хранить в памяти только одно View с текущим изображением. При переходе к очередному изображению добавить новое View к ViewFlipper, а текущее удалить. Таким образом, память приложения не увеличивается и во время освобождается.
Разрабатывать гелерею будет в основной Activity (MainActivity).
Нам потребуются следующие атрибуты:
  • UI элементы
private ViewFlipper gallery;
private TextView txtPages;
private CheckBox checkBoxLoop;
private LayoutInflater inflater = null;
  • Координата X в момент нажатия на ViewFlipper
private float fromPosition;
  • Индекс текущего изображения
private int count;
  • Список изображений
private List<Bitmap> items;

Разработаем основные методы для галереи:
  • Подготовка View с изображением
private View addImage(Bitmap bitmap)
 {
  ImageView view = (ImageView)inflater.inflate(R.layout.gallery_item, null);
  view.setImageBitmap(bitmap);
  
  return view;
 }

  • Удаление изображения из галереи
private void removeImages()
 {
  if (gallery.getChildCount() > 2)
  {
   gallery.removeViewAt(0);
   System.gc();
  }
 }

  • Добавление изображения в галерею в зависимости от направления
private void addNextImage(int position, boolean isLeft)
 {  
  if (isLeft)
  {
   if (position >= 0)
   {
    gallery.addView(addImage(items.get(position)));
   }
  } else 
  {
   if (position < items.size())
    gallery.addView(addImage(items.get(position)));
  }
 }

  • Обновление UI элементов
private void updateTextView()
 {
  String pages = String.format(getString(R.string.str_pages), (count + 1), items.size());
  txtPages.setText(pages);
 }

  • Переход к следующему изображению
public void next()
    {
  if (count >= items.size() - 1 && !checkBoxLoop.isChecked())
   return;
  else if (count >= items.size() - 1 && checkBoxLoop.isChecked())
   count = -1;
  
  count++;
  addNextImage(count, false);
     gallery.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.go_next_in));
        gallery.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.go_next_out));
        gallery.showNext();   
        removeImages(); 
        updateTextView();
    }

  • Переход к предыдущему изображению
public void previous()
    {
  if (count <= 0 && !checkBoxLoop.isChecked())
   return;
  else if (count <= 0 && checkBoxLoop.isChecked())
   count = items.size();
  
  count--;
  addNextImage(count, true);
     gallery.setInAnimation(AnimationUtils.loadAnimation(this, R.anim.go_prev_in));
        gallery.setOutAnimation(AnimationUtils.loadAnimation(this, R.anim.go_prev_out));
        gallery.showNext();
        removeImages(); 
        updateTextView();
    }

  • Инициализация списка с изображениями
private void initList()
 {
  items = new ArrayList();
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_0));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_1));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_2));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_3));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_4));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_5));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_6));
  items.add(BitmapFactory.decodeResource(getResources(), R.drawable.sample_7));
 }

В завершение совместим всю работу с галерей в методе onCreate нашей Activity:
protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
  
  initList();
  
  inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
  
  this.txtPages = (TextView)findViewById(R.id.txtPages);
  this.checkBoxLoop = (CheckBox)findViewById(R.id.checkBoxLoop);
  this.gallery = (ViewFlipper)findViewById(R.id.gallery);  
  this.gallery.setOnTouchListener(new OnTouchListener() {
   
   @Override
   public boolean onTouch(View v, MotionEvent event) {
    switch (event.getAction())
          {
          case MotionEvent.ACTION_DOWN:
              fromPosition = event.getX();
              break;
          case MotionEvent.ACTION_UP:
              float toPosition = event.getX();
              if (fromPosition > toPosition + 20)
              {
               next();
               return true;
              }
              else if (fromPosition < toPosition - 20)
              {
            previous();
               return true;
              } 
          default:
              break;
          }
          return true;
   }
  });
  
  gallery.addView(addImage(items.get(0)));
  updateTextView();
 }

Ссылки

  • Исходные коды данного проекта можно скачать отсюда: zip