I'm implementing an Animation for one menu in my project.
The animation itself is ok, the menu enters and exit just as I wanted: Slide from left to right and right to left, however...
If the entire view is OUT of the screen, then it NEVER comes back egain! If, at least one pixel is still inside the screen, then it comes back normally.
I belive that Android is disposing the layout, and not caring about it after out of the screen bounds. I tried to place a setVisibility(VISIBLE) but it also didn't worked.
Here is the code:
public class ChwaziMenuAnimation extends Animation{
float posStart = 0;
float posTarget = 100;
int getCurrentPosition(){
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
return rootParam.leftMargin;
}
public void setTarget(float target){
// Save current position
posStart = getCurrentPosition();
posTarget = target;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
RelativeLayout.LayoutParams rootParam =
(RelativeLayout.LayoutParams) rootView.getLayoutParams();
// Calculate current position
rootParam.leftMargin = (int) ((posTarget - posStart) * interpolatedTime + posStart);
rootView.setLayoutParams(rootParam);
}
/*
* Since we will be animating the margin, the bounds will always change
*/
public boolean willChangeBounds() {
return true;
};
};
And how I initialize the animation:
public void appear(){
Log.i(TAG, "appear");
menuAnimation.setTarget(0);
menuAnimation.setDuration(750);
rootView.clearAnimation();
rootView.startAnimation(menuAnimation);
}
public void disapear(){
Log.i(TAG, "disapear");
menuAnimation.setTarget(-400);
menuAnimation.setDuration(750);
rootView.startAnimation(menuAnimation);
}
I encountered the same problem, my workaround so far is to extend the view bounds to at least one pixel on the displayable area, and make that part transparent. Ugly, but for me it seems to work.
To make it even more weird: the view did not disappear, when shifted out the right side of the screen, only when shifted out the left side of the screen. But that might be device dependent.
Related
I'm using the following code to expand a view with an animation:
public class HorizontallyAnimate extends Animation {
private int toWidth;
private int startWidth;
private View view;
private String TAG = HorizontallyAnimate.class.getSimpleName();
private int newWidth;
public HorizontallyAnimate(View view) {
this.view = view;
this.startWidth = this.view.getWidth();
this.toWidth = (this.startWidth == view.getHeight() ? this.startWidth * 4 : view.getHeight());
Log.d(TAG,"Start width" + this.startWidth);
Log.d(TAG,"view hieght " + view.getHeight());
}
protected void applyTransformation(float interpolatedTime, Transformation t) {
newWidth = this.startWidth + (int) ((this.toWidth - this.startWidth) * interpolatedTime);
this.view.getLayoutParams().width = newWidth;
this.view.requestLayout();
}
}
The above code animates the view from left to right when the width changes.
But, I'm trying to animate it from the right to left. In other words, the width should grow in opposite direction. How can I be able to do so?
The problem you're dealing with here is an anchoring issue. A view's anchor (or pivot point) determines which point on the view stays still when other parts change.
A view's anchor when adjusting its dimensions highly depends on how the view is laid out. Since you didn't supply any information on how your view is laid out in the code you posted, I'll assume from the problem you're experiencing that the view's horizontal anchor is its left side.
This anchoring issue would produce a growth which would cause the left-most side to stay still, while the right side expands right-wards.
Making the view's left side to expand leftwards while the right side stays still can be achieved in several ways. One way would be to alter the way the view is laid out in its parent (i.e., if the parent is a RelativeLayout, set the view to alignParentRight=true, or playing with gravity in other containers).
However, since you didn't specify how the view is laid out, I will give you a solution which does not make any assumptions on its container. This solution isn't perfect as it may cause some stuttering, but it should still achieve what you're trying to do.
In your applyTransformation method, you will need to compensate for the right growth by translating leftwards. You can compensate for this by using translationX:
protected void applyTransformation(float interpolatedTime, Transformation t) {
// hold the change in a separate variable for reuse.
int delta = (int) ((this.toWidth - this.startWidth) * interpolatedTime);
newWidth = this.startWidth + delta;
this.view.getLayoutParams().width = newWidth;
// shift the view leftwards so that the right side appears not to move.
// shift amount should be equal to the amount the view expanded, but in the
// opposite direction.
this.view.setTranslationX(-delta);
this.view.requestLayout();
}
As you can see, this is a bit of "trick". While the view is expanding to the right, we are moving it to the left at the exact same ratio which causes the illusion of the view expanding to the left.
Test this code and see if it works for you. I would also recommend seeing if you can play around with the view's alignment or gravity from within its container. Doing this would solve your issue in a more standard manner, i.e., without any "tricks".
Hope this helps.
Animation animation = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
ViewGroup.LayoutParams params = slidingPane.getLayoutParams();
int calculatedHeight = expandedHeight - ((int) (expandedHeight * interpolatedTime));
if (calculatedHeight <= collapsedHeight) {
calculatedHeight = collapsedHeight;
}
params.height = calculatedHeight;
slidingPane.setLayoutParams(params);
slidingPane.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
animation.setDuration(ANIMATION_DURATION);
animation.setAnimationListener(animationListener);
slidingPane.startAnimation(animation);
SlidingPane is a LinearLayout and it has a ListView as a child. ListView contains images in every row.
Now this code is working absolutely fine, but animation is not smooth. If I remove images from listview then it works fine.
I have already tried following things based on the answers on SO on similar questions -
1. Hide the content of sliding pane before animation and then again make them visible on animation end. It helps but behavior looks very odd
2. Set android:animateLayoutChanges="true" in xml, but its not animating anything
Is there anyway to solve this problem?
Instead of manually changing the height on each animation step, you should try to use the more systematic animation, like ValueAnimator, or ObjectAnimator:
slidingPane.animate().scaleY(0f).setInterpolator(new AccelerateDecelerateInterpolator()).setDuration(ANIMATION_DURATION);
You can use pivotY/pivotY on the view to control the anchor point of the scale animation.
Related docs:
https://developer.android.com/reference/android/animation/ValueAnimator.html
https://developer.android.com/reference/android/animation/ObjectAnimator.html
https://developer.android.com/guide/topics/graphics/prop-animation.html
I have a RelativeLayout with a ImageView inside it, it is aligned to the right of the screen with
android:layout_alignParentRight="true"
I then apply an animation to the layout, that will scale it to twice it size (on X-axis), but for some reason the alignment is broken and the layout (and image within it) stretches outside of the screen to the right. I was expecting it to grow to the left since it is aligned to the parent right.
I guess I could apply a translate to -X at the same time, but there are problems with this as well (1. it´s a bit complicated to compute, 2. the fillAfter never seems to work when using an AnimationSet).
Does anyone know how to solve this problem smoothly? :)
Apparently, Androids scaling is doing all sorts of bad stuff, for example it does not scale a 9-patch correct. Here is a custom animation for scaling that will solve the above and is compatible with 9-patch images. I also got some info here for the basics, this is just a rewrite of his solution: How to implement expandable panels in Android?
public class ExpandAnimation extends Animation
{
private final int mStartWidth;
private final int mDeltaWidth;
private View view;
public ExpandAnimation(View view, int startWidth, int endWidth)
{
mStartWidth = startWidth;
mDeltaWidth = endWidth - startWidth;
this.view = view;
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
android.view.ViewGroup.LayoutParams lp = view.getLayoutParams();
lp.width = (int) (mStartWidth + mDeltaWidth * interpolatedTime);
view.setLayoutParams(lp);
view.invalidate();
}
#Override
public boolean willChangeBounds()
{
return true;
}
}
As I've a master in MS Paint, I will just upload a picture selfdescripting what I'm trying to achieve.
I've searched, but I'm not really sure what do I've to search. I've found something called Animations. I managed to rotate, fade, etc an element from a View (with this great tutorial http://www.vogella.com/articles/AndroidAnimation/article.html)
But this is a bit limited for what I'm trying to achieve, and now, I'm stuck, because I don't know how is this really called in android development. Tried words like "scrollup layouts" but I didn't get any better results.
Can you give me some tips?
Thank you.
You can see a live example, with this app: https://play.google.com/store/apps/details?id=alexcrusher.just6weeks
Sincerely,
Sergi
Use something like this as your layout (Use Linear, Relative or other layout if you wish):
<LinearLayout
android:id="#+id/lty_parent">
<LinearLayout
android:id="#+id/lyt_first" />
<LinearLayout
android:id="#+id/lyt_second"/>
</LinearLayout>
And then in an onClick method on whatever you want to use to control it, set the Visibility between Visible and Gone.
public void buttonClickListener(){
((Button) findViewById(R.id.your_button))
.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
if (lyt_second.getVisibility() == View.GONE) {
lyt_second.setVisibility(View.VISIBILE);
}
else {
lyt_second.setVisibility(View.GONE);
}
});
Which is fine if you just want a simple appear/disappear with nothing fancy. Things get a little bit more complicated if you want to animate it, as you need to play around with negative margins in order to make it appear to grow and shrink, like so:
We use the same onClick method that we did before, but this time when we click it starts up a custom SlideAnimation for the hidden/visible view.
#Override
public void onClick(View v) {
SlideAnimation slideAnim = new SlideAnimation(lyt_second, time);
lyt_second.startAnimation(slideAnim);
}
The implementation of the SlideAnimation is based on a general Animation class, which we extend and then Override the transformation.
public SlideAnimation(View view, int duration) {
//Set the duration of the animation to the int we passed in
setDuration(duration);
//Set the view to be animated to the view we passed in
viewToBeAnimated = view;
//Get the Margin Parameters for the view so we can edit them
viewMarginParams = (MarginLayoutParams) view.getLayoutParams();
//If the view is VISIBLE, hide it after. If it's GONE, show it before we start.
hideAfter = (view.getVisibility() == View.VISIBLE);
//First off, start the margin at the bottom margin we've already set.
//You need your layout to have a negative margin for this to work correctly.
marginStart = viewMarginParams.bottomMargin;
//Decide if we're expanding or collapsing
if (marginStart == 0){
marginEnd = 0 - view.getHeight();
}
else {
marginEnd = 0;
}
//Make sure the view is visible for our animation
view.setVisibility(View.VISIBLE);
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
super.applyTransformation(interpolatedTime, t);
if (interpolatedTime < 1.0f) {
// Setting the new bottom margin to the start of the margin
// plus the inbetween bits
viewMarginParams.bottomMargin = marginStart
+ (int) ((marginEnd - marginStart) * interpolatedTime);
// Request the layout as it happens so we can see it redrawing
viewToBeAnimated.requestLayout();
// Make sure we have finished before we mess about with the rest of it
} else if (!alreadyFinished) {
viewMarginParams.bottomMargin = marginEnd;
viewToBeAnimated.requestLayout();
if (hideAfter) {
viewToBeAnimated.setVisibility(View.GONE);
}
alreadyFinished = true;
}
hideAfter = false;
}
}
EDIT: If anyone had used this code before and found that if you click on the button that starts the animation more than once before the animation was finished, it would mess up the animation from then on, causing it to always hide the view after the animation finished. I missed the reset of the hideAfter boolean near the bottom of the code, added it now.
you can do this manually by using setvisibility feature on the event onClick()
or
use this
dynamically adding two views one below other
In my Android Layout, I have a TextView. This TextView is displaying a rather large spannable text and it is able to scroll. Now when the phone is rotated, the View is destroyed and created and I have to setText() the TextView again, resetting the scroll position to the beginning.
I know I can use getScrolly() and scrollTo() to scroll to pixel positions, but due to the change in View widths, lines become longer and a line that was at pixel pos 400 might now be at 250. So this is not very helpful.
I need a way to find the first visible line in a TextView in onDestroy() and then a way to make the TextView scroll to this specific piece of text after the rotation.
Any ideas?
This is an old question, but I landed here when searching for a solution to the same problem, so here is what I came up with. I combined ideas from answers to these three questions:
Scroll TextView to text position
Dynamically Modifying Contextual/Long-Press Menu in EditText Based on Position of Long Press
ScrollView .scrollTo not working? Saving ScrollView position on rotation
I tried to extract only the relevant code from my app, so please forgive any errors. Also note that if you rotate to landscape and back, it may not end in the same position you started. For example, say "Peter" is the first visible word in portrait. When you rotate to landscape, "Peter" is the last word on its line, and the first is "Larry". When you rotate back, "Larry" will be visible.
private static float scrollSpot;
private ScrollView scrollView;
private TextView textView;
protected void onCreate(Bundle savedInstanceState) {
textView = new TextView(this);
textView.setText("Long text here...");
scrollView = new ScrollView(this);
scrollView.addView(textView);
// You may want to wrap this in an if statement that prevents it from
// running at certain times, such as the first time you launch the
// activity with a new intent.
scrollView.post(new Runnable() {
public void run() {
setScrollSpot(scrollSpot);
}
});
// more stuff here, including adding scrollView to your main layout
}
protected void onDestroy() {
scrollSpot = getScrollSpot();
}
/**
* #return an encoded float, where the integer portion is the offset of the
* first character of the first fully visible line, and the decimal
* portion is the percentage of a line that is visible above it.
*/
private float getScrollSpot() {
int y = scrollView.getScrollY();
Layout layout = textView.getLayout();
int topPadding = -layout.getTopPadding();
if (y <= topPadding) {
return (float) (topPadding - y) / textView.getLineHeight();
}
int line = layout.getLineForVertical(y - 1) + 1;
int offset = layout.getLineStart(line);
int above = layout.getLineTop(line) - y;
return offset + (float) above / textView.getLineHeight();
}
private void setScrollSpot(float spot) {
int offset = (int) spot;
int above = (int) ((spot - offset) * textView.getLineHeight());
Layout layout = textView.getLayout();
int line = layout.getLineForOffset(offset);
int y = (line == 0 ? -layout.getTopPadding() : layout.getLineTop(line))
- above;
scrollView.scrollTo(0, y);
}
TextView can save and restore its state for you. If you aren't able to use that, you can disable that and explicitly call the methods:
http://developer.android.com/reference/android/widget/TextView.SavedState.html
http://developer.android.com/reference/android/widget/TextView.html#onSaveInstanceState()
http://developer.android.com/reference/android/widget/TextView.html#onRestoreInstanceState(android.os.Parcelable)
The best answer, I got by searching.
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
final ScrollView scrollView = (ScrollView) findViewById(R.id.Trial_C_ScrollViewContainer);
outState.putFloatArray(ScrollViewContainerScrollPercentage,
new float[]{
(float) scrollView.getScrollX()/scrollView.getChildAt(0).getWidth(),
(float) scrollView.getScrollY()/scrollView.getChildAt(0).getHeight() });
}
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
final float[] scrollPercentage = savedInstanceState.getFloatArray(ScrollViewContainerScrollPercentage);
final ScrollView scrollView = (ScrollView) findViewById(R.id.Trial_C_ScrollViewContainer);
scrollView.post(new Runnable() {
public void run() {
scrollView.scrollTo(
Math.round(scrollPercentage[0]*scrollView.getChildAt(0).getWidth()),
Math.round(scrollPercentage[1]*scrollView.getChildAt(0).getHeight()));
}
});
}