自定义ViewGroup动画

2017-07-24  本文已影响0人  隐修丶

今天要写的是一个发散效果的动画, 练习一下自定义viewgroup,先上效果图:

Step1:

依旧是先分析一下,直接开写通常容易翻车。需求是若干View以均匀的角度从中心的View散发出来,动画效果无非就是平移动画,正常情况我们如果直接去写XML布局来做这个效果是很麻烦的,这些View的位置不容易确定,所以自定义一个ViewGroup,在ViewGroup里面摆放子View。先不去考虑动画,我们直接把这些View都按散发后的效果摆放起来。那么先看看view的layout的方法:public void layout(int l, int t, int r, int b) {..},需要传四个参数分别是上下左右的位置,为了方便计算,在这里将每个view看成一个内切圆,先上个草图大概看看(与真正的计算无关)

这里L是我们自己定义的一个长度即中心的view离散发出去View的距离,r为中心 view的半径,散发的角度是平分360度的,那么三角函数来了(这个自行解决)。可以算出距离中心的X和Y长度,从而确定view的位置。

Step2:

首先自定义一个ViewGroup,添加子view(部分代码):

再来看看自定义viewgroup,需要重写onMeasure()和OnLayout()方法,viewgroup默认不回去测量子控件,我们计算式需要知道子控件的大小,所以在onMeaser()测量子控件大小。onLayout()摆放子控件位置,因为是360度发散,摆放计算时需要注意以中心view的圆心为原点,区分出四个象限(每个象限的view情况不一样),方便计算(计算比较麻烦,耐心点,为了效果好点这个length我给了个随机值),如下草图

主要代码如下:

private int mwidth; //viewgroup宽

private int mheight; //viewgroup高

private int radius; //中心view的半径

private int width0; //中心view的宽

private int height0; //中心view的高

@Override

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

super.onMeasure(widthMeasureSpec,heightMeasureSpec);

if(getChildCount() >0) {

for(inti =0;i < getChildCount();i++) { //测量子控件

View child = getChildAt(i);

child.measure(0,0);

}

}

}

@Override

protected voidonSizeChanged(intw, inth, intoldw, intoldh) { //获取viewgroup宽高

super.onSizeChanged(w,h,oldw,oldh);

mwidth= w;

mheight= h;

}

@Override

protected void onLayout(booleanchanged, intl, intt, intr, intb) {

layoutChild0();//第一个view作为中心view摆放

double a =  (Math.toRadians(360) / (getChildCount() -1));//计算平均夹角大小

for(inti =0;i < getChildCount() -1;i++) {//摆放除中心view的其他子控件

double child_a=a*i;//每个view对应的夹角

int length =newRandom().nextInt(200) +200;//随机生成200-400的数

//child.setVisibility(INVISIBLE);//为了动画效果

View child = getChildAt(i +1);

int child_width = child.getMeasuredWidth();//子view的宽高

int child_height = child.getMeasuredHeight();

//区分出四个象限的child,它们的摆放计算方式不同

if(child_a==0){//0度和180度特殊处理一下

child.layout(mwidth/2-child_width/2,mheight/2-child_height-length,mwidth/2+child_width/2,mheight/2-length);

}else if(child_a==Math.toRadians(180)){

child.layout(mwidth/2-child_width/2,mheight/2+length,mwidth/2+child_width/2,mheight/2+child_height+length);

}else if(child_a>0&& child_a<= Math.toRadians(90)) {//第一象限

intx = (int) ((length +radius) * Math.sin(child_a));

inty = (int) ((length +radius) * Math.cos(child_a));

child.layout(mwidth/2+ x,mheight/2-y - child_height /2,mwidth/2+ x + child_width,mheight/2-y + child_height /2);

}else if(child_a > Math.toRadians(90) && child_a < Math.toRadians(180)) {//第二象限

intx = (int) ((length +radius) * Math.sin(Math.toRadians(180)-child_a));

inty = (int) ((length +radius) * Math.cos(Math.toRadians(180)-child_a));

child.layout(mwidth/2+ x,mheight/2+ y - child_height /2,mwidth/2+ x + child_width,mheight/2+ y + child_height /2);

}else if(child_a> Math.toRadians(180) && a*i <=Math.toRadians(270)) {//第三

intx = (int) ((length +radius) * Math.cos(Math.toRadians(270)-child_a));

inty = (int) ((length +radius) * Math.sin(Math.toRadians(270)-child_a));

child.layout(mwidth/2- x - child_width,mheight/2+ y - child_height /2,mwidth/2- x,mheight/2+ y + child_height /2);

}else{//第四

inty = (int) ((length +radius) * Math.cos(Math.toRadians(360)-child_a));

intx = (int) ((length +radius) * Math.sin(Math.toRadians(360)-child_a));

child.layout(mwidth/2- x - child_width,mheight/2- y - child_height /2,mwidth/2- x,mheight/2- y + child_height /2);

}

}

}

//最中间的child摆放

private void layoutChild0() {

View child0 = getChildAt(0);

child0.setOnClickListener(this);

width0= child0.getMeasuredWidth();

height0= child0.getMeasuredHeight();

radius= Math.max(width0,height0) /2;

child0.layout(mwidth/2-width0/2,mheight/2-height0/2,mwidth/2+width0/2,mheight/2+height0/2);

}


经过一系列计算将view摆放好了,其实这个自定义控件已经完成了一大半,主要是摆放复杂点,效果如下图

ok,剩下的就是动画效果了,这里用的是TranslateAnimation(int fromXType, float fromXValue, intto XType, float toXValue,int fromYType, float fromYValue, int toYType, float toYValue),需要的几个参数也很容易明白,关于这个type有三种ABSOLUTE(将自身作0点,往左则减往下则加),RELATIVE_TO_SELF(相对于自己),RELATIVE_TO_PARENT(相对于父容器)。不太了解具体含义也没关系,可以自己去试。分析动画效果,只需要从中心点到我们给它摆放好的位置即可,所以这里用ABSOLUTE代码如下:

private void childAnimation() {//这段代码我是在child0的onClick()里面调用的,此处忽略

for(int i=0;i<getChildCount-1;i++){

View child=getChildAt(i+1);

child.setVisibility(VISIBLE);//在

TranslateAnimation ta=newTranslateAnimation(Animation.ABSOLUTE,-child.getLeft() +mwidth/2-width0/2,Animation.ABSOLUTE,0,Animation.ABSOLUTE,-child.getTop() +mheight/2-height0/2,Animation.ABSOLUTE,0);//从中心点到自己的位置(0)

ta.setDuration(1200);

ta.setStartOffset(200*i);//设置开始偏移时间,每个view平移时间有一定间隔

ta.setFillAfter(true);

ta.setInterpolator(newOvershootInterpolator());

child.startAnimation(ta);

}

}


TheEnd

除了计算麻烦一点,其它的很容易,主要是要理解view和viewgroup的流程,安利一下郭霖大神的View绘制流程http://blog.csdn.net/guolin_blog/article/details/17045157。重要的事情说三遍:拿到效果图一定要一步步分析,耐心点!上一篇留下了一个invalidate()的问题,看完郭霖大神对于invalidate()的分析然后自己找找源码一步步走,你就明白了,invalidate()最终调用了performTraversals()performTraversals方法就是整个View树开始绘制的起始节点,所以,View调用invalidate方法的实质是:层层上传到父级,直到传递到ViewRootImpl后会触发scheduleTraversals方法,然后整个View树就开始重新按照View的绘制流程进行重绘任务。

上一篇下一篇

猜你喜欢

热点阅读