воскресенье, 4 марта 2012 г.

Android. Анимированные переходы между окнами

Увидев переходы между окнами на iPhone решил сделать что-нибудь такое на Android.
В этой статье расскажу и покажу как сделать анимированные переходы между окнами на Android.
Будут рассмотрены diagonal, zoom, slide-переходы. Все что будет показано в этой статье проверено на Android 2.1 и выше.

Ну начнем.

Как всегда создаем наш проект. Сегодня назову его AndroidTransition.
Прежде всего нам надо добавить в папку res папку anim. Она нужна для наших будущих анимаций. Вот так будет выглядеть структура приложения:


Теперь создадим второе activity. ( в мое случае SecondActivity.java ) Надо же куда-то нам переходить из основного окна :). Для него создадим UI в новом second.xml файле.

Вот такие два файла получатся ( second.xml с некоторыми изменениями ):

SecondActivity.java
 package com.lunevich.android;
  
 import android.app.Activity;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.view.View;  
 import android.view.View.OnClickListener;  
 import android.widget.Button;  

 public class SecondActivity extends Activity {  
        
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.second);  
   }  
 }  

second.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent"  
   android:orientation="vertical"  
   android:background="@android:color/background_light" > 
 
   <Button  
     android:id="@+id/btnBack"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_marginTop="50px"  
     android:text="BACK" />  

   <TextView  
     android:id="@+id/textView1"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_margin="40px"  
     android:text="Second View"  
     android:textColor="@android:color/black"  
     android:textAppearance="?android:attr/textAppearanceMedium" />  

 </LinearLayout>  


Теперь изменим наш main.xml файл. Добавим в него 3 кнопки для наших переходов. Выглядеть он будет вот так:

main.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
   android:layout_width="fill_parent"  
   android:layout_height="fill_parent"  
   android:orientation="vertical"  
   android:background="#1B9EE0" >  

   <TextView  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_margin="10px"  
     android:text="@string/hello"  
     android:textColor="@android:color/black" />  

   <Button  
     android:id="@+id/btnZoomTransition"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_margin="10px"  
     android:text="Zoom Transition" />  
 
   <Button  
     android:id="@+id/btnSlideLeftTransition"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_margin="10px"  
     android:text="Slide Left Transition" /> 
 
   <Button  
     android:id="@+id/btnDiagonalTransition"  
     android:layout_width="wrap_content"  
     android:layout_height="wrap_content"  
     android:layout_gravity="center_horizontal"  
     android:layout_margin="10px"  
     android:text="Diagonal Transition" />  

 </LinearLayout>  


Сейчас можно приступить к написанию анимаций для переходов.
Начнем с diagonal-перехода. В нашей папке  anim  создаем 2 xml файла ( diagonal_in.xml и diagonal_out.xml ). Изменяем их так:

diagonal_in.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" >  

   <translate  
     xmlns:android="http://schemas.android.com/apk/res/android"  
     android:duration="2000"  
     android:fromXDelta="-100%"  
     android:fromYDelta="-100%"  
     android:toXDelta="0%"  
     android:toYDelta="0%" />  

 </set>  
diagonal_out.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" >  

   <translate  
     xmlns:android="http://schemas.android.com/apk/res/android"  
     android:duration="2000"  
     android:fromXDelta="0%"  
     android:fromYDelta="0%"  
     android:toXDelta="-100%"  
     android:toYDelta="-100%" />  

 </set>  


Теперь создадим ещё 2 файла: zoom_in.xml и zoom_out.xml. Они будут отвечать за zoom анимированный переход между окнами. Вот так мы их изменим:

zoom_in.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <alpha  
     android:duration="@android:integer/config_mediumAnimTime"  
     android:fromAlpha="0.0"  
     android:interpolator="@android:anim/accelerate_interpolator"  
     android:toAlpha="1.0" />  

 </set>  
zoom_out.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <alpha  
     android:duration="@android:integer/config_mediumAnimTime"  
     android:fromAlpha="1.0"  
     android:interpolator="@android:anim/accelerate_interpolator"  
     android:toAlpha="0.0" />  

 </set>  


Добавим в папку anim ещё 4 файла которые будут отвечать за slide-анимацию. ( slide_left_in.xml, slide_left_out.xml, slide_right_in.xml, slide_right_out.xml )

slide_left_in.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <translate  
     android:duration="800"  
     android:fromXDelta="100%p"  
     android:toXDelta="0" />  

 </set>  
slide_left_out.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <translate  
     android:duration="800"  
     android:fromXDelta="0"  
     android:toXDelta="-100%p" />  

 </set>  
slide_right_in.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <translate  
     android:duration="800"  
     android:fromXDelta="-100%p"  
     android:toXDelta="0" />  

 </set>  
slide_right_out.xml
 <?xml version="1.0" encoding="utf-8"?>  
 <set xmlns:android="http://schemas.android.com/apk/res/android" > 
 
   <translate  
     android:duration="800"  
     android:fromXDelta="0"  
     android:toXDelta="100%p" />  

 </set>  


Вот теперь все наши анимированные переходы готовы.
Можем приступить к изменению AndroidTransitionActivity.java и SecondActivity.java файлов.

Файл AndroidTransitionActivity.java изменим вот так:

AndroidTransitionActivity.java
 package com.lunevich.android;
  
 import android.app.Activity;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.view.View;  
 import android.view.View.OnClickListener;  
 import android.widget.Button;  

 public class AndroidTransitionActivity extends Activity implements OnClickListener { 
 
   // Перечисление TransitionType создано только для демонстрации нескольких анимаций
   public static enum TransitionType {  
        Zoom, SlideLeft, Diagonal  
   }  
   public static TransitionType transitionType;
  
   private Button btnZoomTransition;  
   private Button btnSlideLeftTransition;  
   private Button btnDiagonalTransition;  
 
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.main);  

     // Регистрируем кнопки и вешаем на них обработчики нажатия
     btnZoomTransition = (Button)findViewById(R.id.btnZoomTransition);  
     btnZoomTransition.setOnClickListener(this);  
     btnSlideLeftTransition = (Button)findViewById(R.id.btnSlideLeftTransition);  
     btnSlideLeftTransition.setOnClickListener(this);  
     btnDiagonalTransition = (Button)findViewById(R.id.btnDiagonalTransition);  
     btnDiagonalTransition.setOnClickListener(this);  
   }  

   public void onClick(View v) {  
        // TODO Auto-generated method stub  
        // Завершаем наше текущее activity
        this.finish(); 

        // С помощью intent переходим во второе окно
        Intent intent = new Intent(this, SecondActivity.class);  
        startActivity(intent);
 
        switch (v.getId()) {  
        case R.id.btnZoomTransition: 
             // Устанавливаем тип нашей анимации
             transitionType = TransitionType.Zoom;  

             // Для AndroidTransitionActivity указываем, 
             // с помощью какой анимации мы будем переходить в другое окно
             overridePendingTransition(R.anim.zoom_in, R.anim.zoom_out);  
             break;  
        case R.id.btnSlideLeftTransition:  
             transitionType = TransitionType.SlideLeft;  
             overridePendingTransition(R.anim.slide_left_in, R.anim.slide_left_out);  
             break;  
        case R.id.btnDiagonalTransition:  
             transitionType = TransitionType.Diagonal;  
             overridePendingTransition(R.anim.diagonal_in, R.anim.diagonal_out);  
             break;  
        }  
   }  
 }  


Измененный SecondActivity.java файл теперь будет выглядеть вот так:

SecondActivity.java
 package com.lunevich.android;  

 import android.app.Activity;  
 import android.content.Intent;  
 import android.os.Bundle;  
 import android.view.View;  
 import android.view.View.OnClickListener;  
 import android.widget.Button;  

 public class SecondActivity extends Activity implements OnClickListener {  

   private Button btnBack;    

   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.second); 
 
     // Регистрируем нашу кнопку back и вешаем на неё обработчик нажатия
     btnBack = (Button)findViewById(R.id.btnBack);  
     btnBack.setOnClickListener(this);  
   } 
 
   public void onClick(View v) {  
        // TODO Auto-generated method stub  
        switch (v.getId()) {  
        case R.id.btnBack:  
             onBackPressed();  
             break;  
        }  
   }  

   @Override  
   public void onBackPressed() { 
 
           // Завершаем наше activity
           this.finish(); 

           // Intent отвечает за переходы между activity, 
           // здесь мы возвращаемся обратно в наше главное окно
           Intent intent = new Intent(this, AndroidTransitionActivity.class);  
           startActivity(intent);  

           // Проверяем с помощью какой анимации мы перешли, по той же анимации и возвращаемся
           if (AndroidTransitionActivity.transitionType == AndroidTransitionActivity.TransitionType.SlideLeft) {  
                // Для SecondActivity устанавливаем анимацию перехода
                overridePendingTransition(R.anim.slide_right_in, R.anim.slide_right_out);  
           } else if (AndroidTransitionActivity.transitionType == AndroidTransitionActivity.TransitionType.Zoom) {  
                overridePendingTransition(R.anim.zoom_in, R.anim.zoom_out);  
           } else if (AndroidTransitionActivity.transitionType == AndroidTransitionActivity.TransitionType.Diagonal) {  
                overridePendingTransition(R.anim.diagonal_in, R.anim.diagonal_out);  
           }   
      }  
 }  

Чуть не забыл про AndroidManifest.xml файл. Туда нам необходимо прописать наше добавленное activity ( SecondActivity.java ), иначе ничего работать не будет.  Сделаем мы это вот так:

AndroidManifest.xml
 //...
  <application  
     android:icon="@drawable/ic_launcher"  
     android:label="@string/app_name" >  
     <activity  
       android:label="@string/app_name"  
       android:name=".AndroidTransitionActivity" >  
       <intent-filter >  
         <action android:name="android.intent.action.MAIN" />  
         <category android:name="android.intent.category.LAUNCHER" />  
       </intent-filter>  
     </activity>  
     <activity android:name="SecondActivity"></activity>  // Вот эту строку добавили
   </application> 
 //...

Вот теперь все. После всех добавлений наше приложение имеет вот такую структуру:


Вот такие результаты получились:

diagonal-анимация
zoom-анимация
slide-анимация


Ну все. Мы достигли поставленной задачи и немного разобрались с анимированными переходами  в Android. Теперь наши приложения будут выглядеть ещё красивее.
Надеюсь все, что здесь было показано, понятно.

четверг, 1 марта 2012 г.

AndEngine. Основы.

В сегодняшней статье я расскажу о простых компонентах для написания игр для Android
используя  AndEngine. Покажу как добавить sprite и score-текст на экран.

Пожалуй начнем.

Прежде всего нам необходимо заменить Activity на  BaseGameActivity и переопределить методы.
После этого наш класс должен выглядеть вот так:

AndEnginePart2Activity.java
 package com.lunevich.andengine;
  
 import org.anddev.andengine.engine.Engine;  
 import org.anddev.andengine.entity.scene.Scene;  
 import org.anddev.andengine.ui.activity.BaseGameActivity;
  
 public class AndEnginePart2Activity extends BaseGameActivity { 
 
      // В onLoadEngine мы инициализируем движок  
      @Override  
      public Engine onLoadEngine() {  
           // TODO Auto-generated method stub  
           return null;  
      }  

      // В onLoadResources мы будем загружать ресурсы, 
      // необходимые для нашего приложения, картинки, музыка, и т.д...  
      @Override  
      public void onLoadResources() {  
           // TODO Auto-generated method stub  
      }  

      // В onLoadScene мы создаем сцену  
      @Override  
      public Scene onLoadScene() {  
           // TODO Auto-generated method stub  
           return null;  
      }  

      @Override  
      public void onLoadComplete() {  
           // TODO Auto-generated method stub  
      }  
 }


Если все получилось, можно продолжать. Теперь непосредственно о загрузке, добавлении компонентов на сцену.

Для начала добавим ещё один класс Monster.java. Функционала в него никакого добавлять не будем, только наследуем его от класса Sprite.

Monster.java
 package com.lunevich.andengine;
  
 import org.anddev.andengine.entity.sprite.Sprite;  
 import org.anddev.andengine.opengl.texture.region.TextureRegion;  

 public class Monster extends Sprite {  

      public Monster(float pX, float pY, TextureRegion pTextureRegion) {  
           super(pX, pY, pTextureRegion);  
           // TODO Auto-generated constructor stub  
      }  
 }  


Теперь изменим наш AndEnginePart2Activity.java класс. Выглядеть он будет вот так:

 package com.lunevich.andengine;  

 import org.anddev.andengine.engine.Engine;  
 import org.anddev.andengine.engine.camera.Camera;  
 import org.anddev.andengine.engine.handler.IUpdateHandler;  
 import org.anddev.andengine.engine.options.EngineOptions;  
 import org.anddev.andengine.engine.options.EngineOptions.ScreenOrientation;  
 import org.anddev.andengine.engine.options.resolutionpolicy.RatioResolutionPolicy;  
 import org.anddev.andengine.entity.scene.Scene;  
 import org.anddev.andengine.entity.scene.background.ColorBackground;  
 import org.anddev.andengine.entity.text.ChangeableText; 
 import org.anddev.andengine.entity.util.FPSLogger;  
 import org.anddev.andengine.opengl.font.Font; 
 import org.anddev.andengine.opengl.texture.TextureOptions;  
 import org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlas;  
 import org.anddev.andengine.opengl.texture.atlas.bitmap.BitmapTextureAtlasTextureRegionFactory;  
 import org.anddev.andengine.opengl.texture.region.TextureRegion;  
 import org.anddev.andengine.ui.activity.BaseGameActivity;
  
 import android.content.Context;  
 import android.graphics.Color;  
 import android.graphics.Typeface;
 import android.view.Display;  
 import android.view.WindowManager;  

 public class AndEnginePart2Activity extends BaseGameActivity { 
 
      private Display display;  

      private static int CAMERA_WIDTH = 0;       
      private static int CAMERA_HEIGHT = 0;   

      private Camera mCamera;  
      private Scene mScene;

      private BitmapTextureAtlas mFontTexture;  
      private Font mFont;   
      private BitmapTextureAtlas mBitmapTextureAtlas;       
      private TextureRegion monsterTexture;  

      private Monster monster; 
      private ChangeableText scoreText;  

      // В onLoadEngine мы инициализируем движок  
      @Override  
      public Engine onLoadEngine() {  
           // TODO Auto-generated method stub 

           WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
           display = wm.getDefaultDisplay(); 
 
           // Получаем ширину и высоту нашего экрана  
           CAMERA_WIDTH = display.getWidth();  
           CAMERA_HEIGHT = display.getHeight(); 
 
           // Создаем камеру 
           this.mCamera = new Camera(0, 0, CAMERA_WIDTH, CAMERA_HEIGHT); 
           
           // Создаем движок 
           return new Engine(new EngineOptions(true, ScreenOrientation.LANDSCAPE, new RatioResolutionPolicy(CAMERA_WIDTH, CAMERA_HEIGHT), this.mCamera));  
      }  

      // В onLoadResources мы будем загружать ресурсы, 
      // необходимые для нашего приложения, картинки, музыка, и т.д...  
      @Override  
      public void onLoadResources() {  
           // TODO Auto-generated method stub 
 
           // Создаем новую текстуру 256х256, помните её размер должен быть кратный 2
           this.mBitmapTextureAtlas = new BitmapTextureAtlas(256, 256, TextureOptions.BILINEAR_PREMULTIPLYALPHA);

           // Создаем тектуру для текста
           this.mFontTexture = new BitmapTextureAtlas(256, 256, TextureOptions.BILINEAR);
           
           // Загружаем тектуру монстра  
           this.monsterTexture = BitmapTextureAtlasTextureRegionFactory.createFromAsset(this.mBitmapTextureAtlas, this, "monster/monster.png", 0, 0); 

           // Создаем шрифт
           this.mFont = new Font(this.mFontTexture, Typeface.create(Typeface.DEFAULT, Typeface.BOLD), 32, true, Color.BLACK );
           this.mFont.prepareLetters("1234567890".toCharArray());
 
           // Загружаем тектуры и шрифт в память  
           this.mEngine.getTextureManager().loadTexture(this.mBitmapTextureAtlas); 
           this.mEngine.getTextureManager().loadTexture(this.mFontTexture); 
           this.mEngine.getFontManager().loadFont(this.mFont);  
      }  

      // В onLoadScene мы создаем сцену  
      @Override  
      public Scene onLoadScene() {  
           // TODO Auto-generated method stub  
           this.mEngine.registerUpdateHandler(new FPSLogger()); 
           
           // Создаем сцену
           mScene = new Scene();
  
           // Устанавливаем цвет фона сцены 
           mScene.setBackground(new ColorBackground(0.96f, 0.9215f, 0.8157f)); 
 
           // Создаем нашего монстра и устанавливаем его на определенную позицию 
           monster = new Monster(CAMERA_WIDTH / 2, CAMERA_HEIGHT - 200, this.monsterTexture);   

           // Добавляем монстра на нашу сцену  
           mScene.attachChild(monster);

           // Создаем score-текст  
           scoreText = new ChangeableText(CAMERA_WIDTH - 220, 20, this.mFont, "Score :", "Score : XXXXX".length());

           // Добавляем текст на нашу сцену  
           mScene.attachChild(scoreText);    

           // Создаем цикл нашей сцены, в этом примере мы ничего не будет там делать
           mScene.registerUpdateHandler(new IUpdateHandler() {  

             @Override  
             public void onUpdate(float arg0) { 
                  // Расположение этой строки здесь только для примера
                  scoreText.setText("Score : " + AndEnginePart2Activity.this.mEngine.getSecondsElapsedTotal());
                  // ...  
             }  

             @Override  
             public void reset() {  
                  // ...
             }  
           });  
           return mScene;  
      } 
 
      @Override  
      public void onLoadComplete() {  
           // TODO Auto-generated method stub  
      }  
 }  

Вот такая структура нашего приложения получилась:


И вот результат наших стараний ( пока что только моих :) ) :


Кому необходимо вот монстр -> Monster

Вот и все. С основами остановимся пока на этом. Удачи в написании игр для Android :)