美文网首页
自定义ViewGroup动画

自定义ViewGroup动画

作者: 隐修丶 | 来源:发表于2017-07-24 15:30 被阅读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的绘制流程进行重绘任务。

    相关文章

      网友评论

          本文标题:自定义ViewGroup动画

          本文链接:https://www.haomeiwen.com/subject/jntbkxtx.html