Author Archives: ShaddiX

Создание кастомного адаптера для ListView

Элемент ListView позволяет размещать в приложении прокручиваемые списки, причём как их оформить дело исключительно Ваших навыков и потребностей. Для того, чтобы сделать свой список с использованием кастомного адаптера, нужно сделать следующее: Continue reading

Добавить поле поиска в ActionBar

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

1. Создать папку ./res/xml и добавить в неё XML-файл с названием searchable.xml следующего содержимого:

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
  android:hint="@string/search_hint"
  android:label="@string/app_name" />

2. Немного поправить Android manifest, добавив следующие строки в нужное Activity:

<activity
  <!-- указываем, что данное activity принимать интент, но не запускаться заново -->
  android:launchMode="singleTop" >
  <intent-filter>
    <action android:name="android.intent.action.SEARCH" />
  </intent-filter>
  <meta-data
    android:name="android.app.searchable"
    android:resource="@xml/searchable" />
</activity>

3. Добавить виджет поиска в ActionBar вместе с иконкой. Это делается в файле меню данной Activity

<item
  android:id="@+id/search"
  android:actionViewClass="android.widget.SearchView"
  android:icon="@drawable/ico_search"
  android:showAsAction="collapseActionView|ifRoom"
  android:title="@string/menu_search"/>

4. В методе onCreateOptionsMenu добавить код:

SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
SearchView searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));

5. Словить интент, когда нажали Искать

@Override
public void onNewIntent(Intent intent) {
  // Проверяем, что новый интент передан именно для поиска
  if(Intent.ACTION_SEARCH.equals(intent.getAction())) {
    // Здесь будет храниться то, что пользователь ввёл в поисковой строке
    String search = intent.getStringExtra(SearchManager.QUERY);
  }
}

Отправляем данные в POST-запросе

public void postData() {
  // Подключаемся и указываем принимающий URL
  HttpClient httpclient = new DefaultHttpClient();
  HttpPost httppost = new HttpPost("http://yousite.com/post.php");
 
  try {
    // Создаём коллекцию, которая используется для передачи данных
    List<Item> sendData = new ArrayList<Item>(2);
 
    // Добавляем в неё данные
    sendData.add(new BasicNameValuePair("id", "100001"));
    sendData.add(new BasicNameValuePair("name", "ShaddiX"));
 
    // Упаковываем данные
    httppost.setEntity(new UrlEncodedFormEntity(sendData));
 
    // Выполняем POST-запрос
    HttpResponse response = httpclient.execute(httppost);
  } catch (ClientProtocolException e) {
    // исключение
  } catch (IOException e) {
    // исключение
  }
}

Передача объекта через Intent

Создавая новое Activity, в него можно передавать Intent. Сам по себе интент может содержать данные, которые передаются в него через intent.PutExtra(), а принимаются с использованием метода intent.getExtra(). Также через интент можно передать кастомный объект, что бывает очень полезно. Для этого Ваш объект должен использовать интерфейс Parcelable. Вот как выглядит код:

public class Item implements Parcelable

Использование этого интерфейся подразумевает включение в модель нескольких технических методов для сохранения и солучения объекта. Приведу их в коде:

@Override
public int describeContents() {
  return 0;
}
 
// метод для записи объекта в Parcel
@Override
public void writeToParcel(Parcel parcel, int flags) {
  // для сохранения данных используем Bundle, тут думаю понятно
  // Можно обойтись и без него, но так, как мне кажется удобнее
  Bundle bundle = new Bundle();
 
  bundle.putLong("id", id);
  bundle.putString("name", site);
 
  // сохраняем
  parcel.writeBundle(bundle);
}
 
// возвращаем объект
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
  public Item createFromParcel(Parcel in) {
    return new Item(in);
  }
 
  public Item[] newArray(int size) {
    return new Item[size];
  }
};

В модели осталось добавитьконструктор, который будет заполнять объект данными из Parcel, его используем метод createFromParcel описанный выше

public Item(Parcel parcel) {
  Bundle bundle = parcel.readBundle();
 
  id = bundle.getLong("id");
  name = bundle.getString("name");
}

Передаётся объект стандартно:

Intent intent = new Intent(context, NewActivity.class);
intent.putExtra("item", item);
context.startActivity(intent);

Получить объект, переданный через интент в новой активности можно так:

Item item = getIntent().getParcelableExtra("item");

Америки я конечно не открыл, но думаю новичкам будет полезно.

Размещение элемента ListView в контейнере ScrollView

Разместить элемент ListView в контейнере ScrollView в одном Activity запрещается, как я понимаю потому, что оба эти контейнера имеют свой скрол, соответственно возникает конфликт. Хотя технически ошибки в приложении из-за этого не возникает. Это приводит к следующему: в ScrollView все непрокручиваемые элементы отображаются как и должны, а ListView отображает только одну строку результата, а остальные нужно прокручивать в самом ListView, но он имеет высоту только одного элемента, остальные скрываются. Мне пришло в голову, что можно было бы получить количество элементов ListView и задать его высоту в соответствии с этим количеством.

Итак код XML:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="5dp" >
 
    <ScrollView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="gone" >
 
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical" >
 
            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/hello_world" />
 
            <ListView
                android:id="@+id/listItems"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" >
            </ListView>
        </LinearLayout>
    </ScrollView>
 
</RelativeLayout>

Теперь код для изменения высоты:

// Изменение высоты ListView в зависимости от количества элементов, чтобы вместить в ScrollView
// в параметрах передаём listView для определения высоты
public void setListViewHeightBasedOnChildren(ListView listView) {
    ArrayAdapter listAdapter = (ArrayAdapter) listView.getAdapter(); 
 
    int totalHeight = 0;
    // проходимся по элементам коллекции
    for(int i = 0; i < listAdapter.getCount(); i++) {
      View listItem = listAdapter.getView(i, null, listView);
      listItem.measure(0, 0);
      // получаем высоту
      totalHeight += listItem.getMeasuredHeight();
    }
 
    // устанавливаем новую высоту для контейнера
    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    listView.requestLayout();
}

Ну и применение всего этого на деле:

// listItems это элемент ListView
setListViewHeightBasedOnChildren(listItems);

Добавление фильтра к адаптеру ArrayAdapre

Иногда возникает необходимость фильтрования вывода адаптера ArrayAdapter, в элементе ListView. Это может потребоваться например при использовании поиска, когда результаты вывода в ListView должны соответствовать искомой строке. Сделать это можно через фильтр адаптера. Рассмотрим пример использования фильтра с использованием кастомного объекта Item в ArrayList<Item>.

Для начала необходимо имплементировать в адаптер интерфейс Filterable. А также нужно создать две переменные itemFilter — фильтр, dataOrigin — исходные данные. Делается это вот таким образом:

public class ListAdapter extends ArrayAdapter<String> implements Filterable {
private Filter itemFilter;
......
 
// метод используется для возвращения к исходной коллекции, т.е. мы
// отменяем результаты фильтра при помощи переданной переменной в атрибутах
public void resetData(ArrayList items) {
    data = items;
}
 
// вызов фильтра
@Override
public Filter getFilter() {
    if(itemFilter == null)
        itemFilter = new ItemFilter();
 
    return itemFilter;
}
 
// собственно сам фильтр
private class ItemFilter extends Filter {
    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        FilterResults results = new FilterResults();
 
        // constraint - параметр фильтра переданный через атрибуты
        if(constraint == null || constraint.length() == 0) {
            results.values = dataOrigin;
            results.count = dataOrigin.size();
        } else {
            // создаём коллекцию, в которой будут храниться отфильтрованные результаты
            List newList = new ArrayList();
 
            // проходимся по коллекции
            for(Item i : data) {
                // фильтруем данные
                // если имя (i.getName) приведённое в верхний регистра соответствует (contains) значению
                // переданному в constraint, тогда добавляем его в новую коллекцию
                if(i.getName().toUpperCase().contains(constraint.toString().toUpperCase()))
                    newList.add(i);
            }
 
            // возвращаем новую коллекцию
            results.values = newList;
            // и количество результатов
            results.count = newList.size();
        }
        return results;
    }
 
    // сохраняем результаты и информируем адаптер о том, что данные изменились (notifyDataSetChanged)
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        if(results.count == 0) {
            // notifyDataSetInvalidated();
            data = (List) results.values;
            notifyDataSetChanged();
        } else {
            data = (List) results.values;
            notifyDataSetChanged();
        }
    }
}

Теперь нужно вызвать фильтр:

itemsListAdapter.getFilter().filter("_характеристика для сортировки_");

Preferences и хранение настроек приложения

Очень малое количество приложений может обходиться без того, чтобы хранить свои настройки. К таким можно отнести совсем простые, например калькулятор. Как сохранять и загружать настроки приложения через Preferences мы сейчас и узнаем. Continue reading