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

版權聲明:本文爲博主原創文章,未經博主容許不得轉載java

系列教程:Android開發之從零開始系列git

源碼:AnliaLee/BookPage,歡迎stargithub

你們要是看到有錯誤的地方或者有啥好的建議,歡迎留言評論算法

前言:在上篇Android自定義View——從零開始實現書籍翻頁效果(二)博客中,咱們 補全了翻頁效果以及增長了 取消翻頁的動畫,這期要教你們如何 向View填充內容canvas

本篇只着重於思路和實現步驟,裏面用到的一些知識原理不會很是細地拿來說,若是有不清楚的api或方法能夠在網上搜下相應的資料,確定有大神講得很是清楚的,我這就不獻醜了。本着認真負責的精神我會把相關知識的博文連接也貼出來(其實就是懶不想寫那麼多哈哈),你們能夠自行傳送。爲了照顧第一次閱讀系列博客的小夥伴,本篇會出現一些在以前系列博客就講過的內容,看過的童鞋自行跳過該段便可api

國際慣例,先上效果圖ide


繪製當前頁(A區域)的內容

相關博文連接

Android中的裁剪中Region.Op參數的用法post

A區域的內容,有可能包含文字,圖案等各類元素,所以咱們須要將這些統一繪製到一個Bitmap中,而後繪製到畫布上,固然要記得裁剪這些內容,取其與A區域交集,這樣看起來纔像將內容印在A區域中,修改BookPageView動畫

public class BookPageView extends View {
	//省略部分代碼...
    private Paint textPaint;//繪製文字畫筆
    private void init(Context context, @Nullable AttributeSet attrs){
		//省略部分代碼...
        textPaint = new Paint();
        textPaint.setColor(Color.BLACK);
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.setSubpixelText(true);//設置自像素。若是該項爲true,將有助於文本在LCD屏幕上的顯示效果。
        textPaint.setTextSize(30);
    }

    @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);
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
// bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
// bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);
            }

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

    /** * 繪製A區域內容 * @param canvas * @param pathA * @param pathPaint */
    private void drawPathAContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        //下面開始繪製區域內的內容...
        contentCanvas.drawPath(pathA,pathPaint);//繪製一個背景,用來區分各區域
        contentCanvas.drawText("這是在A區域的內容...AAAA", viewWidth-260, viewHeight-100, textPaint);

        //結束繪製區域內的內容...

        canvas.save();
        canvas.clipPath(pathA, Region.Op.INTERSECT);//對繪製內容進行裁剪,取和A區域的交集
        canvas.drawBitmap(contentBitmap, 0, 0, null);
        canvas.restore();
    }
}
複製代碼

效果如圖spa


繪製下一頁(B區域)的內容

繪製B區域內容的原理和A區域同樣,區別在於B區域內容取的是B區域不一樣於AC區域全集的部分,代碼以下

public class BookPageView extends View {
    private void init(Context context, @Nullable AttributeSet attrs){
// pathBPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));咱們不須要單獨繪製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);

        if(a.x==-1 && a.y==-1){
// bitmapCanvas.drawPath(getPathDefault(),pathAPaint);
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
// bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
// bitmapCanvas.drawPath(getPathAFromLowerRight(),pathAPaint);
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint);
            }
// bitmapCanvas.drawPath(getPathC(),pathCPaint);
// bitmapCanvas.drawPath(getPathB(),pathBPaint);
        }
    }

    /** * 繪製B區域內容 * @param canvas * @param pathPaint * @param pathA */
    private void drawPathBContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        //下面開始繪製區域內的內容...
        contentCanvas.drawPath(getPathB(),pathPaint);
        contentCanvas.drawText("這是在B區域的內容...BBBB", viewWidth-260, viewHeight-100, textPaint);

        //結束繪製區域內的內容...

        canvas.save();
        canvas.clipPath(pathA);//裁剪出A區域
        canvas.clipPath(getPathC(),Region.Op.UNION);//裁剪出A和C區域的全集
        canvas.clipPath(getPathB(), Region.Op.REVERSE_DIFFERENCE);//裁剪出B區域中不一樣於與AC區域的部分
        canvas.drawBitmap(contentBitmap, 0, 0, null);
        canvas.restore();
    }
}
複製代碼

效果如圖


繪製當前頁(A區域)背面(C區域)的內容

相關博文連接

安卓自定義View進階-Matrix原理

C區域的內容填充是本期最複雜的一部分了。AB區域的內容都是直接繪製在相應區域便可,不須要作太多的處理,而咱們看到的C區域內容,即當前頁的背面實際上是這樣來的,如圖(圖出自大神AigeStudio的博客Android翻頁效果原理實現之模擬扭曲

那麼如何獲得該區域的內容呢?看下圖及分析過程

  • 咱們將當前頁設爲矩形ABCD,將矩形ABCD翻轉獲得矩形AB₁CD₁

  • 旋轉2角0的度數獲得矩形AB₂C₂D₂(通過翻轉和旋轉後,此時咱們的XY座標軸方向在圖中右上方已經標出來了) 解析:①設 角ehD角0角heD角1,由於 ehaD垂直平分線,則 角ahD大小爲兩倍 角0; ②又由於咱們要求 AC₂B4 D4平行,且 ACBD平行; ③因此 角C₂AC等於 角ahD,爲兩倍 角0; ④因此旋轉角度爲兩倍 角0

  • 爲了方便後面好計算,咱們將矩形AB₂C₂D₂沿X軸負方向移動e.x的長度,沿Y軸負方向移動矩形長邊的長度(即f.ye.y的長度),最終獲得矩形A₃B₃C₃D₃

  • 最後將矩形A₃B₃C₃D₃沿屏幕原X軸方向移動e.x的長度,沿原Y軸方向移動e.y的長度,獲得矩形A4 B4 C4 D4(即 C₃C4重合,D4a點重合),矩形A4 B4 C4 D4即爲咱們所要的**背面(C區域)**的內容 解析:①設 角CAC₃角A,由於 eOAC平行, eC4AC₃平行,因此 角OeC4角CAC₃相等,即等於 角A; ②由以前的矩形移動咱們知道 AC₃的長度等於 e.x,因此 C₃的X座標e.x·sinAC₃的Y座標e.x·cosA; ③由於 矩形ABCD矩形A4 B4 C4 D4eh爲中軸對稱,因此 eC4的長度等於 e.x,則同理,得 C4的X座標e.x·sinA+e.xC4的Y座標e.x·cosA+e.y; ④由 C4的座標減去 C₃的座標可知 C₃C4的移動距離爲 原X軸方向的e.x原Y軸方向e.y( X軸: (e.x·sinA+e.x) - (e.x·sinA) = e.x,Y軸: (e.x·cosA+e.y) - (e.x·cosA) = e.y

至此,A區域內容翻轉、旋轉再位移獲得C區域內容的原理分析完畢(我已經盡力去描述清楚了,但願你們能看懂和別嫌我講得囉嗦_(:з」∠) _,至於數學知識都交回給老師的同窗本身去網上查吧哈哈),咱們根據以前的分析利用Matrix方面的知識編寫算法,首先看下翻轉矩陣

旋轉矩陣,θ爲要旋轉的角度

咱們按以前的分析先翻轉後旋轉,旋轉角度爲兩倍角0計算,計算過程爲

按照計算結果,開始修改咱們的BookPageView

public class BookPageView extends View {
	//省略部分代碼...
    private Paint pathCContentPaint;//繪製C區域內容畫筆
    private void init(Context context, @Nullable AttributeSet attrs){
		//省略部分代碼...
        pathCContentPaint = new Paint();
        pathCContentPaint.setColor(Color.YELLOW);
        pathCContentPaint.setAntiAlias(true);//設置抗鋸齒
    }

    @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){
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                drawPathCContent(bitmapCanvas,getPathAFromTopRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);

                drawPathCContent(bitmapCanvas,getPathAFromLowerRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint);
            }
        }
    }

    /** * 繪製C區域內容 * @param canvas * @param pathA * @param pathPaint */
    private void drawPathCContent(Canvas canvas, Path pathA, Paint pathPaint){
        Bitmap contentBitmap = Bitmap.createBitmap(viewWidth, viewHeight, Bitmap.Config.RGB_565);
        Canvas contentCanvas = new Canvas(contentBitmap);

        //下面開始繪製區域內的內容...
        contentCanvas.drawPath(getPathB(),pathPaint);//繪製一個背景,path用B的就行
        contentCanvas.drawText("這是在A區域的內容...AAAA", viewWidth-260, viewHeight-100, textPaint);

        //結束繪製區域內的內容...

        canvas.save();
        canvas.clipPath(pathA);
        canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);//裁剪出C區域不一樣於A區域的部分

        float eh = (float) Math.hypot(f.x - e.x,h.y - f.y);
        float sin0 = (f.x - e.x) / eh;
        float cos0 = (h.y - f.y) / eh;
        //設置翻轉和旋轉矩陣
        float[] mMatrixArray = { 0, 0, 0, 0, 0, 0, 0, 0, 1.0f };
        mMatrixArray[0] = -(1-2 * sin0 * sin0);
        mMatrixArray[1] = 2 * sin0 * cos0;
        mMatrixArray[3] = 2 * sin0 * cos0;
        mMatrixArray[4] = 1 - 2 * sin0 * sin0;

        Matrix mMatrix = new Matrix();
        mMatrix.reset();
        mMatrix.setValues(mMatrixArray);//翻轉和旋轉
        mMatrix.preTranslate(-e.x, -e.y);//沿當前XY軸負方向位移獲得 矩形A₃B₃C₃D₃
        mMatrix.postTranslate(e.x, e.y);//沿原XY軸方向位移獲得 矩形A4 B4 C4 D4

        canvas.drawBitmap(contentBitmap, mMatrix, null);
        canvas.restore();
    }
}
複製代碼

效果如圖

還不夠完美,能夠觀察到翻起的當前頁背面還有一些空白的地方沒有繪製內容,這是由於C區域的內容是經過當前頁矩形翻轉、旋轉、位移後獲得的,因此也是矩形,天然不能覆蓋曲線的邊緣區域。咱們須要對這些邊緣區域進行處理,根據背景的複雜度須要不一樣的處理方法,由於咱們是純色背景,因此處理起來會方便一些,這裏只給出純色背景的處理方案,其餘背景圖案你們自行擴展便可

public class BookPageView extends View {
	//省略部分代碼...
    @Override
    protected void onDraw(Canvas canvas) {
		//省略部分代碼...
        super.onDraw(canvas);
        if(a.x==-1 && a.y==-1){
            drawPathAContent(bitmapCanvas,getPathDefault(),pathAPaint);
        }else {
            if(f.x==viewWidth && f.y==0){
                drawPathAContent(bitmapCanvas,getPathAFromTopRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathCContent(bitmapCanvas,getPathAFromTopRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromTopRight(),pathBPaint);
            }else if(f.x==viewWidth && f.y==viewHeight){
                drawPathAContent(bitmapCanvas,getPathAFromLowerRight(),pathAPaint);

                bitmapCanvas.drawPath(getPathC(),pathCPaint);
                drawPathCContent(bitmapCanvas,getPathAFromLowerRight(),pathCContentPaint);
                drawPathBContent(bitmapCanvas,getPathAFromLowerRight(),pathBPaint);
            }
        }
    }

    /** * 繪製C區域內容 * @param canvas * @param pathA * @param pathPaint */
    private void drawPathCContent(Canvas canvas, Path pathA, Paint pathPaint){canvas.save();
		//省略部分代碼...
        canvas.clipPath(pathA);
// canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);//裁剪出C區域不一樣於A區域的部分
        canvas.clipPath(getPathC(),Region.Op.UNION);//裁剪出A和C區域的全集
        canvas.clipPath(getPathC(), Region.Op.INTERSECT);//取與C區域的交集
    }
}
複製代碼

效果如圖

至此本篇教程到此結束,至於填充的內容如何自定義就留給你們自行擴展了,原理都是同樣的。若是你們看了感受還不錯麻煩點個贊,大家的支持是我最大的動力~

相關文章
相關標籤/搜索