Is it possible to have a ListView that draws from multiple sources? I want to have some hard-coded items and items from a ContentProvider in the same list, and I just want to know if that's possible.
You could have both types of items implement an interface such as
public interface Item {
int TYPE_1 = 1;
int TYPE_2 = 2;
int getViewType();
View getView(LayoutInflater inflater, View convertView, ViewGroup parent);
}
Then your Adapter can be for a list of Items. Also, if you're unfamiliar with the View Holder pattern I'd recommend looking it up. A quick search revealed a pretty good looking example here
You can let them extends a common parent class, and then use it to construct the adapter. I have done this before, I hope to help you.
So, I realized that
I don't care if ContentProvider data is updated after the fragment loads and
I want to do manipulations on the data in my list that I do not want reflected in the ContentProvider.
So I'm going to dump my cursor results into an ArrayList along with my hardcoded items, and I think that should work fine.
Related
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.
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
Documents say:
When the content for your layout is dynamic or not pre-determined, you
can use a layout that subclasses AdapterView to populate the layout
with views at runtime. A subclass of the AdapterView class uses an
Adapter to bind data to its layout.
But most of tutorials are in about ListView,GridView,Spinner and Gallery.
I'm looking to extend a subclass directly from AdapterView. I have to create a custom view that it's content is dependent to an adapter.
How can I do this, and what methods must be overridden?
First, you should be absolutely sure that AdapterView is what you want, because not all "dynamic or not pre-determined" views can be implement via AdapterView. Sometimes you'd better create your view extending ViewGroup.
if you want to use AdapterView, take a look at this really nice example. There are a lot of custom views with adapter on GitHub. Check out this one (extends ViewGroup).
This may not be a total answer to your question but i am showing you most probably a starting point or pointer which can guide you:
Sony Developer Tutorials - 3D ListView
ListView extends AbsListView which in turn extends AdapterView<ListAdapter>. So if you absolutely must implement such a custom view from scratch, you could have a look at the source code of those classes:
ListView
AbsListView
AdapterView
But beware, that's quite a task. Perhaps it may be sufficient to use one of the existing classes and tweak the look.
Deriving from AdapterView can work, but it may not be as beneficial as you hope. Some of the infrastructure provided by AdapterView is package-private, meaning we don't have access to it.
For example, AdapterView manages the selected item index for AbsListView and ListView. However, because methods like setNextSelectedPositionInt(int position) (which is the only path to setting mNextSelectedPosition) are package-private, we can't get to them. AbsListView and ListView can get to them because they're in the same package, but we can't.
(If you dig into the AdapterView source you'll find that setNextSelectedPositionInt() is called from handleDataChanged(). Unfortunately handleDataChanged() is also package-private and is _not_called from anywhere else within AdapterView that could be leveraged to enable setting position.)
That means that if you need to manage selected position, you'll need to recreate that infrastructure in your derived class (or you'll need to derive from ListView or AbsListView...though I suspect you'll run into similar problems deriving from AbsListView). It also means that any AdapterView functionality that revolves around item selection likely won't be fully operational.
You could create something like this :
public class SampleAdapter extends BaseAdapter {
public SampleAdapter() {
// Some constructor
}
public int getCount() {
return count; // Could also be a constant. This indicates the # of times the getView gets invoked.
}
public Object getItem(int position) {
return position; // Returns the position of the current item in the iteration
}
public long getItemId(int position) {
return GridView.INVALID_ROW_ID;
}
public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
view = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.some_layout, null);
view.setLayoutParams(new GridView.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
view.setBackgroungColor(Color.RED);
return view;
}
}
And this could be invoked like :
GridView sampleView = (GridView) linearLayout.findViewById(R.id.sample_layout);
sampleView.setAdapter(new SampleAdapter());
I have a JSON string with the multiple instances of the following
Name
Message
Timestamp
Profile photo url
I want to create a ListView where each list will have the above. Which is the better Adapter I need to extend for this particular case, to create a Custom Adapter?
My assumption is that you have parsed the JSON string into an object model prior to considering either case... let's call this class Profile.
Strategy 1
An ArrayAdapter is sufficient with an overriden getView(..) method that will concatenate your multiple fields together in the way you wish.
ArrayAdapter<Profile> profileAdapter = new ArrayAdapter<Profile>(context, resource, profiles) {
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View v;
// use the ViewHolder pattern here to get a reference to v, then populate
// its individual fields however you wish... v may be a composite view in this case
}
}
I strongly recommend the ViewHolder pattern for potentially large lists:
https://web.archive.org/web/20110819152518/http://www.screaming-penguin.com/node/7767
It makes an enormous difference.
Advantage 1. Allows you to apply a complex layout to each item on the ListView.
Advantage 2. Does not require you to manipulate toString() to match your intended display (after all, keeping model and view logic separate is never a bad design practice).
Strategy 2
Alternatively, if the toString() method on your representative class already has a format that is acceptable for display you can use ArrayAdapter without overriding getView().
This strategy is simpler, but forces you to smash everything into one string for display.
ArrayAdapter<Profile> profileAdapter = new ArrayAdapter<Profile>(context, resource, profiles)
This question already has answers here:
Android ListView Refresh Single Row
(7 answers)
Closed 8 years ago.
I'm wondering if it is possible to rerender just one element in a listview? I assume by calling notifyDatasetChanged() is gonna rerender the whole list?
Thanks,
you can't render (refresh) a single row, but instead you can get the requested view and make chages on it directly by calling yourListView.getChildAt(int VisiblePosition); where the visiblePostion is the position in the ListView minus yourListView.getFirstVisiblePosition()
Like this :
View v = listViewItems.getChildAt(position -
listViewItems.getFirstVisiblePosition());
v.setBackgroundColor(Color.GREEN);
I hope this helps...
You can, but it's a bit convoluted. You would have to get the index of the first visible item in the list and then use that do decide how how far down in the list of visual items the item is that needs updated, then grab its view and update it there.
It's much easier to just call notifyDatasetChanged().
Also you can use this:
myListView.invalidateViews();
dataAdapter.remove(dataAdapter.getItem(clickedpos));
dataAdapter.insert(t.getText().toString(), clickedpos);
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 and update must be performed on it.
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 consider scenario when your view get invisible and visible again.
You need to keep track of your adapter (or custom adapter if you are set on fancy features). When you change the data for an item, simply change the fields you are interested in , in your adapter.
Then call notifyDatasetChanged , and the changes will be reflected in your listview.
Note that this approach works exactly the same for Gallery Views as well.