Sunday, 30 June 2013

Seek Bar Example

Main.XML

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="vertical" >

    <SeekBar
        android:id="@+id/seek_bar"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:max="100"
        android:progress="10" />

    <TextView
        android:id="@+id/textview"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:text="" />

</LinearLayout>


Main Java File

package com.example.seekbar;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.widget.SeekBar;
import android.widget.Toast;
import android.widget.SeekBar.OnSeekBarChangeListener;


public class MainActivity extends Activity implements OnSeekBarChangeListener{

private SeekBar mSeekBar;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

       mSeekBar = (SeekBar) findViewById(R.id.seek_bar);
       mSeekBar.setOnSeekBarChangeListener(this);
   }

   @Override
   public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
       Toast.makeText(MainActivity.this, "Seekbar Value : " + progress, Toast.LENGTH_SHORT).show();
   }

   @Override
   public void onStartTrackingTouch(SeekBar seekBar) {
       Toast.makeText(MainActivity.this, "Started Tracking Seekbar", Toast.LENGTH_SHORT).show();
   }

   @Override
   public void onStopTrackingTouch(SeekBar seekBar) {
       mSeekBar.setSecondaryProgress(seekBar.getProgress());
       Toast.makeText(MainActivity.this, "Stopped Tracking Seekbar", Toast.LENGTH_SHORT).show();
   }
}



Tuesday, 25 June 2013

Facebook Like Button Using Android

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"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/button1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="174dp"
        android:text="FbLike" />
  </RelativeLayout>

Web.xml

<?xml version="1.0" encoding="utf-8"?>
<WebView  xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/webView1"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
/>

Main.java

package com.example.fblike;

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

public class MainActivity extends Activity {
Button b;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
b=(Button)findViewById(R.id.button1);
b.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent a=new Intent(getApplicationContext(),WebActivity.class);
startActivity(a);
}
});
}
}


Web.java

package com.example.fblike;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.webkit.WebView;

public class WebActivity extends Activity {

private WebView webView;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_web);

webView = (WebView) findViewById(R.id.webView1);
webView.getSettings().setJavaScriptEnabled(true);
webView.loadUrl("https://www.facebook.com/pages/Pudiya-thalaimurai/150323218406193?fref=ts");
}
}



Wifi App Using Android

XML File

<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"
    tools:context=".MainActivity" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world"
        android:id="@+id/mainText"/>

</RelativeLayout>


JAVA File

package com.example.wifi;

import java.util.List;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

TextView mainText;
    WifiManager mainWifi;
    WifiReceiver receiverWifi;
    List<ScanResult> wifiList;
    StringBuilder sb = new StringBuilder();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
      mainText = (TextView) findViewById(R.id.mainText);
       
      // Initiate wifi service manager
      mainWifi = (WifiManager) getSystemService(Context.WIFI_SERVICE);
       
      // Check for wifi is disabled
      if (mainWifi.isWifiEnabled() == false)
           {   
               // If wifi disabled then enable it
               Toast.makeText(getApplicationContext(), "wifi is disabled..making it enabled", 
               Toast.LENGTH_LONG).show();
                
               mainWifi.setWifiEnabled(true);
           } 
       
      // wifi scaned value broadcast receiver 
      receiverWifi = new WifiReceiver();
       
      // Register broadcast receiver 
      // Broacast receiver will automatically call when number of wifi connections changed
      registerReceiver(receiverWifi, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
      mainWifi.startScan();
      mainText.setText("Starting Scan...");
   }
 
   public boolean onCreateOptionsMenu(Menu menu) {
       menu.add(0, 0, 0, "Refresh");
       return super.onCreateOptionsMenu(menu);
   }
 
   public boolean onMenuItemSelected(int featureId, MenuItem item) {
       mainWifi.startScan();
       mainText.setText("Starting Scan");
       return super.onMenuItemSelected(featureId, item);
   }
 
   protected void onPause() {
       unregisterReceiver(receiverWifi);
       super.onPause();
   }
 
   protected void onResume() {
       registerReceiver(receiverWifi, new IntentFilter(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION));
       super.onResume();
   }
    
   // Broadcast receiver class called its receive method 
   // when number of wifi connections changed
    
   class WifiReceiver extends BroadcastReceiver {
        
       // This method call when number of wifi connections changed
       public void onReceive(Context c, Intent intent) {
            
           sb = new StringBuilder();
           wifiList = mainWifi.getScanResults(); 
           sb.append("\n        Number Of Wifi connections :"+wifiList.size()+"\n\n");
            
           for(int i = 0; i < wifiList.size(); i++){
                
               sb.append(new Integer(i+1).toString() + ". ");
               sb.append((wifiList.get(i)).toString());
               sb.append("\n\n");
           }
            
           mainText.setText(sb);  
       }
        
   }
}
Manifest File

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>

GPS APP using Android

XML File

<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"
    tools:context=".MainActivity" >

   

</RelativeLayout>


JAVA File

package com.example.gps;

import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.app.Activity;
import android.content.Context;
import android.view.Menu;
import android.widget.Toast;

public class MainActivity extends Activity implements LocationListener{

private LocationManager locationManager;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
                 
                /********** get Gps location service LocationManager object ***********/
                locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE);
                 
                /* CAL METHOD requestLocationUpdates */
                   
                  // Parameters :
                  //   First(provider)    :  the name of the provider with which to register 
                  //   Second(minTime)    :  the minimum time interval for notifications, 
                  //                         in milliseconds. This field is only used as a hint 
                  //                         to conserve power, and actual time between location 
                  //                         updates may be greater or lesser than this value. 
                  //   Third(minDistance) :  the minimum distance interval for notifications, in meters 
                  //   Fourth(listener)   :  a {#link LocationListener} whose onLocationChanged(Location) 
                  //                         method will be called for each location update 
                
                 
                locationManager.requestLocationUpdates( LocationManager.GPS_PROVIDER,
                        3000,   // 3 sec
                        10, this);
                 
                /********* After registration onLocationChanged method  ********/
                /********* called periodically after each 3 sec ***********/
            }
             
            /************* Called after each 3 sec **********/
            @Override
            public void onLocationChanged(Location location) {
                    
                String str = "Latitude: "+location.getLatitude()+" Longitude: "+location.getLongitude();

  
                Toast.makeText(getBaseContext(), str, Toast.LENGTH_LONG).show();
            }
         
            @Override
            public void onProviderDisabled(String provider) {
                 
                /******** Called when User off Gps *********/
                 
                Toast.makeText(getBaseContext(), "Gps turned off ", Toast.LENGTH_LONG).show();
            }
         
            @Override
            public void onProviderEnabled(String provider) {
                 
                /******** Called when User on Gps  *********/
                 
                Toast.makeText(getBaseContext(), "Gps turned on ", Toast.LENGTH_LONG).show();
            }
         
            @Override
            public void onStatusChanged(String provider, int status, Bundle extras) {
                // TODO Auto-generated method stub
                 
            }
   }


Manifest File

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.INTERNET"/>

Camera App using android

XML File

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
     tools:context=".CameraPhotoCapture" >
     
             <Button android:text="Capture Photo"
                     android:id="@+id/photo"
                     android:layout_width="wrap_content"
                     android:layout_height="wrap_content" />
             
             <TextView
                 android:id="@+id/message"
                 android:layout_below="@+id/photo"
                 android:text="Click on button to capture image"
                 android:textSize="14dp"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="10dp"
                 android:layout_marginLeft="20dp"
                 />
             
             <TextView
                 android:id="@+id/imageDetails"
                 android:layout_below="@+id/message"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_marginTop="5dp"
                 android:layout_marginLeft="20dp"
                 />
         
             <ImageView
                 android:id="@+id/showImg"
                 android:layout_width="wrap_content"
                 android:layout_height="wrap_content"
                 android:layout_centerHorizontal="true"
                 android:layout_centerVertical="true"
                 android:layout_below="@+id/imageDetails"
                 />
     
</RelativeLayout>


JAVA File

package com.example.camera;

import java.io.IOException;

import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.provider.MediaStore;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.view.Menu;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

public class MainActivity extends Activity {

final static int CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE = 1;

   Uri imageUri                      = null;
   static TextView imageDetails      = null;
   public  static ImageView showImg  = null;
  public MainActivity CameraActivity = null;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

       CameraActivity = this;
       
       imageDetails = (TextView) findViewById(R.id.imageDetails);
       
       showImg = (ImageView) findViewById(R.id.showImg);
       
       final Button photo = (Button) findViewById(R.id.photo);
       
       
       
       photo.setOnClickListener(new OnClickListener() {
           public void onClick(View v) {
               
             /*************************** Camera Intent Start ************************/
               
               // Define the file-name to save photo taken by Camera activity
               
               String fileName = "Camera_Example.jpg";
               
               // Create parameters for Intent with filename
               
               ContentValues values = new ContentValues();
               
               values.put(MediaStore.Images.Media.TITLE, fileName);
               
               values.put(MediaStore.Images.Media.DESCRIPTION,"Image capture by camera");
               
               // imageUri is the current activity attribute, define and save it for later usage
               
               imageUri = getContentResolver().insert(
                       MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
               
               /**** EXTERNAL_CONTENT_URI : style URI for the "primary" external storage volume. ****/

               
               // Standard Intent action that can be sent to have the camera
               // application capture an image and return it.
               
               Intent intent = new Intent( MediaStore.ACTION_IMAGE_CAPTURE );
               
                intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
               
                intent.putExtra(MediaStore.EXTRA_VIDEO_QUALITY, 1);
               
               startActivityForResult( intent, CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
             
            /*************************** Camera Intent End ************************/
               
               
           }  
           
       });
   }


    @Override
    protected void onActivityResult( int requestCode, int resultCode, Intent data)
       {
           if ( requestCode == CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE) {
               
               if ( resultCode == RESULT_OK) {
               
                  /*********** Load Captured Image And Data Start ****************/
                   
                   String imageId = convertImageUriToFile( imageUri,CameraActivity);
                   

                  //  Create and excecute AsyncTask to load capture image

                   new LoadImagesFromSDCard().execute(""+imageId);
                   
                 /*********** Load Captured Image And Data End ****************/
                   
             
               } else if ( resultCode == RESULT_CANCELED) {
                   
                   Toast.makeText(this, " Picture was not taken ", Toast.LENGTH_SHORT).show();
               } else {
                   
                   Toast.makeText(this, " Picture was not taken ", Toast.LENGTH_SHORT).show();
               }
           }
       }
   
   
    /************ Convert Image Uri path to physical path **************/
   
    public static String convertImageUriToFile ( Uri imageUri, Activity activity )  {
   
           Cursor cursor = null;
           int imageID = 0;
           
           try {
           
               /*********** Which columns values want to get *******/
               String [] proj={
                                MediaStore.Images.Media.DATA,
                                MediaStore.Images.Media._ID,
                                MediaStore.Images.Thumbnails._ID,
                                MediaStore.Images.ImageColumns.ORIENTATION
                              };
               
               cursor = activity.managedQuery(
                       
                               imageUri,         //  Get data for specific image URI
                               proj,             //  Which columns to return
                               null,             //  WHERE clause; which rows to return (all rows)
                               null,             //  WHERE clause selection arguments (none)
                               null              //  Order-by clause (ascending by name)
                               
                            );
                                 
               //  Get Query Data
               
               int columnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media._ID);
               int columnIndexThumb = cursor.getColumnIndexOrThrow(MediaStore.Images.Thumbnails._ID);
               int file_ColumnIndex = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
               
               //int orientation_ColumnIndex = cursor.
               //    getColumnIndexOrThrow(MediaStore.Images.ImageColumns.ORIENTATION);
               
               int size = cursor.getCount();
               
               /*******  If size is 0, there are no images on the SD Card. *****/
               
               if (size == 0) {


                   imageDetails.setText("No Image");
               }
               else
               {
             
                   int thumbID = 0;
                   if (cursor.moveToFirst()) {
                       
                       /**************** Captured image details ************/
                       
                       /*****  Used to show image on view in LoadImagesFromSDCard class ******/
                       imageID     = cursor.getInt(columnIndex);
                       
                       thumbID     = cursor.getInt(columnIndexThumb);
                       
                       String Path = cursor.getString(file_ColumnIndex);
                       
                       //String orientation =  cursor.getString(orientation_ColumnIndex);
                       
                       String CapturedImageDetails = " CapturedImageDetails : \n\n"
                                                         +" ImageID :"+imageID+"\n"
                                                         +" ThumbID :"+thumbID+"\n"
                                                         +" Path :"+Path+"\n";
                       
                       // Show Captured Image detail on activity
                       imageDetails.setText( CapturedImageDetails );
                       
                   }
               }  
           } finally {
               if (cursor != null) {
                   cursor.close();
               }
           }
           
           // Return Captured Image ImageID ( By this ImageID Image will load from sdcard )
           
           return ""+imageID;
       }
   
   
        /**
        * Async task for loading the images from the SD card.
        *
        * @author Android Example
        *
        */
       
       // Class with extends AsyncTask class
       
    public class LoadImagesFromSDCard  extends AsyncTask<String, Void, Void> {
           
           private ProgressDialog Dialog = new ProgressDialog(MainActivity.this);
           
           Bitmap mBitmap;
           
           protected void onPreExecute() {
               /****** NOTE: You can call UI Element here. *****/
               
               // Progress Dialog
               Dialog.setMessage(" Loading image from Sdcard..");
               Dialog.show();
           }


           // Call after onPreExecute method
           protected Void doInBackground(String... urls) {
               
               Bitmap bitmap = null;
               Bitmap newBitmap = null;
               Uri uri = null;      
                   
                   
                   try {
                       
                       /**  Uri.withAppendedPath Method Description
                       * Parameters
                       *    baseUri  Uri to append path segment to
                       *    pathSegment  encoded path segment to append
                       * Returns
                       *    a new Uri based on baseUri with the given segment appended to the path
                       */
                       
                       uri = Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "" + urls[0]);
                       
                       /**************  Decode an input stream into a bitmap. *********/
                       bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(uri));
                       
                       if (bitmap != null) {
                           
                           /********* Creates a new bitmap, scaled from an existing bitmap. ***********/

                           newBitmap = Bitmap.createScaledBitmap(bitmap, 170, 170, true);
                           
                           bitmap.recycle();
                           
                           if (newBitmap != null) {
                               
                               mBitmap = newBitmap;
                           }
                       }
                   } catch (IOException e) {
                       // Error fetching image, try to recover
                       
                       /********* Cancel execution of this task. **********/
                       cancel(true);
                   }
               
               return null;
           }
           
           
           protected void onPostExecute(Void unused) {
               
               // NOTE: You can call UI Element here.
               
               // Close progress dialog
                 Dialog.dismiss();
               
               if(mBitmap != null)
               {
                 // Set Image to ImageView
                 
                  showImg.setImageBitmap(mBitmap);
               }
               
           }
           
       }
       
}

Monday, 24 June 2013

ViewFlipper in android

XML File

<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"
    tools:context=".MainActivity" >

   <ViewFlipper
android:id="@+id/view_flipper"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_margin="6dip" >

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Text1"
android:textColor="#00ff00"
android:textSize="14sp"
android:textStyle="bold" >
</TextView>
<TextView
    android:id="@+id/tv"
    android:layout_width="wrap_content"
        android:layout_height="wrap_content"
       
      android:text="A dog limps into a saloon and says...najsndfiaimfi"
        android:singleLine="true"
         android:layout_marginLeft="35dp"
        android:layout_gravity="center"
       android:ellipsize="marquee"
        android:marqueeRepeatLimit ="marquee_forever"
        android:scrollHorizontally="true"
        android:focusable="true"
        android:focusableInTouchMode="true"
        android:duplicateParentState="true">
    <requestFocus android:focusable="true" android:focusableInTouchMode="true"
        android:duplicateParentState="true" />
</TextView>

</LinearLayout>

<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="center" >

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="I&apos;m looking for the man who shot my paw!"
android:textColor="#00ff00"
android:textSize="14sp"
android:textStyle="bold" >
</TextView>
</LinearLayout>
</ViewFlipper>
   

</RelativeLayout>



JAVA File

package com.example.viewflipper;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.widget.TextView;
import android.widget.ViewFlipper;

public class MainActivity extends Activity {
private ViewFlipper vf;
private float lastX;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
vf = (ViewFlipper) findViewById(R.id.view_flipper);
}
public boolean onTouchEvent(MotionEvent touchevent) {
switch (touchevent.getAction()) {
case MotionEvent.ACTION_DOWN: {
lastX = touchevent.getX();
break;
}
case MotionEvent.ACTION_UP: {
float currentX = touchevent.getX();
if (lastX < currentX) {
if (vf.getDisplayedChild() == 0)
break;

vf.showNext();
}
if (lastX > currentX) {
if (vf.getDisplayedChild() == 1)
break;

vf.showPrevious();
}
break;
}
}
return false;
}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

}

ShakeAnimation using android

Here we are going to shake the editText in android

1. XML File

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    android:layout_width="fill_parent"

    android:layout_height="fill_parent"

 

    android:orientation="vertical"

    android:padding="10dip" >



    <TextView

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:layout_marginBottom="10dip"

        android:text="Please enter your password:"

        android:textColor="@android:color/white"

        android:textStyle="bold" />



    <EditText

        android:id="@+id/pw"

        android:layout_width="fill_parent"

        android:layout_height="wrap_content"

        android:clickable="true"

        android:password="true"

        android:singleLine="true" />



    <Button

        android:id="@+id/login"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

     

        android:padding="5dp"

        android:text="Login"

        android:textColor="@android:color/white"

        android:textStyle="bold" />



</LinearLayout>


2. JAVA File

package com.example.shakeanimation;

import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Toast;

public class MainActivity extends Activity implements View.OnClickListener{

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View loginButton = findViewById(R.id.login);
 loginButton.setOnClickListener(this);
}

public void onClick(View v) {
 Animation shake = AnimationUtils.loadAnimation(this, R.anim.shake);
    findViewById(R.id.pw).startAnimation(shake);
    Toast.makeText(this, "Wrong Password", Toast.LENGTH_SHORT).show();
}

}

3. Create shake.xml file in res/anim/

<translate xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="500"
    android:fromXDelta="0"
    android:interpolator="@anim/cycle_7"
    android:toXDelta="10" />

4. Create cycle_1.xml in res/anim/

<cycleInterpolator xmlns:android="http://schemas.android.com/apk/res/android"
    android:cycles="7" />

Sunday, 23 June 2013

SampleCanvas

XML File

<?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" >

 

</LinearLayout>

Java File

package com.SampleCanvas;

import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup.LayoutParams;
import android.view.Window;
import android.view.WindowManager;
import android.widget.FrameLayout;

public class SampleCanvasActivity extends Activity implements OnTouchListener {

DrawPanel dp;
    private ArrayList<Path> pointsToDraw = new ArrayList<Path>();
    private Paint mPaint;
    Path path;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        requestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.main);
        dp = new DrawPanel(this);
        dp.setOnTouchListener(this);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);
        mPaint = new Paint();
        mPaint.setDither(true);
        mPaint.setColor(Color.WHITE);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeJoin(Paint.Join.ROUND);
        mPaint.setStrokeCap(Paint.Cap.ROUND);
        mPaint.setStrokeWidth(30);
        FrameLayout fl = new FrameLayout(this);
        fl.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
        fl.addView(dp);
        setContentView(fl);
    }

   
    @Override
    protected void onPause() {
        // TODO Auto-generated method stub
        super.onPause();
        dp.pause();
    }
    @Override
    protected void onResume() {
        // TODO Auto-generated method stub
        super.onResume();
        dp.resume();
    }
   
    public boolean onTouch(View v, MotionEvent me) {
        // TODO Auto-generated method stub
                synchronized(pointsToDraw)
                {
        if(me.getAction() == MotionEvent.ACTION_DOWN){
            path = new Path();
            path.moveTo(me.getX(), me.getY());
            //path.lineTo(me.getX(), me.getY());
            pointsToDraw.add(path);
        }else if(me.getAction() == MotionEvent.ACTION_MOVE){
            path.lineTo(me.getX(), me.getY());
        }else if(me.getAction() == MotionEvent.ACTION_UP){
            //path.lineTo(me.getX(), me.getY());
        }
        }      
        return true;
    }
   
   
    @SuppressLint("WrongCall")
public class DrawPanel extends SurfaceView implements Runnable{

        Thread t = null;
        SurfaceHolder holder;
        boolean isItOk = false ;

        public DrawPanel(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            holder = getHolder();
        }

        public void run() {
            // TODO Auto-generated method stub
            while( isItOk == true){

                if(!holder.getSurface().isValid()){
                    continue;
                }

                Canvas c = holder.lockCanvas();
                c.drawARGB(255, 0, 0, 0);
                onDraw(c);
                holder.unlockCanvasAndPost(c);
            }
        }

        @Override
        protected void onDraw(Canvas canvas) {
            // TODO Auto-generated method stub
            super.onDraw(canvas);
                        synchronized(pointsToDraw)
                        {
            for (Path path : pointsToDraw) {
                canvas.drawPath(path, mPaint);
            }
                        }
        }

        public void pause(){
            isItOk = false;
            while(true){
                try{
                    t.join();
                }catch(InterruptedException e){
                    e.printStackTrace();
                }
                break;
            }
            t = null;
        }

        public void resume(){
            isItOk = true;
            t = new Thread(this);
            t.start();

        }
    }
}


Page curl effect in android

XML File

<?xml version="1.0" encoding="utf-8"?>
<com.example.pagecurl1.CurlView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:id="@+id/curl"
/>

MainActivity.java

package com.example.pagecurl1;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity {

private CurlView mCurlView;

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        int index = 0;
if (getLastNonConfigurationInstance() != null) {
index = (Integer) getLastNonConfigurationInstance();
}
mCurlView = (CurlView) findViewById(R.id.curl);
mCurlView.setPageProvider(new PageProvider());
mCurlView.setSizeChangedObserver(new SizeChangedObserver());
mCurlView.setCurrentIndex(index);
mCurlView.setBackgroundColor(0xffffff);

}

@Override
public void onPause() {
super.onPause();
mCurlView.onPause();
}

@Override
public void onResume() {
super.onResume();
mCurlView.onResume();
}

@Override
public Object onRetainNonConfigurationInstance() {
return mCurlView.getCurrentIndex();
}

/**
* Bitmap provider.
*/
private class PageProvider implements CurlView.PageProvider {

// Bitmap resources.
private int[] mBitmapIds = { R.drawable.a, R.drawable.b,
R.drawable.c, R.drawable.d,R.drawable.sixth };

@Override
public int getPageCount() {
return 5;
}

private Bitmap loadBitmap(int width, int height, int index) {
Bitmap b = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
b.eraseColor(0xFFFFFFFF);
Canvas c = new Canvas(b);
Drawable d = getResources().getDrawable(mBitmapIds[index]);

int margin = 7;
int border = 3;
Rect r = new Rect(margin, margin, width - margin, height - margin);

int imageWidth = r.width() - (border * 2);
int imageHeight = imageWidth * d.getIntrinsicHeight()
/ d.getIntrinsicWidth();
if (imageHeight > r.height() - (border * 2)) {
imageHeight = r.height() - (border * 2);
imageWidth = imageHeight * d.getIntrinsicWidth()
/ d.getIntrinsicHeight();
}

r.left += ((r.width() - imageWidth) / 2) - border;
r.right = r.left + imageWidth + border + border;
r.top += ((r.height() - imageHeight) / 2) - border;
r.bottom = r.top + imageHeight + border + border;

Paint p = new Paint();
p.setColor(0xFFC0C0C0);
c.drawRect(r, p);
r.left += border;
r.right -= border;
r.top += border;
r.bottom -= border;

d.setBounds(r);
d.draw(c);

return b;
}

@Override
public void updatePage(CurlPage page, int width, int height, int index) {

switch (index) {
// First case is image on front side, solid colored back.
case 0: {
Bitmap front = loadBitmap(width, height, 0);
Bitmap back = loadBitmap(width, height, 0);
page.setTexture(front, CurlPage.SIDE_FRONT);
page.setTexture(back, CurlPage.SIDE_BACK);
//page.setColor(Color.rgb(180, 180, 180), CurlPage.SIDE_BACK);
break;
}
// Second case is image on back side, solid colored front.
case 1: {
Bitmap front = loadBitmap(width, height, 1);
Bitmap back = loadBitmap(width, height, 1);
page.setTexture(front, CurlPage.SIDE_FRONT);
page.setTexture(back, CurlPage.SIDE_BACK);
//page.setColor(Color.rgb(127, 140, 180), CurlPage.SIDE_FRONT);
break;
}
// Third case is images on both sides.
case 2: {
Bitmap front = loadBitmap(width, height, 2);
Bitmap back = loadBitmap(width, height, 2);
page.setTexture(front, CurlPage.SIDE_FRONT);
page.setTexture(back, CurlPage.SIDE_BACK);
break;
}
// Fourth case is images on both sides - plus they are blend against
// separate colors.
case 3: {
Bitmap front = loadBitmap(width, height, 3);
Bitmap back = loadBitmap(width, height, 3);
page.setTexture(front, CurlPage.SIDE_FRONT);
page.setTexture(back, CurlPage.SIDE_BACK);
//page.setColor(Color.argb(127, 170, 130, 255),
// CurlPage.SIDE_FRONT);
//page.setColor(Color.rgb(255, 190, 150), CurlPage.SIDE_BACK);
break;
}
// Fifth case is same image is assigned to front and back. In this
// scenario only one texture is used and shared for both sides.
case 4: {
Bitmap front = loadBitmap(width, height, 4);
Bitmap back = loadBitmap(width, height, 4);
page.setTexture(front, CurlPage.SIDE_FRONT);
page.setTexture(back, CurlPage.SIDE_BACK);
//page.setColor(Color.argb(127, 170, 130, 255),
// CurlPage.SIDE_FRONT);
//page.setColor(Color.rgb(255, 190, 150), CurlPage.SIDE_BACK);
break;
}
case 5:
{
Bitmap front = loadBitmap(width, height, 5);
page.setTexture(front, CurlPage.SIDE_FRONT);
break;
}
}
}

}

/**
* CurlView size changed observer.
*/
private class SizeChangedObserver implements CurlView.SizeChangedObserver {
@Override
public void onSizeChanged(int w, int h) {
if (w > h) {
mCurlView.setViewMode(CurlView.SHOW_TWO_PAGES);
mCurlView.setMargins(.1f, .05f, .1f, .05f);
} else {
mCurlView.setViewMode(CurlView.SHOW_ONE_PAGE);
mCurlView.setMargins(.1f, .1f, .1f, .1f);
}
}
}

}
    

CurlView.java

package com.example.pagecurl1;


import android.content.Context;
import android.graphics.PointF;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class CurlView extends GLSurfaceView implements View.OnTouchListener,
CurlRenderer.Observer {

// Curl state. We are flipping none, left or right page.
private static final int CURL_LEFT = 1;
private static final int CURL_NONE = 0;
private static final int CURL_RIGHT = 2;

// Constants for mAnimationTargetEvent.
private static final int SET_CURL_TO_LEFT = 1;
private static final int SET_CURL_TO_RIGHT = 2;

// Shows one page at the center of view.
public static final int SHOW_ONE_PAGE = 1;
// Shows two pages side by side.
public static final int SHOW_TWO_PAGES = 2;

private boolean mAllowLastPageCurl = true;

private boolean mAnimate = false;
private long mAnimationDurationTime = 300;
private PointF mAnimationSource = new PointF();
private long mAnimationStartTime;
private PointF mAnimationTarget = new PointF();
private int mAnimationTargetEvent;

private PointF mCurlDir = new PointF();

private PointF mCurlPos = new PointF();
private int mCurlState = CURL_NONE;
// Current bitmap index. This is always showed as front of right page.
private int mCurrentIndex = 0;

// Start position for dragging.
private PointF mDragStartPos = new PointF();

private boolean mEnableTouchPressure = false;
// Bitmap size. These are updated from renderer once it's initialized.
private int mPageBitmapHeight = -1;

private int mPageBitmapWidth = -1;
// Page meshes. Left and right meshes are 'static' while curl is used to
// show page flipping.
private CurlMesh mPageCurl;

private CurlMesh mPageLeft;
private PageProvider mPageProvider;
private CurlMesh mPageRight;

private PointerPosition mPointerPos = new PointerPosition();

private CurlRenderer mRenderer;
private boolean mRenderLeftPage = true;
private SizeChangedObserver mSizeChangedObserver;

// One page is the default.
private int mViewMode = SHOW_ONE_PAGE;

/**
* Default constructor.
*/
public CurlView(Context ctx) {
super(ctx);
init(ctx);
}

/**
* Default constructor.
*/
public CurlView(Context ctx, AttributeSet attrs) {
super(ctx, attrs);
init(ctx);
}

/**
* Default constructor.
*/
public CurlView(Context ctx, AttributeSet attrs, int defStyle) {
this(ctx, attrs);
}

/**
* Get current page index. Page indices are zero based values presenting
* page being shown on right side of the book.
*/
public int getCurrentIndex() {
return mCurrentIndex;
}

/**
* Initialize method.
*/
private void init(Context ctx) {
mRenderer = new CurlRenderer(this);
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
setOnTouchListener(this);

// Even though left and right pages are static we have to allocate room
// for curl on them too as we are switching meshes. Another way would be
// to swap texture ids only.
mPageLeft = new CurlMesh(10);
mPageRight = new CurlMesh(10);
mPageCurl = new CurlMesh(10);
mPageLeft.setFlipTexture(true);
mPageRight.setFlipTexture(false);
}

@Override
public void onDrawFrame() {
// We are not animating.
if (mAnimate == false) {
return;
}

long currentTime = System.currentTimeMillis();
// If animation is done.
if (currentTime >= mAnimationStartTime + mAnimationDurationTime) {
if (mAnimationTargetEvent == SET_CURL_TO_RIGHT) {
// Switch curled page to right.
CurlMesh right = mPageCurl;
CurlMesh curl = mPageRight;
right.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT));
right.setFlipTexture(false);
right.reset();
mRenderer.removeCurlMesh(curl);
mPageCurl = curl;
mPageRight = right;
// If we were curling left page update current index.
if (mCurlState == CURL_LEFT) {
--mCurrentIndex;
}
} else if (mAnimationTargetEvent == SET_CURL_TO_LEFT) {
// Switch curled page to left.
CurlMesh left = mPageCurl;
CurlMesh curl = mPageLeft;
left.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
left.setFlipTexture(true);
left.reset();
mRenderer.removeCurlMesh(curl);
if (!mRenderLeftPage) {
mRenderer.removeCurlMesh(left);
}
mPageCurl = curl;
mPageLeft = left;
// If we were curling right page update current index.
if (mCurlState == CURL_RIGHT) {
++mCurrentIndex;
}
}
mCurlState = CURL_NONE;
mAnimate = false;
requestRender();
} else {
mPointerPos.mPos.set(mAnimationSource);
float t = 1f - ((float) (currentTime - mAnimationStartTime) / mAnimationDurationTime);
t = 1f - (t * t * t * (3 - 2 * t));
mPointerPos.mPos.x += (mAnimationTarget.x - mAnimationSource.x) * t;
mPointerPos.mPos.y += (mAnimationTarget.y - mAnimationSource.y) * t;
updateCurlPos(mPointerPos);
}
}

@Override
public void onPageSizeChanged(int width, int height) {
mPageBitmapWidth = width;
mPageBitmapHeight = height;
updatePages();
requestRender();
}

@Override
public void onSizeChanged(int w, int h, int ow, int oh) {
super.onSizeChanged(w, h, ow, oh);
requestRender();
if (mSizeChangedObserver != null) {
mSizeChangedObserver.onSizeChanged(w, h);
}
}

@Override
public void onSurfaceCreated() {
// In case surface is recreated, let page meshes drop allocated texture
// ids and ask for new ones. There's no need to set textures here as
// onPageSizeChanged should be called later on.
mPageLeft.resetTexture();
mPageRight.resetTexture();
mPageCurl.resetTexture();
}

@Override
public boolean onTouch(View view, MotionEvent me) {
// No dragging during animation at the moment.
// TODO: Stop animation on touch event and return to drag mode.
if (mAnimate || mPageProvider == null) {
return false;
}

// We need page rects quite extensively so get them for later use.
RectF rightRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT);
RectF leftRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT);

// Store pointer position.
mPointerPos.mPos.set(me.getX(), me.getY());
mRenderer.translate(mPointerPos.mPos);
if (mEnableTouchPressure) {
mPointerPos.mPressure = me.getPressure();
} else {
mPointerPos.mPressure = 0.8f;
}

switch (me.getAction()) {
case MotionEvent.ACTION_DOWN: {

// Once we receive pointer down event its position is mapped to
// right or left edge of page and that'll be the position from where
// user is holding the paper to make curl happen.
mDragStartPos.set(mPointerPos.mPos);

// First we make sure it's not over or below page. Pages are
// supposed to be same height so it really doesn't matter do we use
// left or right one.
if (mDragStartPos.y > rightRect.top) {
mDragStartPos.y = rightRect.top;
} else if (mDragStartPos.y < rightRect.bottom) {
mDragStartPos.y = rightRect.bottom;
}

// Then we have to make decisions for the user whether curl is going
// to happen from left or right, and on which page.
if (mViewMode == SHOW_TWO_PAGES) {
// If we have an open book and pointer is on the left from right
// page we'll mark drag position to left edge of left page.
// Additionally checking mCurrentIndex is higher than zero tells
// us there is a visible page at all.
if (mDragStartPos.x < rightRect.left && mCurrentIndex > 0) {
mDragStartPos.x = leftRect.left;
startCurl(CURL_LEFT);
}
// Otherwise check pointer is on right page's side.
else if (mDragStartPos.x >= rightRect.left
&& mCurrentIndex < mPageProvider.getPageCount()) {
mDragStartPos.x = rightRect.right;
if (!mAllowLastPageCurl
&& mCurrentIndex >= mPageProvider.getPageCount() - 1) {
return false;
}
startCurl(CURL_RIGHT);
}
} else if (mViewMode == SHOW_ONE_PAGE) {
float halfX = (rightRect.right + rightRect.left) / 2;
if (mDragStartPos.x < halfX && mCurrentIndex > 0) {
mDragStartPos.x = rightRect.left;
startCurl(CURL_LEFT);
} else if (mDragStartPos.x >= halfX
&& mCurrentIndex < mPageProvider.getPageCount()) {
mDragStartPos.x = rightRect.right;
if (!mAllowLastPageCurl
&& mCurrentIndex >= mPageProvider.getPageCount() - 1) {
return false;
}
startCurl(CURL_RIGHT);
}
}
// If we have are in curl state, let this case clause flow through
// to next one. We have pointer position and drag position defined
// and this will create first render request given these points.
if (mCurlState == CURL_NONE) {
return false;
}
}
case MotionEvent.ACTION_MOVE: {
updateCurlPos(mPointerPos);
break;
}
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP: {
if (mCurlState == CURL_LEFT || mCurlState == CURL_RIGHT) {
// Animation source is the point from where animation starts.
// Also it's handled in a way we actually simulate touch events
// meaning the output is exactly the same as if user drags the
// page to other side. While not producing the best looking
// result (which is easier done by altering curl position and/or
// direction directly), this is done in a hope it made code a
// bit more readable and easier to maintain.
mAnimationSource.set(mPointerPos.mPos);
mAnimationStartTime = System.currentTimeMillis();

// Given the explanation, here we decide whether to simulate
// drag to left or right end.
if ((mViewMode == SHOW_ONE_PAGE && mPointerPos.mPos.x > (rightRect.left + rightRect.right) / 2)
|| mViewMode == SHOW_TWO_PAGES
&& mPointerPos.mPos.x > rightRect.left) {
// On right side target is always right page's right border.
mAnimationTarget.set(mDragStartPos);
mAnimationTarget.x = mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT).right;
mAnimationTargetEvent = SET_CURL_TO_RIGHT;
} else {
// On left side target depends on visible pages.
mAnimationTarget.set(mDragStartPos);
if (mCurlState == CURL_RIGHT || mViewMode == SHOW_TWO_PAGES) {
mAnimationTarget.x = leftRect.left;
} else {
mAnimationTarget.x = rightRect.left;
}
mAnimationTargetEvent = SET_CURL_TO_LEFT;
}
mAnimate = true;
requestRender();
}
break;
}
}

return true;
}

/**
* Allow the last page to curl.
*/
public void setAllowLastPageCurl(boolean allowLastPageCurl) {
mAllowLastPageCurl = allowLastPageCurl;
}

/**
* Sets background color - or OpenGL clear color to be more precise. Color
* is a 32bit value consisting of 0xAARRGGBB and is extracted using
* android.graphics.Color eventually.
*/
@Override
public void setBackgroundColor(int color) {
mRenderer.setBackgroundColor(color);
requestRender();
}

/**
* Sets mPageCurl curl position.
*/
private void setCurlPos(PointF curlPos, PointF curlDir, double radius) {

// First reposition curl so that page doesn't 'rip off' from book.
if (mCurlState == CURL_RIGHT
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_ONE_PAGE)) {
RectF pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT);
if (curlPos.x >= pageRect.right) {
mPageCurl.reset();
requestRender();
return;
}
if (curlPos.x < pageRect.left) {
curlPos.x = pageRect.left;
}
if (curlDir.y != 0) {
float diffX = curlPos.x - pageRect.left;
float leftY = curlPos.y + (diffX * curlDir.x / curlDir.y);
if (curlDir.y < 0 && leftY < pageRect.top) {
curlDir.x = curlPos.y - pageRect.top;
curlDir.y = pageRect.left - curlPos.x;
} else if (curlDir.y > 0 && leftY > pageRect.bottom) {
curlDir.x = pageRect.bottom - curlPos.y;
curlDir.y = curlPos.x - pageRect.left;
}
}
} else if (mCurlState == CURL_LEFT) {
RectF pageRect = mRenderer.getPageRect(CurlRenderer.PAGE_LEFT);
if (curlPos.x <= pageRect.left) {
mPageCurl.reset();
requestRender();
return;
}
if (curlPos.x > pageRect.right) {
curlPos.x = pageRect.right;
}
if (curlDir.y != 0) {
float diffX = curlPos.x - pageRect.right;
float rightY = curlPos.y + (diffX * curlDir.x / curlDir.y);
if (curlDir.y < 0 && rightY < pageRect.top) {
curlDir.x = pageRect.top - curlPos.y;
curlDir.y = curlPos.x - pageRect.right;
} else if (curlDir.y > 0 && rightY > pageRect.bottom) {
curlDir.x = curlPos.y - pageRect.bottom;
curlDir.y = pageRect.right - curlPos.x;
}
}
}

// Finally normalize direction vector and do rendering.
double dist = Math.sqrt(curlDir.x * curlDir.x + curlDir.y * curlDir.y);
if (dist != 0) {
curlDir.x /= dist;
curlDir.y /= dist;
mPageCurl.curl(curlPos, curlDir, radius);
} else {
mPageCurl.reset();
}

requestRender();
}

/**
* Set current page index. Page indices are zero based values presenting
* page being shown on right side of the book. E.g if you set value to 4;
* right side front facing bitmap will be with index 4, back facing 5 and
* for left side page index 3 is front facing, and index 2 back facing (once
* page is on left side it's flipped over).
* Current index is rounded to closest value divisible with 2.
*/
public void setCurrentIndex(int index) {
if (mPageProvider == null || index < 0) {
mCurrentIndex = 0;
} else {
if (mAllowLastPageCurl) {
mCurrentIndex = Math.min(index, mPageProvider.getPageCount());
} else {
mCurrentIndex = Math.min(index,
mPageProvider.getPageCount() - 1);
}
}
updatePages();
requestRender();
}

/**
* If set to true, touch event pressure information is used to adjust curl
* radius. The more you press, the flatter the curl becomes. This is
* somewhat experimental and results may vary significantly between devices.
* On emulator pressure information seems to be flat 1.0f which is maximum
* value and therefore not very much of use.
*/
public void setEnableTouchPressure(boolean enableTouchPressure) {
mEnableTouchPressure = enableTouchPressure;
}

/**
* Set margins (or padding). Note: margins are proportional. Meaning a value
* of .1f will produce a 10% margin.
*/
public void setMargins(float left, float top, float right, float bottom) {
mRenderer.setMargins(left, top, right, bottom);
}

/**
* Update/set page provider.
*/
public void setPageProvider(PageProvider pageProvider) {
mPageProvider = pageProvider;
mCurrentIndex = 0;
updatePages();
requestRender();
}

/**
* Setter for whether left side page is rendered. This is useful mostly for
* situations where right (main) page is aligned to left side of screen and
* left page is not visible anyway.
*/
public void setRenderLeftPage(boolean renderLeftPage) {
mRenderLeftPage = renderLeftPage;
}

/**
* Sets SizeChangedObserver for this View. Call back method is called from
* this View's onSizeChanged method.
*/
public void setSizeChangedObserver(SizeChangedObserver observer) {
mSizeChangedObserver = observer;
}

/**
* Sets view mode. Value can be either SHOW_ONE_PAGE or SHOW_TWO_PAGES. In
* former case right page is made size of display, and in latter case two
* pages are laid on visible area.
*/
public void setViewMode(int viewMode) {
switch (viewMode) {
case SHOW_ONE_PAGE:
mViewMode = viewMode;
mPageLeft.setFlipTexture(true);
mRenderer.setViewMode(CurlRenderer.SHOW_ONE_PAGE);
break;
case SHOW_TWO_PAGES:
mViewMode = viewMode;
mPageLeft.setFlipTexture(false);
mRenderer.setViewMode(CurlRenderer.SHOW_TWO_PAGES);
break;
}
}

/**
* Switches meshes and loads new bitmaps if available. Updated to support 2
* pages in landscape
*/
private void startCurl(int page) {
switch (page) {

// Once right side page is curled, first right page is assigned into
// curled page. And if there are more bitmaps available new bitmap is
// loaded into right side mesh.
case CURL_RIGHT: {
// Remove meshes from renderer.
mRenderer.removeCurlMesh(mPageLeft);
mRenderer.removeCurlMesh(mPageRight);
mRenderer.removeCurlMesh(mPageCurl);

// We are curling right page.
CurlMesh curl = mPageRight;
mPageRight = mPageCurl;
mPageCurl = curl;

if (mCurrentIndex > 0) {
mPageLeft.setFlipTexture(true);
mPageLeft
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
mPageLeft.reset();
if (mRenderLeftPage) {
mRenderer.addCurlMesh(mPageLeft);
}
}
if (mCurrentIndex < mPageProvider.getPageCount() - 1) {
updatePage(mPageRight.getTexturePage(), mCurrentIndex + 1);
mPageRight.setRect(mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT));
mPageRight.setFlipTexture(false);
mPageRight.reset();
mRenderer.addCurlMesh(mPageRight);
}

// Add curled page to renderer.
mPageCurl.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT));
mPageCurl.setFlipTexture(false);
mPageCurl.reset();
mRenderer.addCurlMesh(mPageCurl);

mCurlState = CURL_RIGHT;
break;
}

// On left side curl, left page is assigned to curled page. And if
// there are more bitmaps available before currentIndex, new bitmap
// is loaded into left page.
case CURL_LEFT: {
// Remove meshes from renderer.
mRenderer.removeCurlMesh(mPageLeft);
mRenderer.removeCurlMesh(mPageRight);
mRenderer.removeCurlMesh(mPageCurl);

// We are curling left page.
CurlMesh curl = mPageLeft;
mPageLeft = mPageCurl;
mPageCurl = curl;

if (mCurrentIndex > 1) {
updatePage(mPageLeft.getTexturePage(), mCurrentIndex - 2);
mPageLeft.setFlipTexture(true);
mPageLeft
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
mPageLeft.reset();
if (mRenderLeftPage) {
mRenderer.addCurlMesh(mPageLeft);
}
}

// If there is something to show on right page add it to renderer.
if (mCurrentIndex < mPageProvider.getPageCount()) {
mPageRight.setFlipTexture(false);
mPageRight.setRect(mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT));
mPageRight.reset();
mRenderer.addCurlMesh(mPageRight);
}

// How dragging previous page happens depends on view mode.
if (mViewMode == SHOW_ONE_PAGE
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES)) {
mPageCurl.setRect(mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT));
mPageCurl.setFlipTexture(false);
} else {
mPageCurl
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
mPageCurl.setFlipTexture(true);
}
mPageCurl.reset();
mRenderer.addCurlMesh(mPageCurl);

mCurlState = CURL_LEFT;
break;
}

}
}

/**
* Updates curl position.
*/
private void updateCurlPos(PointerPosition pointerPos) {

// Default curl radius.
double radius = mRenderer.getPageRect(CURL_RIGHT).width() / 3;
// TODO: This is not an optimal solution. Based on feedback received so
// far; pressure is not very accurate, it may be better not to map
// coefficient to range [0f, 1f] but something like [.2f, 1f] instead.
// Leaving it as is until get my hands on a real device. On emulator
// this doesn't work anyway.
radius *= Math.max(1f - pointerPos.mPressure, 0f);
// NOTE: Here we set pointerPos to mCurlPos. It might be a bit confusing
// later to see e.g "mCurlPos.x - mDragStartPos.x" used. But it's
// actually pointerPos we are doing calculations against. Why? Simply to
// optimize code a bit with the cost of making it unreadable. Otherwise
// we had to this in both of the next if-else branches.
mCurlPos.set(pointerPos.mPos);

// If curl happens on right page, or on left page on two page mode,
// we'll calculate curl position from pointerPos.
if (mCurlState == CURL_RIGHT
|| (mCurlState == CURL_LEFT && mViewMode == SHOW_TWO_PAGES)) {

mCurlDir.x = mCurlPos.x - mDragStartPos.x;
mCurlDir.y = mCurlPos.y - mDragStartPos.y;
float dist = (float) Math.sqrt(mCurlDir.x * mCurlDir.x + mCurlDir.y
* mCurlDir.y);

// Adjust curl radius so that if page is dragged far enough on
// opposite side, radius gets closer to zero.
float pageWidth = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT)
.width();
double curlLen = radius * Math.PI;
if (dist > (pageWidth * 2) - curlLen) {
curlLen = Math.max((pageWidth * 2) - dist, 0f);
radius = curlLen / Math.PI;
}

// Actual curl position calculation.
if (dist >= curlLen) {
double translate = (dist - curlLen) / 2;
if (mViewMode == SHOW_TWO_PAGES) {
mCurlPos.x -= mCurlDir.x * translate / dist;
} else {
float pageLeftX = mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT).left;
radius = Math.max(Math.min(mCurlPos.x - pageLeftX, radius),
0f);
}
mCurlPos.y -= mCurlDir.y * translate / dist;
} else {
double angle = Math.PI * Math.sqrt(dist / curlLen);
double translate = radius * Math.sin(angle);
mCurlPos.x += mCurlDir.x * translate / dist;
mCurlPos.y += mCurlDir.y * translate / dist;
}
}
// Otherwise we'll let curl follow pointer position.
else if (mCurlState == CURL_LEFT) {

// Adjust radius regarding how close to page edge we are.
float pageLeftX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT).left;
radius = Math.max(Math.min(mCurlPos.x - pageLeftX, radius), 0f);

float pageRightX = mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT).right;
mCurlPos.x -= Math.min(pageRightX - mCurlPos.x, radius);
mCurlDir.x = mCurlPos.x + mDragStartPos.x;
mCurlDir.y = mCurlPos.y - mDragStartPos.y;
}

setCurlPos(mCurlPos, mCurlDir, radius);
}

/**
* Updates given CurlPage via PageProvider for page located at index.
*/
private void updatePage(CurlPage page, int index) {
// First reset page to initial state.
page.reset();
// Ask page provider to fill it up with bitmaps and colors.
mPageProvider.updatePage(page, mPageBitmapWidth, mPageBitmapHeight,
index);
}

/**
* Updates bitmaps for page meshes.
*/
private void updatePages() {
if (mPageProvider == null || mPageBitmapWidth <= 0
|| mPageBitmapHeight <= 0) {
return;
}

// Remove meshes from renderer.
mRenderer.removeCurlMesh(mPageLeft);
mRenderer.removeCurlMesh(mPageRight);
mRenderer.removeCurlMesh(mPageCurl);

int leftIdx = mCurrentIndex - 1;
int rightIdx = mCurrentIndex;
int curlIdx = -1;
if (mCurlState == CURL_LEFT) {
curlIdx = leftIdx;
--leftIdx;
} else if (mCurlState == CURL_RIGHT) {
curlIdx = rightIdx;
++rightIdx;
}

if (rightIdx >= 0 && rightIdx < mPageProvider.getPageCount()) {
updatePage(mPageRight.getTexturePage(), rightIdx);
mPageRight.setFlipTexture(false);
mPageRight.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_RIGHT));
mPageRight.reset();
mRenderer.addCurlMesh(mPageRight);
}
if (leftIdx >= 0 && leftIdx < mPageProvider.getPageCount()) {
updatePage(mPageLeft.getTexturePage(), leftIdx);
mPageLeft.setFlipTexture(true);
mPageLeft.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
mPageLeft.reset();
if (mRenderLeftPage) {
mRenderer.addCurlMesh(mPageLeft);
}
}
if (curlIdx >= 0 && curlIdx < mPageProvider.getPageCount()) {
updatePage(mPageCurl.getTexturePage(), curlIdx);

if (mCurlState == CURL_RIGHT) {
mPageCurl.setFlipTexture(true);
mPageCurl.setRect(mRenderer
.getPageRect(CurlRenderer.PAGE_RIGHT));
} else {
mPageCurl.setFlipTexture(false);
mPageCurl
.setRect(mRenderer.getPageRect(CurlRenderer.PAGE_LEFT));
}

mPageCurl.reset();
mRenderer.addCurlMesh(mPageCurl);
}
}

/**
* Provider for feeding 'book' with bitmaps which are used for rendering
* pages.
*/
public interface PageProvider {

/**
* Return number of pages available.
*/
public int getPageCount();

/**
* Called once new bitmaps/textures are needed. Width and height are in
* pixels telling the size it will be drawn on screen and following them
* ensures that aspect ratio remains. But it's possible to return bitmap
* of any size though. You should use provided CurlPage for storing page
* information for requested page number.<br/>
* <br/>
* Index is a number between 0 and getBitmapCount() - 1.
*/
public void updatePage(CurlPage page, int width, int height, int index);
}

/**
* Simple holder for pointer position.
*/
private class PointerPosition {
PointF mPos = new PointF();
float mPressure;
}

/**
* Observer interface for handling CurlView size changes.
*/
public interface SizeChangedObserver {

/**
* Called once CurlView size changes.
*/
public void onSizeChanged(int width, int height);
}

}


CurlRenderer.java

package com.example.pagecurl1;


import java.util.Vector;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.opengl.GLSurfaceView;
import android.opengl.GLU;

/**
 * Actual renderer class.
 * 
 * @author harism
 */
public class CurlRenderer implements GLSurfaceView.Renderer {

// Constant for requesting left page rect.
public static final int PAGE_LEFT = 1;
// Constant for requesting right page rect.
public static final int PAGE_RIGHT = 2;
// Constants for changing view mode.
public static final int SHOW_ONE_PAGE = 1;
public static final int SHOW_TWO_PAGES = 2;
// Set to true for checking quickly how perspective projection looks.
private static final boolean USE_PERSPECTIVE_PROJECTION = false;
// Background fill color.
private int mBackgroundColor;
// Curl meshes used for static and dynamic rendering.
private Vector<CurlMesh> mCurlMeshes;
private RectF mMargins = new RectF();
private CurlRenderer.Observer mObserver;
// Page rectangles.
private RectF mPageRectLeft;
private RectF mPageRectRight;
// View mode.
private int mViewMode = SHOW_ONE_PAGE;
// Screen size.
private int mViewportWidth, mViewportHeight;
// Rect for render area.
private RectF mViewRect = new RectF();

/**
* Basic constructor.
*/
public CurlRenderer(CurlRenderer.Observer observer) {
mObserver = observer;
mCurlMeshes = new Vector<CurlMesh>();
mPageRectLeft = new RectF();
mPageRectRight = new RectF();
}

/**
* Adds CurlMesh to this renderer.
*/
public synchronized void addCurlMesh(CurlMesh mesh) {
removeCurlMesh(mesh);
mCurlMeshes.add(mesh);
}

/**
* Returns rect reserved for left or right page. Value page should be
* PAGE_LEFT or PAGE_RIGHT.
*/
public RectF getPageRect(int page) {
if (page == PAGE_LEFT) {
return mPageRectLeft;
} else if (page == PAGE_RIGHT) {
return mPageRectRight;
}
return null;
}

@Override
public synchronized void onDrawFrame(GL10 gl) {

mObserver.onDrawFrame();

gl.glClearColor(Color.red(mBackgroundColor) / 255f,
Color.green(mBackgroundColor) / 255f,
Color.blue(mBackgroundColor) / 255f,
Color.alpha(mBackgroundColor) / 255f);
gl.glClear(GL10.GL_COLOR_BUFFER_BIT);
gl.glLoadIdentity();

if (USE_PERSPECTIVE_PROJECTION) {
gl.glTranslatef(0, 0, -6f);
}

for (int i = 0; i < mCurlMeshes.size(); ++i) {
mCurlMeshes.get(i).onDrawFrame(gl);
}
}

@Override
public void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 0, width, height);
mViewportWidth = width;
mViewportHeight = height;

float ratio = (float) width / height;
mViewRect.top = 1.0f;
mViewRect.bottom = -1.0f;
mViewRect.left = -ratio;
mViewRect.right = ratio;
updatePageRects();

gl.glMatrixMode(GL10.GL_PROJECTION);
gl.glLoadIdentity();
if (USE_PERSPECTIVE_PROJECTION) {
GLU.gluPerspective(gl, 20f, (float) width / height, .1f, 100f);
} else {
GLU.gluOrtho2D(gl, mViewRect.left, mViewRect.right,
mViewRect.bottom, mViewRect.top);
}

gl.glMatrixMode(GL10.GL_MODELVIEW);
gl.glLoadIdentity();
}

@Override
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
gl.glClearColor(0f, 0f, 0f, 1f);
gl.glShadeModel(GL10.GL_SMOOTH);
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_NICEST);
gl.glHint(GL10.GL_LINE_SMOOTH_HINT, GL10.GL_NICEST);
gl.glHint(GL10.GL_POLYGON_SMOOTH_HINT, GL10.GL_NICEST);
gl.glEnable(GL10.GL_LINE_SMOOTH);
gl.glDisable(GL10.GL_DEPTH_TEST);
gl.glDisable(GL10.GL_CULL_FACE);

mObserver.onSurfaceCreated();
}

/**
* Removes CurlMesh from this renderer.
*/
public synchronized void removeCurlMesh(CurlMesh mesh) {
while (mCurlMeshes.remove(mesh))
;
}

/**
* Change background/clear color.
*/
public void setBackgroundColor(int color) {
mBackgroundColor = color;
}

/**
* Set margins or padding. Note: margins are proportional. Meaning a value
* of .1f will produce a 10% margin.
*/
public synchronized void setMargins(float left, float top, float right,
float bottom) {
mMargins.left = left;
mMargins.top = top;
mMargins.right = right;
mMargins.bottom = bottom;
updatePageRects();
}

/**
* Sets visible page count to one or two. Should be either SHOW_ONE_PAGE or
* SHOW_TWO_PAGES.
*/
public synchronized void setViewMode(int viewmode) {
if (viewmode == SHOW_ONE_PAGE) {
mViewMode = viewmode;
updatePageRects();
} else if (viewmode == SHOW_TWO_PAGES) {
mViewMode = viewmode;
updatePageRects();
}
}

/**
* Translates screen coordinates into view coordinates.
*/
public void translate(PointF pt) {
pt.x = mViewRect.left + (mViewRect.width() * pt.x / mViewportWidth);
pt.y = mViewRect.top - (-mViewRect.height() * pt.y / mViewportHeight);
}

/**
* Recalculates page rectangles.
*/
private void updatePageRects() {
if (mViewRect.width() == 0 || mViewRect.height() == 0) {
return;
} else if (mViewMode == SHOW_ONE_PAGE) {
mPageRectRight.set(mViewRect);
mPageRectRight.left += mViewRect.width() * mMargins.left;
mPageRectRight.right -= mViewRect.width() * mMargins.right;
mPageRectRight.top += mViewRect.height() * mMargins.top;
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom;

mPageRectLeft.set(mPageRectRight);
mPageRectLeft.offset(-mPageRectRight.width(), 0);

int bitmapW = (int) ((mPageRectRight.width() * mViewportWidth) / mViewRect
.width());
int bitmapH = (int) ((mPageRectRight.height() * mViewportHeight) / mViewRect
.height());
mObserver.onPageSizeChanged(bitmapW, bitmapH);
} else if (mViewMode == SHOW_TWO_PAGES) {
mPageRectRight.set(mViewRect);
mPageRectRight.left += mViewRect.width() * mMargins.left;
mPageRectRight.right -= mViewRect.width() * mMargins.right;
mPageRectRight.top += mViewRect.height() * mMargins.top;
mPageRectRight.bottom -= mViewRect.height() * mMargins.bottom;

mPageRectLeft.set(mPageRectRight);
mPageRectLeft.right = (mPageRectLeft.right + mPageRectLeft.left) / 2;
mPageRectRight.left = mPageRectLeft.right;

int bitmapW = (int) ((mPageRectRight.width() * mViewportWidth) / mViewRect
.width());
int bitmapH = (int) ((mPageRectRight.height() * mViewportHeight) / mViewRect
.height());
mObserver.onPageSizeChanged(bitmapW, bitmapH);
}
}

/**
* Observer for waiting render engine/state updates.
*/
public interface Observer {
/**
* Called from onDrawFrame called before rendering is started. This is
* intended to be used for animation purposes.
*/
public void onDrawFrame();

/**
* Called once page size is changed. Width and height tell the page size
* in pixels making it possible to update textures accordingly.
*/
public void onPageSizeChanged(int width, int height);

/**
* Called from onSurfaceCreated to enable texture re-initialization etc
* what needs to be done when this happens.
*/
public void onSurfaceCreated();
}
}


CurlPage.java

package com.example.pagecurl1;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.RectF;


public class CurlPage {

public static final int SIDE_BACK = 2;
public static final int SIDE_BOTH = 3;
public static final int SIDE_FRONT = 1;

private int mColorBack;
private int mColorFront;
private Bitmap mTextureBack;
private Bitmap mTextureFront;
private boolean mTexturesChanged;

/**
* Default constructor.
*/
public CurlPage() {
reset();
}

/**
* Getter for color.
*/
public int getColor(int side) {
switch (side) {
case SIDE_FRONT:
return mColorFront;
default:
return mColorBack;
}
}

/**
* Calculates the next highest power of two for a given integer.
*/
private int getNextHighestPO2(int n) {
n -= 1;
n = n | (n >> 1);
n = n | (n >> 2);
n = n | (n >> 4);
n = n | (n >> 8);
n = n | (n >> 16);
n = n | (n >> 32);
return n + 1;
}

/**
* Generates nearest power of two sized Bitmap for give Bitmap. Returns this
* new Bitmap using default return statement + original texture coordinates
* are stored into RectF.
*/
private Bitmap getTexture(Bitmap bitmap, RectF textureRect) {
// Bitmap original size.
int w = bitmap.getWidth();
int h = bitmap.getHeight();
// Bitmap size expanded to next power of two. This is done due to
// the requirement on many devices, texture width and height should
// be power of two.
int newW = getNextHighestPO2(w);
int newH = getNextHighestPO2(h);

// TODO: Is there another way to create a bigger Bitmap and copy
// original Bitmap to it more efficiently? Immutable bitmap anyone?
Bitmap bitmapTex = Bitmap.createBitmap(newW, newH, bitmap.getConfig());
Canvas c = new Canvas(bitmapTex);
c.drawBitmap(bitmap, 0, 0, null);

// Calculate final texture coordinates.
float texX = (float) w / newW;
float texY = (float) h / newH;
textureRect.set(0f, 0f, texX, texY);

return bitmapTex;
}

/**
* Getter for textures. Creates Bitmap sized to nearest power of two, copies
* original Bitmap into it and returns it. RectF given as parameter is
* filled with actual texture coordinates in this new upscaled texture
* Bitmap.
*/
public Bitmap getTexture(RectF textureRect, int side) {
switch (side) {
case SIDE_FRONT:
return getTexture(mTextureFront, textureRect);
default:
return getTexture(mTextureBack, textureRect);
}
}

/**
* Returns true if textures have changed.
*/
public boolean getTexturesChanged() {
return mTexturesChanged;
}

/**
* Returns true if back siding texture exists and it differs from front
* facing one.
*/
public boolean hasBackTexture() {
return !mTextureFront.equals(mTextureBack);
}

/**
* Recycles and frees underlying Bitmaps.
*/
public void recycle() {
if (mTextureFront != null) {
mTextureFront.recycle();
}
mTextureFront = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
mTextureFront.eraseColor(mColorFront);
if (mTextureBack != null) {
mTextureBack.recycle();
}
mTextureBack = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
mTextureBack.eraseColor(mColorBack);
mTexturesChanged = false;
}

/**
* Resets this CurlPage into its initial state.
*/
public void reset() {
mColorBack = Color.WHITE;
mColorFront = Color.WHITE;
recycle();
}

/**
* Setter blend color.
*/
public void setColor(int color, int side) {
switch (side) {
case SIDE_FRONT:
mColorFront = color;
break;
case SIDE_BACK:
mColorBack = color;
break;
default:
mColorFront = mColorBack = color;
break;
}
}

/**
* Setter for textures.
*/
public void setTexture(Bitmap texture, int side) {
if (texture == null) {
texture = Bitmap.createBitmap(1, 1, Bitmap.Config.RGB_565);
if (side == SIDE_BACK) {
texture.eraseColor(mColorBack);
} else {
texture.eraseColor(mColorFront);
}
}
switch (side) {
case SIDE_FRONT:
if (mTextureFront != null)
mTextureFront.recycle();
mTextureFront = texture;
break;
case SIDE_BACK:
if (mTextureBack != null)
mTextureBack.recycle();
mTextureBack = texture;
break;
case SIDE_BOTH:
if (mTextureFront != null)
mTextureFront.recycle();
if (mTextureBack != null)
mTextureBack.recycle();
mTextureFront = mTextureBack = texture;
break;
}
mTexturesChanged = true;
}

}


CurlMesh.java

package com.example.pagecurl1;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.RectF;
import android.opengl.GLUtils;

public class CurlMesh {

// Flag for rendering some lines used for developing. Shows
// curl position and one for the direction from the
// position given. Comes handy once playing around with different
// ways for following pointer.
private static final boolean DRAW_CURL_POSITION = false;
// Flag for drawing polygon outlines. Using this flag crashes on emulator
// due to reason unknown to me. Leaving it here anyway as seeing polygon
// outlines gives good insight how original rectangle is divided.
private static final boolean DRAW_POLYGON_OUTLINES = false;
// Flag for enabling shadow rendering.
private static final boolean DRAW_SHADOW = true;
// Flag for texture rendering. While this is likely something you
// don't want to do it's been used for development purposes as texture
// rendering is rather slow on emulator.
private static final boolean DRAW_TEXTURE = true;

// Colors for shadow. Inner one is the color drawn next to surface where
// shadowed area starts and outer one is color shadow ends to.
private static final float[] SHADOW_INNER_COLOR = { 0f, 0f, 0f, .5f };
private static final float[] SHADOW_OUTER_COLOR = { 0f, 0f, 0f, .0f };

// Let's avoid using 'new' as much as possible. Meaning we introduce arrays
// once here and reuse them on runtime. Doesn't really have very much effect
// but avoids some garbage collections from happening.
private Array<ShadowVertex> mArrDropShadowVertices;
private Array<Vertex> mArrIntersections;
private Array<Vertex> mArrOutputVertices;
private Array<Vertex> mArrRotatedVertices;
private Array<Double> mArrScanLines;
private Array<ShadowVertex> mArrSelfShadowVertices;
private Array<ShadowVertex> mArrTempShadowVertices;
private Array<Vertex> mArrTempVertices;

// Buffers for feeding rasterizer.
private FloatBuffer mBufColors;
private FloatBuffer mBufCurlPositionLines;
private FloatBuffer mBufShadowColors;
private FloatBuffer mBufShadowVertices;
private FloatBuffer mBufTexCoords;
private FloatBuffer mBufVertices;

private int mCurlPositionLinesCount;
private int mDropShadowCount;

// Boolean for 'flipping' texture sideways.
private boolean mFlipTexture = false;
// Maximum number of split lines used for creating a curl.
private int mMaxCurlSplits;

// Bounding rectangle for this mesh. mRectagle[0] = top-left corner,
// mRectangle[1] = bottom-left, mRectangle[2] = top-right and mRectangle[3]
// bottom-right.
private final Vertex[] mRectangle = new Vertex[4];
private int mSelfShadowCount;

private boolean mTextureBack = false;
// Texture ids and other variables.
private int[] mTextureIds = null;
private final CurlPage mTexturePage = new CurlPage();
private final RectF mTextureRectBack = new RectF();
private final RectF mTextureRectFront = new RectF();

private int mVerticesCountBack;
private int mVerticesCountFront;

/**
* Constructor for mesh object.
* @param maxCurlSplits
*            Maximum number curl can be divided into. The bigger the value
*            the smoother curl will be. With the cost of having more
*            polygons for drawing.
*/
public CurlMesh(int maxCurlSplits) {
// There really is no use for 0 splits.
mMaxCurlSplits = maxCurlSplits < 1 ? 1 : maxCurlSplits;

mArrScanLines = new Array<Double>(maxCurlSplits + 2);
mArrOutputVertices = new Array<Vertex>(7);
mArrRotatedVertices = new Array<Vertex>(4);
mArrIntersections = new Array<Vertex>(2);
mArrTempVertices = new Array<Vertex>(7 + 4);
for (int i = 0; i < 7 + 4; ++i) {
mArrTempVertices.add(new Vertex());
}

if (DRAW_SHADOW) {
mArrSelfShadowVertices = new Array<ShadowVertex>(
(mMaxCurlSplits + 2) * 2);
mArrDropShadowVertices = new Array<ShadowVertex>(
(mMaxCurlSplits + 2) * 2);
mArrTempShadowVertices = new Array<ShadowVertex>(
(mMaxCurlSplits + 2) * 2);
for (int i = 0; i < (mMaxCurlSplits + 2) * 2; ++i) {
mArrTempShadowVertices.add(new ShadowVertex());
}
}

// Rectangle consists of 4 vertices. Index 0 = top-left, index 1 =
// bottom-left, index 2 = top-right and index 3 = bottom-right.
for (int i = 0; i < 4; ++i) {
mRectangle[i] = new Vertex();
}
// Set up shadow penumbra direction to each vertex. We do fake 'self
// shadow' calculations based on this information.
mRectangle[0].mPenumbraX = mRectangle[1].mPenumbraX = mRectangle[1].mPenumbraY = mRectangle[3].mPenumbraY = -1;
mRectangle[0].mPenumbraY = mRectangle[2].mPenumbraX = mRectangle[2].mPenumbraY = mRectangle[3].mPenumbraX = 1;

if (DRAW_CURL_POSITION) {
mCurlPositionLinesCount = 3;
ByteBuffer hvbb = ByteBuffer
.allocateDirect(mCurlPositionLinesCount * 2 * 2 * 4);
hvbb.order(ByteOrder.nativeOrder());
mBufCurlPositionLines = hvbb.asFloatBuffer();
mBufCurlPositionLines.position(0);
}

// There are 4 vertices from bounding rect, max 2 from adding split line
// to two corners and curl consists of max mMaxCurlSplits lines each
// outputting 2 vertices.
int maxVerticesCount = 6 + 2 + (2 * mMaxCurlSplits);
ByteBuffer vbb = ByteBuffer.allocateDirect(maxVerticesCount * 3 * 4);
vbb.order(ByteOrder.nativeOrder());
mBufVertices = vbb.asFloatBuffer();
mBufVertices.position(0);

if (DRAW_TEXTURE) {
ByteBuffer tbb = ByteBuffer
.allocateDirect(maxVerticesCount * 2 * 4);
tbb.order(ByteOrder.nativeOrder());
mBufTexCoords = tbb.asFloatBuffer();
mBufTexCoords.position(0);
}

ByteBuffer cbb = ByteBuffer.allocateDirect(maxVerticesCount * 4 * 4);
cbb.order(ByteOrder.nativeOrder());
mBufColors = cbb.asFloatBuffer();
mBufColors.position(0);

if (DRAW_SHADOW) {
int maxShadowVerticesCount = (mMaxCurlSplits + 2) * 2 * 2;
ByteBuffer scbb = ByteBuffer
.allocateDirect(maxShadowVerticesCount * 4 * 4);
scbb.order(ByteOrder.nativeOrder());
mBufShadowColors = scbb.asFloatBuffer();
mBufShadowColors.position(0);

ByteBuffer sibb = ByteBuffer
.allocateDirect(maxShadowVerticesCount * 3 * 4);
sibb.order(ByteOrder.nativeOrder());
mBufShadowVertices = sibb.asFloatBuffer();
mBufShadowVertices.position(0);

mDropShadowCount = mSelfShadowCount = 0;
}
}

/**
* Adds vertex to buffers.
*/
private void addVertex(Vertex vertex) {
mBufVertices.put((float) vertex.mPosX);
mBufVertices.put((float) vertex.mPosY);
mBufVertices.put((float) vertex.mPosZ);
mBufColors.put(vertex.mColorFactor * Color.red(vertex.mColor) / 255f);
mBufColors.put(vertex.mColorFactor * Color.green(vertex.mColor) / 255f);
mBufColors.put(vertex.mColorFactor * Color.blue(vertex.mColor) / 255f);
mBufColors.put(Color.alpha(vertex.mColor) / 255f);
if (DRAW_TEXTURE) {
mBufTexCoords.put((float) vertex.mTexX);
mBufTexCoords.put((float) vertex.mTexY);
}
}

/**
* Sets curl for this mesh.
* @param curlPos
*            Position for curl 'center'. Can be any point on line collinear
*            to curl.
* @param curlDir
*            Curl direction, should be normalized.
* @param radius
*            Radius of curl.
*/
public synchronized void curl(PointF curlPos, PointF curlDir, double radius) {

// First add some 'helper' lines used for development.
if (DRAW_CURL_POSITION) {
mBufCurlPositionLines.position(0);

mBufCurlPositionLines.put(curlPos.x);
mBufCurlPositionLines.put(curlPos.y - 1.0f);
mBufCurlPositionLines.put(curlPos.x);
mBufCurlPositionLines.put(curlPos.y + 1.0f);
mBufCurlPositionLines.put(curlPos.x - 1.0f);
mBufCurlPositionLines.put(curlPos.y);
mBufCurlPositionLines.put(curlPos.x + 1.0f);
mBufCurlPositionLines.put(curlPos.y);

mBufCurlPositionLines.put(curlPos.x);
mBufCurlPositionLines.put(curlPos.y);
mBufCurlPositionLines.put(curlPos.x + curlDir.x * 2);
mBufCurlPositionLines.put(curlPos.y + curlDir.y * 2);

mBufCurlPositionLines.position(0);
}

// Actual 'curl' implementation starts here.
mBufVertices.position(0);
mBufColors.position(0);
if (DRAW_TEXTURE) {
mBufTexCoords.position(0);
}

// Calculate curl angle from direction.
double curlAngle = Math.acos(curlDir.x);
curlAngle = curlDir.y > 0 ? -curlAngle : curlAngle;

// Initiate rotated rectangle which's is translated to curlPos and
// rotated so that curl direction heads to right (1,0). Vertices are
// ordered in ascending order based on x -coordinate at the same time.
// And using y -coordinate in very rare case in which two vertices have
// same x -coordinate.
mArrTempVertices.addAll(mArrRotatedVertices);
mArrRotatedVertices.clear();
for (int i = 0; i < 4; ++i) {
Vertex v = mArrTempVertices.remove(0);
v.set(mRectangle[i]);
v.translate(-curlPos.x, -curlPos.y);
v.rotateZ(-curlAngle);
int j = 0;
for (; j < mArrRotatedVertices.size(); ++j) {
Vertex v2 = mArrRotatedVertices.get(j);
if (v.mPosX > v2.mPosX) {
break;
}
if (v.mPosX == v2.mPosX && v.mPosY > v2.mPosY) {
break;
}
}
mArrRotatedVertices.add(j, v);
}

// Rotated rectangle lines/vertex indices. We need to find bounding
// lines for rotated rectangle. After sorting vertices according to
// their x -coordinate we don't have to worry about vertices at indices
// 0 and 1. But due to inaccuracy it's possible vertex 3 is not the
// opposing corner from vertex 0. So we are calculating distance from
// vertex 0 to vertices 2 and 3 - and altering line indices if needed.
// Also vertices/lines are given in an order first one has x -coordinate
// at least the latter one. This property is used in getIntersections to
// see if there is an intersection.
int lines[][] = { { 0, 1 }, { 0, 2 }, { 1, 3 }, { 2, 3 } };
{
// TODO: There really has to be more 'easier' way of doing this -
// not including extensive use of sqrt.
Vertex v0 = mArrRotatedVertices.get(0);
Vertex v2 = mArrRotatedVertices.get(2);
Vertex v3 = mArrRotatedVertices.get(3);
double dist2 = Math.sqrt((v0.mPosX - v2.mPosX)
* (v0.mPosX - v2.mPosX) + (v0.mPosY - v2.mPosY)
* (v0.mPosY - v2.mPosY));
double dist3 = Math.sqrt((v0.mPosX - v3.mPosX)
* (v0.mPosX - v3.mPosX) + (v0.mPosY - v3.mPosY)
* (v0.mPosY - v3.mPosY));
if (dist2 > dist3) {
lines[1][1] = 3;
lines[2][1] = 2;
}
}

mVerticesCountFront = mVerticesCountBack = 0;

if (DRAW_SHADOW) {
mArrTempShadowVertices.addAll(mArrDropShadowVertices);
mArrTempShadowVertices.addAll(mArrSelfShadowVertices);
mArrDropShadowVertices.clear();
mArrSelfShadowVertices.clear();
}

// Length of 'curl' curve.
double curlLength = Math.PI * radius;
// Calculate scan lines.
// TODO: Revisit this code one day. There is room for optimization here.
mArrScanLines.clear();
if (mMaxCurlSplits > 0) {
mArrScanLines.add((double) 0);
}
for (int i = 1; i < mMaxCurlSplits; ++i) {
mArrScanLines.add((-curlLength * i) / (mMaxCurlSplits - 1));
}
// As mRotatedVertices is ordered regarding x -coordinate, adding
// this scan line produces scan area picking up vertices which are
// rotated completely. One could say 'until infinity'.
mArrScanLines.add(mArrRotatedVertices.get(3).mPosX - 1);

// Start from right most vertex. Pretty much the same as first scan area
// is starting from 'infinity'.
double scanXmax = mArrRotatedVertices.get(0).mPosX + 1;

for (int i = 0; i < mArrScanLines.size(); ++i) {
// Once we have scanXmin and scanXmax we have a scan area to start
// working with.
double scanXmin = mArrScanLines.get(i);
// First iterate 'original' rectangle vertices within scan area.
for (int j = 0; j < mArrRotatedVertices.size(); ++j) {
Vertex v = mArrRotatedVertices.get(j);
// Test if vertex lies within this scan area.
// TODO: Frankly speaking, can't remember why equality check was
// added to both ends. Guessing it was somehow related to case
// where radius=0f, which, given current implementation, could
// be handled much more effectively anyway.
if (v.mPosX >= scanXmin && v.mPosX <= scanXmax) {
// Pop out a vertex from temp vertices.
Vertex n = mArrTempVertices.remove(0);
n.set(v);
// This is done solely for triangulation reasons. Given a
// rotated rectangle it has max 2 vertices having
// intersection.
Array<Vertex> intersections = getIntersections(
mArrRotatedVertices, lines, n.mPosX);
// In a sense one could say we're adding vertices always in
// two, positioned at the ends of intersecting line. And for
// triangulation to work properly they are added based on y
// -coordinate. And this if-else is doing it for us.
if (intersections.size() == 1
&& intersections.get(0).mPosY > v.mPosY) {
// In case intersecting vertex is higher add it first.
mArrOutputVertices.addAll(intersections);
mArrOutputVertices.add(n);
} else if (intersections.size() <= 1) {
// Otherwise add original vertex first.
mArrOutputVertices.add(n);
mArrOutputVertices.addAll(intersections);
} else {
// There should never be more than 1 intersecting
// vertex. But if it happens as a fallback simply skip
// everything.
mArrTempVertices.add(n);
mArrTempVertices.addAll(intersections);
}
}
}

// Search for scan line intersections.
Array<Vertex> intersections = getIntersections(mArrRotatedVertices,
lines, scanXmin);

// We expect to get 0 or 2 vertices. In rare cases there's only one
// but in general given a scan line intersecting rectangle there
// should be 2 intersecting vertices.
if (intersections.size() == 2) {
// There were two intersections, add them based on y
// -coordinate, higher first, lower last.
Vertex v1 = intersections.get(0);
Vertex v2 = intersections.get(1);
if (v1.mPosY < v2.mPosY) {
mArrOutputVertices.add(v2);
mArrOutputVertices.add(v1);
} else {
mArrOutputVertices.addAll(intersections);
}
} else if (intersections.size() != 0) {
// This happens in a case in which there is a original vertex
// exactly at scan line or something went very much wrong if
// there are 3+ vertices. What ever the reason just return the
// vertices to temp vertices for later use. In former case it
// was handled already earlier once iterating through
// mRotatedVertices, in latter case it's better to avoid doing
// anything with them.
mArrTempVertices.addAll(intersections);
}

// Add vertices found during this iteration to vertex etc buffers.
while (mArrOutputVertices.size() > 0) {
Vertex v = mArrOutputVertices.remove(0);
mArrTempVertices.add(v);

// Local texture front-facing flag.
boolean textureFront;

// Untouched vertices.
if (i == 0) {
textureFront = true;
mVerticesCountFront++;
}
// 'Completely' rotated vertices.
else if (i == mArrScanLines.size() - 1 || curlLength == 0) {
v.mPosX = -(curlLength + v.mPosX);
v.mPosZ = 2 * radius;
v.mPenumbraX = -v.mPenumbraX;

textureFront = false;
mVerticesCountBack++;
}
// Vertex lies within 'curl'.
else {
// Even though it's not obvious from the if-else clause,
// here v.mPosX is between [-curlLength, 0]. And we can do
// calculations around a half cylinder.
double rotY = Math.PI * (v.mPosX / curlLength);
v.mPosX = radius * Math.sin(rotY);
v.mPosZ = radius - (radius * Math.cos(rotY));
v.mPenumbraX *= Math.cos(rotY);
// Map color multiplier to [.1f, 1f] range.
v.mColorFactor = (float) (.1f + .9f * Math.sqrt(Math
.sin(rotY) + 1));

if (v.mPosZ >= radius) {
textureFront = false;
mVerticesCountBack++;
} else {
textureFront = true;
mVerticesCountFront++;
}
}

// We use local textureFront for flipping backside texture
// locally. Plus additionally if mesh is in flip texture mode,
// we'll make the procedure "backwards". Also, until this point,
// texture coordinates are within [0, 1] range so we'll adjust
// them to final texture coordinates too.
if (textureFront != mFlipTexture) {
v.mTexX *= mTextureRectFront.right;
v.mTexY *= mTextureRectFront.bottom;
v.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT);
} else {
v.mTexX *= mTextureRectBack.right;
v.mTexY *= mTextureRectBack.bottom;
v.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK);
}

// Move vertex back to 'world' coordinates.
v.rotateZ(curlAngle);
v.translate(curlPos.x, curlPos.y);
addVertex(v);

// Drop shadow is cast 'behind' the curl.
if (DRAW_SHADOW && v.mPosZ > 0 && v.mPosZ <= radius) {
ShadowVertex sv = mArrTempShadowVertices.remove(0);
sv.mPosX = v.mPosX;
sv.mPosY = v.mPosY;
sv.mPosZ = v.mPosZ;
sv.mPenumbraX = (v.mPosZ / 2) * -curlDir.x;
sv.mPenumbraY = (v.mPosZ / 2) * -curlDir.y;
sv.mPenumbraColor = v.mPosZ / radius;
int idx = (mArrDropShadowVertices.size() + 1) / 2;
mArrDropShadowVertices.add(idx, sv);
}
// Self shadow is cast partly over mesh.
if (DRAW_SHADOW && v.mPosZ > radius) {
ShadowVertex sv = mArrTempShadowVertices.remove(0);
sv.mPosX = v.mPosX;
sv.mPosY = v.mPosY;
sv.mPosZ = v.mPosZ;
sv.mPenumbraX = ((v.mPosZ - radius) / 3) * v.mPenumbraX;
sv.mPenumbraY = ((v.mPosZ - radius) / 3) * v.mPenumbraY;
sv.mPenumbraColor = (v.mPosZ - radius) / (2 * radius);
int idx = (mArrSelfShadowVertices.size() + 1) / 2;
mArrSelfShadowVertices.add(idx, sv);
}
}

// Switch scanXmin as scanXmax for next iteration.
scanXmax = scanXmin;
}

mBufVertices.position(0);
mBufColors.position(0);
if (DRAW_TEXTURE) {
mBufTexCoords.position(0);
}

// Add shadow Vertices.
if (DRAW_SHADOW) {
mBufShadowColors.position(0);
mBufShadowVertices.position(0);
mDropShadowCount = 0;

for (int i = 0; i < mArrDropShadowVertices.size(); ++i) {
ShadowVertex sv = mArrDropShadowVertices.get(i);
mBufShadowVertices.put((float) sv.mPosX);
mBufShadowVertices.put((float) sv.mPosY);
mBufShadowVertices.put((float) sv.mPosZ);
mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX));
mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY));
mBufShadowVertices.put((float) sv.mPosZ);
for (int j = 0; j < 4; ++j) {
double color = SHADOW_OUTER_COLOR[j]
+ (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j])
* sv.mPenumbraColor;
mBufShadowColors.put((float) color);
}
mBufShadowColors.put(SHADOW_OUTER_COLOR);
mDropShadowCount += 2;
}
mSelfShadowCount = 0;
for (int i = 0; i < mArrSelfShadowVertices.size(); ++i) {
ShadowVertex sv = mArrSelfShadowVertices.get(i);
mBufShadowVertices.put((float) sv.mPosX);
mBufShadowVertices.put((float) sv.mPosY);
mBufShadowVertices.put((float) sv.mPosZ);
mBufShadowVertices.put((float) (sv.mPosX + sv.mPenumbraX));
mBufShadowVertices.put((float) (sv.mPosY + sv.mPenumbraY));
mBufShadowVertices.put((float) sv.mPosZ);
for (int j = 0; j < 4; ++j) {
double color = SHADOW_OUTER_COLOR[j]
+ (SHADOW_INNER_COLOR[j] - SHADOW_OUTER_COLOR[j])
* sv.mPenumbraColor;
mBufShadowColors.put((float) color);
}
mBufShadowColors.put(SHADOW_OUTER_COLOR);
mSelfShadowCount += 2;
}
mBufShadowColors.position(0);
mBufShadowVertices.position(0);
}
}

/**
* Calculates intersections for given scan line.
*/
private Array<Vertex> getIntersections(Array<Vertex> vertices,
int[][] lineIndices, double scanX) {
mArrIntersections.clear();
// Iterate through rectangle lines each re-presented as a pair of
// vertices.
for (int j = 0; j < lineIndices.length; j++) {
Vertex v1 = vertices.get(lineIndices[j][0]);
Vertex v2 = vertices.get(lineIndices[j][1]);
// Here we expect that v1.mPosX >= v2.mPosX and wont do intersection
// test the opposite way.
if (v1.mPosX > scanX && v2.mPosX < scanX) {
// There is an intersection, calculate coefficient telling 'how
// far' scanX is from v2.
double c = (scanX - v2.mPosX) / (v1.mPosX - v2.mPosX);
Vertex n = mArrTempVertices.remove(0);
n.set(v2);
n.mPosX = scanX;
n.mPosY += (v1.mPosY - v2.mPosY) * c;
if (DRAW_TEXTURE) {
n.mTexX += (v1.mTexX - v2.mTexX) * c;
n.mTexY += (v1.mTexY - v2.mTexY) * c;
}
if (DRAW_SHADOW) {
n.mPenumbraX += (v1.mPenumbraX - v2.mPenumbraX) * c;
n.mPenumbraY += (v1.mPenumbraY - v2.mPenumbraY) * c;
}
mArrIntersections.add(n);
}
}
return mArrIntersections;
}

/**
* Getter for textures page for this mesh.
*/
public synchronized CurlPage getTexturePage() {
return mTexturePage;
}

/**
* Renders our page curl mesh.
*/
public synchronized void onDrawFrame(GL10 gl) {
// First allocate texture if there is not one yet.
if (DRAW_TEXTURE && mTextureIds == null) {
// Generate texture.
mTextureIds = new int[2];
gl.glGenTextures(2, mTextureIds, 0);
for (int textureId : mTextureIds) {
// Set texture attributes.
gl.glBindTexture(GL10.GL_TEXTURE_2D, textureId);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D,
GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S,
GL10.GL_CLAMP_TO_EDGE);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T,
GL10.GL_CLAMP_TO_EDGE);
}
}

if (DRAW_TEXTURE && mTexturePage.getTexturesChanged()) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]);
Bitmap texture = mTexturePage.getTexture(mTextureRectFront,
CurlPage.SIDE_FRONT);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0);
texture.recycle();

mTextureBack = mTexturePage.hasBackTexture();
if (mTextureBack) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]);
texture = mTexturePage.getTexture(mTextureRectBack,
CurlPage.SIDE_BACK);
GLUtils.texImage2D(GL10.GL_TEXTURE_2D, 0, texture, 0);
texture.recycle();
} else {
mTextureRectBack.set(mTextureRectFront);
}

mTexturePage.recycle();
reset();
}

// Some 'global' settings.
gl.glEnableClientState(GL10.GL_VERTEX_ARRAY);

// TODO: Drop shadow drawing is done temporarily here to hide some
// problems with its calculation.
if (DRAW_SHADOW) {
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mDropShadowCount);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
gl.glDisable(GL10.GL_BLEND);
}

if (DRAW_TEXTURE) {
gl.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mBufTexCoords);
}
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices);
// Enable color array.
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufColors);

// Draw front facing blank vertices.
gl.glDisable(GL10.GL_TEXTURE_2D);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront);

// Draw front facing texture.
if (DRAW_TEXTURE) {
gl.glEnable(GL10.GL_BLEND);
gl.glEnable(GL10.GL_TEXTURE_2D);

if (!mFlipTexture || !mTextureBack) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]);
} else {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]);
}

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, mVerticesCountFront);

gl.glDisable(GL10.GL_BLEND);
gl.glDisable(GL10.GL_TEXTURE_2D);
}

int backStartIdx = Math.max(0, mVerticesCountFront - 2);
int backCount = mVerticesCountFront + mVerticesCountBack - backStartIdx;

// Draw back facing blank vertices.
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount);

// Draw back facing texture.
if (DRAW_TEXTURE) {
gl.glEnable(GL10.GL_BLEND);
gl.glEnable(GL10.GL_TEXTURE_2D);

if (mFlipTexture || !mTextureBack) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[0]);
} else {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIds[1]);
}

gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, backStartIdx, backCount);

gl.glDisable(GL10.GL_BLEND);
gl.glDisable(GL10.GL_TEXTURE_2D);
}

// Disable textures and color array.
gl.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);

if (DRAW_POLYGON_OUTLINES) {
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glLineWidth(1.0f);
gl.glColor4f(0.5f, 0.5f, 1.0f, 1.0f);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufVertices);
gl.glDrawArrays(GL10.GL_LINE_STRIP, 0, mVerticesCountFront);
gl.glDisable(GL10.GL_BLEND);
}

if (DRAW_CURL_POSITION) {
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glLineWidth(1.0f);
gl.glColor4f(1.0f, 0.5f, 0.5f, 1.0f);
gl.glVertexPointer(2, GL10.GL_FLOAT, 0, mBufCurlPositionLines);
gl.glDrawArrays(GL10.GL_LINES, 0, mCurlPositionLinesCount * 2);
gl.glDisable(GL10.GL_BLEND);
}

if (DRAW_SHADOW) {
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
gl.glEnableClientState(GL10.GL_COLOR_ARRAY);
gl.glColorPointer(4, GL10.GL_FLOAT, 0, mBufShadowColors);
gl.glVertexPointer(3, GL10.GL_FLOAT, 0, mBufShadowVertices);
gl.glDrawArrays(GL10.GL_TRIANGLE_STRIP, mDropShadowCount,
mSelfShadowCount);
gl.glDisableClientState(GL10.GL_COLOR_ARRAY);
gl.glDisable(GL10.GL_BLEND);
}

gl.glDisableClientState(GL10.GL_VERTEX_ARRAY);
}

/**
* Resets mesh to 'initial' state. Meaning this mesh will draw a plain
* textured rectangle after call to this method.
*/
public synchronized void reset() {
mBufVertices.position(0);
mBufColors.position(0);
if (DRAW_TEXTURE) {
mBufTexCoords.position(0);
}
for (int i = 0; i < 4; ++i) {
Vertex tmp = mArrTempVertices.get(0);
tmp.set(mRectangle[i]);

if (mFlipTexture) {
tmp.mTexX *= mTextureRectBack.right;
tmp.mTexY *= mTextureRectBack.bottom;
tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_BACK);
} else {
tmp.mTexX *= mTextureRectFront.right;
tmp.mTexY *= mTextureRectFront.bottom;
tmp.mColor = mTexturePage.getColor(CurlPage.SIDE_FRONT);
}

addVertex(tmp);
}
mVerticesCountFront = 4;
mVerticesCountBack = 0;
mBufVertices.position(0);
mBufColors.position(0);
if (DRAW_TEXTURE) {
mBufTexCoords.position(0);
}

mDropShadowCount = mSelfShadowCount = 0;
}

/**
* Resets allocated texture id forcing creation of new one. After calling
* this method you most likely want to set bitmap too as it's lost. This
* method should be called only once e.g GL context is re-created as this
* method does not release previous texture id, only makes sure new one is
* requested on next render.
*/
public synchronized void resetTexture() {
mTextureIds = null;
}

/**
* If true, flips texture sideways.
*/
public synchronized void setFlipTexture(boolean flipTexture) {
mFlipTexture = flipTexture;
if (flipTexture) {
setTexCoords(1f, 0f, 0f, 1f);
} else {
setTexCoords(0f, 0f, 1f, 1f);
}
}

/**
* Update mesh bounds.
*/
public void setRect(RectF r) {
mRectangle[0].mPosX = r.left;
mRectangle[0].mPosY = r.top;
mRectangle[1].mPosX = r.left;
mRectangle[1].mPosY = r.bottom;
mRectangle[2].mPosX = r.right;
mRectangle[2].mPosY = r.top;
mRectangle[3].mPosX = r.right;
mRectangle[3].mPosY = r.bottom;
}

/**
* Sets texture coordinates to mRectangle vertices.
*/
private synchronized void setTexCoords(float left, float top, float right,
float bottom) {
mRectangle[0].mTexX = left;
mRectangle[0].mTexY = top;
mRectangle[1].mTexX = left;
mRectangle[1].mTexY = bottom;
mRectangle[2].mTexX = right;
mRectangle[2].mTexY = top;
mRectangle[3].mTexX = right;
mRectangle[3].mTexY = bottom;
}

/**
* Simple fixed size array implementation.
*/
private class Array<T> {
private Object[] mArray;
private int mCapacity;
private int mSize;

public Array(int capacity) {
mCapacity = capacity;
mArray = new Object[capacity];
}

public void add(int index, T item) {
if (index < 0 || index > mSize || mSize >= mCapacity) {
throw new IndexOutOfBoundsException();
}
for (int i = mSize; i > index; --i) {
mArray[i] = mArray[i - 1];
}
mArray[index] = item;
++mSize;
}

public void add(T item) {
if (mSize >= mCapacity) {
throw new IndexOutOfBoundsException();
}
mArray[mSize++] = item;
}

public void addAll(Array<T> array) {
if (mSize + array.size() > mCapacity) {
throw new IndexOutOfBoundsException();
}
for (int i = 0; i < array.size(); ++i) {
mArray[mSize++] = array.get(i);
}
}

public void clear() {
mSize = 0;
}

@SuppressWarnings("unchecked")
public T get(int index) {
if (index < 0 || index >= mSize) {
throw new IndexOutOfBoundsException();
}
return (T) mArray[index];
}

@SuppressWarnings("unchecked")
public T remove(int index) {
if (index < 0 || index >= mSize) {
throw new IndexOutOfBoundsException();
}
T item = (T) mArray[index];
for (int i = index; i < mSize - 1; ++i) {
mArray[i] = mArray[i + 1];
}
--mSize;
return item;
}

public int size() {
return mSize;
}

}

/**
* Holder for shadow vertex information.
*/
private class ShadowVertex {
public double mPenumbraColor;
public double mPenumbraX;
public double mPenumbraY;
public double mPosX;
public double mPosY;
public double mPosZ;
}

/**
* Holder for vertex information.
*/
private class Vertex {
public int mColor;
public float mColorFactor;
public double mPenumbraX;
public double mPenumbraY;
public double mPosX;
public double mPosY;
public double mPosZ;
public double mTexX;
public double mTexY;

public Vertex() {
mPosX = mPosY = mPosZ = mTexX = mTexY = 0;
mColorFactor = 1.0f;
}

public void rotateZ(double theta) {
double cos = Math.cos(theta);
double sin = Math.sin(theta);
double x = mPosX * cos + mPosY * sin;
double y = mPosX * -sin + mPosY * cos;
mPosX = x;
mPosY = y;
double px = mPenumbraX * cos + mPenumbraY * sin;
double py = mPenumbraX * -sin + mPenumbraY * cos;
mPenumbraX = px;
mPenumbraY = py;
}

public void set(Vertex vertex) {
mPosX = vertex.mPosX;
mPosY = vertex.mPosY;
mPosZ = vertex.mPosZ;
mTexX = vertex.mTexX;
mTexY = vertex.mTexY;
mPenumbraX = vertex.mPenumbraX;
mPenumbraY = vertex.mPenumbraY;
mColor = vertex.mColor;
mColorFactor = vertex.mColorFactor;
}

public void translate(double dx, double dy) {
mPosX += dx;
mPosY += dy;
}
}
}