Android自定義View——從零開始實現(xiàn)書籍翻頁效果(一)

版權聲明:本文為博主原創(chuàng)文章劲绪,未經(jīng)博主允許不得轉(zhuǎn)載盛撑。
系列教程:Android開發(fā)之從零開始系列
源碼:github.com/AnliaLee/BookPage副女,歡迎star

大家要是看到有錯誤的地方或者有啥好的建議妥曲,歡迎留言評論

前言:本篇是系列博客的第三篇,這次我們要研究 書籍翻頁效果 古毛。不知道大家平時有沒用過iReader、掌閱這些小說軟件都许,里面的翻頁效果感覺十分的酷炫稻薇。有心想研究研究如何實現(xiàn),于是網(wǎng)上找了找胶征,發(fā)現(xiàn)這方面的教學資料非常少塞椎,所幸能找到何明桂大大Android 實現(xiàn)書籍翻頁效果----原理篇這樣的入門博客(感謝大大 Orz),我們就以這篇博客為切入點從零實現(xiàn)我們自己的翻頁效果睛低。由于這次坑比較深案狠,預計會寫好幾期,感興趣的小伙伴可以點下關注以便及時收到更新提醒钱雷,謝謝大家的支持 ~

本篇只著重于思路和實現(xiàn)步驟骂铁,里面用到的一些知識原理不會非常細地拿來講,如果有不清楚的api或方法可以在網(wǎng)上搜下相應的資料罩抗,肯定有大神講得非常清楚的拉庵,我這就不獻丑了。本著認真負責的精神我會把相關知識的博文鏈接也貼出來(其實就是懶不想寫那么多哈哈)套蒂,大家可以自行傳送钞支。為了照顧第一次閱讀系列博客的小伙伴,本篇會出現(xiàn)一些在之前系列博客就講過的內(nèi)容操刀,看過的童鞋自行跳過該段即可

國際慣例烁挟,先上效果圖,本次主要實現(xiàn)了基本的上下翻頁效果右側(cè)最大翻頁距離的限制

目錄
  • 計算與繪制各個標識點
  • 連接各標識點繪制A骨坑、B撼嗓、C區(qū)域
  • 測量及自適應View的寬高
  • 通過觸摸控制各標識點位置
  • 限制右側(cè)翻頁的最大距離

計算與繪制各個標識點

相關博文鏈接

Android 實現(xiàn)書籍翻頁效果----原理篇
Android 自定義View (一)

在看這篇博客之前,希望大家能先了解一下書籍翻頁的實現(xiàn)原理卡啰,博客鏈接我已經(jīng)貼出來了静稻。通過原理講解我們知道,整個書籍翻頁效果界面分成了三個區(qū)域匈辱,A為當前頁區(qū)域振湾,B為下一頁區(qū)域,C為當前頁背面亡脸,如圖所示

書籍翻頁效果的實現(xiàn)就是要以我們觸摸屏幕位置的坐標為基礎繪制出這三個區(qū)域押搪,形成模擬翻頁的特效树酪。要繪制這三個區(qū)域,我們需要通過一組特定的點來完成大州,這些點的坐標需要通過兩個已知的點(觸摸點续语、相對邊緣角)計算得到,下圖我將各個特定點的位置和計算公式貼出來厦画,大家對照著原理一起理解(渣畫工望體諒 ╮(╯▽╰)╭ )疮茄,其中b點是由aecj的交點,k點是由ahcj的交點

簡單總結一下根暑,a是觸摸點力试,f是觸摸點相對的邊緣角,eh我們設置為af的垂直平分線排嫌,則gaf的中點畸裳,abak淳地、dj直線怖糊;曲線cdb是起點為c,控制點為e颇象,終點為b二階貝塞爾曲線伍伤;曲線kij是起點為k,控制點為h夯到,終點為j二階貝塞爾曲線嚷缭,區(qū)域AB耍贾、C就由這些點和線劃分開來阅爽。我們將這些點稱為標識點,下一步就是模擬設定af點的位置荐开,將這組標識點繪制到屏幕上來驗證我們的計算公式是否正確付翁,創(chuàng)建BookPageView

public class BookPageView extends View {
    private Paint pointPaint;//繪制各標識點的畫筆
    private Paint bgPaint;//背景畫筆

    private MyPoint a,f,g,e,h,c,j,b,k,d,i;

    private int defaultWidth;//默認寬度
    private int defaultHeight;//默認高度
    private int viewWidth;
    private int viewHeight;

    public BookPageView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        init(context,attrs);
    }

    private void init(Context context, @Nullable AttributeSet attrs){
        defaultWidth = 600;
        defaultHeight = 1000;

        viewWidth = defaultWidth;
        viewHeight = defaultHeight晃听;

        a = new MyPoint(400,800);
        f = new MyPoint(viewWidth,viewHeight);
        g = new MyPoint();
        e = new MyPoint();
        h = new MyPoint();
        c = new MyPoint();
        j = new MyPoint();
        b = new MyPoint();
        k = new MyPoint();
        d = new MyPoint();
        i = new MyPoint();
        calcPointsXY(a,f);

        pointPaint = new Paint();
        pointPaint.setColor(Color.RED);
        pointPaint.setTextSize(25);

        bgPaint = new Paint();
        bgPaint.setColor(Color.GREEN);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //為了看清楚點與View的位置關系繪制一個背景
        canvas.drawRect(0,0,viewWidth,viewHeight,bgPaint);
        //繪制各標識點
        canvas.drawText("a",a.x,a.y,pointPaint);
        canvas.drawText("f",f.x,f.y,pointPaint);
        canvas.drawText("g",g.x,g.y,pointPaint);

        canvas.drawText("e",e.x,e.y,pointPaint);
        canvas.drawText("h",h.x,h.y,pointPaint);

        canvas.drawText("c",c.x,c.y,pointPaint);
        canvas.drawText("j",j.x,j.y,pointPaint);

        canvas.drawText("b",b.x,b.y,pointPaint);
        canvas.drawText("k",k.x,k.y,pointPaint);

        canvas.drawText("d",d.x,d.y,pointPaint);
        canvas.drawText("i",i.x,i.y,pointPaint);
    }

    /**
     * 計算各點坐標
     * @param a
     * @param f
     */
    private void calcPointsXY(MyPoint a, MyPoint f){
        g.x = (a.x + f.x) / 2;
        g.y = (a.y + f.y) / 2;

        e.x = g.x - (f.y - g.y) * (f.y - g.y) / (f.x - g.x);
        e.y = f.y;

        h.x = f.x;
        h.y = g.y - (f.x - g.x) * (f.x - g.x) / (f.y - g.y);

        c.x = e.x - (f.x - e.x) / 2;
        c.y = f.y;

        j.x = f.x;
        j.y = h.y - (f.y - h.y) / 2;

        b = getIntersectionPoint(a,e,c,j);
        k = getIntersectionPoint(a,h,c,j);

        d.x = (c.x + 2 * e.x + b.x) / 4;
        d.y = (2 * e.y + c.y + b.y) / 4;

        i.x = (j.x + 2 * h.x + k.x) / 4;
        i.y = (2 * h.y + j.y + k.y) / 4;
    }

    /**
     * 計算兩線段相交點坐標
     * @param lineOne_My_pointOne
     * @param lineOne_My_pointTwo
     * @param lineTwo_My_pointOne
     * @param lineTwo_My_pointTwo
     * @return 返回該點
     */
    private MyPoint getIntersectionPoint(MyPoint lineOne_My_pointOne, MyPoint lineOne_My_pointTwo, MyPoint lineTwo_My_pointOne, MyPoint lineTwo_My_pointTwo){
        float x1,y1,x2,y2,x3,y3,x4,y4;
        x1 = lineOne_My_pointOne.x;
        y1 = lineOne_My_pointOne.y;
        x2 = lineOne_My_pointTwo.x;
        y2 = lineOne_My_pointTwo.y;
        x3 = lineTwo_My_pointOne.x;
        y3 = lineTwo_My_pointOne.y;
        x4 = lineTwo_My_pointTwo.x;
        y4 = lineTwo_My_pointTwo.y;

        float pointX =((x1 - x2) * (x3 * y4 - x4 * y3) - (x3 - x4) * (x1 * y2 - x2 * y1))
                / ((x3 - x4) * (y1 - y2) - (x1 - x2) * (y3 - y4));
        float pointY =((y1 - y2) * (x3 * y4 - x4 * y3) - (x1 * y2 - x2 * y1) * (y3 - y4))
                / ((y1 - y2) * (x3 - x4) - (x1 - x2) * (y3 - y4));

        return  new MyPoint(pointX,pointY);
    }
}

實體類MyPoint用來存放我們的標識點坐標

public class MyPoint {
    public float x,y;
    public MyPoint(){}
    public MyPoint(float x, float y){
        this.x = x;
        this.y = y;
    }
}

界面布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.anlia.pageturn.BookPageView
        android:id="@+id/view_book_page"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="15dp"
        android:layout_marginTop="15dp"/>
</RelativeLayout>

在Activity中進行注冊

bookPageView = (BookPageView) findViewById(R.id.view_book_page);

效果如圖


連接各標識點繪制A百侧、B、C區(qū)域

相關博文鏈接

Android-貝塞爾曲線
安卓自定義View進階:Path基本操作
android 自定義view 緩存技術
Android中Canvas繪圖之PorterDuffXfermode使用及工作原理詳解
Android 自定義View學習(五)——Paint 關于PorterDuffXfermode學習

前文我們提到ab能扒、ak佣渴、dj直線曲線cdb是起點為c初斑,控制點為e辛润,終點為b二階貝塞爾曲線曲線kij是起點為k见秤,控制點為h砂竖,終點為j二階貝塞爾曲線真椿。通過觀察分析得知,區(qū)域A是由View左上角乎澄,左下角突硝,曲線cdb, 直線ab置济、ak解恰,曲線kij浙于,右上角連接而成的區(qū)域,修改BookPageView路媚,利用path繪制處區(qū)域A

public class BookPageView extends View {
    //省略部分代碼...
    private Paint pathAPaint;//繪制A區(qū)域畫筆
    private Path pathA;
    private Bitmap bitmap;//緩存bitmap
    private Canvas bitmapCanvas;

    private void init(Context context, @Nullable AttributeSet attrs){
        //省略部分代碼...
        pathAPaint = new Paint();
        pathAPaint.setColor(Color.GREEN);
        pathAPaint.setAntiAlias(true);//設置抗鋸齒

        pathA = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //省略部分代碼...
        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);
        bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
        canvas.drawBitmap(bitmap,0,0,null);
    }

    /**
     * 獲取f點在右下角的pathA
     * @return
     */
    private Path getPathAFromLowerRight(){
        pathA.reset();
        pathA.lineTo(0, viewHeight);//移動到左下角
        pathA.lineTo(c.x,c.y);//移動到c點
        pathA.quadTo(e.x,e.y,b.x,b.y);//從c到b畫貝塞爾曲線整慎,控制點為e
        pathA.lineTo(a.x,a.y);//移動到a點
        pathA.lineTo(k.x,k.y);//移動到k點
        pathA.quadTo(h.x,h.y,j.x,j.y);//從k到j畫貝塞爾曲線围苫,控制點為h
        pathA.lineTo(viewWidth,0);//移動到右上角
        pathA.close();//閉合區(qū)域
        return pathA;
    }
}

效果如圖

區(qū)域C理論上應該是由點a,b,d,i,k連接而成的閉合區(qū)域,但由于di是曲線上的點,我們沒辦法直接從d出發(fā)通過path繪制路徑連接b點(i,k同理)剂府,也就不能只用path的情況下直接繪制出區(qū)域C拧揽,我們需要用PorterDuffXfermode方面的知識“曲線救國”。我們試著先將點a,b,d,i,k連接起來腺占,觀察閉合區(qū)域與區(qū)域A之間的聯(lián)系淤袜。修改BookPageView

private void init(Context context, @Nullable AttributeSet attrs){
    //省略部分代碼...
    pathCPaint = new Paint();
    pathCPaint.setColor(Color.YELLOW);
    pathCPaint.setAntiAlias(true);//設置抗鋸齒
    
    pathC = new Path();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);

    bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
    bitmapCanvas = new Canvas(bitmap);
    bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
    bitmapCanvas.drawPath(getPathC(),pathCPaint);
    canvas.drawBitmap(bitmap,0,0,null);
}

/**
 * 繪制區(qū)域C
 * @return
 */
private Path getPathC(){
    pathC.reset();
    pathC.moveTo(i.x,i.y);//移動到i點
    pathC.lineTo(d.x,d.y);//移動到d點
    pathC.lineTo(b.x,b.y);//移動到b點
    pathC.lineTo(a.x,a.y);//移動到a點
    pathC.lineTo(k.x,k.y);//移動到k點
    pathC.close();//閉合區(qū)域
    return pathC;
}

效果如圖

我們將兩條曲線也畫出來對比觀察

觀察分析后可以得出結論,區(qū)域C由直線ab,bd,dj,ik,ak連接而成的區(qū)域 減去 與區(qū)域A交集部分 后剩余的區(qū)域衰伯。于是我們設置區(qū)域C畫筆Xfermode模式為DST_ATOP

pathCPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));

效果如圖

最后是區(qū)域B铡羡,因為區(qū)域B處于最底層,我們直接將區(qū)域B畫筆Xfermode模式設為DST_ATOP意鲸,在區(qū)域A烦周、C之后繪制即可,修改BookPageView

private void init(Context context, @Nullable AttributeSet attrs){
    //省略部分代碼...
    pathBPaint = new Paint();
    pathBPaint.setColor(getResources().getColor(R.color.blue_light));
    pathBPaint.setAntiAlias(true);//設置抗鋸齒
    pathBPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
    
    pathB = new Path();
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    //省略部分代碼...
    bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
    bitmapCanvas = new Canvas(bitmap);
    bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
    bitmapCanvas.drawPath(getPathC(),pathCPaint);
    bitmapCanvas.drawPath(getPathB(),pathBPaint);
    canvas.drawBitmap(bitmap,0,0,null);
}

/**
 * 繪制區(qū)域B
 * @return
 */
private Path getPathB(){
    pathB.reset();
    pathB.lineTo(0, viewHeight);//移動到左下角
    pathB.lineTo(viewWidth,viewHeight);//移動到右下角
    pathB.lineTo(viewWidth,0);//移動到右上角
    pathB.close();//閉合區(qū)域
    return pathB;
}

效果如圖

翻頁可以從右下方翻自然也可以從右上方翻怎顾,我們將f點設在右上角读慎,由于View上下兩部分是呈鏡像的,所以各標識點的位置也應該是鏡像對應的槐雾,因為區(qū)域B和C的繪制與f點沒有關系夭委,所以我們只需要修改區(qū)域A的繪制邏輯,新增getPathAFromTopRight()方法

public class BookPageView extends View {
    //省略部分代碼...
    private void init(Context context, @Nullable AttributeSet attrs){
        a = new MyPoint(400,200);
        f = new MyPoint(viewWidth,0);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //省略部分代碼...
        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);
//        bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
        bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
        bitmapCanvas.drawPath(getPathC(),pathCPaint);
        bitmapCanvas.drawPath(getPathB(),pathBPaint);
    }

    /**
     * 獲取f點在右上角的pathA
     * @return
     */
    private Path getPathAFromTopRight(){
        pathA.reset();
        pathA.lineTo(c.x,c.y);//移動到c點
        pathA.quadTo(e.x,e.y,b.x,b.y);//從c到b畫貝塞爾曲線蚜退,控制點為e
        pathA.lineTo(a.x,a.y);//移動到a點
        pathA.lineTo(k.x,k.y);//移動到k點
        pathA.quadTo(h.x,h.y,j.x,j.y);//從k到j畫貝塞爾曲線闰靴,控制點為h
        pathA.lineTo(viewWidth,viewHeight);//移動到右下角
        pathA.lineTo(0, viewHeight);//移動到左下角
        pathA.close();
        return pathA;
    }
}

效果如圖


測量及自適應View的寬高

相關博文鏈接

淺談自定義View的寬高獲取
教你搞定Android自定義View

之前由于測試效果沒有對View的大小進行重新測量彪笼,在實現(xiàn)觸摸翻頁之前先把這個結了。重寫View的onMeasure()方法

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    int height = measureSize(defaultHeight, heightMeasureSpec);
    int width = measureSize(defaultWidth, widthMeasureSpec);
    setMeasuredDimension(width, height);

    viewWidth = width;
    viewHeight = height;
    f.x = width;
    f.y = height;
    calcPointsXY(a,f);//將初始化計算放在這
}

private int measureSize(int defaultSize,int measureSpec) {
    int result = defaultSize;
    int specMode = View.MeasureSpec.getMode(measureSpec);
    int specSize = View.MeasureSpec.getSize(measureSpec);

    if (specMode == View.MeasureSpec.EXACTLY) {
        result = specSize;
    } else if (specMode == View.MeasureSpec.AT_MOST) {
        result = Math.min(result, specSize);
    }
    return result;
}

通過觸摸控制各標識點位置

我們的需求是蚂且,在上半部分翻頁時f點在右上角配猫,在下半部分翻頁時f則在右下角,當手指離開屏幕時回到初始狀態(tài)杏死,根據(jù)需求,修改BookPageView

public class BookPageView extends View {
    //省略部分代碼...
    public static final String STYLE_TOP_RIGHT = "STYLE_TOP_RIGHT";//f點在右上角
    public static final String STYLE_LOWER_RIGHT = "STYLE_LOWER_RIGHT";//f點在右下角
    
    private void init(Context context, @Nullable AttributeSet attrs){ 
        //省略部分代碼...
        a = new MyPoint();
        f = new MyPoint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int height = measureSize(defaultHeight, heightMeasureSpec);
        int width = measureSize(defaultWidth, widthMeasureSpec);
        setMeasuredDimension(width, height);

        viewWidth = width;
        viewHeight = height;
        a.x = -1;
        a.y = -1;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        bitmap = Bitmap.createBitmap((int) viewWidth, (int) viewHeight, Bitmap.Config.ARGB_8888);
        bitmapCanvas = new Canvas(bitmap);
        if(a.x==-1 && a.y==-1){
            bitmapCanvas.drawPath(getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
                bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
                bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
            }

            bitmapCanvas.drawPath(getPathC(),pathCPaint);
            bitmapCanvas.drawPath(getPathB(),pathBPaint);
        }
        canvas.drawBitmap(bitmap,0,0,null);
    }

    /**
     * 設置觸摸點
     * @param x
     * @param y
     * @param style
     */
    public void setTouchPoint(float x, float y, String style){
        switch (style){
            case STYLE_TOP_RIGHT:
                f.x = viewWidth;
                f.y = 0;
                break;
            case STYLE_LOWER_RIGHT:
                f.x = viewWidth;
                f.y = viewHeight;
                break;
            default:
                break;
        }
        a.x = x;
        a.y = y;
        calcPointsXY(a,f);
        postInvalidate();
    }

    /**
     * 回到默認狀態(tài)
     */
    public void setDefaultPath(){
        a.x = -1;
        a.y = -1;
        postInvalidate();
    }

    /**
     * 繪制默認的界面
     * @return
     */
    private Path getPathDefault(){
        pathA.reset();
        pathA.lineTo(0, viewHeight);
        pathA.lineTo(viewWidth,viewHeight);
        pathA.lineTo(viewWidth,0);
        pathA.close();
        return pathA;
    }

    public float getViewWidth(){
        return viewWidth;
    }

    public float getViewHeight(){
        return viewHeight;
    }
}

在Activity中監(jiān)聽View的onTouch狀態(tài)

bookPageView.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                if(event.getY() < bookPageView.getViewHeight()/2){//從上半部分翻頁
                    bookPageView.setTouchPoint(event.getX(),event.getY(),bookPageView.STYLE_TOP_RIGHT);
                }else if(event.getY() >= bookPageView.getViewHeight()/2) {//從下半部分翻頁
                    bookPageView.setTouchPoint(event.getX(),event.getY(),bookPageView.STYLE_LOWER_RIGHT);
                }
                break;
            case MotionEvent.ACTION_MOVE:
                bookPageView.setTouchPoint(event.getX(),event.getY(),"");
                break;
            case MotionEvent.ACTION_UP:
                bookPageView.setDefaultPath();//回到默認狀態(tài)
                break;
        }
        return false;
    }
});

注意冯丙,要設置android:clickabletrue胃惜,否則無法監(jiān)聽到ACTION_MOVEACTION_UP狀態(tài)

<com.anlia.pageturn.BookPageView
    android:id="@+id/view_book_page"
    android:layout_width="300dp"
    android:layout_height="450dp"
    android:layout_marginLeft="15dp"
    android:layout_marginTop="15dp"
    android:clickable="true"/>

效果如圖

到這里我們已經(jīng)實現(xiàn)了基本的翻頁效果,但要還原真實的書籍翻頁效果利虫,我們還需要設置一些限制條件來完善我們的項目


限制右側(cè)翻頁的最大距離

對于一般的書本來說糠惫,最左側(cè)應該是釘起來的寞钥,也就是說如果我們從右側(cè)翻頁理郑,翻動的距離是有限制的您炉,最下方翻頁形成的曲線起點(c點)的x坐標不能小于0(上方同理)赚爵,按照這個限定條件冀膝,修改我們的BookPageView

/**
 * 設置觸摸點
 * @param x
 * @param y
 * @param style
 */
public void setTouchPoint(float x, float y, String style){
    switch (style){
        case STYLE_TOP_RIGHT:
            f.x = viewWidth;
            f.y = 0;
            break;
        case STYLE_LOWER_RIGHT:
            f.x = viewWidth;
            f.y = viewHeight;
            break;
        default:
            break;
    }
    MyPoint touchPoint = new MyPoint(x,y);
    //如果大于0則設置a點坐標重新計算各標識點位置麻掸,否則a點坐標不變
    if(calcPointCX(touchPoint,f)>0){
        a.x = x;
        a.y = y;
        calcPointsXY(a,f);
    }else {
        calcPointsXY(a,f);
    }
    postInvalidate();
}
/**
 * 計算C點的X值
 * @param a
 * @param f
 * @return
 */
private float calcPointCX(MyPoint a, MyPoint f){
    MyPoint g,e;
    g = new MyPoint();
    e = new MyPoint();
    g.x = (a.x + f.x) / 2;
    g.y = (a.y + f.y) / 2;

    e.x = g.x - (f.y - g.y) * (f.y - g.y) / (f.x - g.x);
    e.y = f.y;

    return e.x - (f.x - e.x) / 2;
}

效果如圖

至此本篇教程就告一段落了脊奋,當然還有許多功能需要繼續(xù)完善诚隙,例如橫向翻頁久又、翻頁動畫籽孙、陰影效果等等,這些都會在后面的教程中一一解決瓜客。如果大家看了感覺還不錯麻煩點個贊谱仪,你們的支持是我最大的動力~


最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市贴浙,隨后出現(xiàn)的幾起案子崎溃,更是在濱河造成了極大的恐慌,老刑警劉巖呼巷,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異乡范,居然都是意外死亡晋辆,警方通過查閱死者的電腦和手機瓶佳,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門为朋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來习寸,“玉大人霞溪,你說我怎么就攤上這事≈欣Γ” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵殴蓬,是天一觀的道長染厅。 經(jīng)常有香客問我糟秘,道長尿赚,這世上最難降的妖魔是什么凌净? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任须教,我火速辦了婚禮轻腺,結果婚禮上贬养,老公的妹妹穿的比我還像新娘误算。我一直安慰自己迷殿,他們只是感情好庆寺,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布知纷。 她就那樣靜靜地躺著,像睡著了一般撩匕。 火紅的嫁衣襯著肌膚如雪止毕。 梳的紋絲不亂的頭發(fā)上扁凛,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天谨朝,我揣著相機與錄音字币,去河邊找鬼洗出。 笑死,一個胖子當著我的面吹牛阱洪,可吹牛的內(nèi)容都是我干的冗荸。 我是一名探鬼主播俏竞,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼魂毁,長吁一口氣:“原來是場噩夢啊……” “哼席楚!你這毒婦竟也來了税稼?” 一聲冷哼從身側(cè)響起郎仆,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤抛寝,失蹤者是張志新(化名)和其女友劉穎曙旭,沒想到半個月后桂躏,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剂习,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年购对,在試婚紗的時候發(fā)現(xiàn)自己被綠了骡苞。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片楷扬。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡躲株,死狀恐怖霜定,靈堂內(nèi)的尸體忽然破棺而出廊鸥,到底是詐尸還是另有隱情惰说,我是刑警寧澤吆视,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布啦吧,位于F島的核電站授滓,受9級特大地震影響褒墨,放射性物質(zhì)發(fā)生泄漏擎宝。R本人自食惡果不足惜绍申,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望胃碾。 院中可真熱鬧仆百,春花似錦、人聲如沸吁讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拴曲。三九已至疗韵,卻和暖如春蕉汪,著一層夾襖步出監(jiān)牢的瞬間者疤,已是汗流浹背吧慢。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工除秀, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留册踩,地道東北人暂吉。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像挤渔,于是被迫代替她去往敵國和親判导。 傳聞我的和親對象是個殘疾皇子骡楼,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容