Category Archives: Adapterit

Android-listat ja -adapterit

Näyttäessämme tietoa laitteen näytöllä joudumme usein turvautumaan listoihin. Emme usein tiedä kuinka monta riviä listassa on ja ne voivat ohjelman ajoaikana muuttua. Lähes jokaisessa ohjelmassa joutuu käyttämään jotain listaa, gridiä, galleriaa tai jotain muuta Androidin AdapterViewn toteuttavaa luokkaa.

Kaikki luokat jotka jatkavat AdapterViewtä on tarkoitettu näyttämään tietoa selkeissä riveissä (list), sarakkeissa (gallery) tai ruudussa (grid). AdapterView tarvitsee toimiakseen Adapterin, jolle on annettu tarvittavat objektit. Adapterin ei kuitenkaan tule itse hallinnoida objekteja vaan säilyttää vain viittaus niihin. Objektien säilyttäminen on tämän artikkelin asian ulkopuolella. Tässä artikkelissa käydään läpi kuinka Adapterit toimivat ja mikä on paras käytäntö niiden tekemisessä. Kiinnitämme myös huomiota listan ja sen myötä myös adapterin muistin kulutukseen, sillä esimerkiksi kuvien näyttäminen listassa aiheuttaa herkästi muistin ylittämisen ja ohjelman kaatumisen.

Kuinka tehdä Adapteri

Adapteria tehdessä suosittelen käyttämään yliluokkana ArrayAdapter<näytettävä objekti> luokkaa, joka toteuttaa jo lähes kaikki tarvitsemasi metodit. Sinun tarvitsee vain käsitellä, miten jokainen adapterille antamasi objekti näytetään getView()-metodissa.

Toimivan adapterin tekemiseen sinun täytyy jatkaa ArrayAdapteria ja toteuttaa tälle adapterille construktori ja haluamasi lainen getView()-metodi. Tässä esimerkki yksinkertaisesta adapterista, joka näyttää objektin tekstiä jokaisella adapteria käyttävän AdapterView:n alkiossa.

public class TestAdapter extends ArrayAdapter<YourObject> {
public TestAdapter(Context context, int textViewResourceId, List<YourObject> objects) {
    super(context, textViewResourceId, objects);
} 

// Luo AdapterViewn ilmentymän alkio tallennetusta objektista
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    // Adapteri uudelleen käyttää listassa olevia näkymiä uudelleen,
    // mutta listaa luotaessa nämä ovat null:ja ja tarvitsee alustaa.
    // Jos listan näkymä on jo asetettu ei sitä tarvitse enää uudelleen luoda
    // tehokkaassa adapterissa tämä luonti jätetään väliin, kun sitä ei tarvita.
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.your_adapter_view_layout, null);
    }
    // tämän jäälkeen tarvitsee vain ottaa oikeasta adapterin alkiosta tarvittavat tiedot
    //  ja asettaa ne näkymään oikeille paikoille.
    YourObject item = getItem(position);
    // Huomaa että tekstikentän etsiminen vie turhaan tehoja ja aiheuttaa tarpeettomia viittauksia
    TextView text = (TextView)
    convertView.findViewById(R.id.adapter_item_textview);
    text.setText(YourObject.getText()); 

    return convertView; }
}

 

ViewHolder pattern: Lisää adapterin suorituskykyä

ViewHolder-malli tehostaa Adapterien käyttöä poistamalla tarpeen etsiä tarvittavat näkymä widgetit uudelleen getView()-metodissa. Mallin idea on luoda uudelleen käytettävä, Adapterin sisäinen, ViewHolder luokka, joka asetetaan uudelleenkäytettävän convertViewn sisälle tagiin. ViewHolder pitää sisällään referenssit oman convertViewnsä käytettyihin widgetteihin. Tämä poistaa widgettien etsimisen findViewById()-metodilla.

Myös jos tekstikentille tarvitsee lisätä kuuntelijoita riittää että ne tehdään vain kerran ViewHolderia luotaessa. Adapterisi toteuttaa ViewHolder mallin esimerkiksi näin:

// Luo AdapterViewn ilmentymän alkio tallennetusta objektista
@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        convertView = inflater.inflate(R.layout.your_adapter_view_layout, parent, false);

        // Luo ja lisää ViewHolder omalle näkymälleen tagiksi
        convertView.setTag(new ViewHolder(convertView));
    }
    ViewHolder viewHolder = (ViewHolder) convertView.getTag();
    YourObject item = getItem(position);

    viewHolder.text.setText(item.getText());
    viewHolder.image.setImageDrawable(item.getImageDrawable());

    return convertView;
}

private static class ViewHolder {
    final TextView text;
    final ImageView image;

    public ViewHolder(View convertView) {
        text = (TextView) convertView.findViewById(R.id.adapter_item_textview);
        image = (ImageView) convertView.findViewById(R.id.adapter_item_imageview);
    }
}

Monimutkaisemmissa riveissä ViewHoldering käyttäminen helpottaa ja tehostaa listan käyttämistä, koska vaikka oikean näkymän etsiminen convertView:stä kestää vain muutamia millisekuntteja, jos sen tekee esimerkiksi kymmenen kertaa voi lista nykiä selvästi kovassa käytössä.

Vinkki:

Listat ovat hyviä näyttämään tietoa, mutta vältä tiedon muokkauksen mahdollistamista listojen tai muiden AdapterView:n sisällä, esimerkiksi EditText widgeteillä. Tallentaminen ja oikean tiedon näyttäminen, kun listan näkymiä uudelleenkäytetään tulee todella vaikeaksi.