版權(quán)聲明:本文為博主原創(chuàng)文章,未經(jīng)博主允許不得轉(zhuǎn)載。
系列教程:Android開發(fā)之從零開始系列
源碼:github.com/AnliaLee/BookPage,歡迎star大家要是看到有錯(cuò)誤的地方或者有啥好的建議,歡迎留言評(píng)論
前言:在上篇Android自定義View——從零開始實(shí)現(xiàn)書籍翻頁效果(二)博客中,我們 補(bǔ)全了翻頁效果以及增加了 取消翻頁的動(dòng)畫,這期要教大家如何 向View填充內(nèi)容
本篇只著重于思路和實(shí)現(xiàn)步驟,里面用到的一些知識(shí)原理不會(huì)非常細(xì)地拿來講,如果有不清楚的api或方法可以在網(wǎng)上搜下相應(yīng)的資料,肯定有大神講得非常清楚的,我這就不獻(xiàn)丑了。本著認(rèn)真負(fù)責(zé)的精神我會(huì)把相關(guān)知識(shí)的博文鏈接也貼出來(其實(shí)就是懶不想寫那么多哈哈),大家可以自行傳送。為了照顧第一次閱讀系列博客的小伙伴,本篇會(huì)出現(xiàn)一些在之前系列博客就講過的內(nèi)容,看過的童鞋自行跳過該段即可
國(guó)際慣例,先上效果圖

目錄
- 繪制當(dāng)前頁(A區(qū)域)的內(nèi)容
- 繪制下一頁(B區(qū)域)的內(nèi)容
- 繪制當(dāng)前頁(A區(qū)域)背面(C區(qū)域)的內(nèi)容
繪制當(dāng)前頁(A區(qū)域)的內(nèi)容
相關(guān)博文鏈接
A區(qū)域的內(nèi)容,有可能包含文字,圖案等各種元素,因此我們需要將這些統(tǒng)一繪制到一個(gè)Bitmap中,然后繪制到畫布上,當(dāng)然要記得裁剪這些內(nèi)容,取其與A區(qū)域的交集,這樣看起來才像將內(nèi)容印在A區(qū)域中,修改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);//設(shè)置自像素。如果該項(xiàng)為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區(qū)域內(nèi)容
* @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);
//下面開始繪制區(qū)域內(nèi)的內(nèi)容...
contentCanvas.drawPath(pathA,pathPaint);//繪制一個(gè)背景,用來區(qū)分各區(qū)域
contentCanvas.drawText("這是在A區(qū)域的內(nèi)容...AAAA", viewWidth-260, viewHeight-100, textPaint);
//結(jié)束繪制區(qū)域內(nèi)的內(nèi)容...
canvas.save();
canvas.clipPath(pathA, Region.Op.INTERSECT);//對(duì)繪制內(nèi)容進(jìn)行裁剪,取和A區(qū)域的交集
canvas.drawBitmap(contentBitmap, 0, 0, null);
canvas.restore();
}
}
效果如圖

繪制下一頁(B區(qū)域)的內(nèi)容
繪制B區(qū)域內(nèi)容的原理和A區(qū)域一樣,區(qū)別在于B區(qū)域內(nèi)容取的是B區(qū)域不同于AC區(qū)域全集的部分,代碼如下
public class BookPageView extends View {
private void init(Context context, @Nullable AttributeSet attrs){
// pathBPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));我們不需要單獨(dú)繪制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區(qū)域內(nèi)容
* @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);
//下面開始繪制區(qū)域內(nèi)的內(nèi)容...
contentCanvas.drawPath(getPathB(),pathPaint);
contentCanvas.drawText("這是在B區(qū)域的內(nèi)容...BBBB", viewWidth-260, viewHeight-100, textPaint);
//結(jié)束繪制區(qū)域內(nèi)的內(nèi)容...
canvas.save();
canvas.clipPath(pathA);//裁剪出A區(qū)域
canvas.clipPath(getPathC(),Region.Op.UNION);//裁剪出A和C區(qū)域的全集
canvas.clipPath(getPathB(), Region.Op.REVERSE_DIFFERENCE);//裁剪出B區(qū)域中不同于與AC區(qū)域的部分
canvas.drawBitmap(contentBitmap, 0, 0, null);
canvas.restore();
}
}
效果如圖

繪制當(dāng)前頁(A區(qū)域)背面(C區(qū)域)的內(nèi)容
相關(guān)博文鏈接
C區(qū)域的內(nèi)容填充是本期最復(fù)雜的一部分了。AB區(qū)域的內(nèi)容都是直接繪制在相應(yīng)區(qū)域即可,不需要做太多的處理,而我們看到的C區(qū)域內(nèi)容,即當(dāng)前頁的背面其實(shí)是這樣來的,如圖(圖出自大神AigeStudio的博客Android翻頁效果原理實(shí)現(xiàn)之模擬扭曲)

那么如何得到該區(qū)域的內(nèi)容呢?看下圖及分析過程

我們將當(dāng)前頁設(shè)為矩形ABCD,將矩形ABCD翻轉(zhuǎn)得到矩形AB?CD?
旋轉(zhuǎn)2倍角0的度數(shù)得到矩形AB?C?D?(經(jīng)過翻轉(zhuǎn)和旋轉(zhuǎn)后,此時(shí)我們的XY坐標(biāo)軸方向在圖中右上方已經(jīng)標(biāo)出來了)
解析:①設(shè) 角ehD為 角0, 角heD為 角1,因?yàn)?eh為 aD的 垂直平分線,則 角ahD大小為兩倍 角0; ②又因?yàn)槲覀円?AC?與 B4 D4平行,且 AC和 BD平行; ③所以 角C?AC等于 角ahD,為兩倍 角0; ④所以旋轉(zhuǎn)角度為兩倍 角0為了方便后面好計(jì)算,我們將矩形AB?C?D?沿X軸負(fù)方向移動(dòng)e.x的長(zhǎng)度,沿Y軸負(fù)方向移動(dòng)矩形長(zhǎng)邊的長(zhǎng)度(即f.y或e.y的長(zhǎng)度),最終得到矩形A?B?C?D?
最后將矩形A?B?C?D?沿屏幕原X軸方向移動(dòng)e.x的長(zhǎng)度,沿原Y軸方向移動(dòng)e.y的長(zhǎng)度,得到矩形A4 B4 C4 D4(即 C?與 C4重合,D4與a點(diǎn)重合),矩形A4 B4 C4 D4即為我們所要的背面(C區(qū)域)的內(nèi)容
解析:①設(shè) 角CAC?為 角A,因?yàn)?eO與 AC平行, eC4與 AC?平行,所以 角OeC4與 角CAC?相等,即等于 角A; ②由之前的矩形移動(dòng)我們知道 AC?的長(zhǎng)度等于 e.x,所以 C?的X坐標(biāo)為 e.x·sinA, C?的Y坐標(biāo)為 e.x·cosA; ③因?yàn)?矩形ABCD與 矩形A4 B4 C4 D4以 eh為中軸對(duì)稱,所以 eC4的長(zhǎng)度等于 e.x,則同理,得 C4的X坐標(biāo)為 e.x·sinA+e.x, C4的Y坐標(biāo)為 e.x·cosA+e.y; ④由 C4的坐標(biāo)減去 C?的坐標(biāo)可知 C?到 C4的移動(dòng)距離為 原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區(qū)域內(nèi)容翻轉(zhuǎn)、旋轉(zhuǎn)再位移得到C區(qū)域內(nèi)容的原理分析完畢(我已經(jīng)盡力去描述清楚了,希望大家能看懂和別嫌我講得啰嗦_(:з」∠) _,至于數(shù)學(xué)知識(shí)都交回給老師的同學(xué)自己去網(wǎng)上查吧哈哈),我們根據(jù)之前的分析利用Matrix方面的知識(shí)編寫算法,首先看下翻轉(zhuǎn)矩陣

旋轉(zhuǎn)矩陣,θ為要旋轉(zhuǎn)的角度

我們按之前的分析先翻轉(zhuǎn)后旋轉(zhuǎn),旋轉(zhuǎn)角度為兩倍角0計(jì)算,計(jì)算過程為

按照計(jì)算結(jié)果,開始修改我們的BookPageView
public class BookPageView extends View {
//省略部分代碼...
private Paint pathCContentPaint;//繪制C區(qū)域內(nèi)容畫筆
private void init(Context context, @Nullable AttributeSet attrs){
//省略部分代碼...
pathCContentPaint = new Paint();
pathCContentPaint.setColor(Color.YELLOW);
pathCContentPaint.setAntiAlias(true);//設(shè)置抗鋸齒
}
@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區(qū)域內(nèi)容
* @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);
//下面開始繪制區(qū)域內(nèi)的內(nèi)容...
contentCanvas.drawPath(getPathB(),pathPaint);//繪制一個(gè)背景,path用B的就行
contentCanvas.drawText("這是在A區(qū)域的內(nèi)容...AAAA", viewWidth-260, viewHeight-100, textPaint);
//結(jié)束繪制區(qū)域內(nèi)的內(nèi)容...
canvas.save();
canvas.clipPath(pathA);
canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);//裁剪出C區(qū)域不同于A區(qū)域的部分
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;
//設(shè)置翻轉(zhuǎn)和旋轉(zhuǎn)矩陣
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);//翻轉(zhuǎn)和旋轉(zhuǎn)
mMatrix.preTranslate(-e.x, -e.y);//沿當(dāng)前XY軸負(fù)方向位移得到 矩形A?B?C?D?
mMatrix.postTranslate(e.x, e.y);//沿原XY軸方向位移得到 矩形A4 B4 C4 D4
canvas.drawBitmap(contentBitmap, mMatrix, null);
canvas.restore();
}
}
效果如圖

還不夠完美,可以觀察到翻起的當(dāng)前頁背面還有一些空白的地方?jīng)]有繪制內(nèi)容,這是因?yàn)?strong>C區(qū)域的內(nèi)容是通過當(dāng)前頁矩形翻轉(zhuǎn)、旋轉(zhuǎn)、位移后得到的,所以也是矩形,自然不能覆蓋曲線的邊緣區(qū)域。我們需要對(duì)這些邊緣區(qū)域進(jìn)行處理,根據(jù)背景的復(fù)雜度需要不同的處理方法,因?yàn)槲覀兪?strong>純色背景,所以處理起來會(huì)方便一些,這里只給出純色背景的處理方案,其他背景圖案大家自行擴(kuò)展即可
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區(qū)域內(nèi)容
* @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區(qū)域不同于A區(qū)域的部分
canvas.clipPath(getPathC(),Region.Op.UNION);//裁剪出A和C區(qū)域的全集
canvas.clipPath(getPathC(), Region.Op.INTERSECT);//取與C區(qū)域的交集
}
}
效果如圖

至此本篇教程到此結(jié)束,至于填充的內(nèi)容如何自定義就留給大家自行擴(kuò)展了,原理都是一樣的。如果大家看了感覺還不錯(cuò)麻煩點(diǎn)個(gè)贊,你們的支持是我最大的動(dòng)力~