本文實(shí)現(xiàn)小說翻頁的基本原理
自定義View實(shí)戰(zhàn)篇(二)實(shí)現(xiàn)小說翻頁二實(shí)現(xiàn)翻頁動畫窃诉、陰影杨耙、內(nèi)容
一、簡介
首先感謝hmg25的Android 實(shí)現(xiàn)書籍翻頁效果----原理篇飘痛,本文參考其實(shí)現(xiàn)珊膜,旨在鞏固這方面的知識,以及為自己后面的實(shí)戰(zhàn)做準(zhǔn)備宣脉。
研讀了實(shí)戰(zhàn)上面的原理篇之后车柠,我們可以知道實(shí)現(xiàn)翻頁效果,其實(shí)是根據(jù)一些動態(tài)點(diǎn)進(jìn)行計算塑猖,然后進(jìn)行剪切竹祷,最后繪制在畫布上,下面依次寫出各個點(diǎn)的計算方法羊苟。
首先塑陵,我們將綠色部分稱作A
區(qū)域、藍(lán)色為B
區(qū)域蜡励、黃色為C
區(qū)域猿妈。
a
:觸摸點(diǎn),在onTouchonTouchEvent()
中獲取X巍虫、Y坐標(biāo)。f
:即view的大小鳍刷,通過onMeasuer()
獲取View的寬高占遥。-
g
:g
是af
的中點(diǎn),根據(jù)數(shù)學(xué)公式可得:g.x=(a.x+f.x)/2;
g.y=(a.y+f.y)/2
; -
e:根據(jù)相似三角形
egm
和ggm
可知输瓜,對應(yīng)邊成比例可得:e.x = g.x - (f.y - g.y) * ((f.y - g.y) / (f.x - g.x));
e.y = f.y;
-
h:同理瓦胎,根據(jù)相似三角形
egf
和fgh
可得:h.x = f.x;
h.y = g.y - (f.x - g.x) * (f.x - g.x) / (f.y - g.y);
-
c:設(shè)
n
為ag
中點(diǎn),同理尤揣,根據(jù)相似三角形fge
和fnc
搔啊,且比例為1:2,可得c.x = e.x - (f.x - e.x) / 2;
c.y = f.y;
-
c:設(shè)
n
為ag
中點(diǎn)北戏,同理负芋,根據(jù)相似三角形fgh
和fnj
,且比例為1:2嗜愈,可得j.x = f.x;
j.y = h.y - (f.y - h.y) / 2;
-
b
&k
:根據(jù)相似三角形abk
和aeh
旧蛾,且比例為1:2,可得
b.x = (a.x+e.x)/2;
b.y = (a.y+e.y)/2;
k.x = (a.x+h.x)/2;
k.y = (a.y+h.y)/2;
-
p
為cb
的中點(diǎn)蠕嫁,d
為pe
的中點(diǎn)锨天,所以d.x=((c.x+b.x)/2+e.x)/2
d.y=((c.y+b.y)/2+e.y)/2
r
為kj
的中點(diǎn),d
為hr
的中點(diǎn)剃毒,所以
i.x=((k.x+j.x)/2+h.x)/2
i.y=((k.y+j.y)/2+h.y)/2
二病袄、實(shí)現(xiàn)仿真翻頁
1搂赋、基本實(shí)現(xiàn)
(1)首先,我們定義一個類保存各個點(diǎn)的坐標(biāo)益缠,然后由觸摸點(diǎn)a
和已知的點(diǎn)f
獲取其他坐標(biāo)脑奠,由此我們通過不斷獲取觸摸點(diǎn)然后配合f點(diǎn)坐標(biāo)對各個點(diǎn)進(jìn)行更新。
public class Point {
public float x;
public float y;
}
/**
* 計算各個點(diǎn)的坐標(biāo)
* @param a a點(diǎn)的坐標(biāo)
* @param f f點(diǎn)的坐標(biāo)
*/
void calculationPoint(Point a, Point 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.x = (a.x + e.x) / 2;
b.y = (a.y + e.y) / 2;
k.x = (a.x + h.x) / 2;
k.y = (a.y + h.y) / 2;
d.x = ((c.x + b.x) / 2 + e.x) / 2;
d.y = ((c.y + b.y) / 2 + e.y) / 2;
i.x = ((k.x + j.x) / 2 + h.x) / 2;
i.y = ((k.y + j.y) / 2 + h.y) / 2;
}
(2)獲取各個點(diǎn)坐標(biāo)后左刽,我們需要對A
捺信、B
、C
區(qū)域得到去Path
路徑
A
區(qū)域計算方法:
左下角A
區(qū)域我們可以從0.0
出發(fā)欠痴,畫直線至左下角迄靠,然后畫直線到C
點(diǎn),然后由二次貝塞爾曲線到b
點(diǎn)喇辽,然后畫直線a
點(diǎn)掌挚,在畫直線到k
點(diǎn),再由二次貝塞爾曲線到j
點(diǎn)菩咨,然后畫直線到右上角最后閉合到0.0
點(diǎn)吠式。
mPathA.reset();
mPathA.lineTo(0, height);
mPathA.lineTo(c.x, c.y);
mPathA.quadTo(e.x, e.y, b.x, b.y);
mPathA.lineTo(a.x, a.y);
mPathA.lineTo(k.x, k.y);
mPathA.quadTo(h.x, h.y, j.x, j.y);
mPathA.lineTo(width, 0);
mPathA.close();
return mPathA;
B
區(qū)域計算方法:我們只需獲取整個頁面的path
即可,原因是因?yàn)槲覀兎摰奈恢每赡苁侨我庖粋€角抽米,但是如果我們將區(qū)域B
也跟隨F
點(diǎn)去判斷的話特占,那代碼將不夠靈了。
mPathB.reset();
mPathB.lineTo(0, height);
mPathB.lineTo(width, height);
mPathB.lineTo(width, 0);
mPathB.close();//閉合區(qū)域
return mPathB;
C
區(qū)域計算方法:C
區(qū)域與A
基本相同
mPathC.reset();
mPathC.moveTo(i.x, i.y);
mPathC.lineTo(d.x, d.y);
mPathC.lineTo(b.x, b.y);
mPathC.lineTo(a.x, a.y);
mPathC.lineTo(k.x, k.y);
mPathC.close();//閉合區(qū)域
return mPathC;
(3)對A
云茸、B
是目、C
根據(jù) Path
進(jìn)行裁切,裁切我們要用到Canvans
的clipPath
方法:
clipPath
由兩個構(gòu)造方法clipPath(Path path)
标捺、clipPath(Path path, Region.Op op)
-
op:
DIFFRENCE
是第一次不同于第二次的部分顯示A-B
REPLAC
是顯示第二次的B
REVERSE_DIFFRENCE
是第二次不同于第一次的部分顯示
INTERSECT
是交集顯示
UNION
是全部顯示A+B
XOR
是補(bǔ)集(全集減去交集剩余部分)顯示
A區(qū)域安裝Path直接剪切
canvas.clipPath(getPathA());
B區(qū)域懊纳,先剪切A
,在剪切C
,然后我們設(shè)置UNION
即剪切A+C
的區(qū)域亡容,然后設(shè)置REVERSE_DIFFERENCE
嗤疯,剪切除A+C
的部分,即B
的部分闺兢。
canvas.clipPath(getPathA());
canvas.clipPath(getPathC(), Region.Op.UNION);
canvas.clipPath(getPathB(), Region.Op.REVERSE_DIFFERENCE);
C區(qū)域:這里需要思考茂缚,為什么我們不直接剪切C
而是先剪切A
在剪切C
且減去與區(qū)域A
的交集部分
canvas.clipPath(getPathA());
canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);
當(dāng)然到這里我們已經(jīng)可以將代碼組合一下,實(shí)現(xiàn)最簡單的翻頁了列敲。
(3)通過觸摸事件阱佛,實(shí)現(xiàn)滑動,完整代碼如下戴而。
/**
* @author Active_Loser
* @date 2018/11/18
* Content: 自定義PageView
* A: 表示當(dāng)前頁面
* B: 表示上一頁或下一頁的頁面
* C: 表示翻起的頁面凑术,即當(dāng)前頁的背面
*/
public class PageView extends View {
private Path mPathA;
private Path mPathB;
private Path mPathC;
private Bitmap mBitmapA;
private Bitmap mBitmapB;
private Bitmap mBitmapC;
/**
* 測量出view的寬高
*/
private int width, height;
private Point a, f, g, e, h, c, j, b, k, d, i;
public PageView(Context context) {
this(context, null);
}
public PageView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public PageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
a = new Point();
f = new Point();
g = new Point();
e = new Point();
h = new Point();
c = new Point();
j = new Point();
b = new Point();
k = new Point();
d = new Point();
i = new Point();
mPathA = new Path();
mPathB = new Path();
mPathC = new Path();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
width = getDefaultSize(600, widthMeasureSpec);
height = getDefaultSize(1000, heightMeasureSpec);
setMeasuredDimension(width, height);
f.x = width;
f.y = height;
mBitmapA = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mBitmapB = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
mBitmapC = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas mCanvasA = new Canvas(mBitmapA);
mCanvasA.drawColor(Color.GREEN);
Canvas mCanvasB = new Canvas(mBitmapB);
mCanvasB.drawColor(Color.YELLOW);
Canvas mCanvasC = new Canvas(mBitmapC);
mCanvasC.drawColor(Color.BLUE);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
a.x = x;
a.y = y;
calculationPoint(a, f);
postInvalidate();
return true;
}
@Override
protected void onDraw(Canvas canvas) {
drawA(canvas);
drawC(canvas);
drawB(canvas);
}
/**
* 剪切A區(qū)域
*/
private void drawA(Canvas canvas) {
canvas.save();
canvas.clipPath(getPathA());
canvas.drawBitmap(mBitmapA, 0, 0, null);
canvas.restore();
}
/**
* 剪切C區(qū)域
*/
private void drawC(Canvas canvas) {
canvas.save();
canvas.clipPath(getPathA());
canvas.clipPath(getPathC(), Region.Op.REVERSE_DIFFERENCE);
canvas.drawBitmap(mBitmapC, 0, 0, null);
canvas.restore();
}
/**
* 剪切B區(qū)域
*/
private void drawB(Canvas canvas) {
canvas.save();
canvas.clipPath(getPathA());
canvas.clipPath(getPathC(), Region.Op.UNION);
canvas.clipPath(getPathB(), Region.Op.REVERSE_DIFFERENCE);
canvas.drawBitmap(mBitmapB, 0, 0, null);
canvas.restore();
}
/**
* 獲取區(qū)域A的path
*/
private Path getPathA() {
mPathA.reset();
mPathA.lineTo(0, height);
mPathA.lineTo(c.x, c.y);
mPathA.quadTo(e.x, e.y, b.x, b.y);
mPathA.lineTo(a.x, a.y);
mPathA.lineTo(k.x, k.y);
mPathA.quadTo(h.x, h.y, j.x, j.y);
mPathA.lineTo(width, 0);
mPathA.close();
return mPathA;
}
/**
* 獲取區(qū)域C的path
*/
private Path getPathC() {
mPathC.reset();
mPathC.moveTo(i.x, i.y);
mPathC.lineTo(d.x, d.y);
mPathC.lineTo(b.x, b.y);
mPathC.lineTo(a.x, a.y);
mPathC.lineTo(k.x, k.y);
mPathC.close();//閉合區(qū)域
return mPathC;
}
/**
* 獲取區(qū)域B的path
*/
private Path getPathB() {
mPathB.reset();
mPathB.lineTo(0, height);
mPathB.lineTo(width, height);
mPathB.lineTo(width, 0);
mPathB.close();//閉合區(qū)域
return mPathB;
}
/**
* 計算各個點(diǎn)的坐標(biāo)
*
* @param a a點(diǎn)的坐標(biāo)
* @param f f點(diǎn)的坐標(biāo)
*/
void calculationPoint(Point a, Point 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.x = (a.x + e.x) / 2;
b.y = (a.y + e.y) / 2;
k.x = (a.x + h.x) / 2;
k.y = (a.y + h.y) / 2;
d.x = ((c.x + b.x) / 2 + e.x) / 2;
d.y = ((c.y + b.y) / 2 + e.y) / 2;
i.x = ((k.x + j.x) / 2 + h.x) / 2;
i.y = ((k.y + j.y) / 2 + h.y) / 2;
}
}
2、限制翻頁距離
我們觀察上面的翻頁動畫最后時所意,淮逊,我們的書籍隨時翻動催首,但是左側(cè)最后也隨之翻動起來,這樣明顯不符合翻頁的規(guī)則泄鹏,我們對C
點(diǎn)進(jìn)行限制郎任。
思考,若我們的C
點(diǎn)為負(fù)數(shù)备籽,即左側(cè)也被翻起的時候传黄,我們需要將C
點(diǎn)一直放在零界點(diǎn)的位置勾怒,而j
點(diǎn)繼續(xù)向上移動糖驴,因此我們使用相似圖形的原理淆党,梯形camf和c1a1m1f1相似,重新計算a
的坐標(biāo)(a1
)珠闰。
private void calculationAByYouch(){
float cf = width - c.x;
float pf = Math.abs(f.x - a.x);
float p1f = width * pf / cf;
a.x = Math.abs(f.x - p1f);
float h1 = Math.abs(f.y - a.y);
float a1p1 = h1 * pf / cf;
a.y = Math.abs(f.y - a1p1);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
float x = event.getX();
float y = event.getY();
a.x = x;
a.y = y;
calculationPoint(a, f);
if (c.x<0){
calculationAByYouch();
calculationPoint(a, f);
}
postInvalidate();
return true;
}