Android: Duplicating View element in the ViewGroup - android

I am working on class that extends ViewGroup to arrange the View items for the GridView.
I can easily add a new View item inside it by:
ImageView view = new ImageView(context);
view.setImageBitmap(BitmapFactory.decodeResource( getResources(), R.drawable.ic_launcher));
addView(view);
Or removing View item is also easy
removeViewAt(remove_index)
Swapping the item can be done by
addView(new_index, removeViewAt(old_index));
but I want to duplicate the View item when one item is dragged over the another one.
I tried to duplicate the the item by
addView(getChildAt(index))
And this shows the exception error
The specified child already has a parent. You must call removeView() on the child's parent first
I also tried to store all the view items in the List, called the method removeAllView() and again added the views in class.
ArrayList<View> children = new ArrayList<View>();
for (int i = 0; i < getChildCount(); i++){
children.add(getChildAt(i));
}
children.add(getChildAt(index)); // duplicate this item
removeAllViews();
for (int i = 0; i < children.size(); i++){
addView(children.get(i));
}
This still shows the exception error as above:
The view inflating may work but I want to copy the same view without going for the external resource.
So I want the method to detach that View from parent ViewGroup and make multiple copy (Duplicate) of it inside the class.
Any help is appreciated.

First, you're trying to add that same object again, which doesn't really make sense - the new view has to be a separate object, you'd have to duplicate the original first, e.g. using .clone() method.
But, unfortunately, even if you did, you couldn't add the cloned view to the ViewGroup, here's why.
The exception you get is the result of ViewGroup checking your View's parent for null
So, in order to add the cloned view, you'd have to set your view's mParent member to null, which you can't do directly because the method that does that is not public: View.assignParent()
You could try to clone the View after you call .removeViewAt() so that it doesn't have a parent at the time of cloning, then add the original view back to it's position and then proceed with adding the clone to the required place, but as S.D. mentioned you'd have to have some hassle with cloning plus this way is very obscure and will require the ViewGroup to relayout 2 times.
A better solution is to assign a tag to each view that contains the necessary info to create another View like that and use it when you need to clone.
I would do something like this:
public interface ViewCloner {
public View clone(Context context);
}
public static class ImageViewCloner implements ViewCloner {
private int mImgResId;
public ImageViewCloner(int imgResourceId) {
this.mImgResId = imgResourceId;
}
#override
public View clone(Context context) {
ImageView view = new ImageView(context);
view.setImageBitmap(BitmapFactory.decodeResource( context.getResources(), mImgResId));
// Add the tag to the clone as well, so it, too, can be cloned
view.setTag(new ImageViewCloner(mImgResId));
return view;
}
}
// When creating the original view
int resId = R.drawable.ic_launcher;
ImageView view = new ImageView(context);
view.setImageBitmap(BitmapFactory.decodeResource( getResources(), resId));
view.setTag(new ImageViewCloner(resId));
// When cloning the view
ViewCloner vc = (ViewCloner) getChildAt(index).getTag();
View clone = vc.clone(getContext());
addView(clone);
For any additional view or group you'll want to use instead of the single ImageView thing just create another implementation of ViewCloner and you're good to go without having to modify your container's behaviour.

Duplicating an object requires a good implementation of clone() method.
I don't think Android's view classes do this well, so you may need to create a custom type of view that can produce a copy of itself. View class does have methods to save/restore state: with onSaveInstanceState () and onRestoreInstanceState() which you can use to copy View's state.
Also, you will need to take care of event listeners registered on that view.

Thanks for the answer S.D and Ivan.
After the long break I could find my own answer keeping those solutions in my mind.
The Clone method directly cannot be added in the View and adding the interface makes more complexity for the codes.
Even my requirement was to clone the view for which the image was dynamically added and source was unknown.
Some trick must be done just to copy the view,
first of all get the another instance of view and copying the properties such as drawable, background, padding, etc on the second one.
The solution was much easier by using following codes.
// Create new Instance of imageView
ImageView view = new ImageView(context);
// Get the original view
ImageView org = (ImageView)getChildAt(index);
// Copy drawable of that image
view.setImageDrawable(org.getDrawable());
// Copy Background of that image
view.setBackground(org.getBackground());
// Copy other required properties
....
// Lastly add that view
addView(view);

Related

Dynamically add a button to a view from a listview Adapter GetView method

As a little eperiment, I'm trying to do the following.
I have an AXML describing a vertical linear layout which contains a listview (only filling 200dp of the vertical linear layout ). The AXML is inflated when the activity starts with SetContentView. Then the listview is correctly populated with values using its Adapter.
In the GetView method of the listview Adapter, I am trying to also dynamically create a button and add it to the linear layout, but for some reason the button is not added.
If I try to add the button in the constructor method of the Adapter instead, it is correctly added.
Can you tell me what could be possibly going wrong?
Let me add some code:
class TracksAdapter : BaseAdapter<string> {
Activity context;
List<Dictionary<string,string>> trackList;
// constructor
public TracksAdapter (Activity context, List<Dictionary<string,string>> trackList) {
this.context = context;
this.trackList = trackList;
// Just as a little test, if I create the button from here it will be correctly added to linear layout:
var ll = context.FindViewById<LinearLayout>(Resource.Id.linLayForResultsActivity);
Button b1 = new Button(context);
b1.Text = "Btn";
ll.AddView(b1);
}
public override View GetView(int position, View oldView, ViewGroup parent) {
// if I create the button from here it will not be added to the layout
var ll = context.FindViewById<LinearLayout>(Resource.Id.linLayForResultsActivity);
Button b1 = new Button(context);
b1.Text = "Btn";
ll.AddView(b1);
// this other code is working
View view = context.LayoutInflater.Inflate(Resource.Layout.ResultItem, null);
var artistLabel = view.FindViewById<TextView>(Resource.Id.resultArtistNameTextView);
artistLabel.Text = trackList[position]["trackArtistName"];
return view;
}
}
Update: adding some more context information because I know this can be a bit weird to understand without it:
In GetView, I don't need to return the new button I am trying to create there. GetView only need to return a listview view item, but, along its execution, GetView also has to create and add a button to the linear layout containing the listview.
The real code is much more complex than that. I have simplified it in the question. In the real code, the listview items are made of text and a button. The GetView also attaches event handlers to the buttons. Then what I need is, when a user clicks a button in any of the listview items, another button is added below the listview. So I need the code for adding another button to be in GetView, and the button needs to be added outside of the listview, ie. to the linear layout containing the listview.
Use the LayoutInflator to create a view based on your layout template, and then inject it into the view where you need it.
LayoutInflater vi = (LayoutInflater) getApplicationContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = vi.inflate(R.layout.your_layout, null);
// fill in any details dynamically here
TextView textView = (TextView) v.findViewById(R.id.a_text_view);
textView.setText("your text");
// insert into main view
ViewGroup insertPoint = (ViewGroup) findViewById(R.id.insert_point);
insertPoint.addView(v, 0, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
I looked in you code, you are returning view, while you add the button to ll, you should return ll
what you return in getView() is what you see in the list item layout, since you're adding the button to ll and returning view, the button won't appear.
you can add the button to view as you implementation
Also check this:
Try using boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params)
http://developer.android.com/reference/android/view/ViewGroup.html#addViewInLayout(android.view.View, int, android.view.ViewGroup.LayoutParams)
It's working... Without making any changes now it's working as it should... ! Ugh!
I really don't know what I was doing wrong here... probably it was because of some sort of caching of older version of the installed APK.. ? I know this sort of stuff can happen, and that's why I've always been uninstalling the app before deplyoing the new version to the device... but still...!

Why dynamically create framelayout?

I am trying to understand how the Tinder like/dislike card system works by looking through this example on GitHub: https://github.com/kikoso/Swipeable-Cards/blob/master/AndTinder/src/main/java/com/andtinder/view/CardStackAdapter.java. I understand the importance of BaseAdapters and populating the view/card with the necessary info. This part of the code that is confusing the hell out of me is this:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
FrameLayout wrapper = (FrameLayout) convertView;
FrameLayout innerWrapper;
View cardView;
View convertedCardView;
if (wrapper == null) {
wrapper = new FrameLayout(mContext);
wrapper.setBackgroundResource(R.drawable.card_bg);
if (shouldFillCardBackground()) {
innerWrapper = new FrameLayout(mContext);
innerWrapper.setBackgroundColor(mContext.getResources().getColor(R.color.card_bg));
wrapper.addView(innerWrapper);
} else {
innerWrapper = wrapper;
}
cardView = getCardView(position, getCardModel(position), null, parent);
innerWrapper.addView(cardView);
} else {
if (shouldFillCardBackground()) {
innerWrapper = (FrameLayout) wrapper.getChildAt(0);
} else {
innerWrapper = wrapper;
}
cardView = innerWrapper.getChildAt(0);
convertedCardView = getCardView(position, getCardModel(position), cardView, parent);
if (convertedCardView != cardView) {
wrapper.removeView(cardView);
wrapper.addView(convertedCardView);
}
}
return wrapper;
}
Why are FrameLayouts being created dynamically? It seems like multiple FrameLayouts are being made with wrapper and inner wrapper? What does wrapper even mean? Why are classes like shouldFillCardBackground() used but not even defined anywhere in the repository?
First of all, it is important to notice that the CardStackAdapter you link to is abstract, so it will never be instantiated directly. Rather, the class SimpleCardStackAdapter will be used for instance.
That said, lets get the purpose of the method clear; The getView() method's purpose is to create and populate a view for the given position.
Creating layouts can be done either by inflating them from xml, or otherwise, by creating them in code. The latter is happening here. The main reason that they create the layout dynamically, is to keep things dynamic. The exact views in the layout depend on some configurations which are checked at runtime (in the method shouldFillCardBackground()). Note that this could also be achieved by creating the layout in xml, then inflate it, and dynamically hide/show (or remove/add) views and layouts. The auther of the code simply chose to do it in code.
A wrapper is an object (in this case a layout) which does not much more that holding other objects (other layouts). In the context of layouts, they are usually used to add some kind of background or padding. That is also what is happening here.
shouldFillCardBackground() is a method, not a class, and it is definitely defined: in line 71.
It is important to realize that it seems that the main puropose of this code is to be an example, a demo. It is possible it is not fully functional and that not everything is implemented the best way (for instance, shouldFillCardBackground() returns true by default).

adding imageButtons by code

I have a base adapter class which i use to fill in a listview. Some of the contents are defined in a layout file and i also need to dynamically add in a certain number of image button depending on the int value passed to the base adapter.
The obj is a object that has the int value along with a arrayList of bitmaps;
when i run this code i get more image buttons then the value of obj.value.
likePre_pics is the name of the arrayList of bitmaps
Can someone please help?
public class News_Feed_BaseAdapter extends BaseAdapter{
#Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
LinearLayout linLayout =
(LinearLayout)convertView.findViewById(R.id.like_preview_LinearLayout);
for(int i=0; i< obj.value;i++)
{
ImageButton op= new ImageButton(context);
LayoutParams lpView = new LayoutParams(100, 100);
//op.setImageBitmap(obj.get(position).likePre_pics.get(i));
linLayout.addView(op,lpView);
}
}
}
As i can see from your code there is two problem,first is IndexOutOfBoundsException. ArrayList IndexOutOfBoundsException only occurs when there is more/less item then you are referring to.
//op.setImageBitmap(obj.get(position).likePre_pics.get(i));
The line you commented out. You are referring to obj.get(position) where obj has obj.size() number of element.
Next is ImageButton issue since you are not reusing the convertview in an efficient way when you are adding new imagebutton into linLayout of convertview that is why linLayout is showing more then 2 imagebutton.
For example: If you have 20 items in your list that means getView will be called 20 times. As you will find in android documentation convertView is the old view that gets passed for you to reuse and every-time it gets passed to you , you are adding more imagebutton into it. That's why imagebutton problems are occurring.
Take a look into Romain Guy's World of ListView google i/o presentation if you are interested.

How to inflate multiple instances of a layout with the same id inside an inflated layout

I have a LinearLayout with many nested LinearLayouts and TextViewss
My main activity inflates the main LinearLayout,
Then I load data from a server and based on the data received, I add multiple Layouts in a place holder (LinearLayout)
This is simple a news page where I load Images associated with the news and place it inside an initially empty LinearLayout.
Each Image has the following info: Title(TextView), Date(TextView), Image(ImageView) so what I actually do is the following:
*Please notice that this is only the essential coded in the question I elemenated all the try -> catch ... if/else ....etc
public void addImages(JSONArray images){
ViewGroup vg = (ViewGroup) findViewById(R.id.imagesPlaceHolder);
// loop on images
for(int i =0;i<images.length;i++){
View v = getLayoutInflater().inflate(R.layout.image_preview,vg);
// then
I think that here is the problem
ImageView imv = (ImageView) v.findViewById(R.id.imagePreview);
TextView dt = (TextView) v.findViewById(R.id.dateHolder);
TextView ttl = (TextView) v.findViewById(R.id.title);
// then
dt.setText("blablabla");
ttl.setText("another blablabla");
// I think the problem is here too, since it's referring to a single image
imv.setTag( images.getJSONObject(i).getString("image_path").toString() );
// then Image Loader From Server or Cache to the Image View
}
}
The code above works good for a single image
But for multiple images the Image Loader doesn't work I guess it's because all ImageViews (Inflated multiple times) have the same ID
When you provide a ViewGroup to be used as the parent, the View returned by inflate() is this parent (vg in your case) and not the newly created View. Therefore, v points toward the ViewGroup vg and not toward the newly created View and as all of your children have the same id, the same subviews (imv, dt, ttl) are returned each time.
Two solutions. The first one is to change their id right after you are finished with them, before the next iteration. Therefore, on the next creation at the beginning of the next iteration, the newly created Views will have a different IDs from the older Views because they will still use the old constant defined in R.
The other solution would be to add the parameter false to the call to inflate() so that the newly created view will not be attached to the ViewGroup and will then be returned by the inflate() function instead of the ViewGroup. The rest of your code will then works as attended with the exception that you will have to attach them to the ViewGroup at the end of the iteration.
Notice that you still need to provide a ViewGroup because it will be used to determine the value of the LayoutParams.
I had the same problem, and based on the answer from #SylvainL, here'a a working solution:
// myContext is, e.g. the Activity.
// my_item_layout has a TextView with id='text'
// content is the parent view (e.g. your LinearLayoutView)
// false means don't add direct to the root
View inflated = LayoutInflater.from(myContext).inflate(R.layout.my_item_layout, content, false);
// Now, before we attach the view, find the TextView inside the layout.
TextView tv = (TextView) inflated.findViewById(R.id.text);
tv.setText(str);
// now add to the LinearLayoutView.
content.addView(inflated);
Is there a reason why the ImageView in the layout XML needs to have an ID? Could you erase the android:id attributes from the image_preview.xml layout and then simply iterate through the children of the inflated LinearLayout? For example:
ViewGroup v = (ViewGroup)getLayoutInflater().inflate(R.layout.image_preview,vg);
ImageView imv = (ImageView) v.getChildAt(0);
TextView dt = (TextView) v.getChildAt(1);
TextView ttl = (TextView) v.getChildAt(2);
I inflate XML-Layout with dynnamic and get text of id
private val onAddView = View.OnClickListener {
val parent = viewForm.findViewById<LinearLayout>(R.id.layout_parent)
LayoutInflater.from(activity).inflate(R.layout.layout_child, parent) // layout_child has id "tv_attribute"
}
private val onSave = View.OnClickListener {
val parent = viewForm.findViewById<LinearLayout>(R.id.layout_parent)
for (i in 0 until parent.childCount) {
val getText = parent.getChildAt(i).findViewById<TextView>(R.id.tv_attribute).text
}
}

ListView get slow when the Base adapter.getview() method is called

I have been debugging my application and i saw that when i was scrolling the listview the method getView() of the class BaseAdapter is called to generate new views
public View getView(int position, View convertView, ViewGroup parent) {
Article article = this.articles.get(position);
return new MainView(this.context, articulo.getTitle() , articles.getDescription(),articles.getImgUrl());) }
when i scroll the listActivity to see the new items this method is invoked again to create the below list view items, as a consequence that the list items have images the ListActivity get slow, is there any way to create all the items view once, and not create ListItems when we are scrolling the listActivity
ListViews are highly optimized for performance, you should use ViewHolder inside your ListAdapter to cache the ListItems.
check http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
the rule is, first set up your customview, pack everything inside your holder and pin this holder onto the view, the second time the view is used android simple extract the holder information (really fast).
It's probably slowing down because of the number of objects that are created. For performance you should reuse your rows. See the getView implementation here: http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List4.html and http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/view/List14.html
You should not create a new View on each call to getView. The convertView that is being passed in allows use to reuse an existing View. In your case this will be an instance of MainView. So you can do something like this:
MainView mv;
if (convertView != null){
mv = (MainView) convertView;
((TextView) mv.findViewById(R.id.title)).setText(articulo.getTitle());
// similar for description and imgUrl
} else {
mv = new MainView(...);
}
return mv;
In addition, you could use the ViewHolder pattern suggested by Michele. This will allow you to avoid the findViewById lookups when setting title etc. Here is a great explanation of ViewHolder.

Categories

Resources