效果
CoffeeView
CoffeeView
大概思路
- 自定義view,直接繼承view
- 復(fù)寫onSizeChanged()方法,在此計(jì)算杯墊,杯子,煙霧效果的path
- 在onDraw()方法中,描繪杯墊,杯子
- 處理煙霧動畫效果
畫杯子
這里需要畫兩部分內(nèi)容,第一部分是杯子,第二部分是杯耳(提手的地方)
我們可以使用addRoundRect來描繪圓角矩形,并且可指定每個圓角的半徑即圓角的程度
/**
* Add a closed round-rectangle contour to the path. Each corner receives
* two radius values [X, Y]. The corners are ordered top-left, top-right,
* bottom-right, bottom-left
*
* @param rect The bounds of a round-rectangle to add to the path
* @param radii Array of 8 values, 4 pairs of [X,Y] radii
* @param dir The direction to wind the round-rectangle's contour
*/
public void addRoundRect(RectF rect, float[] radii, Direction dir) {
if (rect == null) {
throw new NullPointerException("need rect parameter");
}
addRoundRect(rect.left, rect.top, rect.right, rect.bottom, radii, dir);
}
以下代碼注釋:
//計(jì)算view的中心點(diǎn)坐標(biāo)
mCenterX = w / 2;
mCenterY = h / 2;
//杯子的寬,為view的寬度的2/3
float cupWidth = w * 2 / 3f;
//杯子的高,為view的高度3/8
float cupHeight = (h / 2) * 3 / 4f;
//計(jì)算出杯子的中心點(diǎn)坐標(biāo)
float cupCenterX = mCenterX;
float cupCenterY = mCenterY + cupHeight / 2;
//計(jì)算杯子矩形的左上右上的圓角半徑
float cupTopRoundRadius = Math.min(cupWidth, cupHeight) / 20f;
//計(jì)算杯子矩形的左下右下的圓角半徑
float cupBottomRoundRadius = cupTopRoundRadius * 10;
//重置杯子path
mCupPath.reset();
//添加杯子(杯身)軌跡
mCupPath.addRoundRect(new RectF(cupCenterX - cupWidth / 2, cupCenterY - cupHeight / 2 - cupHeight / 10, cupCenterX + cupWidth / 2, cupCenterY + cupHeight / 2), new float[]{cupTopRoundRadius, cupTopRoundRadius, cupTopRoundRadius, cupTopRoundRadius, cupBottomRoundRadius, cupBottomRoundRadius, cupBottomRoundRadius, cupBottomRoundRadius}, Path.Direction.CW);
//計(jì)算杯耳寬度
float cupEarWidth = (w - cupWidth) * 3 / 4f;
//計(jì)算杯耳高度
float cupEarHeight = cupHeight / 3;
//計(jì)算杯耳的中心點(diǎn)坐標(biāo)
float cupEarCenterX = mCenterX + cupWidth / 2;
float cupEarCenterY = mCenterY + cupHeight / 2;
//計(jì)算杯耳的圓角半徑
float cupEarRoundRadius = Math.min(cupEarWidth, cupEarHeight) / 2f;
//設(shè)置杯耳畫筆的描邊寬度
mCupEarPaint.setStrokeWidth(Math.min(cupEarWidth, cupEarHeight) / 3f);
//重置杯耳path
mCupEarPath.reset();
//添加杯耳軌跡
mCupEarPath.addRoundRect(new RectF(cupEarCenterX - cupEarWidth / 2, cupEarCenterY - cupEarHeight / 2 - cupHeight / 10, cupEarCenterX + cupEarWidth / 2,
cupEarCenterY + cupEarHeight / 2),
new float[]{cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius,
cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius, cupEarRoundRadius}, Path.Direction.CW);
在onDraw方法中
canvas.drawPath(mCupEarPath, mCupEarPaint);
canvas.drawPath(mCupPath, mCupPaint);
畫杯墊
首先計(jì)算杯墊path軌跡
//計(jì)算杯墊寬度
float coasterWidth = cupWidth;
//計(jì)算杯墊高度
float coasterHeight = (h / 2 - cupHeight) * 1 / 3f;
//計(jì)算杯墊中心點(diǎn)坐標(biāo)
float coasterCenterX = mCenterX;
float coasterCenterY = mCenterY + cupHeight + (h / 2 - cupHeight) / 2f;
//計(jì)算杯墊圓角半徑
float coasterRoundRadius = Math.min(coasterWidth, coasterHeight) / 2f;
//重置杯墊path
mCoasterPath.reset();
//添加杯墊軌跡
mCoasterPath.addRoundRect(new RectF(coasterCenterX - coasterWidth / 2, coasterCenterY - coasterHeight / 2,
coasterCenterX + coasterWidth / 2, coasterCenterY + coasterHeight / 2),
coasterRoundRadius, coasterRoundRadius, Path.Direction.CW);
在onDraw方法中
canvas.drawPath(mCoasterPath, mCoasterPaint);
畫煙霧
煙霧原理
- 根據(jù)貝塞爾曲線添加波浪軌跡
- 根據(jù)LinearGradient實(shí)現(xiàn)顏色漸變效果
每條煙霧大概如下效果
CoffeeView
當(dāng)移動至煙霧底部的時候,重新將其移動至頭部,這樣循環(huán)動畫,就會顯示無線的滾動效果
代碼注釋如下:
//計(jì)算煙霧的寬度
float vaporsStrokeWidth = cupWidth / 15f;
//計(jì)算煙霧相隔距離大小
float vaporsGapWidth = (cupWidth - VAPOR_COUNT * vaporsStrokeWidth) / 4f;
mVaporsHeight = cupHeight * 4 / 5f;
//設(shè)置煙霧畫筆描邊大小
mVaporPaint.setStrokeWidth(vaporsStrokeWidth);
float startX, startY, stopX, stopY;
//設(shè)置漸變效果
LinearGradient linearGradient = new LinearGradient(mCenterX, mCenterY, mCenterX, mCenterY - mVaporsHeight,
new int[]{mVaporColor, Color.TRANSPARENT},
null, Shader.TileMode.CLAMP);
//煙霧畫筆增加漸變效果的渲染器
mVaporPaint.setShader(linearGradient);
//增加每條煙霧的path的貝塞爾波浪
for (int i = 0; i < VAPOR_COUNT; i++) {
mVaporsPath[i].reset();
startX = (mCenterX - cupWidth / 2) + vaporsStrokeWidth / 2 + i * vaporsStrokeWidth + (i + 1) * vaporsGapWidth;
startY = mCenterY + mVaporsHeight;
stopX = startX;
stopY = mCenterY - mVaporsHeight;
mVaporsPath[i].moveTo(startX, startY);
mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, startY - mVaporsHeight / 4,
startX, startY - mVaporsHeight / 2);
mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, startY - mVaporsHeight * 3 / 4,
startX, mCenterY);
mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, mCenterY - mVaporsHeight / 4,
startX, mCenterY - mVaporsHeight / 2);
mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, mCenterY - mVaporsHeight * 3 / 4,
stopX, stopY);
//add twice the bezier curve
mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, stopY - mVaporsHeight / 4,
startX, stopY - mVaporsHeight / 2);
mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, stopY - mVaporsHeight * 3 / 4,
startX, stopY - mVaporsHeight);
mVaporsPath[i].quadTo(startX - vaporsGapWidth / 2, stopY - mVaporsHeight - mVaporsHeight / 4,
startX, stopY - mVaporsHeight - mVaporsHeight / 2);
mVaporsPath[i].quadTo(startX + vaporsGapWidth / 2, stopY - mVaporsHeight - mVaporsHeight * 3 / 4,
stopX, stopY - 2 * mVaporsHeight);
}
煙霧動畫處理:
每條煙霧都有一個path記錄其軌跡,利用path的transform方法可移動path
/**
* Transform the points in this path by matrix, and write the answer
* into dst. If dst is null, then the the original path is modified.
*
* @param matrix The matrix to apply to the path
* @param dst The transformed path is written here. If dst is null,
* then the the original path is modified
*/
public void transform(Matrix matrix, Path dst) {
long dstNative = 0;
if (dst != null) {
dst.isSimplePath = false;
dstNative = dst.mNativePath;
}
nTransform(mNativePath, matrix.native_instance, dstNative);
}
ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, -vaporHeight);
final int finalI = i;
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatorValues[finalI] = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
valueAnimator.setDuration(1000);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.setRepeatCount(ValueAnimator.INFINITE);
valueAnimator.setRepeatMode(ValueAnimator.RESTART);
mValueAnimators.add(valueAnimator);
在onDraw方法中,描繪煙霧
private void drawVapors(Canvas canvas){
for (int i = 0; i < VAPOR_COUNT; i++){
mCalculateMatrix.reset();
mCalculatePath.reset();
float animatedValue = mAnimatorValues[i];
mCalculateMatrix.postTranslate(0, animatedValue);
mVaporsPath[i].transform(mCalculateMatrix, mCalculatePath);
canvas.drawPath(mCalculatePath, mVaporPaint);
}
}
我這里設(shè)置了每條煙霧的移動速度是一樣的,你們可以下載源碼來修改,看看不同的移動速度的效果