I have an imageview that's included inside a RelativeLayout. When the imageview is clicked, I animate the entire RelativeLayout with a translate animation to move it down.
when I click the imageview again (in it's new location) it's supposed to move it back up, but it does not. However, if I click where the imageview started, it does move the entire "panel" back up. Why is the imageview not moving with the relativelayout...at least insofar as the clickability of it. The actual image is moving, but the spot where it's clickable is not.
here is my xml for the layout:
<RelativeLayout
android:layout_alignParentTop="true"
android:layout_alignParentLeft="true"
android:layout_width="fill_parent"
android:layout_height="120px"
android:layout_marginTop="-106px"
android:id="#+id/chatbox"
android:visibility="invisible">
<TextView
android:layout_width="fill_parent"
android:layout_height="106px"
android:background="#000000"
android:id="#+id/chattext" />
<ImageView
android:layout_width="20px"
android:layout_height="14px"
android:id="#+id/chatbubble"
android:layout_below="#id/chattext"
android:src="#drawable/chatbubble" />
</RelativeLayout>
Edit: I should add that I'm using Animation.setFillAfter(true) to hold the panel in place after the animation completes.
This is because animations actually do not affect views positions, they just draw them. So to handle click at new place you have to place something there (i.e. invisible FrameLayout). Or you can change your views margins on animation complete, so the views actually will move to the place.
I had the same problem and the best solution I found is to add an AnimationListener to your animation and move the view yourself in that listener as Konstantin Burov said :
animation.setFillAfter(false);
AnimationListener animListener = new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationRepeat(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
RelativeLayout.LayoutParams relativeLayoutParams = (LayoutParams) yourView.getLayoutParams();
//To avoid the flicker
yourView.clearAnimation();
relativeLayoutParams.setMargins(0, 0, 0, Utils.dpToPx(activity, newPositionDp);
yourView.setLayoutParams(relativeLayoutParams);
}
};
animation.setAnimationListener(animListener);
The Utils.dpToPx may be useful too :
public final static int dpToPx(Context context, int dp) {
return ((int)((dp * context.getResources().getDisplayMetrics().density) + 0.5));
}
Related
I have two layouts (green on top, red on bottom) in a vertical LinearLayout (parent) looking similar to this:
.
When focus goes from the green to red, I would like the green to slide up off the screen and have the red simultaneously slide up with it and fill the whole screen. And when focus moves from red back up I want the green to slide back into the screen and return to the original configuration. I have tried looking at many other questions but none have had the solution I need. I tried just changing visibility between gone and visible but I want it to be a smooth animation. I've tried using parentLayout.animate().translationY(greenLayout.getHeight()) on the outer LinearLayout and that does give the animation I want but then the red does not expand to fill the screen, like this:
.
I know this question is similar to this one but that question is really old and only had one answer which didn't work for me.
My solution has a lot of different pieces, so I'll start with the full XML and java code, and then talk about the important bits:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="#+id/green"
android:layout_width="match_parent"
android:layout_height="100dp"
android:background="#0f0" />
<View
android:id="#+id/red"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:background="#f00"/>
</LinearLayout>
In the XML, the only really important part is that the red view uses a height of 0dp and weight of 1. This means it takes up all extra vertical space, which will be important when we get rid of the green view.
public class MainActivity extends AppCompatActivity {
private int originalHeight;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final View green = findViewById(R.id.green);
final View red = findViewById(R.id.red);
green.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
green.getViewTreeObserver().removeGlobalOnLayoutListener(this);
originalHeight = green.getHeight();
}
});
green.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, originalHeight, 0);
}
});
red.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
animateHeightOfView(green, 0, originalHeight);
}
});
}
private void animateHeightOfView(final View view, int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int height = (int) valueAnimator.getAnimatedValue();
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = height;
view.setLayoutParams(params);
}
});
animator.start();
}
}
In the Java, the two main parts are the ViewTreeObserver.OnGlobalLayoutListener and the animateHeightOfView() method.
The OnGlobalLayoutListener exists to capture the green view's original height. We have to use a listener to do this instead of just writing originalHeight = green.getHeight() inside onCreate() because the green view isn't actually laid out at that point, so getHeight() would return 0 if we tried that.
The animateHeightOfView() method leverages the ValueAnimator class to animate the height of whatever view you pass to it. Since there's no direct setter for a view's height, we can't use simpler methods like .animate(). We set up the ValueAnimator to produce int values on every frame, and then we use a ValueAnimator.AnimatorUpdateListener to modify the view's LayoutParams to set the height.
Feel free to play with it. I'm using click listeners to trigger the animation, and you mentioned focus, but you should be able to call animateHeightOfView() in a different way if it suits you.
What I'm trying to achieve
I have a group of buttons in a GridLayout. One of the buttons toggles the function of the other buttons between rounding up and rounding down.
I am trying to implement an animation that changes the color of the buttons in a ripple like effect. The circular reveal animation is the effect I want where the new colour spreads from the touch point on the toggling button to the rest of the other buttons. I only want the animation to grow outward from the touch point in both cases, switching from rounding up to rounding down and back.
Problem
The animation is only working in the first instance from rounding up to rounding down (my app starts with rounding up as the default). There is no animation when switching from rounding down back to rounding up; all the buttons just changes in colour and the toggle button changes icon.
This is my first app so I am having difficulty trying to work out where I've gone wrong.
My methodology and code
In my activity_main.xml layout file, I've created two sets of GridLayouts (each with child buttons) and layered them one on top of the other by wrapping both in a FrameLayout. One set is given a different colour to the other set; I also have a different icon for the toggling button for each set to differentiate between the two rounding method. I've set the visibility attribute on the second GridLayout parent to invisible.
<FrameLayout
<GridLayout
android:id="#+id/roundUp"
<ImageButton
android:id="#+id/buttonU1"
... />
<Button ... />
<Button ... />
</GridLayout>
<GridLayout
android:id="#+id/roundDown"
android:visibility="invisible"
<ImageButton
android:id="#+id/buttonD1"
... />
<Button ... />
<Button ... />
</GridLayout>
</FrameLayout>
In my MainActivity.java file:
import ...
boolean roundingMode;
public class MainActivity extends AppCompatActivity {
GridLayout roundUp, roundDown;
ImageButton buttonU1, buttonD1;
Button ...
#Override;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
roundUp = (GridLayout) findViewById(R.id.roundUp);
buttonU1 = (ImageButton) findViewById(R.id.buttonU1);
roundDown = (GridLayout) findViewById(R.id.roundDown);
buttonD1 = (ImageButton) findViewById(R.id.buttonD1);
roundingMode = true;
buttonU1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
buttonCircularRevealAnim(roundDown);
roundingMode = false;
}
});
buttonD1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
buttonCircularRevealAnim(roundUp);
roundingMode = true;
}
});
}
public void buttonCircularRevealAnim(View view) {
int originX = roundUp.getWidth() / 2;
int originY = roundUp.getHeight() / 2;
float finalRadius = (float) Math.hypot(originX,originY);
Animator anim;
if (roundingMode) {
anim = ViewAnimationUtils.createCircularReveal(roundDown, originX, originY, 0, finalRadius);
roundDown.setVisibility(View.VISIBLE);
anim.start();
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
roundUp.setVisibility(View.INVISIBLE);
}
});
} else {
anim = ViewAnimationUtils.createCircularReveal(roundUp, originX, originY, 0, finalRadius);
roundUp.setVisibility(View.VISIBLE);
anim.start();
anim.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
roundDown.setVisibility(View.INVISIBLE);
}
});
}
}
Ignore the fact that I currently have the origin of the animation in the center of the GridLayout. I just want to get the basics working first.
I think the problem lies in the logic and/or the set up of the animation?
I've tried moving the originX, originY and finalRadius calculations into the if statement and using the roundUp (for roundingMode true) and roundDown GridLayouts (for roundingMode false) but that didn't work - so I figure it doesn't matter if it is invisible (in the scenario where you're switching from rounding down to rounding up).
Eventually understood that the circular reveal animation works to reveal an overlapping view that is initially invisible. So in order to use it to change the colour of a view back and forth you have to apply the new colour (from the revealed view) to the view underneath after the animation has ended and then make the revealed view invisible again with the other colour applied to it.
My original code was trying to reveal the view underneath which is of course impossible.
I found the following page very helpful:
http://myhexaville.com/2016/12/19/keep-your-app-material/
I have a TextView inside a LinearLayout. My goal is to animate the TextView height change when its text changes and need more or less lines.
I split the work this way :
Fade out the old text
Animate the TextView new height (and the parent LinearLayout)
Fade in the new text
The fade in / fade out part is easy, but I struggle for the height change animation.
Here's my simplified layout :
<LinearLayout
android:id="#+id/fragment_tooltip_tooltip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/tooltip_blue_bg"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="#+id/fragment_tooltip_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
And here is what I tried :
final int currentHeight = tvMessage.getHeight();
tvMessage.setText(toTooltip.getMessage());
tvMessage.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
TooltipFragment.this.tvMessage.getViewTreeObserver().removeGlobalOnLayoutListener(this);
final int newHeight = tvMessage.getHeight();
ResizeAnimation resizeAnimation = new ResizeAnimation(tvMessage, tvMessage.getWidth(), tvMessage.getWidth(),
currentHeight, newHeight);
resizeAnimation.setDuration(ANIM_DURATION_TRANSITION_TEXT_RESIZE);
resizeAnimation.setInterpolator(new FastOutSlowInInterpolator());
resizeAnimation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
// No-op
}
#Override
public void onAnimationEnd(Animation animation) {
// Finally we show the new content
...
}
#Override
public void onAnimationRepeat(Animation animation) {
// No-op
}
});
tvMessage.startAnimation(resizeAnimation);
}
});
The problem with this solution is that the tvMessage.setText(toTooltip.getMessage()); line used to measure the new height expand immediately the TextView and LinearLayout, then the animation resize the view to its previous size before applying the resize the animation, producing an ugly visual effect.
I believe a TextSwitcher will not animate the height change and so is not a solution, but I didn't tried it.
The solution for my problem was to use a dummy TextView and measure it with the new text instead of using the actual TextView. This way my TextView is not resized before the animation, and I just have to set the text at the end of the animation.
See this answer for details : Getting height of text view before rendering to layout
You could use a handler that runs recursively for this, looks a bit cleaner too. So, for example, use this:
Call the function startTextAnimation()
Have a Handler animHandler = new Handler(); in your onCreate()
Then use this function:
public void startTextAnimation() {
textView.setHeight(textView.getHeight() + 1);
if (textView.getHeight < *whatheightyouwant*) {
aninHandler.postDelayed(new Runnable() {
#Override
public void run() {
startTextAnimation();
}
}, 200);
}
}
You can set parameters accordingly, it should do the job. Hope it helps.
I am trying to make an app where in the first screen, an image (name of the app) flies into the background, and gets fixed into a certain location. I am new to android and would appreciate any help.
I have tried using setAnimationListener():
public class Pageone extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_main);
final ImageView imageView=(ImageView)findViewById(R.id.image);
Animation anim1 = new TranslateAnimation(0, 0, 1024, 824);
anim1.setDuration(3000);
anim1.setFillAfter(true);
anim1.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Animation anim2 = new TranslateAnimation(0, 0, 824, 1024);
anim2.setDuration(3000);
anim2.setFillAfter(true);
imageView.clearAnimation();
imageView.startAnimation(anim2);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
imageView.startAnimation(anim1);
}
}
and the xml page as -
<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:paddingBottom="#dimen/activity_vertical_margin"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:paddingRight="#dimen/activity_horizontal_margin"
android:paddingTop="#dimen/activity_vertical_margin"
tools:context="com.example.meeting.MainActivity$PlaceholderFragment"
android:background="#drawable/tapregister" >
<ImageView
android:id="#+id/image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="#drawable/meet"
/>
</RelativeLayout>'
While running the app in android virtual device manager, I find the image fixed as seen in the layout section.
You don't need two animations to do what you want. You just need one animation that moves your ImageView from Bottom to the center of the screen.
So, drop your anim1.setAnimationListener() code. Change the Ydelta values to start in position Y+300 (for example) and end in position Y+0 (your layout original position). This will place your ImageView on the screen bottom, 300 px below your initial position and move it to the center, this is, to your layout initial position. Like this:
final ImageView imageView=(ImageView)findViewById(R.id.image);
Animation anim1 = new TranslateAnimation(0,0,300,0);
anim1.setDuration(3000);
anim1.setFillAfter(true);
imageView.startAnimation(anim1);
So I start my activity and then
setContentView(R.layout.myxmllayoutfile);
All is well, but I want to make my imagebutton suddenly grow (scale) from nothing (ie 1%). However the layout is already displaying the button so it suddenly disappears then grows back rather than growing from nothing.
I have some alternatives but is there a real solution?:
1. flying the imagebutton animate in from offscreen? ; or
2. making it tiny in the xml and then growing it, then if necessary changing the clickable area?; or
3. is there a better solution?
UPDATE:
As suggested I tried:
<ImageButton
android:id="#+id/spinner"
android:scaleType="fitXY"
android:layout_width="120dp"
android:layout_height="120dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:background="#drawable/clear"
android:orientation="horizontal"
android:visibility="invisible"
android:src="#drawable/spin"
/>
and in my java:
scaleView.startAnimation(scanimation);
ImageButton spinnerbutton=(ImageButton)findViewById(R.id.spinner);
spinnerbutton.setVisibility(View.VISIBLE);
but it is still visible before it shrinks to 1% then grows! Suggestions welcome.
UPDATE2:
Nothing has changed with the edited below code:
public void growit() {
final ImageView scaleView = (ImageView) findViewById(R.id.spinner);
Animation scanimation = AnimationUtils.loadAnimation(this, R.anim.throbbing2);
scanimation.setFillAfter(true);
scanimation.setAnimationListener(new Animation.AnimationListener() {
public void onAnimationStart(Animation a) { Log.e("growit", "---- animation start listener called");
scaleView.setVisibility(View.VISIBLE);
}
public void onAnimationRepeat(Animation a) {
}
public void onAnimationEnd(Animation a) {
}
});
scaleView.startAnimation(scanimation);
}
Make imagebutton hidden.
Then in your animation set Animation listener and on start animation callback method set the button visible
UPDATE:
Animation animation = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f);
animation.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
mImageButton.setVisibility(View.VISIBLE);
}
#Override
public void onAnimationRepeat(Animation animation) { }
#Override
public void onAnimationEnd(Animation animation) { }
});
mImageButton.startAnimation(animation);
No permanent solution. Just used a workaround of making it tiny in the xml and then growing it as suggested in the question as a possible "solution"/workaround but not ideal.