Урок 53. SimpleCursorTreeAdapter, пример использования


В этом уроке:

- используем SimpleCursorTreeAdapter для построения списка


SimpleCursorTreeAdapter не является полноценным готовым адаптером. Это абстрактный класс, он требует реализацию метода Cursor getChildrenCursor (Cursor groupCursor).

Что это за метод и чего в нем нужно написать, чтобы все заработало? Чтобы понять это, надо разобраться в алгоритме работы этого адаптера, давайте рассмотрим один из конструкторов: SimpleCursorTreeAdapter (Context context, Cursor cursor, int groupLayout, String[] groupFrom, int[] groupTo, int childLayout, String[] childFrom, int[] childTo).

Мы указываем в конструкторе cursor – это курсор групп. В нем содержатся данные по группам, необходимые вам для вывода данных в список. Т.е. все как при использовании SimpleCursorAdapter (см. предыдущие уроки). И адаптер спокойно отображает свернутые группы, используя данные из этого курсора. При сопоставлении полей курсора и View-компонентов он использует массивы groupFrom[] и groupTo[].

Но если мы захотим группу развернуть и посмотреть элементы, то адаптеру неоткуда взять данные по элементам. Курсор cursor содержит данные только по группам. В этом случае адаптер ставит в cursor позицию, соответствующую раскрываемой группе, вызывает метод getChildrenCursor, передает ему этот курсор групп и ожидает в ответ курсор элементов этой группы.

Мы в реализации этого метода получаем id текущей раскрываемой группы из курсора групп, делаем по нему запрос в БД и получаем в виде курсора данные по элементам группы. Этот курсор элементов мы возвращаем, как результат метода getChildrenCursor и адаптер использует его, чтобы создать элементы группы. При сопоставлении полей курсора и View-компонентов используются массивы childFrom и childTo.

Ну и поглядим, что там еще осталось в конструкторе:
context – контекст
groupLayout – layout-ресурс для отображения группы
childLayout - layout-ресурс для отображения элемента

В целом все несложно, рассмотрим на примере. Сделаем список из компаний (групп) и их смартфонов (элементов).

Создадим проект:

Project name: P0531_SimpleCursorTreeAdapter
Build Target: Android 2.3.3
Application name: SimpleCursorTreeAdapter
Package name: ru.startandroid.develop.p0531simplecursortreeadapter
Create Activity: MainActivity


Экран main.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<ExpandableListView
android:id="@+id/elvMain"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</ExpandableListView>
</LinearLayout>



Т.к. нам нужна будет БД, выделим под работу с ней отдельный класс

DB.java:

package ru.startandroid.develop.p0531simplecursortreeadapter;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class DB {

 
private static final String DB_NAME = "mydb";
 
private static final int DB_VERSION = 1;

 
// имя таблицы компаний, поля и запрос создания
 
private static final String COMPANY_TABLE = "company";
 
public static final String COMPANY_COLUMN_ID = "_id";
 
public static final String COMPANY_COLUMN_NAME = "name";
 
private static final String COMPANY_TABLE_CREATE = "create table "
     
+ COMPANY_TABLE + "(" + COMPANY_COLUMN_ID
      +
" integer primary key, " + COMPANY_COLUMN_NAME + " text" + ");";
 
 
// имя таблицы телефонов, поля и запрос создания
 
private static final String PHONE_TABLE = "phone";
 
public static final String PHONE_COLUMN_ID = "_id";
 
public static final String PHONE_COLUMN_NAME = "name";
 
public static final String PHONE_COLUMN_COMPANY = "company";
 
private static final String PHONE_TABLE_CREATE = "create table "
     
+ PHONE_TABLE + "(" + PHONE_COLUMN_ID
      +
" integer primary key autoincrement, " + PHONE_COLUMN_NAME
      +
" text, " + PHONE_COLUMN_COMPANY + " integer" + ");";

 
private final Context mCtx;

 
private DBHelper mDBHelper;
 
private SQLiteDatabase mDB;

 
public DB(Context ctx) {
   
mCtx = ctx;
 
}

 
// открываем подключение
 
public void open() {
   
mDBHelper = new DBHelper(mCtx, DB_NAME, null, DB_VERSION);
    mDB = mDBHelper.getWritableDatabase
();
 
}

 
// закрываем подключение
 
public void close() {
   
if (mDBHelper != null)
     
mDBHelper.close();
 
}

 
// данные по компаниям
 
public Cursor getCompanyData() {
   
return mDB.query(COMPANY_TABLE, null, null, null, null, null, null);
 
}

 
// данные по телефонам конкретной группы
 
public Cursor getPhoneData(long companyID) {
   
return mDB.query(PHONE_TABLE, null, PHONE_COLUMN_COMPANY + " = "
       
+ companyID, null, null, null, null);
 
}

 
private class DBHelper extends SQLiteOpenHelper {

   
public DBHelper(Context context, String name, CursorFactory factory,
       
int version) {
     
super(context, name, factory, version);
   
}

   
@Override
   
public void onCreate(SQLiteDatabase db) {
     
ContentValues cv = new ContentValues();

     
// названия компаний (групп)
     
String[] companies = new String[] { "HTC", "Samsung", "LG" };
     
     
// создаем и заполняем таблицу компаний
     
db.execSQL(COMPANY_TABLE_CREATE);
     
for (int i = 0; i < companies.length; i++) {
       
cv.put(COMPANY_COLUMN_ID, i + 1);
        cv.put
(COMPANY_COLUMN_NAME, companies[i]);
        db.insert
(COMPANY_TABLE, null, cv);
     
}

     
// названия телефонов (элементов)
     
String[] phonesHTC = new String[] { "Sensation", "Desire",
         
"Wildfire", "Hero" };
      String
[] phonesSams = new String[] { "Galaxy S II", "Galaxy Nexus",
         
"Wave" };
      String
[] phonesLG = new String[] { "Optimus", "Optimus Link",
         
"Optimus Black", "Optimus One" };

     
// создаем и заполняем таблицу телефонов
     
db.execSQL(PHONE_TABLE_CREATE);
      cv.clear
();
     
for (int i = 0; i < phonesHTC.length; i++) {
       
cv.put(PHONE_COLUMN_COMPANY, 1);
        cv.put
(PHONE_COLUMN_NAME, phonesHTC[i]);
        db.insert
(PHONE_TABLE, null, cv);
     
}
     
for (int i = 0; i < phonesSams.length; i++) {
       
cv.put(PHONE_COLUMN_COMPANY, 2);
        cv.put
(PHONE_COLUMN_NAME, phonesSams[i]);
        db.insert
(PHONE_TABLE, null, cv);
     
}
     
for (int i = 0; i < phonesLG.length; i++) {
       
cv.put(PHONE_COLUMN_COMPANY, 3);
        cv.put
(PHONE_COLUMN_NAME, phonesLG[i]);
        db.insert
(PHONE_TABLE, null, cv);
     
}
    }

   
@Override
   
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
  }

}

Мы создаем две таблицы company (компании) и phone (телефоны). В таблице телефонов мы указываем для каждого телефона id его группы из таблицы компаний. Также создаем методы для открытия и закрытия подключения, и методы для получения данных по группе и по элементам конкретной группы.


Код MainActivity.java:

package ru.startandroid.develop.p0531simplecursortreeadapter;

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.widget.ExpandableListView;
import android.widget.SimpleCursorTreeAdapter;

public class MainActivity extends Activity {

 
ExpandableListView elvMain;
  DB db;

 
/** Called when the activity is first created. */
 
public void onCreate(Bundle savedInstanceState) {
   
super.onCreate(savedInstanceState);
    setContentView
(R.layout.main);

   
// подключаемся к БД
   
db = new DB(this);
    db.open
();

   
// готовим данные по группам для адаптера
   
Cursor cursor = db.getCompanyData();
    startManagingCursor
(cursor);
   
// сопоставление данных и View для групп
   
String[] groupFrom = { DB.COMPANY_COLUMN_NAME };
   
int[] groupTo = { android.R.id.text1 };
   
// сопоставление данных и View для элементов
   
String[] childFrom = { DB.PHONE_COLUMN_NAME };
   
int[] childTo = { android.R.id.text1 };

   
// создаем адаптер и настраиваем список
   
SimpleCursorTreeAdapter sctAdapter = new MyAdapter(this, cursor,
        android.R.layout.simple_expandable_list_item_1, groupFrom,
        groupTo, android.R.layout.simple_list_item_1, childFrom,
        childTo
);
    elvMain =
(ExpandableListView) findViewById(R.id.elvMain);
    elvMain.setAdapter
(sctAdapter);
 
}

 
protected void onDestroy() {
   
super.onDestroy();
    db.close
();
 
}

 
class MyAdapter extends SimpleCursorTreeAdapter {

   
public MyAdapter(Context context, Cursor cursor, int groupLayout,
        String
[] groupFrom, int[] groupTo, int childLayout,
        String
[] childFrom, int[] childTo) {
     
super(context, cursor, groupLayout, groupFrom, groupTo,
          childLayout, childFrom, childTo
);
   
}

   
protected Cursor getChildrenCursor(Cursor groupCursor) {
     
// получаем курсор по элементам для конкретной группы
     
int idColumn = groupCursor.getColumnIndex(DB.COMPANY_COLUMN_ID);
     
return db.getPhoneData(groupCursor.getInt(idColumn));
   
}
  }
}

В onCreate мы подключаемся к БД, создаем адаптер и список.

В onDestroy закрываем подключение.

Класс MyAdapter – реализация абстрактного SimpleCursorTreeAdapter. Конструктор используем по умолчанию. В методе getChildrenCursor мы получаем id текущей группы и по нему получаем курсор с данными об элементах группы.

Все сохраняем, запускаем.


Список-дерево работает. 


Если вы повесите для списка обработчики нажатий на группы и элементы, они будут предоставлять вам не только позицию пункта в списке, но и соответствующее значение _id из БД.

Сопоставление данных и View-компонентов происходит так же, как и для SimpleCursorAdapter. Если есть внешний ViewBinder, вызывается его метод setViewValue. Если нет, то идет стандартная обработка для TextView и ImageView. Подробности биндинга можно найти в прошлых уроках.


На следующем уроке:

- создаем свой адаптер на основе BaseAdapter