Keeping state of a view constant in Recyclerview after calling notifydatasetchanged() - java

I have a recycler adapter which has 3 itemviewtypes. No.1 type of view has an initial animation to show the view which I've written under the holder instance check in the OnBindView . it works fine. but when there is any change in the other view types, notifydatasetchanged() is called and the adapter is set again which makes the animation start again.
i tried putting a flag for the animation where it would only animate the first time. is there a smarter way to do this?
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int
viewType) {
mSharedPreferencesManager.setVersionNewFeatureCheck(BuildConfig.VERSION_NAME);
Logger.i(TAG, "onCreateViewHolder, " + viewType);
if (viewType == TYPE_HEADER) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.converation_header_row, parent, false);
return new HeaderViewHolder(v);
} else if(viewType == TYPE_ANNOUNCEMENT) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_new_feature_notification, parent, false);
return new FeatureNotificationViewHolder(v);
}
else{
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.conversation_row, parent, false);
return new ConversationRowViewHolder(v);
}
}
f (holder instanceof FeatureNotificationViewHolder) {
final FeatureNotificationViewHolder featureNotificationViewHolder = (FeatureNotificationViewHolder) holder;
featureNotificationViewHolder.chatNewFeatureNotif.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
final int height = featureNotificationViewHolder.chatNewFeatureNotif.getMeasuredHeight();
featureNotificationViewHolder.chatNewFeatureNotif.setVisibility(View.GONE);
featureNotificationViewHolder.chatNewFeatureNotif.getViewTreeObserver().removeOnPreDrawListener(this);
mOverflowAnimations.showView(featureNotificationViewHolder.chatNewFeatureNotif, height, 6);
return false;
}
});

.notifyDataSetChanged() will notify all of your items inside the RecyclerView, if you don't want everything to reset consider using .notifyItemChanged(int position) method on your adapter instance. You'll need to know exactly the index/position of the item to be notified.

Instead of using notifyDataSetChanged, you should use notifyItemChanged to only change the item that needs to be changed, and not the whole adapter or data set.
This won't lead to the unnecessary animation because you're only changing a specific item. This is also much faster and takes up less processing and rendering power.
You can read more about it here.

Related

strike thru text doesn't save

I'm in the process of making a grocery list app and I wrote the code to where when I click an item, it'll mark it off.
This is my code for that section:
lv.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
TextView text = (TextView) view;
if (!text.getPaint().isStrikeThruText()) {
text.setPaintFlags(text.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
}else{
text.setPaintFlags(text.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
}
}
});
}
It works exactly like i want but when I add another item after an item is marked off, all of the items that are marked off, the marks disappear.
When I add an item, it's like it resets. it doesn't delete any of my items, just the strike_thru part of it. any help would be greatly appreciated! thanks
Your ListView's Adapter contains a method called getView, which is called when a list view item needs to be displayed in an actual View. The Views in your ListView will be discarded if you scroll too far off screen, or invalidate the whole ListView.
My guess is that adding an item is invalidating the ListView.
Your getView method should set the paint flags on the view that it returns. Assuming your list view is displaying a String[], you will also need a boolean[] to hold whether or not an item is complete. You would need to initialize this to all falses, add a completed[i] = !completed[i] at the beginning of your onItemClick. Then you can check competed[i] instead of isStrikeThruText in your if statement, later in that method. Finally, your getView can look like this
#NonNull
#Override
public View getView(int position, #Nullable View convertView, #NonNull ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(android.R.layout.simple_list_item_1, parent, false);
}
TextView textView = (TextView) convertView;
textView.setText(items[position]);
if (completed[position]) {
textView.setPaintFlags(textView.getPaintFlags() | Paint.STRIKE_THRU_TEXT_FLAG);
} else {
textView.setPaintFlags(textView.getPaintFlags() & (~Paint.STRIKE_THRU_TEXT_FLAG));
}
return textView;
}

2 different layouts for the type of each row

I know that there are similar questions, but i couldn't really understand, so i decided to ask again.
I am trying to do something similar to a chat, so in the list view there would be 2 types of rows, the ones of the received items and the ones of the sent items. The ones that were received will inflate the recvRow.xml layout, and the ones that were sent will inflate the sentRow.xml layout.
I have a list of items that are suposed to be shown ( name, img, etc) and i have a boolean that represents if the object was sent or received.
The problem is that those layouts have different buttons and if for example i receive something first, it will obviously load the recvRow.xml , but then if I try to send something, it will throw a nullpointerexception because on one of the buttons that only exists in the sentRow.xml layout (and doesn't exist in the recvRow.xml).
Since i am checking that boolean that i told you guys before, i don't see the need to use the getItemViewType method.
Here is my code for the getView method:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
viewHolder = null;
if (convertView == null) {
viewHolder=new ViewHolder();
LayoutInflater layoutInflater = (LayoutInflater) activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
System.out.println("Transf Rec Adapter : Going to draw AN ITEM : "+ listOfItems.get(position).getAppName());
System.out.println("Transf Rec Adapter : The received bool is : "+ listOfItems.get(position).isReceived());
if(listOfItems.get(position).isReceived()){
view=layoutInflater.inflate(R.layout.highway_transf_record_recv_row,parent,false);
viewHolder.deleteFile=(ImageView) view.findViewById(R.id.transfRecRowDelete);
viewHolder.installButton = (ImageView) view.findViewById(R.id.transfRecRowInstall);
}else{//se enviado
view=layoutInflater.inflate(R.layout.highway_transf_record_sent_row,parent,false);
viewHolder.reSendButton=(ImageView) view.findViewById(R.id.transfRecReSendButton);
}
viewHolder.appImageIcon=(ImageView) view.findViewById(R.id.transfRecRowImage);
viewHolder.appNameLabel=(TextView) view.findViewById(R.id.transfRecRowText);
viewHolder.appVersionLabel = (TextView) view.findViewById(R.id.transfRecRowAppVersion);
viewHolder.senderInfo = (TextView) view.findViewById(R.id.apkOrigin);
viewHolder.rowImageLayout = (RelativeLayout) view.findViewById(R.id.transfRecRowImageLayout);
viewHolder.appInfoLayout = (RelativeLayout) view.findViewById(R.id.appInfoLayout);
}else{
view= convertView;
viewHolder = (ViewHolder) convertView.getTag();
}
if(listOfItems.get(position).isReceived()){
System.out.println("INSIDE THE GET VIEW TRANSF REC ADAPTER : RECEIVED SOMETHING");
viewHolder.senderInfo.setText("You received this app :");
myOnClickListenerToInstall = new MyOnClickListenerToInstall(position);
viewHolder.installButton.setOnClickListener(myOnClickListenerToInstall);
myOnClickListenerToDelete= new MyOnClickListenerToDelete(position);
viewHolder.deleteFile.setOnClickListener(myOnClickListenerToDelete);
}else{
System.out.println("INSIDE THE GET VIEW TRANSF REC ADAPTER : SENT SOMETHING");
viewHolder.senderInfo.setText("You sent this app :");
ReSendListener reSendListener=new ReSendListener(position);
viewHolder.reSendButton.setOnClickListener(reSendListener);//null pointer exception here
}
viewHolder.appNameLabel.setText(listOfItems.get(position).getAppName());
viewHolder.appImageIcon.setImageDrawable(listOfItems.get(position).getIcon());
viewHolder.appVersionLabel.setText(listOfItems.get(position).getVersionName());
view.setTag(viewHolder);
return view;
}
The id's are right and everything exists on the viewHolder.
You need to use getItemViewType because of recycling. Otherwise you can try to recycle a received item into a non-received view, which will screw things up royally. However it is ok for getItemViewType to just return 0 or 1 based on that boolean. It doesn't need to be very complicated for 2 view types.
You should override getItemViewType() in your adapter. This method will indicate the type of view which should be inflated for each row. You also need to override getViewTypeCount().

Multiple listeners

I've a question about ListView and listeners.
Suppose i have a listview in my app. Each item of it contains a checkbox.
And i've the following code:
public View getView(final int position, View convertView, ViewGroup parent) {
CheckBox checkbox = (CheckBox)v.findViewById(R.id.checkbox);
checkbox.setOnCheckedChangeListener(new OnCheckedChangeListener(){
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// some code
}
});
}
As you can see I set setOnCheckedChangeListener every time when getView method is called.
So is it right way to set listener? Or should I set it once? Is it bad in terms of performance? Or it doesn't matter? And if I set the listener multiple times whether it will overwrite previous listener or will i have multiple listeners for this event?
Each listener that you set will overwrite the previous one (if any). Setting a listener, or anything else, will consume a super small amount of time, however here you're also creating a new anonymous class which will take longer.
For the maximum performance, I would make a few adjustments:
Use convertViews to cache the views
Use ViewHolder pattern to avoid multiple findViewById calls
Use a single listener for all checkboxes and set it only on the cached views (if convertView == null)
Save the position of the item that the CheckBox is located in as the CheckBox's tag
Here's an example:
private static class ViewHolder {
CheckBox checkBox;
}
private CompoundButton.OnCheckedChangeListener mCheckListener = new CompoundButton
.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
Log.e("TAG", "CheckBox position: " + buttonView.getTag());
}
};
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
/* Inflate the layout here */
holder = new ViewHolder();
holder.checkBox = (CheckBox) convertView.findViewById(R.id.checkbox);
/* Find other views in you layout here */
holder.checkBox.setOnCheckedChangeListener(mCheckListener);
// Set the holder as tag, so you can fetch it later for re-use
convertView.setTag(holder);
} else {
// Fetch the holder
holder = (ViewHolder) convertView.getTag();
}
// Set the checkbox position
holder.checkBox.setTag(position);
return convertView;
}
If you're not familiar with using convertViews and the ViewHolder pattern you should watch The World of ListView that explains it all.
Is it bad in terms of performance? Or it doesn't matter?
It does not matter. The setter just assign the object.
And if I set the listener multiple times whether it will overwrite
previous listener or will i have multiple listeners for this event?
you will have just one for each instance of the checkbox upon you called setOnCheckedChangeListener. The ListView is particular case due of its recycling mechanism

GreenDroid PagedView - Multiple pages (different layout) help.. Android SDK

I've spent the day playing around with the GreenDroid PagedView and PagedAdaptor to create multiple screens that one can swipe through (a la iPhone style and Android Home Screen style). It's a very cool effect and the GreenDroid library is awesome. Soo.. I can get this working for one XML layout (code below), but what I am attempting to do is have three screens with different layouts. I've played around and got it working successfully with one method, but am unsure whether the method I'm using will cause problems due to repeat inflation of XML layout at every method call. I'll give some examples of what I've tried to save time. I'm hoping someone reads this that has had experience with GreenDroid and can point me in the right direction. Maybe this will also help someone else too..
Here is my code:
HomePageActivity.java
public class HomePageActivity extends GDActivity {
private static final int PAGE_COUNT = 3;
private final Handler mHandler = new Handler();
private PageIndicator mPageIndicator;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setActionBarContentView(R.layout.homepage_view); // container xml
final PagedView pagedView = (PagedView) findViewById(R.id.homepage_view);
pagedView.setOnPageChangeListener(mOnPagedViewChangedListener);
pagedView.setAdapter(new HomePageAdapter());
mPageIndicator = (PageIndicator) findViewById(R.id.page_indicator);
mPageIndicator.setDotCount(PAGE_COUNT);
setActivePage(pagedView.getCurrentPage());
}
private void setActivePage(int page) {
mPageIndicator.setActiveDot(page);
}
private OnPagedViewChangeListener mOnPagedViewChangedListener = new OnPagedViewChangeListener() {
#Override
public void onStopTracking(PagedView pagedView) {
}
#Override
public void onStartTracking(PagedView pagedView) {
}
#Override
public void onPageChanged(PagedView pagedView, int previousPage, int newPage) {
setActivePage(newPage);
}
};
private class HomePageAdapter extends PagedAdapter {
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public Object getItem(int position) {
return null;
}
#Override
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) {
Log.i("getView","ConvertView is null");
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
}
Log.i("getView Page",Integer.toString(position));
return convertView;
}
}
The two lines:
Log.i("getView","ConvertView is null");
Log.i("getView Page",Integer.toString(position));
I am using to debug this to see what is happening via. LogCat and so I can experiment.
The first line is displayed ONCE - first time application is created. I've noticed that VERY VERY occasionally as I swipe through the pages (I have three - set by variable PAGE_COUNT at top), that after 30-50 swipes I will see this message again. This shows that this layout is only INFLATED once - for all screens.
The second line I naturally see every time I change page. The integer position is either 0, 1 or 2 in this particular case.
The XML layout homepage_one is a simple layout with three TextViews. The XML layout homepage_view is the container for these pages.
homepage_view.xml
<merge
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:greendroid="http://schemas.android.com/apk/res/com.cyrilmottier.android.gdcatalog">
<greendroid.widget.PagedView
android:id="#+id/homepage_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/splash" />
<greendroid.widget.PageIndicator
android:id="#+id/page_indicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:paddingTop="10dp" />
Naturally I've tried some obvious methods such as this: (which works as wanted)
public View getView(int position, View convertView, ViewGroup parent) {
if (position == 0) {
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
} else if( position == 1 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_two, parent, false);
} else {
convertView = getLayoutInflater().inflate(R.layout.homepage_three, parent, false);
}
return convertView;
}
This works as intended. Every screen is different as I want. I've not noticed any performance problems during testing, but it's worth noting in the original instance that we only inflate the layout ONCE. As this method is called (which is every time the page changes, we inflate the layout again. This doesn't seem resourceful, but then again this is my first week with Android development and I don't know how the architecture works... (just reading, reading, reading and testing, testing, testing) - so hence why I would like to hear from more experienced and knowledgeable developers. This may well be acceptable.. I would like to hear from you.
I tried this:
public View getView(int position, View convertView, ViewGroup parent) {
if (PAGE_ONE == false && position == 0) {
convertView = getLayoutInflater().inflate(R.layout.homepage_one, parent, false);
PAGE_ONE = true;
} else if(PAGE_TWO == false && position == 1 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_two, parent, false);
PAGE_TWO = true;
} else if( PAGE_THREE == false && position == 2 ){
convertView = getLayoutInflater().inflate(R.layout.homepage_three,parent, false);
PAGE_THREE = true;
}
return convertView;
}
PAGE_ONE, PAGE_TWO, PAGE_THREE were set in the class as private boolean = false. The consideration behind this is that we only inflate layout once and every other call to this method simply returns the already inflated xml layout. Unfortunately this crashes my emulator and my phone (HTC EVO 3D). As Jay-Z would say, on to the next one...
Any ideas guys?
The GreenDroid's PagedView is normally intended to display a large set of pages. It can be considered as an horizontal paged ListView. This is the reason why using it is very close to the common ListAdapter.
In your case, you want to keep three pages in memory. This is completely possible but not recommended as you are preventing the PagedView from recycling your pages and also increasing the amount of View in memory.
Having only 3 pages (with a relatively flat and narrow View hierarchy) is OK. A better way do to what you are trying to do would be to lazily-initialize the pages and keep a reference on each pages.
private static final int PAGE_COUNT = 3;
private View[] mPages = new View[PAGE_COUNT];
public View getView(int position, View convertView, ViewGroup parent) {
if (mPages[position] == null) {
View page = createPageAtPosition(position, parent);
mPages[position] = page;
}
return mPages[position];
}
Of course you may have a warning in your logs as the PagedView notify the developer when the convertView is not reused.
I ran into a similar issue as well. Not sure if it is a bug with GreenDroid or user error on my part. (The latter is more likely)
Anyway, here is the solution I came up with. Your solution was nearly there, but you missed the critical 'else' condition when grabbing an existing view. It's working quite well for me.
/** get the view to display */
#Override
public View getView(int position, View convertView, ViewGroup parent) {
pageEnum currentPage = pageEnum.values()[position];
if(null == mViews[position]) {
switch(currentPage) {
case PAGE_1:
convertView = getLayoutInflater().inflate(R.layout.page1, parent, false);
break;
case PAGE_2L:
convertView = getLayoutInflater().inflate(R.layout.page2, parent, false);
break;
case PAGE_3:
convertView = getLayoutInflater().inflate(R.layout.page3, parent, false);
break;
default:
convertView = null;
Log.e(TAG, "Invalid page.");
break;
} /* switch(currentPage) */
mViews[position] = convertView;
} /* if(null == mViews[position]) */
else {
convertView = mViews[position];
}
return convertView;
} /* getView() */

Android Extendable ListView Child Click

At the moment I have this code running. I am working in eclipse and at the moment am getting this error
The method getItem(int) is undefined for the type Expandable.MySimpleCursorTreeAdapter
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition,
int childPosition, long id) {
// use groupPosition and childPosition to locate the current item in the adapter
Intent intent = new Intent(Categories.this, com.random.max.Random.class);
Cursor cursor = (Cursor) mscta.getItem(childPosition);
intent.putExtra("EMPLOYEE_ID", cursor.getInt(cursor.getColumnIndex("_id")));
//Cursor cursor = (Cursor) adapter.getItem(position);
//intent.putExtra("EMPLOYEE_ID", cursor.getInt(cursor.getColumnIndex("_id")));
startActivity(intent);
return true;
}
You using a Cursor Adapter, Cursors can only be iterated over (in a sequence).
So there is no getItem(position) method because you cant pick a specific item.
Use a different Adapter for your underlying DataModel like a ArrayAdapter.
Ok here some code for a AdapterImplementation
first:
YourCustomAdapter extends ArrayAdapter<YourDataObject>
than you simple implement the inherited Methods, the important Methods are getView and getItem
in getView you define how the Data will be shown in your ListView.
Use ViewHolder to cache your Items for scrolling.
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ViewHolder holder;
View v = convertView;
if (v == null) {
v = inflater.inflate(layoutResource, null);
holder = new ViewHolder();
holder.firstLine = (TextView) v.findViewById(R.id.textview);
v.setTag(holder);
} else {
// Get the ViewHolder back to get fast access to the TextView
// and the ImageView.
holder = (ViewHolder) v.getTag();
}
holder.firstLine = "test";
return v;
}
basicly you fill your holder with your stuff and save it inside your View, the next time your dont have to fill in your Resources again.
the second method getItem(int position) is simple:
you have to specify how to get the item on position "position" on your DataStructure.
If you have an Array you could write:
#Override
public long getItem(int position) {
return myDataArray.get(position);
}

Categories