How to refresh a child view in a GridView? - android

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!");
}
}

Related

Android ListView with Custom Adapter Has Visual Glitch

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
}

How to get child view from RecyclerView?

I am trying to get child view by position. I could get view when one item is clicked:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getActivity(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
((MainActivity) getActivity()).showSellRecordFragment(position, view);
}
}));
Now I cannot get child view, without click - let's say by position for example:
rvSellRecords.someMagicalMethodWhichReturnsViewByPosition(5);
Question: How to get child view from RecyclerView?
EDIT FOR BOUNTY:
I have RecyclerView to show products list. When I click on it, I am adding new Fragment where I show product information. While opening I am updating toolbar with view from RecyclerView - this is working perfectly:
rvSellRecords.addOnItemTouchListener(new RecyclerItemClickListener(getContext(), new RecyclerItemClickListener.OnItemClickListener() {
#Override
public void onItemClick(View view, int position) {
sellPresenter.onSellRecordSelected(position, view);
}
}));
When I click blue button with "+", I am incrementing quantity by 1.
public void onIncrementButtonClicked(){
sellRecord.setCount(sellRecord.getCount() + 1);
showQuantity();
bus.post(new SellRecordChangedEvent(sellRecord, sellRecordPosition));
}
Then I am posting updated sellRecord to first fragment using EventBus. There I am updating list data. I supposed that updating value(sell) automatically updates adapter. Now I am getting view from adapter using custom method(getView) which was created by me(you can find it below).
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
This is my adapter class - I changed adapter little bit to collect view in list and added method which returns view for respective position:
public class SellRecordsAdapter extends RecyclerView.Adapter<SellRecordsAdapter.ViewHolder> {
.....
.....
.....
List<View> viewList;
public SellRecordsAdapter(List<SellRecord> sellRecordList) {
.....
viewList = new ArrayList<>();
}
.....
.....
.....
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
.....
.....
.....
viewList.add(i, viewHolder.itemView);
}
public View getView(int position){
return viewList.get(position);
}
}
My problem: when I updating view in toolbar, I am getting old view. When quantity is 3, I am getting view with 2. When quantity 10 - view is with 9.
My question: how to get view from recycler view using position of item(without on click listener)?
Use recyclerView.findViewHolderForLayoutPosition(position) or
reyclerView.findViewHolderForAdapterPosition(position) to get the viewholder for postion. Then you can access any child from your viewholder.
Checkout Recyclerview
RecyclerView.ViewHolder holder = recycleView.findViewHolderForAdapterPosition(position);
ImageView imageView = holder.itemView.findViewById(R.id.iv_product);
This is a supplement to #Ravi Teja's answer. You can get the viewHolder from the recyclerView using position of the particular item, then get a particular view from the viewHolder as shown above
You can use RecyclerView's LayoutManager for it.
View view = layoutManager.findViewByPosition(position)
Hope this helps someone:
I was getting null pointer exceptions with:
recyclerView.findViewHolderForAdapterPosition
recyclerView.findViewHolderForItemId
layoutManager.findViewByPosition.
The reason was that there is a slight delay for the viewholder to be created.
I found the solution here: https://stackoverflow.com/a/33414430/7952427
I post an answer because which is really complex to findviews() from RecyclerView.
#Joe: After spending 4hours found one answer. Which gives me the proper view of the index.
mAdapter is adapter of RecyclerView
View v = recyclerView.findViewHolderForItemId(mAdapter.getItemId(index/position)).itemView;
Now just access your views by:
v.findViewById(R.id.edittext) OR any id.
it helped me, make a 100 ms delay before manipulate it, like this:
Handler handler = new Handler();
mHandler.postDelayed(new Runnable() {
#Override
public void run() {
// rcv is my recyclerview
rcvStatus.getChildAt(1).setBackground(getActivity().getResources().getDrawable(R.drawable.disabled));
// or:
rcvStatus.getChildAt(1).setClickable(false);
}
}, 100);
Write this method in adapter.
public Object getItem(int position) {
return yourArrayList.get(position);
}
and you just need to call it like
yourAdapter.getItem(2);
pass your required position.
Hope it solves your problem.
just put this method in your code and you can call it as you likes
void someMagicalMethodWhichReturnsViewByPosition(int position){
//I assumes child views are CardView
CardView c = (CardView)rvSellRecords.getItem(int position);
///optional codes
//////////
}
now I understand your problem. you need to use interface for join recyclerview item and activity.
you must define an interface class like below:
public interface IViewClick {
public void onClickButtonAdd();
}
add this parameter to your adapter class:
private IViewClick mListener;
and initialize it in constructor with value that get from inputs.
when user click on PLUS button, you send event to activity by this line:
mListener.onClickButtonAdd();
in your activity class you must implements IViewClick interface and add your code there, like this:
#Override
public void onClickButtonAdd() {
/// TODO every thing that you want.
/// change your toolbar values.
}
it is not good solution for you.
RecyclerView.ViewHolder holder =
mRecyclerView.findViewHolderForItemId(mAdapter.getItemId(i));
I wouldn't recommend tracking the view list yourself. It could lead to weird issues with item updates, position updates, etc.
Instead on your SellRecordChangedEvent, use findViewHolderForAdapterPosition() instead of adapter.getView().
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
View view = yourrecyclerview.findViewHolderForAdapterPosition(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
http://developer.android.com/reference/android/support/v7/widget/RecyclerView.html#findViewHolderForAdapterPosition(int)
And as a side note, it's better to implement an actual item click listener to the itemView on the ViewHolder instead of using touch listener. There's lots of examples of this online.
So the recyclerview and your product information are in 2 different fragments yes? You are expecting the recyclerview's views to update when they are not even in foreground? also you are changing adapter data item's data at position event.getSellRecordPosition() , but you are not notifying the adapter that its dataset changed, either by adapter.notifyDataSetChanged() or the other notifyItemChanged(position) methods.
I'd modify your onEvent() like so:
#Subscribe
public void onEvent(SellRecordChangedEvent event){
sell.getSellRecords().set(event.getSellRecordPosition(), event.getSellRecord());
sell.recalculate();
int position = event.getSellRecordPosition();
MyViewHolder holder = adapter.onCreateViewHolder(yourRecyclerView, 0);
adapter.onBindViewHolder(holder,position);
View view = adapter.getView(position);
bus.post(new TransactionTitleChangedEvent(null, view));
}
Calling on createViewHolder and next BindViewHolder on your adapter will definitely update the views for that position, then your adapter.getView(position) should return you the latest view.
Here MyViewHolder is your viewholder class and yourRecyclerview, is the reference to your recycler view
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
View viewTelefone = recycler_view.getChildAt(i);
}
If you want to replace text on a particular edit text for same position:
for (int i = 0; i < recycler_view.getAdapter().getItemCount(); i++) {
if(adpterPostion==i)
{
View viewTelefone = recycler_view.getChildAt(i);
EditText et_mobile = (EditText) viewTelefone.findViewById(R.id.et_mobile);
et_mobile.setText("1111111");
}
}

Set up ListView from a String[][]

I have a String[][] of data and I am trying to make a custom listView from it.
Here is the data
String[][] myDataArray = {{"cat1","cat2","cat3","cat4"},
{"dog1","dog2","dog3"},
{"lion1"},
{"monkey1","monkey2"}};
And now here is how I am trying to display this data in my listView. I want each array within the array to have its own row. So all the cats will be in one row (cell), all the dogs will be in another row and so on. Here is a picture to make it clear each item in the row, is a textView.
I have made cell_4.xml, cell_3.xml, cell_2.xml, cell_1.xml layout file for each of the rows.
And then in the activity that I am trying to show this, I just have a plain old listView.
Now I am not quite sure how to edit/ adapt the data. I have to display it in this way. So that it uses the correct cell layout for each array within the String[]. I was thinking about using a switch statement to get the number of items in each inner array. But having some trouble with the ArrayAdapter. To get it set up.
I have looked at a couple of examples on stackoverflow like this one Custom ListView Android to try and figure this out but can't get it.
EDIT
Here is trying to set up adapter and call MyListViewAdapter, but I don't know what to set as context.
here is the code:
private void handleData(String[][] data){
BaseAdapter adapter = MyListAdapter(context, data);
ListView list = (ListView) findViewById(R.id.mealsListView);
list.setAdapter(adapter);
}
Some thoughts:
1) If you are determined to use ListView, skip this point. Else, you might be interested in GRIDVIEW that natively support a table structure.
2) Your idea is consistent. ListView only knows about ROWS, so your adapter will be called for you to display a ROW, and it's up to you to transform the array in that row into an element with multiple cells. You'll do that in getView()
3) You'll make use of the Item Types (getViewTypeCount and getItemViewType) to declare you have different item types. Each type will be a row with a given number of cells: 1,2,3,4...
you will override getViewTypeCount() to return the maximum number of cells in a row
you will either inflate a static layout for the number of cells a row has, or generate it dynamically
Let's get started ... First of all in the adapter we override the Type methods to declare
our rows will be of different types:
#Override
public int getViewTypeCount() {
return 4;
// you have 4 types of rows.
// SUPER IMPORTANT: No row in the array can have more cells than this number
// or getView will crash (you'd have to define additional layouts)
}
#Override
public int getItemViewType(int position) {
// for a given position, you need to return what type is it. This number ranges
// from 0 to itemtypecount-1. We return the length of the array (number of cells)
// this function is called by the View Recycler to appropriately pass you the
// correct view to reuse in convertView
return myDataArray[position].length - 1;
}
And then we need to implement getView(). The typical implementation will be the first one, where you create different XMLs, and the second one is a more advanced implementation where we dynamically create the layouts without any xml.
First Case: Static Layouts
Ideal if you limit the Row Array Length to say 3 or 4, to avoid creating dozens of layouts. So you define 4 xmls (ie. row_1_childs, row_2_childs, row_3_childs, row_4_childs) that will be the templates of rows with that number of children. Then,
and then in GetView:
// we define an array of layout ids to quickly select the layout to inflate depending on
// the number of rows:
private final static int[] sLayouts=new int[] {
R.layout.row_1_childs,
R.layout.row_2_childs,
R.layout.row_3_childs,
R.layout.row_4_childs
};
public View getView (int position, View convertView, ViewGroup parent) {
int maxcells=myDataArray[position].length;
if (convertView == null) {
// generate the appropriate type
if (maxcells<=sLayout.length) {
// just check we are in bounds
convertView=LayoutInflater.from(parent.getContext()).inflate(sLayout[maxcells-1], null);
} else {
// you have a row with too many elements, need to define additional layouts
throw new RuntimeException ("Need to define more layouts!!");
}
}
// At this point, convertView is a row of the correct type, either just created,
// or ready to recycle. Just fill in the cells
// for example something like this
ViewGroup container=(ViewGroup)convertView;
for (int i=0; i<maxcells; i++) {
// We assume each row is a (linear)layout whose only children are textviews,
// one for each cell
TextView cell=(TextView)container.getChildAt(i); // get textview for cell i
cell.setText(myDataArray[position][i]);
cell.setTag( new PositionInfo(position, i)); // we store the cell number and row inside the TextView
cell.setOnClickListener(mCellClickListener);
}
return convertView;
}
Second Case: Dynamic Layouts
Another solution would be to dynamically generate the rows, and dynamically generate as many text views as you might need. To do so, keep overriding getViewTypeCount() to return the Maximum number of children, and define getView like this:
public View getView (int position, View convertView, ViewGroup parent) {
String rowData=myDataArray[position];
if (convertView==null) {
// generate a LinearLayout for number of children:
LinearLayout row=new LinearLayout(context);
for (int i=0, len=rowData.length(); i<len; i++) {
// generate a textview for each cell
TextView cell=new TextView(parent.getContext());
// we will use the same clicklistener (very efficient)
cell.setOnClickListener(mCellClickListener);
row.addView(cell, new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1)); // same width for each cell
}
convertView=row;
}
// here convertView has the correct number of children, same as before:
ViewGroup container=(ViewGroup)convertView;
for (int i=0, len=rowData.length(); i<len; i++) {
TextView cell=(TextView)container.getChildAt(i);
cell.setText(rowData[i]);
cell.setTag( new PositionInfo(position, i)); // we store the cell number and row inside the TextView
}
return convertView;
}
// auxiliar class to store row and col in each textview for the clicklistener
private class PositionInfo {
public int row, col;
public PositionInfo(int row, int col) { this.row=row; this.col=col; }
}
// trick: only one clicklistener for millions of cells
private View.OnClickListener mCellClickListener=new View.OnClickListener() {
#Override
public void onClick(View v) {
PositionInfo position=(PositionInfo)v.getTag(); // we stored this previously
// you pressed position.row and position.col
}
}
Solution (1) is cool to manually create the layouts and configure them a lot.
Solution (2) is cool to programmatically support any number of cells, in case they are very different
Both solutions are pretty efficient, because they play nice with the View recycler: If you fail to use View Types and you constantly inflate layouts, your ListView will be laggy and waste a lot of memory and resources.
You might run into problems if the size of each string in the row varies and you might then have to push data onto the next line.
Try using an alternate view, if your aim is categorization of similar data, expandable listview is an option to consider.
You will need to make your own adapter by extending BaseAdapter. You can check the data's size the getView() method, and inflate the correct layout.
UPDATE:
public class MyListAdapter extends BaseAdapter{
String[][] mData;
LayoutInflater mLayoutInflater;
public MyListAdapter(Context context, String[][] data) {
mData = data;
mLayoutInflater = LayoutInflater.from(context);
}
#Override
public int getCount() {
return mData.length;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
String data[] = mData.get(position);
switch(data.length){
case 4:
convertView = mLayoutInflater.inflate(R.layout.cell_4, parent, false);
TextView t1 = (TextView) convertView.findViewById(R.id.one);
t1.setText(data[0]);
break;
case 3:
convertView = mLayoutInflater.inflate(R.layout.cell_3, parent, false);
break;
case 2:
convertView = mLayoutInflater.inflate(R.layout.cell_2, parent, false);
break;
case 1:
convertView = mLayoutInflater.inflate(R.layout.cell_1, parent, false);
break;
default:
convertView = mLayoutInflater.inflate(R.layout.blank, parent, false);
}
return convertView;
}
}

Android ListView Row with text and row with images

My problem is I have a ListView in an Activity and now I load text in rows but in one of those rows, I need to load some thumbnails. These thumbs must be clickable to see larger images in another Activity.
I cannot load text in the first row of the ListView and thumbs in the second.
Anyone can help?
You can do this by defining a custom Adapter, defining different viewType, for instance:
public class MyAdapter extends Adapter {
// [implement required methods]
#Override
public int getViewTypeCount() {
return 2;
}
#Override
public int getItemViewType(int position) {
if (/* specific row */) {
return 1;
}
return 0;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View rowView;
if (/* specific row */) {
rowView = inflater.inflate(R.layout.layout_image,
parent, false);
} else {
rowView = inflater.inflate(R.layout.layout_text,
parent, false);
}
// etc.
}
}
You can do this by making changes in you adapter class. In that class you have to set some logic inside getView() method so that the images get displayed where ever you want. And you have to display the image programmatically because if you set the imagview in your xml then you must have to set image in every imageview.
This is my opinion, there could be a smarter way to do this. I am new to android.

How can I update a single row in a ListView?

I have a ListView which displays news items. They contain an image, a title and some text. The image is loaded in a separate thread (with a queue and all) and when the image is downloaded, I now call notifyDataSetChanged() on the list adapter to update the image. This works, but getView() is getting called too frequently, since notifyDataSetChanged() calls getView() for all visible items. I want to update just the single item in the list. How would I do this?
Problems I have with my current approach are:
Scrolling is slow
I have a fade-in animation on the image which happens every time a single new image in the list is loaded.
I found the answer, thanks to your information Michelle.
You can indeed get the right view using View#getChildAt(int index). The catch is that it starts counting from the first visible item. In fact, you can only get the visible items. You solve this with ListView#getFirstVisiblePosition().
Example:
private void updateView(int index){
View v = yourListView.getChildAt(index -
yourListView.getFirstVisiblePosition());
if(v == null)
return;
TextView someText = (TextView) v.findViewById(R.id.sometextview);
someText.setText("Hi! I updated you manually!");
}
This question has been asked at the Google I/O 2010, you can watch it here:
The world of ListView, time 52:30
Basically what Romain Guy explains is to call getChildAt(int) on the ListView to get the view and (I think) call getFirstVisiblePosition() to find out the correlation between position and index.
Romain also points to the project called Shelves as an example, I think he might mean the method ShelvesActivity.updateBookCovers(), but I can't find the call of getFirstVisiblePosition().
AWESOME UPDATES COMING:
The RecyclerView will fix this in the near future. As pointed out on http://www.grokkingandroid.com/first-glance-androids-recyclerview/, you will be able to call methods to exactly specify the change, such as:
void notifyItemInserted(int position)
void notifyItemRemoved(int position)
void notifyItemChanged(int position)
Also, everyone will want to use the new views based on RecyclerView because they will be rewarded with nicely-looking animations! The future looks awesome! :-)
This is how I did it:
Your items (rows) must have unique ids so you can update them later. Set the tag of every view when the list is getting the view from adapter. (You can also use key tag if the default tag is used somewhere else)
#Override
public View getView(int position, View convertView, ViewGroup parent)
{
View view = super.getView(position, convertView, parent);
view.setTag(getItemId(position));
return view;
}
For the update check every element of list, if a view with given id is there it's visible so we perform the update.
private void update(long id)
{
int c = list.getChildCount();
for (int i = 0; i < c; i++)
{
View view = list.getChildAt(i);
if ((Long)view.getTag() == id)
{
// update view
}
}
}
It's actually easier than other methods and better when you dealing with ids not positions! Also you must call update for items which get visible.
get the model class first as global like this model class object
SampleModel golbalmodel=new SchedulerModel();
and initialise it to global
get the current row of the view by the model by initialising the it to global model
SampleModel data = (SchedulerModel) sampleList.get(position);
golbalmodel=data;
set the changed value to global model object method to be set and add the notifyDataSetChanged its works for me
golbalmodel.setStartandenddate(changedate);
notifyDataSetChanged();
Here is a related question on this with good answers.
The answers are clear and correct, I'll add an idea for CursorAdapter case here.
If youre subclassing CursorAdapter (or ResourceCursorAdapter, or SimpleCursorAdapter), then you get to either implement ViewBinder or override bindView() and newView() methods, these don't receive current list item index in arguments. Therefore, when some data arrives and you want to update relevant visible list items, how do you know their indices?
My workaround was to:
keep a list of all created list item views, add items to this list from newView()
when data arrives, iterate them and see which one needs updating--better than doing notifyDatasetChanged() and refreshing all of them
Due to view recycling the number of view references I'll need to store and iterate will be roughly equal the number of list items visible on screen.
int wantedPosition = 25; // Whatever position you're looking for
int firstPosition = linearLayoutManager.findFirstVisibleItemPosition(); // This is the same as child #0
int wantedChild = wantedPosition - firstPosition;
if (wantedChild < 0 || wantedChild >= linearLayoutManager.getChildCount()) {
Log.w(TAG, "Unable to get view for desired position, because it's not being displayed on screen.");
return;
}
View wantedView = linearLayoutManager.getChildAt(wantedChild);
mlayoutOver =(LinearLayout)wantedView.findViewById(R.id.layout_over);
mlayoutPopup = (LinearLayout)wantedView.findViewById(R.id.layout_popup);
mlayoutOver.setVisibility(View.INVISIBLE);
mlayoutPopup.setVisibility(View.VISIBLE);
For RecycleView please use this code
I used the code that provided Erik, works great, but i have a complex custom adapter for my listview and i was confronted with twice implementation of the code that updates the UI. I've tried to get the new view from my adapters getView method(the arraylist that holds the listview data has allready been updated/changed):
View cell = lvOptim.getChildAt(index - lvOptim.getFirstVisiblePosition());
if(cell!=null){
cell = adapter.getView(index, cell, lvOptim); //public View getView(final int position, View convertView, ViewGroup parent)
cell.startAnimation(animationLeftIn());
}
It's working well, but i dont know if this is a good practice.
So i don't need to implement the code that updates the list item two times.
exactly I used this
private void updateSetTopState(int index) {
View v = listview.getChildAt(index -
listview.getFirstVisiblePosition()+listview.getHeaderViewsCount());
if(v == null)
return;
TextView aa = (TextView) v.findViewById(R.id.aa);
aa.setVisibility(View.VISIBLE);
}
I made up another solution, like RecyclyerView method void notifyItemChanged(int position), create CustomBaseAdapter class just like this:
public abstract class CustomBaseAdapter implements ListAdapter, SpinnerAdapter {
private final CustomDataSetObservable mDataSetObservable = new CustomDataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
public void notifyItemChanged(int position) {
mDataSetObservable.notifyItemChanged(position);
}
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
public boolean areAllItemsEnabled() {
return true;
}
public boolean isEnabled(int position) {
return true;
}
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getView(position, convertView, parent);
}
public int getItemViewType(int position) {
return 0;
}
public int getViewTypeCount() {
return 1;
}
public boolean isEmpty() {
return getCount() == 0;
} {
}
}
Don't forget to create a CustomDataSetObservable class too for mDataSetObservable variable in CustomAdapterClass, like this:
public class CustomDataSetObservable extends Observable<DataSetObserver> {
public void notifyChanged() {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
}
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
public void notifyItemChanged(int position) {
synchronized(mObservers) {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {#link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {#link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
mObservers.get(position).onChanged();
}
}
}
on class CustomBaseAdapter there is a method notifyItemChanged(int position), and you can call that method when you want update a row wherever you want (from button click or anywhere you want call that method). And voila!, your single row will update instantly..
My solution:
If it is correct*, update the data and viewable items without re-drawing the whole list. Else notifyDataSetChanged.
Correct - oldData size == new data size, and old data IDs and their order == new data IDs and order
How:
/**
* A View can only be used (visible) once. This class creates a map from int (position) to view, where the mapping
* is one-to-one and on.
*
*/
private static class UniqueValueSparseArray extends SparseArray<View> {
private final HashMap<View,Integer> m_valueToKey = new HashMap<View,Integer>();
#Override
public void put(int key, View value) {
final Integer previousKey = m_valueToKey.put(value,key);
if(null != previousKey) {
remove(previousKey);//re-mapping
}
super.put(key, value);
}
}
#Override
public void setData(final List<? extends DBObject> data) {
// TODO Implement 'smarter' logic, for replacing just part of the data?
if (data == m_data) return;
List<? extends DBObject> oldData = m_data;
m_data = null == data ? Collections.EMPTY_LIST : data;
if (!updateExistingViews(oldData, data)) notifyDataSetChanged();
else if (DEBUG) Log.d(TAG, "Updated without notifyDataSetChanged");
}
/**
* See if we can update the data within existing layout, without re-drawing the list.
* #param oldData
* #param newData
* #return
*/
private boolean updateExistingViews(List<? extends DBObject> oldData, List<? extends DBObject> newData) {
/**
* Iterate over new data, compare to old. If IDs out of sync, stop and return false. Else - update visible
* items.
*/
final int oldDataSize = oldData.size();
if (oldDataSize != newData.size()) return false;
DBObject newObj;
int nVisibleViews = m_visibleViews.size();
if(nVisibleViews == 0) return false;
for (int position = 0; nVisibleViews > 0 && position < oldDataSize; position++) {
newObj = newData.get(position);
if (oldData.get(position).getId() != newObj.getId()) return false;
// iterate over visible objects and see if this ID is there.
final View view = m_visibleViews.get(position);
if (null != view) {
// this position has a visible view, let's update it!
bindView(position, view, false);
nVisibleViews--;
}
}
return true;
}
and of course:
#Override
public View getView(final int position, final View convertView, final ViewGroup parent) {
final View result = createViewFromResource(position, convertView, parent);
m_visibleViews.put(position, result);
return result;
}
Ignore the last param to bindView (I use it to determine whether or not I need to recycle bitmaps for ImageDrawable).
As mentioned above, the total number of 'visible' views is roughly the amount that fits on the screen (ignoring orientation changes etc), so no biggie memory-wise.
In addition to this solution (https://stackoverflow.com/a/3727813/5218712) just want to add that it should work only if listView.getChildCount() == yourDataList.size();
There could be additional view inside ListView.
Example of how the child elements are populated:

Categories

Resources