jbox2d貼紙動畫

使用jbox2d物理引擎打造摩拜單車貼紙動畫效果

接下來我們在看下摩拜單車安卓客戶端貼紙動畫,是什麼樣子呢?先上一副動態圖:

mobike_demo.gif

效果很炫,也很漂亮,至少看起來實現的難度要比ofo那個大的多。那麼該怎麼實現呢?(這裏插下題外話,ios端比較容易實現,因爲系統內置了某些api,可以很方便的實現這樣的效果),可我們是安卓程序員啊,怎麼辦?自定義view?嗯,好像可以,但是這座標怎麼計算?並且還持續運動着,還有彈力,摩擦力,碰撞力…..

大腦一片茫然,如果產品經理堅決的確定需要這個效果,咋整?先不慌,我們可以先了解下摩拜單車是如何實現這樣的效果的,很不幸,經過反編譯摩拜單車apk後,安裝包進行了加固,連混淆後的代碼都看不到.

於是就上網各種搜索酷炫動畫,不經意間發現了jbox2d物理引擎,然後回頭在看下摩拜單車的apk,發現了其用到了libgdx-box2d,所以說摩拜是使用libgdx-box2d物理引擎來實現的這個效果:

image.png

jbox2d和libgdx-box2d:

jbox2d和libgdx-box2d有什麼區別,他們之間的關係是什麼?

jbox2d:

jbox2d百度百科這樣描述的: jbox2D物理引擎原版 Box2D 是採用C++編寫的,後來擴展到java,as等多種版本。著名手機遊戲憤怒的小鳥便是採用jbox2D物理引擎。不過java版的jbox2D引擎性能不如C++環境下運行的性能好。在性能配置比較好的手機上面,jbox2D效果也是不錯的。

libgdx:

libgdx百度百科這樣描述的:libGdx是一個跨平臺的2D/3D的遊戲開發框架,它由Java/C/C++語言編寫而成。

可能看完百度百科,還是比較模糊,我們只知道了他倆都是物理引擎,並且知道了憤怒的小鳥,就是用jbox2d引擎開發的。

後來經過搜索瞭解得出這樣一句話:Libgdx使用jni封裝了box2d的c++版本,使得其運行效率比其他同級的物理引擎如jbox2d快不少。所以最後的結論是jbox2d是面向java的,運行效率要慢。而libgdx是面向c/c++的,運行效率要快。

這次效果的實現是基於jbox2d上完成的,最終效果圖如下:

mobike.gif

APK 下載體驗:

qrcode.png

Github地址: https://github.com/andmizi/MobikeTags 歡迎star

jbox2d快速上手,推薦參考百度文庫 https://wenku.baidu.com/view/c584cbfaf90f76c661371a5f.html

網上的文檔都是比較過時的,新版中有些地方有變化,不過不影響快速入門。

jbox2d Github: https://github.com/jbox2d/jbox2d

簡單描述下jbox2d物理引擎在安卓中的用法,jbox2d物理引擎並不負責view的繪製,只負物理數據的計算和分析,如物體的密度,質量,摩擦力,速度,碰撞力,恢復力……

通俗的講,在安卓中一個view代表了一個物體,也就是剛體,通過view和剛體的綁定並設置初始參數,最後不停的draw,再從綁定的剛體中取出參數繪製到界面上,如此循環。而物體的所有物理參數都由jbox2d物理引擎幫助我們完成。

在MobikeLibrary中,自己只寫了兩個類,就實現瞭如上效果

image.png

Mobike和MobikeView是自己寫的,org.jbox2d是官方的源代碼,MobikeLibrary的具體使用請查看 https://github.com/andmizi/MobikeTags

MobikeView.java:

package com.mobike.library;
import android.content.Context;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.widget.FrameLayout;

/**
 * Created by kimi on 2017/7/8 0008.
 * Email: [email protected]
 */

public class MobikeView extends FrameLayout {

    private Mobike mMobike;

    public MobikeView(@NonNull Context context) {
        this(context,null);
    }

    public MobikeView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setWillNotDraw(false);
        mMobike = new Mobike(this);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mMobike.onSizeChanged(w,h);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        mMobike.onLayout(changed);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        mMobike.onDraw(canvas);
    }

    public Mobike getmMobike(){
        return this.mMobike;
    }
}

MobikeView繼承自Framelayout,比較簡單,這裏略過

Mobike.java

package com.mobike.library;

import android.graphics.Canvas;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import org.jbox2d.collision.shapes.CircleShape;
import org.jbox2d.collision.shapes.PolygonShape;
import org.jbox2d.collision.shapes.Shape;
import org.jbox2d.common.Vec2;
import org.jbox2d.dynamics.Body;
import org.jbox2d.dynamics.BodyDef;
import org.jbox2d.dynamics.BodyType;
import org.jbox2d.dynamics.FixtureDef;
import org.jbox2d.dynamics.World;
import java.util.Random;

/**
 * Created by kimi on 2017/7/8 0008.
 * Email: [email protected]
 */

public class Mobike {

    public static final String TAG = Mobike.class.getSimpleName();

    private World world;
    private float dt = 1f / 60f;
    private int velocityIterations = 3;
    private int positionIterations = 10;
    private float friction = 0.3f,density = 0.5f,restitution = 0.3f,ratio = 50;
    private int width,height;
    private boolean enable = true;
    private final Random random = new Random();

    private ViewGroup mViewgroup;

    public Mobike(ViewGroup viewgroup) {
        this.mViewgroup = viewgroup;
        density = viewgroup.getContext().getResources().getDisplayMetrics().density;
    }

    public void onSizeChanged(int width,int height){
        this.width = width;
        this.height = height;
        //sizeChanged的時候獲取到viewgroup的寬和高
    }

    public void onDraw(Canvas canvas) {
        if(!enable){ //設置標記,在界面可見的時候開始draw,在界面不可見的時候停止draw
            return;
        }
        //dt 更新引擎的間隔時間
        //velocityIterations 計算速度
        //positionIterations 迭代的次數
        world.step(dt,velocityIterations,positionIterations);
        int childCount = mViewgroup.getChildCount();
        for(int i = 0; i < childCount; i++){
            View view = mViewgroup.getChildAt(i);
            Body body = (Body) view.getTag(R.id.mobike_body_tag);
            if(body != null){
                //從view中獲取綁定的剛體,取出參數,開始更新view
                view.setX(metersToPixels(body.getPosition().x) - view.getWidth() / 2);
                view.setY(metersToPixels(body.getPosition().y) - view.getHeight() / 2);
                view.setRotation(radiansToDegrees(body.getAngle() % 360));
            }
        }
        //手動調用,反覆執行draw方法
        mViewgroup.invalidate();
    }

    public void onLayout(boolean changed) {
        createWorld(changed);
    }

    public void onStart(){
        setEnable(true);
    }

    public void onStop(){
        setEnable(false);
    }

    public void update(){
        world = null;
        onLayout(true);
    }

    private void createWorld(boolean changed) {
        //jbox2d中world稱爲世界,這裏創建一個世界
        if(world == null){
            world = new World(new Vec2(0, 10.0f));
            //創建邊界,注意邊界爲static靜態的,當物體觸碰到邊界,停止模擬該物體
            createTopAndBottomBounds();
            createLeftAndRightBounds();
        }
        int childCount = mViewgroup.getChildCount();
        for(int i = 0; i < childCount; i++){
            View view = mViewgroup.getChildAt(i);
            Body body = (Body) view.getTag(R.id.mobike_body_tag);
            if(body == null || changed){
                createBody(world,view);
            }
        }
    }

    private void createBody(World world, View view) {
        //創建剛體描述,因爲剛體需要隨重力運動,這裏type設置爲DYNAMIC
        BodyDef bodyDef = new BodyDef();
        bodyDef.setType(BodyType.DYNAMIC);

        //設置初始參數,爲view的中心點
        bodyDef.position.set(pixelsToMeters(view.getX() + view.getWidth() / 2) ,
                             pixelsToMeters(view.getY() + view.getHeight() / 2));
        Shape shape = null;
        Boolean isCircle = (Boolean) view.getTag(R.id.mobike_view_circle_tag);
        if(isCircle != null && isCircle){
            //創建圓體形狀
            shape = createCircleShape(view);
        }else{
            //創建多邊形形狀
            shape = createPolygonShape(view);
        }
        //初始化物體信息
        //friction  物體摩擦力
        //restitution 物體恢復係數
        //density 物體密度
        FixtureDef fixture = new FixtureDef();
        fixture.setShape(shape);
        fixture.friction = friction;
        fixture.restitution = restitution;
        fixture.density = density;

        //用世界創建出剛體
        Body body = world.createBody(bodyDef);
        body.createFixture(fixture);
        view.setTag(R.id.mobike_body_tag,body);
        //初始化物體的運動行爲
        body.setLinearVelocity(new Vec2(random.nextFloat(),random.nextFloat()));
    }

    private Shape createCircleShape(View view){
        CircleShape circleShape = new CircleShape();
        circleShape.setRadius(pixelsToMeters(view.getWidth() / 2));
        return circleShape;
    }

    private Shape createPolygonShape(View view){
        PolygonShape polygonShape = new PolygonShape();
        polygonShape.setAsBox(pixelsToMeters(view.getWidth() / 2),pixelsToMeters(view.getHeight() / 2));
        return polygonShape;
    }

    private void createTopAndBottomBounds() {
        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.STATIC;

        PolygonShape box = new PolygonShape();
        float boxWidth = pixelsToMeters(width);
        float boxHeight =  pixelsToMeters(ratio);
        box.setAsBox(boxWidth, boxHeight);

        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = box;
        fixtureDef.density = 0.5f;
        fixtureDef.friction = 0.3f;
        fixtureDef.restitution = 0.5f;

        bodyDef.position.set(0, -boxHeight);
        Body topBody = world.createBody(bodyDef);
        topBody.createFixture(fixtureDef);

        bodyDef.position.set(0, pixelsToMeters(height)+boxHeight);
        Body bottomBody = world.createBody(bodyDef);
        bottomBody.createFixture(fixtureDef);
    }

    private void createLeftAndRightBounds() {
        float boxWidth = pixelsToMeters(ratio);
        float boxHeight = pixelsToMeters(height);

        BodyDef bodyDef = new BodyDef();
        bodyDef.type = BodyType.STATIC;

        PolygonShape box = new PolygonShape();
        box.setAsBox(boxWidth, boxHeight);
        FixtureDef fixtureDef = new FixtureDef();
        fixtureDef.shape = box;
        fixtureDef.density = 0.5f;
        fixtureDef.friction = 0.3f;
        fixtureDef.restitution = 0.5f;

        bodyDef.position.set(-boxWidth, boxHeight);
        Body leftBody = world.createBody(bodyDef);
        leftBody.createFixture(fixtureDef);


        bodyDef.position.set(pixelsToMeters(width) + boxWidth, 0);
        Body rightBody = world.createBody(bodyDef);
        rightBody.createFixture(fixtureDef);
    }

    private float radiansToDegrees(float radians) {
        return radians / 3.14f * 180f;
    }

    private float degreesToRadians(float degrees){
        return (degrees / 180f) * 3.14f;
    }

    public float metersToPixels(float meters) {
        return meters * ratio;
    }

    public float pixelsToMeters(float pixels) {
        return pixels / ratio;
    }

    public void random() {
        //彈一下,模擬運動
        int childCount = mViewgroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            Vec2 impulse = new Vec2(random.nextInt(1000) - 1000, random.nextInt(1000) - 1000);
            View view = mViewgroup.getChildAt(i);
            Body body = (Body) view.getTag(R.id.mobike_body_tag);
            if(body != null){
                body.applyLinearImpulse(impulse, body.getPosition(),true);
            }
        }
    }

    public void onSensorChanged(float x,float y) {
        //傳感器模擬運動
        int childCount = mViewgroup.getChildCount();
        for (int i = 0; i < childCount; i++) {
            Vec2 impulse = new Vec2(x, y);
            View view = mViewgroup.getChildAt(i);
            Body body = (Body) view.getTag(R.id.mobike_body_tag);
            if(body != null){
                body.applyLinearImpulse(impulse, body.getPosition(),true);
            }
        }
    }

    public float getFriction() {
        return friction;
    }

    public void setFriction(float friction) {
        if(friction >= 0){
            this.friction = friction;
        }
    }

    public float getDensity() {
        return density;
    }

    public void setDensity(float density) {
        if(density >= 0){
            this.density = density;
        }
    }

    public float getRestitution() {
        return restitution;
    }

    public void setRestitution(float restitution) {
        if(restitution >= 0){
            this.restitution = restitution;
        }
    }

    public float getRatio() {
        return ratio;
    }

    public void setRatio(float ratio) {
        if(ratio >= 0){
            this.ratio = ratio;
        }
    }

    public boolean getEnable() {
        return enable;
    }

    public void setEnable(boolean enable) {
        this.enable = enable;
        mViewgroup.invalidate();
    }
}

閱讀順序,onlayout—ondraw,如果你仔細閱讀過上面的百度文庫,應該都可以看的懂代碼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章