Recyclerview Layout Manager Get View in Position After Scrolling in Android - java

Recyclerview is scrolling with it's LinearLayoutManager like that lm.scrollToPositionWithOffset(position, offset). How to get the view where in scrolled position before scrolling? The view that will scrolled returning null after scrolling because still not created. I've try Runnable, Observer and onLayoutCompleted but still null. How to get the view?
lm.scrollToPositionWithOffset(position, offset);
recyclerView.post(new Runnable(){
#Override
public void run(){
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = recyclerView.getLayoutManager().getChildAt(position); // returning null.
}
});
recyclerView.getViewTreeObserver()
.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = recyclerView.getLayoutManager().getChildAt(position); // returning null.
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}});
#Override // in LinearLayoutManager
public void onLayoutCompleted(RecyclerView.State state) {
super.onLayoutCompleted(state);
View v1 = recyclerView.getChildAt(position); // returning null.
View v2 = this.getChildAt(position); // returning null.
}

View v = lm.getChildAt(position);
lm.scrollToPositionWithOffset(position, offset);
v.post(new Runnable() {
#Override
public void run() {
// View is ready here.
});

If you're trying to modify the view content, you should do this:
Modify your current model: itemList.get(position) // and change
any property here to handle that state
Call adapter.notifyItemChanged(position)
Make sure you have the right logic on your ViewHolder to handle
this changes and that should modify your View
But if you really wanna change things through the ViewHolder you can also do this: recyclerView.findViewHolderForAdapterPosition(position) and then:
if (null != holder) {
holder.itemView.findViewById(R.id.YOUR_ID) // call any method do you want to
}
I really recommend the first option, but that's up to you. Hope this helps you!

Related

Click listener inside OnBindViewHolder

I have the following code for the recyclerview adapter for an android app that I'm working on right now:
#Override
public void onBindViewHolder(final FeedViewHolder contactViewHolder, final int i) {
final FeedInfo ci = feedInfoList.get(i);
//Set the text of the feed with your data
contactViewHolder.feedText.setText(ci.getFeed());
contactViewHolder.surNameText.setText(ci.getSurName());
contactViewHolder.nameText.setText(ci.getFirstName());
contactViewHolder.feedDate.setText(ci.getDate());
contactViewHolder.numberOfGoingText.setText(ci.getNumber_of_going());
contactViewHolder.numberOfInterestedText.setText(ci.getNumber_of_interested());
//seteaza fotografia de profil in postare
new ProfilePictureDownloadImage(contactViewHolder.profilePicture).execute(ci.getProfileImageURL());
ImageButton interestedButton = contactViewHolder.interestedButton;
interestedButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int position = i;
FeedInfo fi = feedInfoList.get(position);
int displayedNumberOfInterested = Integer.parseInt(ci.getNumber_of_interested()) + 1;
contactViewHolder.numberOfInterestedText.setText(Integer.toString(displayedNumberOfInterested));
System.out.println("emilutzy interested from within" + fi.getPostID());
contactViewHolder.surNameText.setText("kk");
}
});
}
The problem is the click listener. In theory the button I press should increment the number right next to it. However, since I have to declare onBindViewHolder's arguments as final, only the first click works, the rest of the clicks do not change the value of the number. I am new to Android, so could you please help me find a better solution?
There's a nice method called getAdapterPosition() that you can use in your RecyclerView's ViewHolder.
Instead of setting the click listener in onBindViewHolder, set it in the constructor of your ViewHolder like so:
public class FeedViewHolder extends RecyclerView.ViewHolder {
private TextView feedText;
private TextView surNameText;
private Button interestedButton;
// ... the rest of your viewholder elements
public FeedViewHolder(View itemView) {
super(itemView);
feedtext = itemView.findViewById(R.id.feedtext);
// ... find your other views
interestedButton.setOnClickListener(new View.OnClickListener() {
final FeedInfo fi = feedInfoList.get(getAdapterPosition());
int numInterested = Integer.parseInt(ci.getNumber_of_interested()) + 1;
// setting the views here might work,
// but you will find that they reset themselves
// after you scroll up and down (views get recycled).
// find a way to update feedInfoList,
// I like to use EventBus to send an event to the
// host activity/fragment like so:
EventBus.getDefault().post(
new UpdateFeedInfoListEvent(getAdapterPosition(), numInterested));
// in your host activity/fragment,
// update the list and call
// notifyDatasetChanged/notifyDataUpdated()
//on this RecyclerView adapter accordingly
});
}
}
Don't set your position in onBindViewHolder to final (Android Studio will warn you why).
I'm not sure how the object FeedInfo looks like but you could also at a method called for example increaseNumberOfInterested() which would increase the value of Number_of_interested by one and would persist in the object when the recyclerview recycle the cell. it would like kind of like below
#Override
public void onBindViewHolder(final FeedViewHolder contactViewHolder, final int i) {
final FeedInfo ci = feedInfoList.get(i);
//Set the text of the feed with your data
contactViewHolder.numberOfInterestedText.setText(ci.getNumber_of_interested());
contactViewHolder.interestedButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//Increase the number of interested in the object, so it can be persisted when cell is reclycled
ci.setNumberOfInterested(ci.getNumber_of_interested()) + 1);
//Get new value and display
contactViewHolder.numberOfInterestedText.setText(Integer.toString(ci.getNumber_of_interested()));
}

Add different layout of infoWindow to marker

I have a couple of markers on my map. To each one of them, I want to inflate a custom infoWindow.
The problem i'm having is that the infoWindow is the same for each one. I have read a couple of stack threads but I haven't figured out how to fix it.
Snippet where I add the markers to the map
for (int i = 0; i<cityObjects.size(); i++){
CityObject cObject = cityObjects.get(i);
Coordinates loc = cObject.getCoordinates();
LatLng pos = new LatLng(loc.getLatitude(), loc.getLongitude());
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName()));
loadInfoWindow(cObject.getImgs().get(0), cObject.getName());
builder.include(pos);
}
Method to inflate the custom infoWindow
public void loadInfoWindow(final String url, final CharSequence title) {
mMap.setInfoWindowAdapter(new GoogleMap.InfoWindowAdapter() {
#Override
public View getInfoWindow(Marker arg0) {
arg0.getId();
View v = getActivity().getLayoutInflater().inflate(R.layout.layout_info_window, null);
Button info = (Button) v.findViewById(R.id.infoButton);
info.setText(title);
BitmapLayout back = (BitmapLayout) v.findViewById(R.id.bitmapBackground);
Picasso.with(getContext()).load(url).into(back);
return v;
}
#Override
public View getInfoContents(Marker arg0) {
return null;
}
});
}
I read something about setInfoWindowAdapter being a setter and therefore overrides the infoWindow each time the for-loop iterates. Does anyone have a good solution on how to recognize the markers so I can inflate different layouts?
You can use marker.setTag() and marker.getTag(). You can give a tag to marker.for example
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName())).setTag(1);
mMap.addMarker(new MarkerOptions().position(pos).title(cObject.getName())).setTag(2);
and then in loadInfoWindow method
if((int)arg0.getTag==1)
{
//do something
}
else{
//do something
}
I feel a bit stupid for not seeing this but I managed to write a solution.
Thanks to #chetanprajapat for getting me on the right track.
Since I have all information about the marker(location, title), I can just pass that into a created class which will check for the title and then inflate the proper layout.
Created class for inflating the correct layout
public class CustomInfoWindowAdapter implements GoogleMap.InfoWindowAdapter{
#Override
public View getInfoWindow(Marker arg0) {
for (CityObject cityObject : cityObjects){
if (arg0.getTitle().equals(cityObject.getName())){
View v = getActivity().getLayoutInflater().inflate(R.layout.layout_info_window, null);
Button info = (Button) v.findViewById(R.id.infoButton);
info.setText(cityObject.getName());
BitmapLayout back = (BitmapLayout) v.findViewById(R.id.bitmapBackground);
Picasso.with(getContext()).load(cityObject.getImgs().get(0)).into(back);
return v;
}
}
return null;
}
#Override
public View getInfoContents(Marker arg0) {
return null;
}
}
And then I just set the adapter to a new Instance of CustomInfoWindowAdapter
mMap.setInfoWindowAdapter(new CustomInfoWindowAdapter());
Again, thanks to #chetanprajapat for the help.

Android: animateLayoutChanges(false) programmatically giving NullPointerException

This is not duplicate of 'animateLayoutChanges programmatically'.
I want the default animation effects on my view changes except from the fade out effect on removing the objects. I cannot use disableTransitionType() because it was added in API 16. All I could think of was disabling the android:animateLayoutChanges to false, remove the view and setting it again to true. In my recyclerView I thought of removing the animation by holder.rootView.setLayoutTansition(null). But unfortunately it is giving me NullPointerException.
A part of my code is something like this:
#Override
public void onBindViewHolder(final MyViewHolder holder, int position) {
SavedMessagePackage current = data.get(position);
final String message = current.message;
holder.message.setText(message);
holder.message.setMaxLines(2);
holder.message.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (holder.button.getVisibility() == View.GONE) {
holder.button.setVisibility(View.VISIBLE);
holder.message.setMaxLines(Integer.MAX_VALUE);
holder.message.setText(message);
}
else if (holder.button.getVisibility() == View.VISIBLE) {
holder.root_view.setLayoutTransition(null); // NullPointerException
holder.button.setVisibility(View.GONE);
holder.message.setMaxLines(2);
holder.root_view.setLayoutTransition(new LayoutTransition());
}
}
});
holder.button.setVisibility(View.GONE);
}
How can I achieve this?
In your case you could have holder or root_view being null, but since holder was accessed successfully before, so root_view is null.
Therefore it's most likely you didn't assign holder.root_view before.

Android - GridView setSelection not working

Currently using a tabbar/viewpager with fragments setup for this project. Fragment 2 contains a gridview. At app startup I'm trying to select a gridview cell by default - but no matter what I do it does not 'select'. I'm beginning to wonder if this is because at the time the selection tries to take place, the gridview is off screen (page/fragment 2 of the viewpager).
What I'm doing is after the getView method of the GridViewAdapter is initially complete (I'm comparing position to total number of possible cells to determine this) I fire a listener message to select the default cell in the GridView. I did it this way to (a) ensure that the cell I'm trying to select is non-null, and (b) I wondered if the getView method was resetting the selection somehow.
public View getView(int position, View convertView, ViewGroup parent) {
View row = convertView;
// * Other code that sets up the view
if (listener!=null) {
if ((list.size()-1)==position) {
Log.d(TAG, "Today position set: " + todayPosition);
listener.todayPositionFound(todayPosition);
}
} else {
Log.d(TAG, "LISTENER IS NULL");
}
return row;
}
and then...
public void todayPositionFound(final int position) {
// * ------------------------
// * On startup, select today
// * ------------------------
mCurrentlySelectedDate = DateHelper.todayAsString();
Log.d(TAG, "Todays Position Found: " + position);
View v = calendarView.getChildAt(position);
if (v!=null) {
Log.d(TAG, "V not NULL - SELECTING");
v.setSelected(true);
}
Log.d(TAG, "SELECTED? " + calendarView.getSelectedItemPosition());
}
All of this goes off without a problem, aside from the fact that the view is then NOT selected. Furthermore, when I getSelectedItemPosition it returns -1 ... even though I just 'selected' position 16. Any thoughts on this would be much appreciated. Thank you!
To get this working I used a Handler and Runnable:
public void todayPositionFound(final int position) {
Handler h = new Handler();
Runnable r =new Runnable() {
public void run() {
View v = calendarView.getChildAt(position);
if (v!=null) {
v.setSelected(true);
}
}
};
h.postDelayed(r, 500);
}
If someone has a better solution please do let me know. Thanks!
You can only update the list scroll position after the the List/GridView has been drawn. This happens a short time after onCreate() or onResume() or onCreateView() has been called.
You could try using a Global layout listener to tell you when the GridView has been drawn, for example:
GridView calendarView = (GridView)findViewById(R.id.YOUR_VIEW_ID);
ViewTreeObserver viewTreeObserver = calendarView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
this.calendarView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
View v = calendarView.getChildAt(position);
if (v!=null) {
v.setSelected(true);
}
}
});

Set EditText visibility based on Spinner value

I'm trying to create this dialog:
.
When Spinner is set to custom value, TextEdit should automatically appear. I'm calling View.setVisible() on the TextView but the visibility is not evaluated immediately but waits to another change - e.g. adding another row or setting a date.
The code:
...
customText = (EditText) v.findViewById(R.id.edit_custom_text);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
s.setAdapter(adapter);
s.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int i, long l) {
SpinnerItem si = (SpinnerItem) adapterView.getItemAtPosition(i);
evt.type = si.eventType;
if (evt.type == EventType.CUSTOM) {
customText.setVisibility(View.VISIBLE);
} else {
customText.setVisibility(View.GONE);
}
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
//do nothing
}
});
I tried View.invalidate() (on parent view) and View.refreshDrawableState() with no luck :/
Edit: The code above is reached (verified by debugger) and I also tried View.INVISIBLE. The view is just not refreshed immediately but only after another change in view.
For Example see this
s.setOnItemSelectedListener(new OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> parentView,View selectedItemView, int position, long id) {
if ("YES".equals(s.getSelectedItem().toString().toUpperCase())) {
youredittxt.setVisibility(View.VISIBLE);
} else if ("NO".equals(s.getSelectedItem().toString().toUpperCase())) {
youredittxt.setVisibility(View.INVISIBLE);
}}
#Override
public void onNothingSelected(AdapterView<?> arg0) {
// TODO Auto-generated method stub
}
});
That should work, could it be that your layout somehow doesn't allow/recognises this change perhaps?
Try changing it to INVISIBLE instead of GONE, including (important!) in your layout xml file.
If that works for some reason, try something like this:
customText.getParent().requestLayout(); //possibly the parent of that etc
As a follow up question, are you in the main UI thread? Because Android have some built in features and policies, so only the owning thread will be able to change the UI.
If you are outside the same thread, try:
customText.getHandler().post(new Runnable() {
public void run() {
customText.setVisibility(View.VISIBLE);
}
});
Hope this helps! :)
Verify that you are actually reaching your code block.
customText.setVisibility(View.GONE);

Categories