自定義灑豆子

 

 

先上效果圖java

    

 

 

 

灑豆子的效果,突發奇想,以爲這個動畫挺有意思的,就抽空寫了一個玩玩android

繪製流程:git

  定義6個‘’豆子‘’,每一個豆子有各自的屬性,大小,拋出的速度等,而後控制每一個的方向和狀態,回彈效果使用差值器 BounceInterpolatorgithub

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類看起來很清爽算法

下面是豆子類canvas

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方法中app

默認是正常拋出,而後碰到邊緣後改變狀態往回彈dom

使用上只關注兩個方法就好了ide

 

 這裏是把控件放在了一個dialog裏面,這個看我的喜歡,顯然dialog不是很適合,或者能夠加到下拉庫的頭部上去,效果應該不錯post

<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

相關文章
相關標籤/搜索