Android Canvas 实现抽屉动画和翻滚动画
需求
在开发中,有这样的需求,view在某种操作下会有抽屉的展示,而且里面的文字会有翻滚的效果,如下:
即列表向上滑动时,旁边的气泡会折叠(并未折叠所有),向下滑动时,气泡会展开,当有新内容时,里面的文字内容会翻滚显示。
实现
所有的效果都是通过Canvans来实现,也是希望借该博客来和大家来分享一下,当开发时视觉需要实现某些动画效果时,第一想到的不是通过Google去搜索别人的实现,然后去套用,而是沉下心来去思考,去分解动画,然后通过Canvas去实现,当你这样做以后,你会发现实际上并非这么难,才能以不变应万变。这样当需求改变时,才能很快的解决。
抽屉效果
-
分解展开动画
�上图可以看出,动画从第一幅图到最后一幅图右边的圆角是不变的,这时候很容易就有思路,假设整个动画的持续时间是500ms,我们只需要在500ms之内匀速�绘制一个半圆角矩形就可以了。
-
代码实现
绘制初始化圆角矩形
private void drawView(Canvas canvas) { mDrawPath.reset(); mDrawPath.addRoundRect(mExpandViewRect, mRadiusRectF, Path.Direction.CW); canvas.drawPath(mDrawPath, mBorderPaint); //绘制背景 mDrawPath.reset(); mDrawPath.addRoundRect(mExpandViewRect, mRadiusRectF, Path.Direction.CW); canvas.drawPath(mDrawPath, mBackPaint); }
我们通过Path来绘制半圆角矩形,每个角的角度可以自己设置。如下代码:
public void setRadius(float leftTop, float rightTop, float rightBottom, float leftBottom) { mRadiusRectF[0] = leftTop; mRadiusRectF[1] = leftTop; mRadiusRectF[2] = rightTop; mRadiusRectF[3] = rightTop; mRadiusRectF[4] = rightBottom; mRadiusRectF[5] = rightBottom; mRadiusRectF[6] = leftBottom; mRadiusRectF[7] = leftBottom; }
当外界触发展开操作时,设置当前状态为从展开到折叠,然后在onDraw函数里进行绘制,如果设置动画时间为500ms。
private void drawExpand2CollapseView(Canvas canvas) { float ratio = getAnimRatio(); float distance = mExpandOrCollapseDistance * ratio; int left = (int) (mBorderWidth + distance); mExpandViewRect.set(left, mExpandViewRect.top, mExpandViewRect.right, mExpandViewRect.bottom); drawView(canvas); drawCollapseText(canvas); if (ratio == 1) { mCurrentStatus = STATUS_COLLAPSE; } else { invalidate(); } } private float getAnimRatio() { long now = System.currentTimeMillis(); float ratio = (now - mStartTime) / mDrawerAnimDuration; return ratio >= 1 ? 1 : ratio; }
其中mStartTime是触发展开操作的起始时间,通过当前时间和起始时间的差值再来除以动画持续的时间,得到当前的比例,再把比例乘以需要伸缩的那部分距离,就得到当前绘制的长度,如果比例为1,则证明时间用完,动画结束。大体的思路是这个样子。
文字在这个过程中怎么处理,完全随着自己业务需要而修改,比方说字体平移,字体变大都可以,只需要拿到这个比例算出值,然后通过canvas来绘制即可,非常方便
翻转效果
android里面的翻转效果很多童鞋会想到用TextSwitcher来实现,但是在这种场景下是做不了的,因为首先这个view是一个圆角矩形,如果把他作为TextSwitcher的子view来进行翻转,会发现有圆角哪一侧有很显然的漏洞,�如下如所示:
所以还是得用Canvas来实现这个动效,视觉要求,当有新内容来时,里面的文字垂直滚动即可。
-
分解滚动动画
当下一个文字来时,上一个文字向上平移并且透明度变小,新来的文字慢慢平移到中间,并且透明度变大。知道了这么多,代码写起来就得心应手了。下面截下关键代码:
-
实现
private void drawNextTextView(Canvas canvas) { drawExpandBack(canvas); float ratio = getNextTextRatio(); String text; Rect bounds; Paint.FontMetricsInt fontMetrics = null; int baseline; float start; if (ratio != 1) { bounds = new Rect(); mTextPaint.getTextBounds(mCurrentText, 0, mCurrentText.length(), bounds); start = mExpandViewRect.left + mLeftTopRectRadius * 0.8f + (mExpandViewRect.width() - mLeftTopRectRadius - bounds.width()) / 2; fontMetrics = mTextPaint.getFontMetricsInt(); baseline = (int) ((mHeight * (1 - ratio) - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top); mTextPaint.setAlpha((int) (255 * (1 - ratio))); canvas.drawText(mCurrentText, start, baseline, mTextPaint); bounds = new Rect(); mTextPaint.getTextBounds(mNextText, 0, mNextText.length(), bounds); start = mExpandViewRect.left + mLeftTopRectRadius * 0.8f + (mExpandViewRect.width() - mLeftTopRectRadius - bounds.width()) / 2; baseline = (int) (((mHeight - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top) + mHeight / 2 * (1 - ratio)); mTextPaint.setAlpha((int) (255 * ratio)); canvas.drawText(mNextText, start, baseline, mTextPaint); postInvalidateDelayed(50); } else { mTextPaint.setAlpha(255); bounds = new Rect(); mTextPaint.getTextBounds(mNextText, 0, mNextText.length(), bounds); fontMetrics = mTextPaint.getFontMetricsInt(); start = mExpandViewRect.left + mLeftTopRectRadius * 0.8f + (mExpandViewRect.width() - mLeftTopRectRadius - bounds.width()) / 2; baseline = ((mHeight - fontMetrics.bottom + fontMetrics.top) / 2 - fontMetrics.top); canvas.drawText(mNextText, start, baseline, mTextPaint); String text1 = mNextText; mNextText = mCurrentText; mCurrentText = text1; } }
基本思路也是设置一个动画时间,算出当前变化的比例,通过Canvas来不停的绘制文字达到滚动的效果。如果说需要有更立体的效果,比方说翻滚的过程中字体大小也需要改变,按照这样的思路,设置paint的textsize�即可。
好,今天的分享就到这里。这里贴出这个效果的 Github Demo地址。�里面还有其他动画哟,多多支持,点个赞哈。