Okay so I do this (not actual code)
try {
final View view = LayoutInflater.from(context).inflate(R.layout.ownComponent, null);
} catch (InflateExpection e) {
}
if (view != null) {
// This child is null about 5/10 times!
View child = view.findViewById(R.id.ownComponentChild);
}
I read that after inflate it is not guaranteed that child views are inflated, so what would be neat way to get callback when all childs are ready?
Maybe I misunderstood what you're trying to do, but it seems like you're inflating a View and not a layout...
Try
View view = LayoutInflater.from(context).inflate(R.layout.LAYOUT_THAT_HOLDS_ownComponent, this, true);
and then view will hold the entire layout, from which you can find the child by Id with
view.findViewById(...);
Edit:
Hard to know if it's related as you didn't post enough code but try this:
Get the View view out of the try/catch and put it as a class member. loose the final and cast the child.
example (assuming ownComponentChild is a FrameLayout):
FrameLayout child = (FrameLayout)view.findViewById(R.id.ownComponentChild);
This seems to happen randomly so my only guess is that memory is getting low in this case, because I have to recreate so many UI components fast to get this reproduced.
Related
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).
I have a ListView in my application. The adapter for this listview contains multiple item view types (around 5 till now), via which I can inflate different types of row views inside the listview.
All row views inflated inside the adapter are custom subclassed view/view group.
public class CustomView1 extends RelativeLayout {
Bundle bundle;
public CustomView1(Bundle bundle) {
super(context);
this.bundle = bundle;
addSubViews(bundle.getBundleList("list"));
}
private void addSubViews(ArrayList<Bundle> list) {
for(Bundle element : list) {
//add sub views via reflection
View view = (View) Class.forName(packageName + type).getConstructor(Bundle.class).newInstance(element);
addView(view);
}
}
//called from getView() in adapter when convertView != null
public void onRecycle(Bundle bundle) {
if(bundle != this.bundle) {
this.bundle = bundle;
removeAllViews();
addSubViews(bundle.getBundleList("list"));
}
}
}
Bundle passed to each custom view contains layout info for that view. In this way, I can create and add any view/viewgroup inside any viewgroup. All well till now.
Now the problem comes when this code runs inside ListView. Since all the view types are created by the adapter initially, scrolling jerks a lot because the adapter keeps on creating new custom views of different itemViewType. How to reduce those jerks in listview ? Any ideas? In the listview, all viewTypes are different at the top 5 positions, so the adapter has to create these views and that makes the experience sluggish.
Even when the adapter recycles similar view type convertViews after 5th index, I clear the container using removeAllViews() and run this loop again because the subView bundle list of the incoming bundle from 6th position onwards might be different. So in the end, adapter is only recycling empty ViewGroups. Since the subView list can possibly contain anything (maybe one more bundle list inside any element bundle), I have to do removeAllViews() to accommodate new subview tree in the recycled convertView.
I thought of using vertical ScrollView but that would take too much memory upfront, and the number of custom views inflated is dynamic, can increase to 20.
The app is running but the scroll is so bad there is hardly any usability left, so its looking like till now I have achieved nothing by adding so much dynamic behavior also. Please suggest me ways to counter this problem.
I am suspecting that the use of setLayoutParams inside CustomView classes may be stopping the scroll because I set the width/height of all views after they are created.
Update #1 getView() code using ViewHolder pattern
ViewHolder holder;
if(convertView == null) {
holder = new ViewHolder();
holder.customView1 = new CustomView1(bundle);
convertView = holder.customView1;
convertView.setTag(holder);
} else {
holder = (ViewHolder)convertView.getTag();
}
holder.customView1.onRecycle(bundle);
ListView has excellent support for different View types. Just make sure to use view holder pattern to avoid jerky scrolling and then override getViewTypeCount() and getItemViewType().
More detail http://android.amberfog.com/?p=296
This might be a little bit hard to explain, so the best way I can think of, is providing you a Video showing up the issue.
In the Video I show myself scrolling listview, and after 5 seconds, a View is created and added inside that holder in the bottom. In that moment, listview is refreshed.
http://tinypic.com/player.php?v=vpz0k8%3E&s=8#.U0VrIvl_t8E
The issue is the following:
I've an Activity with a layout that consists of a:
Fragment (above RelativeLayout), match parent, match parent.
RelativeLayout, as wrap content.
The fragment displays a ListView with animations for every row.
If I add a View on the "RelativeLayout", it makes the fragment to readjust to the new size, as it's set above this RelativeLayout, so every Row is rebuilt again.
Do you guys think in any way to avoid this?
EDIT: Sourcecode:
https://bitbucket.org/sergicast/listview-animated-buggy
Don't start the animation if the layout process for the added footer view is running. The end of the layout process can be determined using the ViewTreeObserver (the start obviously starts with adding the footer view):
hand.postDelayed(new Runnable() {
#Override
public void run() {
ViewTreeObserver viewTreeObserver = holder.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#SuppressWarnings("deprecation")
#Override
public void onGlobalLayout() {
holder.getViewTreeObserver().removeGlobalOnLayoutListener(this);
mIgnoreAnimation = false;
}
});
mIgnoreAnimation = true;
holder.addView(viewToAdd);
}
}, 5000);
Add this method to your Activity:
public boolean ignoreAnimation() {
return mIgnoreAnimation;
}
And check it in your Fragment:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = new TextView(context);
tv.setText("Pos: " + position);
tv.setTextSize(35f);
if (runAnimation()) {
Animation anim = AnimationUtils.loadAnimation(context, R.anim.animation);
tv.startAnimation(anim);
}
return tv;
}
private boolean runAnimation() {
Activity activity = getActivity();
if (activity != null && activity instanceof MainActivity) {
return ! ((MainActivity)activity).ignoreAnimation();
}
return true;
}
Of course the whole Activity - Fragment communication can be improved considerably but the example gives you the idea how to solve the problem in general.
While it prevents the animation from being started, it doesn't prevent the ListView from being refreshed although the user won't notice. If you are concerned about performance you can improve the Adapter code by re-using the views:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = FragmentTest.this.getActivity();
TextView tv = null;
if (convertView != null && convertView instanceof TextView) {
tv = (TextView) convertView;
}
else {
tv = new TextView(context);
}
Yes, I can think of a possible way to solve this.
Your problem is:
You have set layout params of your holder to wrap_content. By default, when it has no content, it is "zero-sized" somewhere in the bottom and invisible to you (not invisible in terms of Android, though, sic!)
When you add a View to this holder, the framework understands, that the size of your holder container is different now. But this container is a child of another container - your root RelativeLayout, which, in turn, contains another child - your <fragment>.
Thus, framework decides, the root container alongside with its children should get laid out again. That's why your list gets invalidated and redrawn.
To fix the issue with list getting invalidated and redrawn, simply specify some fixed layout parameters to your holder. For example:
<RelativeLayout
android:id="#+id/holder"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true" >
</RelativeLayout>
That will prevent the list from being redrawn. But in that case you'll get your holder displayed from the very beginning.
Yes. This is the expected behavior of RelativeLayout
You are adding the ListView Fragment and TextView into a RelativeLayout, So whenever there is a change in the child view dimension, will affect the other child in the RelativeLayout.
So here when you add a new TexView , the other child Fragment is affected even though its height is match_parent.
You can fix this only by changing the parent layout to LinearLayout.
The Android developer guide seems to suggest that Activity.setContentView() can only be called with a layout ID (R.layout.*). However, I can see view IDs (R.id.*) being used to call the method. For example, in org/xbmc/android/widget/slidingtabs/SlidingTabActivity.java of XBMC, I can see the following code:
private void ensureTabHost() {
if (mTabHost == null) {
this.setContentView(R.id.slidingtabhost);
}
}
So, what does it mean to call setContentView() with a view ID? Thanks!
Additional question based on comment - is "setContentView(viewId);" equivalent to "View v = findViewById(viewId); setContentView(v);"?
Not
that Activity.setContentView() can only be called with a layout ID (R.layout.)
Just any view id can be called by the setContentView().
And layout is also a view!
I think the document should say:Set the activity content from a view(not only a layout) resource. The resource will be inflated, adding all top-level views to the activity. Actually ,it works like this: If you make a setConentView(R.layout.my_layout); then android os will do the following works:
LayoutInflater inflater= (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View layout = inflater.inflate(R.layout.my_layout, null);
setConentView(layout);
if you make a setContentView(R.id.myview);it is also the same way to inflate.
LayoutInflater inflater=(LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View myview = inflater.inflate(R.id.myview, null);
setConentView(myview); `
So I say they are the same.
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);