How can I programmatically translate an arbitrary view without using an animation (android.view.animation.*)?
API 11 introduced setTranslationX and setTranslationY, setX and setY, and getMatrix—all of which can be used to accomplish what I'm looking for. I cannot seem to find an equivalent on the prior API levels, however.
The android.view.animation.TranslateAnimation uses getMatrix in its implementation (as far back as API 4) but it was a private API throughout this time.
Is there any way to accomplish this without resorting to reflection?
Favorited because I would love to be proven wrong on this one, but I ran into a similar wall when doing some Drag-n-Drop investigations awhile back.
I'm pretty sure you're stuck with offsetTopAndBottom() and offsetLeftAndRight() for moving a View around without an animation in early APIs. The big downside here is that these methods actually modify the top/bottom/left/right values, so you have to do some pretty heavy tracking if you want to go back to where you started from.
Another option would be to simply use a TranslateAnimation with a VERY short duration and set to fillAfter...
Other options require subclassing, and still aren't pretty, like translating the Canvas the View draws on. The problem here is you also have to create a parent that doesn't clip it's children so the view can draw outside its own bounds.
Yes, you can use TranslateAnimation. With fillAfter set to true your view will stay translated even after the animation ends. Here is an example:
TranslateAnimation anim=new TranslateAnimation(0, 0, 20, 20);
anim.setFillAfter(true);
anim.setDuration(0);
yourView.startAnimation(anim);
Grishka's answer helped me a lot, works perfectly. In order to animate the translation using the Honeycomb API with nineoldandroids, I set up a FloatEvaluator for the translation value and then used either setTranslationX on Honeycomb+ or the TranslateAnimation method. Here is my code:
public class TranslationXEvaluator extends FloatEvaluator {
private View view;
public TranslationXEvaluator(View view) {
this.view = view;
}
#Override
public Float evaluate(float fraction, Number startValue, Number endValue) {
float translationX = super.evaluate(fraction, startValue, endValue);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
view.setTranslationX(translationX);
} else {
TranslateAnimation anim = new TranslateAnimation(translationX, translationX, 0, 0);
anim.setFillAfter(true);
anim.setDuration(0);
view.startAnimation(anim);
}
return translationX;
}
}
I was having the same problem. I was able to achieve the translation by manipulating the layoutparams, specifically the margins, of the views I wanted to translate. You can use negative values for the margins btw.
One alternative to TranslateAnimation is to subclass a View and translate the Canvas on dispatchDraw.
Something like:
public class TranslateView extends View {
private float mTranslationX = 0;
private float mTranslationY = 0;
private boolean mWillUseSDKTranslation = true;
#Override
protected void dispatchDraw(final Canvas canvas) {
if (mWillUseSDKTranslation == false) {
// keep track of the current state
canvas.save(Canvas.MATRIX_SAVE_FLAG);
// translate
canvas.translate(mTranslationX, mTranslationY);
// draw it
super.dispatchDraw(canvas);
// restore to the previous state
canvas.restore();
} else {
super.dispatchDraw(canvas);
}
}
public void setTranslationValue(final float aTranslationX, final float aTranslationY) {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) {
mWillUseSDKTranslation = false;
mTranslationX = aTranslationX;
mTranslationY = aTranslationY;
invalidate();
} else {
setTranslationX(aTranslationX);
setTranslationY(aTranslationY);
}
}
}
You also can try this code: https://github.com/BeyondAR/beyondar/blob/development/android/BeyondAR_Framework/src/com/beyondar/android/view/CustomLayout.java
Use the method setPosition.
A workaround I found was to set negative margins using:
layoutParams.setMargins(left, top, right, bottom);
yourView.setLayoutParams(layoutParams);
Related
I have a class which is extended from ImageView (lets call it 'surface'). On onDraw method, a little dot is drawing on canvas. When I click a button I try to move this dot to another location. You can consider like manual version of translate animation. It works but now I try to figured out speed of this moving. I mean I want dot moving faster.
Relevant part of surface :
private float actual_x=100,actual_y=100; // Dot is drawn on 100,100 at beginning
private float increase_x,increase_y;
private boolean isMovingResume=false;
private int moving_counter;
public void changeLocation(float x,float y){
isMovingResume=true;
moving_counter=0;
increase_x=(x-actual_x)/50;
increase_y=(y-actual_y)/50;
invalidate(); // This trigger onDraw method
}
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawCircle(actual_x,actual_y,15,fill_paint);
if(isMovingResume){
actual_x=actual_x+increase_x;
actual_y=actual_y+increase_y;
if(moving_counter==49){ // Moving will end after 50 redraw
isMovingResume=false;
}
else{
moving_counter++;
}
invalidate(); //redraw
}
}
And my button click :
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
surface.changeLocation(200,200);
}
});
Like I said, it works but I want it more faster. For example, in this case moving time is 2 second, How can I make it 0,5 second ?
Thanks for all replies.
You need to switch to surfaceview or even faster textureview, both use a canvas as well, and have better performance. Textureview especially as it is hardware accelerated and I also feel it behaves better than surfaceview. That or even consider going to GLSurfaceView.
You can see some example code of surfaceview and textureview: Here
I want to create skew animation for a View in android.
So, I have rectangular view (this is normal/start state), at the end it should looks like this:
I can do such a thing with android.view.animation package using Transformation of TYPE_MATRIX. So I can create Matrix and use preSkew method, apply this Matrix to my custom animation and then start this animation on a view. So this is OK.
BUT
The problem is I want to do this with ObjectAnimator.
Because using android.view.animation package is not recommended to use.
But I can't get the solution for this problem with ObjectAnimator class.
If I have ImageView, I can use method setImageMatrix for ObjectAnimator (and also create custom MatrixEvaluator to evaluate corresponding matrices for each factor),
but I want to get solution for this problem with a View class, not for some subclass of it.
And the main reason is, that TextView, for example, doesn't have public method like setMatrix, and I want to get the solution for TextView too. So get the solution for base View class is the main desire.
There is another solution: create custom, for example, SkewTextView, in which onDraw method we use canvas.skew method.
But this is worse solution even than using android.view.animation package, because you need to create wrappers for any View subclass you want to skew.
So, I don't know, how I can skew View with a ObjectAnimator (as View class doesn't have public methods like setSkew or setMatrix).
How would you solve this problem?
Have you any idea of how to solve this problem?
Is there a solution for this problem with ObjectAnimator?
Any help (or even thoughts) is greatly appreciated
Thanks
Well without subclassing this might be a bit tricky. My main idea is to use TimeAnimator, which will provide you time callbacks in conjunction with Camera (which can use Matrix transformations). Or you can use TimeAnimator and make a Bitmap from the View you want to skew, make the original View invisible, and draw the Bitmap on each TimeAnimator step applying a Matrix to it. But I'm not sure if this is the optimal way to do that, since you will have to create a new Bitmap on each frame
EDIT: No, Camera approach is not the way to do that as it is stated in comments below
I created a skew animation using Android Property Animation APIs in my project (GitHub Link).
What I did:
Created a custom view (SkewView) and added a SKEW_X property to it.
public class SkewView extends ImageView {
private static final String TAG = "SkewView";
public static final Property SKEW_X
= new FloatProperty("skewX") {
#Override
public void setValue(SkewView object, float value) {
object.setSkewX(value);
}
#Override
public Float get(SkewView object) {
return object.getSkewX();
}
};
private float mSkewX;
// ...
public float getSkewX() {
return mSkewX;
}
public void setSkewX(float skewX) {
mSkewX = skewX;
invalidate();
}
#Override
protected void onDraw(Canvas canvas) {
if (mSkewX != 0) {
canvas.skew((float) (mSkewX * Math.PI / 180.0f), 0);
}
super.onDraw(canvas);
}
}
Used ObjectAnimator to animate this property.
ObjectAnimator skewAnimator = ObjectAnimator.ofFloat(mTarget, SkewView.SKEW_X, 30);
I know multiple ways to get location values of a View.
getLocationOnScreen()
getLocationInWindow()
getLeft()
However, none of them actually returns the current location of the View I moved by startAnimation() method, but only the original location.
So, now let's make a View that moves to the right by 10 pixels on each Click (I'm omitting the layout, since you can just place whatever view in your main XML and give it onClickListener).
public class AndroidTestActivity extends Activity implements OnClickListener {
LinearLayout testView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
testView = (LinearLayout) this.findViewById(R.id.test);
testView.setOnClickListener(this);
}
public void onClick(View v) {
int[] pLoS = new int[2];
testView.getLocationOnScreen(pLoS);
TranslateAnimation move = new TranslateAnimation(pLoS[0], pLoS[0] + 10, 0f, 0f);
move.setFillAfter(true);
move.setFillEnabled(true);
testView.startAnimation(move);
}
}
As you see, this doesn't work as I intended, since getLocationOnScreen() always returns the same value (in my case, 0), and doen't reflect the value I used in TranslateAnimation...
Any idea?
Assuming you're using Android < 3.0 then your question may be in a similar vein to mine I asked here. Basically Animations are separate from the View itself i.e. Android animates a copy of your View. That is why getLocationOnScreen() always returns 0. It's not the view that has moved (animated) it was the copy that moved (animated). If you see the answers to my question this issue has been addressed in later versions of Android.
Well, if you are trying to see how many pixels the view has shifted during/after its animation, then you can open up the Transformation object on the animation.
Here's an example using the AnimationListener:
animation.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
Transformation trans = new Transformation();
// startTime + duration = end of animation
int endTime = animation.getStartTime()+animation.getDuration();
animation.getTransformation(endTime, trans);
Matrix transformationMatrix = trans.getMatrix();
float[] matrixVals = new float[9];
transformationMatrix.getValues(matrixVals);
float xTraveled = matrixVals[2];
float yTraveled = matrixVals[5];
// do something with them here...
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
I may not be exactly correct in the array indices for these, definitely use Matrix.toString() and look at the values yourself. xTraveled and yTraveled will give you the amount of distance traveled by the TranslateAnimation at the indicated time (in this case, at the end of the animation).
animations on android gingerbread and below do not really change the view in any way , only change the way it is shown.
the only way to get the new position is by calculating it.
I need to scale View by setting the scale factor in percents. I need to set exactly the same param as ScaleAnimation use, because I'm going to scale it using ScaleAnimation in the future. How do I do it?
Thanks
I've solved the problem by creating animation xml file with duration=0, which sets desired parameters. It is also possible to create such animation via code. Then to apply it, just use:
Animation animationInit = AnimationUtils.loadAnimation(context, R.anim.gallery_init);
v.startAnimation(animationInit);
This will allow you to use straight up float values (percent) when handeling drawing and placements. Refer to: this lovely site which really helps with views.
public class MyView extends View {
...
#Override public void onDraw(Canvas canvas) {
canvas.save(Canvas.MATRIX_SAVE_FLAG);
canvas.scale((getWidth() > getHeight()) ? getHeight() : getWidth());
// Your other code
canvas.restore();
}
...
}
I have a button that I want to put on a 45 degree angle. For some reason I can't get this to work.
Can someone please provide the code to accomplish this?
API 11 added a setRotation() method to all views.
You could create an animation and apply it to your button view. For example:
// Locate view
ImageView diskView = (ImageView) findViewById(R.id.imageView3);
// Create an animation instance
Animation an = new RotateAnimation(0.0f, 360.0f, pivotX, pivotY);
// Set the animation's parameters
an.setDuration(10000); // duration in ms
an.setRepeatCount(0); // -1 = infinite repeated
an.setRepeatMode(Animation.REVERSE); // reverses each repeat
an.setFillAfter(true); // keep rotation after animation
// Aply animation to image view
diskView.setAnimation(an);
Extend the TextView class and override the onDraw() method. Make sure the parent view is large enough to handle the rotated button without clipping it.
#Override
protected void onDraw(Canvas canvas) {
canvas.save();
canvas.rotate(45,<appropriate x pivot value>,<appropriate y pivot value>);
super.onDraw(canvas);
canvas.restore();
}
I just used the simple line in my code and it works :
myCusstomView.setRotation(45);
Hope it works for you.
One line in XML
<View
android:rotation="45"
... />
Applying a rotation animation (without duration, thus no animation effect) is a simpler solution than either calling View.setRotation() or override View.onDraw method.
// substitude deltaDegrees for whatever you want
RotateAnimation rotate = new RotateAnimation(0f, deltaDegrees,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
// prevents View from restoring to original direction.
rotate.setFillAfter(true);
someButton.startAnimation(rotate);
Rotating view with rotate() will not affect your view's measured size. As result, rotated view be clipped or not fit into the parent layout. This library fixes it though:
https://github.com/rongi/rotate-layout
Joininig #Rudi's and #Pete's answers. I have created an RotateAnimation that keeps buttons functionality also after rotation.
setRotation() method preserves buttons functionality.
Code Sample:
Animation an = new RotateAnimation(0.0f, 180.0f, mainLayout.getWidth()/2, mainLayout.getHeight()/2);
an.setDuration(1000);
an.setRepeatCount(0);
an.setFillAfter(false); // DO NOT keep rotation after animation
an.setFillEnabled(true); // Make smooth ending of Animation
an.setAnimationListener(new AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {}
#Override
public void onAnimationRepeat(Animation animation) {}
#Override
public void onAnimationEnd(Animation animation) {
mainLayout.setRotation(180.0f); // Make instant rotation when Animation is finished
}
});
mainLayout.startAnimation(an);
mainLayout is a (LinearLayout) field
As mentioned before, the easiest way it to use rotation available since API 11:
android:rotation="90" // in XML layout
view.rotation = 90f // programatically
You can also change pivot of rotation, which is by default set to center of the view. This needs to be changed programatically:
// top left
view.pivotX = 0f
view.pivotY = 0f
// bottom right
view.pivotX = width.toFloat()
view.pivotY = height.toFloat()
...
In Activity's onCreate() or Fragment's onCreateView(...) width and height are equal to 0, because the view wasn't measured yet. You can access it simply by using doOnPreDraw extension from Android KTX, i.e.:
view.apply {
doOnPreDraw {
pivotX = width.toFloat()
pivotY = height.toFloat()
}
}
if you wish to make it dynamically with an animation:
view.animate()
.rotation(180)
.start();
THATS IT
#Ichorus's answer is correct for views, but if you want to draw rotated rectangles or text, you can do the following in your onDraw (or onDispatchDraw) callback for your view:
(note that theta is the angle from the x axis of the desired rotation, pivot is the Point that represents the point around which we want the rectangle to rotate, and horizontalRect is the rect's position "before" it was rotated)
canvas.save();
canvas.rotate(theta, pivot.x, pivot.y);
canvas.drawRect(horizontalRect, paint);
canvas.restore();
fun rotateArrow(view: View): Boolean {
return if (view.rotation == 0F) {
view.animate().setDuration(200).rotation(180F)
true
} else {
view.animate().setDuration(200).rotation(0F)
false
}
}
That's simple,
in Java
your_component.setRotation(15);
or
your_component.setRotation(295.18f);
in XML
<Button android:rotation="15" />