I'm making a custom Adapter, FieldAdapter, that extends BaseAdapter. I am created a GridView of LinearLayout views which contain a TextView and either a Spinner OR an EditText.
It seems that, after looking around a lot, that the optimized method is to use the ViewHolder method. But this seems to be optimized for a list of items that all share the same attributes.
What makes this a potential problem is the fact that I may have 4 views with a TextView and EditText, and another 5 that contain a TextView and a spinner so I can't assume a certain layout.
Is it optimal to have logic in my getView method that checks what kind of view needs to be returned for each item or is there a better way? Should I just have ViewHolder objects to hold the corresponding layouts? ie DropdownViewHolder and TextViewHolder?
Does this render the convertView useless?
The following is my code that works, but I've noticed that if I have a large list of items and scroll up and down quickly my entire list disappears which leads me to believe I'm not doing something correctly.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) this.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layoutSection = (LinearLayout) inflater.inflate(R.layout.dropdown, null);
TextView labelText = new TextView(this.context);
labelText.setText(data.get(position).getLabelText());
labelText.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
layoutSection.addView(labelText);
if (data.get(position).getFieldType().equalsIgnoreCase("dropdown")) {
Spinner spinner = new Spinner(this.context);
spinner.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
// Options
final List<String> options = new ArrayList<String>();
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<String>(context,
android.R.layout.simple_list_item_1, options);
spinner.setAdapter(spinnerAdapter);
layoutSection.addView(spinner);
} else if (data.get(position).getFieldType().equalsIgnoreCase("text")) {
EditText textbox = new EditText(this.context);
textbox.setText("Hi");
textbox.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
layoutSection.addView(textbox);
}
return layoutSection;
} else {
return convertView;
}
}
see those methods: getItemViewType & getViewTypeCount
Related
I have a custom adapter that list my items. in each Item I check database and draw some circles with colors.
As you see in code I check if convertView==null defines new viewHolder and draw my items. but when I scroll listview very fast every drawn data ( not title and texts) show wrongs!
How I can manage dynamic View creation without showing wrong data?!
UPDATE
This is my attempts:
I used ui-thread to update my list but the result is same and data drawing go wrong.
in second I try to load all data with my object so that there is no need to check db in adapter. but it problem is still remains...
finally I create the HashMap<key,LinearLayout> and cache every drawn layout with id of its item. So if it's drawn before I just load its view from my HashMap and every dynamic layout will create just once. But it still shows wrong data on fast scrolling! Really I don't know what to do next!
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
final ViewHolder viewHolder;
final MenuStructureCase item = getItem(position);
if (convertView == null) {
viewHolder = new ViewHolder();
convertView = this.mInflater.inflate(R.layout.fragment_menu_item, null);
viewHolder.menu_title = (TextView) convertView.findViewById(R.id.menu_title);
viewHolder.tag_list_in_menu_linear_layout = (LinearLayout) convertView.findViewById(R.id.tag_list_in_menu_linear_layout);
viewHolder.menu_delete = (ImageButton) convertView.findViewById(R.id.image_button_delete);
importMenuTags(viewHolder, getItem(position), viewHolder.tag_list_in_menu_linear_layout);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
viewHolder.menu_title.setText(item.getTitle());
}
return convertView;
}
and this is importMenuTags():
private void importMenuTags(ViewHolder viewHolder, MenuStructureCase item, LinearLayout layout) {
List<String> tags = db.getMenuTags(item.getTitle()); //this present list of string that contain my tags
for (String tag : tags) {
Drawable drawable = getContext().getResources().getDrawable(R.drawable.color_shape);
drawable.setColorFilter(Color.parseColor(each_tag_color), PorterDuff.Mode.SRC_ATOP);
RelativeLayout rl = new RelativeLayout(getContext());
LinearLayout.LayoutParams lparams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
lparams.setMargins(15, 15, 15, 15);
lparams.width = 50;
lparams.height = 50;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
rl.setBackground(drawable);
} else {
rl.setBackgroundDrawable(drawable);
}
rl.setLayoutParams(lparams);
layout.addView(rl);
}
}
You have to select data from db before adapter initialization. So that
getItem(position)
will return already a "ready" item-object.
You shouldn't set the values to Views inside
if (convertView == null) {
...
}
This code is only for a viewHolder initialization. You create a new one, if convertView is null or read it as tag.
Setting of values you have to do after viewHolder initialization, actually where you set the title.
But in order to increase a performance, you shouldn't select the values from db on each step of getView. You have to have everything prepared (already selected).
You can do this way:
First of all create method inside adapter class:
public void updateNewData(List<MenuStructureCase> newList){
this.currentList = newList;
notifyDataSetChanged();
}
Now call above method whenever you want to update ListView.
How to call with object of CustomAdapter:
mAdapter.updateNewData(YourNewListHere);
Hope this will help you.
Rendering of data takes times and may be that's causing the issue when you are scrolling fast.
You can restirct the scrolling ( like Gmail : use a pull to refresh ) so that a less amount to data is processed in a list view at single time .
use RecyclerView instead of listview for better performance
ListView recreates the view on scrolling .
May be you can explain more about your problem , then we can provide the inputs accordingly.
I am trying to create a listview with each list item is different and their layouts are created by code. The initial layouts look fine, but when i scroll out and in, all the layout items added programatically are added again, resulting duplicate items. How can I solve that problem?
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
MyHolder holder;
if (v == null) {
LayoutInflater li = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = li.inflate(R.layout.empty_item, null);
holder = new MyHolder(v);
v.setTag(holder);
} else {
holder = (MyHolder) v.getTag();
}
TextView textView = new TextView(activity);
textView.setText(items.get(position).getItems().get(i).getText());
textView.setLayoutParams(params);
holder.linearLayout.addView(textView);
return v;
}
class MyHolder{
public TextView tvTitle;
public LinearLayout linearLayout;
public CardView cardView;
public MyHolder(View base){
tvTitle = (TextView)base.findViewById(R.id.tvTitle);
cardView = (CardView)base.findViewById(R.id.card_view2);
linearLayout = (LinearLayout)base.findViewById(R.id.linearLayoutCard);
}
}
I'm not even sure how this code is running, because you have no return statement in your getView() method. But you need to return the convertView in getView() so that the ListView can reuse those when it needs to. Otherwise, it'll just keep asking for new views every time it needs them. So you would just put return v; at the end of getView().
Additionally, you are creating and adding new TextViews to your MyHolder objects outside of the if (v == null) block. Normally you would instantiate new views like this if the convertView is null. If it isn't, you just pass it through to the ListView or make updates to it before passing it back. So what's happening is, the convertView is available (not null), but instead of using it, you're adding a brand new TextView instead, which is why you are getting duplicates.
I've implemented this sample (2. Custom Adapter example)
http://www.mkyong.com/android/android-gridview-example/
and it works.
I then wanted to "refresh" the data with a new set, which didn't work until I did this:
imageAdapter.notifyDataSetChanged();
and then removed the following check in the ImageAdapter getView method :
if (convertView == null) {
Here is my current getView method
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View gridView;
// if (convertView == null) { // stopped my GridView from updating !!
if (true)
{
gridView = new View(context);
// get layout from mobile.xml
gridView = inflater.inflate(R.layout.mobile, null);
// set value into textview
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
} else {
gridView = (View) convertView;
}
return gridView;
}
I'm concerned it is now doing unnecessary processing over and over - i.e. inflating view multiple times?
Should I be creating a new GridView every time this is called ?
it didn't work because the following rows
TextView textView = (TextView) gridView
.findViewById(R.id.grid_item_label);
textView.setText(mobileValues[position]);
// set image based on selected text
ImageView imageView = (ImageView) gridView
.findViewById(R.id.grid_item_image);
imageView.setImageResource(R.drawable.square);
go outside the if/else. The check if (convertView == null) is necessary. If you don't have it you will inflate n different views, with n == getCount(). For n large it will be a problem. And you probably want to implement the android ViewHolder pattern, to give your users the best UX possible.
As correctly pointed out by #Vzsg, get rid also of gridView = new View(context);. It an additional useless allocation
I usually just update the adapter with the new results, and set it to the GridView.
So in your example, when I want to update my GridView - I do the following.
gridView.setAdapter(newAdapter);
You can have a utility method, such as that will make get a new Adapter much easier.
private ArrayAdapter getAdapter(String [] data){...}
the problems is, that I wan't to make ListView with elements which are containing image, description and two buttons. I'm making them in my own BaseAdapter extension, but fragment which is containing ListView is closing (wihtout errors in logcat..). I've found, that ListView is working well, when I'm not returning layout-type elements. So there is my sample with 'sample linear layout', which is not working.. Is there any possibility, to show layouts in ListView?
Here is my code:
Creating part:
lv = (ListView) getView().findViewById(R.id.main_wall_ambajes_lv);
AmbajAdapter aa = new AmbajAdapter(getActivity().getApplicationContext(), StaticData.ambajes);
lv.setAdapter(aa);
My getView method from adapter:
public View getView(int position, View convertView, ViewGroup parent) {
LinearLayout ll = new LinearLayout(getActivity());
ll.setOrientation(LinearLayout.VERTICAL);
ImageView iv = new ImageView(getActivity());
iv.setImageBitmap(placeholderBitmap);
ll.addView(iv);
ll.addView(iv);
ll.addView(iv);
ll.addView(iv);
return ll;
}
I don't know why you don't have any error however I don't think you proceed the correct way.
Usually you create the layout in the xml file of the layout folder and only inflate it in the getView(), for example as follow :
private LayoutInflater mInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
#Override
public View getView(int position, View view, ViewGroup parent) {
if (view == null) {
view = mInflater.inflate(R.layout.your_custom_layout, parent, false);
}
//your code for setting the image or other things goes here
//for example if you have a textView
TextView textView = (TextView) view.findViewById(R.id.my_textview_id);
textView.setText("my custom text for this cell");
return (view);
}
and your_custom_layout is simply the xml file of your layout.
Note that for performance reason due to cell recycling I only inflate the view when it is null and I only read once the LayoutInflater context and put it in mInflater. However for the best performance you should use a ViewHolder, but it is out of the scope of your question.
I am making bar charts in custom list view. when I scroll list, components get shuffle. How can it be stopped.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
LayoutInflater layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = layoutInflater.inflate(R.layout.stats_row_layout, null);
}
Stat stat = objects.get(position);
if (stat.isFlag()) {
LinearLayout linearLayout = (LinearLayout) convertView.findViewById(R.id.parentLayout);
linearLayout.setVisibility(LinearLayout.VISIBLE);
} else {
LinearLayout linearLayout = (LinearLayout) convertView.findViewById(R.id.parentLayout);
linearLayout.setVisibility(LinearLayout.GONE);
}
if (!stat.isExist()) {
stat.setExist(true);
LinearLayout linearLayout = (LinearLayout) convertView.findViewById(R.id.statGraphLayout);
linearLayout.removeAllViews();
if (stat.getmView() == null) {
ImageView mView = new StatBarChartVie(context, stat.getStatValues());
stat.setmView(mView);
}
LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.FILL_PARENT, 50);
stat.getmView().setLayoutParams(params);
linearLayout.addView(stat.getmView());
}
}
return convertView;
}
Using the visible / gone flags doesn't seem right in a list view.
Because of how a list view recycles views, that is probably the problem.
You may need to be a bit smarter about how you access the array. And not include invisible items in the count.
Whenever you change the data ( possibly overriding notifyDataSetChanged() calling its super), you could create a new Index ArrayList with only the values which are visible. So you no longer need to use the invisible settings. This way you only loop through the array on data changes.