自定义view实现阻尼效果的加载动画
需要知识:
1.二次贝塞尔曲线
2.动画知识
3.基础自定义view知识
先来解释下什么叫阻尼运动
阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减的振动,又称减幅振动、衰减振动。[1]不论是弹簧振子还是单摆由于外界的摩擦和介质阻力总是存在,在振动过程中要不断克服外界阻力做功,消耗能量,振幅就会逐渐减小,经过一段时间,振动就会完全停下来。这种振幅随时间减小的振动称为阻尼振动.因为振幅与振动的能量有关,阻尼振动也就是能量不断减少的振动.阻尼振动是非简谐运动.阻尼振动系统属于耗散系统。这里的阻尼是指任何振动系统在振动中,由于外界作用或系统本身固有的原因引起的振动幅度逐渐下降的特性,以及此一特性的量化表征。
本例中文字部分凹陷就是这种效果,当然这篇文章知识带你简单的使用.
跳动的水果效果实现
剖析:从上面的效果图中很面就是从顶端向下掉落然后再向上期间旋转即可.
那么我们首先自定义一个view继承FrameLayout
publicclassMyextendsFrameLayout{
publicMy(Contextcontext){
super(context);
}
publicMy(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
}
也许有人会问我看到你效果图到顶部或者底部就变成向上或者向下了.你三张够吗?
答:到顶部或者底部旋转180度即可
我们现在自定义中定义几个变量
//用于记录当前图片使用数组中的哪张
intindexImgFlag=0;
//下沉图片前面三个图片的id
intallImgDown[]={R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};
//动画效果一次下沉或上弹的时间animationDuration2=一次完整动画时间
intanimationDuration=1000;
//弹起来的图片
ImageViewiv;
//图片下沉高度(即从最高点到最低点的距离)
intdownHeight=2;
//掉下去的动画
privateAnimationtranslateDown;
//弹起动画
privateAnimationtranslateUp;
//旋转动画
privateObjectAnimatorrotation;
我们再来看看初始化动画的方法(此方法使用了递归思想,实现无限播放动画,大家可以看看哪里不理解)
//初始化弹跳动画
publicvoidMyAnmation(){
//下沉效果动画
translateDown=newTranslateAnimation(
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);
translateDown.setDuration(animationDuration);
//设置一个插值器动画将会播放越来越快模拟重力
translateDown.setInterpolator(newAccelerateInterpolator());
//上弹动画
translateUp=newTranslateAnimation(
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0
);
translateUp.setDuration(animationDuration);
////设置一个插值器动画将会播放越来越慢模拟反重力
translateUp.setInterpolator(newDecelerateInterpolator());
//当下沉动画完成时播放启动上弹
translateDown.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationStart(Animationanimation){
iv.setImageResource(allImgDown[indexImgFlag]);
rotation=ObjectAnimator.ofFloat(iv,"rotation",180f,360f);
rotation.setDuration(1000);
rotation.start();
}
@Override
publicvoidonAnimationEnd(Animationanimation){
iv.startAnimation(translateUp);
}
@Override
publicvoidonAnimationRepeat(Animationanimation){
}
});
//当上移动画完成时播放下移动画
translateUp.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationStart(Animationanimation){
indexImgFlag=1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;
iv.setImageResource(allImgDown[indexImgFlag]);
rotation=ObjectAnimator.ofFloat(iv,"rotation",0.0f,180f);
rotation.setDuration(1000);
rotation.start();
}
@Override
publicvoidonAnimationEnd(Animationanimation){
//递归
iv.startAnimation(translateDown);
}
@Override
publicvoidonAnimationRepeat(Animationanimation){
}
});
}
以上代码知识:
插值器:会让一个动画在播放时在某一时间段加快动画或者减慢
//设置一个插值器动画将会播放越来越快模拟重力
1.translateDown.setInterpolator(newAccelerateInterpolator());
这个插值器速率表示图:
可以从斜率看到使用此插值器动画将越来越快.意义在于模仿下落时重力的影响
////设置一个插值器动画将会播放越来越慢模拟反重力
2.translateUp.setInterpolator(newDecelerateInterpolator());
速率图:
最后我们初始化下图片控件到我们的自定义view
privatevoidinit(){
//初始化弹跳图片控件
iv=newImageView(getContext());
ViewGroup.LayoutParamsparams=newViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
iv.setLayoutParams(params);
iv.setImageResource(allImgDown[0]);
this.addView(iv);
iv.measure(0,0);
iv.getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener(){
@Override
publicvoidonGlobalLayout(){
if(!flagMeure)
{
flagMeure=true;
//由于画文字是由基准线开始
path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2,textHeight+iv.getHeight()+downHeightiv.getHeight());
//计算最大弹力
maxElasticFactor=(float)(textHeight/elastic);
//初始化贝塞尔曲线
path.rQuadTo(textWidth/2,0,textWidth,0);
//初始化上弹和下沉动画
MyAnmation();
iv.startAnimation(translateDown);
}
}
});
}
上面的知识:
1.iv.measure(0,0);主动通知系统去测量此控件不然iv.getwidth=0;
//下面这个是同理等iv测量完时回调不然iv.getwidth=0;
2.iv.getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener(){
…
}
原因:TextViewtv=newTextView()或者LayoutInflat填充布局都是
异步所以你在new出来或者填充时直接获取自然返回0
到现在为止你只需要在自定义view的onSizeChanged回调方法中调用init()即可看到动画的弹动
@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
super.onSizeChanged(w,h,oldw,oldh);
init();
}
此方法会在onmesure方法执行完成后回调这样你就可以在此方法获得自定义view的宽高了
效果图:
画文字成u形
首先你得知道如下知识
贝塞尔曲线
这里我们用到2此贝塞尔曲线
我们看看大概是什么叫2次贝塞尔曲线
我们看看三个点p0p1p2我们把p0称为开始点p1为控制点p2结束点,那么可以用贝塞尔公式画出如图曲线
这里写图片描述
这里大家没必要深究怎么画出来.也不需要你懂这个要数学基础的
那么我们在安卓中怎么画呢?
Pathpath=newPath();
//p0的xy坐标
path.moveTo(p0.x,y);
path.rQuadTo(p1.x,p1.y,p2.x,p2.y);
这就是API调用方法是不是很简单?那么你又会问那么怎么画出来呢?
很简单在dispatchDraw方法或者onDraw中调用
@Override
protectedvoiddispatchDraw(Canvascanvas){
super.dispatchDraw(canvas);
canvas.drawPath(path,paint);
}
那么你画出来的效果应该和在Ps用钢笔画出来的差不多ps中钢笔工具就是二次贝塞尔曲线
(借用下图片)
如果你的三个点的位置如刚开的图片p0p1p2(p1在p0右上方并且p1在p2左上方)一样那么在屏幕中的显示效果如下
这里随扩张下dispatchDraw和ondraw的区别
如果你的自定义view是继承view那么会先调用ondraw->>dispatchDraw
如果你的自定义view是继承viewgroup那么会跳过ondraw方法直接调用dispathchDraw这里特别注意!!我们这个案例中继承的是FrameLayout,
而frameLayout又是继承自viewgroup所以….
那么我们回到主题如何画一个U形文字?简单就是说按照我们画出来的曲线在上面写字如:文字是”CSDN开源中国”如何让这几个字贴着我们的曲线写出来?
这里介绍一个API
canvas.drawTextOnPath()
第一个参数:文字类型为字符串
第二个参数:路径也就是我们前面的二次贝塞尔曲线
第三个参数:沿着路径文字开始的位置说白了偏移量
第四个参数:贴着路径的高度的偏移量
hOffset:
Thedistancealongthepathtoaddtothetext’sstartingposition
vOffset:
Thedistanceabove(-)orbelow(+)thepathtopositionthetext
//ok我们看看他可以画出什么样子的文字
这种看大家对贝塞尔曲线的理解,你理解的越深那么你可以画出的图像越多,当然不一定要用贝塞尔曲线
确定贝塞尔曲线的起点
我们在回过头来看看我们的效果图
我们可以看到文字应该是在iv(弹跳的图片中央位置且正好在iv弹到底部的位置)
这里我们先补充知识在考虑计算
我们来学习一下文字的测量我们来看幅图
我们调用画文字的API时
canvas.drawTextOnPath或者canvas.drawText是从基准线开始画的也就是说途中的baseline开始画.
如:
canvas.drawText(“FMY”,0,0,paint);
那么你将看不到文字只能在屏幕看到文字底部如下图:
另一个同理APIdrawTextOnPath也是
再看看几个简单的API
1.paint.measureText(“FMY”);返回在此画笔paint下写FMY文字的宽度
下面的API会把文字的距离左边left上边top右边right底部的bottom的值写入此矩形那么
rect.right-rect.left=文字宽度
rect.bottom-rect.top=文字高度
//矩形
Rectrect=newRect();
//将文字画入矩形目的是为了测量高度
paint.getTextBounds(printText,0,printText.length(),rect);
那么请看:
privatevoidinit(){
//初始化弹跳图片控件
iv=newImageView(getContext());
ViewGroup.LayoutParamsparams=newViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
iv.setLayoutParams(params);
iv.setImageResource(allImgDown[0]);
this.addView(iv);
//画笔的初始化
paint=newPaint();
paint.setStrokeWidth(1);
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(50);
paint.setAntiAlias(true);
//矩形
Rectrect=newRect();
//将文字画入矩形目的是为了测量高度
paint.getTextBounds(printText,0,printText.length(),rect);
//文本宽度
textWidth=paint.measureText(printText);
//获得文字高度
textHeight=rect.bottom-rect.top;
//初始化路径
path=newPath();
iv.setX(getWidth()/2);
iv.measure(0,0);
iv.getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLaywww.shanxiwang.netoutListener(){
@Override
publicvoidonGlobalLayout(){
if(!flagMeure)
{
flagMeure=true;
//由于画文字是由基准线开始
path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2,textHeight+iv.getHeight()+downHeightiv.getHeight());
//计算最大弹力
maxElasticFactor=(float)(textHeight/elastic);
//初始化贝塞尔曲线
path.rQuadTo(textWidth/2,0,textWidth,0);
//初始化上弹和下沉动画
MyAnmation();
iv.startAnimation(translateDown);
}
}
});
}
我们现在写一个类当iv图片(弹跳图)碰到文字顶部时设置一个监听器时间正好是弹图向上到顶部的时间期间不断让文字凹陷在恢复正常
//用于播放文字下沉和上浮动画传入的数值必须是图片下沉和上升的一次时间
publicvoidinitAnimation(intduration){
//这里为什maxElasticFactor/4好看...另一个同理这个数值大家自行调整
ValueAnimatoranimator=ValueAnimator.ofFloat(maxElasticFactor/4,(float)(maxElasticFactor/1.5),0);
animator.setDuration(duration/2);
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
calc();//重新画路径
nowElasticFactor=(float)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
再来一个重新绘画路径计算的方法
publicvoidcalc(){
//重置路径
path.reset();
//由于画文字是由基准线开始
path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2,textHeight+iv.getHeight()+downHeightiv.getHeight());
//画二次贝塞尔曲线
path.rQuadTo(textWidth/2,nowElasticFactor,textWidth,0);
}
好了到这里我们看看完整源码吧:
packagecom.example.administrator.myapplication;
importandroid.animation.ObjectAnimator;
importandroid.animation.ValueAnimator;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.Paint;
importandroid.graphics.Path;
importandroid.graphics.Rect;
importandroid.util.AttributeSet;
importandroid.view.ViewGroup;
importandroid.view.ViewTreeObserver;
importandroid.view.animation.AccelerateInterpolator;
importandroid.view.animation.Animation;
importandroid.view.animation.DecelerateInterpolator;
importandroid.view.animation.TranslateAnimation;
importandroid.widget.FrameLayout;
importandroid.widget.ImageView;
publicclassMyextendsFrameLayout{
//画笔
privatePaintpaint;
//路径
privatePathpath;
//要输入的文本
privateStringprintText="正在加载";
//文本宽
privatefloattextWidth;
//文本高
privatefloattextHeight;
//测量文字宽高的时候使用的矩形
privateRectrect;
//最大弹力系数
privatefloatelastic=1.5f;
//最大弹力
privatefloatmaxElasticFactor;
//当前弹力
privatefloatnowElasticFactor;
//用于记录当前图片使用数组中的哪张
intindexImgFlag=0;
//下沉图片
intallImgDown[]={R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};
//动画效果一次下沉或上弹的时间animationDuration2=一次完整动画时间
intanimationDuration=1000;
//弹起来的图片
ImageViewiv;
//图片下沉高度(即从最高点到最低点的距离)
intdownHeight=2;
privateAnimationtranslateDown;
privateAnimationtranslateUp;
privateObjectAnimatorrotation;
publicMy(Contextcontext){
super(context);
}
publicMy(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
@Override
protectedvoiddispatchDraw(Canvascanvas){
super.dispatchDraw(canvas);
canvas.drawTextOnPath(printText,path,0,0,paint);
}
//用于播放文字下沉和上浮动画传入的数值必须是图片下沉和上升的一次时间
publicvoidinitAnimation(intduration){
//这里为什maxElasticFactor/4为什么
ValueAnimatoranimator=ValueAnimator.ofFloat(maxElasticFactor/4,(float)(maxElasticFactor/1.5),0);
animator.setDuration(duration/2);
animator.addUpdateListener(newValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
calc();
nowElasticFactor=(float)animation.getAnimatedValue();
postInvalidate();
}
});
animator.start();
}
publicvoidcalc(){
//重置路径
path.reset();
//由于画文字是由基准线开始
path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2,textHeight+iv.getHeight()+downHeightiv.getHeight());
//画二次贝塞尔曲线
path.rQuadTo(textWidth/2,nowElasticFactor,textWidth,0);
}
//初始化弹跳动画
publicvoidMyAnmation(){
//下沉效果动画
translateDown=newTranslateAnimation(
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);
translateDown.setDuration(animationDuration);
//设置一个插值器动画将会播放越来越快模拟重力
translateDown.setInterpolator(newAccelerateInterpolator());
//上弹动画
translateUp=newTranslateAnimation(
Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,
Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0
);
translateUp.setDuration(animationDuration);
////设置一个插值器动画将会播放越来越慢模拟反重力
translateUp.setInterpolator(newDecelerateInterpolator());
//当下沉动画完成时播放启动上弹
translateDown.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationStart(Animationanimation){
iv.setImageResource(allImgDown[indexImgFlag]);
rotation=ObjectAnimator.ofFloat(iv,"rotation",180f,360f);
rotation.setDuration(1000);
rotation.start();
}
@Override
publicvoidonAnimationEnd(Animationanimation){
iv.startAnimation(translateUp);
initAnimation(animationDuration);
}
@Override
publicvoidonAnimationRepeat(Animationanimation){
}
});
//当上移动画完成时播放下移动画
translateUp.setAnimationListener(newAnimation.AnimationListener(){
@Override
publicvoidonAnimationStart(Animationanimation){
indexImgFlag=1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;
iv.setImageResource(allImgDown[indexImgFlag]);
rotation=ObjectAnimator.ofFloat(iv,"rotation",0.0f,180f);
rotation.setDuration(1000);
rotation.start();
}
@Override
publicvoidonAnimationEnd(Animationanimation){
//递归
iv.startAnimation(translateDown);
}
@Override
publicvoidonAnimationRepeat(Animationanimation){
}
});
}
booleanflagMeure;
@Override
protectedvoidonSizeChanged(intw,inth,intoldw,intoldh){
super.onSizeChanged(w,h,oldw,oldh);
init();
}
privatevoidinit(){
//初始化弹跳图片控件
iv=newImageView(getContext());
ViewGroup.LayoutParamsparams=newViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
iv.setLayoutParams(params);
iv.setImageResource(allImgDown[0]);
this.addView(iv);
//画笔的初始化
paint=newPaint();
paint.setStrokeWidth(1);
paint.setColor(Color.CYAN);
paint.setStyle(Paint.Style.FILL);
paint.setTextSize(50);
paint.setAntiAlias(true);
//矩形
rect=newRect();
//将文字画入矩形目的是为了测量高度
paint.getTextBounds(printText,0,printText.length(),rect);
//文本宽度
textWidth=paint.measureText(printText);
//获得文字高度
textHeight=rect.bottom-rect.top;
//初始化路径
path=newPath();
iv.setX(getWidth()/2);
iv.measure(0,0);
iv.getViewTreeObserver().addOnGlobalLayoutListener(newViewTreeObserver.OnGlobalLayoutListener(){
@Override
publicvoidonGlobalLayout(){
if(!flagMeure)
{
flagMeure=true;
//由于画文字是由基准线开始
path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2,textHeight+iv.getHeight()+downHeightiv.getHeight());
//计算最大弹力
maxElasticFactor=(float)(textHeight/elastic);
//初始化贝塞尔曲线
path.rQuadTo(textWidth/2,0,textWidth,0);
//初始化上弹和下沉动画
MyAnmation();
iv.startAnimation(translateDown);
}
}
});
}
}
|
|