Quickly switching and animating views in a FrameLayout messes up the views - android

I have a very strange problem with Android animations, I tried many different approaches and components, and still couldn't find any explanation.
I have a FrameLayout which is a container for views, and a Button.
This FrameLayout should display only one view at a time, and when I click on the Button, I want the FrameLayout to display another view, and start an animation on the view that is removed from the FrameLayout.
The specificity here is that I only use two views, and I want to switch between these two views.
The problem:
When I click the button multiple times really fast, and then stop clicking, the two views are shown at the same time one on top off the other, and won't disappear. The container still contains only one view though... definitely strange!
error screenshot http://i.minus.com/jDXyvUsE1LCOx.png
I was able to reproduce this with a simple example:
public class TestAnimActivity extends Activity implements OnClickListener {
private FrameLayout container;
private TextView current;
private TextView next;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
current = new TextView(this);
current.setText("View 1 YEAH");
next = new TextView(this);
next.setText(" View 2 DOH");
container = (FrameLayout) findViewById(R.id.container);
container.addView(current);
findViewById(R.id.button).setOnClickListener(this);
}
public void onClick(View v) {
Animation outAnimation = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
current.startAnimation(outAnimation);
container.addView(next);
container.removeView(current);
TextView temp = current;
current = next;
next = temp;
}
}
As you can see the animations are being started on the views while previous ones are still running, and the way I do that may somehow be the source of the problem.
If I comment the animation related code, it works perfectly:
// Animation outAnimation = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
// current.startAnimation(outAnimation);
If I stop reusing views and create new views instead, it also works perfectly:
// next = temp;
next = new TextView(this);
next.setText("View: " + new Random().nextInt());
But I don't want to create new views :-) .
It seems that the problem is related to starting animations several times on a view while adding / removing this view from its parent.
I first tried with a ViewFlipper, then a ViewAnimator, then looked up the Android source code and ended up doing this manually to reproduce the problem.
If you want the layout to be able to test by yourself, here is my main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/container"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<Button
android:id="#+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Click me fast" />
</LinearLayout>
Thank you for your time!

I finally found a way to fix the issue.
When we remove a view from a ViewGroup, if there is an animation running on this view, it is automatically added to the "disappearing children" list of the ViewGroup.
The issue seemed to appear when we tried to add to the ViewGroup a view that has been removed from it but was still in its disappearing children.
There's an easy way to fix that : viewGroup.clearDisappearingChildren();
Here would be the new implementation of the onClick() method :
public void onClick(View v) {
next.clearAnimation();
container.clearDisappearingChildren();
Animation outAnimation = AnimationUtils.loadAnimation(this, android.R.anim.fade_out);
current.startAnimation(outAnimation);
container.addView(next);
container.removeView(current);
TextView temp = current;
current = next;
next = temp;
}

If it works without quickly pressing the button, and works without the animation then you may be seeing how animations actually work. From my understanding what is displayed in the animation is separate from the View objects they are animating. You can try animating from the parent view instead of the ones that keep being added and removed. Or make sure to clean up the animation before removing the view, as it sounds like the view isn't able to get the onAnimationEnd method where it can remove the phantom image being displayed (setFillAfter(false) which is the default behavior of an Animation).

Related

Android app dynamically created buttons appearing all in the same place

I'm working on my first android app and have run into some trouble. I'm trying to start a new activity which takes in an ArrayList from the previous activity and creates a button for each item in the list. I've tried looking at many other stack overflow posts but none of them have helped solve my problem.
I have tried many different strategies, and my code is currently as below:
public class VideoMenu extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_video_menu);
Bundle b = getIntent().getExtras();
ArrayList<String> videos = b.getStringArrayList("videos");
setContentView(R.layout.activity_video_menu);
for(int i=0;i<videos.size();i++){
Button myButton = new Button(this);
myButton.setText(videos.get(i));
myButton.setId(i);
RelativeLayout ll = (RelativeLayout)findViewById(R.id.videos);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
if (i != 0) {
lp.addRule(RelativeLayout.END_OF, i - 1);
}
myButton.setLayoutParams(lp);
ll.addView(myButton,lp);
}
}
}
and XML code:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/videos"
android:layout_alignParentBottom="true"
android:layout_alignWithParentIfMissing="true"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:orientation="vertical">
This compiles just fine, but all of the buttons appear on top of one another. If you could include reasons for why any code suggestions might work I would appreciate it. I'd like to learn more about this :)
Edit: fixed typo
The reason is that you are adding the buttons to a RelativeLayout. RelativeLayout basically add all views to wherever you specify in the view. You're not really specifying, so they're all appearing on top of each other.
In order to fix this, change your RelativeLayout to a LinearLayout, with orientation of either horizontal or vertical. horizontal will add views side by side in a horizontal line, and vertical will add them in a vertical line.

Adding a view dynamically to Layout

I'm having a hard time with Android as a pretty newbie in the platform, and as I learnt, many people too. I don't know how many hours I have lost so far - it's terrifying even to count them.
I want basically to add a new TextView (or any other View) to LinearLayout after clicking the button. Here's this part of the code:
public void btnClick(View view) {
final LinearLayout ll = (LinearLayout) findViewById(R.id.test_story_screen_layout);
//checking if child is being added - it is, this value is increase with every button click
android.util.Log.d("child: ", Integer.toString(ll.getChildCount()));
android.util.Log.d("height: ", Integer.toString(ll.getMeasuredHeight()));
ll.post(new Runnable() {
public void run() {
final TextView tv = new TextView(MainActivity.this);
tv.setText("new one " + (new Random()).nextInt());
tv.setTextColor(Color.YELLOW);
LayoutParams lp = new LayoutParams(200, 500);
tv.setLayoutParams(lp);
tv.setVisibility(View.VISIBLE);
ll.addView(tv, ll.getChildCount());
//checking if the run() is called - it is
ll.setBackgroundColor(Color.GREEN);
}
});
ll.requestLayout(); //invalidate() and postInvalidate() also not working
}
But the newly added View is not visible (but is added as a child to LinearLayout).
After hours of checking what's wrong, I only found out that when I replace this line:
ll.addView(tv, ll.getChildCount());
with this:
ll.addView(tv, ll.getChildCount() - 1);
Then new View is visible, but it replaces the previous one - which is not desired.
I already checked some solutions, like this: Refreshing a LinearLayout after adding a view
They didn't help me with the issue.
UPDATE:
Linear Layout looks just fine with predefined in XML two Views (as in the code below). Here's intersting part of the XML:
<LinearLayout
android:id="#+id/test_story_screen_layout"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:orientation="vertical"
android:layout_margin="16dp" >
<ImageView
android:id="#+id/imageView1"
android:src="#drawable/example"
android:layout_width="16dp"
android:layout_height="16dp" />
<TextView
android:id="#+id/textView1"
android:text="Hello World!"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
Nothing unusual I think. So these two Views (Image and Text) are visible of course. Then any new TextView I add to this LinearLayout dynamically (via the btnClick() as in the code above) is not visible, but it is indeed added to Layout, as the ll.getChildCount() is increased every time when the child view is added (= button clicked).
In order to test this further, I added these two lines at the end of the btnClick() method:
android.util.Log.d("tv.getMeasuredWidth: ", Integer.toString(tv.getMeasuredWidth()));
android.util.Log.d("tv.getMeasuredHeight: ", Integer.toString(tv.getMeasuredHeight()));
I'm guessing the problem is the tv (TextView) rather than ll (LinearLayout), as tv gets width and height both 0.
Issue is you are initializing your linearlayout ll with what defined in your xml every time on button click and due to this your linearlayout is getting renewed. Move
final LinearLayout ll = (LinearLayout) findViewById(R.id.test_story_screen_layout);
in your onCreate if using Activity, onCreateView if using Fragment.
Your layout horizontal by default, When you add new view it set it up out from right border of screen. Just set orientation vertical in layout for you linear layout
The linearLayout orientation is horizontal by default set orientation to vertical.
if you want to add View as last element in Layout you can just do this:
ll.addView(tv);
There were number of things related in this Android issue, some of this were:
ll.post() made sense and appared to be required in my case
UI in Android should be updated in a special, not any, way, and it refers to e.g. Async Tasks
visibility of an item (item should be visible)
proper margins (when a screen e.g. is rotated to landscape, margin appeared to be too big)
Damn it. I find Android to be one of least thought-through platforms I've seen.

Android TextView cannot setBackground Color or Drawable

wonder if anyone has came across this problem before. I have an app which detects faces and places touchable squares around the faces, all in a RelativeLayout. When touched I want to add some text into the View which is all working nicely, but when I go to simply add a background to the TextView it just does nothing. I've tried a standard background setBackgroundColor(Color.WHITE); instead of the background I really want to use (setBackgroundResource(R.drawable.nametag);) and still nothing.
TextView nameLabelView = new TextView(activity);
nameLabelView.setText(fullname);
nameLabelView.setTextColor(Color.BLACK);
nameLabelView.setBackgroundColor(Color.WHITE); //TODO <-- wth??
//nameLabelView.setBackgroundResource(R.drawable.nametag);
nameLabelView.setGravity(Gravity.CENTER_HORIZONTAL);
//duplicate layout params from active face View so label sits inside it
ViewGroup.LayoutParams lp = selectedFaceView.getLayoutParams();
nameLabelView.setLayoutParams(lp);
facePreviewLayout.addView(nameLabelView);
Strange one, hope it's obvious to someone out there, thanks in advance!
I have tested your code and it works ok for me. It is necessary to provide more information so I can help more with it. The code below works fine.
MainActivity.java
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView nameLabelView = new TextView(getApplicationContext());
nameLabelView.setText("Test");
nameLabelView.setTextColor(Color.WHITE);
nameLabelView.setBackgroundColor(Color.BLACK); //TODO <-- wth??
//nameLabelView.setBackgroundResource(R.drawable.nametag);
nameLabelView.setGravity(Gravity.CENTER_HORIZONTAL);
//duplicate layout params from active face View so label sits inside it
FrameLayout selectedFaceView = (FrameLayout)findViewById(R.id.frame_layout);
ViewGroup.LayoutParams lp = selectedFaceView.getLayoutParams();
nameLabelView.setLayoutParams(lp);
RelativeLayout facePreviewLayout = (RelativeLayout)findViewById(R.id.relative_layout);
facePreviewLayout.addView(nameLabelView);
}
}
activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<FrameLayout
android:id="#+id/frame_layout"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="#00FF00" >
</FrameLayout>
</RelativeLayout>
Thank you janzoner for your test really appreciate it, good to know I wasn't going mad. I've found a way to work around it though, I have no idea why this method works and the setup I had before didn't, perhaps I'd been staring at it too long and missed something somewhere else.
Basically beforehand I was adding the name label TextView into the main RelativeLayout (whose children were the actual image and all the face Views etc).
Now I have moved the name label TextView into its own RelativeLayout which sits around the face, and that's magically done the trick. This is a cleaner way of doing things anyway as you can't iterate through child Views of Views but you can children of RelativeLayout's and I will need this later!
Phew!

Change from one layout view to another and going back?

want to make an Android app that starts with a main layout and when you push a button (called stateButton) that is in this layout the layout changes to a main2 layout containing another button (called boton2), and when you push this one you get back to the first main.
I want to do this in the same activity without creating or starting another one.
Here I show you part of the code:
public class NuevoshActivity extends Activity
implements SensorEventListener, OnClickListener {
private Button stateButton;
private Button boton2;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
this.stateButton = (Button) this.findViewById(R.id.boton);
this.boton2 = (Button) this.findViewById(R.id.boton2);
stateButton.setOnClickListener(this);
boton2.setOnClickListener(this);
}
#Override
public void onClick(View v) {
if(v==stateButton) {
setContentView(R.layout.main2);
}
else if(v==boton2) {
setContentView(R.layout.main);
}
}
}
The mains only have some images, text views and the buttons.
But I've some troubles. Can't it just be as simple as that or what am I missing or what is wrong?
When you use findViewById, you are actually trying to find a view inside the layout you specified by the setContentView. So using setContentView again and again might bring problems when you are trying to check for buttons.
Instead of using a setContentView, I would add the 2 layouts for the screen as child's for a view-flipper which only shows one child at a time. And you can specify the index of which child to show. The benefit of using a view flipper is that you can easily specify a 'in' and 'out' animation for the view if you need an animation when you switch between views. This is a lot cleaner method then recalling setContentView again and again.
The FrameLayout handles this wonderfully... Use this with the <include... contstruct to load multiple other layouts, then you can switch back and forth between them by using setvisibility(View.VISIBLE); and setVisibility(View.INVISIBLE); on the individual layouts.
For example:
Main XML including two other layouts:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout android:id="#+id/frameLayout1" android:layout_width="fill_parent" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android">
<include android:id="#+id/buildinvoice_step1_layout" layout="#layout/buildinvoice_step1" android:layout_width="fill_parent" android:layout_height="fill_parent"></include>
<include android:id="#+id/buildinvoice_step2_layout" android:layout_width="fill_parent" layout="#layout/buildinvoice_step2" android:layout_height="fill_parent"></include>
</FrameLayout>
Code to switch between layouts:
findViewById(R.id.buildinvoice_step1_layout).setVisibility(View.VISIBLE);
findViewById(R.id.buildinvoice_step2_layout).setVisibility(View.INVISIBLE);
You will also need to set the visibility of the individual layouts when the activity starts (or in XML) otherwise you will see them both - one on top of the other.
Your boton2 button will be NULL because the definition of the button is in main2.xml.
The only views you will be able to find are the views which are defined in main.xml.
Thanks!!! All the info was usefull to understand a lot of things and as C0deAttack commented I've got troubles with the button on the main2. What I've done is to set View.VISIBLE and View.GONE to the TextViews and Buttons that I wanted in each layout. Thank you very much.

Android: Inflate View under a View then slide top view off

My issue is that I have a main screen, and I would like to dynamically spawn a view under it with a button click, then slide the main view off the screen revealing the view below it. I've accomplished this, but I feel like there's got to be a better way. The way I've done it is very limited in that you can't just spawn views over and over again under the main.
My main XML file looks like this:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout
android:id="#+id/subpage"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</RelativeLayout>
<RelativeLayout
android:id="#+id/homescreen"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/homebg"
>
</RelativeLayout>
</RelativeLayout>
</RelativeLayout>
I've deleted some unnecessary stuff. This is what's important. Notice the first child of the main layout is a relative layout with the id "subpage." As it is I use java to inflate another layout into the subpage layout when a button is clicked then I animate the "homescreen" layout off the screen. It seems like I shouldn't have to have the subpage declared in advance though. I guess my question is, is there a way to dynamically declare a new child layout underneath an existing layout?
=======================================================================
Edit: Part 2 of question
I'm trying to use addView and the app crashes. This is the code I use to try to add a view and inflate my xml into it. In the code below subview is a ViewGroup because as I understand it you can only inflate into ViewGroups, not regular views. Also 'activity' is defined at the top of the class as 'private Activity activity = this'. Any ideas what could be causing the crash?
btnHelp.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
subView = (ViewGroup)new View(getApplicationContext());
mainScreen.addView(subView,1);
LayoutInflater inflater = activity.getLayoutInflater();
inflater.inflate(R.layout.help, subView);
}
});
=======================================================================
Edit: Part 3 of question
So one more issue. Everything works great as far as inflating and sliding off. However, the view that is inflated has a button in it. I'm trying to assign a listener to that button, but it doesn't seem to work. I'm doing it by adding the listener to the button after the layout inflater is called in the btnHelp I've been working on. Here's the code:
btnHelp.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
LayoutInflater inflater = activity.getLayoutInflater();
mainScreen.addView(inflater.inflate(R.layout.help, null),0);
homeScrn.startAnimation(slideLeftOut);
btnBackHome = (ImageView)findViewById(R.id.backMenuBtn);
btnBackHome.setOnClickListener(goHome);
}
});
goHome is a handler I've defined below this as such:
private OnClickListener goHome = new OnClickListener(){
public void onClick(View v) {
Log.d("ClickEvent: ","btnBackHome Clicked");
homeScrn.startAnimation(slideRightIn);
}
};
When I click the button referenced by btnBackHome it doesn't do anything. I'm just not sure if it's because the listener isn't actually being assigned, something is keeping the button from actually being clicked, or something else.
Call addView() on the RelativeLayout to add children to it, where the children are either inflated (getLayoutInflater().inflate()) or constructed directly in Java.
Also, you might consider using a ViewFlipper, considering that it does what you're seeking (animated transition from child to child, with only one child visible at a time in the steady state), perhaps with less code.
The default animation when starting a new Activity is a sliding animation.. why not just separate your "homescreen" and "subpage" into 2 different XML files and 2 Activities?

Categories

Resources