欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

自定义洒豆子

程序员文章站 2022-07-01 20:54:27
先上效果图 洒豆子的效果,突发奇想,觉得这个动画挺有意思的,就抽空写了一个玩玩 绘制流程: 定义6个‘’豆子‘’,每个豆子有各自的属性,大小,抛出的速度等,然后控制每个的方向和状态,回弹效果使用差值器 BounceInterpolator package com.fragmentapp.view.b ......

 

 

先上效果图

自定义洒豆子  自定义洒豆子  

 

 自定义洒豆子

 

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

绘制流程:

  定义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

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

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

自定义洒豆子

到这里就完工了

 

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

 

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