参考CSDN的博客:http://blog.csdn.net/aigestudio/article/details/42712269
最近在有这样的需求,完成类似掌阅和塔读的仿真翻页的效果。这里针对实现这种效果,对业务需求的理解记录如下:
1.参考图:
图片一 (图片来源 AigeStudio博客)
2.当前书页被翻起时 会呈现上边这种样子(当然如果C点翻过window的右上顶点时会展现另外一种效果,见图片二),整体的效果中中间区域是由以下线构成:贝塞尔曲线GIF + 线段FO + 线段OD + 贝塞尔曲线DJC + 贝塞尔曲线DJF的顶点F 到 贝塞尔曲线GIF的顶点I构成. (如果C点翻过window的右上顶点时中间区域构成的曲线为: 贝塞尔曲线GIF + 线段FO + 线段OD + 线段D到window的右上顶点 + window的右上顶点 到 贝塞尔曲线GIF的顶点I 构成)
图片二 (图片来源 AigeStudio博客)
3.针对曲线上各点计算如下:
情况一:(CURVATURE 为曲度 1/4)
// 计算曲线起点G点
float startXBtm = btmX2 - CURVATURE * sizeShort;
float startYBtm = mViewHeight;
// 计算曲线终点 F点
float endXBtm = mPointX + (1 - CURVATURE) * (tempAM);
float endYBtm = mPointY + (1 - CURVATURE) * mL;
// 计算曲线控制点 A点
float controlXBtm = btmX2;
float controlYBtm = mViewHeight;
// 计算曲线顶点 I点 (因为三角形是等腰三角形,贝塞尔曲线的顶点是三角形的控制点上高的中点,所以在坐标轴中 可以计算得到其坐标,方法如下)
float bezierPeakXBtm = 0.25F * startXBtm + 0.5F * controlXBtm + 0.25F * endXBtm;
float bezierPeakYBtm = 0.25F * startYBtm + 0.5F * controlYBtm + 0.25F * endYBtm;
/** 生成带曲线的四边形路径*/
mPath.moveTo(startXBtm, startYBtm);
mPath.quadTo(controlXBtm, controlYBtm, endXBtm, endYBtm);
mPath.lineTo(mPointX, mPointY);
mPath.lineTo(topX1, 0);
mPath.lineTo(topX2, 0);
mPath.lineTo(bezierPeakXBtm, bezierPeakYBtm);
情况二:
/** 计算参数 */
float leftY = mViewHeight - sizeLong;
float btmX = mViewWidth - sizeShort;
// 计算曲线起点 G点 和 C点
float startXBtm = btmX - CURVATURE * sizeShort;
float startYBtm = mViewHeight;
float startXLeft = mViewWidth;
float startYLeft = leftY - CURVATURE * sizeLong;
/** 限制左侧曲线起点*/
if (startYLeft <= 0) {
startYLeft = 0;
}
/* * 限制右侧曲线起点 */
if (startXBtm <= 0) {
startXBtm = 0;
}
// 计算曲线终点 F点和 D点
float endXBtm = mPointX + (1 - CURVATURE) * (tempAM);
float endYBtm = mPointY + (1 - CURVATURE) * mL;
float endXLeft = mPointX + (1 - CURVATURE) * mK;
float endYLeft = mPointY - (1 - CURVATURE) * (sizeLong - mL);
// 计算曲线控制点 A点 和 B点
float controlXBtm = btmX;
float controlYBtm = mViewHeight;
float controlXLeft = mViewWidth;
float controlYLeft = leftY;
// 计算曲线顶点 I点 和 J点
float bezierPeakXBtm = 0.25F * startXBtm + 0.5F * controlXBtm + 0.25F * endXBtm;
float bezierPeakYBtm = 0.25F*startYBtm+0.5F*controlYBtm+0.25F* endYBtm;
Float bezierPeakXLeft=0.25F * startXLeft + 0.5F * controlXLeft + 0.25F * endXLeft;
float bezierPeakYLeft = 0.25F * startYLeft + 0.5F * controlYLeft + 0.25F * endYLeft;
/** 生成带曲线的三角形路径 */
mPath.moveTo(startXBtm, startYBtm);
mPath.quadTo(controlXBtm, controlYBtm, endXBtm, endYBtm);
mPath.lineTo(mPointX, mPointY);
mPath.lineTo(endXLeft, endYLeft);
mPath.quadTo(controlXLeft, controlYLeft, startXLeft, startYLeft);
4.对于当前页 及下一页的绘制
当前页的绘制:(绘制的是贝塞尔曲线与顶角的连接的区域图 与 当前页的整张图的异或处理 就是当前页的图)
mPath0.reset();
mPath0.moveTo(mBezierStart1.x, mBezierStart1.y);
mPath0.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x,
mBezierEnd1.y);
mPath0.lineTo(mTouch.x, mTouch.y);
mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y);
mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x,
mBezierStart2.y);
mPath0.lineTo(mCornerX, mCornerY);
mPath0.close();
canvas.save();
canvas.clipPath(path, Region.Op.XOR);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.restore();
绘制下一页:(绘制第二页的内容 INTERSECT 表示两个集合的交集)
mPath1.reset();
mPath1.moveTo(mBezierStart1.x, mBezierStart1.y);
mPath1.lineTo(mBeziervertex1.x, mBeziervertex1.y);
mPath1.lineTo(mBeziervertex2.x, mBeziervertex2.y);
mPath1.lineTo(mBezierStart2.x, mBezierStart2.y);
mPath1.lineTo(mCornerX, mCornerY);
mPath1.close();
mDegrees = (float) Math.toDegrees(Math.atan2(mBezierControl1.x
- mCornerX, mBezierControl2.y - mCornerY));
int leftx;
int rightx;
GradientDrawable mBackShadowDrawable;
if (mIsRTandLB) {
leftx = (int) (mBezierStart1.x);
rightx = (int) (mBezierStart1.x + mTouchToCornerDis / 4);
mBackShadowDrawable = mBackShadowDrawableLR;
} else {
leftx = (int) (mBezierStart1.x - mTouchToCornerDis / 4);
rightx = (int) mBezierStart1.x;
mBackShadowDrawable = mBackShadowDrawableRL;
}
canvas.save();
canvas.clipPath(mPath0);
canvas.clipPath(mPath1, Region.Op.INTERSECT);
canvas.drawBitmap(bitmap, 0, 0, null);
canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y);
mBackShadowDrawable.setBounds(leftx, (int) mBezierStart1.y, rightx,
(int) (mMaxLength + mBezierStart1.y));
mBackShadowDrawable.draw(canvas);
canvas.restore();
绘制当前页翻页时的阴影(阴影是根据当前页翻起时贝塞尔曲线两顶点的连接线上与触摸点的连线围成的一个区域周边显示的阴影效果)
int i = (int) (mBezierStart1.x + mBezierControl1.x) / 2;
float f1 = Math.abs(i - mBezierControl1.x);
int i1 = (int) (mBezierStart2.y + mBezierControl2.y) / 2;
float f2 = Math.abs(i1 - mBezierControl2.y);
float f3 = Math.min(f1, f2);
mPath1.reset();
mPath1.moveTo(mBeziervertex2.x, mBeziervertex2.y);
mPath1.lineTo(mBeziervertex1.x, mBeziervertex1.y);
mPath1.lineTo(mBezierEnd1.x, mBezierEnd1.y);
mPath1.lineTo(mTouch.x, mTouch.y);
mPath1.lineTo(mBezierEnd2.x, mBezierEnd2.y);
mPath1.close();
GradientDrawable mFolderShadowDrawable;
int left;
int right;
if (mIsRTandLB) {
left = (int) (mBezierStart1.x - 1);
right = (int) (mBezierStart1.x + f3 + 1);
mFolderShadowDrawable = mFolderShadowDrawableLR;
} else {
left = (int) (mBezierStart1.x - f3 - 1);
right = (int) (mBezierStart1.x + 1);
mFolderShadowDrawable = mFolderShadowDrawableRL;
}
canvas.save();
canvas.clipPath(mPath0);
canvas.clipPath(mPath1, Region.Op.INTERSECT);
mPaint.setColorFilter(mColorMatrixFilter);
float dis = (float) Math.hypot(mCornerX - mBezierControl1.x,
mBezierControl2.y - mCornerY);
float f8 = (mCornerX - mBezierControl1.x) / dis;
float f9 = (mBezierControl2.y - mCornerY) / dis;
mMatrixArray[0] = 1 - 2 * f9 * f9;
mMatrixArray[1] = 2 * f8 * f9;
mMatrixArray[3] = mMatrixArray[1];
mMatrixArray[4] = 1 - 2 * f8 * f8;
mMatrix.reset();
mMatrix.setValues(mMatrixArray);
mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y);
mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y);
canvas.drawBitmap(bitmap, mMatrix, mPaint);
mPaint.setColorFilter(null);
canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y);
mFolderShadowDrawable.setBounds(left, (int) mBezierStart1.y, right,
(int) (mBezierStart1.y + mMaxLength));
mFolderShadowDrawable.draw(canvas);
canvas.restore();
5.因为只是项目的一部分,暂时没有拆分出来,所以暂时没有demo。仅做记录。
再次声明下参考博客:http://blog.csdn.net/aigestudio/article/details/42712269