Skip to content

Commit f789589

Browse files
committed
viewholder post added
1 parent 41bb8a7 commit f789589

File tree

5 files changed

+757
-0
lines changed

5 files changed

+757
-0
lines changed

_drafts/2018-10-15-viewholder.md

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
---
2+
layout: post
3+
title: "ViewHolder"
4+
date: 2018-10-15
5+
categories: ["Wzorce projektowe"]
6+
image: viewholder
7+
github: design-patterns/tree/master/viewholder
8+
description: "Wzorce projektowe / kreacyjny"
9+
keywords: "viewholder, wzorzec, wzorce projektowe, wzorzec kreacyjny, design patterns, android, java, programowanie, programming"
10+
---
11+
12+
## Zastosowanie
13+
`ViewHolder` (ang. `ViewHolder`) (wzorzec kreacyjny) ma za zadanie zoptymalizować proces renderowania widoku kolekcji elementów poprzez ponowne użycie obiektów istniejących widoków. W trakcie przewijania listy dla każdego elementu kolekcji wywoływana jest kosztowna operacja wiązania widoku `findViewById`. W efekcie wzrasta zużycie zasobów, powstaje więcej obiektów widoku niż to w danej chwili potrzebne, a także przewijanie listy nie jest płynne. `ViewHolder` likwiduje wspomniane problemy. Przechowuje referencję do związanych instancji widoku w postaci obiektu typu `ViewHolder` i w razie potrzeby udostępnia je do ponownego użycia. Ilość stworzonych obiektów `ViewHolder` jest zależna od wielkości ekranu urządzenia i wynosi mniej więcej tyle ile maksymalnie może zostać wyświetlonych elementów w widoku (naraz bez przewijania).
14+
15+
## Ograniczenia
16+
Wykorzystanie wzorca `ViewHolder` nieznacznie zwiększa poziom skomplikowania.
17+
18+
## Użycie
19+
Wzorzec używany jest w widokach kolekcji w celu optymalizacji zużycia zasobów pamięci urządzenia. Dla niewielkich kolekcji (mieszczących się w całości na ekranie) różnica w wydajności może nie występować lub być niezauważalna, jednakże gdy nie mieszczą się one na ekranie i możliwe jest przewinięcie listy wówczas skok wydajności jest zauważalnym gołym okiem.
20+
21+
## Implementacja
22+
Wewnętrzna klasa `ViewHolder` przechowuje referencję do obiektów widoku `View`. Referencje są ustawiane w klasie adaptera bezpośrednio na obiekcie viewholder lub przekazywany jest cały widok do konstruktora `ViewHolder`, który sam dba o związanie obiektów widoku. Klasa `Adapter` wykorzystuje obiekt typu `ViewHolder` do pobrania referencji i przekazania danych do widoków.
23+
24+
![ViewHolder diagram](/assets/img/diagrams/viewholder.svg){: .center-image }
25+
26+
W celu uproszczenia idei wzorca `ViewHolder` przedstawiona poniżej implementacja dedykowania jest dla widoku kolekcji typu `ListView` oraz klasy adaptera rozszerzającego `BaseAdapter`. Jednakże zalecane jest wykorzystanie widoku kolekcji `RecyclerView` wraz implementacją klasy rozszerzającej `RecyclerView.Adapter`.
27+
28+
{% highlight java %}
29+
public class ItemAdapter extends BaseAdapter {
30+
31+
private List<Item> items;
32+
33+
public ItemAdapter(List<Item> items) {
34+
this.items = items;
35+
}
36+
37+
class ViewHolder {
38+
39+
private TextView textView;
40+
private ImageView imageView;
41+
}
42+
43+
@Override
44+
public View getView(int position, View convertView, ViewGroup parent) {
45+
ViewHolder viewHolder;
46+
47+
//check if received view has been inflated before
48+
if(convertView == null) {
49+
//get layout to inflate
50+
LayoutInflater inflater = LayoutInflater.from(getContext());
51+
convertView = inflater.inflate(R.layout.item, parent, false);
52+
viewHolder = viewHolder(convertView);
53+
54+
//inflate views into viewHolder
55+
viewHolder.textView = convertView.findViewById(R.id.textView);
56+
viewHolder.imageView = convertView.findViewById(R.id.imageView);
57+
convertView.setTag(viewHolder);
58+
}
59+
else {
60+
//receive inflated view from memory
61+
viewHolder = (ViewHolder) convertView.getTag();
62+
}
63+
64+
//set data into views
65+
Item item = items.get(position);
66+
viewHolder.textView.setText(item.getText());
67+
viewHolder.imageView.seImageResource(item.getImage());
68+
69+
return convertView;
70+
}
71+
72+
//here should be other adapter methods override from superclass
73+
@Override
74+
public int getCount() {
75+
return items.size();
76+
}
77+
78+
@Override
79+
public Item getItem(int position) {
80+
return item.get(position);
81+
}
82+
83+
@Override
84+
public long getItemId(int position) {
85+
return position;
86+
}
87+
}
88+
{% endhighlight %}
89+
90+
Aktywność lub fragment tworzą tworzą widok listy do którego przekazany jest adapter z danymi kolekcji wykorzystujący `ViewHolder`.
91+
92+
{% highlight java %}
93+
//some activity or fragment lifecycle methods
94+
//this work can be done in e.g. in onCreate or onCreateView
95+
96+
//create or get some collection
97+
List<Item> items = new ArrayList<>();
98+
items.add(new Item("Item1", R.drawable.image1));
99+
items.add(new Item("Item2", R.drawable.image2));
100+
items.add(new Item("Item3", R.drawable.image3));
101+
//more and more items
102+
103+
//insert adapter with ViewHolder into ListView
104+
ListView listView = findViewById(R.id.listView);
105+
ItemAdapter adapter = new ItemAdapter(items);
106+
listView.setAdapter(adapter);
107+
{% endhighlight %}
108+
109+
## Przykład
110+
Aplikacja `FoodDeliver` umożliwia użytkownikom dokonanie zamówienia dostawy jedzenia z wybranej restauracji. Wiele widoków aplikacji (w tym widok pozycji menu z danej restauracji) wykorzystuje mechanizm widoku przewijalnej listy. W celu optymalizacji wydajności działania listy do realizacji tego zadania użyto kontrolkę `RecyclerView` wraz z implementacją `ViewHolder`.
111+
112+
{% highlight java %}
113+
114+
public class FoodAdapter extends RecyclerView.Adapter<FoodAdapter.ViewHolder> {
115+
116+
private Context context;
117+
private List<Food> items;
118+
119+
public FoodAdapter(Context context, List<Food> items) {
120+
this.context = context;
121+
this.items = items;
122+
}
123+
124+
class ViewHolder extends RecyclerView.ViewHolder {
125+
126+
private ImageView imageView;
127+
private TextView nameView;
128+
private TextView priceView;
129+
private Button cartButton;
130+
131+
ViewHolder(View view) {
132+
super(view);
133+
imageView = view.findViewById(R.id.food_image);
134+
nameView = view.findViewById(R.id.food_name);
135+
priceView = view.findViewById(R.id.food_price);
136+
cartButton = view.findViewById(R.id.food_cart);
137+
}
138+
139+
public void setData(Food item) {
140+
Picasso.with(context).load(item.getImageUrl()).into(imageView);
141+
nameView.setText(item.getName());
142+
priceView.setText(item.getPrice() + " PLN");
143+
cartButton.setOnClickListener(v -> addToCart(item));
144+
}
145+
}
146+
147+
@Override
148+
public FoodAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
149+
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.food_item, parent, false);
150+
return new FoodAdapter.ViewHolder(view);
151+
}
152+
153+
@Override
154+
public void onBindViewHolder(@NonNull FoodAdapter.ViewHolder holder, int position) {
155+
Food item = items.get(position);
156+
holder.setData(item);
157+
}
158+
159+
@Override
160+
public int getItemCount() {
161+
return items.size();
162+
}
163+
164+
private void addToCart(Food item) {
165+
//add dish to cart action
166+
}
167+
}
168+
{% endhighlight %}
169+
170+
Aktywność lub fragment pobierają dane z serwera o dostępnych pozycjach menu. Następnie tworzony jest adapter, który jest przekazywany do widoku kolekcji `RecyclerView`.
171+
172+
{% highlight java %}
173+
//user chose restaurant and goes to dishes selection screen
174+
//app receives data from server for choosen restaurant and load into items collection
175+
List<Food> items = new ArrayList<>();
176+
items.add("Pizza Margherita", 15.00, "http://example.com/margherita.jpg");
177+
items.add("Pizza Pepperoni", 18.00, "http://example.com/pepperoni.jpg");
178+
items.add("Pizza Hawaii", 20.00, "http://example.com/hawaii.jpg");
179+
//and more more items
180+
181+
//inflate layout and other lifecycle stuff
182+
RecyclerView recyclerView = findViewById(R.id.recyclerView);
183+
184+
//insert adapter into recyclerView
185+
LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
186+
recyclerView.setLayoutManager(layoutManager);
187+
FoodAdapter adapter = new FoodAdapter(getActivity(), items);
188+
recyclerView.setAdapter(adapter);
189+
190+
//scrolling the list is smooth and faster
191+
{% endhighlight %}
192+
193+
## Biblioteki
194+
Użycie widoku typu `RecyclerView` wymusza na programiście implementację wewnętrznej klasy `RecyclerView.ViewHolder`. Realizacja wzorca w starszych widokach kolekcji np.: `ListView`, `GridView` jest opcjonalna.

0 commit comments

Comments
 (0)