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);
}
}
});
Related
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!
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()));
}
I am facing a Firebase RecyclerView problem where I cannot remove unwanted CardViews from my RecyclerViews. In my code I check the city's name and the guide's chosen city to match them. It populates guide's details only if the guide's city matches the picked city, but it also shows empty cardview with default layout.
guideDataRef = FirebaseDatabase.getInstance().getReference().child("Guides");
public void recycler() {
super.onStart();
try {
//Guide RecyclerView
Query guideQuery = guideDataRef.orderByKey();
guideQuery.keepSynced(true);
FirebaseRecyclerOptions guideOptions =
new FirebaseRecyclerOptions.Builder<UserModelClass>().setQuery(guideQuery, UserModelClass.class).build();
guideAdapter = new FirebaseRecyclerAdapter<UserModelClass, guideViewHolder>(guideOptions) {
#Override
protected void onBindViewHolder(#NonNull guideViewHolder holder, final int position, #NonNull final UserModelClass model) {
String pickedcity = model.getPickedCity();
String postname = (String) cityName.getText();
if(pickedcity.equals(postname)) {
final String guide_key= getRef(position).getKey();
holder.setGuideName(model.getName());
holder.setGuideSurname(model.getSurName());
holder.setGuideImage(getApplicationContext(), model.getPhotoURL());
// holder.mView.setVisibility(View.VISIBLE);
//Guide Click listener
holder.mView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent guideHireIntent = new Intent(getApplication(), GuideHireActivity.class);
guideHireIntent.putExtra("guide_id", guide_key);
finish();
startActivity(guideHireIntent);
}
});
}
}
#NonNull
#Override
public guideViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout_guides, parent, false);
return new guideViewHolder(view);
}
#Override
public void onError(DatabaseError e){
Toast.makeText(getApplicationContext(), "Error by stopping ", Toast.LENGTH_SHORT).show();
}
#Override
public int getItemCount() {
return super.getItemCount();
}
#Override
public void onDataChanged() {
super.onDataChanged();
notifyDataSetChanged();
}
};
guideAdapter.notifyDataSetChanged();
guideRecyclerView.setAdapter(guideAdapter);
guideAdapter.startListening();
} catch (DatabaseException e) {
Toast.makeText(this, "Error", Toast.LENGTH_SHORT).show();
}
}
enter image description here
enter image description here
I can change the adapter visibility to gone if it does not match with the requirements but the problem is that after making it's visibility gone it is still there holding the place (but invisible - there's still an empty space). How can I avoid populating an item from the recycler view completely, instead of making it invisible if the requirements do not match?
You're not showing what guideDataRef is in your code, so I'm assuming that it's just aDatabaseReference object for everything beneath a \Guides node.
If you're doing that, you're going to get a call for onBindViewHolder for every child at that particular location. This means that you're going to be asked to make a view for every child. You cannot choose whether or not a view will appear for that item.
It looks like you're assuming that your if statement in onBindViewHolder method will skip over those items. But what's actually happening is that you're simply allowing an empty view to occupy that spot in the list.
Instead, you should come up with a query that generates only the items of interest to your list. This means you'll have to tell Firebase to filter for children that meet your criteria.
You can also read the entire contents of the location, manually filter out the items you don't want, and build a list of items you do want. You can then build an custom adapter with that list, and it can then become the input to a ListView or even better to a RecyclerView.
I have been reading different posts on here about baseadapters and trying to learn so that I can fix my issue but I haven't been able to resolve it. On my BaseAdapter I have a String called post that is used in a column in the listview. If the post is longer than 13 characters then it is shortened automatically when the user Clicks on the shortened post then it displays it's full length,however the issue is that once you scroll down the listview and come back up to that same post it's still shortened even though the user clicked before to show the full post. I think this is an issue of the Listview or Baseadapter recycling or cache mechanism is there anyway I can fix this? This image will clear things up .. This post is more than 13 characters so it shows the shortened version
if a user wants to read it in full then they will click on the Read More which will then show all of the content which looks like this
and when the user scrolls down or up that same long post will return to this without the user clicking it again, which I want to avoid
I know that the Listview recycles but how can I update it? This is my code below
public class LocalFeed_CustomView extends BaseAdapter {
JSONObject names;
Context ctx;
Activity m;
// More is the default value
String result="More";
#Override
public int getCount() {
try {
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
return jaLocalstreams.length();
} catch (Exception e) {
Toast.makeText(ctx, "Error: Please try again", Toast.LENGTH_LONG).show();
return names.length();
}
}
#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) {
View row=convertView;
MyViewHolder holder=null;
try {
if (row == null) {
LayoutInflater li = (LayoutInflater) m.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
row = li.inflate(R.layout.customadapter, null);
holder = new MyViewHolder(row);
row.setTag(holder);
} else {
holder = (MyViewHolder) row.getTag();
}
final MyViewHolder finalHolder1=holder;
// Json data has been read
JSONArray jaLocalstreams = names.getJSONArray("localstreams");
final JSONObject jsonObject = jaLocalstreams.getJSONObject(position);
// if post length is more than 14 then shorten it
if (jsonObject.getString("post").length() > 14) {
holder.post.setText(jsonObject.getString("post").substring(0, 13) + "...Read More");
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
try {
// if Result is More then show full post
if (result.equals("More")) {
finalHolder1.post.setText(jsonObject.getString("post") + "... Read Less");
result = "Less";
}
else
{
//Result is Less so shorten it again
finalHolder1.post.setText(jsonObject.getString("post").substring(0, 13) + "... Read More");
result = "More";
}
} catch (JSONException e) {
e.printStackTrace();
}
}
});
} else{
// This Post is already less than 14 characters so no Onclick here
holder.post.setText(jsonObject.getString("post"));
}
return row;
} catch (Exception e) {
e.printStackTrace();
}
return row;
}
class MyViewHolder{
TextView post;
MyViewHolder(View v)
{
post = (TextView)v.findViewById(R.id.post);
}
}
}
The adapter represents the model of the list at any given moment in time.
What this means to you is that if a user clicks a TextView to expand it with the idea that the view is going to stay expanded, then that expanded TextView is state information that will have to be captured in the adapter.
Adapters should always be thought of in two phases:
Event (like onClick()) will update state in the adapter and call notifyDataSetChanged().
getView() uses the current state to create the view.
So let's say in the adapter constructor we create an array of flags
boolean expanded[] = new boolean[size];
where size is the length of your list.
Then you can do this:
// use the current state to create the view
String text;
if (expanded[position]) {
text = jsonObject.getString("post") + "... Read Less";
} else {
text = jsonObject.getString("post").substring(0, 13) + "...Read More";
}
holder.post.setText(text);
holder.post.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// update the current state and request list refresh
expanded[position] = ! expanded[position]; // toggle
notifyDataSetChanged();
}
});
This code doesn't do exactly what yours does, I just wanted to give you the basic idea.
What I want to accomplish:
I want one TextView per row of a ListView to be dynamically set (its display text) to the value from a SQLite database.
What I tried:
Inside getView(), I assigned the said TextView via findViewById() to a global variable. Then I assigned the value position (from the parameter of getView()) to my global variable mListRowPosition. After this, I execute my AsyncTask sub-class via new AttemptGetCustomerInfo().execute().
My AsyncTask sub-class gets the SQLiteDatabase from my DatabaseHelper. With the mListRowPosition it receives the customer_id from the method getCustomerID() of the Service object inside the ArrayList<Service> dataset.
Together with the customer_id, it builds a SQL query to get the shortname of the customer. After querying, it gets the String shortname from the Cursor. I then setText(shortname) the global TextView from before (inside getView()).
The problems with that:
This 'kind of works' at some point, but it seems to be so slow that only the last (almost every time) has a value set as its text. Sometimes it also gets it wrong.
After debug-logging I saw that getView() gets called a lot faster than the AsyncTask is even finished (this makes sense, but it destroys my solution for my problem).
Also interesting: My debug log tells me that getView() gets called more often then there are data entries inside the ArrayList. If there are 5 entries, it will call getView() about 15 to 20 times. Why?
The code behind that:
public class ServiceAdapter extends ArrayAdapter<Service> {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// Get the data item for this position
Service service = getItem(position);
// Check if an existing view is being reused, otherwise inflate the view
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(
R.layout.listview_row, parent, false);
}
// Lookup view for data population
TextView quantity = (TextView) convertView
.findViewById(R.id.QUANTITY_CELL);
TextView description = (TextView) convertView
.findViewById(R.id.DESCRIPTION_CELL);
Button delete = (Button) convertView.findViewById(R.id.BUTTON_DELETE);
customerView = (TextView) convertView.findViewById(R.id.CUSTOMER_VIEW);
mListRowPosition = position;
Log.d("ServiceAdapter", "getView changed mListRowPositon to be "
+ String.valueOf(mListRowPosition));
new AttemptGetCustomerInfo().execute();
// Populate the data into the template view using the data object
quantity.setText(String.valueOf(service.getQuantity()));
description.setText(service.getDescription());
// Set up the listener for the delete button.
final int pos = position;
delete.setOnClickListener(new Button.OnClickListener() {
#Override
public void onClick(View v) {
showDialog("deleteButton", pos);
}
});
customerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showDialog("customerView", pos);
}
});
// Return the completed view to render on screen
return convertView;
}
class AttemptGetCustomerInfo extends AsyncTask<String, String, String> {
String shortname = null;
#Override
protected String doInBackground(String... params) {
DatabaseHelper db_helper = new DatabaseHelper(context);
SQLiteDatabase db = db_helper.getReadableDatabase();
Log.d("AttemptGetCustomerInfo",
"ListRowPos: " + String.valueOf(mListRowPosition));
Log.d("AttemptGetCustomerInfo", "description of ListRowPos: "
+ services.get(mListRowPosition).getDescription());
int customer_id = services.get(mListRowPosition).getCustomerID();
Log.d("AttemptGetCustomerInfo",
"customer id: " + String.valueOf(customer_id));
Cursor c = db.rawQuery(
"SELECT " + CustomerEntry.COLUMN_NAME_SHORTNAME + " FROM "
+ CustomerEntry.TABLE_NAME + " WHERE "
+ CustomerEntry.COLUMN_NAME_CUSTOMER_ID + "="
+ customer_id, null);
Log.d("AttemptGetCustomerInfo",
"available cursor size" + String.valueOf(c.getCount()));
if (c.getCount() == 0) {
Log.d("AttemptGetCustomerInfo", "There are no Cursor entries!");
return null;
}
c.moveToFirst();
shortname = c
.getString(c
.getColumnIndexOrThrow(CustomerEntry.COLUMN_NAME_SHORTNAME));
db.close();
return null;
}
#Override
protected void onPostExecute(String s) {
if (shortname != null) {
customerView.setText(shortname);
}
}
}
Additional info:
I didn't paste all of the code and a lot of code refering happens inside the above code to which no code is there also. I hope the function of my methods not shown are understandable via the method name.
So, lets begin.
If you want to display information from your database in your Listview I strongly recommend you using CursorAdapter instead of ArrayAdapter it will work far faster than it is probably now.
About the getView() calling, that happens because of the way Android paint listviews, the first time Android will call getView() several times in order to display properly things, if you change for example your ListView height from match_parent to wrap_content or viceversa you will notice that your getView() method will get called a different number of times.
Now about your code, you are not properly programming your getView() method. Inside the first part if (convertView == null) you should define your views using the ViewHolder pattern which will improve your performance.
Another problem I find here is that you are launching your AsyncTask everytime you your getView() method is called, that will produce a problem with your ListView scrolling as it will prevent of going smoothly (In a tablet for example you will run like 40 or 50 asynctask one after another, and that is a heavy workload).
If you want to keep your current code which I would strongly discourage you to do it, you will need a way of controlling if the current row have been executed your AsyncTask code in order to not repeat that work.
Hope it helps