Page Curl effect with Image and Text in Android

1. Create a new project with package name – com.example.curlpagetutorial
2. Edit the 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" >

     <minh.app.mbook.MyLayout
        android:id="@+id/mainpage"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="1"
        android:background="#FFf3c5d6"
        android:orientation="vertical" />

     <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="9"
        android:gravity="bottom"
        android:orientation="horizontal" >
     <Button
            android:id="@+id/btn_pre"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="Back" />
     <Button
            android:id="@+id/btn_next"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:text="Next" />
    </LinearLayout>
</LinearLayout>

3. Create page1.xml

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

    <ImageView
        android:id="@+id/home"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:background="@drawable/cloud" />

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_weight="9"
        android:gravity="bottom"
        android:orientation="horizontal" >

        <Button
            android:id="@+id/btn_pre"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:background="@android:color/transparent"
            android:text="Back" />

        <Button
            android:id="@+id/btn_next"
            android:layout_width="fill_parent"
            android:layout_height="fill_parent"
            android:layout_weight="1"
            android:background="@android:color/transparent"
            android:text="Next" />
    </LinearLayout>
</LinearLayout

4. Create another page2.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_margin="10dp"
        android:text="@string/text"
        android:textSize="20sp"
        android:textStyle="bold" />

</LinearLayout>

5. Now create a new package with name com.example.curlpagetutorial.layout
6. Create a new class in com.example.curlpagetutorial.layout named CustomLayout.java

package com.example.curlpagetutorial.layout;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.RelativeLayout;

public abstract class CustomLayout extends RelativeLayout {
    
    public CustomLayout(Context context) {
        super(context);
        
    }

    public CustomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        try {
            // Init game
            getPageContent();            
        } catch (Exception e) {
            // bug
            e.printStackTrace();
        }
    }

    //
    abstract protected void getPageContent();
}

7. Now create a class for example, TestLayout.java under package name com.example.curlpagetutorial and import the CustomLayout.java class

package com.example.curlpagetutorial;

import com.example.curlpagetutorial.layout.CustomLayout;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ScrollView;

public class MyLayout extends CustomLayout{
    // Game name
    public static final String NAME = "SpaceBlaster";
    private Context mContext;
    //
    private int screenWidth;
    private int screenHeight;
    private int totalPages;
    //
    private MyBookView mBookView;
    public MyLayout(Context context) {
        super(context);
        mContext = context;
        init();
    }
    public MyLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        init();
    }
    private ScrollView scrollView;
    //
    private void init() {
        totalPages = 2;
        Display display = ((WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();        
        screenWidth = display.getWidth();
        screenHeight = display.getHeight();
        //
        mBookView = new MyBookView(mContext, screenWidth, screenHeight);
        // prepare view to take all screenshoot
        LinearLayout ll = new LinearLayout(mContext);
        ll.setOrientation(LinearLayout.VERTICAL);
        
        LayoutInflater factory = LayoutInflater.from(mContext);    
        View view = factory.inflate(R.layout.page1, null);                
        ll.addView(view, screenWidth,screenHeight);        
        View view2 = factory.inflate(R.layout.page2, null);        
        ll.addView(view2, screenWidth,screenHeight);
        
        // hide scrollview's component to have clear bitmap
        scrollView = new ScrollView(mContext);
        scrollView.addView(ll);
        scrollView.setVerticalScrollBarEnabled(false);
        scrollView.setHorizontalScrollBarEnabled(false);        
        scrollView.setFadingEdgeLength(0);
        addView(scrollView);
        
        // add true view and some actions
        View view3 = factory.inflate(R.layout.page1, null);        
        addView(view3, screenWidth, screenHeight);
        
        View view4 = factory.inflate(R.layout.page2, null);        
        addView(view4, screenWidth, screenHeight);

        addView(mPoemView);        
        mPoemView.setView(view3, view4);                
    }
    //
    public void next(){
        mPoemView.next();
    }
    //
    public void pre(){
        mPoemView.pre();
    }

    private Bitmap mBitmap;
    private Bitmap page1, page2;

    public void getPageContent() {
        if(mBitmap==null){
            setBitmap();
        }
    }
    private void setBitmap() {        
        mBitmap = Bitmap.createBitmap(screenWidth, screenHeight*totalPages, Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(mBitmap);
        scrollView.draw(canvas);

        page1 = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas1 = new Canvas(page1);
        canvas1.drawBitmap(mBitmap, 0, 0, new Paint());
        page2 = Bitmap.createBitmap(screenWidth, screenHeight, Bitmap.Config.ARGB_8888);
        Canvas canvas2 = new Canvas(page2);
        canvas2.drawBitmap(mBitmap, 0, -screenHeight, new Paint());
        //removeAllViews();
        scrollView.setVisibility(View.GONE);
        //
        mPoemView.init(page1, page2);
        //
    }    
}

8. Now create a class MyBookView.java

package com.example.curlpagetutorial;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class MyBookView extends View{
    // MyHandler myHandler = new MyHandler();
    private static int DEFAULT_FLIP_VALUE = 20;
    private static int FLIP_SPEED = 30;
    private long mMoveDelay = 1000 / 30;

    float xTouchValue = DEFAULT_FLIP_VALUE, yTouchValue = DEFAULT_FLIP_VALUE;

    // String result = "";
    class FlippingHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            Log.i("Thong bao: ", "Clock Handler is still running");
            MyPoemView.this.flip();
        }

        public void sleep(long delayMillis) {
            this.removeMessages(0);
            sendMessageDelayed(obtainMessage(0), delayMillis);
        }
    }

    FlippingHandler flippingHandler;
    int width;
    int height;
    float oldTouchX, oldTouchY;
    boolean flipping = false;
    boolean next;
    Point A, B, C, D, E, F;
    Bitmap visiblePage;
    Bitmap invisiblePage;
    Paint flipPagePaint;
    boolean flip = false;
    Context context;
    int loadedPages = 0;
    long timeToLoad = 0;
    // boolean loadingDone = false;
    boolean onloading = true;
    boolean onMoving = false;

    public MyBookView(Context context, int width, int height) {
        super(context);
        this.context = context;
        this.width = width;
        this.height = height;
        }

    public void init(Bitmap page1, Bitmap page2) {
        flippingHandler = new FlippingHandler();
        flipPagePaint = new Paint();
        flipPagePaint.setColor(Color.GRAY);
        flipPagePaint.setShadowLayer(5, -5, 5, 0x99000000);
        A = new Point(10, 0);
        B = new Point(width, height);
        C = new Point(width, 0);
        D = new Point(0, 0);
        E = new Point(0, 0);
        F = new Point(0, 0);

        xTouchValue = yTouchValue = DEFAULT_FLIP_VALUE;
        visiblePage = page1;
        invisiblePage = page2;
        onMoving = false;
        flipping = false;

        loadData();
    }

    private void loadData() {
        // listOfPages.add("this is my text");
        onloading = false;
    }

    private View visibleView, invisibleView;
    public void setView(View view1, View view2){
        visibleView = view1;
        invisibleView = view2;
        visibleView.setVisibility(VISIBLE);
        invisibleView.setVisibility(GONE);
        this.setVisibility(GONE);
    }

    public void next(){
        visibleView.setVisibility(GONE);
        this.setVisibility(VISIBLE);
        flipping = true;
        next = true;        
        flip();
    }

    public void pre(){
        swap2Page();
        visibleView.setVisibility(GONE);
        this.setVisibility(VISIBLE);
        flipping = true;
        next = false;
        xTouchValue = width-30;
        flip();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
         if (!onloading) {
            switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                oldTouchX = event.getX();
                oldTouchY = event.getY();
                flip = true;
                if (oldTouchX > (width >> 1)) {
                    xTouchValue = DEFAULT_FLIP_VALUE;
                    yTouchValue = DEFAULT_FLIP_VALUE;
                    // set invisible page's content
                    next = true;
                } else {
                    // set invisible page's content
                    // invisiblePage.setContent(index-1, null);
                    next = false;
                    swap2Page();
                    xTouchValue = width;
                    yTouchValue = DEFAULT_FLIP_VALUE;
                }
                break;
            case MotionEvent.ACTION_UP:
                if (onMoving) {
                    xTouchValue = width - A.x;
                    onMoving = false;
                }
                flipping = true;
                flip();
                break;
            case MotionEvent.ACTION_MOVE:
                // Log.i("Thong bao: ","x="+x+" y="+y);
                onMoving = true;
                float xMouse = event.getX();
                float yMouse = event.getY();
                xTouchValue -= (xMouse - oldTouchX) / 1;
                yTouchValue -= yMouse - oldTouchY;
                if (xMouse < oldTouchX) {
                    if (!next) {
                        flip = false;
                    }
                    next = true;
                } else {
                    if (next) {
                        flip = false;
                    }
                    next = false;
                }
                oldTouchX = event.getX();
                oldTouchY = event.getY();
                this.invalidate();
                break;
            }
        }
        return true;
    }
    public void flip() {
        if (flipping) {
           if (xTouchValue > width || xTouchValue < DEFAULT_FLIP_VALUE) {
                flipping = false;
                if (!flipping) {
                    if (next) {
                        swap2Page();
                    }
                    flip = false;
                    xTouchValue = DEFAULT_FLIP_VALUE;
                    yTouchValue = DEFAULT_FLIP_VALUE;
                    swap2View();
                }
                return;
            }
            if (next) {
                xTouchValue += FLIP_SPEED;
            } else {
                xTouchValue -= FLIP_SPEED;
            }
            this.invalidate();
            flippingHandler.sleep(mMoveDelay);
        }
    }

 @Override
    protected void onDraw(Canvas canvas) {
        width = getWidth();
        height = getHeight();
        if (flipping) {
            pointGenerate(xTouchValue, width, height);
        } else {
            // pointGenerateII(xTouchValue, yTouchValue, width, height);
            pointGenerate(xTouchValue, width, height);
        }
        // First Page render
        Paint paint = new Paint();
        canvas.drawColor(Color.GRAY);
        canvas.drawBitmap(visiblePage, 0, 0, paint);

        // Second Page Render
        Path pathX = pathOfTheMask();
        canvas.clipPath(pathX);
        canvas.drawBitmap(invisiblePage, 0, 0, paint);
        canvas.restore();
        // Flip Page render

        Path pathX2 = pathOfFlippedPaper();
        canvas.drawPath(pathX2, flipPagePaint);
        pathX = null;
        pathX2 = null;
        paint = null;
    }

    private Path pathOfTheMask() {
        Path path = new Path();
        path.moveTo(A.x, A.y);
        path.lineTo(B.x, B.y);
        path.lineTo(C.x, C.y);
        path.lineTo(D.x, D.y);
        path.lineTo(A.x, A.y);
         return path;
    }

    private Path pathOfFlippedPaper() {
        Path path = new Path();
        path.moveTo(A.x, A.y);
        path.lineTo(D.x, D.y);
        path.lineTo(E.x, E.y);
        path.lineTo(F.x, F.y);
        path.lineTo(A.x, A.y);
        return path;
    }

    private void pointGenerate(float distance, int width, int height) {
        float xA = width - distance;
        float yA = height;      
        float xD = 0;
        float yD = 0;
        if (xA > width / 2) {
            xD = width;
            yD = height - (width - xA) * height / xA;
        } else {
            xD = 2 * xA;
            yD = 0;
        }        

        double a = (height - yD) / (xD + distance - width);
        double alpha = Math.atan(a);
        double _cos = Math.cos(2 * alpha), _sin = Math.sin(2 * alpha);
        // E
        float xE = (float) (xD + _cos * (width - xD));
        float yE = (float) -(_sin * (width - xD));
        // F
        float xF = (float) (width - distance + _cos * distance);
        float yF = (float) (height - _sin * distance);
        if (xA > width / 2) {
            xE = xD;
            yE = yD;
        }
        A.x = xA;
        A.y = yA;
        D.x = xD;
        D.y = yD;
        E.x = xE;
        E.y = yE;
        F.x = xF;
        F.y = yF;
    }
    float oldxF = 0, oldyF = 0;

    private void pointGenerateII(float xTouch, float yTouch, int width, int height) {
        float yA = height;
        float xD = width;
        float xF = width - xTouch + 0.1f;
        float yF = height - yTouch + 0.1f;
        if (A.x == 0) {
            xF = Math.min(xF, oldxF);
            yF = Math.max(yF, oldyF);
        }
        float deltaX = width - xF;
        float deltaY = height - yF;
        float BH = (float) (Math.sqrt(deltaX * deltaX + deltaY * deltaY) / 2);
        double tangAlpha = deltaY / deltaX;
        double alpha = Math.atan(tangAlpha);
        double _cos = Math.cos(alpha), _sin = Math.sin(alpha);
        float xA = (float) (width - (BH / _cos));
        float yD = (float) (height - (BH / _sin));
        xA = Math.max(0, xA);
        if (xA == 0) {
            oldxF = xF;
            oldyF = yF;
        }
        float xE = xD;
        float yE = yD;
        if (yD < 0) {
            xD = width + (float) (tangAlpha * yD);
            yE = 0;
            xE = width + (float) (Math.tan(2 * alpha) * yD);
        }        
        A.x = xA;
        A.y = yA;
        D.x = xD;
        D.y = Math.max(0, yD);
        E.x = xE;
        E.y = yE;
        F.x = xF;
        F.y = yF;
    }

    private void swap2Page() {
        Bitmap temp = visiblePage;
        visiblePage = invisiblePage;
        invisiblePage = temp;
        temp = null;
    }
    private void swap2View(){
        View t = visibleView;
        visibleView = invisibleView;
        invisibleView = t;
        t=null;

        visibleView.setVisibility(VISIBLE);
        this.setVisibility(GONE);
    }
}

9. Create the MainBook.java class

package com.example.curlpagetutorial;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;

public class MainBook extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        final MyLayout page = (MyLayout) findViewById(R.id.mainpage);
        View pre = findViewById(R.id.btn_pre);
        pre.setOnClickListener(new OnClickListener() {
            
            @Override
            public void onClick(View v) {
                page.pre();              
            }
        });
        View next = findViewById(R.id.btn_next);
        next.setOnClickListener(new OnClickListener() {            
            @Override
            public void onClick(View v) {
                page.next();               
            }
        });
    }           
    @Override
    protected void onPause() {
        super.onPause();
        onStop();
    }
}

10. Create Point.java class

package com.example.curlpagetutorial;
public class Point {
    float x;
    float y;
    public Point(float x, float y){
        this.x = x;
        this.y = y;
    }   
}

11. Manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.curlpagetutorial"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk android:minSdkVersion="4" />

    <application
        android:configChanges="keyboardHidden|orientation"
        android:icon="@drawable/icon"
        android:label="@string/app_name"
        android:screenOrientation="portrait" >
        <activity
            android:name=".MainBook"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

 

6 thoughts on “Page Curl effect with Image and Text in Android

  1. […] Source: Page Curl effect with Image and Text in Android […]

    Like

  2. plz show the output

    Like

  3. you have to put images for need to get better idea

    Like

  4. What is mPoemView in above example..???

    Like

  5. plz attach the sample project.

    Like

  6. Where is the Source Code for this?

    Like

Leave a comment