четверг, 17 января 2013 г.

Планирование дел: Сохранение и загрузка списка. Расширение функционала


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


Практика

Заметка: Для того чтобы комментарии в скаченном проекте были читаемы (русский язык), необходимо поменять кодировку проекта на UTF-8 (Project –> Properties –> Resource –> Text file incoding).
В этой статье я покажу вам, как работать с файлами на sd-карте (сохранение и загрузка). А так же мы расширим возможные действия с нашим списком с помощью основного и контекстного меню. Результат программы вы можете посмотреть на скриншотах, представленных ниже.
    

Перейдем сразу к практике.

1. Сохранение/Загрузка списка
Для получения доступа к SD-карте используется класс Environment. Этот класс предоставляет доступ к переменным среды. Используем метод getExternalStorageDirectory().getAbsolutePath() для получения пути к SD-карте устройства.
В классе Utils добавим строчку:
 // Директория на SD-карте, где храниться список
 public static File cacheDir = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/customlistview");
Для сохранения и загрузки листа используется следующая идея: сохранять объект (ArrayList) целиком в бинарный файл. Для правильной работы мы в предыдущей статье добавили интерфейс Serializable для класса ToDoItem.
  /**
  * Сохранить список на SD-карте
  */
 private synchronized void saveList() {
  try {
   File infoFile = new File(Utils.cacheDir, "cache");
   infoFile.createNewFile();
   ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(infoFile));
   out.writeObject(list);
   out.close();
  } catch (Exception e) {
   e.printStackTrace();
  }
 }

 /**
  * Загрузить список с SD-карты
  */
 @SuppressWarnings("unchecked")
 public boolean loadList() {
  try {
   Utils.cacheDir.mkdir();
   File infoFile = new File(Utils.cacheDir, "cache");
   infoFile.createNewFile();
   ObjectInputStream in = new ObjectInputStream(new FileInputStream(infoFile));
   list = (List<ToDoItem>) in.readObject();
   in.close();
   return true;
  } catch (Exception e) {
   e.printStackTrace();
   return false;
  }
 }

Внесем изменения в существующие алгоритмы:
В методе onCreate() вместо initList() будем использовать loadList().
Так же необходимо добавить сохранение списка при смене статуса элемента.  Это происходит в Handler’е.
 private Handler handler = new Handler() {
  public void handleMessage(Message msg) {

   switch (msg.what)
   {
   ...
   case MSG_CHANGE_ITEM: // Сделано / Не сделано дело
    ToDoItem item = list.get(msg.arg1);
    item.setCheck(!item.isCheck());
    Utils.sorting(list, 0);
    saveList();
    adapter.notifyDataSetChanged();
    setCountPurchaseProduct();
    break; 
   }
  }
 };

2. Разработка основного меню
Основное меню появляется в приложении при нажатии на кнопку меню на девайсе.
С помощью основного меню наше приложение сможет выполнять следующие функции:
  • добавить элемент;
  • удалить все элементы;
  • отметить все элементы;
  • убрать отметки со всех элементов.
Сначала добавим все необходимые строковые ресурсы в файл strings.xml
      <string name="menu_item_add">Добавить</string>
      <string name="menu_item_remove">Удалить</string>
      <string name="menu_item_rename">Переименовать</string>
      <string name="menu_item_remove_all">Удалить все</string>
      <string name="menu_item_check_all">Сделано все</string>
      <string name="menu_item_uncheck_all">Ничего не сделано</string>
     
      <string name="btn_yes">Да</string>
      <string name="btn_no">Нет</string>
     
      <string name="msg_empty">Название не может быть пустым</string>
Теперь разработает макет для меню. Необходимо в папке res создать директорию menu. В ней создать файл menu.xml

Исходный код файла menu.xml:
<menu xmlns:android="http://schemas.android.com/apk/res/android" >

    <item
        android:id="@+id/menu_item_add"
        android:title="@string/menu_item_add"/>
    <item
        android:id="@+id/menu_item_remove_all"
        android:title="@string/menu_item_remove_all"/>
    <item
        android:id="@+id/menu_item_check_all"
        android:title="@string/menu_item_check_all"/>
    <item
        android:id="@+id/menu_item_uncheck_all"
        android:title="@string/menu_item_uncheck_all"/>

</menu>

Теперь необходимо разработать методы, которые будут выполняться при выборе пунктов меню.
  • Добавить элемент (showAddToDoItemDialog)
Данный метод будет создавать диалоговое окно с полем для ввода названия. После нажатия на кнопку «Да», в список добавится новый элемент.
  /**
  * Открыть диалоговое окно для добавления айтема
  */
 private void showAddToDoItemDialog()
 {
  AlertDialog.Builder builder = new AlertDialog.Builder(this);
  builder.setTitle(getString(R.string.menu_item_add));
  final Context context = this;
  final EditText input = new EditText(this);  
        builder.setView(input);
        
        builder.setPositiveButton(getString(R.string.btn_yes), new DialogInterface.OnClickListener() {
         
            @Override
            public void onClick(DialogInterface dialog, int whichButton) {
                String value = input.getText().toString();
                if (value.equals(""))
                 Toast.makeText(context, getString(R.string.msg_empty), Toast.LENGTH_SHORT).show();
                else 
                {
                 list.add(new ToDoItem(value, getIndexFromList() + 1));
                 Utils.sorting(list, 0);
                 saveList();
                 getHandler().sendEmptyMessage(MainActivity.MSG_UPDATE_ADAPTER);
                }

            }
        });
 
        builder.setNegativeButton(getString(R.string.btn_no), new DialogInterface.OnClickListener() {
 
            @Override
            public void onClick(DialogInterface dialog, int which) {
                return;
            }
        });
  AlertDialog alert = builder.create();
  alert.show();
 }

  • Удалить все элементы (removeAll)
Данный метод будет полностью очищать список.
  /**
  * Удаление всех айтемов из списка
  */
 private void removeAll() {
  list.clear();
  saveList();
  getHandler().sendEmptyMessage(MainActivity.MSG_UPDATE_ADAPTER);
 }

  • Отметить/Убрать отметки со всех элементов (setCheckAll)
Данный метод, в зависимости от параметра, устанавливает/убирает отметку с каждого элемента в списке.
  /**
  * Сделать/Не сделать все дела
  */
 private void setCheckAll(boolean check)
 {
  Iterator<ToDoItem> it = list.iterator();
  while (it.hasNext())
  {
   ToDoItem item = it.next();
   item.setCheck(check);
  }
  Utils.sorting(list, 0);
  saveList();
  getHandler().sendEmptyMessage(MainActivity.MSG_UPDATE_ADAPTER);
 }

  • Получение последнего индекса в списке (getIndexFromList)
Данный метод возвращает  последний максимальный индекс в списке.
  /**
  * Получение последнего индекса в списке
  */
 private int getIndexFromList()
 {
  int index = 0;
  Iterator<ToDoItem> it = list.iterator();
  while (it.hasNext())
  {
   ToDoItem item = it.next();
   if (item.getIndex() > index)
    index = item.getIndex();
  }
  return index;
 }

Теперь добавим меню в Activity.
  /**
  * Создание основного меню
  */
 @Override
 public boolean onCreateOptionsMenu(Menu menu) {
  getMenuInflater().inflate(R.menu.menu, menu);
  return true;
 }
   
 /**
  * Обработка нажатия на айтем основного меню
  */
 @Override
 public boolean onOptionsItemSelected(MenuItem item) {
  int id = item.getItemId();
  switch (id)
  {
  case R.id.menu_item_add: // Добавление айтема
   showAddToDoItemDialog();
   return true;
  case R.id.menu_item_remove_all: // Удаление всех айтемов
   removeAll();
   return true;
  case R.id.menu_item_check_all: // Сделать все дела
   setCheckAll(true);
   return true;
  case R.id.menu_item_uncheck_all: // Вернуть лист в начальное состояние
   setCheckAll(false);
   return true;
  default:
   return super.onOptionsItemSelected(item);
  }  
 }

3. Разработка контекстного меню
Контекстное меню для ListView появляется при долгом нажатии на элемент. Регистрируется с помощью метода registerForContextMenu().
С помощью контекстного меню наше приложение сможет выполнять следующие функции:
  • удалить элемент;
  • переименовать элемент.
Сначала разработаем используемые методы для контекстного меню.
  • Удалить выбранный элемент (removeItem)
Удаляет указанный элемент из списка. После удаления происходит переиндексирование списка.
  /**
  * Удаление айтема из списка
  */
 private void removeItem(int index) {
  list.remove(index);
  reindexList();
  saveList();
  getHandler().sendEmptyMessage(MainActivity.MSG_UPDATE_ADAPTER);
 }

  • Переименовать выбранный элемент (showRenameDialog)
Данный метод будет создавать диалоговое окно с полем для изменения названия элемента.
  /**
  * Открыть диалоговое окно для переименования айтема
  */
 private void showRenameDialog(final int index)
 {
  AlertDialog.Builder builder = new AlertDialog.Builder(this);
  builder.setTitle(getString(R.string.menu_item_rename));
  final Context context = this;
  final EditText input = new EditText(this);  
  input.setText(list.get(index).getName());
        builder.setView(input);
        
        builder.setPositiveButton(getString(R.string.btn_yes), new DialogInterface.OnClickListener() {
         
            @Override
            public void onClick(DialogInterface dialog, int whichButton) {
                String value = input.getText().toString();
                if (value.equals(""))
                 Toast.makeText(context, getString(R.string.msg_empty), Toast.LENGTH_SHORT).show();
                else 
                {
                 list.get(index).setName(value);
                 saveList();
                 getHandler().sendEmptyMessage(MainActivity.MSG_UPDATE_ADAPTER);
                }

            }
        });
 
        builder.setNegativeButton(getString(R.string.btn_no), new DialogInterface.OnClickListener() {
 
            @Override
            public void onClick(DialogInterface dialog, int which) {
                return;
            }
        });
  AlertDialog alert = builder.create();
  alert.show();
 }

  • Переиндексирование списка (reindexList)
Данный метод упорядочивает индексы в списке после удаления элемента.
  /**
  * Перераспределить индексы для айтемов в списке
  */
 private void reindexList()
 {
  int index = 1;
  Utils.sorting(list, 1);
  Iterator<ToDoItem> it = list.iterator();
  while (it.hasNext())
  {
   ToDoItem item = it.next();
   item.setIndex(index);
   index++;
  }
  Utils.sorting(list, 0);
 }

Теперь добавим контекстное меню в Activity.
Зарегистрируем меню для ListView в методе onCreate():
registerForContextMenu(listview); // Регистрация контекстного меню для ListView)

Добавим методы в Activity:
  /**
  * Создание контекстного меню для ListView
  */
 @Override
 public void onCreateContextMenu(ContextMenu menu, View v,
   ContextMenuInfo menuInfo) {
  int index = ((AdapterView.AdapterContextMenuInfo)menuInfo).position;
  if (index == list.size() + 1 || index == 0)
   return;
  super.onCreateContextMenu(menu, v, menuInfo);
  menu.add(0, MSG_REMOVE_ITEM, Menu.NONE, R.string.menu_item_remove);
  menu.add(0, MSG_RENAME_ITEM, Menu.NONE, R.string.menu_item_rename);
 }
 
 /**
  * Обработка нажатия на айтем контекстного меню
  */
 @Override
 public boolean onContextItemSelected(MenuItem item) {

  super.onContextItemSelected(item);
  AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo)item.getMenuInfo();
  int index = menuInfo.position - 1;
  
  switch (item.getItemId())
  {
  case MSG_REMOVE_ITEM:  // Удалить айтем
   removeItem(index);
   return true;
  case MSG_RENAME_ITEM: // Переименовать айтем   
   showRenameDialog(index);
   return true;
  }
  return false;
 }

Ссылки

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

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