How do I initialize elements of a GridView on creation? - android

My android is very rusty, so this is the best way I can explain this:
A card contains an image, a value, and a URL
I have an array of values, a parallel array of images, and of URLs (values[i] <-> images[i] <-> URLs[i])
Have a GridView that I want to use to display many of these cards
The problem:
I have a class that extends BaseAdapter to create a custom view to display the three elements of the card
Using the getView method of said adapter, I use the "i" expected by getView as a mental index of which card we are talking about.
Unfortunately I realized that i=0 means the currently visible first card, I thought it meant the overall first card. This makes it useless as a system to keep track of the overall position of cards.
So, the visible elements are populated correctly in the view. But, if I scroll down and then back up, some internal elements have been jumbled up. So clicking a card might now lead to the URL of a card that was initialized after it.
What I need help with:
A better way to index or populate each card's content that will be permanent.
I am wildly confident I am doing this in a horrendous way. I'm imagining there must be some way to say that:
When GridView is created -> populate each card's details and fill in GridView.
Current Main Activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_card_list);
gv = (GridView) findViewById(R.id.cardGridView);
gv.setAdapter(new CardView(this, cardURLs, cardNames, cardPrices, cardImages));
}
Current CardView Activity:
public CardView(CardListActivity mainActivity, String[] cardURLs, String[] cardNames, Double[] cardPrices, int[] cardImages){
//...
inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public class Holder{
TextView priceTV;
ImageView cardIV;
String cardName;
}
#Override
public View getView(final int i, View convertView, ViewGroup parent) {
//...
View rowView;
rowView = inflater.inflate(R.layout.card_item_view, null);
//HERE IS WHERE I SET THE PRICE AND IMAGE USING i
holder.priceTV.setText("$" + prices[i].toString());
holder.cardIV.setImageResource(images[i]);
//...
return rowView;
}

Turns out the problem was something else.
The actual problem ended up being caused by these Dialogs I would create to verify if the user wanted to open the website.
I was creating them inside getView, all in the same variable, which meant that the last elelemnt to get initialized would be the one used in the dialog.
I fixed this by moving the dialog creation into the onClick for the view.

Firstly, You should wrap your contents into objects so that each CardContent object contains a url, an image and a value, Then pass those into your adapter. That will be much easier on you, you only need to maintain 1 List of CardContent rather than 3 individual lists and hoping the order doesn't get messed up.
Secondly, This sounds like a case for a Recyclerview. You can use a GridLayoutManager with a Recyclerview instead of a GridView so that your views get recycled and you have less overhead. Luckily the code is largely the same.
See https://developer.android.com/training/material/lists-cards.html for pretty much what you want.

Related

Different item view for each item in a Recycler View

I am trying to build a horizontal recycler view and each item is the same parent type but can include up to ten different subviews attached to it. I have looked at this answer which explains how to override
getItemViewType(int position)
and
createViewHolder(ViewGroup parent, int viewType)
But this only works if you have a limited number of views for each item. The list of items I have to display can have a size of 40 and each one can include up to 8 images each in a different position on the parent view.
My original plan was to create each item view dynamically in the Recycler View Adapter in
onBindViewHolder(ViewHolder viewHolder, int i)
This caused issues because I had to create all the individual views and attach them to the parent view each time I saw the item. So then I created a list of views in the activity which holds the recycler view and instead of passing in the list of objects to create the views, I just passed in the views themselves. This worked much better due to the fact I never had to read each item every time and create a custom view for each item every time it loaded.
My problem now is
It is still not as smooth as I would like. If you have any solutions or ideas on how I could do this faster or more proper. let me know.
Here is an example image of three possible different views for the recycler view. So these views only include the photos and shapes. But other views include text and colors.
Each item view has the option for up to 8 images, borders or no borders around >these images, up to 4 text boxes with striped, solid line, no borders, clip art >and the option for different colours on every one view items. Also different >sizes and different positions on each item. The possibilities are pretty much endless of what the item can look like
We can actually simplify this to
I have view with maximum 8 images and 4 text, that can have different
style and position.
Then there is only 45 possibilities for picking how many images and text there will be (counting possibility of no image or text).
So if i make simple RelativeLayout with enought elements, and then will move them as we need...
Hovewer saying 45 possible types are well...
Way too much. You see making any adapter carry too many item types are not very smart, simply put when adapter is set for recycler view (or ListView) one of first thing it does is creating "small" ViewPool (RecyclerViewPool), its basically simple sparse array , that will keep any removed ViewHolder, until its time to be reused will come, and if the heap is too big it will be removed. That is theory, in practice there are limit of how many scap childs can be at specified time (for recycler its around 5 i think), so if we will be creating view holders with many types we want be hitting that cache most of the time, and since there is no scrap view holder for our type createViewHolder will be called, and in fact this will feel more like we dont have view holder at all.
One more thing.
So then I created a list of views in the activity which holds the recycler view >and instead of passing in the list of objects to create the views, I just >passed in the views themselves. This worked much better due to the fact I never >had to read each item every time and create a custom view for each item every >time it loaded.
Recycler view is used to to unload items (views) that are no longer needed, or should i say that are not visible to user, and also to reause old views us much as possible to reduce number of layout inflatings, and searches for id.
And by loading them all at all times, this functionality is ommited.
If im not mistaken this might be just a bit slower than creating ScrollView with LinearLayout (Vertical) and putting all views inside.
Hovewer biggest drawback here is that all your views are loaded at all times, and if they are simple i dont really see why not use ScrollView + LinearLayout, but if not well this gets complicated, on new phones, memory is not such big problem, there are phones with 500+ mb, but even so not every phone will give you 300 mb for your application.
My Answer
I can guess it want be what you wanted to hear :)
The thing that slows down layout inflating, isnt actually setting texts, background or position but loading resources to do that, and searching for views.
So lets start with:
Use cache for your loaded resources to reuse them us much as you can, (i added small section about image loading at the end).
FindViewById is really slow, i mean it, just by using viewholder pattern and removing searching for view you can get a nice speed up.
You want be getting away from setting layout for each element.
Idea is pretty simple create groups of layouts depending on amount of your main hierarchy Views (this time) Images and text. While creating ViewHolder create RelativeLayout, push enough images/text to suit your need for element at position. While binding data move elements and style them as needed.
To reduce number of types, lets use number of images/text rounded to 2.
Updating LayoutParams does not cost us much as most think, it cost some, dont get me wrong, this basically forces parent to make additional child measurement and layout update, thats all, and while view is going to be diplayed it has to make this measurement and layout update anyway :)
Lets say we have this two groups
Images (max 2) id: 0
Images (max 4) id: 1
Images (max 6) id: 2
Images (max 8) id: 3
And
Texts (max 2) id: 0
Texts (max 4) id: 1
And this is how i will be "naming" types
int type = (img_id)<<1 | (txt_id)
And total type count is actually
int type_count = (4 * 2) = 8
End our "base" for adapter is something like this
public class MyAdapter<MyType extends MyAdapter.MyTypeBase> extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
Context context;
ArrayList<MyType> elements;
public MyAdapter(Context context, ArrayList<MyType> elements) {
this.context = context;
this.elements = elements;
}
public int getItemCount() { return elements.size(); }
public MyType get(int position) { return elements.get(position); }
public int getItemViewType(int position) {
int imgs = get(position).getImageCount();
int txts = get(position).getTextCount();
imgs = (imgs/2) * 2 + (imgs % 2 == 0 ? 0 : 1);
txts = (txts/2) * 2 + (txts % 2 == 0 ? 0 : 1);
return imgs << 1 | txts;
}
#Override public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
return new ViewHolder(new RelativeLayout(context), position); // or use inflater to inflate some base layout
}
class ViewHolder extends RecyclerView.ViewHolder {
int textCount, imageCount;
RelativeLayout root;
ImageView [] imageViews;
TextView [] textViews;
public ViewHolder(RelativeLayout view, int position) {
super(view);
root = view;
textCount = (position & 1) * 2;
imageCount = (position >> 1) * 2;
textViews = new TextView[textCount];
imageViews = new ImageView[imageCount];
for (int i = 0; i < imageCount; i++) {
root.addView(imageViews[i] = new ImageView(context)); // or use inflater
}
for (int i = 0; i < textCount; i++) {
root.addView(textViews[i] = new TextView(context)); // or use inflater
}
}
}
interface MyTypeBase {
int getImageCount();
int getTextCount();
}
And yes somethings is missing :)
public void onBindViewHolder(ViewHolder viewHolder, int position) {
MyTypeBase element = get(position);
vh.textViews[vh.textCount-1].setVisibility(element.getTextCount()%2 == 0? View.VISIBLE : View.GONE);
vh.imageViews[vh.imageCount-1].setVisibility(element.getImageCount()%2 == 0? View.VISIBLE : View.GONE);
// now this is your place to shine
}
Since i dont know what exactly you can get as "data" for your element cant help you much here. So in on bindViewHolder, you viewHolder will have enough images and text as your element needs, reload any image and move them.
Last notes
I should also add fev notes here and there :)
Be lazy. Yes i mean it when you want to be lazy you try to make your solution as simple as possible making code more easy to read, and less likely to have major bugs :)
Also please use library for displaying and caching images instead of loading them each time. There are quite few good ones like UniwersalImageLoader, Picasso or Volley. I personally use UIL so heres example.
// This might be done in helper class ^^
DisplayImageOptions OPTIONS_DISC_AND_CACHE = new DisplayImageOptions.Builder()
.cacheOnDisk(true)
.cacheInMemory(true)
.build();
ImageLoaderConfiguration configuration = new ImageLoaderConfiguration.Builder(context)
// .memoryCacheSize((int) (Runtime.getRuntime().maxMemory() / 1024) / 16) // if you want to reduce it some more ^^
.threadPoolSize(4)
.tasksProcessingOrder(QueueProcessingType.LIFO)
.build();
imageLoader.init(configuration);
// And thats how to use it
ImageLoader.getInstance().displayImage("MY URL", (ImageView) myImageView, Tools.OPTIONS_DISC_AND_CACHE);
And why you should use such library, dunno... Images are loaded asynchronusly (not on ui, making it more resposible), you dont care for how they are actually loaded, and when, adding placeholders is easy, cache in memory and cache in for disk allows for faster loading times.
And lastly, english is not my primary language, so if i misspelled something, forgot comma, or wrote sentence in wrong manner, please do make corrections.
Cheers.
1 Create a list of lists each item should contain 1 or more image
List<List<image>> list
2 pass it to your adapter
3 in the getItemView method use if to set the type according to the
list.get(position).size();
4 use if again or isinstanceof or I think the viewHolder has a method to get its class and cast your viewHolder to the right class .

android custom listactivity arrayadapter

I'm trying to wrap my head around how to implement a custom arrayadapter to provide items for a list activity and then display that selected item in a new activity.
For example, i'm pulling a list of documents from a RESTful web service and i want to display these in a listactivity. My first call to the API will return JSON of documents with two fields: title, and id. I'd like to populate my listactivity with just the title of the documents in the UI. When i click on the item it, ideally, it will open a normal activity where it will make another API call to return the entire selected document in JSON format and display it into the new activity's UI.
After googling around, i've come up with what i think my needed steps are:
Create a RecordListItem class that will only contain the title and id's of the list items.
Create an arrayadapter of the RecordListItem type.
Attach that arrayadapter to my listactivity.
???
I'm confused on the proper way to pass the id of the selected item to the normal activity so i can make the API call to pull that specific record. Do those steps make sense?
I'm used to web dev so this is a different way of thinking and i'm stuck. Can someone explain the correct steps or possibly point me to a tutorial that displays the selected item in a new activity?
In your ArrayAdapter constructor you pass the array with the title/id
public Docs_Array(Context context, Object[] docs) {
super(context, R.layout.row, docs);
this.context = context;
this.docs = docs;
}
in the getView method you'll set each row's layout and information, the "position" argument is how you will get the document that you want. You can cast the Object[i] instance to another array if you want to have more than just one thing, like title+id.
public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(R.layout.row, parent, false);
TextView textView = (TextView)rowView.findViewById(R.id.row_title);
textView.setText(((String[])docs[position])[0]); // 0 for title.
}
add an onClickListener to each row view, and ((String[])docs[position])[1] will give you the id for the document title that was clicked.
Without seeing any code, it sounds like you are on the right track. I would suggest maybe storing the id's in a HashMap using the title as the key and the id as thevalue. Then, when you select your item you can look up theidusing thetitleand send it to your nextActivity`. That is probably how I would handle it.
As far as tutorials, I would start with the Docs if you haven't already. Then if you have a specific problem I'm sure you can find similar issues people have had on SO and many more on the Google. Hope this helps

Confused about ViewHolder pattern and convertView

I am new to Android development and reading through some example code. I have copied one method from the sample code in an Adapter class (derived from ArrayAdapter), the derived class has a checkbox in addition to the text view:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View listItem = super.getView(position, convertView, parent);
CheckedTextView checkMark = null;
ViewHolder holder = (ViewHolder) listItem.getTag();
if (holder != null) {
checkMark = holder.checkMark;
} else {
checkMark = (CheckedTextView) listItem.findViewById(android.R.id.text1);
holder = new ViewHolder(checkMark);
listItem.setTag(holder);
}
checkMark.setChecked(isInCollection(position));
return listItem;
}
private class ViewHolder {
protected final CheckedTextView checkMark;
public ViewHolder(CheckedTextView checkMark) {
this.checkMark = checkMark;
}
}
The sample code is to optimize the getView by caching the View within a ViewHolder object.
Where I am confused is I thought the convertView, if not null, would be re-purposed and then the View data is populated into it and returned.
If this is the case, then how could the setTag / getTag methods called in the code be relied upon? It would seem that the same object would have to be retrieved in order for it to work?
perhaps view returned from getTag on a subsequent call is for a different list item, and returns the wrong view
Adapters use a RecycleBin. This class allows the ListView to only create as many row layouts as will fit on the screen, plus one or two for scrolling and pre-loading. So if you have a ListView with 1000 rows and a screen that only displays 7 rows, odds are the ListViiew will only have 8 unique Views.
Now to your question using my example above: only eight row layouts and 8 subsequent ViewHolders are ever created. When the users scrolls no new row layouts are ever created; only the content of the row layout changes. So getTag() will always have a valid ViewHolder that references the appropriate View(s).
(Does that help?)
You're on the right track, here's some information that may help make more sense of how ListViews work:
A simple implementation of the getView() method has two goals. The first is inflating the View to be shown on the list. The second is populating the View with the data that needs to be shown.
As you stated, ListViews re-purpose the Views that compose the list. This is sometimes referred to as view recycling. The reason for this is scalability. Consider a ListView that contains the data of 1000 items. Views can take up a lot of space, and it would not be feasible to inflate 1000 Views and keep them all in memory as this could lead to performance hits or the dreaded OutOfMemoryException. In order to keep ListViews lightweight, Android uses the getView() method to marry Views with the underlying data. When the user scrolls up and down the list, any Views that move off the screen are placed in a pool of views to be reused. The convertView parameter of getView() comes from this list. Initially, this pool is empty, so null Views are passed to getView(). Thus, the first part of getView should be checking to see if convertView has been previously inflated. Additionally, you'll want to configure the attributes of convertView that will be common to all list items. That code will look something like this:
if(convertView == null)
{
convertView = new TextView(context);
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
The second part of an implementation of getView() looks at your underlying data source for the list and configures this specific instance of the View. For example, in our test list, we may have an Array of Strings to set the text of the view, and want to set the tag as the current position in the Data of this View. We know which item in the list we're working with based on the position parmeter. This configuration comes next.
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
This allows us to minimize the amount of time we spend inflating/creating new views, a costly operation, while still being able to quickly configuring each view for display. Putting it all together, your method will look like this:
#Override
public View getView(int position, View convertView, ViewGroup)
{
if(convertView == null)
{
convertView = new TextView(context);
//For more complex views, you may want to inflate this view from a layout file using a LayoutInflator, but I'm going to keep this example simple.
//And now, configure your View, for example...
convertView.setTextSize(28);
convertView.setTextColor(R.color.black);
}
//Configure the View for the item at 'position'
String listText = myListStringsArray[position];
((TextView)convertView).setText(listText);
convertView.setTag(position);
//Finally, we'll return the view to be added to the list.
return convertView;
}
As you can see, a ViewHolder isn't needed because the OS handles it for you! The Views themselves should be considered temporary objects and any information they need to hold onto should be managed with your underlying data.
One further caveat, the OS does nothing to the Views that get placed in the pool, they're as-is, including any data they've been populated with or changes made to them. A well-implemented getView() method will ensure that the underlying data keeps track of any changes in the state of views. For example, if you change text color of your TextView to red onClick, when that view is recycled the text color will remain red. Text color, in this case, should be linked to some underlying data and set outside of the if(convertView == null) conditional each time getView() is called. (Basically, static setup common for all convertViews happens inside the conditional, dynamic setup based on the current list item and user input happens after) Hope this helps!
Edited - Made the example simpler and cleaned up the code, thanks Sam!

Setting tags to each item in a ListView in Android?

I have a ListView where I want each item to have an ID number attached to it (not the same as the position number). I was hoping this could be done by setting a tag to each View item in the ListView using setTag() when these Views are being created.
Right now I'm creating the ListView like this:
final ListView listview = (ListView) findViewById(R.id.listView1);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, names);
listview.setAdapter(adapter);
The names variable in the ArrayAdapter parameters above is an ArrayList, and each string value in this list also has a unique ID that I want to link to this string somehow.
Is there any way I can get access to and modify each of the Views with a tag? One idea was to create my own extended class of ArrayAdapter and override the getView() method, but I don't really understand how it works and how I would go about doing this.
Or is there a better way to link IDs with each string like this than adding tags like I'm trying to do?
Create a ViewBinder and set the tags as the ListView is being populated with whatever you need. You can check all properties of the view to determine what tag goes where, so this should be what you're looking for.
myAdapter.setViewBinder(new MyViewBinder());
public class MyViewBinder implements ViewBinder {
#Override
public boolean setViewValue(View view, Object data, String text){
//Since it iterates through all the views of the item, change accordingly
if(view instanceof TextView){
((TextView)view).setTag("whatever you want");
}
}
}
I just used this exact same answer on another question (albeit slightly different) yesterday.
about getView , it works by using a method of recycling views. i will try to explain it in a simple way.
suppose you have tons of items that can be viewed . you don't want to really create tons of views too , since that would take a lot of memory . google thought of it and provide you the means to update only the views that need to be shown at any specific time.
so , if there is an empty space on the listview , it will be filled with a new view . if the user scrolls , the view that becomes hidden is recycled and given back to you on the getView , to be updated with the data of the one that is shown instead .
for example , if you scroll down , the upper view becomes hidden for the end user , but in fact it becomes the exact same view that is on the bottom .
in order to understand how to make the listview have the best performance and see in practice how and why it works as i've talked about , watch this video:
http://www.youtube.com/watch?v=wDBM6wVEO70
as for tags , i think you want to do something else , since the data itself (usually some sort of collection, like an arrayList) already knows where to update , because you get the position via the getView . if you want a specific view to update , you might be able to do so by using a hashmap that keeps upadting , which its key is the position in the collection , and the value is the associated view . on each time you go to getView , you need to remove the entry that belong to the view (if exists) and assign the new position with the view that you got/created .
Thanks for the answers. thisMayhem's answer would probably have been easier in the end, but on my quest to learn more I ended up making my own adapter according to this tutorial. I pass down the names and the IDs into the adapter and set the names as the text of the TextViews and the IDs as the tags.
I would rather go with the solution discussed in this thread. It is always the easiest to have all related data in same place and in this case you just create a class to hold all the information you will need for every item.

Marking text in the row of the list as "READ" permenatly Android

What I'm trying to accomplish here is to set the text in a given row as read if the user clicks on it, now I was able to do that by using the onclick method, the problem with it is that it goes away when an intent is fired or the user exits the app. I want the Text to be set up as read permanently. here is my piece of code if anybody can help I would greatly appreciate it.
Thank you in advance:
public void onListItemClick(ListView parent, View v, int position, long id) {
LinearLayout ll = (LinearLayout) v;
TextView clickedTextView = (TextView) ll.getChildAt(1);
clickedTextView.setTextColor(Color.GREEN);
StringTokenizer st = new StringTokenizer(strings[position],"<#>");
for(int i=0;i<3;i++)
{
coupon = st.nextToken("<#>");
}
sharable=st.nextToken();
Intent i = new Intent(getApplicationContext(), CouponImage.class);
i.putExtra("The coupon", coupon);
i.putExtra("Sharable", sharable);
startActivity(i);
}
You'll have to store the read status for each text item, either in a SQLite Database, or in a flat file in Internal Storage.
This is because whenever you scroll the list, leave the app and come back, etc. you end up with the ListAdapter re-rendering your row view, and your views in a list are never 1:1 with underlying data due to view recycling. If you want your change to be "sticky" you need to think about modifying the underlying data for the ListAdapter in a way it the adapter knows how to render correctly, not just changing this particular instance of a row -- you're marking the item read, not just setting one instance of a rendered view of the item as read. You can think of it as an MVC thing if it helps.
That is, the real change here should be to your Adapter's getView method, with a change to its data source and then possibly a call to notifyDataSetChanged.
Fredley's answer about SQLite or storage may be overkill if that data doesn't need to persist beyond this one session in the activity, or if the data you're working with is also transient (e.g. network data that changes often that's temporarily loaded into an ArrayAdapter), and in any case it's a bit misleading because just dumping data to disk doesn't solve the fundamental issue with your conflation of views of data with your models.

Categories

Resources