转载请注明出处

准备

气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,等等一系列因素,想要在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预览

#####实现原理:
通过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设置一个没有重量,没有速度的边界

由上图可以看见,因为红色区域没有重量,没有速度,当物体运动到边界时,无法继续进入红色区域,只能在白色区域运动,由于重力方向向下,最终物体会静止在白色区域底部.
下面看一下如何设置零重力边界:

 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绑定bodybody.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);}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {super.onLayout(changed, l, t, r, b);ballView.onLayout(changed);}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);ballView.onDraw(canvas);}@Overrideprotected 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);//设置tagpoolBall.addView(imageView, layoutParams);//把imageView添加到父控件imageView.setOnClickListener(new View.OnClickListener() {//设置点击事件@Overridepublic 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

转载请注明出处

android仿摩拜贴纸碰撞|气泡碰撞相关推荐

  1. Android 仿摩拜贴纸的动画

    最近在摩拜上发现一个特别喜欢的动画效果  一眼就喜欢上了    然后就开始了我的模仿之路   在CSDN搜索时发现原来我不是第一人   通过对其博客的观摩完成了一个简单的实现 需要深入开发的可以去(h ...

  2. android 吐泡泡动画,android仿摩拜贴纸碰撞|气泡碰撞

    转载请注明出处 准备 气泡碰撞最重要的就是边缘检测,气泡的运动涉及到重力,方向,重心,线速度,角速度,,等等一系列因素,想要在android 用view描述现实世界中的气泡实在是难度很大.网上查找资料 ...

  3. android 滑动接听源码,android仿摩拜单车APP、炫酷RecyclerView、卡片滑动、仿饿了么点餐、自定义索引等源码...

    Android精选源码 Android优质博客 前言permissions4m 最初的设计是仅仅做成一个编译器注解框架,在1.0.0版本时,它纯粹地实现了原生 Android 请求流程,关于它的设计思 ...

  4. android 卡片上滑放大,android仿摩拜单车APP、炫酷RecyclerView、卡片滑动、仿饿了么点餐、自定义索引等源码...

    Android精选源码 Android优质博客 前言permissions4m 最初的设计是仅仅做成一个编译器注解框架,在1.0.0版本时,它纯粹地实现了原生 Android 请求流程,关于它的设计思 ...

  5. Android百度地图实例详解之仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)

    Android百度地图实例详解之仿摩拜单车APP(包括附近车辆.规划路径.行驶距离.行驶轨迹记录,导航等) 标签: android百度地图行驶轨迹记录共享单车行驶距离和时间 2017-03-08 20 ...

  6. 仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,导航等)

    本文是由奇虎360公司高磊关于使用百度地图仿摩拜单车APP,原文地址:http://blog.csdn.net/gaolei1201/article/details/60876811 最近共享单车很火 ...

  7. 微信小程序仿摩拜单车

    本小程序仿摩拜单车的地图显示和拖动部分,单车数据采用周边厕所模拟.index.wxml如下: <map id="map" bindcontroltap="bindc ...

  8. Android百度地图实例详解之仿摩拜单车APP(包括附近车辆、规划路径、行驶距离、行驶轨迹记录,轨迹回放,导航等)

    转载请标明地址:http://blog.csdn.net/gaolei1201/article/details/60876811 2016一路有你,2017一起奋斗! 最近共享单车很火,动辄几亿美刀, ...

  9. 高仿摩拜解锁单车的加载控件

    一.写在前面 最近在下班骑车回家的过程中,发现摩拜单车的解锁进度条还是挺有意思的,它是一直转,根据进度增大圆弧角度,最后有一个打钩的动画,好了,话不多说,马上就来实现一下.上面图的效果不是很好,真实效 ...

最新文章

  1. 如何用Dart写一个单例
  2. handlebars.js {{#if}}中的逻辑运算符是有条件的
  3. cjson使用_LiteOS云端对接教程01-cJSON组件使用教程
  4. Java 7 vs Groovy 2.1性能比较
  5. 使用 Flomesh 强化 Spring Cloud 服务治理
  6. java开心消消乐代码_Vue实现开心消消乐游戏算法
  7. ajax jsp jquery,ajax +jquery 基本
  8. NSURLConnection和Runloop
  9. guzz 1.3.0大版本发布,支持Spring事务
  10. 李宏毅机器学习HW2-winner or loser-利用逻辑回归进行收入分类
  11. 最新酷盒工具箱iApp源码9.5版+功能很多
  12. 地级市交通基础设施数据,省份交通基础设施数据,处理好的面板数据(excel或stata版本)
  13. android 相对布局(RelativeLayout)
  14. 微软邮箱(@outlook.com/@hotmail.com):双重验证+应用密码
  15. Android Studio设置自动换行快捷键
  16. 【渝粤教育】电大中专中医基础知识 (3)作业 题库
  17. 2022-2028年中国航空货运产业发展动态及竞争战略分析报告
  18. 新版方正教务系统爬虫
  19. 最快的分布式关系型数据库MEMSQL
  20. 元宵到·桃花开,情人节征文活动榜单已公布!

热门文章

  1. C#USB连接斑马条码打印机打印二维码、图片、及中文文字_实战项目中摘选
  2. 季逸超:90后IT少年的“盖茨梦”
  3. 致远SPM解决方案之费用管理
  4. 好的网站内容文章是网站发展的动力
  5. 【网路编程】网络基础知识(IP、子网掩码、网关等)概念概述
  6. Java for Web学习笔记(三五):自定义tag(3)TLDS和Tag Handler
  7. c#开发Windows服务程序及部署
  8. 【问答】Q群问关于机器人关节电机选择
  9. android撕衣服应用介绍,Android开发基础面试题
  10. Vue slot-scope的理解(适合初学者)