ListView – kontrolka dostępna od SDK 2.0 w przestrzeni nazw Osp::Ui::Controls, która wyświetla opcje wyboru w postaci listy.
Artykuł jest kontynuacją wcześniejszego artykułu pod tytułem: UI: ListView (Lista) i zaznajomienie się z nim jest obowiązkowe.
Wszystkie informacje w nim zawarte, jak również kod źródłowy są aktualne i będą wykorzystane także w tym artykule.
Naszym celem będzie stworzenie listy o następującym wyglądzie:
Jest to pewnego rodzaju lista kontaktów 🙂 Po zaznaczeniu opcji pojawia się wiek wybranego kontaktu i podświetlenie opcji – to tylko taki przykład.
Wiek będzie jakąś liczbą losową, aby nie komplikować naszej aplikacji o dodatkowe struktury danych przechowujące kontakty.
My skorzystamy ze zwykłej kolekcji Stringów z poprzedniego przykładu inicjalizując ją po prostu innymi danymi.
Musimy się zastanowić jak pociąć nasz obrazek na mniejsze kawałki i za ich pomocą rysować listę o dowolnych rozmiarach na dowolnych urządzeniach (z rożnymi rozdzielczościami).
W przypadku aplikacji J2ME, gdzie urządzenia miały dość ograniczone zasoby, z opcji listy zaznaczonej szarym gradientem wyciąłbym po prostu fragment o szerokości 1px i wysokości 100% opcji.
Przy rysowaniu zastosowałbym pętlę, która wstawiałaby ten fragment obrazka tyle razy, ile wynosi szerokość ekranu.
Zaletą tego sposobu jest na pewno mniejsze użycie pamięci (dyskowej), ale o wiele razy dłuższe renderowanie grafiki.
Jest to temat rzeka, dlatego ograniczę się do zoptymalizowania aplikacji pod telefony z rozdzielczością WVGA.
W związku z tym, że aplikację piszemy tylko dla trybu portretowego, maksymalna szerokość listy dla WVGA to 480px.
Tło opcji listy po wycięciu posiada rozmiary: 480px x 41px.
ListItem.png – tło bez podświetlenia:
ListItemHover.png – tło z podświetleniem:
Czas otworzyć projekt z poprzedniego artykułu – zaczynami programować 🙂
Dodajemy nasze obrazki (oba) do projektu:
Po dodaniu naszych obrazków, środowisko stworzyło odpowiednie foldery z obrazkami:
Aby zaimplementować rysowanie opcji listy, należy stworzyć nową klasę implementującą interfejs: Osp::Ui::Controls::ICustomElement.
Interfejs wymaga implementacji tylko jednej metody:
virtual bool OnDraw(Osp::Graphics::Canvas &canvas, const Osp::Graphics::Rectangle &rect, Osp::Ui::Controls::ListItemDrawingStatus status);
Po stworzeniu klasy przenosimy plik nagłówkowy MyCustomListItem.h z folderu src do inc. Wystarczy w oknie Project Explorer przeciągnąć go myszką.
Następne modyfikujemy plik nagłówkowy MyCustomListItem.h wg. poniższego wzorca. Możesz skopiować cały kod i wkleić w miejsce starego.
/* * MyCustomListItem.h * * Created on: 03-10-2011 * Author: markac */ #ifndef MYCUSTOMLISTITEM_H_ #define MYCUSTOMLISTITEM_H_ #include <FApp.h> #include <FBase.h> #include <FUi.h> #include <FGraphics.h> using namespace Osp::Base; using namespace Osp::Graphics; class MyCustomListItem : public Osp::Ui::Controls::ICustomElement { public: MyCustomListItem(Osp::Base::String& person, int age); virtual bool OnDraw(Osp::Graphics::Canvas &canvas, const Osp::Graphics::Rectangle &rect, Osp::Ui::Controls::ListItemDrawingStatus status); protected: static Osp::Graphics::Bitmap* __pItemNormalBitmap; static Osp::Graphics::Bitmap* __pItemHoverBitmap; String* __person; int __age; }; #endif /* MYCUSTOMLISTITEM_H_ */
Implementacja metody rysującej w pliku MyCustomListItem.cpp może wyglądać jak poniżej.
/* * MyCustomListItem.cpp * * Created on: 03-10-2011 * Author: markac */ #include "MyCustomListItem.h" Osp::Graphics::Bitmap* MyCustomListItem::__pItemNormalBitmap = null; Osp::Graphics::Bitmap* MyCustomListItem::__pItemHoverBitmap = null; MyCustomListItem::MyCustomListItem(Osp::Base::String& person, int age) { __person = &person; __age = age; if (__pItemNormalBitmap == null) { // Pierwsze tworzenie obrazka __pItemNormalBitmap = Osp::App::AppResource::GetInstance()->GetBitmapN("ListItem.png"); } if (__pItemHoverBitmap == null) { // Pierwsze tworzenie obrazka __pItemHoverBitmap = Osp::App::AppResource::GetInstance()->GetBitmapN("ListItemHover.png"); } } bool MyCustomListItem::OnDraw(Osp::Graphics::Canvas &canvas, const Osp::Graphics::Rectangle &rect, Osp::Ui::Controls::ListItemDrawingStatus status) { Osp::Graphics::Font font; font.Construct(FONT_STYLE_PLAIN, 28); canvas.SetFont(font); if (status == Osp::Ui::Controls::LIST_ITEM_DRAWING_STATUS_NORMAL) { canvas.SetForegroundColor(Color(77, 77, 77)); canvas.DrawBitmap(Point(0, 0), *__pItemNormalBitmap); canvas.DrawText(Point(20, (rect.height / 2) - font.GetMaxHeight() / 2), *__person); } else if (status == Osp::Ui::Controls::LIST_ITEM_DRAWING_STATUS_PRESSED || status == Osp::Ui::Controls::LIST_ITEM_DRAWING_STATUS_HIGHLIGHTED) { canvas.SetForegroundColor(Color::COLOR_WHITE); canvas.DrawBitmap(Point(0, 0), *__pItemHoverBitmap); canvas.DrawText(Point(20, (rect.height / 2) - font.GetMaxHeight() / 2), *__person); /** * Pozycjonowanie wieku w prostokącie i centrowanie */ Osp::Graphics::Font ageFont; ageFont.Construct(FONT_STYLE_PLAIN, 18); canvas.SetFont(ageFont); String age; age.Append(__age); Dimension ageDimension; ageFont.GetTextExtent(age, age.GetLength(), ageDimension); int x = rect.width - 48 - (ageDimension.width / 2); int y = (rect.height / 2) - (ageDimension.height / 2); canvas.DrawText(Point(x, y), age); } return true; }
Pozostało nam jeszcze dołączyć plik nagłówkowy MyCustomListItem.h w pliku Form1.h:
#include "MyCustomListItem.h"
i lekko zmodyfikować metodę CreateItem() z pliku Form1.cpp zaimplementowaną w poprzednim artykule. Oto cały kod nowej metody:
Osp::Ui::Controls::ListItemBase* Form1::CreateItem(int index, int itemWidth) { ListAnnexStyle style = LIST_ANNEX_STYLE_NORMAL; CustomItem* pItem = new CustomItem(); pItem->Construct(Osp::Graphics::Dimension(itemWidth, 41), style); String* text; __strings.GetAt(index, text); int age = Utility::Math::Rand() % 99; // 0-99 MyCustomListItem* myItem = new MyCustomListItem(*text, age); int elementId = index; pItem->AddElement(Osp::Graphics::Rectangle(0, 0, itemWidth, 41), elementId, *myItem); return pItem; }
W związku z tym, że operujemy na obrazkach, powinniśmy dodać odpowiednie uprawnienie: IMAGE do pliku manifest.xml.
Powiadomienie o tym fakcie ukaże się w dymku przed niektórymi funkcjami.
Uprawnienie jest wymagane na urządzeniu docelowym. W przypadku emulatora, jesteśmy tylko o tym informowani.
Dociekliwi programiści zapewne zauważą, że nigdzie nie są zwalniane zasoby tworzone w konstruktorze klasy MyCustomListItem. Chodzi tutaj o funkcję GetBitmapN().
W projekcie do pobrania (ukaże się wkrótce) zostanie dodana nowa metoda statyczna zwalniająca te zasoby podczas zamykania aplikacji.
A o to efekt końcowy:
Działanie aplikacji demonstruje także poniższy film:
[yframe url=’http://www.youtube.com/watch?v=KQntRfOM7d0′]
Komentarze mile widziane.
Akurat teraz tworzę listy i zastanawiałem się jak w prosty sposób dodać dodatkowe informacje, a tu proszę…. kompletne rozwiązanie. Świetny tutorial.
Trzeba pamiętać, żeby zwolnić pamięć zajmowaną przez myItem. Na tamten czas o tym zapomniałem i jeszcze nie zmodyfikowałem artykułu.
Myślę, że jest to dość jasne ;D
MarekMariusz jak zwykle mnie nie zawiódł, a co do konkretów to uważam ten typ tworzenia listy jest lepszy od poprzedniego nie tylko z powodu lepszego wyglądy, ale i jak autor pisze jest mniejsze użycie pamięci co jest naprawdę dużym plusem. Widzę też, że muszę przyspieszyć prace nad animacją, bo przydałaby się do demonstracji.