13

In my app I am using Rotate3dAnimation to show a Google map. The code is working fine, but the animation is not smooth, some lines are also visible while rotating the view. Please take a look at my code and suggest me how can I make this animation more smoother? Suggestion on achieving this type of animation in any other efficient way is highly appreciated. enter image description here

public class EventsActivity extends MapActivity implements DialogInterface.OnDismissListener {

        private EventsItemModel     eventsItemModel;
        private Integer             eventItemId;
        private Integer             eventCategoryId;
        private static MapOverlay   mapOverlay;
        Drawable                    marker;
        Context                     context;
        private static String       MY_LOCATION = "My Location";
        private ViewGroup           mContainer;
        private ImageView           mImageView;
        private MapView             mMapView;
        private static boolean      isFlipped   = false;

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.event_item_detail);
            mContainer = (ViewGroup) findViewById(R.id.event_container);
            // Since we are caching large views, we want to keep their cache
            // between each animation
            mContainer.setPersistentDrawingCache(ViewGroup.PERSISTENT_ANIMATION_CACHE);
            mMapView = (MapView) findViewById(R.id.mapview);
            mImageView = (ImageView) findViewById(R.id.mapPreview);

            mImageView.setOnClickListener(new OnClickListener() {

                @Override
                public void onClick(View v) {
                    isFlipped = true;
                    applyRotation(1, 0, 90);
                }
            });

            try {
                eventCategoryId = getIntent().getIntExtra(AppConstants.EVENT_CATEGORY, 0);
                eventItemId = getIntent().getIntExtra(AppConstants.EVENT_ID, 0);
            }
            catch (Exception e) {
                e.printStackTrace();
            }

        }

        public void onResume() {
            super.onResume();
            WeakReference<EventsActivity> weakContext = new WeakReference<EventsActivity>(this);
            EventsAsyncTask task = new EventsAsyncTask(weakContext);
            task.execute(eventItemId, eventCategoryId);
        }

        public void onTaskComplete(EventsItemModel eiModel) {
            this.eventsItemModel = eiModel;
            TextView calTitle = (TextView) findViewById(R.id.news_title);
            TextView eventTitle = (TextView) findViewById(R.id.cal_event_title);
            TextView calDate = (TextView) findViewById(R.id.cal_date);
            TextView calTime = (TextView) findViewById(R.id.cal_time);
            TextView calAddress = (TextView) findViewById(R.id.cal_address);
            TextView calDescription = (TextView) findViewById(R.id.cal_description);

            try {
                calTitle.setText(eventsItemModel.getEventsCategory().getTitle());
                calTitle.setVisibility(View.VISIBLE);
                eventTitle.setText(eventsItemModel.getEventTitle());
                calDate.setText(eventsItemModel.getFormattedDateRange());
                // TODO:Format start and end time
                calTime.setText("Time: " + eventsItemModel.getFormattedStartTime() + " - " + eventsItemModel.getFormattedEndTime());
                calAddress.setText(eventsItemModel.getAddress());
                calDescription.setText(eventsItemModel.getDescription());
                System.out.println("<<<<<<<<< EventsActivity >>>>>>>>> isRead? " + eventsItemModel.getReadUnread());
                eventsItemModel.setReadUnread(true);
                System.out.println("<<<<<<<<<< EventsActivity >>>>>>>>>> isRead? " + eventsItemModel.getReadUnread());
            }
            catch (Exception e) {
                e.printStackTrace();
            }

            mMapView.setBuiltInZoomControls(true);
            setMapParameters();
            createItemizedOverlay();
            setLocationMarker(createMarker(R.drawable.location_marker));
            showLocationPointOnMap();
        }

        @Override
        public void onDismiss(DialogInterface dialog) {

        }

        @Override
        protected boolean isRouteDisplayed() {
            return false;
        }

        public void createItemizedOverlay() {
            mapOverlay = new MapOverlay(this);
        }

        public void setLocationMarker(Drawable marker) {
            mapOverlay.setLocationMarker(marker);
        }

        public void showLocationPointOnMap() {

            GeoPoint geoPoint = new GeoPoint(0, 0);
            if (eventsItemModel != null && eventsItemModel.getLatitude() != null && eventsItemModel.getLatitude().length() > 0 && eventsItemModel.getLongitude() != null
                    && eventsItemModel.getLongitude().length() > 0) {
                try {
                    geoPoint = new GeoPoint((int) (Double.parseDouble(eventsItemModel.getLatitude()) * 1E6), (int) (Double.parseDouble(eventsItemModel.getLongitude()) * 1E6));
                }
                catch (NumberFormatException e) {
                    e.printStackTrace();
                }
                OverlayItem item = new OverlayItem(geoPoint, MY_LOCATION, null);
                mapOverlay.addItem(item);
                mMapView.getOverlays().add(mapOverlay);

                // move to location
                mMapView.getController().animateTo(geoPoint);
                // redraw map
                mMapView.postInvalidate();
            }

        }

        public void setStreetView(boolean isStreetView) {
            mMapView.setStreetView(isStreetView);
        }

        public void setSatelliteView(boolean isSatelliteView) {
            mMapView.setSatellite(isSatelliteView);
        }

        public void setZoom(int zoomLevel) {
            mMapView.getController().setZoom(zoomLevel);
        }

        private void setMapParameters() {
            // setStreetView(true);
            // setSatelliteView(false);
            setZoom(17);
        }

        private Drawable createMarker(int iconID) {
            // Initialize icon
            Drawable icon = getResources().getDrawable(iconID);
            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
            return icon;
        }

        @Override
        protected void onStop() {
            // TODO Auto-generated method stub
            super.onStop();
        }

        @Override
        protected void onPause() {
            // TODO Auto-generated method stub
            super.onPause();
        }

        /**
         * Setup a new 3D rotation on the container view.
         * 
         * @param position
         *            the item that was clicked to show a picture, or -1 to show the list
         * @param start
         *            the start angle at which the rotation must begin
         * @param end
         *            the end angle of the rotation
         */
        private void applyRotation(int position, float start, float end) {
            // Find the center of the container
            final float centerX = mContainer.getWidth() / 2.0f;
            final float centerY = mContainer.getHeight() / 2.0f;

            // Create a new 3D rotation with the supplied parameter
            // The animation listener is used to trigger the next animation
            final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, centerX, centerY, 310.0f, true);
            rotation.setDuration(500);
            rotation.setFillAfter(true);
            rotation.setInterpolator(new AccelerateInterpolator());
            rotation.setAnimationListener(new DisplayNextView(position));

            mContainer.startAnimation(rotation);
        }

        /**
         * This class listens for the end of the first half of the animation. It then posts a new action that effectively swaps the views when the container is rotated 90 degrees and thus invisible.
         */
        private final class DisplayNextView implements Animation.AnimationListener {
            private final int   mPosition;

            private DisplayNextView(int position) {
                mPosition = position;
            }

            public void onAnimationStart(Animation animation) {
            }

            public void onAnimationEnd(Animation animation) {
                mContainer.post(new SwapViews(mPosition));
            }

            public void onAnimationRepeat(Animation animation) {
                // Do nothing!!
            }
        }

        /**
         * This class is responsible for swapping the views and start the second half of the animation.
         */
        private final class SwapViews implements Runnable {
            private final int   mPosition;

            public SwapViews(int position) {
                mPosition = position;
            }

            public void run() {
                final float centerX = mContainer.getWidth() / 2.0f;
                final float centerY = mContainer.getHeight() / 2.0f;
                Rotate3dAnimation rotation;

                if (mPosition > -1) {
                    mImageView.setVisibility(View.GONE);
                    mMapView.setVisibility(View.VISIBLE);
                    mMapView.requestFocus();

                    rotation = new Rotate3dAnimation(-90, 180, centerX, centerY, 310.0f, false);
                    rotation.reset();
                }
                else {
                    mMapView.setVisibility(View.GONE);
                    mImageView.setVisibility(View.VISIBLE);
                    mImageView.requestFocus();

                    rotation = new Rotate3dAnimation(90, 0, centerX, centerY, 310.0f, false);
                }

                rotation.setDuration(100);
                rotation.setFillAfter(true);
                rotation.setInterpolator(new DecelerateInterpolator());

                mContainer.startAnimation(rotation);
            }
        }

        @Override
        public void onBackPressed() {
            if (isFlipped) {
                applyRotation(-1, 0, -90);
                isFlipped = false;
            }
            else {
                super.onBackPressed();
            }
        }

    }

My xml layout is as follows:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/event_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#426773" >

    <include
        android:id="@+id/news_header"
        layout="@layout/news_header" />

    <TextView
        android:id="@+id/cal_event_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/news_header"
        android:padding="5dp"
        android:textColor="@android:color/white"
        android:textSize="22sp"
        android:textStyle="bold"
        android:typeface="sans" />

    <RelativeLayout 
        android:id="@+id/date_time_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/cal_event_title">

    <TextView
        android:id="@+id/cal_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:padding="5dp"
        android:textColor="@android:color/white" />

    <TextView
        android:id="@+id/cal_time"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/cal_date"
        android:padding="5dp"
        android:textColor="@android:color/white" />
    </RelativeLayout>

    <ImageView
        android:id="@+id/mapPreview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/cal_event_title"
        android:layout_alignParentRight="true"
        android:paddingRight="5dp"       
        android:clickable="true"
        android:src="@drawable/ic_event_map"
        android:onClick="showMap"
        android:background="@drawable/textview_border"
        android:layout_marginRight="5dp"/>

    <TextView
        android:id="@+id/cal_address"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_below="@id/date_time_container"
        android:padding="5dp"
        android:textColor="@android:color/white"
        android:textSize="16sp"
        android:textStyle="bold"
        android:typeface="sans" />

    <ScrollView
        android:id="@+id/scroll_description"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/cal_address"
        android:padding="5dp"
        android:scrollbars="vertical" >

        <RelativeLayout
            android:id="@+id/map_container"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <TextView
                android:id="@+id/cal_description"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:textColor="@android:color/white"/>
        </RelativeLayout>

    </ScrollView>

    <com.google.android.maps.MapView
        android:id="@+id/mapview"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        android:apiKey="your_google_api_key"
        android:clickable="true"
        android:visibility="gone" />

</RelativeLayout>
orchidrudra
  • 1,172
  • 1
  • 12
  • 30
  • 1
    I'm using Rotate3dAnimation in my application right now, and I'm facing similar problem. There is a lag in which some frames are skipped. On researching on it, I found out that during animation if gc is fired it takes away few msecs, which leads to some frames skipping. This is the problem in my case. So check whether the garbage collector is the troublemaker. – Harshal Kshatriya Jun 15 '12 at 08:23

4 Answers4

6

I will just give a small hint; however right now I am so busy at work I can not implement this.

The steps are

  • get your drawing cache Bitmap
  • set your content to an imageview only with this Bitmap
  • apply the animation to this imageview
  • at the end of the animation re set your content

This I believe will maximize the performance.

I will try to write some code later.

CODE

View longLivingReference; //keep a reference
private void applyRotation(int position, float start, float end) {
    longLivingReference = findViewById(R.id.event_container);
    longLivingReference .setDrawingCacheEnabled(true);
    Bitmap bitmapForAnimation = Bitmap.createBitmap(longLivingReference.getDrawingCache());
    ImageView iv = new ImageView(mContext);
    iv = new ImageView(mContext);
    iv.setImageBitmap(bitmapForAnimation);
    setContentView(iv);

    final float centerX = mContainer.getWidth() / 2.0f;
    final float centerY = mContainer.getHeight() / 2.0f;
    final Rotate3dAnimation rotation = new Rotate3dAnimation(start, end, centerX, centerY, 310.0f, true);
    rotation.setDuration(500);
    rotation.setFillAfter(true);
    rotation.setInterpolator(new AccelerateInterpolator());
    rotation.setAnimationListener(yourAnimationListener {
        //whatever your AnimationListener is, you can call super.onAnimationEnd if needed
        @Override
        public void onAnimationEnd(Animation animation) {
            setContentView(longLivingReference);
        }
    });
    iv.startAnimation(rotation);
}
Sherif elKhatib
  • 45,786
  • 16
  • 89
  • 106
  • where is you event_container? Post it – Sherif elKhatib Jun 18 '12 at 06:42
  • event_container is the parent layout (a RelativeLayout) which contains all other views. I updated my formatting, somehow is was not showing that RelativeLayout. Please take a look at it. Thanks – orchidrudra Jun 18 '12 at 06:49
  • Cant do `setContentView(bitmapForAnimation);` as this method expects an int value, and `bitmapForAnimation` is not an int. What to do? – orchidrudra Jun 18 '12 at 07:33
  • sorry I will remove that line! you should stick to `setContentView(iv);` – Sherif elKhatib Jun 18 '12 at 07:56
  • Getting error `E/AndroidRuntime( 995): FATAL EXCEPTION: main E/AndroidRuntime( 995): java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@40856418` – orchidrudra Jun 18 '12 at 08:41
  • I edited the line that initializes `bitmapForAnimation` please try it – Sherif elKhatib Jun 18 '12 at 08:50
  • After applying this change, when I click on the button which suppose to rotate the view, nothing happens, the screen freezes. Logcat does not report any error, but if I press back button, the whole application crashes. In my onAnimationEnd() method, I am calling `setContatntView()` after that `mContainer.post(new SwapViews(mPosition));` is it ok? – orchidrudra Jun 18 '12 at 09:11
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/12677/discussion-between-sherif-elkhatib-and-orchidrudra) – Sherif elKhatib Jun 18 '12 at 09:14
5

I have made the animation like this. I had the same problems. So, suggestions:

  • make xml layout as simple as possible, you can test it using Hierarchy View tool in android. It tool shows the time of building and drawing the laoyuts;

  • images on layout should have so low weight as possible;

  • use hardware acceleration if your device supports it (in manifest):

  • I have noticed one more interesing behavior. If I call some code in onAnimationEnd(Animation animation) method, the animation freezes for a short time. This problem I was solved using next construction:

    private static final int DELAY_AFTER_ANIMATION = 10;

    public void onAnimationEnd(Animation animation) { new Handler().postDelayed(new Runnable() { @Override public void run() { setData(); // do the heavy code here } }, DELAY_AFTER_ANIMATION); }

To construct animation I used the same code (Rotate3dAnimation). For calling animation(main difference is using the isReverse parameter):

public void apply3dRotation(float start, float end, AnimationListener listener, boolean isReverse) {
    View view = getRotateView();
    if(view == null){
        return;
    }

    if (isHardwareAcceleartionNotSupported()){
        AndroidHelper.disableHardwareAccelerationOnView(view, this.getClass());
    }

    final float centerX = view.getWidth() / 2.0f;
    final float centerY = view.getHeight() / 2.0f;

    Flip3dAnimation rotation;

    rotation = new Flip3dAnimation(start, end, centerX, centerY, 310.0f, isReverse);
    rotation.setDuration(ANIMATION_DURATION);
    rotation.setFillAfter(true);
    rotation.setInterpolator(new AccelerateInterpolator());

    if(listener != null){
        rotation.setAnimationListener(listener);
    }

    view.startAnimation(rotation);
}

isHardwareAcceleartionNotSupported() method checks the OS version. In my project I disabled acceleration for smartphones. In AndroidHelper class:

public static void disableHardwareAccelerationOnView(View view, Class c){
    try {
        view.setLayerType(View.LAYER_TYPE_SOFTWARE, null);
    } catch (Error e) {
        Log.i(c.getSimpleName(), e.getMessage());
    }
}

And one problem yet. If the animation hides when screen rotates to 90 degrees, it's a problem of a camera. In this case we should to place image farther from the spectator.

  • And one more trick - sometimes I set the pause before animation start:

      animation.setStartOffset(100);
    
kc ochibili
  • 3,103
  • 2
  • 25
  • 25
Alex Kucherenko
  • 20,168
  • 2
  • 26
  • 33
  • Ok I will try your suggestion, lets see what happens. – orchidrudra Jun 18 '12 at 05:47
  • I added `animation.setStartOffset(100);` and also shorten the duration of my animation, now its looking much faster. Those lines are showing less frequently. – orchidrudra Jun 18 '12 at 06:54
  • Necessarily try out the hardware acceleration – Alex Kucherenko Jun 18 '12 at 07:59
  • Cant use hardware acceleration because it is from api 11 (android 3.0) but I am using api 8 (android 2.2) – orchidrudra Jun 18 '12 at 08:43
  • In this case you can disable acceleration for not supported devices only. I have edited apply3dRotation() method. It really works in one of my projects. – Alex Kucherenko Jun 18 '12 at 08:45
  • Just to add this here (and maybe it’s ’cause I got the wrong version of Rotate3dAnimation.java, but I had a problem with choppiness and overdraw. To fix this, I had to add a few lines in protected void applyTransformation(float interpolatedTime, Transformation t) after final Matrix matrix = t.getMatrix(); I added: t.clear(); t.setTransformationType(Transformation.TYPE_MATRIX); And now the animation runns smooth and without any chop or artifacts! – MacD Jul 17 '13 at 09:00
2

One option for (non-OpenGL) 3d animation effects on Android is to implement the animations in a ViewGroup's getChildStaticTransform method using the graphics.Camera and Matrix classes.

In broad terms it's done like this:

  • Extend ViewGroup or a subclass thereof.

  • In the constructors, set staticTransformationEnabled to true:

     setStaticTransformationsEnabled(true);
    
  • Override the protected method getChildStaticTransformation(View view, Transformation t).

  • In getChildStaticTransformation, use graphics.Camera to rotate the View as per your picture.

  • Get the camera's matrix and adjust it to center the camera position on the view.

For example, this is how a 3d translation effect is done in the 3d carousel by Igor Kushnarev:

protected boolean getChildStaticTransformation(View child, Transformation transformation) {
   //...
    // Center of the item
    float centerX = (float)child.getWidth()/2, centerY = (float)child.getHeight()/2;

    // Save camera
    mCamera.save();

    // Translate the item to it's coordinates
    final Matrix matrix = transformation.getMatrix();
    mCamera.translate(((CarouselImageView)child).getX(), 
            ((CarouselImageView)child).getY(), 
            ((CarouselImageView)child).getZ());

    // Get the camera's matric and position the item
    mCamera.getMatrix(matrix);
    matrix.preTranslate(-centerX, -centerY);
    matrix.postTranslate(centerX, centerY);

    // Restore camera
    mCamera.restore();      

    return true;
 }    

Here are some more code examples on how to use graphics.Camera and Matrixin getChildStaticTransformation:

  • ViewPager3d by Inovex. This project is interesting because if you run it as is, the 3d animation is not smooth (on a Galaxy S2). Hoverever, if you strip it of the non-camera/matrix overscroll animations but keep the getChildStaticTransformation 3d effects done with camera and matrix, the 3d effects are smooth.

  • CoverFlow by Neil Davies.

Gunnar Karlsson
  • 28,350
  • 10
  • 68
  • 71
-1

I cannot understand ur question completely but according to me,

The key to a smoothly scrolling Rotate3dAnimation is to keep the application’s main thread (the UI thread) free from heavy processing. Ensure you do any disk access, network access, or SQL access in a separate thread.

This link isfor listview Rotate3dAnimation...

Name is Nilay
  • 2,743
  • 4
  • 35
  • 77