
Android简单实现⾃定义的动画效果
我⾃⼰公司的业务需求上是基本不会涉及动画相关的,也可以说基本两年没怎么做过动画相关的开发,这次写这篇⽂章是因为读到⼀篇⽐较好的⽂
章,想把它记录下来,以后有做动画效果的需求的话就能快速上⼿。
参考《Android开发艺术探索》7.3.4对任意属性做动画
众所周知Android⾥⾯动画有3种,View动画、帧动画和属性动画。不同的场景使⽤不同的动画,对控件⽽⾔,你想实现⼀个⾃定义动画的效果,
也就是随⼼,那就使⽤属性动画会⽐较好,因为View动画只提供了简单的平移、翻转、缩放、透明度。
当然这⾥也不会去介绍⼀些基础的内容。为什么单独说这⼀⼩节,因为我觉得这个地⽅是这章(第七章)最经典的地⽅。
⼀.对任意属性做动画
按照书上的逻辑来讲。
⼀开始举了⼀个栗⼦,给Button加个动画,让这个Button的宽度增加500px。
直接写属性动画的代码:
(mButton,"width",500).tDuration(5000).start();
这⾥是对这个Button的width属性做动画,也就是对宽度这个属性做动画,但是并不能实现我们想要的宽度增加500px的效果。这时候就先引出
了⼀个重要的结论:
属性动画要求动画作⽤的对象提供该属性的get和t⽅法
这个很重要,意思就是说,这个mButton对象,没有提供getWidth和tWidth⽅法。其实这说法有点那啥,因为Button是有getWidth和
tWidth的⽅法,但是为什么没⽣效呢?
这是展⽰了tWidth的源码
publicvoidtWidth(intpixels){
mMaxWidth=mMinWidth=pixels;
mMaxWidthMode=mMinWidthMode=PIXELS;
requestLayout();
invalidate();
}
发现这⾥并不是设置Button的宽度,⽽是设置Button的最⼤宽度和最⼩宽度。
也就是说在上⾯的动画中:真的的是随着时间的改变去改变控件的最⼤最⼩宽度,所以视觉看不出效果,因为实际作⽤的属性不是你想要去让它作
⽤的属性
所以想要实现效果,应该要提供该属性正确的get/t⽅法
书上⼜写出了这样的⼀个结论(原话):
针对上诉问题,官⽅⽂档上告诉我们有3种解决⽅法:
给你的对象加上get和t⽅法,如果你有权限的话
⽤⼀个类来包装原始对象,间接为其提供get和t⽅法
采⽤ValueAnimator,监听动画过程,⾃⼰实现属性的变化
第⼀个⽅案肯定不可⾏,系统的View不是我们说改就改的,然后⽤两个Demo分别来描述⽅法2和⽅法3(不能复制要⼿写,好不想写)
⽅法2:
privatestaticclassViewWrapper{
privateViewmTarget;
publicViewWrapper(Viewtarget){
mTarget=target;
}
publicintgetWidth(){
outParams().width;
}
publicvoidtWidth(intwidth){
outParams().width=width;
tLayout();
}
}
调⽤的时候
ViewWrapperwrapper=newViewWrapper(mButton);
(wrapper,"width",500).tDuration(5000).start();
这样就能正常展⽰我们想要的动画效果,对象是wrapper,它确实为width属性提供了get/t⽅法。动画内部在执⾏过程中会拿到传给它的属
性,然后⽤反射去调⽤它的get/st⽅法。但是怎么说呢,具体的属性改变的算法还是要你去⼿动写。
⽅法3:
ValueAnimatorvalueAnimator=(1,100);
ateListener(orUpdateListener(){
privateIntEvaluatormEvaluator=newIntEvaluator();
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
intcurrentValue=(Integer)matedValue();
floatfraction=matedFraction();
outParams().width=te(fraction,start,end);
tLayout();
}
});
这个⽅法就是对动画进⾏监听,每监听到⼀帧的时候再做具体的操作。可以看到这⾥也没写get/t⽅法,⼤概就那么⼀个意思就得了,也不是⾮
得说⼀定要这样写,或者不⽤这两种⽅法也还有其它⽅法能实现。
总之,属性的具体的变化,是需要我们⾃⼰去写逻辑(如果要实现⾃定义的效果),⽽ObjectAnimator、ValueAnimator这些属性⼴告的类,
能告诉我们这个动画执⾏的整个过程,简单的说就是我们知道播放到哪⼀帧,就能做到在这⼀帧做什么效果。
这两个Demo也是我觉得这章最有意思的地⽅。
⼆.⾃⼰实现⼀个⾃定义动画
光看肯定是3天忘,⾃⼰动⼿写个简单的⾃定义动画效果。
我弄⼀个圆形View,可以拖动它,点击下去的时候圆会放⼤并变成正⽅形,然后移动,松开时正⽅形会缩⼩并变回圆,⽽且我们要让圆变成正⽅
形不是秒变,要有个渐变的效果(只实现了简单的渐变效果,因为懒得去计算)。
最终的效果也没有做成gif,想看效果的直接复制代码去试就⾏,不多。
1.绘制view的初始位置
我这Demo再补充⼀些细节的东西,还是《Android开发艺术探索》3.1.2View的位置参数
不是什么难点,就是有些细节要注意,这个view的位置参数列举了3套
(1)Left,Right,Top,Bottom,当成⼀个矩形看(圆也是矩形),就是4条边距离⽗容器xy轴的距离。
(2)x,y左上点的坐标
(3)translationX,translationY距离⽗View坐标的偏移量
我就直接贴代码讲吧。
先看Activity布局
xmlns:android="/apk/res/android"
android:id="@+id/fl_content"
android:padding="100px"
android:layout_width="match_parent"
android:layout_height="match_parent">
这⾥padding⼀个100px,是为了⽅便介绍这些⽅位,单位相同容易看,实际开发中肯定最好⽤dp做单位。
然后看Activity代码
publicclassRsActivityextendsActivity{
privateFrameLayoutflContent;
privateRoundSquareViewroundSquareView;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
te(savedInstanceState);
tContentView(ty_rs);
initView();
}
privatevoidinitView(){
flContent=findViewById(_content);
roundSquareView=newRoundSquareView(this);
Paramslp=Params(150,150);
outParams(lp);
w(roundSquareView);
(100);
(100);
}
@Override
protectedvoidonResume(){
me();
Handlerhandler=newHandler();
layed(newRunnable(){
@Override
publicvoidrun(){
Log.v("mmp","getLeft"+t());
Log.v("mmp","getTranslationX"+nslationX());
Log.v("mmp","getX"+());
(0);
}
},1000);
}
}
RoundSquareView是⼀个⾃定义View,先不⽤管它。
第⼀个问题来了,为什么要写个延迟,重点啊,因为getLeft,就是获取第⼀套的那4个属性,要在view绘制完成之后才能获取到,不然获取到的
会是0,加个延迟是为了保证绘制完成(这只是Demo,真实开发中判断绘制完成肯定不能⽤延时这样玩)
看看显⽰结果
再看看打印结果
距离顶部和左边都是200px,但是left打印是100,如果把⽗布局中的padding去掉的话,left会打印0,这就是第⼆个地⽅,使⽤Left的时候要注
意padding,并且它不算translationX偏移的部分。
然后translationX也打印的是100,他也不算是padding的部分。
最后getX是200,他是正常的相对于⽗布局的部分距离,不受任何影响。
也证实了x=left+translationX
这⾥只是为了介绍⼀些细节上的东西⽽已,onResume⾥⾯的代码对我们要实现的功能没有任何意义,所以使⽤时请屏蔽掉。
2.⾃定义View
看看代码
publicclassRoundSquareViewextendsView{
privatefloatoldRawX=0;
privatefloatoldRawY=0;
privateintcurrentValue=0;
privatebooleanisExpand;
publicRoundSquareView(Contextcontext){
super(context);
}
publicRoundSquareView(Contextcontext,AttributeSetattrs){
super(context,attrs);
}
publicRoundSquareView(Contextcontext,AttributeSetattrs,intdefStyleAttr){
super(context,attrs,defStyleAttr);
}
@Override
publicvoiddraw(Canvascanvas){
(canvas);
Paintpaint=newPaint();
or();
le();
okeWidth(1);
intw=getWidth()/2;
inth=getHeight()/2;
if(!isExpand){
if(!isExpand){
//⾮扩⼤状态下的绘制
rcle(w,h,w+currentValue,paint);
}el{
//扩⼤状态下的绘制
rcle(w,h,w+(100-currentValue),paint);
}
}
@Override
publicbooleanonTouchEvent(MotionEventevent){
switch(ion()){
_DOWN:
isExpand=fal;
expand();
oldRawX=X();
oldRawY=Y();
returntrue;
_MOVE:
moveView(X(),Y());
returntrue;
_UP:
oldRawX=0;
oldRawX=0;
isExpand=true;
narrow();
returntrue;
}
hEvent(event);
}
@Override
publicbooleanperformClick(){
mClick();
}
privatevoidmoveView(floatrawX,floatrawY){
//计算偏移量
floatofftX=rawX-oldRawX;
floatofftY=rawY-oldRawY;
//更改位置
tX(getX()+offtX);
tY(getY()+offtY);
//更新位置
oldRawX=rawX;
oldRawY=rawY;
}
/**
*扩⼤
*/
privatevoidexpand(){
intw=getWidth();
inth=getHeight();
Paramslp=outParams();
ValueAnimatorvalueAnimator=(1,100);
ateListener(orUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
currentValue=(int)matedValue();
Log.v("mmp","动画进度"+currentValue);
=w+(w/100*currentValue);
=h+(h/100*currentValue);
outParams(lp);
}
});
ation(300).start();
}
/**
*缩⼩
*/
privatevoidnarrow(){
intw=getWidth();
inth=getHeight();
Paramslp=outParams();
ValueAnimatorvalueAnimator=(1,100);
ateListener(orUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
currentValue=(int)matedValue();
Log.v("mmp","动画进度"+currentValue);
=w-(w/200*currentValue);
=h-(h/200*currentValue);
outParams(lp);
}
});
ation(300).start();
}
}
⾃定义View,在draw中⽤Paint画个圆,这个,这个没什么好说的,都能看懂,最后
if(!isExpand){
//⾮扩⼤状态下的绘制
rcle(w,h,w+currentValue,paint);
}el{
//扩⼤状态下的绘制
rcle(w,h,w+(100-currentValue),paint);
}
先不⽤管,先看成
rcle(w,h,w,paint);
这样就画成⼀个圆了。
然后写onTouchEvent让view随⼿指移动⽽移动。记录改变前的⼿指的点击位置oldRawX,oldRawY,去获取当前的位置减去旧的位置就能得到
⼀个偏移量,然后再⽤这个偏移量去改变x和y属性,这是最简单的view随⼿指移动的⽅式。
然后在按下时调⽤expand()做放⼤的属性动画,再抬起时调⽤narrow()做缩⼩的属性动画。
这⾥是⽤了上⾯的⽅法3去实现属性动画,这⾥离就不多说了,定义了⼀个变量isExpand来记录当前是否处于放⼤的状态。
最后,这个Demo只是简单的实现动画的效果,会存在⼀些问题,⽐如说在放⼤动画的执⾏过程中抬起,这些都没做处理,时间问题,就不打算花
太多时间在这个Demo上。
本文发布于:2023-03-02 05:42:24,感谢您对本站的认可!
本文链接:https://www.wtabcd.cn/zhishi/a/16777069445623.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文word下载地址:自定义动画.doc
本文 PDF 下载地址:自定义动画.pdf
| 留言与评论(共有 0 条评论) |