zoukankan      html  css  js  c++  java
  • 自定义洒豆子

    先上效果图

        

     

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

    绘制流程:

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

  • 相关阅读:
    如何弹出QQ临时对话框实现不添加好友在线交谈效果
    让sublime text3支持Vue语法高亮显示[转]
    spa(单页面应用)的优缺点[转]
    vue-devtoools 调试工具安装
    元素视差方向移动jQuery插件-类似github 404页面效果
    js删除数组元素、清空数组的简单方法
    sublime text3 setting-user
    vue环境搭建
    Starting httpd:Could not reliably determine the server's fully qualified domain name
    使用传输表空间迁移数据
  • 原文地址:https://www.cnblogs.com/LiuZhen/p/8337101.html
Copyright © 2011-2022 走看看