Android ListView with Custom Adapter Has Visual Glitch - android

I'm new to Android programming and trying to figure out how to optimize my ListView adapter. I wrote a custom adapter to add CardViews to my ListView:
public class CardAdapter extends BaseAdapter {
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context ) {
this.context = context;
}
public void add( CardView view ) {
cards.add( view );
}
#Override
public int getCount() {
return cards.size();
}
#Override
public int getViewTypeCount() {
return getCount();
}
#Override
public int getItemViewType(int position) {
return position;
}
#Override
public Object getItem(int i) {
if( i >= cards.size() || i < 0 )
return null;
return cards.get( i );
}
#Override
public long getItemId(int i) {
return cards.get(i).getId();
}
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if( view == null ) {
view = (CardView) getItem( position );
}
return view;
}
}
The MainActivity is something like:
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListView list = findViewById(R.id.notesList);
CardAdapter adapter = new CardAdapter(getBaseContext());
adapter.add( makeCard() );
// repeat making cards however many times...
list.setAdapter(adapter);
}
private CardView makeCard() {
CardView card = new CardView(this);
// Do some things to customize the CardView
return card;
}
}
Now it seems like Android is trying to recycle the views, but is doing a lousy job of it. Initially the page looks fine, but as soon as I begin scrolling, the cards start to move all over the screen, the spacing changes, the cards begin to overlap, etc. .
Note that I only scrolled for a couple seconds, this was not a gradual change, the cards were kind of 'jumping' around the screen. The only solution I have come up with so far is changing my getView to be:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
return (CardView) getItem(position);
}
And this works, but even loading ~10 items is very slow, and the scrolling lags.

Kindly use RecyclerView it uses view holder pattern that resolve all these lagging issues

Change your implementation from ListView to RecyclerView.
RecyclerView is a ViewGroup added to the android studio as a successor
of the GridView and ListView. It is an improvement on both of them and
can be found in the latest v-7 support packages. It has been created
to make possible construction of any lists with XML layouts as an item
which can be customized vastly while improving on the efficiency of
ListViews and GridViews. This improvement is achieved by recycling the
views which are out of the visibility of the user. For example, if a
user scrolled down to a position where the items 4 and 5 are visible;
items 1, 2 and 3 would be cleared from the memory to reduce memory
consumption.
From GeeksforGeeks.
Ps: the link has a lot of explanation, tutorial and codes...
The RecyclerView was created specifically to solve the kind of problem you're having. It has a lot of improvements over ListView, like less memory consumption, less lagging, better scrooling...

ok,its too simple
first remember u must pass what u want use in adapter,but now u just pass a (this) to this adapter.
use code below for your Constructor .
have good codding.
private final ArrayList<CardView> cards = new ArrayList<CardView>();
private Context context;
public CardAdapter( Context context , ArrayList<CardView> cards) {
this.context = context;
this.cards =cards
}

Related

StaggeredGridLayoutManager items in adjacent columns swapping positions

I am using a StaggeredGridLayoutManager to layout Cardviews, where each card has a button which can expand and contract the card to show different information. The cards are laid out in two columns vertically across the screen. The cards are backed by a RealmObject and I am using a RealmRecyclerViewAdapter to lay them out in the recycler view but I don't think that is the problem.
When I tap on a button which causes onBindViewHolder() to be called, occasionally my cards will swap places across the columns. Only cards that are in adjacent columns swap and I can show this by increasing the number of columns to three or four and watching the behaviour. By changing the number of columns to one the cards stop swapping. There is a nice animation when the cards swap with eachother but if I turn off the animations (recyclerView.setItemAnimator(null)) the cards still swap just without the animation.
The cards only swap position when onBindViewHolder() is called and it happens often enough to be obvious but I am not able to replicate the functionality, it seems to happen randomly.
Here is some code:
Fragment that displays the RecyclerView:
public class DeviceCardListFragment extends Fragment {
private RecyclerView recyclerView;
private RealmResults<Bank> deviceList;
public int locationId = 0;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.card_list_fragment, container, false);
recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
//Turn off the blinking animation when the Realm Refreshes
RecyclerView.ItemAnimator animator = recyclerView.getItemAnimator();
if (animator instanceof SimpleItemAnimator) {
((SimpleItemAnimator)animator).setSupportsChangeAnimations(false);
}
StaggeredGridLayoutManager manager = new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
manager.setGapStrategy(GAP_HANDLING_NONE);
recyclerView.setLayoutManager(manager);
recyclerView.setAdapter(new StaggeredDeviceAdapter(getContext(), deviceList));
recyclerView.setItemViewCacheSize(40);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
}
StaggeredDeviceAdapter:
public class StaggeredDeviceAdapter extends RealmRecyclerViewAdapter<Bank, StaggeredDeviceViewHolder> {
private RealmResults<Bank> devices;
private Context context;
public StaggeredDeviceAdapter(Context context, RealmResults<Bank> list) {
super(list, true);
this.devices = list;
this.context = context;
}
#Override
public StaggeredDeviceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View layoutView = LayoutInflater.from(context).inflate(R.layout.device_card, null);
//setHasStableIds(true);
StaggeredDeviceViewHolder holder = new StaggeredDeviceViewHolder(layoutView);
return holder;
}
#Override
public void onBindViewHolder(StaggeredDeviceViewHolder holder, int position) {
holder.setValues(devices.get(position), context);
}
}
StaggeredDeviceViewHolder:
I have pulled out my actual view configuration into another class for reusability which is quite large but I don't think anything in there is important.
public class StaggeredDeviceViewHolder extends RecyclerView.ViewHolder {
private View view;
DeviceCardHolder holder;
public StaggeredDeviceViewHolder(View view) {
super(view);
holder = new DeviceCardHolder(view, false);
//holder.initialise();
this.view = view;
}
//Called from outside the class
//Sets all of the fields to whatever values we want
public void setValues(Bank bank, Context context) {
holder.initialise();
holder.setValues(bank, context);
}
}
Please let me know if you want any other information from me.
EDIT:
Adding Realm Information
deviceList = realm.where(Bank.class).equalTo("locationSection",location).findAllSorted("locationID");
None of those fields are being changed in code, in fact they don't even have setters.
Because the realm is sorted and the cards only swap along the horizontal axis makes me think that the issue is with the StaggeredGridLayoutManager rather than Realm.
EDIT:
Through additional testing I have been able to reliably replicate the issue.
I am using a StaggeredGridLayoutManager because my cards can change size. This is done by pressing a button on the card. Expanding and returning the card to its original size does not cause onBindViewHolder() to be called. The cards below the ones who's size is changing swap columns when onBindViewHolder() is called after one of the cards has changed size. The cards don't swap every time but I would say it happens around 80% of the time. Only cards directly underneath the ones I am manipulating swap positions.
This makes me think that the StaggeredGridLayoutManager is getting confused as to where its indexes are supposed to be when the cards change size, maybe there is a setting about indexes that I am missing....
EDIT:
The issue I am having is basically the same one outlined in this question:
… but it doesn't have an answer

Recycler View Onclick method in Activity?

I'm trying to create a UI similar to Google Keep. I know how to create a staggered View using a Recycler View. If i click a specific Card. Then it has to open a activity.
I can achieve this using onclick method.
This same scenario happens in atleast 5 different Activities in my App.
My question is that
Can I use this single Adapter in all those 5 places ?
If yes, where should i place the onclick actions ?
If no, How can I Create a staggered layout like Keep?
Thanks in Advance!
(See application for RecyclerView below in edits)
Like I mentioned in my comment, it's certainly fine to have separate adapters for all your Activities which use different data and views. As your app data and layouts get more complex, so does your code...that's just the way it is.
But if some of your Activities used similar data in their ListViews -- maybe, for example, two TextViews and an ImageButton -- you could save some effort by defining a single adapter that can be used for multiple Activities. You would then instantiate separate objects for each Activity, similar to the way you would create several ArrayAdapter<String> objects to populate multiple ListViews.
The BaseAdapter is a great class to extend when writing a custom adapter. It's flexible and allows you complete control over the data that's getting shown in your ListView. Here's a minimal example:
public class CustomBaseAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> listData;
public CustomBaseAdapter(Context context, ArrayList<String> listData) {
this.context = context;
this.listData = listData;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.your_list_item_layout, parent, false);
//populate the view with your data -- some examples...
TextView textData = (TextView) convertView.findViewById(R.id.yourTextView);
textData.setText(listData.get(position));
ImageButton button = (ImageButton) convertView.findViewById(R.id.yourImageButton);
button.setOnClickListener(new View.OnClickListener() {
//...
//...
});
}
return convertView;
}
#Override
public Object getItem(int position) {
return 0;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getCount() {
return listData.size();
}
}
So the key part of this code is obviously the getView() method, which is called every time the ListView needs some data to display. For efficiency, views are stored in something called a convertView so they may be re-used and not have to be inflated every time a view appears on the screen.
So what we do in getView() is first find out if the convertView exists. If it does, we just pass that back to the calling ListView because everything should already be instantiated and ready to go. If the convertView is null, it means the data hasn't been instantiated (or needs to be re-instantiated for whatever reason), and so we inflate a brand new view and populate it with our data.
So in the case of this example adapter above, if several of your Activities all displayed a single list of Strings, you could reuse this adapter for each one, passing in a different ArrayList<String> through the constructor each time you created a new object. But obviously you could pass in more than just Strings if you had more data to show. The level of complexity is up to you. And if the difference among your Activities was too great, I would just create custom versions of this class for all of them and just instantiate them and populate them however you'd like. It will keep all your data very organized and encapsulated.
Hope this helps! Feel free to ask questions for more clarification if you need it.
EDIT IN RESPONSE TO COMMENTS
Since you are using a RecyclerView instead of just plain ListViews (which I, for some reason, totally forgot) you could still do something very similar using a RecyclerView.Adapter<YourViewHolder> instead. The difference would be that instead of inflating the views in a getView() method, they are inflated inside your custom ViewHolder, which I assume you already have. The code might look something like this:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<StringViewHolder> {
private final List<String> items;
public CustomRecyclerViewAdapter(ArrayList<String> items) {
this.items = items;
}
#Override
public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//So instead of inflating the views here or in a getView() like in
//in the BaseAdapter, you would instead inflate them in your custom
//ViewHolder.
return new StringViewHolder(parent);
}
#Override
public void onBindViewHolder(StringViewHolder holder, int position) {
holder.setModel(items.get(position));
}
#Override
public long getItemId(int position) {
return items.get(position).hashCode();
}
#Override
public int getItemCount() {
return items.size();
}
}

RecyclerView set views to be recycled

in my app, I'm implementing a recyclerview. My dataset for this recyclerview will have varying sizes according to the options that I set for the data to be displayed on the recyclerview.
One of the actions that I take with my recyclerview is to "expand" an item when a click is done on it, displaying further options in it. When pressing on this "expanded" item, I perform the action of "closing" it. Also, there can only be on "expanded" item at maximum at any moment.
The thing is that I understand that recyclerview recycles its row-views when they get out of sight for improved performance. However, because I am trying to have only one "expanded" item at a time, this recycling messes it up quite a lot.
What happens right now is that when I "expand", say the item related to position 1 of my dataset, as shown in the image below.
When I scroll down, I will see that the rowview for this item being recycled at a random chance since I will see this "expanded" view on items that I have not set to be "expanded", as shown in the image below.
And of course, when this happens, then when I scroll back to the item that I have selected to "expand", it will be "closed" as you would have expected.
So I have been thinking that I could resolve this problem by setting the possible number of views to be something like 80% of my dataset size will decrease the possibility of this problem occurring while still reduced, but enjoy some improved performance.
Another solution I thought about was disabling this "expanded" view from being recycled for other views and when this "expanded" item's position comes into screen, it gets bounded to this specific view. I thought of this solution after seeing that there is a concept of "scrap" and "recycle" for recyclerview, but I am not so sure if this method is even possible because I think I have only vaguely understood this side of recyclerview.
That being said, my question is are there ways for me to set the number of views to be recycled for a recycled view? Or even better, having one view from being recycled for items other than the "expanded" item?
Thanks in advance.
EDIT:
here's my (I know it's very messy I'm sorry...) code for my adapter:
public class DrinkMenuItem extends RecyclerView.Adapter<DrinkMenuItem.ViewHolder> {
private Context context;
private ViewGroup parent;
private ArrayList<Drink> menu;
private ArrayList<DrinkSelected> selected;
private DrinkMenuBasketItem selectedAdapter;
public int expanded = -1;
public boolean expandedVisible = false;
private DrinkMenuDropdownItem dropdownAdapter;
public static class ViewHolder extends RecyclerView.ViewHolder {
public RelativeLayout layout;
public TextView name, price;
public ListView dropdown;
public RelativeLayout basket;
public boolean tabbed = false;
public ViewHolder(View itemView) {
super(itemView);
layout = (RelativeLayout)itemView.findViewById(R.id.drink_menu_layout);
name = (TextView)itemView.findViewById(R.id.drink_menu_name);
price = (TextView)itemView.findViewById(R.id.drink_menu_price);
dropdown = (ListView)itemView.findViewById(R.id.drink_menu_dropdown_list);
basket = (RelativeLayout)itemView.findViewById(R.id.drink_menu_basket_button);
}
}
public DrinkMenuItem(Context context, ArrayList<Drink> menu, ArrayList<DrinkSelected> selected, DrinkMenuBasketItem selectedAdapter) {
this.context = context;
this.menu = menu;
this.selected = selected;
this.selectedAdapter = selectedAdapter;
this.dropdownAdapter = null;
}
public void updateDropdown(int requestedOption, int responsedOptionitem) {
dropdownAdapter.updateSelectedOption(requestedOption, responsedOptionitem);
notifyDataSetChanged();
}
// Create new views (invoked by the layout manager)
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
this.parent = parent;
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_drink_menu, parent, false);
return new ViewHolder(itemView);
}
// Replace the contents of a view (invoked by the layout manager)
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Drink d = menu.get(position);
holder.name.setText(d.getName());
holder.price.setText(d.getPrice() + d.totalAdditionalPrice() + "원");
if(position == expanded) {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
setOnClickEvent(holder, position, parent);
}
private void setOnClickEvent(final ViewHolder holder, final int position, final ViewGroup parent) {
holder.layout.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (!holder.tabbed) {
//close dropdown of expanded view
if(expanded != -1) notifyItemChanged(expanded);
//make dropdown
dropdownAdapter = new DrinkMenuDropdownItem(context, menu, position, holder.price);
holder.dropdown.setAdapter(dropdownAdapter);
setListViewHeight(holder.dropdown);
//set dropdown-related stuff
holder.tabbed = true;
holder.basket.setVisibility(View.VISIBLE);
expanded = position;
expandedVisible = true;
((RecyclerView) parent).smoothScrollToPosition(position);
} else {
//delete dropdown
holder.dropdown.setAdapter(null);
menu.get(position).returnToUnselected();
holder.price.setText(menu.get(position).getPrice() + "원");
setListViewHeight(holder.dropdown);
expanded = -1;
expandedVisible = false;
//reset dropdown-related stuff
holder.tabbed = false;
holder.basket.setVisibility(View.GONE);
}
}
});
...
}
}
I have exactly same issue in my project. I did not succeed solving it with recyclerView. But the solution would be one of the following:
Create an expandableListView instead of recyclerView and everything will work great.
Create a ScrollView, and put a LinearLayout with android:orientation="vertical". Then, create a loop and insert all your custom views, and set click listener where you wish to expand.
Use an Expandable RecyclerView Library like one of these:
https://github.com/h6ah4i/android-advancedrecyclerview
https://github.com/bignerdranch/expandable-recycler-view

How to handle card's button OnClick event in GridView? Best practice

I'm trying to find the best solution to handle OnClick event, which generates by my card's button (see the picture bellow) within GridView.
So as you can see, I have just a normal GridView with cells made of my custom Card.
I just initialize GridView and it's adapter:
mGrid = (GridView) findViewById(R.id.grid);
mAdapter = new ImageTopicsAdapter(..blah blah blah..);
mGrid.setAdapter(mAdapter);
As you probably know I can easily handle OnClick events generated by GridView. But it will work only if I click on the card itself:
mGrid.setOnItemClickListener(..blah blah blah..);
I want to build something similar to this (see code bellow), so I can easily "implement" my Activity to handle my card's button OnClick event:
mGrid.setOnItemButtonClickListener(..blah blah blah..);
What is the best (clean\easy\elegant) way to do this?
Any help is truly appreciated. Alex. P.S. Sorry for my English:)
Since you want to dispatch to your activity, I would recommend exposing a method in the activity and call it directly from your click listener. The shortest (and cleanest from my perspective):
in your Adapter, say ArrayAdapter
define to listen for clicks (to avoid multitude of anonymous listener instances)
dispatch a call directly to your activity (since every view context is an activity)
context above can be treated as your ApplicationActivity only if you didn't manually provide some other context, say application context
private final MyAdapter extends ArrayAdapter implements View.OnClickListener {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(this);
return card;
}
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing()) {
applicationActivity.onCardButtonClick();
}
}
}
// in your ApplicationActivity
public final class ApplicationActivity extends Activity {
...
public void onCardButtonClick() {
// deal with your click
}
}
There are other, textbook options (setting a listener, or activity in your view creation and so forth) but I avoid them since they don't solve absolutely anything.
They just add more dust in your code.
Any View context defined properly points to the activity (since it is a context too) which holds all view structure. This way you can access your activity quick and relatively easy.
BTW Event bus is not a good option since event buses are great for one-to-many relations (one dispatcher, many listeners) but add more complexity when used intensively for one-to-one calls (dispatcher-listener)
Addition for the comment
You can tweak a little the code and rather using the adapter, you can dispatch directly from your cell. In other words rather using the adapter as a delegate, create an anonymous listener and then reach and call the activity directly from your card button click:
public final MyAdapter extends ArrayAdapter {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// inflate your card then get a reference to your button
View card = ....;
card.findViewById(R.id.YOUR_BUTTON_ID).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (activity != null && !activity.isFinishing() && !activity.isDestroyed()) {
applicationActivity.onCardButtonClick();
}
}
});
return card;
}
}
Addition for the comment - Compound View
To encapsulate all cell logic, you can create a custom view from scratch or use a compound view. The example below is using a compound view:
public class ApplicationActivity extends Activity {
....
public void onCardButtonClick(Cell cell) {
// do whatever you want with the model/view
}
}
// ViewModel instances are used in your adapter
public final class ViewModel {
public final String description;
public final String title;
public ViewModel(String title, String description) {
this.title = title != null ? title.trim() : "";
this.description = description != null ? description.trim() : "";
}
}
public final class Cell extends LinearLayout {
private View button;
private ViewModel model;
// ViewModel is data model and is the list of items in your adapter
public void update(ViewModel model) {
this.model = model;
// update your card with your model
}
public ViewModel getModel() {
return model;
}
#Override
protected void onAttachedToWindow() {
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener {
#Override
public void onClick(View view) {
ApplicationActivity activity = (ApplicationActivity) view.getContext();
if (model != null && activity != null && !activity.isFinishing() && !activity.isDestroyed() {
activity.onCardButtonClick(Cell.this);
}
}
});
}
}
// then your adapter `getView()` needs to inflate/create your compound view and return it
public final MyAdapter extends ArrayAdapter {
private final List<ViewModel> items;
public MyAdapter() {
// update your models from outside or create on the fly, etc.
this.items = ...;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
// inflate - say it is a layout file 'cell.xml'
convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.cell);
}
((Cell) convertView).update(items.get(position));
return convertView;
}
}
Adapter should handle this. Generally your Adapter should have method like setOnOptionsClickListener(OnOptionsClickListener listener) assuming that we are talking about ellipsis button.
So in your Activity/Fragment you use following code
public interface OnOptionsClickListener {
void onOptionsClicked(View view, PictureItem item);
}
mAdapter= new MyGridAdapter();
mAdapter.setOnOptionsClickListener(new OnOptionsClickListener() {
public void onClick(View view, PictureItem item) {
//process click
}
});
And following inside Adapter
public void setOnOptionsClickListener(OnOptionsClickListener l) {
mOnOptionsClickListener = l;
}
findViewById(R.id.btn_options).setOnClickListener(new OnClickListener(){
public void OnClick(View view) {
mOnOptionsClickListener.onOptionsClicked(view, currentPictureItem);
}
});
Please notice. You need to declare interface only if you need to have extra parameters in OnClick() method (for example currentPictureItem to get image url or item id). Otherwise, you can use just OnClickListener.
Edit
So here is explanation. Adapter serves like a View-provider for your GridView. It creates views and it configure it basic state. That's why all click listeners should be set in Adapter during views initializing. Moreover, we don't want to have a messy Activity with nested Adapter, but we want to have Adapter as a separate class. This is the reason you will usually need to create additional interface in order to have an access to currentItem object to extract data from.
Looks like nobody knows how to do this. So I found solution myself with help of #Dimitar G. and #Konstantin Kiriushyn. Thank you, guys.
1) I will create my own custom CardView using Compound View system, which will be pretty simple: LinearLayout + ImageView + TextView + Button.
public class TopicCardView extends LinearLayout {
private ImageView mImage;
private Button mButtonMenu;
private TextView mTitle;
public TopicCardView (Context context) {
initializeViews(context);
}
private void initializeViews(Context context) {
LayoutInflater inflater = (LayoutInflater) context .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.topic_card_view, this);
}
private void setTitle(...) {
...
}
private void setImage(...) {
...
}
private void setMenuClickListener(...) {
...
}
// and so on...
}
2) Then I will create method called createListOfGridCardsFromDB(...) in Activity\Fragment. It will generate list (LinkedList) of my custom CardViews (and it will also set titles\images and listeners to CardViews).
3) And then I will pass this generated LinkedList of my CardViews to GridViewAdapter.
This system makes able to use only one Adapter for all my card-grids in app. It also makes able to do nothing with clicks, interfaces, listeners and stuff in Adapter.

How to refresh a child view in a GridView?

I have a GridView showing some TextViews, the views are drawn so that ONE is marked as selected. The problem is that when the user interaction changes the selected item in my logic I want to update the views involved, namely the view showing previous selected item and the view showing the current selected item. (Of course in my real problem the child views in the grid are more complex as well as the update process which could involve some calculations, loading of resources, accessing databases, etc.)
Till now I'm using BaseAdapter.notifyDataSetChanged or GridView.invalidateViews and both do the work but also updates ALL the visible child views of the GridView but what I want is to update just TWO views among them.
How can I do that? How to get just the two views for the updating process?
Note: I'm facing this problem with ListViews also but I think maybe it will have the same solution.
Example code:
public class MainActivity extends Activity {
private int selectedPos = 36;
private class MyAdapter extends BaseAdapter {
#Override
public int getCount() {
return 100;
}
#Override
public Object getItem(int position) {
return position;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView tv;
if(convertView==null) {
tv = new TextView(MainActivity.this);
} else {
tv = (TextView) convertView;
}
Log.d("Updating -----", String.valueOf(position));
if(position==selectedPos)
tv.setText("Item ".toUpperCase() + String.valueOf(position));
else
tv.setText("Item " + String.valueOf(position));
return tv;
}
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final GridView gv = (GridView) findViewById(R.id.gridView1);
gv.setAdapter(new MyAdapter());
gv.setOnItemClickListener(new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
selectedPos = position;
//gv.invalidateViews();
((MyAdapter)gv.getAdapter()).notifyDataSetChanged();
}
});
}
}
What I'm looking for is something like (some kind of pseudo code):
updateThisViewAsNotSelected(selectedPos);
gv.redrawThisView(selectedPos);
selectedPos = position;
updateThisViewAsSelected(selectedPos);
gv.redrawThisView(selectedPos);
Recreating views all the time:
What exactly is the problem of updating the views when getView is called? If you do nothing but change the text of a TextView I can see no problem.
If you are planning to do something more you need to change your view on Adapters. See below.
Long running operations and adapters
You should never do any long running operations in the adapter itself. And not in getView at all.
You should begin thinking about Adapters as... well adapters. They adapt the data you have to the current UI context.
So it's not the job of the adapter to fetch data from the database or anything like that. The adapter should be fed data and simply adapt it into views.
For feeding the adapter with data consider using AsyncTask. It performs operations away from the UI threat and and can update the adapter when it is done.
You can for example let the Adapter have a List of objects that represent each view. Then to update the adapter you just need to provide it a new list of changed objects and notify it that the data has changed.
You need to traverse through the visible children of the list or grid view to achieve to that. For example, if you'd like to achieve that with a grid view:
for(int k = gridView.getFirstVisiblePosition(); k <= gridView.getLastVisiblePosition(); k++) {
View view = gridView.getChildAt(k);
// you can update the view here
}
If you'd like to get a child from a specific position:
int firstVisiblePosition = gridView.getFirstVisiblePosition();
for (int k = 0; k < gridView.getChildCount(); k++ ) {
int current = firstVisiblePosition + k;
if (current == updateThisPosition) {
View child = gridView.getChildAt(i);
// Update the view
TextView anything = (TextView) child.findViewById(R.id.anything_text);
anything.setText("updated!");
}
}

Categories

Resources