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();//闭合区域
return pathA;
}
}
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a51342e05a9?imageView2/0/w/1280/h/960/ignore-error/1)
**区域C**理论上应该是由点**a**,**b**,**d**,**i**,**k**连接而成的闭合区域,但由于**d**和**i**是曲线上的点,我们没办法直接从**d**出发通过**path**绘制路径连接**b**点(**i**,**k**同理),也就不能只用**path**的情况下直接绘制出**区域C**,我们需要用**PorterDuffXfermode**方面的知识“曲线救国”。我们试着先将点**a**,**b**,**d**,**i**,**k**连接起来,观察闭合区域与**区域A**之间的联系。修改**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);
}
/**
- 绘制区域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();//闭合区域 return pathC; }
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a51328cc528?imageView2/0/w/1280/h/960/ignore-error/1)
我们将两条曲线也画出来对比观察
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a5158cd3dc7?imageView2/0/w/1280/h/960/ignore-error/1)
观察分析后可以得出结论,**区域C**是 **由直线ab,bd,dj,ik,ak连接而成的区域** 减去 **与区域A交集部分** 后剩余的区域。于是我们设置**区域C**画笔**Xfermode**模式为**DST\_ATOP**
pathCPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.DST_ATOP));
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a515892fd66?imageView2/0/w/1280/h/960/ignore-error/1)
最后是**区域B**,因为**区域B**处于最底层,我们直接将**区域B**画笔**Xfermode**模式设为**DST\_ATOP**,在**区域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); }
/**
- 绘制区域B
- @return
*/ private Path getPathB(){ pathB.reset(); pathB.lineTo(0, viewHeight);//移动到左下角 pathB.lineTo(viewWidth,viewHeight);//移动到右下角 pathB.lineTo(viewWidth,0);//移动到右上角 pathB.close();//闭合区域 return pathB; }
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a515c1389b5?imageView2/0/w/1280/h/960/ignore-error/1)
翻页可以从右下方翻自然也可以从右上方翻,我们将**f**点设在右上角,由于View上下两部分是呈**镜像**的,所以各标识点的位置也应该是镜像对应的,因为**区域B和C**的绘制与**f**点没有关系,所以我们只需要修改**区域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;
}
}
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a515bdc35a8?imageView2/0/w/1280/h/960/ignore-error/1)
* * *
测量及自适应View的宽高
-------------
> ### 相关博文链接
>
> [浅谈自定义View的宽高获取](
)
>
> [教你搞定Android自定义View](
)
之前由于测试效果没有对View的大小进行重新测量,在实现触摸翻页之前先把这个结了。重写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**则在右下角,当手指离开屏幕时回到**初始状态**,根据需求,修改**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();
}
/**
* 回到默认状态
*/
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中监听View的**onTouch**状态
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();//回到默认状态 break; } return false; } });
注意,要设置**android:clickable**为**true**,否则无法监听到**ACTION\_MOVE**和**ACTION\_UP**状态
<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”/>
效果如图
![](https://user-gold-cdn.xitu.io/2017/12/14/16053a5164a04ce5?imageslim)
到这里我们已经实现了基本的翻页效果,但要还原真实的书籍翻页效果,我们还需要设置一些限制条件来完善我们的项目
* * *
限制右侧翻页的最大距离
-----------
对于一般的书本来说,最左侧应该是钉起来的,也就是说如果我们从右侧翻页,翻动的距离是**有限制的**,最下方翻页形成的曲线起点(**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
|