Android自定义View——从零开始实现书籍翻页效果(一)

/**

  • 获取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;
    }
    }

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

区域C理论上应该是由点a,b,d,i,k连接而成的闭合区域,但由于di是曲线上的点,我们没办法直接从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;
    }

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

我们将两条曲线也画出来对比观察

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

观察分析后可以得出结论,区域C由直线ab,bd,dj,ik,ak连接而成的区域 减去 与区域A交集部分 后剩余的区域。于是我们设置区域C画笔Xfermode模式为DST_ATOP

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

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后是区域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;
    }

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

翻页可以从右下方翻自然也可以从右上方翻,我们将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;
    }
    }

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传


测量及自适应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.xviewWidth && f.y0){
bitmapCanvas.drawPath(getPathAFromTopRight(),pathAPaint);
}else if(f.xviewWidth && f.yviewHeight){
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:clickabletrue,否则无法监听到ACTION_MOVEACTION_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”/>

效果如图

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

到这里我们已经实现了基本的翻页效果,但要还原真实的书籍翻页效果,我们还需要设置一些限制条件来完善我们的项目


限制右侧翻页的最大距离

对于一般的书本来说,最左侧应该是钉起来的,也就是说如果我们从右侧翻页,翻动的距离是有限制的,最下方翻页形成的曲线起点(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

文末

当你打算跳槽的时候,应该把“跳槽成功后,我能学到什么东西?对我的未来发展有什么好处”放在第一位。这些东西才是真正引导你的关键。在跳槽之前尽量“物尽其用”,把手头上的工作做好,最好是完成了某个项目或是得到提升之后再走。跳槽不是目的,而是为了达到最终职业目标的手段

最后祝大家工作升职加薪,面试拿到心仪Offer


网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

尽量“物尽其用”,把手头上的工作做好,最好是完成了某个项目或是得到提升之后再走。跳槽不是目的,而是为了达到最终职业目标的手段**

最后祝大家工作升职加薪,面试拿到心仪Offer

[外链图片转存中…(img-wgFLBN9J-1714452079890)]
[外链图片转存中…(img-JUfkVyT3-1714452079890)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 17
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值