先上效果图

    

 

 

 

洒豆子的效果,突发奇想,觉得这个动画挺有意思的,就抽空写了一个玩玩

绘制流程:

  定义6个‘’豆子‘’,每个豆子有各自的属性,大小,抛出的速度等,然后控制每个的方向和状态,回弹效果使用差值器 BounceInterpolator

package com.fragmentapp.view.beans;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.BounceInterpolator;

import com.fragmentapp.R;
import com.fragmentapp.helper.RandomUtil;

/**
 * Created by liuzhen on 2017/1/17.
 */

public class BeansView extends View {

    private Paint paint;
    private int mWidth;
    private int mHeight;
    private int top;

    private ValueAnimator va;

    private Beans beans1,beans2,beans3,beans4,beans5,beans6;

    public BeansView(Context context) {
        this(context, null);
    }

    public BeansView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BeansView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        Log.e("tag","init");
        setWillNotDraw(false);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.color_ff9c19));
        //随机生成球体的大小、
        beans1 = new Beans(RandomUtil.random(5,15));
        beans2 = new Beans(RandomUtil.random(5,15));
        beans3 = new Beans(RandomUtil.random(5,15));
        beans4 = new Beans(RandomUtil.random(5,15));
        beans5 = new Beans(RandomUtil.random(5,15));
        beans6 = new Beans(RandomUtil.random(5,15));
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();
            this.top = top;
            startAnim();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //正常向右掉落,这里也可以利用随机生成方向,这里就固定左边三个右边三个
        canvas.drawCircle(beans1.getCx(), beans1.getCy(), beans1.getRadius(), paint);
        canvas.drawCircle(beans2.getCx(), beans2.getCy(), beans2.getRadius(), paint);
        canvas.drawCircle(beans3.getCx(), beans3.getCy(), beans3.getRadius(), paint);
        //让球往左边掉落
        canvas.drawCircle(-beans4.getCx()+mWidth, beans4.getCy(), beans4.getRadius(), paint);
        canvas.drawCircle(-beans5.getCx()+mWidth, beans5.getCy(), beans5.getRadius(), paint);
        canvas.drawCircle(-beans6.getCx()+mWidth, beans6.getCy(), beans6.getRadius(), paint);

    }

    public void startAnim() {
        if (mWidth == 0) return;

        beans1.setState(0);
        beans1.setOff(0);
        beans1.setRand(RandomUtil.random(20));//随机生成抛出的速度值

        beans2.setState(0);
        beans2.setOff(0);
        beans2.setRand(RandomUtil.random(20));

        beans3.setState(0);
        beans3.setOff(0);
        beans3.setRand(RandomUtil.random(20));

        beans4.setState(0);
        beans4.setOff(0);
        beans4.setRand(RandomUtil.random(20));

        beans5.setState(0);
        beans5.setOff(0);
        beans5.setRand(RandomUtil.random(20));

        beans6.setState(0);
        beans6.setOff(0);
        beans6.setRand(RandomUtil.random(20));

        va = ValueAnimator.ofFloat(top, mHeight - top);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                float val = (float)animation.getAnimatedValue();

                beans1.setCy(val);
                beans1.move(mWidth);//先移动坐标,实际上是改变了off偏移量的值
                beans1.setCx(mWidth / 2 + beans1.getOff());//刷新X轴坐标

                beans2.setCy(val);
                beans2.move(mWidth);
                beans2.setCx(mWidth / 2 + beans2.getOff());

                beans3.setCy(val);
                beans3.move(mWidth);
                beans3.setCx(mWidth / 2 + beans3.getOff());

                beans4.setCy(val);
                beans4.move(mWidth);
                beans4.setCx(mWidth / 2 + beans4.getOff());

                beans5.setCy(val);
                beans5.move(mWidth);
                beans5.setCx(mWidth / 2 + beans5.getOff());

                beans6.setCy(val);
                beans6.move(mWidth);
                beans6.setCx(mWidth / 2 + beans6.getOff());

                invalidate();
            }
        });
        va.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animator) {

            }

            @Override
            public void onAnimationEnd(Animator animator) {
                //防止停止后球体因为半径的不一样而降落到地面的水平不一样,统一水平线
                beans1.setCy(mHeight - beans1.getRadius());

                beans2.setCy(mHeight - beans2.getRadius());

                beans3.setCy(mHeight - beans3.getRadius());

                beans4.setCy(mHeight - beans4.getRadius());

                beans5.setCy(mHeight - beans5.getRadius());

                beans6.setCy(mHeight - beans6.getRadius());

                invalidate();
            }

            @Override
            public void onAnimationCancel(Animator animator) {

            }

            @Override
            public void onAnimationRepeat(Animator animator) {

            }
        });
        va.setInterpolator(new BounceInterpolator());//重力差值器
        va.setDuration(3000);
        va.setRepeatMode(ValueAnimator.RESTART);
        va.start();
    }

    public void stopAnim() {
        va.cancel();
        va = null;
    }

}
View Code

这里主要把逻辑封装到单独的对象里面去了,所以view类看起来很清爽

下面是豆子类

package com.fragmentapp.view.beans;

import android.util.Log;

/**
 * Created by liuzhen on 2018/1/18.
 */

public class Beans {

    public Beans(){ }

    public Beans(int radius){
        this.radius = radius;
    }

    /**X坐标*/
    private float cx;
    /**Y坐标*/
    private float cy;
    /**偏移量*/
    private float off;
    /**随机生成的速度值*/
    private float rand;
    /**是否碰到边缘*/
    private int state;
    /**圆球的大小*/
    private float radius;

    /**移动 X 坐标,并且碰到边界后回弹*/
    public void move(int width){
        if (cx < 0 || state == 1) {//碰到左边的边缘
            state = 1;
            off += rand;
        } else if (cx >= width || state == 2) {//碰到右边的边缘
            state = 2;
            off -= rand;
        }else if(state == 0) {
            state = 0;
            off += rand;
        }
//        Log.e("tag","-- cx "+(int)cx  + " width "+width + " state "+state);
    }

    public float getCx() {
        return cx;
    }

    public void setCx(float cx) {
        this.cx = cx;
    }

    public float getOff() {
        return off;
    }

    public void setOff(float off) {
        this.off = off;
    }

    public float getRand() {
        return rand;
    }

    public void setRand(float rand) {
        this.rand = rand;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public float getCy() {
        return cy;
    }

    public void setCy(float cy) {
        this.cy = cy;
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }
}
View Code

主要逻辑集中在move方法中

默认是正常抛出,然后碰到边缘后改变状态往回弹

使用上只关注两个方法就行了

 

 这里是把控件放在了一个dialog里面,这个看个人喜欢,显然dialog不是很适合,或者可以加到下拉库的头部上去,效果应该不错

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/shape_dialog_bg"
    android:padding="@dimen/d20.0"
    android:orientation="vertical"
    android:id="@+id/root">

    <com.fragmentapp.view.beans.BeansView
        android:id="@+id/beans"
        android:layout_width="@dimen/d350.0"
        android:layout_height="@dimen/d300.0"
        android:layout_gravity="center_horizontal" />

    <!--<View-->
        <!--android:layout_width="match_parent"-->
        <!--android:layout_height="@dimen/d1.0"-->
        <!--android:background="@color/white"/>-->

    <TextView
        android:id="@+id/tv_val"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="@dimen/d20.0"
        android:text="加载中..."
        android:textColor="@color/color_cccccc"
        android:textSize="@dimen/d43.0" />

</LinearLayout>
View Code

 

到这里基本完成了,不过这样的效果绘制显然不是很好看,而且很low,所以要优化一下绘制的图形,让它看起来更高大上一些,最优先需要改的肯定是圆球了,各种3D形状,很好看,不过没法绘制,只能网上找个图片直接drawbitmap了,大多的华丽都是跟图片搭配的

 然而就是代码了,代码看起来也有点low,也需要优化一下

 1:代码优化

  以前的是固定对象,然后绘制,肯定需要稍微动态一点了

2:圆球绘制

  以前的是直接绘制圆,不好看,从网上下载一个圆形 icon,代替圆,看起来更立体一点

这里的做法是把圆形对象也放进实体类里面去,方便统一获取,然后创建一个统一管理实体类的集合

 先获取到我们的icon,随机产生圆球的大小,添加进集合

接下来所有的固定的地方都换成for循环来代替

是不是方便多了,看起来简洁多了,可以对比两边的代码,你会发现,哎呦,不错哦

package com.fragmentapp.view.beans;

import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.BounceInterpolator;

import com.fragmentapp.R;
import com.fragmentapp.helper.RandomUtil;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by liuzhen on 2017/1/17.
 */

public class BeansView extends View {

    private Paint paint;
    private int mWidth;
    private int mHeight;
    private int top;

    private ValueAnimator va;

    private List<Beans> beans = null;

    public BeansView(Context context) {
        this(context, null);
    }

    public BeansView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public BeansView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        Log.e("tag","init");
        setWillNotDraw(false);
        paint = new Paint();
        paint.setAntiAlias(true);
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(getResources().getColor(R.color.color_ff9c19));

        beans = new ArrayList<>();

        Bitmap bitmap = ((BitmapDrawable)(getResources().getDrawable(R.mipmap.ball))).getBitmap();
        //随机生成球体的大小、
        for(int i = 0;i < 6; i ++){
            int radius = RandomUtil.random(15,30);
            final Beans b = new Beans(radius);
            b.setDirection(i % 2 == 0 ? Beans.Left : Beans.Right);
            b.bitmap = Bitmap.createScaledBitmap(bitmap,radius,radius,true);
            beans.add(b);
        }

    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            mWidth = getWidth();
            mHeight = getHeight();
            this.top = top;
            startAnim();
        }
    }

    @Override
    protected void onDraw(Canvas canvas) {

        for (Beans b : beans) {
            if (b.bitmap != null) {
                if (b.getDirection() == Beans.Left) {
                    canvas.drawBitmap(b.bitmap, b.getCx(), b.getCy(), null);
                } else {
                    canvas.drawBitmap(b.bitmap, -b.getCx() + mWidth, b.getCy(), null);
                }
            }
        }

    }

    public void startAnim() {
        if (mWidth == 0 || beans.size() == 0) return;
        for (Beans b : beans) {
            b.setState(0);
            b.setOff(0);
            b.setRand(RandomUtil.random(20));//随机生成抛出的速度值
        }

        va = ValueAnimator.ofFloat(top, mHeight);
        va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                float val = (float)animation.getAnimatedValue();

                for (Beans b : beans) {
                    b.setCy(val - b.getRadius());
                    b.move(mWidth);//先移动坐标,实际上是改变了off 偏移量的值
                    b.setCx(mWidth / 2 + b.getOff());//刷新X轴坐标
                }

                postInvalidate();
            }
        });
        va.setInterpolator(new BounceInterpolator());//重力差值器
        va.setDuration(3500);
        va.setRepeatMode(ValueAnimator.RESTART);
        va.start();
    }

    public void stopAnim() {
        va.cancel();
        va = null;
    }

}
View Code
package com.fragmentapp.view.beans;

import android.graphics.Bitmap;
import android.util.Log;

/**
 * Created by liuzhen on 2018/1/18.
 */

public class Beans {

    public Beans(){ }

    public Beans(int radius){
        this.radius = radius;
    }

    public static final int Left = 0;
    public static final int Right = 1;

    private int direction;
    /**X坐标*/
    private float cx;
    /**Y坐标*/
    private float cy;
    /**偏移量*/
    private float off;
    /**随机生成的速度值*/
    private float rand;
    /**是否碰到边缘*/
    private int state;
    /**圆球的大小*/
    private float radius;
    public Bitmap bitmap;

    /**移动 X 坐标,并且碰到边界后回弹*/
    public void move(int width){
        if (cx < 0 || state == 1) {//碰到左边的边缘
            state = 1;
            off += rand;
        } else if (cx >= width || state == 2) {//碰到右边的边缘
            state = 2;
            off -= rand;
        }else if(state == 0) {
            state = 0;
            off += rand;
        }
//        Log.e("tag","-- cx "+(int)cx  + " width "+width + " state "+state);
    }

    public float getCx() {
        return cx;
    }

    public void setCx(float cx) {
        this.cx = cx;
    }

    public float getOff() {
        return off;
    }

    public void setOff(float off) {
        this.off = off;
    }

    public float getRand() {
        return rand;
    }

    public void setRand(float rand) {
        this.rand = rand;
    }

    public int getState() {
        return state;
    }

    public void setState(int state) {
        this.state = state;
    }

    public float getCy() {
        return cy;
    }

    public void setCy(float cy) {
        this.cy = cy;
    }

    public float getRadius() {
        return radius;
    }

    public void setRadius(float radius) {
        this.radius = radius;
    }

    public int getDirection() {
        return direction;
    }

    public void setDirection(int direction) {
        this.direction = direction;
    }
}
View Code

 

接下来继续美化,可以在下面添加一个回弹的跳板样式,看起来效果更好,也同样是用二级贝塞尔来实现,先绘制出接触面

一条直线,然后物体挑落到底下的时候在触发接触面的控制点,达到回弹效果,但是怎么知道物体落到了地面吗,差值器我没找到什么好的办法去解决,所以自己先想了一个方法去判断到底了,但是感觉不太好,不过目前本人还没有更好的方法去实现

 就是通过自己自定义差值器,然后在差值器里面添加回调判断,代码如下

package com.fragmentapp.view;

import android.util.Log;
import android.view.animation.BounceInterpolator;
import android.view.animation.Interpolator;

/**
 * Created by liuzhen on 2018/2/2.
 */

public class MyBounceInterpolator implements Interpolator {

    private CallBack callBack;
    private boolean t_3 = false,t_7 = false,t_9 = false,t_1 = false;

    public MyBounceInterpolator(CallBack callBack){
        this.callBack = callBack;
    }

    private float bounce(float t) {
        return t * t * 8.0f;
    }

    @Override
    public float getInterpolation(float t) {
//        Log.e("tag",""+t);
        t *= 1.1226f;
        if (t < 0.3535f) {
//            Log.e("tag","----1");0.1 0.2 0.3
            if (t_3 == false){
                t_3 = true;
//                callBack.toLast();
            }
            return bounce(t);
        } else if (t < 0.7408f) {
//            Log.e("tag","----2");4=0.6 5=0.8 6=0.8
            if (t_7 == false){
                t_7 = true;
                callBack.toLast();
            }
            return bounce(t - 0.54719f) + 0.7f;
        } else if (t < 0.9644f) {
//            Log.e("tag","----3");7=0.8 8=0.9 9=1
            if (t_9 == false){
                t_9 = true;
                callBack.toLast();
            }
            return bounce(t - 0.8526f) + 0.9f;
        } else {
//            Log.e("tag","----4");
            if (t_1 == false){
                t_1 = true;
                callBack.toLast();
            }
            return bounce(t - 1.0435f) + 0.95f;
        }
    }

    public interface CallBack{
        void toLast();
    }

}
View Code

这样就是说在物体开始回弹的时候回调,并且只有一次,不过看效果后发现有点误差,就是前面的几次回弹物体并没有在最底部就回弹了,这个想想后发现没有什么合适的方法解决,看看是不是可以改变它原本的算法去控制,后来在判断的地方把数值提高了一点,

发现果然有效果,好了,因为每个回弹的数值都是经过那里的

到这里告一段落了,不过不知道有没有发现有点问题,因为我的背景设置的是白色的,而且吃食的那段动画其实也是重新绘制了一层白色,所以看不出来,但是如果背景没有设置或者不是白色,那么就会出现问题了,这显然也不是我们想要的

于是还是得在次去优化,这里想来想去也只能在创建一个画板去绘制了,分两块,就类似于橡皮擦一样的效果,所以得小小的修改一下

 

然后把吃食物的那段的绘制移到新的画板中,这样就可以达到吃的效果了

protected void onDraw(Canvas canvas) {
        if (!isDraw) return;

        //绘制大球
        path.reset();
        path.moveTo(startPoint.x + faceRadius/2,startPoint.y);
        path.cubicTo(movePoint1.x,movePoint1.y + faceRadius/2,movePoint2.x,movePoint2.y + faceRadius/2,endPoint.x - faceRadius/2,endPoint.y);
        canvas.drawPath(path, facePaint);

        //绘制小球,需要在最后面绘制
        canvas.drawArc(rectF, angle, 360 - angle * 2, true, facePaint);

        if (mBitmap != null) {
            canvas.drawBitmap(mBitmap, 0, 0, defPaint);
        }
    }

    private void draw(){

        //绘制“食物”
        foodPath.reset();
        foodPath.moveTo(startPoint.x,startPoint.y);
        foodPath.cubicTo(movePoint1.x,movePoint1.y,movePoint2.x,movePoint2.y,endPoint.x,endPoint.y);
        mCanvas.drawPath(foodPath, effectPaint);
        //吃掉“食物”
        for (PointF f : clears) {
            RectF rectF = new RectF(f.x-foodRadius*2,f.y-foodRadius*2,f.x+foodRadius*2,f.y+foodRadius*2);
            mCanvas.drawOval(rectF,clearPaint);
        }

        postInvalidate();
    }

在次运行,可以看到,背景已经都移除,在无背景的状态下正常显示动画

 

 

下面是下载地址,谢谢收藏  ^_^

 

GitHub:https://github.com/1024477951/FragmentApp

posted on 2018-02-08 14:45  翻滚的咸鱼  阅读(639)  评论(0编辑  收藏  举报