I'm trying to create a UI similar to Google Keep. I know how to create a staggered View using a Recycler View. If i click a specific Card. Then it has to open a activity.
I can achieve this using onclick method.
This same scenario happens in atleast 5 different Activities in my App.
My question is that
Can I use this single Adapter in all those 5 places ?
If yes, where should i place the onclick actions ?
If no, How can I Create a staggered layout like Keep?
Thanks in Advance!
(See application for RecyclerView below in edits)
Like I mentioned in my comment, it's certainly fine to have separate adapters for all your Activities which use different data and views. As your app data and layouts get more complex, so does your code...that's just the way it is.
But if some of your Activities used similar data in their ListViews -- maybe, for example, two TextViews and an ImageButton -- you could save some effort by defining a single adapter that can be used for multiple Activities. You would then instantiate separate objects for each Activity, similar to the way you would create several ArrayAdapter<String> objects to populate multiple ListViews.
The BaseAdapter is a great class to extend when writing a custom adapter. It's flexible and allows you complete control over the data that's getting shown in your ListView. Here's a minimal example:
public class CustomBaseAdapter extends BaseAdapter {
private Context context;
private ArrayList<String> listData;
public CustomBaseAdapter(Context context, ArrayList<String> listData) {
this.context = context;
this.listData = listData;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.your_list_item_layout, parent, false);
//populate the view with your data -- some examples...
TextView textData = (TextView) convertView.findViewById(R.id.yourTextView);
textData.setText(listData.get(position));
ImageButton button = (ImageButton) convertView.findViewById(R.id.yourImageButton);
button.setOnClickListener(new View.OnClickListener() {
//...
//...
});
}
return convertView;
}
#Override
public Object getItem(int position) {
return 0;
}
#Override
public long getItemId(int position) {
return 0;
}
#Override
public int getCount() {
return listData.size();
}
}
So the key part of this code is obviously the getView() method, which is called every time the ListView needs some data to display. For efficiency, views are stored in something called a convertView so they may be re-used and not have to be inflated every time a view appears on the screen.
So what we do in getView() is first find out if the convertView exists. If it does, we just pass that back to the calling ListView because everything should already be instantiated and ready to go. If the convertView is null, it means the data hasn't been instantiated (or needs to be re-instantiated for whatever reason), and so we inflate a brand new view and populate it with our data.
So in the case of this example adapter above, if several of your Activities all displayed a single list of Strings, you could reuse this adapter for each one, passing in a different ArrayList<String> through the constructor each time you created a new object. But obviously you could pass in more than just Strings if you had more data to show. The level of complexity is up to you. And if the difference among your Activities was too great, I would just create custom versions of this class for all of them and just instantiate them and populate them however you'd like. It will keep all your data very organized and encapsulated.
Hope this helps! Feel free to ask questions for more clarification if you need it.
EDIT IN RESPONSE TO COMMENTS
Since you are using a RecyclerView instead of just plain ListViews (which I, for some reason, totally forgot) you could still do something very similar using a RecyclerView.Adapter<YourViewHolder> instead. The difference would be that instead of inflating the views in a getView() method, they are inflated inside your custom ViewHolder, which I assume you already have. The code might look something like this:
public class CustomRecyclerViewAdapter extends RecyclerView.Adapter<StringViewHolder> {
private final List<String> items;
public CustomRecyclerViewAdapter(ArrayList<String> items) {
this.items = items;
}
#Override
public StringViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//So instead of inflating the views here or in a getView() like in
//in the BaseAdapter, you would instead inflate them in your custom
//ViewHolder.
return new StringViewHolder(parent);
}
#Override
public void onBindViewHolder(StringViewHolder holder, int position) {
holder.setModel(items.get(position));
}
#Override
public long getItemId(int position) {
return items.get(position).hashCode();
}
#Override
public int getItemCount() {
return items.size();
}
}
Related
I have fragment "Incoming" on one slide of a viewpager. This fragment contains a RecyclerView populated with custom-relative-layouts. The LinearLayoutManager orientation is Vertical.
I have a second fragment "Find" on the next slide of the said viewpager. "Find" will consist of two recyclerviews. It will have a Horizontal recyclerview filled with cardviews (fast loading of profile pictures). Underneath that, I am loading more slowly another recyclerview with a custom-relative-layout, the same as in the "incoming" fragment.
Does that make sense? I'll elaborate some more:
The question is for these three recyclerviews, should I declare a new RecyclerAdapter for each one? The reason I ask is that they'll all have unknown variable item_counts.
Here is the RecyclerAdapter I have for "Incoming":
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder>{
private Context mContext;
public RecyclerAdapter(Context context, List<Incoming> items) {
mContext = context;
}
public static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View v) {
super(v);
// Define all of the components in the view
}
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
LayoutInflater mInf = LayoutInflater.from(mContext);
View customView = mInf.inflate(R.layout.item_layout_incoming, parent, false);
final ViewHolder vh = new ViewHolder(customView);
return vh;
}
#Override
public int getItemCount(){ return 6; } // THIS IS TEMPORARY; WILL BE VARIABLE
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
// Replace contents
}
For my criteria, should I create another Adapter for my horizontal-cardview-recyclerview? It seems repetitive, but otherwise, how would I handle either inflating cardview or item_layout_incoming?
Seems like there should be a DRY way to do this, without hits to performance. Thanks
You are using fragments so you will create 2 objects of that class. So it's the same thing. you just reduce compiler load by reducing the task of loading the new class into memory and then create its object.
It's better to use two different Adapter because of 2 reasons.
Your code will become ugly I mean so much congested and so many if
else condition.
In future, if you need to change something in layouts then again it
will affect all objects if same adapter class.
So my advice do developer friendly code and create two classes.
I have a listview that is populated via an adapter. I need one of the items (which are just bits of text) in the listview to have a different background compared to the others (to mark it as the currently selected one).
I have all of the background logic, I just need a way to say listview.setBackgroundById(int position)
or something like that.
How do I do this?
This needs to be as simple as possible, 'cause all of the other things done in the Activity are currently working perfectly. :)
As asked, this is my Adapter that I'm using:
public class SampleAdapter extends ArrayAdapter<SampleItem> {
private String title;
public SampleAdapter(Context context) {
super(context, 0);
}
public View getView(final int position, View convertView, ViewGroup parent) {
if (convertView == null) {
convertView = LayoutInflater.from(getContext()).inflate(R.layout.row_station, null);
}
TextView title = (TextView)convertView.findViewById(R.id.station_name);
font.setFont(title, 2, getActivity());
title.setText(getItem(position).title);
RelativeLayout info = (RelativeLayout)convertView.findViewById(R.id.info_relative_button);
info.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
MainActivity.setCurrentTab(41);
MainActivity.setBackDisabled(true);
Log.e("current tab:",String.valueOf(MainActivity.getCurrentTab()));
Fragment fragment = new StationInfo();
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
UserManager.getInstance().setStationId(getItem(position).id);
}
});
return convertView;
}
}
The SampleItem has 2 String fields, title and id, it's very simple.
You need to use a custom list adapter and have it return views with your desired background. Create a class extending ListAdapter or any of the existing SimpleAdapter etc and override getView to inflate a suitable view for your element, and add any logic you need to set the background of that view.
There is no way to tell the listview itself to decorate some of its elements by id or position.
Update: I just noticed you added the list adapter code.
Since you are already implementing getView, to change the background of your element simply call convertView.setBackgroundColor, or have two different views inflated depending on the situation.
(BTW it's really bad practice to call static methods on your activity like in your onClickListener.)
In ListView adapter:
#Override
public View getView(int position, View view, ViewGroup viewGroup) {
if(view==null)
....
//for example every even list item to be grey, every odd to be white
if(((position+1)%2)==0)
view.setBackgroundColor(mContext.getResources().getColor(R.color.grey));
else view.setBackgroundColor(mContext.getResources().getColor(android.R.color.white));
Hope you get an idea...
I want to create a listview that displays many different things on Android Studio. The purpose of this is to create a dynamic form that will have any number of category A, B, or C according to what the form maker wants.
So, the form maker will get a list of options: Category A, B, C etc. and they choose how many spots for each they want. For example, let's say A is "References" and the form maker wants to have three spots for it, I want that the form to have 3 spots for category A.
Long story short, how would is there an array adapter or something that would help me with this? Is there a tutorial you guys know about?
Thanks in advance!!
Your best approach is to create a custom Adapter, and in that adapter create a collection of type Object (or any superclass common to those classes). Then, in the getView method, depending on the type of object you are retrieving from the collection, display one thing or the other.
You need to create a custom Adapter, there are lot of tutorials online.Best ones are
http://www.androidhive.info/2014/07/android-custom-listview-with-image-and-text-using-volley/
http://www.vogella.com/tutorials/AndroidListView/article.html
Also I would suggest you to look into recyclerview which is faster.
http://javatechig.com/android/android-recyclerview-example
Here's how a customlistadapter would look like:
public class CustomListAdapter extends BaseAdapter {
private Activity activity;
private LayoutInflater inflater;
public CustomListAdapter(Activity activity, List<obj> item) {
//set any data you want
}
#Override
public int getCount() {
return item.size();
}
#Override
public Object getItem(int location) {
return item.get(location);
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if (inflater == null)
inflater = (LayoutInflater) activity
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (convertView == null)
convertView = inflater.inflate(R.layout.list_row, null);
TextView title = (TextView) convertView.findViewById(R.id.title);
return convertView;
}
}
I am trying to modify the sample code below. It currently populates a View that contains an imageview and a textview. I have added an additional textview to my XML layout and am trying to figure out how to replace the simple array with a hash map or even a multidimensional array to populate not just the imageview and the first textview but also the second one.
I would appreciate sample code that shows the entire process. Thanks!
public class DynamicDemo extends ListActivity {
TextView selection;
private static final String[] items={"lorem", "ipsum", "dolor",
"sit", "amet"}
#Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.main);
setListAdapter(new IconicAdapter());
selection=(TextView)findViewById(R.id.selection);
}
public void onListItemClick(ListView parent, View v,
int position, long id) {
selection.setText(items[position]);
}
class IconicAdapter extends ArrayAdapter<String> {
IconicAdapter() {
super(DynamicDemo.this, R.layout.row, R.id.label, items);
}
public View getView(int position, View convertView,
ViewGroup parent) {
View row=super.getView(position, convertView, parent);
ImageView icon=(ImageView)row.findViewById(R.id.icon);
if (items[position].length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}
return(row);
}
}
}
The easiest thing to do is use an ArrayAdapter<MyDataObject> where
public class MyDataObject {
public String string1;
public String string2;
// any other useful attributes
}
And then you would change items to a MyDataObject[] items stored in your class, and instead of doing super.getView(index) you'd do items[index] (which would yield a MyDataObject) and use that data instead.
Also, importantly: you should use the convertView. And possibly the ViewHolder pattern.
Edit: At OP's request, a little more elaboration. Note that this uses the convertView pattern but not the ViewHolder pattern (you should be able to adopt that fairly easily).
In your Adapter, you'd change getView() as follows:
public View getView(int position, View convertView,
ViewGroup parent) {
ViewGroup row;
if(convertView == null){
// create your view here.
row = getLayoutInflater().inflate(R.layout.row);
} else {
row = convertView;
}
// note: when you implement ViewHolder, the ViewHolder will
// hold this reference so that you don't need to look it up every time.
ImageView icon=(ImageView)row.findViewById(R.id.icon);
// here you're employing the "items" array that you were using
// before, except now it contains MyDataObjects. pick out the
// string (or other data you want to check) from the resulting MyDataObject,
// and see if it's longer than 4 characters.
MyDataObject objectAtThisPosition = items[position];
if (objectAtThisPosition.string1.length()>4) {
icon.setImageResource(R.drawable.delete);
}
else {
icon.setImageResource(R.drawable.ok);
}
// Do whatever else you want to with objectAtThisPosition.
return(row);
}
That's it for the easy way, and quite similar to what you have.
Some more detail; if you don't care, skip it. :)
I know that Adapters can seem magical, so in the interest of showing how ListView adapters work, here's an example using a List instead of an Array, so we can remove any magic that ArrayAdapter does with the array behind the scenes. I use a List because they can be more versatile for whatever you're trying to accomplish (ArrayList or LinkedList or what-have-you).
To use a List you'd have the following in your Activity:
private List<MyDataObject> myList = new ArrayList<MyDataObject>();
And instead of items[position] you'd use
MyDataObject objectAtThisPosition = myList.get(position);
If you want to change your data set dynamically, you should probably use this approach (keeping myList at the Activity level) instead of using an Array and an ArrayAdapter. That would mean you'd need to change from extending ArrayAdapter<String> to just extending BaseAdapter<MyDataObject> (most of the methods in BaseAdapter are trivial to implement) since our data size, for example, would be determined by our list, and not the ArrayAdapter's array.
I know that's kind of a fire hose, but let me know if you have any questions!
Use a separator in string, like \t.
Or use an array of straing arrays.
Or use an array of Pair<String, String>.
Or use an array of custom objects.
I have some extended cursor adapter, in witch I call super with the context and the resource layout for the item in the list, something like this.
call to super in my adapter:
super(activity, viewResource, c, false);
creation of my adapter:
new MyCursorAdapter(this, null, R.layout.my_list_item, null);
What I want to achieve is something like my stupid mock up made in paint.
Put into words I want to have different kinds of layout for the items, for example I want all even items to have layout1 and all odd to have the layout2. So far I am able to give only one layout in this case R.layout.my_list_item. Is it possible to dynamically change the layout ?
Is it possible to construct the adapter to have items with different layout ? My goal is to dynamically chose the layout of the item. I do not want to have just one layout for all of the items I want to have foe example two...
Thanks
Yes, you're going to have to do two things though. First, override the getItemViewType() method in your adapter so that you can be sure your bindView() only get's views that appropriate for a particular position in the list, like so:
public int getItemViewType(int position){
if(correspondsToViewType1(position)){
return VIEW_TYPE_1;
}
else(correspondsToViewType2(position)){
return VIEW_TYPE_2;
}
//and so on and so forth.
}
Once you do that, just have a simple test in your bindView() that checks to see what type of view it should have recieved and setup things accordingly like so:
public void bindView(View view, Context context, Cursor cursor){
if(correspondsToViewType1(cursor)){
//Now we know view is of a particular type and we can do the
//setup for it
}
else if(correspondsToViewType2(cursor){
//Now we know view is of a different type and we can do the
//setup for it
}
}
Note that you're going to have to have to different methods for correpondsToViewType, one that takes a cursor and one that takes an int (for a position). The implementation for these will vary depending on what you want to do.
Note that doing things this way will allow you to reuse potentially recycled views. If you don't do this, your app is going to take a huge performance hit. Scrolling will be super choppy.
I'm guessing your extending SimpleCursorAdapter from the name of your custom adapter. You will want to override the function getView in your adapter and depending on the object in the list inflate a different layout and return that view.
EX:
#Override
public View getView (int position, View convertView, ViewGroup parent)
{
Object myObject = myList.get(position);
if(convertView == null)
{
if( something to determine layout )
convertView = inflater.inflate(Layout File);
else
convertView = inflater.inflate(Some Other Layout File);
}
//Set up the view here, such as setting textview text and such
return convertView;
}
This is just an example and is somewhat sudo code so it will need some adjustments for your specific situation.
Just override the newView Method:
public class MyCursorAdapter extends CursorAdapter {
private final LayoutInflater inflater;
private ContentType type;
public MyCursorAdapter (Context context, Cursor c) {
super(context, c);
inflater = LayoutInflater.from(context);
}
#Override
public void bindView(View view, Context context, Cursor cursor) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
// get elements for type1
} else {
// get elements for type1
}
}
#Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
if( cursor.getString(cursor.getColumnIndex("type")).equals("type1") ) {
final View view = inflater.inflate(R.layout.item_type1, parent, false);
} else {
final View view = inflater.inflate(R.layout.item_type2, parent, false);
}
return view;
}