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();
Related
I am wondering, is there a way to animate a substring in Android?
Suppose I have a string "I have 3 Apples."
I want to animate just the numerical part of the string, like scale up or maybe zoom the text view.
I recommend using a SpannableStringBuilder and a ValueAnimator for the text size.
String text = "your string";
String animTarget = "animation target");
final SpannableStringBuilder sBuilder = new SpannableStringBuilder(text);
int index = text.indexOf(animTarget);
float size;
RelativeSizeSpan textSize;
TextView view;
ValueAnimator animator = ValueAnimator.ofFloat(startingTextSize, endingTextSize);
animator.setDuration(duration);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator animation) {
size = (float) animation.getAnimatedValue();
textSize = new RelativeSizeSpan(size);
sBuilder.setSpan(textSize, // Span to add
index, // Start of the span (inclusive)
index+1, // End of span
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE // Do not extend);
view.setText(sBuilder)
}
});
)
Basic idea.
I'm wondering what would be the best solution to get to the result shown below.
Here is what i've found so far:
an ImageView for the forest and a transparent surfaceView (to handle touch) on which I would draw the rectangles?
Or...
Just One SurfaceView with the image set as background and rectangles directly drawn on...?
For those 2 I've already chosen a RelativeLayout.
Which of those 2 would be the most efficient and easiest to do?
Or maybe there is another way which I haven't think about.
In any case thanks for your advice, here is what I tend to...
I've implemented this by placing the image in a RelativeLayout (FrameLayout would work too), and then adding each outlined view programatically. If you know the x and y origin (perhaps as a ratio to the image) and the size for each area, you can easily inflate each view/area (with a black border, transparent center), make it clickable and set a listener, and then set it's origin by adjusting it's margins. You may want to perform all of this after the image has finished laying out:
I put this in onActivityCreated of my Fragment, but other lifecycle methods would work too...
ViewTreeObserver vto = image.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
if (image.getMeasuredHeight() > 0) {
addHotSpots();
ViewTreeObserver obs = image.getViewTreeObserver();
obs.removeGlobalOnLayoutListener(this);
}
}
});
And this is how I actually place all the hotspots/areas:
protected void addHotSpots() {
HotSpot[] hotSpots = res.hotspots;
for (HotSpot hs : hotSpots) {
addHotSpotToImage(hs);
}
private void addHotSpotToImage(HotSpot hs) {
int height = image.getMeasuredHeight();
int width = image.getMeasuredWidth();
//this piece will probably be different for you
//depending on what you know about the area's intended size/position
double hsHeightRatio = hs.lr.y - hs.ul.y;
double hsWidthRatio = hs.lr.x - hs.ul.x;
double leftMargin = hs.ul.x * width;
double topMargin = hs.ul.y * height;
double hsHeight = height * hsHeightRatio;
double hsWidth = width * hsWidthRatio;
LayoutInflater vi = (LayoutInflater) image.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View newSpot = vi.inflate(R.layout.question_hotspot, null);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams((int) hsWidth, (int) hsHeight);
newSpot.setTag(hs.key);
newSpot.setFocusable(true);
newSpot.setClickable(true);
newSpot.setFocusableInTouchMode(true);
newSpot.setOnTouchListener(this);
params.topMargin = (int) topMargin;
params.leftMargin = (int) leftMargin;
image.addView(newSpot, params);
}
I have a bitmap and below it is a time line.
As an example consider the right side layout of the FIGURE.
All the bottom timelines (1, 2, 3...) are in the same height from top.
The timeline is a textview which has fixed layout height and width as it is defined in xml
like timeline 1 is defined as:
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="#+id/HView"
android:layout_marginLeft="18dp"
android:layout_marginTop="345dp"
android:textSize="14sp"
android:text="1"
android:textColor="#000000" />
However the bitmap height and width can vary as it is done programatically.
So in certain cases, the bitmap height increases enough to overlap the timeline. In other words,
the vertical position of bitmap increases with respect to the vertical position of the timeline.
I want to get:
1) the ended vertical position of bitmap with respect to top of the screen.
2) the ended vertical position of timeline with respect to top of the screen.
I tried to do the following:
TextView bottomTimeLine = (TextView) view.findViewById(R.id.textView1);
bottomTimeLine.getHeight(); //returns 0.
bottomTimeLine.getBottom(); //returns 0.
ImageView img = new ImageView(getActivity());
img.setImageDrawable(getResources().getDrawable(R.drawable.disp_bg));
img.getHeight(); //returns 0.
img.getBottom(); //returns 0.
As seen from the code, both the methods, getHeight() and getBottom() are returning height as 0.
How to get the height (view end position) of both with respect to top of the cell display ?
Hope this helps
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
this.setMeasuredDimension(
parentWidth / 2, parentHeight);
}
This is how it can be done:
final TextView bottomTimeLine = (TextView) view.findViewById(R.id.textView1);
final int[] timelineCoord = new int[2];
final int[] imgCoord = new int[2];
ViewTreeObserver vto = bottomTimeLine.getViewTreeObserver();
vto.addOnGlobalLayoutListener((new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
bottomTimeLine.getLocationOnScreen(timelineCoord);
Log.d(" bottomTimeLine H ", ""+timelineCoord[1]);
timelineHeight = timelineCoord[1];
}
}));
ViewTreeObserver vt1 = img.getViewTreeObserver();
vt1.addOnGlobalLayoutListener((new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
img.getLocationOnScreen(imgCoord);
imgHeight = imgCoord[1] + img.getHeight();
Log.d("Img H ", ""+imgHeight);
if(imgHeight < timelineHeight)
{
int heightDiff = imgHeight - timelineHeight ;
heightDiff = heightDiff + 3;
img.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, heightDiff));
}
}
}));
How could I move image when scrolling seekbar? I want to move this indicator image when changing seekbar thumb position.
Here is image.
Some adjustments may need to be made depending upon your choice of image and/or its alignment with the seekbar. I am sure someone else here will come up with a better approach, but here's my take on it:
// Declare global variables
SeekBar sb;
LinearLayout.LayoutParams params;
Point p;
ImageView iv;
// Initialize the widgets
sb = (SeekBar) findViewById(....);
iv = (ImageView) findViewById(....);
// Since I added SeekBar and ImageView to a LinearLayout.
// Use LayoutParams for whichever container you use.
params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
// Starting position
params.leftMargin = 0;
// Decide the value based on where you wish to place the image w.r.t the SeekBar
params.topMargin = ...;
iv.setLayoutParams(params);
// You will use this to get the width of the screen
p = new Point();
getWindowManager().getDefaultDisplay().getSize(p);
sb.setOnSeekBarChangeListener(new OnSeekBarChangeListener() {
#Override
public void onStopTrackingTouch(SeekBar seekBar) {}
#Override
public void onStartTrackingTouch(SeekBar seekBar) {}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
// ((float)progress * p.x) / 100): "p.x" holds the screen's width.
// The expression computes the percentage of
// screen width given the "progress" value
// (progress * 0.5): This is being used to offset image position.
// I am using the offset because it seemed that the image
// was leading the SeekBar at higher progress values.
// You can try different values to see which one works best.
int measure = (int)((((float)progress * p.x) / 100) - (progress * 0.5));
// When "measure" will become equal to "p.x"(at progress = 100),
// the image will be outside the view when we set its "leftMargin".
// But, the image will start disappearing before that.
// When this situation comes, set the "leftMargin" to a maximum value
// which is the screen width - ImageView' width
if (p.x - measure < iv.getWidth()) {
params.leftMargin = p.x - iv.getWidth();
} else {
params.leftMargin = measure ;
}
// Set the "topMargin" to the value you decided upon before
params.topMargin = ...;
// Set LayoutParams
iv.setLayoutParams(params);
}
});
Edit (for user Shripal's comment) -- above the SeekBar (Y-axis):
layout.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<RelativeLayout
android:id="#+id/relativeLayout1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="100dp" > <!-- adjust this in java code -->
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="#drawable/some_drawable" />
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:text="50" />
</RelativeLayout>
<SeekBar
android:id="#+id/seekBar1"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Here is the xml code
<SeekBar
android:id="#+id/seek_bar"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:thumb="#drawable/seek_thumb_icon"
android:progressDrawable="#drawable/progress"/>
Heres the java code
#Override
public void onProgressChanged(SeekBar seekBar, int i, boolean b) {
mSeekBarProgressText.setText(String.valueOf(i) + " %");
mParcentValue = i;
setPercent(mDiscountPercentText , "-" + i , getResources().getDimension(R.dimen.discount_percent_text));
if (i > 0){
findMGTextView(R.id.card_without_discount).setVisibility(View.GONE);
} else {
findMGTextView(R.id.card_without_discount).setVisibility(View.VISIBLE);
}
int measure = (int)((((float)i * point.x) / 100) - (i * 0.5));
if (i < 50){
mSeekBarProgressText.setBackgroundResource(R.drawable.cucich1);
params.leftMargin = measure ;
} else {
mSeekBarProgressText.setBackgroundResource(R.drawable.cucich_reverse1);
params.leftMargin = measure - (mSeekBarProgressText.getWidth() + 20);
}
params.topMargin = 0;
mSeekBarProgressText.setLayoutParams(params);
findMGTextView(R.id.card_without_discount1).setLayoutParams(params);
}
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.