I am changing the left margin of an image view in the following manner :
ViewGroup.MarginLayoutParams layoutParams = (MarginLayoutParams) image.getLayoutParams ();
layoutParams.leftMargin = VALUE;
image.setLayoutParams ( layoutParams );
I would like the change in margin to apply with animation. Any clues ?
What I tried :
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat ( image , "x" , VALUE);
objectAnimator.start();
This works perfectly, as the image is moved to the specified X value with animation, HOWEVER the value of layoutParams.leftMargin remains unchanged !! So I cannot use this method, because if I try to change the value of layoutParams.leftMargin to 100 after using the objectAnimator with the value 100, the value applied is not correct ( 200 is applied instead of 100, the effect if the objectAnimator remains eventhough I am setting the left margin in the following manner :
layoutParams.leftMargin = 100;
Use Animation class, not ObjectAnimator.
final int newLeftMargin = <some value>;
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
LayoutParams params = yourView.getLayoutParams();
params.leftMargin = (int)(newLeftMargin * interpolatedTime);
yourView.setLayoutParams(params);
}
};
a.setDuration(500); // in ms
yourView.startAnimation(a);
Please note that you should use correct LayoutParams class i.e. if your view is the child of LinearLayout then params should be LinearLayout.LayoutParams
I came by this question, but I couldn't use it because I want to animate the margin from a negative value to 0, so I used valueAnimater base on user1991679 answer:
final View animatedView = view.findViewById(R.id.animatedView);
final LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) animatedView.getLayoutParams();
ValueAnimator animator = ValueAnimator.ofInt(params.bottomMargin, 0);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator)
{
params.bottomMargin = (Integer) valueAnimator.getAnimatedValue();
animatedView.requestLayout();
}
});
animator.setDuration(300);
animator.start();
You must change LinearLayout.LayoutParams according to animatedView container.
Also you can use nineoldandroids for older version that don't have ValueAnimator.
The answer from user1991679 is great, but if you need to interpolate a margin from any other value but 0, you need to use it in your calculations:
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) mBottomLayout.getLayoutParams();
final int bottomMarginStart = params.bottomMargin; // your start value
final int bottomMarginEnd = <your value>; // where to animate to
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
ViewGroup.MarginLayoutParams params = (MarginLayoutParams) mBottomLayout.getLayoutParams();
// interpolate the proper value
params.bottomMargin = bottomMarginStart + (int) ((bottomMarginEnd - bottomMarginStart) * interpolatedTime);
mBottomLayout.setLayoutParams(params);
}
};
a.setDuration(300);
mBottomLayout.startAnimation(a);
In my case I needed to animate an "enter the screen" animation, coming from "-48dp" to 0. Without the start value, the animation is always 0, thus jumping, not animating the view. The solution was to interpolate the offset and add it to the original value.
Nothing worked for me like I wanted so...
I needed to create ToggleMenu from -80 dp (oldLeftMargin) to 0dp.
Same with bottomMargin, etc.
Now it works:
final int oldLeftMargin = (int) getResources().getDimension(R.dimen.left_menu_button_margin_left);
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) llMeniuToggle.getLayoutParams();
params.leftMargin = oldLeftMargin + (int) ((0 - oldLeftMargin) * interpolatedTime);
llMeniuToggle.setLayoutParams(params);
}
};
a.setDuration(500);
llMeniuToggle.startAnimation(a);
You can use the following
image.animate().setDuration(durationIn).translationXBy(offsetFloat).start();
You can also add .setInterpolator(new BounceInterpolator()) to change the look of the animation.
Related
This is bottom margin:
#Override
protected void directionDownScrolling(View recyclerView) {
MarginLayoutParams params = (MarginLayoutParams) recyclerView.getLayoutParams();
params.setMargins(0, 0, 0,
(int) recyclerView.getContext().getResources().getDimension(R.dimen.dimen_recycler_view_spacing));
mHandler.postDelayed(() -> recyclerView.setLayoutParams(params), 250);
}
And Top Padding :
#Override
protected void directionDownScrolling(View recyclerView) {
// Calculate ActionBar height
TypedValue tv = new TypedValue();
int actionBarHeight = recyclerView.getContext().getTheme().resolveAttribute(android.R.attr.actionBarSize, tv, true) ?
TypedValue.complexToDimensionPixelSize(tv.data, recyclerView.getContext().getResources().getDisplayMetrics()) :
(int) recyclerView.getContext().getResources().getDimension(R.dimen.dimen_recycler_view_spacing);
recyclerView.setPadding(0, actionBarHeight, 0, 0);
}
As you see top padding applies without no delay, but I expect bottom margin appears after 250ms,
but as soon as top padding applies, bottom margin appears as well. Why and how to fix it?
You're getting the layout parameters from recyclerView:
MarginLayoutParams params = (MarginLayoutParams) recyclerView.getLayoutParams();
Then you setup the margins directly on it:
params.setMargins(0, 0, 0,
(int) recyclerView.getContext().getResources().getDimension(R.dimen.dimen_recycler_view_spacing));
So the delayed message doesn't do anything:
mHandler.postDelayed(() -> recyclerView.setLayoutParams(params), 250);
This would make a difference if you'd set the layout params to some other instance. Instead call setMargins delayed:
#Override
protected void directionDownScrolling(View recyclerView) {
MarginLayoutParams params = (MarginLayoutParams) recyclerView.getLayoutParams();
int marginBottom = (int) recyclerView.getContext().getResources().getDimension(R.dimen.dimen_recycler_view_spacing));
mHandler.postDelayed(() -> {
params.setMargins(0, 0, 0, marginBottom);
recyclerView.requestLayout();
}, 250);
}
I am trying to animate the text inside a TextView to grow and change from black to green. The color change works perfectly, and the text size animates between the right sizes, but the text increases in size from the top left point. I would like the text to increase in size from the center. I have tried using .setGravity(Gravity.Center), .setPivotY()and .setPivotX(), and a few other solutions but nothing seems to be working. Also tried using. TranslateY but that seems to move the entire TextView rather than the just the text inside and was getting messy resetting the text position after.
Textview tv_CurrentWord = (TextView) findViewById(R.id.tv_currentWord);
final float defaultTextSize = tv_CurrentWord.getTextSize();
final float finalTextSize = defualtTextSize* 1.2f;
final float g = 155;
if (mValueAnimator.isRunning()){
mValueAnimator.cancel();
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
tv_CurrentWord.setTextColor(Color.BLACK);
}
mValueAnimator.removeAllUpdateListeners();
mValueAnimator.setDuration(200);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX, (finalTextSize-defaultTextSize)*animatedValue+defaultTextSize);
int color = Color.rgb(
0,
(int) (g*animatedValue)
,0);
tv_CurrentWord.setTextColor(color);
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter()
{
#Override
public void onAnimationEnd(Animator animation)
{
mCurrentWord = "";
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX,
defaultTextSize);
tv_CurrentWord.setTextColor(Color.BLACK);
tv_CurrentWord.setText(mCurrentWord);
}
});
mValueAnimator.start();
and the XML. The LinearLayout container was a recent addition to try to keep the text centered, but I am not sure it is needed.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="#dimen/current_word_height"
android:layout_below="#+id/rl_players_and_scores"
android:id="#+id/ll_current_word_container">
<TextView
android:id="#+id/tv_currentWord"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:ellipsize="start"
android:gravity="center"
android:singleLine="true"
android:textSize="40sp"
android:textStyle="bold" />
</LinearLayout>
I ended up figuring this out. Posting here for anyone else who might have the same question. I used .setTranslationY and .setY. The XML LinearLayout wrapper around the TextView was important because it allowed the TextView to move without throwing off other elements in the parent layout that were positioned off of it. .setTranslationY was used to offset the increasing text size in the TextView. Since .setTextSize(px) and .setTranslationY(px) both take pixels the Y translation just had to be half the change in text size. .setY() was used in the animation end, or on restart of the animation to reset the TextView to the original position. .setTranslationX was not needed because the text actually stays centered horizontally during the animation.
Textview tv_CurrentWord = (TextView) findViewById(R.id.tv_currentWord);
float currentWordYPosition = tv_CurrentWord.getY();
final float defaultTextSize = tv_CurrentWord.getTextSize();
final float finalTextSize = defualtTextSize* 1.2f;
final float g = 155;
if (mValueAnimator.isRunning()){
mValueAnimator.cancel();
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX,
defaultTextSize);
tv_CurrentWord.setTextColor(Color.BLACK);
tv_CurrentWord.setY(currentWordYPosition);
}
mValueAnimator.removeAllUpdateListeners();
mValueAnimator.setDuration(200);
mValueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float animatedValue = (float) valueAnimator.getAnimatedValue();
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX,
(finalTextSize-defaultTextSize)*animatedValue+defaultTextSize);
tv_CurrentWord.setTranslationY(-1*(finalTextSize-
defaultTextSize)*animatedValue/2);
int color = Color.rgb(
0,
(int) (g*animatedValue)
,0);
tv_CurrentWord.setTextColor(color);
}
});
mValueAnimator.addListener(new AnimatorListenerAdapter()
{
#Override
public void onAnimationEnd(Animator animation)
{
mCurrentWord = "";
tv_CurrentWord.setTextSize(TypedValue.COMPLEX_UNIT_PX,
defaultTextSize);
tv_CurrentWord.setTextColor(Color.BLACK);
tv_CurrentWord.setText(mCurrentWord);
tv_CurrentWord.setY(currentWordYPosition);
}
});
mValueAnimator.start();
I want to animate the change in the padding of a view. The resting place of the translation animation is the same as the padding I want to apply.
TranslateAnimation moveleft = new TranslateAnimation(Animation.ABSOLUTE, 0.0f,
Animation.ABSOLUTE, PADDING, Animation.ABSOLUTE,
0.0f, Animation.ABSOLUTE, 0.0f);
moveLeft.setDuration(500);
moveLeft.setFillAfter(true);
This starts the view's animation then sets the padding. This doesn't exactly work because it cause a graphical glitch.
v.startAnimation(moveleft);
v.setPadding(PADDING, 0, 0,0);
Use ValueAnimator, its really simple and unclutter
say,
we have to change right padding to _20dp where as left, top and bottom padding are _6dp, _6dp and 0 respectively.
ofInt() is varagrs type. the value we have to animate is pass in it as KeyValue pair (arg1 = current value, arg2 = target value,............)
Here we go,
ValueAnimator animator = ValueAnimator.ofInt(view.getPaddingRight(), _20dp);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator){
view.setPadding(_6dp, _6dp, (Integer) valueAnimator.getAnimatedValue(), 0);
}
});
animator.setDuration(200);
animator.start();
Instead of setting your padding right away, why not try an animation listener to set the padding after the animation has completed?
v.setAnimationListener(new Animation.AnimationListener() {
...
#Override
public void onAnimationEnd(){
v.setPadding(PADDING, 0, 0,0);
}
...
});
v.startAnimation(moveleft);
Here is how to animate setPadding in Kotlin:
private fun setPaddingWithAnimation() {
val paddingDp: Int = 20
val density: Float = requireActivity().getResources().getDisplayMetrics().density
val paddingPixel: Int = (paddingDp * density).toInt()
val animator = ValueAnimator.ofInt(recyclerItems.paddingRight, paddingPixel)
animator.addUpdateListener { valueAnimator -> binding.recyclerHealthTips.recyclerHealthTips.setPadding(
paddingPixel, 0, valueAnimator.animatedValue as Int, 0) }
animator.duration = 500
animator.start()
}
I have two views in a RelativeLayout, both of which fill the screen, so view B is on top of view A. I also have an animation defined which can move view B partially offscreen to show view A underneath. The animation works fine, but I'm having the classic issue of the view bounds not moving with the view, so the button that I use to trigger the animation (which is located on view B) is only clickable from its original position, no matter where view B is located. The issue that I'm having is that after the animation ends, when I set the layout params it's causing view B to be redrawn again, translated from the location of the end of the animation.
As a concrete example, the left edge of view B is initially at x = 0, with a button at x = 450. When the button is pressed, an animation moves the view to x = -400. This works properly - the view is partially off the left hand side of the screen, and the button is now at x = 50, so it is still on screen. The click area for the button though is still at x = 450. So now I set the layout params on view B:
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) viewB.getLayoutParams();
lp.rightMargin = 400;
viewB.setLayoutParams(lp);
Once the new params are set, the view gets 400px of padding on the right, moving the entire view to x = -800. The clickable area for the button is now properly at x = 50 though, so it seems like I can have it look right or act right. Any idea what I'm doing wrong? Here's how the animation is set up.
Animation anim = null;
anim = new TranslateAnimation(0, -400, 0, 0);
anim.setAnimationListener(this);
anim.setDuration(duration);
viewB.startAnimation(anim);
I was able to get things working by changing the layout params before or after the animation, as appropriate:
private int marginOffsets;
public void triggerAnimation(boolean show, offset)
{
int curX = 0;
int newX = 0;
Animation anim = null;
this.showingPanel = show;
if(show)
{
curX = 0 - offset;
android.widget.RelativeLayout.LayoutParams lp = new android.widget.RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT);
lp.rightMargin = 0;
rootPanel.setLayoutParams(lp);
}
else
{
newX = 0 - offset;
}
marginOffsets = newX < 0 ? 0 - offset : offset;
anim = new TranslateAnimation(curX, newX, 0, 0);
anim.setAnimationListener(this);
anim.setDuration(duration);
startAnimation(anim);
}
public void onAnimationEnd(Animation anim)
{
//This prevents flicker when the view is moving onscreen.
clearAnimation();
if(!showingPanel)
{
//Move the margin to move the actual bounds so click events still work.
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT);
lp.rightMargin = 0 - marginOffsets;
rootPanel.setLayoutParams(lp);
}
}
I have to develop a UI like below:
I want to show this type of image and show hotspot on that image. The position of hotspot will be dynamic, as per x,y and radius provided the circle will be drawn on the original picture. The user can click on the hotspots and onclick action will be defined on the specific hotspot on which the user will click.
What is best process to develop this type of UI?
Make your main layout a RelativeLayout and then you can add programmatically a ImageView with an onClickListener to your layout with the following code:
private void addImageView(RelativeLayout mainLayout, int x, int y, int width, int height, OnClickListener onClickListener){
ImageView imageView = new ImageView(this);
imageView.setAdjustViewBounds(false);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.height = height;
params.width = width;
imageView.setLayoutParams(params);
imageView.setScaleType(ImageView.ScaleType.FIT_XY); //remove this if you want to keep aspect ratio
imageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher)); //here goes your drawable
params.leftMargin = x - width/2;
params.topMargin = y - height/2;
imageView.setOnClickListener(onClickListener);
mainLayout.addView(imageView);
}
to use it you call:
RelativeLayout mainLayout = (RelativeLayout) findViewById(R.id.relativeLayout); //this is your main layout
addImageButton(mainLayout, 200, 300, 200, 200, new OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, "clicked", Toast.LENGTH_SHORT).show();
}
});
You can also use a ImageButton to achive the same porpose, although the image size will be affected by button border:
private void addImageButton(RelativeLayout mainLayout, int x, int y, int width, int height, OnClickListener onClickListener){
ImageButton imageButton = new ImageButton(this);
imageButton.setAdjustViewBounds(true);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
params.height = height;
params.width = width;
imageButton.setLayoutParams(params);
imageButton.setScaleType(ImageView.ScaleType.FIT_XY);
imageButton.setImageDrawable(getResources().getDrawable(R.drawable.ic_launcher));
params.leftMargin = x - width/2;
params.topMargin = y - height/2;
imageButton.setOnClickListener(onClickListener);
mainLayout.addView(imageButton);
}
Try it.