精华内容
下载资源
问答
  • Android 气泡碰撞

    千次阅读 2017-11-15 16:02:55
    最近有个需求要实现气泡碰撞的效果,本来有两个参考: 1:仿摩拜贴纸动画的效果:博客地址:http://m.blog.csdn.net/qq_28268507/article/details/74892372采用jbox2d物理引擎来实现的,但是没法实现需求的效果2:...

    最近有个需求要实现气泡碰撞的效果,本来有两个参考:
    1:仿摩拜贴纸动画的效果:

    这里写图片描述

    博客地址:http://m.blog.csdn.net/qq_28268507/article/details/74892372

    采用jbox2d物理引擎来实现的,但是没法实现需求的效果

    2:Android,气泡动画。(碰撞算法的半成品)

    这里写图片描述

    博客地址:http://blog.csdn.net/u010386612/article/details/50580080

    博主采用动态绘制来实现,但是碰撞算法没有实现,这就很蛋疼了。

    最后还是采用了第二种方式来实现,自己在博主的基础上改进代码,增加碰撞的算法,勉强实现了气泡碰撞动画。

    效果如下:

    这里写图片描述

    由于图片现在,录制的不多,下面分析一下代码:

    主要修改的地方有两点:

    1:增加初始化圆球不重叠的逻辑

    /**
         * 初始化圆球不重叠
         * @param mBall
         * @param position
         */
        private void checkCollide(Ball mBall, int position) {
            float cx = mBall.cx;
            float cy = mBall.cy;
            int cr = mBall.radius;
            for (int i = 0; i < bList.size(); i++) {
                if (i == position) {
                    return;
                }
                Ball balls = bList.get(i);
                if (checkCollideCircle(cx, cy, cr, balls.cx, balls.cy, balls.radius)) {
                    mBalls[position].cx = mRandom.nextInt(mWidth - mBalls[i].radius) + mBalls[i].radius;
                    mBalls[position].cy = mRandom.nextInt(mHeight - mBalls[i].radius) + mBalls[i].radius;
                    checkCollide(mBalls[position], position);
                }
            }
        }
     /**
         * 两个圆的碰撞检测
         *
         * @param x1
         * @param y1
         * @param r1
         * @param x2
         * @param y2
         * @param r2
         * @return
         */
        private  boolean checkCollideCircle(float x1, float y1, int r1, float x2,
                                                 float y2, int r2) {
            boolean iscollide = false;
            //两点之间的距离 小于两半径之和就发生了碰撞
            if (Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) <= r1 + r2+stoke) {
                iscollide = true;
            }else {
                iscollide = false;
            }
            return iscollide;
        }

    由于圆心是随机的,初始化的时候尽量不要让圆球重叠,所以这里用了一个递归的算法,如果小球数量过多,会有内存问题。

    2:增加气泡碰撞算法

     /**
         * 气泡碰撞算法
         * @param ball
         * @param position
         */
        private void collisionDetectingAndChangeSpeeds(Ball ball, int position) {
            float cx = ball.cx;
            float cy = ball.cy;
            int cr = ball.radius;
            for (int i = 0; i < mCount; i++) {
                if (i == position) {
                    return;
                }
                Ball balls = mBalls[i];
                if (checkCollideCircle(cx, cy, cr, balls.cx, balls.cy, balls.radius)) {
                    ball.vx = -ball.vx;
                    ball.vy = -ball.vy;
                    balls.vx = -balls.vx;
                    balls.vy = -balls.vy;
                }
            }
        }
    
        /**
         * 两个圆的碰撞检测
         *
         * @param x1
         * @param y1
         * @param r1
         * @param x2
         * @param y2
         * @param r2
         * @return
         */
        private  boolean checkCollideCircle(float x1, float y1, int r1, float x2,
                                                 float y2, int r2) {
            boolean iscollide = false;
            //两点之间的距离 小于两半径之和就发生了碰撞
            if (Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) <= r1 + r2+stoke) {
                iscollide = true;
            }else {
                iscollide = false;
            }
            return iscollide;
        }

    这里的气泡碰撞算法,看着其实很简单,考虑两两碰撞,不考虑更多的碰撞,因为太复杂,我的算法水平也是半吊子,别笑,主要的实现就是检测如果两个圆球距离小于等于半径,就速度反向。

    整体实现的代码:

    package com.orangetimes.filter.widget;
    
    import android.content.Context;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.os.Looper;
    import android.support.annotation.Nullable;
    import android.text.Layout;
    import android.text.StaticLayout;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.Log;
    import android.util.TypedValue;
    import android.view.MotionEvent;
    import android.view.View;
    
    import com.github.lzyzsd.randomcolor.RandomColor;
    import com.orangetimes.filter.R;
    import com.orangetimes.filter.model.TaskModel;
    
    import java.util.ArrayList;
    import java.util.Random;
    
    /**
     * Created by zj on 2017/10/17.
     */
    
    public class BallView extends View {
    
        private Random mRandom;
        private TextPaint tPaint;
        private ArrayList<TaskModel> datas = new ArrayList<>();
    
        class Ball {
            int radius; // 半径
            float cx;   // 圆心
            float cy;   // 圆心
            float vx; // X轴速度
            float vy; // Y轴速度
            Paint paint;
            String content;
            String taskId;
    
            // 移动
            void move() {
                //向角度的方向移动,偏移圆心
                cx += vx;
                cy += vy;
            }
    
            int left() {
                return (int) (cx - radius);
            }
    
            int right() {
                return (int) (cx + radius);
            }
    
            int bottom() {
                return (int) (cy + radius);
            }
    
            int top() {
                return (int) (cy - radius);
            }
        }
    
        private int mCount = 10;   // 小球个数
        private int maxRadius;  // 小球最大半径
        private int minRadius; // 小球最小半径
        private int minSpeed = 1; // 小球最小移动速度
        private int maxSpeed = 5; // 小球最大移动速度
    
        private int mWidth = 200;
        private int mHeight = 200;
        private int position;
        private RandomColor randomColor;
        private float stokeWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,
                getResources().getDimensionPixelSize(R.dimen.x5), getContext().getResources().getDisplayMetrics());
        private int stoke = getResources().getDimensionPixelSize(R.dimen.x5);
    
    
        public Ball[] mBalls;   // 用来保存所有小球的数组
        public ArrayList<Ball> bList;//保存设置圆心的小球集合
    
        public BallView(Context context) {
            this(context, null);
        }
    
        public BallView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initPaint();
        }
    
        private void initPaint() {
            bList = new ArrayList<>();
            // 初始化所有球(设置颜色和画笔, 初始化移动的角度)
            mRandom = new Random();
            randomColor = new RandomColor(); // 随机生成好看的颜色,github开源库。
            tPaint = new TextPaint();
            tPaint.setAntiAlias(true);
            tPaint.setTextAlign(Paint.Align.CENTER);
            tPaint.setColor(Color.WHITE);
            tPaint.setStyle(Paint.Style.FILL);
            tPaint.setTextSize(getResources().getDimensionPixelSize(R.dimen.x32));
            mBalls = new Ball[mCount];
            for (int i = 0; i < mCount; i++) {
                mBalls[i] = new Ball();
                // 设置画笔
                Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
                paint.setColor(randomColor.randomColor());
                paint.setStyle(Paint.Style.STROKE);
                paint.setStrokeWidth(stokeWidth);
                paint.setAlpha(180);
    
                // 设置速度
                float speedX = (mRandom.nextInt(maxSpeed - minSpeed + 1) + 5) / 10f;
                float speedY = (mRandom.nextInt(maxSpeed - minSpeed + 1) + 5) / 10f;
                mBalls[i].paint = paint;
                mBalls[i].vx = mRandom.nextBoolean() ? speedX : -speedX;
                mBalls[i].vy = mRandom.nextBoolean() ? speedY : -speedY;
            }
        }
    
        public BallView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            mWidth = resolveSize(mWidth, widthMeasureSpec);
            mHeight = resolveSize(mHeight, heightMeasureSpec);
            setMeasuredDimension(mWidth, mHeight);
            maxRadius = mWidth / 10;
            minRadius = mWidth / 10;
            // 初始化圆的半径和圆心
            for (int i = 0; i < mCount; i++) {
                if (datas.size() > 0) {
                    if ("1".equals(datas.get(i).getDifficultyLevel())) {
                        mBalls[i].radius = mWidth / 14+stoke;
                    } else if ("2".equals(datas.get(i).getDifficultyLevel())) {
                        mBalls[i].radius = mWidth / 13+stoke;
                    } else if ("3".equals(datas.get(i).getDifficultyLevel())) {
                        mBalls[i].radius = mWidth / 12+stoke;
                    } else if ("4".equals(datas.get(i).getDifficultyLevel())) {
                        mBalls[i].radius = mWidth / 11+stoke;
                    } else if ("5".equals(datas.get(i).getDifficultyLevel())) {
                        mBalls[i].radius = maxRadius+stoke;
                    } else {
                        mBalls[i].radius = minRadius+stoke;
                    }
                } else {
                    mBalls[i].radius = maxRadius+stoke;
                }
                // 初始化圆心的位置, x最小为 radius, 最大为mwidth- radius
                mBalls[i].cx = mRandom.nextInt(mWidth - mBalls[i].radius) + mBalls[i].radius;
                mBalls[i].cy = mRandom.nextInt(mHeight - mBalls[i].radius) + mBalls[i].radius;
                //
                bList.add(mBalls[i]);
                position = i;
                checkCollide(mBalls[position], position);
            }
        }
    
        /**
         * 初始化圆球不重叠
         * @param mBall
         * @param position
         */
        private void checkCollide(Ball mBall, int position) {
            float cx = mBall.cx;
            float cy = mBall.cy;
            int cr = mBall.radius;
            for (int i = 0; i < bList.size(); i++) {
                if (i == position) {
                    return;
                }
                Ball balls = bList.get(i);
                if (checkCollideCircle(cx, cy, cr, balls.cx, balls.cy, balls.radius)) {
                    mBalls[position].cx = mRandom.nextInt(mWidth - mBalls[i].radius) + mBalls[i].radius;
                    mBalls[position].cy = mRandom.nextInt(mHeight - mBalls[i].radius) + mBalls[i].radius;
                    checkCollide(mBalls[position], position);
                }
            }
        }
    
    
        @Override
        protected void onDraw(Canvas canvas) {
            long startTime = System.currentTimeMillis();
            /**
             *  先画出所有圆
             */
            if (datas.size() > 0) {
                for (int i = 0; i < datas.size(); i++) {
                    Ball ball = mBalls[i];
                    canvas.drawCircle(ball.cx, ball.cy, ball.radius, ball.paint);
                    Rect rect = new Rect((int) (ball.cx - ball.radius), (int) (ball.cy - ball.radius), (int) (ball.cx + ball.radius), (int) (ball.cy + ball.radius));
                    Paint.FontMetrics fontMetrics = tPaint.getFontMetrics();
                    float top = fontMetrics.top;//为基线到字体上边框的距离,即上图中的top
                    float bottom = fontMetrics.bottom;//为基线到字体下边框的距离,即上图中的bottom
                    int baseLineY = (int) (rect.centerY() + top);//基线中间点的y轴计算公式
                    String str = datas.get(i).getName();
                    ball.content = datas.get(i).getName();
                    ball.taskId = datas.get(i).getId();
                    StaticLayout layout = new StaticLayout(str, tPaint, 2 * ball.radius, Layout.Alignment.ALIGN_NORMAL, 1.0F, 0.0F, true);
                    canvas.save();
                    canvas.translate(rect.centerX(), baseLineY);//从100,100开始画
                    layout.draw(canvas);
                    canvas.restore();//别忘了restore
    //            canvas.drawText(str, rect.centerX(), baseLineY, tPaint);
                }
    
                /**
                 *  球碰撞边界
                 */
    
                for (int i = 0; i < datas.size(); i++) {
                    Ball ball = mBalls[i];
                    collisionDetectingAndChangeSpeed(ball); // 碰撞边界的计算
                    collisionDetectingAndChangeSpeeds(ball, i);
                    ball.move(); // 移动
                }
    
                long stopTime = System.currentTimeMillis();
                long runTime = stopTime - startTime;
                // 16毫秒执行一次
                postInvalidateDelayed(Math.abs(runTime - 16));
            }
        }
    
        public void setData(ArrayList<TaskModel> mList) {
            this.mCount = mList.size();
            this.datas = mList;
            if (Looper.getMainLooper() == Looper.myLooper()) {
                invalidate();
            } else {
                postInvalidate();
            }
        }
    
        /**
         * 气泡碰撞算法
         * @param ball
         * @param position
         */
        private void collisionDetectingAndChangeSpeeds(Ball ball, int position) {
            float cx = ball.cx;
            float cy = ball.cy;
            int cr = ball.radius;
            for (int i = 0; i < mCount; i++) {
                if (i == position) {
                    return;
                }
                Ball balls = mBalls[i];
                if (checkCollideCircle(cx, cy, cr, balls.cx, balls.cy, balls.radius)) {
                    ball.vx = -ball.vx;
                    ball.vy = -ball.vy;
                    balls.vx = -balls.vx;
                    balls.vy = -balls.vy;
                }
            }
        }
    
        /**
         * 两个圆的碰撞检测
         *
         * @param x1
         * @param y1
         * @param r1
         * @param x2
         * @param y2
         * @param r2
         * @return
         */
        private  boolean checkCollideCircle(float x1, float y1, int r1, float x2,
                                                 float y2, int r2) {
            boolean iscollide = false;
            //两点之间的距离 小于两半径之和就发生了碰撞
            if (Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2)) <= r1 + r2+stoke) {
                iscollide = true;
            }else {
                iscollide = false;
            }
            return iscollide;
        }
    
        // 判断球是否碰撞碰撞边界
        public void collisionDetectingAndChangeSpeed(Ball ball) {
            int left = getLeft();
            int top = getTop();
            int right = getRight();
            int bottom = getBottom();
    
            float speedX = ball.vx;
            float speedY = ball.vy;
    
            // 碰撞左右,X的速度取反。 speed的判断是防止重复检测碰撞,然后黏在墙上了=。=
            if (ball.left() <= left && speedX < 0) {
                ball.vx = -ball.vx;
            } else if (ball.top() <= top && speedY < 0) {
                ball.vy = -ball.vy;
            } else if (ball.right() >= right && speedX > 0) {
                ball.vx = -ball.vx;
            } else if (ball.bottom() >= bottom && speedY > 0) {
                ball.vy = -ball.vy;
            }
        }
    
        float x, y;
    
        @Override
        public boolean onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_DOWN) {
                x = event.getX();
                y = event.getY();
                for (int i = 0; i < mCount; i++) {
                    Ball ball = mBalls[i];
                    //点击位置x坐标与圆心的x坐标的距离
                    int distanceX = (int) Math.abs(ball.cx - x);
                    //点击位置y坐标与圆心的y坐标的距离
                    int distanceY = (int) Math.abs(ball.cy - y);
                    //点击位置与圆心的直线距离
                    int distanceZ = (int) Math.sqrt(Math.pow(distanceX, 2) + Math.pow(distanceY, 2));
                    if (distanceZ <= ball.radius) {
                        Log.e("ball---->", ball.content);
                        progressListener.getTaskId(ball.taskId);
                    }
                }
            }
            return true;
        }
    
        public interface BallViewListener {
            void getTaskId(String taskId);
        }
    
        public BallViewListener progressListener;
    
        public void setBallViewListener(BallViewListener listener) {
            progressListener = listener;
        }
    }

    这里面需要提到的就是由于是动态刷新,所以在加载数据的时候需要调用requestLayout,

    开放的接口如下:

      if (list != null) {
                ballview.setData(list);
                ballview.requestLayout();
            }

    效果:

    这里写图片描述

    展开全文
  • 气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料后,找到了一个正好能满足我们需求的库:jbox2d是...

    转载请注明出处

    准备

    气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料后,找到了一个正好能满足我们需求的库:jbox2d是一个2D物理引擎,原版是Box2D,采用c++编写,jbox2d是原版的java版.在github下载项目编译生成jar包,生成过程可以参考SyncAny-JBox2D运用-打造摩拜单车贴纸动画效果,感谢SyncAny提供的思路,不过使用gradle打包时报错,通过mvn install成功生成jar包.不想麻烦可以点此 链接 下载,密码:oqwo

    JBox2D初步了解点击查看详细介绍
    1. World类(世界) 用于创建物理世界
    2. Body类(刚体)理解为生活中的物体
    3. BodyDef类(刚体描述)位置,状态等
    4. Vec2类(二维向量)可以用来描述运动的方向,力的方向,位置,速度等
    5. FixtureDef类( 夹具,我的理解是物体是属性,包括,shape形状,density密度,friction摩擦系数[0,1],restitution恢复系数[0,1]等)
    6. Shape的子类(描述刚体的形状,有PolygonShape,CircleShape)

    有了上面的准备工作,和对JBox2D初步了解,我们就可以开始撸代码啦…
    还是先看效果演示: demo.APK预览
    ball.gif
    #####实现原理:
    通过JBox2D提供的类和方法描述物体的运动状态然后改变android中view状态
    android中改变view的位置可以通过实时改变view的坐标位置实现,而这个不停变化的坐标值,我们可以通过Body类获取.可以这样理解,body就是生活中的一个物体,而view则是这个物体的影子,物体向左移动,影子也会跟着向左移动,前提是有光照,而我们要做的就是让这个物体和影子建立联系 绑定起来 ,如何找到 “光”?
    ######就是绑定起来,使用view.setTag()把body绑定起来
    #####实现步骤:

    1. 创建物理世界
    if (world == null) {
                world = new World(new Vec2(0f, 10f));//创建世界,设置重力方向,y向量为正数重力方向向下,为负数则向上
            }
    
    1. 设置世界的边界
      可以采用AABB类设置边界,这里我们通过Body刚体的属性(BodyType类)来框定一个边界.
      BodyType类源码:
    /**
     * The body type.
     * static: zero mass, zero velocity, may be manually moved
     * kinematic: zero mass, non-zero velocity set by user, moved by solver
     * dynamic: positive mass, non-zero velocity determined by forces, moved by solver
     * 
     * @author daniel
     */
    public enum BodyType {
    	STATIC, KINEMATIC, DYNAMIC
    }
    

    定义了一个枚举,STATIC:0重量,0速度;KINEMATIC:零质量,非零速度由用户设定;DYNAMIC:非零速度,正质量,非零速度由力决定
    所有我们可以通过STATIC设置一个没有重量,没有速度的边界
    2108-5-19.png
    由上图可以看见,因为红色区域没有重量,没有速度,当物体运动到边界时,无法继续进入红色区域,只能在白色区域运动,由于重力方向向下,最终物体会静止在白色区域底部.
    下面看一下如何设置零重力边界:

     BodyDef bodyDef = new BodyDef();
     bodyDef.type = BodyType.STATIC;//设置零重力,零速度
    PolygonShape polygonShape1 = new PolygonShape();//创建多边形实例
    polygonShape1.setAsBox(bodyWidth, bodyRatio);//多边形设置成为盒型也就是矩形,传入的参数为宽高
    FixtureDef fixtureDef = new FixtureDef();
    fixtureDef.shape = polygonShape1;//形状设置为上面的多边形(盒型)
    fixtureDef.density = 1f;//物质密度随意
    fixtureDef.friction = 0.3f;//摩擦系数[0,1]
    fixtureDef.restitution = 0.5f;//恢复系数[0,1]
    bodyDef.position.set(0, -bodyRatio);//边界的位置
    Body bodyTop = world.createBody(bodyDef);//世界中创建刚体
    bodyTop.createFixture(fixtureDef);//刚体添加夹具
    

    对于恢复系数restitution 等于1时,为完全弹性碰撞,在(0,1)之间为非完全弹性碰撞,等于0时,会融入其中.
    上面就是一个矩形边界,创建四个围成一个封闭的边界,
    3. 创建圆形刚体
    上面我们已经创建了一个多边形刚体,圆形刚体的创建方式和上面相同

    CircleShape circleShape = new CircleShape();
    circleShape.setRadius(radius);//设置半径
    FixtureDef fixture = new FixtureDef();
    fixture.setShape(shape);
    fixture.density = density;
    fixture.friction = friction;
    fixture.restitution = restitution;
    Body body = world.createBody(bodyDef);//用世界创建出刚体
    body.createFixture(fixture);
    
    1. 让body刚体动起来
      查看Body的源码可以知道,提供了一些运动的方法
      /**
       * Set the linear velocity of the center of mass.
       * 
       * @param v the new linear velocity of the center of mass.
       */
      public final void setLinearVelocity(Vec2 v) {
        if (m_type == BodyType.STATIC) {
          return;
        }
        if (Vec2.dot(v, v) > 0.0f) {
          setAwake(true);
        }
        m_linearVelocity.set(v);
      }
    

    通过调用setLinearVelocity(Vec2 v)给body的重心设置一个线性速度

      /**
       * Apply an impulse at a point. This immediately modifies the velocity. It also modifies the
       * angular velocity if the point of application is not at the center of mass. This wakes up the
       * body if 'wake' is set to true. If the body is sleeping and 'wake' is false, then there is no
       * effect.
       * 
       * @param impulse the world impulse vector, usually in N-seconds or kg-m/s.
       * @param point the world position of the point of application.
       * @param wake also wake up the body
       */
      public final void applyLinearImpulse(Vec2 impulse, Vec2 point, boolean wake) {
        if (m_type != BodyType.DYNAMIC) {
          return;
        }
        if (!isAwake()) {
          if (wake) {
            setAwake(true);
          } else {
            return;
          }
        }
        m_linearVelocity.x += impulse.x * m_invMass;
        m_linearVelocity.y += impulse.y * m_invMass;
        m_angularVelocity +=
            m_invI * ((point.x - m_sweep.c.x) * impulse.y - (point.y - m_sweep.c.y) * impulse.x);
      }
    

    调用applyLinearImpulse(Vec2 impulse, Vec2 point, boolean wake) 给某一点施加一个脉冲,会立刻修改速度,如果作用点不在重心就会修改角速度.Vec2可以传入随机数,产生随机的速度
    5. 绑定view
    我们获取viewGroup的子控件view的个数,创建对用数量的body,并且给view设置tag为body

    ...
       int childCount = mViewGroup.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childAt = mViewGroup.getChildAt(i);
                Body body = (Body) childAt.getTag(R.id.body_tag);
                if (body == null || haveDifferent) {
                    createBody(world, childAt);
                }
            }
    ...
     /**
         * 创建刚体
         */
        private void createBody(World world, View view) {
            BodyDef bodyDef = new BodyDef();
            bodyDef.type = BodyType.DYNAMIC;//有重量,有速度
            bodyDef.position.set(view.getX() + view.getWidth() / 2 ,view.getY() + view.getHeight() / 2);
            Shape shape = null;
            Boolean isCircle = (Boolean) view.getTag(R.id.circle_tag);
            if (isCircle != null && isCircle) {
                shape = createCircle(view);
            }
            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.body_tag, body);//给view绑定body
            body.setLinearVelocity(new Vec2(random.nextFloat(), random.nextFloat()));//线性运动
        }
    
    1. 实时绘制view的位置
      实时获取body刚体的位置,然后设置给view,调用invalidate()重绘
        public void onDraw(Canvas canvas) {
            if (!startEnable)
                return;
            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.body_tag); //从view中获取绑定的刚体
                if(body != null){
                    view.setX(body.getPosition().x - view.getWidth() / 2);//获取刚体的位置信息
                    view.setY(body.getPosition().y - view.getHeight() / 2);
                    view.setRotation(radiansToDegrees(body.getAngle() % 360));//设置旋转角度
                }
            }
            mViewGroup.invalidate();//更新view的位置
        }
    

    这个方法在重写的自定义父控件的onDraw()方法中调用,后面回帖出源码
    7. 自定义ViewGroup
    代码很简单,需要注意的是,我们需要重写onDraw()方法,所以要清除WillNotDraw标记,这样onDraw(0方法才会执行.

    public class PoolBallView extends FrameLayout {
        private BallView ballView;
        public PoolBallView(Context context) {
            this(context,null);
        }
        public PoolBallView(Context context, AttributeSet attrs) {
            this(context, attrs,-1);
        }
        public PoolBallView(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            setWillNotDraw(false);//重写ondraw需要
            ballView = new BallView(context, this);
        }
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            super.onLayout(changed, l, t, r, b);
            ballView.onLayout(changed);
        }
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
            ballView.onDraw(canvas);
        }
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            super.onSizeChanged(w, h, oldw, oldh);
            ballView.onSizeChanged(w,h);
        }
        public BallView getBallView(){
            return this.ballView;
        }
    }
    
    使用

    xml布局中添加此ViewGroup容器
    java操作代码

    FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
            layoutParams.gravity = Gravity.TOP | Gravity.CENTER_HORIZONTAL;
            for (int i = 0; i < imgs.length; i++) {
                ImageView imageView = new ImageView(this);//创建imageView控件
                imageView.setImageResource(imgs[i]);//设置资源图片
                imageView.setTag(R.id.circle_tag, true);//设置tag
                poolBall.addView(imageView, layoutParams);//把imageView添加到父控件
                imageView.setOnClickListener(new View.OnClickListener() {//设置点击事件
                    @Override
                    public void onClick(View v) {
                        Toast.makeText(MainActivity.this, "点击了气泡", Toast.LENGTH_SHORT).show();
                    }
                });
    

    ok至此,小球就会自由落体运动了,后面可以再加入陀螺仪方向感应,等控制操作,下面贴出小球创建控制类代码:

    public class BallView {
    
        private Context   context;
        private World     world;//世界
        private int       pWidth;//父控件的宽度
        private int       pHeight;//父控件的高度
        private ViewGroup mViewGroup;//父控件
        private float density     = 0.5f;//物质密度
        private float friction    = 0.5f;//摩擦系数
        private float restitution = 0.5f;//恢复系数
        private final Random random;
        private boolean startEnable        = true;//是否开始绘制
        private int     velocityIterations = 3;//迭代速度
        private int     positionIterations = 10;//位置迭代
        private float   dt                 = 1f / 60;//刷新时间
        private int     ratio              = 50;//物理世界与手机虚拟比例
    
        public BallView(Context context, ViewGroup viewGroup) {
            this.context = context;
            this.mViewGroup = viewGroup;
            random = new Random();
        }
        public void onDraw(Canvas canvas) {
            if (!startEnable)
                return;
            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.body_tag); //从view中获取绑定的刚体
                if (body != null) {
                    //获取刚体的位置信息
                    view.setX(metersToPixels(body.getPosition().x) - view.getWidth() / 2);
                    view.setY(metersToPixels(body.getPosition().y) - view.getHeight() / 2);
                    view.setRotation(radiansToDegrees(body.getAngle() % 360));
                }
            }
            mViewGroup.invalidate();//更新view的位置
        }
    
        /**
         * @param b
         */
        public void onLayout(boolean b) {
            createWorld(b);
        }
    
        /**
         * 创建物理世界
         */
        private void createWorld(boolean haveDifferent) {
    
            if (world == null) {
                world = new World(new Vec2(0f, 10f));//创建世界,设置重力方向
                initWorldBounds();//设置边界
            }
            int childCount = mViewGroup.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View childAt = mViewGroup.getChildAt(i);
                Body body = (Body) childAt.getTag(R.id.body_tag);
                if (body == null || haveDifferent) {
                    createBody(world, childAt);
                }
            }
        }
    
        /**
         * 创建刚体
         */
        private void createBody(World world, View view) {
            BodyDef bodyDef = new BodyDef();
            bodyDef.type = 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.circle_tag);
            if (isCircle != null && isCircle) {
                shape = createCircle(view);
            }
            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.body_tag, body);
            //初始化物体的运动行为
            body.setLinearVelocity(new Vec2(random.nextFloat(), random.nextFloat()));
    
        }
    
        /**
         * 设置世界边界
         */
        private void initWorldBounds() {
            BodyDef bodyDef = new BodyDef();
            bodyDef.type = BodyType.STATIC;//设置零重力,零速度
            float bodyWidth = pixelsToMeters(pWidth);
            float bodyHeight = pixelsToMeters(pHeight);
            float bodyRatio = pixelsToMeters(ratio);
            PolygonShape polygonShape1 = new PolygonShape();
            polygonShape1.setAsBox(bodyWidth, bodyRatio);
    
            FixtureDef fixtureDef = new FixtureDef();
            fixtureDef.shape = polygonShape1;
            fixtureDef.density = 1f;//物质密度
            fixtureDef.friction = 0.3f;//摩擦系数
            fixtureDef.restitution = 0.5f;//恢复系数
    
            bodyDef.position.set(0, -bodyRatio);
            Body bodyTop = world.createBody(bodyDef);//世界中创建刚体
            bodyTop.createFixture(fixtureDef);//刚体添加夹具
    
            bodyDef.position.set(0, bodyHeight + bodyRatio);
            Body bodyBottom = world.createBody(bodyDef);//世界中创建刚体
            bodyBottom.createFixture(fixtureDef);
    
            PolygonShape polygonShape2 = new PolygonShape();
            polygonShape2.setAsBox(bodyRatio, bodyHeight);
            FixtureDef fixtureDef2 = new FixtureDef();
            fixtureDef2.shape = polygonShape2;
            fixtureDef2.density = 0.5f;//物质密度
            fixtureDef2.friction = 0.3f;//摩擦系数
            fixtureDef2.restitution = 0.5f;//恢复系数
    
            bodyDef.position.set(-bodyRatio, bodyHeight);
            Body bodyLeft = world.createBody(bodyDef);//世界中创建刚体
            bodyLeft.createFixture(fixtureDef2);//刚体添加物理属性
    
            bodyDef.position.set(bodyWidth + bodyRatio, 0);
            Body bodyRight = world.createBody(bodyDef);//世界中创建刚体
            bodyRight.createFixture(fixtureDef2);//刚体添加物理属性
    
        }
        /**
         * 创建圆形描述
         */
        private Shape createCircle(View view) {
            CircleShape circleShape = new CircleShape();
            circleShape.setRadius(pixelsToMeters(view.getWidth() / 2));
            return circleShape;
        }
        /**
         * 随机运动
         * 施加一个脉冲,立刻改变速度
         */
        public void rockBallByImpulse() {
            int childCount = mViewGroup.getChildCount();
            for (int i = 0; i < childCount; i++) {
                Vec2 mImpulse = new Vec2(random.nextInt(1000), random.nextInt());
                View view = mViewGroup.getChildAt(i);
                Body body = (Body) view.getTag(R.id.body_tag);
                if (body != null) {
                    body.applyLinearImpulse(mImpulse, body.getPosition(), true);
                    Log.e("btn", "有脉冲");
                } else {
                    Log.e("btn", "body == null");
                }
            }
        }
        /**
         * 向指定位置移动
         */
        public void rockBallByImpulse(float x, float y) {
            int childCount = mViewGroup.getChildCount();
            for (int i = 0; i < childCount; i++) {
                Vec2 mImpulse = new Vec2(x, y);
                View view = mViewGroup.getChildAt(i);
                Body body = (Body) view.getTag(R.id.body_tag);
                if (body != null) {
                    body.applyLinearImpulse(mImpulse, body.getPosition(), true);
                }
            }
        }
        public float metersToPixels(float meters) {
            return meters * ratio;
        }
        public float pixelsToMeters(float pixels) {
            return pixels / ratio;
        }
        /**
         * 弧度转角度
         *
         * @param radians
         * @return
         */
        private float radiansToDegrees(float radians) {
            return radians / 3.14f * 180f;
        }
        /**
         * 大小发生变化
         * @param pWidth
         * @param pHeight
         */
        public void onSizeChanged(int pWidth, int pHeight) {
            this.pWidth = pWidth;
            this.pHeight = pHeight;
        }
        private void setStartEnable(boolean b) {
            startEnable = b;
        }
        public void onStart() {
            setStartEnable(true);
        }
        public void onStop() {
            setStartEnable(false);
        }
    }
    

    项目地址:https://github.com/truemi/ballPull

    转载请注明出处

    展开全文
  • 我们从气泡碰撞中获得了时域重力波形的定性见解:在(i)期间,波形显示了碰撞的非线性,其特征在于调制频率和尖峰状的颠簸,而在(ii)期间,波形 表现出碰撞的线性,以平滑的单色振荡为特征。
  • 为了探究赤铁矿絮凝体-气泡的碰撞过程,基于统计平均和颗粒自由沉降的思想,通过自行搭建的颗粒-气泡碰撞观测系统对不同絮凝体颗粒尺寸与气泡直径、絮凝体密度和分形维数之间的碰撞概率变化进行研究.结果表明:在气泡...
  • 1 2 34 5 6 Title7 8 *{9 margin:0;10 padding:011 }1213 #main{14 margin:0 auto;15 position:relative;16 background-color:#fff17 }1819 #main div{20 position:absolute;21 width:1...

    1

    2

    3

    4

    5

    6

    Title

    7

    8 *{

    9 margin:0;

    10 padding:011 }

    12

    13 #main{

    14 margin:0 auto;

    15 position:relative;

    16 background-color:#fff17 }

    18

    19 #main div{

    20 position:absolute;

    21 width:100px;

    22 height:100px;

    23 overflow:hidden;

    24 -moz-border-radius:50%;

    25 -webkit-border-radius:50%;

    26 border-radius:50%;

    27 background:blue;

    28 }

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    展开全文
  • 优化气泡碰撞案例 首先上次零基础C#学习(一)中写到了一个timer控制气泡碰撞的案例,其中过程较为复杂,所以这次我们把他进行整理优化. 同样也是实现了一个timer 控制气泡的碰撞,但这次较为简单. 如何让多个气泡并存,都...

    优化版的气泡碰撞案例

    首先上次零基础C#学习(一)中写到了一个timer控制气泡碰撞的案例,其中过程较为复杂,所以这次我们把他进行整理优化.

    同样也是实现了一个timer 控制气泡的碰撞,但这次较为简单.

    如何让多个气泡并存,都显示在界面上?

    其中还实现了多个窗体对象并存的现象.

     timer1.Start();//开启计时器timer1
                //下面主要讲的是,气泡案例的简单方法,和多个气泡并存的显示.
            }
            int x = 10;//整形赋值
            int y = 10;//同上
    
            private void timer1_Tick(object sender, EventArgs e)//计时器timer1的事件
            {
                this.Left += x;
                this.Top += y;
                if (this.Top + this.Height >= Screen.PrimaryScreen.WorkingArea.Height || this.Top <= 0)
                    //上面这个代码上一个案例解释过,这是2.0版,其中加入了||(和),将两个if语句合并为一个.
                {
                    //实例化的form1对象名称f1,f1这个对象具备了form1的所有东西.
                    Form1 f1 = new Form1();//创建form1气泡,就是创建了一个对象
                    //new form1()就是相当于重新在执行一遍form1.
                    f1.Show();//显示这个对象f1
                    y *= -1;//这是改变对象的根本,y*=-1======>y=y*-1
                }
                if (this.Left + this.Width >= Screen.PrimaryScreen.WorkingArea.Width || this.Left <= 0)
                //上面这个代码上一个案例解释过,这是2.0版,其中加入了||(和),将两个if语句合并为一个.
                {
                    x *= -1;//这和上面的y一样
                }
    
    展开全文
  • 案例:气泡碰撞案例 一、制作气泡以前的准备工作 private void Form1_Load(object sender, EventArgs e) { this.FormBorderStyle = FormBorderStyle.None;//先把窗体设置为无边框的样式 this.Width = 200;//设置...
  • 为了研究絮团的形态对絮团与气泡碰撞及吸附的影响,设计了图像分析系统,拍摄了絮团与气泡接触过程中的视频文件并从中提取含有絮团位置及形态特征的静态帧。使用Image Pro Plus 6.0根据絮团的粒径对絮团进行了粒度级的...
  • 原生js生成气泡碰撞,随机生成颜色 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <style> * { margin: 0; padding: 0 ...
  • <!DOCTYPE html><... <head> <meta charset="UTF-8">...随机生成气泡碰撞</title> <style> html { height: 100%; } body { width: 100%; margin:...
  • js气泡碰撞反弹效果

    2016-08-22 08:38:22
    一个用js代码实现的,类似于windows气泡屏幕保护程序,仅供娱乐!
  • 此类气泡碰撞会以非热方式产生重粒子,这些重粒子会进一步失衡而衰减到SM中,并产生观察到的重子不对称性。 该过程可以在非常低的温度下进行,从而提供了新的球鞘后重生机理。 在本文中,我们提出了一个完全可...
  • JavaSwing气泡碰撞动画

    2018-06-21 16:21:55
    看到一个算法教学视频上的demo感觉很有意思,就自己模仿写了一下,再添加了一个气泡碰撞的判断。程序入口import java.awt.*; public class Main { public static void main(String[] args) { int sceneWidth=...
  • 一、要使用(学到)的知识 1、如何在解决方案资源管理器中新建项目? ​ (1)选择解决方案资源管理器在最上边解决方案资源管理器“…”=》右击新建项目=》选择...​ (1)如果要实现多个气泡出现,就必须创建...
  • 一个不错的H5游戏源码,支持手机端微信游戏,就是气泡游戏,有喜欢的朋友就以学一下,很好玩的,真的不错的源代码,想要吗?想要你就说嘛,你不说我怎么知道你想要呢
  • 转载请注明出处准备气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料后,找到了一个正好能满足...
  • 碰撞函数 85 function crash(a) { 86 var ball1x = container[a].cx; // 在数组中任意球的圆心坐标 87 var ball1y = container[a].cy; // 思路:先随便拿一个球,然后遍历所有球,拿这个球和...
  • // 气泡运动 move: function(ball) { var self = this; var T = self.T_bound, B = self.B_bound, L = self.L_bound, R = self.R_bound, BC = self.bounce, BX = ball.offsetLeft, BT = ball.offsetTop; ball....
  • HTML5响应式手机模板:H5游戏网页设计——html5手机微信气泡碰撞游戏源码 HTML+CSS+JavaScript 手机网站模板 手机模板 响应式手机网站 html5手机网站模板 企业手机网站模板 公司手机网站模板 作品介绍 1.网页作品...
  • 着重考虑纳米颗粒电性质对气泡附近颗粒动力学的影响。在传统的郎之万方程的基础上,添加了颗粒气泡的静电作用以及颗粒扩散电层变形的效应。对典型的运动状态进行深入的...结论具有定性分析纳米颗粒与气泡碰撞的意义。
  • JS彩色气泡移动碰撞特效是一款基于VUE.JS实现的全屏彩色透明气泡浮动碰撞特效。
  • JS彩色气泡移动碰撞特效是一款基于VUE.JS实现的全屏彩色透明气泡浮动碰撞特效。
  • flash气泡效果+碰撞 边框. 绿色的背景. 等等等等等

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,002
精华内容 400
关键字:

气泡碰撞