I am trying to put a GLSurfaceView and a button into the same xml layout. But my application keep closing automatically whenever I run it on my device.
Could someone please help me and tell what am I missing or what is wrong with my code?
here is my code
===================
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id= "#+id/linearlayout1" >
<Button
android:id="#+id/buttonID"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="10dip"
android:text="A Button" />
<com.example.test2.MyGLSurfaceView
android:id="#+id/glSurfaceViewID"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0.23" />
</LinearLayout>
================
package com.example.test2;
import android.opengl.GLSurfaceView;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.view.MotionEvent;
public class MainActivity extends Activity {
private GLSurfaceView mGLView;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Create a GLSurfaceView instance and set it
// as the ContentView for this Activity
mGLView = new MyGLSurfaceView(this);
//setContentView(mGLView);
setContentView(R.layout.activity_main);
}
// #Override
protected void onPause() {
super.onPause();
mGLView = (MyGLSurfaceView)findViewById(R.id.glSurfaceViewID);
mGLView.onPause();
}
//
#Override
protected void onResume() {
super.onResume();
mGLView = (MyGLSurfaceView)findViewById(R.id.glSurfaceViewID);
mGLView.onResume();
}
}
class MyGLSurfaceView extends GLSurfaceView {
private final MyGLRenderer mRenderer;
public MyGLSurfaceView(Context context) {
super(context);
// Create an OpenGL ES 2.0 context.
setEGLContextClientVersion(2);
// Set the Renderer for drawing on the GLSurfaceView
mRenderer = new MyGLRenderer();
setRenderer(mRenderer);
// Render the view only when there is a change in the drawing data
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
}
private final float TOUCH_SCALE_FACTOR = 180.0f / 320;
private float mPreviousX;
private float mPreviousY;
#Override
public boolean onTouchEvent(MotionEvent e) {
// MotionEvent reports input details from the touch screen
// and other input controls. In this case, you are only
// interested in events where the touch position changed.
float x = e.getX();
float y = e.getY();
switch (e.getAction()) {
case MotionEvent.ACTION_MOVE:
float dx = x - mPreviousX;
float dy = y - mPreviousY;
// reverse direction of rotation above the mid-line
if (y > getHeight() / 2) {
dx = dx * -1 ;
}
// reverse direction of rotation to left of the mid-line
if (x < getWidth() / 2) {
dy = dy * -1 ;
}
mRenderer.mAngle += (dx + dy) * TOUCH_SCALE_FACTOR; // = 180.0f / 320
requestRender();
}
mPreviousX = x;
mPreviousY = y;
return true;
}
}
===========================
Thank you.
In MainActivity.onCreate() use findViewById instead of creating a new View.(like u did in onPause, onResume) and only assign the variable once.
setContentView(R.layout.activity_main);
mGLView = (MyGLSurfaceView) findViewById(R.id.glSurfaceViewID);
Make sure MyGLSurfaceView has constructors with AttributeSet so it could be inflated from XML. Check out this stackoverflow atricle for the constructors.
And aswell change the order of the Button and MyGLSurfaceView in the XML, because now Button will be below the MyGLSurfaceView in your layout so you won't be able to see it.
This is a 7 year old QA, this is how you do it now with Kotlin:
import android.content.Context
import android.opengl.GLES20
import android.opengl.GLSurfaceView
import android.util.AttributeSet
import javax.microedition.khronos.egl.EGLConfig
import javax.microedition.khronos.opengles.GL10
class GameView
#JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : GLSurfaceView(context, attrs) {
companion object{
// Support OpenGL ES 2.0
private const val OPEN_GLES_VERSION = 2
}
private val renderer: GameRenderer
init {
//create an OpenGL ES 2.0-compatible context.
setEGLContextClientVersion(OPEN_GLES_VERSION)
// Set the Renderer for drawing on the GLSurfaceView
renderer = GameRenderer().apply {
setRenderer(this)
}
}
inner class GameRenderer : Renderer {
override fun onSurfaceCreated(unused: GL10, config: EGLConfig) {
// Set the background frame color
GLES20.glClearColor(1.0f, 1.0f, 0.0f, 1.0f)
}
override fun onDrawFrame(unused: GL10) {
// Redraw background color
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT)
}
override fun onSurfaceChanged(unused: GL10, width: Int, height: Int) {
GLES20.glViewport(0, 0, width, height)
}
}
}
In XML
<com.hiteshsahu.opengl.boilerplate.GameView
android:id="#+id/glView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
Related
Im new in Android world. I want to put some parallax background effects in my app.
How can I do it? How to approach to this in Android?
Is there any productive way to create 2-3 layer parallax background? Is there some tool, or class in android API?
Or maybe I have to modify background image location or margins "manually" in code?
Im using API level 19.
I have tried to understand Paralloid library, but this is too big to understand without any explanation. Im new to Android and Java, im not familiar with all Layouts and other UI objects, however I'm familiar with MVC.
I started bounty, maybe someone can explain step by step how that library works.
This is what you can do:
In your activity/fragment layout file specify 2 ScrollView's (say background_sv and content_sv).
<?xml version="1.0" encoding="utf-8"?>
<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" >
<com.example.parallax.MyScrollView
android:id="#+id/background_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/parallax_bg"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="..." />
</com.example.parallax.MyScrollView>
<com.example.parallax.MyScrollView
android:id="#+id/content_sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" >
</LinearLayout>
</com.example.parallax.MyScrollView>
</RelativeLayout>
Add a dummy view in the content scrollview of the height of the background and make it transparent. Now, attach a scroll listener to the content_sv. When the content scrollview is scrolled, call
mBgScrollView.scrollTo(0, (int)(y /*scroll Of content_sv*/ / 2f));
The existing API's doesn't have the support to get the scroll events.
Hence, we need to create a Custom ScrollView, to provide the ScrollViewListener.
package com.example.parallax;
// imports;
public class MyScrollView extends ScrollView {
public interface ScrollViewListener {
void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy);
}
private ScrollViewListener scrollViewListener = null;
public MyScrollView(Context context) {
super(context);
}
public MyScrollView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public MyScrollView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public void setScrollViewListener(ScrollViewListener scrollViewListener) {
this.scrollViewListener = scrollViewListener;
}
#Override
protected void onScrollChanged(int x, int y, int oldx, int oldy) {
super.onScrollChanged(x, y, oldx, oldy);
if(scrollViewListener != null) {
scrollViewListener.onScrollChanged(this, x, y, oldx, oldy);
}
}
}
Here is the activity which hosts both the content ScrollView and background ScrollView
package com.example.parallax;
// imports;
public class ParallaxActivity extends Activity implements ScrollViewListener {
private MyScrollView mBgScrollView;
private MyScrollView mContentScrollView;
#Override
public void onCreate(Bundle savedInstanceState) {
mBgScrollView = findViewById(R.id.background_sv);
mContentScrollView = findViewById(R.id.content_sv);
mContentScrollView.setOnScrollListener(this);
}
// this is method for onScrollListener put values according to your need
#Override
public void onScrollChanged(MyScrollView scrollView, int x, int y, int oldx, int oldy) {
super.onScrollChanged(scrollView, x, y, oldx, oldy);
// when the content scrollview will scroll by say 100px,
// the background scrollview will scroll by 50px. It will
// look like a parallax effect where the background is
// scrolling with a different speed then the content scrollview.
mBgScrollView.scrollTo(0, (int)(y / 2f));
}
}
I think the question is unclear, so this is not really an answer so much as an attempt to clarify with more detail than I could include in a comment.
My question is about what kind of parallax effect you want to achieve. Given these three examples (they are demo apps you can install from the Play Store), which if any has the type of parallax effect you want? Please answer in a comment.
Paralloid Demo
Parallax Scroll Demo
Google IO App
Given an answer, we all will find it easier to help out. If you edit your question to include this information, it will be improved.
The following contains an example application published by the author of Paralloid:
https://github.com/chrisjenx/Paralloid/tree/master/paralloidexample
From the GitHub page under the 'Getting Started' section:
Layout
ScrollView
This is an example, please refer to the paralloidexample App for full
code.
<FrameLayout ..>
<FrameLayout
android:id="#+id/top_content"
android:layout_width="match_parent"
android:layout_height="192dp"/>
<uk.co.chrisjenx.paralloid.views.ParallaxScrollView
android:id="#+id/scroll_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true">
<LinearLayout
android:id="#+id/scroll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="192dp"/>
</uk.co.chrisjenx.paralloid.views.ParallaxScrollView>
</FrameLayout>
Fragment
Inside your onViewCreated() or onCreateView().
//...
FrameLayout topContent = (FrameLayout) rootView.findViewById(R.id.top_content);
ScrollView scrollView = (ScrollView) rootView.findViewById(R.id.scroll_view);
if (scrollView instanceof Parallaxor) {
((Parallaxor) scrollView).parallaxViewBy(topContent, 0.5f);
}
// TODO: add content to top/scroll content
Thats it!
Have a look at the Parallaxor interface for applicable Parallax
methods.
Hope this helps!
Also, here is a link to Google's 'getting started' page for android.
Also, here is a link to a 'java tutorial for complete beginners'.
As well as link to some documentation about layouts, which 'define the visual structure for a user interface'.
That being said, you would use the layout to define what the interface looks like and use the subsequent example code to define what happens when you interact with it.
P.S. You can see the application in action here
I use the ParallaxScroll library. Very easy to use, good samples and well documented.
Here is how it can be done using ScrollView and it's background image. I've committed the code in github.
You need to extend the ScrollView and Drawable classes.
By default the ScrollView background height will be same as viewport height. To achieve the parallax effect, the background height should be larger and should be based on the ScrollView child height and the background scrolling factor we want to impose.
Background scroll factor of 1 indicates, background height is same as ScrollView child height and hence background will scroll with same offset as the child scrolls.
0.5 indicates, background height is 0.5 times ScrollView child extended height and will scroll 50% slower compared to the child contents. This effectively brings the parallax scrolling effect.
Call following method from ScrollView constructor:
void init() {
// Calculate background drawable size before first draw of scrollview
getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
// Remove the listener
getViewTreeObserver().removeOnPreDrawListener(this);
mDrawable = (ParallaxDrawable) getBackground();
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// Get the only child of scrollview
View child = getChildAt(0);
int width = child.getWidth();
// calculate height of background based on child height and scroll factor
int height = (int) (getHeight() + (child.getHeight() - getHeight()) * mScrollFactor);
mDrawable.setSize(width, height);
}
return true;
}
});
}
When ScrollView is scrolled, take into consideration the scroll offset while drawing the background. This basically achieves the parallax effect.
ParallaxScrollView:
protected void onScrollChanged(int x, int y, int oldX, int oldY) {
if(mDrawable != null && mDrawable instanceof ParallaxDrawable) {
// set the scroll offset for the background drawable.
mDrawable.setScrollOffset(x*mScrollFactor, y*mScrollFactor);
}
}
ParallaxDrawable:
#Override
public void draw(Canvas canvas) {
// To move the background up, translate canvas by negative offset
canvas.translate(-mScrollXOffset, -mScrollYOffset);
mDrawable.draw(canvas);
canvas.translate(mScrollXOffset, mScrollYOffset);
}
protected void onBoundsChange(Rect bounds) {
// This sets the size of background drawable.
mDrawable.setBounds(new Rect(bounds.top, bounds.left, bounds.left + mWidth, bounds.top + mHeight));
}
Usage of ParallaxScrollView and ParallaxDrawable:
public class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.parallax_layout);
final ParallaxScrollView scrollView = (ParallaxScrollView) findViewById(R.id.sv);
ParallaxDrawable drawable = new ParallaxDrawable(getResources().getDrawable(R.drawable.bg));
scrollView.setBackground( drawable, 0.2f );
}
}
parallax_layout.xml:
<manish.com.parallax.ParallaxScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/sv"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#fff"
android:text="#string/text" />
<View
android:layout_width="match_parent"
android:layout_height="5dp" />
...
</LinearLayout>
</manish.com.parallax.ParallaxScrollView>
The Android API does not support much concrete tools for it as you probably noticed. In API 20 they added elevation which is an attribute for depth. This does not support parallax layouts itself but I would say it's a step by Google to make this kind of work easier. If you want a wild guess on if and when, I would say that parallax utilities could be added before API 25 is released, based on the latest update and the progress in battery efficiency.
For now all you need is to listen for some kind of movement and change the views positions based on a value representing elevation.
Your question made me upgrade my own project and this is how I did it using ViewDragHelper inside a Fragment.
public class MainFragment extends Fragment implements View.OnTouchListener {
private ImageView mDecor, mBamboo, mBackgroundBamboo;
private RelativeLayout mRootLayout;
private ViewDragHelper mDragHelper;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mRootLayout = (RelativeLayout) inflater.inflate(R.layout.fragment_main, container, false);
mRootLayout.setOnTouchListener(this);
mDecor = (ImageView) mRootLayout.findViewById(R.id.decor);
mBamboo = (ImageView) mRootLayout.findViewById(R.id.bamboo);
mBackgroundBamboo = (ImageView) mRootLayout.findViewById(R.id.backround_bamboo);
mDragHelper = ViewDragHelper.create(mRootLayout, 1.0f, new ViewDragHelper.Callback() {
private final float MAX_LEFT = -0;
private final float MAX_TOP = -20;
private final float MAX_RIGHT = 50;
private final float MAX_BOTTOM = 10;
private final float MULTIPLIER = 0.1f;
private final int DECOR_ELEVATION = 3;
private final int FRONT_BAMBOO_ELEVATION = 6;
private final int BACKGROUND_BAMBOO_ELEVATION = 1;
private float mLeft = 0;
private float mTop = 0;
#Override
public boolean tryCaptureView(View view, int i) {
return true;
}
#Override
public int clampViewPositionVertical(View child, int top, int dy) {
mTop += dy * MULTIPLIER;
mTop = mTop > MAX_BOTTOM ? MAX_BOTTOM : mTop < MAX_TOP ? MAX_TOP : mTop;
mDecor.setTranslationY(mTop * DECOR_ELEVATION);
mBamboo.setTranslationY(mTop * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationY(mTop * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
mLeft += dx * MULTIPLIER;
mLeft = mLeft < MAX_LEFT ? MAX_LEFT : mLeft > MAX_RIGHT ? MAX_RIGHT : mLeft;
mDecor.setTranslationX(mLeft * DECOR_ELEVATION);
mBamboo.setTranslationX(mLeft * FRONT_BAMBOO_ELEVATION);
mBackgroundBamboo.setTranslationX(mLeft * BACKGROUND_BAMBOO_ELEVATION);
return 0;
}
#Override
public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy){
mRootLayout.requestLayout();
}
});
return mRootLayout;
}
#Override
public boolean onTouch(View view, MotionEvent motionEvent) {
mDragHelper.processTouchEvent(motionEvent);
// you can still use this touch listener for buttons etc.
return true;
}
}
Hi You can go with the below-given code for ParallaxView class
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import java.util.ArrayList;
public class ParallaxView extends SurfaceView implements Runnable {
private volatile boolean running;
private Thread gameThread = null;
// For drawing
private Paint paint;
private Canvas canvas;
private SurfaceHolder ourHolder;
// Holds a reference to the Activity
Context context;
// Control the fps
long fps =60;
// Screen resolution
int screenWidth;
int screenHeight;
ParallaxView(Context context, int screenWidth, int screenHeight) {
super(context);
this.context = context;
this.screenWidth = screenWidth;
this.screenHeight = screenHeight;
// Initialize our drawing objects
ourHolder = getHolder();
paint = new Paint();
}
#Override
public void run() {
while (running) {
long startFrameTime = System.currentTimeMillis();
update();
draw();
// Calculate the fps this frame
long timeThisFrame = System.currentTimeMillis() - startFrameTime;
if (timeThisFrame >= 1) {
fps = 1000 / timeThisFrame;
}
}
}
private void update() {
// Update all the background positions
}
private void draw() {
if (ourHolder.getSurface().isValid()) {
//First we lock the area of memory we will be drawing to
canvas = ourHolder.lockCanvas();
//draw a background color
canvas.drawColor(Color.argb(255, 0, 3, 70));
// Draw the background parallax
// Draw the rest of the game
paint.setTextSize(60);
paint.setColor(Color.argb(255, 255, 255, 255));
canvas.drawText("I am a plane", 350, screenHeight / 100 * 5, paint);
paint.setTextSize(220);
canvas.drawText("I'm a train", 50, screenHeight / 100*80, paint);
// Draw the foreground parallax
// Unlock and draw the scene
ourHolder.unlockCanvasAndPost(canvas);
}
}
// Clean up our thread if the game is stopped
public void pause() {
running = false;
try {
gameThread.join();
} catch (InterruptedException e) {
// Error
}
}
// Make a new thread and start it
// Execution moves to our run method
public void resume() {
running = true;
gameThread = new Thread(this);
gameThread.start();
}
}// End of ParallaxView
To know more you can go **
here
**: http://gamecodeschool.com/android/coding-a-parallax-scrolling-background-for-android/
We all know this article of how to create "card filp" animations using new api.
But how can I make this on apis < 3.0?
Update:
As long as there are good and easy-to-use libraries like android-FlipView I don't think you really need to do such animation by yourself.
Found the answer. If you want to do flip animation on ALL ANDROID VERSIONS, use this:
Activity layout file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/main_activity_root"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#android:color/transparent" >
<RelativeLayout
android:id="#+id/main_activity_card_face"
android:layout_width="300dp"
android:layout_height="407dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="#drawable/front"
android:clickable="true"
android:onClick="onCardClick"
android:padding="5dp" >
</RelativeLayout>
<RelativeLayout
android:id="#+id/main_activity_card_back"
android:layout_width="300dp"
android:layout_height="407dp"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:background="#drawable/back"
android:clickable="true"
android:onClick="onCardClick"
android:visibility="gone" >
</RelativeLayout>
</RelativeLayout>
As the layout file flips two view groups you could put anything else inside the view group and it should work. Now lets look at the methods inside the activity which handles calling the flip animation code:
public void onCardClick(View view)
{
flipCard();
}
private void flipCard()
{
View rootLayout = findViewById(R.id.main_activity_root);
View cardFace = findViewById(R.id.main_activity_card_face);
View cardBack = findViewById(R.id.main_activity_card_back);
FlipAnimation flipAnimation = new FlipAnimation(cardFace, cardBack);
if (cardFace.getVisibility() == View.GONE)
{
flipAnimation.reverse();
}
rootLayout.startAnimation(flipAnimation);
}
And finally the FlipAnimation class:
public class FlipAnimation extends Animation
{
private Camera camera;
private View fromView;
private View toView;
private float centerX;
private float centerY;
private boolean forward = true;
/**
* Creates a 3D flip animation between two views.
*
* #param fromView First view in the transition.
* #param toView Second view in the transition.
*/
public FlipAnimation(View fromView, View toView)
{
this.fromView = fromView;
this.toView = toView;
setDuration(700);
setFillAfter(false);
setInterpolator(new AccelerateDecelerateInterpolator());
}
public void reverse()
{
forward = false;
View switchView = toView;
toView = fromView;
fromView = switchView;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight)
{
super.initialize(width, height, parentWidth, parentHeight);
centerX = width/2;
centerY = height/2;
camera = new Camera();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t)
{
// Angle around the y-axis of the rotation at the given time
// calculated both in radians and degrees.
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
// Once we reach the midpoint in the animation, we need to hide the
// source view and show the destination view. We also need to change
// the angle by 180 degrees so that the destination does not come in
// flipped around
if (interpolatedTime >= 0.5f)
{
degrees -= 180.f;
fromView.setVisibility(View.GONE);
toView.setVisibility(View.VISIBLE);
}
if (forward)
degrees = -degrees; //determines direction of rotation when flip begins
final Matrix matrix = t.getMatrix();
camera.save();
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
Here is the link for original post:
Displaying card flip animation on old android
UPDATE from #FMMobileFelipeMenezes .
if you want the animation with a smooth scale to flip, change this part of code to (applyTransformation) :
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0, 0, Math.abs(degrees)*2);
camera.getMatrix(matrix);
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
UPDATE from #Hesam
There is good tutorial that I recommend to read it. Although it's not as nice as Android tutorial based on fragments, it's worth to be read and useful if you want to assign animation to layouts and views as well as have it on old APIs.
Use Android's scale animation to simulate a 3D flip
Improved project on github by #LenaBru
I used Flextra code below, and if you want the animation with a smooth scale to flip, change this part of code to (applyTransformation) :
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0, 0, Math.abs(degrees)*2);
camera.getMatrix(matrix);
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
I played with this all day, and finally achieved the ultimate goal - a smooth cardflip like rotation animation of two views!
I put demo project here
public class FlipAnimation extends Animation {
private Camera camera;
private View fromView;
private View toView;
private float centerX;
private float centerY;
private boolean forward = true;
/**
* Creates a 3D flip animation between two views.
*
* #param fromView
* First view in the transition.
* #param toView
* Second view in the transition.
*/
public FlipAnimation(View fromView, View toView) {
this.fromView = fromView;
this.toView = toView;
setDuration(1500);
setFillAfter(false);
// setInterpolator(new AccelerateDecelerateInterpolator());
setInterpolator(new LinearInterpolator());
}
public void reverse() {
if (forward) {
View switchView = toView;
toView = fromView;
fromView = switchView;
}
forward = false;
}
#Override
public void initialize(int width, int height, int parentWidth, int parentHeight) {
super.initialize(width, height, parentWidth, parentHeight);
centerX = width / 2;
centerY = height / 2;
camera = new Camera();
}
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
// Angle around the y-axis of the rotation at the given time
// calculated both in radians and degrees.
final double radians = Math.PI * interpolatedTime;
float degrees = (float) (180.0 * radians / Math.PI);
//scale down the views a bit, so that they would look nice when the rotation begins
if (interpolatedTime <= 0.05f) {
fromView.setScaleX(1 - interpolatedTime);
fromView.setScaleY(1 - interpolatedTime);
toView.setScaleX(1 - interpolatedTime);
toView.setScaleY(1 - interpolatedTime);
}
// Once we reach the midpoint in the animation, we need to hide the
// source view and show the destination view. We also need to change
// the angle by 180 degrees so that the destination does not come in
//It is very important to call "toView.bringToFront()" and not play with the
// visibility of the views, because if you apply this animation more than once,
//the subsequent calls may fail
if (interpolatedTime >= 0.5f) {
degrees -= 180.f;
toView.bringToFront();
//these two lines force a layout redraw
((View)toView.getParent()).requestLayout();
((View)toView.getParent()).invalidate();
}
//scale the views back to their original size (Assuming original size was 1)
if (interpolatedTime >= 0.95f) {
fromView.setScaleX(interpolatedTime);
fromView.setScaleY(interpolatedTime);
toView.setScaleX(interpolatedTime);
toView.setScaleY(interpolatedTime);
}
if (forward)
degrees = -degrees; // determines direction of rotation when flip
// begins
final Matrix matrix = t.getMatrix();
camera.save();
camera.translate(0, 0, Math.abs(degrees) * 2);
camera.getMatrix(matrix);
camera.rotateY(degrees);
camera.getMatrix(matrix);
camera.restore();
matrix.preTranslate(-centerX, -centerY);
matrix.postTranslate(centerX, centerY);
}
}
and call it like this
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.FragmentActivity;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Toast;
public class MainActivity extends FragmentActivity {
private boolean showingBack;
private FragmentLeft left = new FragmentLeft();
private FragmentRight right = new FragmentRight();
private Context context;
private Handler handler;
private FlipAnimation flipAnimation;
private FlipAnimation backFlip;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
handler = new Handler(getMainLooper());
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, right, "fragmentRight").commit();
getSupportFragmentManager().beginTransaction().add(R.id.fragment_container, left, "fragmentLeft").commit();
findViewById(R.id.flip).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
flipAnimation = new FlipAnimation(left.getView(), right.getView());
backFlip = new FlipAnimation(left.getView(), right.getView());
handler.removeCallbacks(rotate);
handler.postDelayed(rotate, 100);
}
});
}
private Runnable rotate = new Runnable() {
#Override
public void run() {
//put a variable showingBack, do not rely on view properties to flip
if (!showingBack) {
//very important to flip both views, so that when the
//left view goes to back and right view goes to front,
//the right view finishes the rotation
left.getView().startAnimation(flipAnimation);
right.getView().startAnimation(flipAnimation);
Toast.makeText(context, "flip", Toast.LENGTH_LONG).show();
showingBack = true;
} else {
showingBack = false;
backFlip.reverse();
Toast.makeText(context, "backflip", Toast.LENGTH_LONG).show();
//very important to flip both views, so that when the
//right view goes to back and right view goes to front,
//the left view finishes the rotation
left.getView().startAnimation(backFlip);
right.getView().startAnimation(backFlip);
}
}
};
}
These are the fragments
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FragmentRight extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_right, container,false);
}
}
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class FragmentLeft extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_left, container,false);
}
}
and finally the view itself
activity_main.xml
<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"
android:background="#ff151515"
tools:context="com.example.flipviewtest.MainActivity" >
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/hello_world" />
<FrameLayout
android:id="#+id/fragment_container"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_centerInParent="true" >
</FrameLayout>
<Button
android:id="#+id/flip"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="flip" />
</RelativeLayout>
fragment_left.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="#ffff0000"
>
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff0ffff0"
android:layout_margin="20dp" />
</LinearLayout>
fragment_right.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ff00ff00"
android:orientation="vertical" >
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp"
android:background="#ff0000ff" />
</LinearLayout>
note some of the code taken from Flextra's and #FMMobileFelipeMenezes answers
There is good tutorial that I recommend to read it. Although it's not as nice as Android tutorial based on fragments, it's worth to be read and useful if you want to assign animation to layouts and views as well as have it on old APIs.
Use Android's scale animation to simulate a 3D flip
I want to add an animated dot that has an oscillating type animation (this will actually be drawn on top of an image, if that makes any difference).
Here's a sample of what I mean:
Could this somehow be done with two images and an animation between them? I'm not too clear on that, so some sample code would be nice. (or a link to a tutorial).
Cheers and thanks in advance.
I'm not sure about Your exact requirements, but for me it looks like You need smth like extending 'ring' above the circle. I've tried to implement it using custom ViewGroup in order to have all that functionality encapsulated in some 'container'. Steps are below:
1) Add values/attrs.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="OscillatorAnimatedView">
<attr name="centerImage" format="reference" />
<attr name="oscillatorImage" format="reference" />
<attr name="oscillatorInterval" format="integer" />
<attr name="oscillatorMaxExtend" format="float" />
</declare-styleable>
</resources>
2) Add view to You layout file:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res/com.alexstarc.tests"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.alexstarc.tests.views.OscillatorAnimatedView
android:id="#+id/oscillator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
custom:centerImage="#drawable/center"
custom:oscillatorImage="#drawable/circle" />
</RelativeLayout>
3) Add images for center and circle to Your drawables (below is just random examples from the internet, note thayt it should be png with transparency):
drawable/center
drawable/circle
4) Create Your view (in my case it's com.alexstarc.tests.views.OscillatorAnimatedView):
package com.ruinalst.performance.tests.views;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.view.ViewGroup;
import android.view.animation.BounceInterpolator;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import com.ruinalst.performance.tests.R;
/**
* Specific view to provide 'oscilllator' kind of animation using two input views
*/
public final class OscillatorAnimatedView extends RelativeLayout {
/* Internal constants, mostly for default values */
/** default oscillator interval */
private static final int DEFAULT_INTERVAL = 700;
/** default oscillator extend */
private static final float DEFAULT_EXTEND = 1.5f;
/** Image to be displayed at the center */
private ImageView mCenterImage = null;
/** Image to oscillate */
private ImageView mOscillatorImage = null;
/** Oscillator animation */
private AnimatorSet mAnimatorSet = null;
public OscillatorAnimatedView(final Context context, final AttributeSet attrs) {
super(context, attrs);
initAndCompose(attrs);
}
public OscillatorAnimatedView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
initAndCompose(attrs);
}
/**
* Internal init function to init all additional data
* and compose child for this ViewGroup
*
* #param attrs {#link AttributeSet} with data from xml attributes
*/
private void initAndCompose(final AttributeSet attrs) {
if (null == attrs) {
throw new IllegalArgumentException("Attributes should be provided to this view," +
" at least centerImage and oscillatorImage should be specified");
}
final TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.OscillatorAnimatedView, 0, 0);
final Drawable centerDrawable = a.getDrawable(R.styleable.OscillatorAnimatedView_centerImage);
final Drawable oscillatorDrawable = a.getDrawable(R.styleable.OscillatorAnimatedView_oscillatorImage);
if (null == centerDrawable || null == oscillatorDrawable) {
throw new IllegalArgumentException("Attributes should be provided to this view," +
" at least centerImage and oscillatorImage should be specified");
}
final int oscillatorInterval = a.getInt(R.styleable.OscillatorAnimatedView_oscillatorInterval, DEFAULT_INTERVAL);
final float maxOscillatorExtend = a.getFloat(R.styleable.OscillatorAnimatedView_oscillatorMaxExtend, DEFAULT_EXTEND);
a.recycle();
// Create child and add them into this view group
mCenterImage = new ImageView(getContext());
mCenterImage.setImageDrawable(centerDrawable);
addInternalChild(mCenterImage);
mOscillatorImage = new ImageView(getContext());
mOscillatorImage.setImageDrawable(oscillatorDrawable);
addInternalChild(mOscillatorImage);
// Init animation
mAnimatorSet = new AnimatorSet();
mAnimatorSet.setDuration(oscillatorInterval);
final ObjectAnimator scaleXAnimator = ObjectAnimator.ofFloat(mOscillatorImage, "ScaleX", 1.0f, maxOscillatorExtend);
scaleXAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleXAnimator.setRepeatMode(ObjectAnimator.INFINITE);
final ObjectAnimator scaleYAnimator = ObjectAnimator.ofFloat(mOscillatorImage, "ScaleY", 1.0f, maxOscillatorExtend);
scaleYAnimator.setRepeatCount(ObjectAnimator.INFINITE);
scaleYAnimator.setRepeatMode(ObjectAnimator.INFINITE);
mAnimatorSet.playTogether(scaleXAnimator, scaleYAnimator);
mAnimatorSet.setInterpolator(new BounceInterpolator());
}
/**
* Internal helper to add child view to this ViewGroup.
* Used currently only for two internal ImageViews
*
* #param child {#link ImageView} to be added
*/
private void addInternalChild(final ImageView child) {
final LayoutParams params = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(CENTER_IN_PARENT, 1);
addView(child, params);
}
/**
* Starts animation for this view
*/
public void start() {
mAnimatorSet.start();
}
/**
* Stops animation for this view
*/
public void stop() {
mAnimatorSet.end();
}
}
5) In Your activity do smth like:
#Override
protected void onResume() {
super.onResume();
mOscillatorView.start();
}
#Override
protected void onPause() {
super.onPause();
mOscillatorView.stop();
}
Please note, that it's not release version and it most probably can be improved in many ways.
You can also play with interpolators or create Your own in order to have expected animation.
Are you looking for a Drawable Animation? Seems like that would do what you're looking for. You can use a RelativeLayout to position it on top of your other views.
Also, if you need more complex animation, you could use a SurfaceView and paint the canvas to your linking.
Override the onDraw method and draw a first circle for your button, also, create a boolean variable to control when your button is going to pulse and when is not.Finally draw your second circle with an alpha as a background. Making a pulsating effect:
#Override
protected void onDraw(Canvas canvas) {
int w = getMeasuredWidth();
int h = getMeasuredHeight();
//Draw circle
canvas.drawCircle(w/2, h/2, MIN_RADIUS_VALUE , mCirclePaint);
if (mAnimationOn) {
if (mRadius >= MAX_RADIUS_VALUE)
mPaintGoBack = true;
else if(mRadius <= MIN_RADIUS_VALUE)
mPaintGoBack = false;
//Draw pulsating shadow
canvas.drawCircle(w / 2, h / 2, mRadius, mBackgroundPaint);
mRadius = mPaintGoBack ? (mRadius - 0.5f) : (mRadius + 0.5f);
invalidate();
}
super.onDraw(canvas);
}
public void animateButton(boolean animate){
if (!animate)
mRadius = MIN_RADIUS_VALUE;
mAnimationOn = animate;
invalidate();
}
I've written a custom view, with the OpenGL_1 technology, in order to let user rotate a red triangle just by dragging it along x axis. (Will give a rotation around Y axis). It works, but there is a bit of latency when dragging from one direction to the other (without releasing the mouse/finger). So it seems that my code is not yet "goal perfect". (I am convinced that no code is perfect in itself).
I thought of using a quaternion, but maybe it won't be so usefull : must I really use a Quaternion (or a kind of Matrix) ?
I've designed application for Android 4.0.3, but it could fit into Android api 3 (Android 1.5) as well (at least, I think it could).
So here is my main layout :
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<com.laurent_bernabe.android.triangletournant3d.MyOpenGLView
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>
Here is my main activity :
MainActivity.java
package com.laurent_bernabe.android.triangletournant3d;
import android.app.Activity;
import android.os.Bundle;
import android.support.v4.app.NavUtils;
import android.view.Menu;
import android.view.MenuItem;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.activity_main, menu);
return true;
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
}
And finally, my OpenGL view
MyOpenGLView.java
package com.laurent_bernabe.android.triangletournant3d;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.opengl.GLU;
import android.util.AttributeSet;
import android.view.MotionEvent;
public class MyOpenGLView extends GLSurfaceView implements Renderer {
public MyOpenGLView(Context context, AttributeSet attrs) {
super(context, attrs);
setRenderer(this);
}
public MyOpenGLView(Context context) {
this(context, null);
}
#Override
public boolean onTouchEvent(MotionEvent event) {
int actionMasked = event.getActionMasked();
switch(actionMasked){
case MotionEvent.ACTION_DOWN:
savedClickLocationX = (int) event.getX();
break;
case MotionEvent.ACTION_UP:
savedClickLocationX = null;
break;
case MotionEvent.ACTION_MOVE:
Integer newClickLocationX = (int) event.getX();
int dx = newClickLocationX - savedClickLocationX;
angle += dx / 180.0f * 3.14159265f;
break;
}
return true;
}
#Override
public void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
gl.glLoadIdentity();
GLU.gluLookAt(gl,
0f, 0f, 5f,
0f, 0f, 0f,
0f, 1f, 0f
);
gl.glRotatef(angle, 0f, 1f, 0f);
gl.glColor4f(1f, 0f, 0f, 0f);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, triangleCoordsBuff);
gl.glDrawArrays(GL10.GL_TRIANGLES, 0, 3);
}
#Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
GLU.gluPerspective(gl, 60f, (float) width / height, 0.1f, 10f);
gl.glMatrixMode(GL10.GL_MODELVIEW);
}
#Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glEnable(GL10.GL_DEPTH_TEST);
gl.glClearDepthf(1.0f);
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);
buildTriangleCoordsBuffer();
}
private void buildTriangleCoordsBuffer() {
ByteBuffer buffer = ByteBuffer.allocateDirect(4*triangleCoords.length);
buffer.order(ByteOrder.nativeOrder());
triangleCoordsBuff = buffer.asFloatBuffer();
triangleCoordsBuff.put(triangleCoords);
triangleCoordsBuff.rewind();
}
private float [] triangleCoords = {-1f, -1f,
+1f, -1f,
+1f, +1f};
private FloatBuffer triangleCoordsBuff;
private float angle = 0f;
private Integer savedClickLocationX;
}
(Modified thanks to Mohamed Abdallah).
I don't think I really have to give you my manifest file. But I can if you think it is necessary.
I've just tested on Emulator and on real device.
My configuration
Android SDK 20.0.3
Eclipse Juno
Ubuntu 12.10 64 bits
OpenGL driver installed (from Canonical).
So, how can improve the reactivity ?
Thanks in advance.
Emulators should not be your reference for performance (especially in OpenGL) as it is very slow (it gives you an indication but not the real performance). Test it on a real device (low end if you want to feel the worst user experience).
Also, I read in a book before that calls to Math class introduce some delay. So you can enhance the code (if you need after testing on a real device) by manually converting degrees to Radians without the call to Math class (just divide by 180 & multiply by Pi)
I need to create a circle that rotates and contains data for my application. Should I create a customized object for my application or should I make a in-application widget?
While on the topic, how do you refer to a widget within an application instead of a stand alone widget for the android desktop?
This is a rotatable LinearLayout that you can put everything in it and you can rotate it by degree if you customize it. use rotate() method to rotate it and...
enjoy! ;)
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.LinearLayout;
public class RotateLinearLayout extends LinearLayout {
private Matrix mForward = new Matrix();
private Matrix mReverse = new Matrix();
private float[] mTemp = new float[2];
private float degree = 0;
public RotateLinearLayout(Context context) {
super(context);
}
public RotateLinearLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void dispatchDraw(Canvas canvas) {
try {
if (degree == 0) {
super.dispatchDraw(canvas);
return;
}
canvas.rotate(degree, getWidth() / 2, getHeight() / 2);
mForward = canvas.getMatrix();
mForward.invert(mReverse);
canvas.save();
canvas.setMatrix(mForward); // This is the matrix we need to use for
// proper positioning of touch events
super.dispatchDraw(canvas);
canvas.restore();
invalidate();
} catch (Exception e) {
}
}
#Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (degree == 0) {
return super.dispatchTouchEvent(event);
}
// final float[] temp = mTemp;
// temp[0] = event.getX();
// temp[1] = event.getY();
// mReverse.mapPoints(temp);
// event.setLocation(temp[0], temp[1]);
event.setLocation(getWidth() - event.getX(), getHeight() - event.getY());
return super.dispatchTouchEvent(event);
}
public void rotate() {
if (degree == 0) {
degree = 180;
} else {
degree = 0;
}
}
}
Update:
add this code to your xml layout and put your Views like ImageView or another LinearLayout in it :
<org.mabna.order.ui.RotateLinearLayout android:id="#+id/llParent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_gravity="center"
android:gravity="center"
android:orientation="horizontal" >
<ImageView
android:id="#+id/myImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dip"
android:src="#drawable/main01" />
</org.mabna.order.ui.RotateLinearLayout>
in onCreate() method:
llParent = (RotateLinearLayout) this.findViewById(R.id.llParent);
in onClickListener of a button:
protected void btnRotate_onClick() {
llParent.rotate();
}
Update2:
You can use an animation for rotation before real rotation (llParent.rotate();). it needs an animation layout like rotate_dialog.xml:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000" android:fromDegrees="-180" android:toDegrees="0"
android:pivotX="50%" android:pivotY="50%" android:fillAfter="true" />
and in your code:
protected void btnRotate_onClick() {
// rotate
Animation rotateAnimation = AnimationUtils.loadAnimation(this,
R.anim.rotate_dialog);
llParent.startAnimation(rotateAnimation);
llParent.rotate();
}
There is a fairly easy way to make a rotating animation from a custom widget derived from the View class. After the view is created and placed in your layout, you can call View.setAnimation(Animation) or View.startAnimation(Animation), supplying a RotateAnimation on the view to start it. Here is an example of a rotation animation defined in xml, that can be loaded from your activity with getResources().getAnimation(int).
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="float"
android:toDegrees="float"
android:pivotX="float"
android:pivotY="float" />