Android自定义view实现小球弹跳和蛛网效果
先上效果图:
小球碰撞效果: 蛛网效果:
这两个效果的实现都是使用了自定义view,主要是重写里面的onDraw方法
小球碰撞的代码:
先定义小球的类,存储小球的位置、速度、画笔等等信息:
MyBallTest.java
public class MyBallTest { private int positionX; private int positionY; private int speedX; private int speedY; private int radius; private int weight; private Paint paint; public MyBallTest(double positionX, double positionY, double speedX, double speedY, double radius, int weight){ this.positionX = (int) positionX; this.positionY = (int) positionY; this.speedX = (int) speedX; this.speedY = (int) speedY; this.radius = (int) radius; this.weight = weight; paint = new Paint(); paint.setStrokeWidth(1); int currColor = (int) -(Math.random() * (16777216 - 1) + 1); paint.setColor(currColor); paint.setAntiAlias(true); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setAlpha(101); } public void setPositonX(double positonX){ this.positionX = (int) positonX; } public void setPositonY(double positionY){ this.positionY = (int)positionY; } public void setSpeedX(double speedX){ this.speedX = (int)speedX; } public void setSpeedY(double speedY){ this.speedY = (int)speedY; } public int getPositonX(){ return positionX; } public int getPositionY(){ return positionY; } public int getSpeedX(){ return speedX; } public int getSpeedY(){ return speedY; } public int getRadius(){ return radius; } public int getWeight(){ return weight; } public Paint getPaint(){ return paint; } }
然后用一个辅助类来更新小球的移动和碰撞数据
GetListData.java
public class GetListData { private ArrayList<MyBallTest> myBallTestList = new ArrayList<>(); private float screenWidth; private float screenHeight; private boolean[][] stillCrash = new boolean[20][20]; public GetListData(float screenHeight, float screenWidth){ this.screenHeight = screenHeight; this.screenWidth = screenWidth; InIt(); } private void InIt(){ for (int i = 0; i == 0 ;){ MyBallTest myBallTest = getMyBallTestSample(); if (myBallTest != null){ myBallTestList.add(myBallTest); } if (myBallTestList.size() == 20){ i = 1 ; } } for (int i = 0; i < 20; i ++){ for (int j = 0; j < 20; j++){ stillCrash[i][j] = false; } } } private MyBallTest getMyBallTestSample(){ double positionX = Math.random() * screenWidth; double positionY = Math.random() * screenHeight; double speedX = Math.random() * 30 - 15; double speedY = Math.random() * 30 - 15; double radius = Math.random() * 50 + 30; int weigth = (int) (Math.random() * 3 + 1); MyBallTest myBallTest = new MyBallTest(positionX, positionY, speedX, speedY, radius, weigth); if (myBallTest.getPositonX() + radius >= screenWidth){ myBallTest.setPositonX(screenWidth - radius); } if (myBallTest.getPositionY() + radius >= screenHeight){ myBallTest.setPositonY(screenHeight - radius); } if (myBallTest.getPositonX() <= radius){ myBallTest.setPositonX(radius); } if (myBallTest.getPositionY() <= radius){ myBallTest.setPositonY(radius); } if (myBallTestList.size() != 0 && myBallTest != null){ for (int i = 0; i < myBallTestList.size(); i ++){ MyBallTest myBallTest1 = myBallTestList.get(i); if (myBallTest1 != null){ if (Math.pow( myBallTest.getPositonX() - myBallTest1.getPositonX(), 2 ) + Math.pow( myBallTest.getPositionY() - myBallTest1.getPositionY(), 2) < Math.pow(myBallTest.getRadius(), 2) + Math.pow(myBallTest1.getRadius(), 2) ){ return null; } } } } return myBallTest; } public void refreshThePosition(){ if (myBallTestList.size() == 0){ InIt(); } for (int i = 0; i < myBallTestList.size(); i ++){ int positionXNow = myBallTestList.get(i).getPositonX(); int positionYNow = myBallTestList.get(i).getPositionY(); int speedXNow = myBallTestList.get(i).getSpeedX(); int speedYNow = myBallTestList.get(i).getSpeedY(); int radius = myBallTestList.get(i).getRadius(); int weight = myBallTestList.get(i).getWeight(); int positionXNext = myBallTestList.get(i).getPositonX() + myBallTestList.get(i).getSpeedX(); int positionYNext = myBallTestList.get(i).getPositionY() + myBallTestList.get(i).getSpeedY(); int speedXNext = speedXNow; int speedYNext = speedYNow; if (positionXNext - radius < 0){ positionXNext = Math.abs(speedXNow) - positionXNow + 2 * radius; speedXNext = -speedXNow; }if (positionXNext + radius > screenWidth){ int screenWidthInt = (int) screenWidth; positionXNext = 2 * screenWidthInt - Math.abs(speedXNow) - radius * 2 - positionXNow; speedXNext = -speedXNow; } if (positionYNext - radius < 0){ positionYNext = Math.abs(speedYNow) - positionYNow + 2 * radius; speedYNext = -speedYNow; }if (positionYNext + radius > screenHeight){ int screenHeightInt = (int) screenHeight; positionYNext = 2 * screenHeightInt - Math.abs(speedYNow) - radius * 2 - positionYNow; speedYNext = -speedYNow; } for (int j = 0; j < i; j ++){ if (Math.pow(positionXNext - myBallTestList.get(j).getPositonX(), 2) + Math.pow(positionYNext - myBallTestList.get(j).getPositionY(), 2) <= Math.pow(radius + myBallTestList.get(j).getRadius(), 2) ) { if (stillCrash[i][j] == true){ //如果检测到碰撞的时候发现上一帧就已经与这个小球碰撞到,则不改变任何属性 }else { stillCrash[i][j] = true; if (myBallTestList.get(j).getSpeedX() * speedXNow < 0){ speedXNext = -speedXNext ; positionXNext = positionXNow; myBallTestList.get(j).setSpeedX( -myBallTestList.get(j).getSpeedX() ); } if (myBallTestList.get(j).getSpeedY() * speedYNext < 0){ speedYNext = -speedYNext; positionYNext = positionYNow; myBallTestList.get(j).setSpeedY( -myBallTestList.get(j).getSpeedY() ); } if (myBallTestList.get(j).getSpeedX() * speedXNext > 0){ if (Math.abs(speedXNext) > Math.abs(myBallTestList.get(j).getSpeedX())){ speedXNext = -speedXNext; }else { myBallTestList.get(j).setSpeedX(-myBallTestList.get(i).getSpeedX()); } } if (myBallTestList.get(j).getSpeedY() * speedYNext > 0){ if (Math.abs(speedYNext) > Math.abs(myBallTestList.get(j).getSpeedY())){ speedYNext = -speedYNext; }else { myBallTestList.get(j).setSpeedY(-myBallTestList.get(j).getSpeedY()); } } } if (Math.pow(positionXNext - myBallTestList.get(j).getPositonX(), 2) + Math.pow(positionYNext - myBallTestList.get(j).getPositionY(), 2) <= Math.pow(radius + myBallTestList.get(j).getRadius(), 2) ){ stillCrash[i][j] = true; }else { stillCrash[i][j] = false; } }else { stillCrash[i][j] = false; } } myBallTestList.get(i).setPositonX(positionXNext); myBallTestList.get(i).setPositonY(positionYNext); if (speedXNext == 0){ speedXNext = (int) (Math.random() * 30 - 15); } if (speedYNext == 0){ speedYNext = (int) (Math.random() * 30 - 15); } myBallTestList.get(i).setSpeedX(speedXNext); myBallTestList.get(i).setSpeedY(speedYNext); } } public ArrayList<MyBallTest> getTheData(){ return myBallTestList; } }
这里有一个问题比较大的点是如何来判断小球之间相互撞击的,开始的时候我是直接判定俩小球是否相撞,然后反向改变速度,但是当两个小球的下一帧没有分开的时候,两个小球会一直黏在一起并且来回运动,所以使用一个数组,来存储上一帧两个小球是否相撞,如果上一帧没有发生碰撞,即碰撞刚开始发生,那么改变小球的速度方向,如果上一帧已经和这个小球碰撞,那么说明碰撞还没有结束,不做任何操作。另外速度上的处理因为按照物理规则计算方向向量什么的实在太麻烦,所以写的比较简单,碰撞之后的方向有的时候也会显得比较奇怪。
之后自定义view,拿到辅助类中的数据,来进行页面的初始化或者更新操作
MyTestView.java
public class MyTestView extends View { private final String LOG_TAG = "MyTestView"; private ArrayList<MyBallTest> myBallTestList = new ArrayList<>(); public MyTestView(Context context) { super(context); } public MyTestView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public MyTestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public MyTestView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (myBallTestList.size() != 0){ for (int i = 0; i < myBallTestList.size(); i ++){ // Log.i(LOG_TAG, " onDraw is been used"); MyBallTest myBallTest = myBallTestList.get(i); canvas.drawCircle(myBallTest.getPositonX(), myBallTest.getPositionY(), myBallTest.getRadius(), myBallTestList.get(i).getPaint()); } }else { Paint paint = new Paint(); paint.setStrokeWidth(1); paint.setColor(Color.parseColor("#ff0000")); paint.setAntiAlias(true); paint.setStyle(Paint.Style.STROKE); canvas.drawCircle(1/2* getWidth(), 1/2 * getHeight(), 50, paint); } } public void refreshTheCircle(ArrayList<MyBallTest> myBallTestList){ this.myBallTestList = myBallTestList; invalidate(); } }
最后,在activity文件中,用线程来更新数据
MainActivity.java
public class MainActivity extends AppCompatActivity { private final String LOG_TAG = "MainActivity"; Handler mHandler; GetListData mGetListData; Button mButtonToTestView2; int i = 0; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final MyTestView myTestView = findViewById(R.id.my_view_test); mButtonToTestView2 = findViewById(R.id.to_testview2); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { myTestView.refreshTheCircle(mGetListData.getTheData()); return false; } }); Thread mThread = new Thread(new Runnable() { @Override public void run() { try { Thread.sleep( 3000); } catch (InterruptedException e) { e.printStackTrace(); } mGetListData = new GetListData(myTestView.getHeight(), myTestView.getWidth()); for (;i ==0 ;){ int j = i % 1000; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } mGetListData.refreshThePosition(); mHandler.sendEmptyMessage(j); } } }); mThread.start(); mButtonToTestView2.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(MainActivity.this, TestView2Activity.class); startActivity(intent); } }); } }
蛛网效果相对于碰撞效果实现起来要简单很多,因为只考虑和边缘的碰撞和小球之间的距离,根据距离来绘制直线,控制透明度。
小球类:
SpiderWebBall.java
public class SpiderWebBall { private int mPositionX; private int mPositionY; private int mSpeedX; private int mSpeedY; private int mRadius; private Paint mPaint; public SpiderWebBall(double mPositionX, double mPositionY, double mSpeedX, double mSpeedY, double mRadius){ this.mPositionX = (int) mPositionX; this.mPositionY = (int) mPositionY; this.mSpeedX = (int) mSpeedX; this.mSpeedY = (int) mSpeedY; this.mRadius = (int) mRadius; mPaint = new Paint(); mPaint.setStyle(Paint.Style.FILL_AND_STROKE); mPaint.setStrokeWidth(1); int color = (int) -(Math.random() * (16777216 - 1) + 1); mPaint.setColor(1677216); //透明度设置要在颜色之后,否则不起作用 mPaint.setAlpha(200); mPaint.setAntiAlias(true); } public void setPositionX(int mPositionX){ this.mPositionX = mPositionX; } public void setPositionY(int mPositionY){ this.mPositionY = mPositionY; } public void setSpeedX(int mSpeedX){ this.mSpeedX = mSpeedX; } public void setSpeedY(int mSpeedY){ this.mSpeedY = mSpeedY; } public Paint getPaint(){ return mPaint; } public int getPositionX(){ return mPositionX; } public int getPositionY(){ return mPositionY; } public int getSpeedX(){ return mSpeedX; } public int getSpeedY(){ return mSpeedY; } public int getRadius(){ return mRadius; } }
辅助类更新数据的时候,除了小球撞到边界反弹之外,还需要实现以下的效果:1、距离较短的小球之间画一条直线 2、小球之间距离越长,线段的透明度越高,距离越短,透明度就越低
SpiderWebBallDataHelper.java
public class SpiderWebBallDataHelper { private final String LOG_TAG = "SpiderWebBallDataHelper"; int mHeightOfView; int mWidthOfView; boolean[][] refreshTheLines = new boolean[150][150]; ArrayList<SpiderWebBall> mBallCrashDataList = new ArrayList<>(); public SpiderWebBallDataHelper(int mHeightOfView, int mWidthOfView){ this.mHeightOfView = mHeightOfView; this.mWidthOfView = mWidthOfView; } //获取球数据的数组 public ArrayList<SpiderWebBall> getListData(){ for (int i = 0; i == 0; ){ SpiderWebBall mBallCrash = getOneBallData(); if (mBallCrash != null){ mBallCrashDataList.add(mBallCrash); } if (mBallCrashDataList.size() == 120){ i = 1; } } for (int i = 0; i < 150; i ++){ for (int j = 0; j < 150; j ++){ refreshTheLines[i][j] = false; } } return mBallCrashDataList; } //获取单独一个球的数据 private SpiderWebBall getOneBallData(){ double positionX = Math.random() * mWidthOfView; double positionY = Math.random() * mHeightOfView; double radius = 1; double speedX = Math.random() * 12 - 6; double speedY = Math.random() * 12 - 6; if (speedX >= 0){ speedX += 2; }else if (speedX < 0){ speedX -= 2; } if (speedY >= 0){ speedY += 2; }else if (speedY < 0){ speedY -= 2; } //初始化时防止其出界 if (positionX + radius > mWidthOfView){ positionX = mWidthOfView - radius; }if (positionX - radius < 0){ positionX = radius; }if (positionY + radius > mHeightOfView){ positionY = mHeightOfView - radius; }if (positionY - radius < 0){ positionY = radius; } SpiderWebBall mBallCrash = new SpiderWebBall(positionX, positionY, speedX, speedY,radius); //初始化时候防止小球与其他小球重合 for (int i = 0; i < mBallCrashDataList.size(); i ++ ){ if ( Math.pow(positionX - mBallCrashDataList.get(i).getPositionX(), 2) + Math.pow(positionY - mBallCrashDataList.get(i).getPositionY(), 2) < Math.pow(radius + mBallCrashDataList.get(i).getRadius(), 2) ){ return null; } } return mBallCrash; } //更新小球的位置数据 public ArrayList<SpiderWebBall> refreshTheBallData(){ for (int i = 0; i < mBallCrashDataList.size(); i ++){ int positionXPast = mBallCrashDataList.get(i).getPositionX(); int positionYPast = mBallCrashDataList.get(i).getPositionY(); int speedXPast = mBallCrashDataList.get(i).getSpeedX(); int speedYPast = mBallCrashDataList.get(i).getSpeedY(); mBallCrashDataList.get(i).setPositionX(mBallCrashDataList.get(i).getSpeedX() + mBallCrashDataList.get(i).getPositionX()); mBallCrashDataList.get(i).setPositionY(mBallCrashDataList.get(i).getSpeedY() + mBallCrashDataList.get(i).getPositionY()); changeSpeedWhenCrashSide(i, positionXPast, positionYPast, speedXPast, speedYPast); } return mBallCrashDataList; } //当碰撞上边界时候反弹 private void changeSpeedWhenCrashSide(int i, int positionXPast, int positionYPast, int speedXPast, int speedYPast){ int positionX = mBallCrashDataList.get(i).getPositionX(); int positionY = mBallCrashDataList.get(i).getPositionY(); int radius = mBallCrashDataList.get(i).getRadius(); int speedX = mBallCrashDataList.get(i).getSpeedX(); int speedY = mBallCrashDataList.get(i).getSpeedY(); if (positionX + radius > mWidthOfView){ mBallCrashDataList.get(i).setPositionX(2 * mWidthOfView - Math.abs(speedX) - radius * 2 - positionXPast); mBallCrashDataList.get(i).setSpeedX(-speedXPast); } if (positionY + radius > mHeightOfView){ mBallCrashDataList.get(i).setPositionY(2 * mHeightOfView - Math.abs(speedY) - radius * 2 - positionYPast); mBallCrashDataList.get(i).setSpeedY(-speedYPast); } if (positionX - radius < 0){ mBallCrashDataList.get(i).setPositionX(Math.abs(speedXPast) - positionXPast + 2 * radius); mBallCrashDataList.get(i).setSpeedX(-speedXPast); } if (positionY - radius < 0){ mBallCrashDataList.get(i).setPositionY(Math.abs(speedYPast) - positionYPast + 2 * radius); mBallCrashDataList.get(i).setSpeedY(-speedYPast); } } public boolean[][] refreshTheLinesData(){ for (int i = 0; i < 150; i ++){ for (int j = 0; j < 150; j++){ refreshTheLines[i][j] = false; } } for (int i = 0; i < mBallCrashDataList.size(); i ++){ for (int j = 0; j < i; j ++){ if (Math.pow(mBallCrashDataList.get(i).getPositionX() - mBallCrashDataList.get(j).getPositionX(), 2) + Math.pow(mBallCrashDataList.get(j).getPositionY()- mBallCrashDataList.get(i).getPositionY(), 2) <= Math.pow(200, 2)){ refreshTheLines[i][j] = true; } } } return refreshTheLines; } }
自定义view:
SpiderWebView.java
public class SpiderWebView extends View { ArrayList<SpiderWebBall> mBallCrashList = new ArrayList<>(); private boolean[][] drawLines = new boolean[150][150]; private int positionTouchX = -1; private int positionTouchY = -1; public SpiderWebView(Context context) { super(context); } public SpiderWebView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } public SpiderWebView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mBallCrashList.size() != 0){ for (int i = 0; i < mBallCrashList.size(); i ++){ canvas.drawCircle(mBallCrashList.get(i).getPositionX(), mBallCrashList.get(i).getPositionY(), mBallCrashList.get(i).getRadius(), mBallCrashList.get(i).getPaint()); if (positionTouchY != -1 && positionTouchX != -1){ if (Math.sqrt(Math.pow( mBallCrashList.get(i).getPositionX() - positionTouchX, 2) + Math.pow(mBallCrashList.get(i).getPositionY() - positionTouchY, 2)) < 200){ Paint paint = new Paint(); paint.setColor(Color.parseColor("#502E3030")); // paint.setStrokeWidth(1); // int color = (int) -(Math.random() * (16777216 - 1) + 1); // paint.setColor(color); paint.setAntiAlias(true); canvas.drawLine(mBallCrashList.get(i).getPositionX(), mBallCrashList.get(i).getPositionY(), positionTouchX,positionTouchY, paint); } } } for (int i = 0; i < mBallCrashList.size(); i ++){ for (int j = 0; j < i; j ++){ if (drawLines[i][j]){ Paint paint = new Paint(); // paint.setAlpha(100); paint.setColor(Color.parseColor("#502E3030")); int alpha = (int) (4000 / Math.sqrt(Math.pow( mBallCrashList.get(i).getPositionY() - mBallCrashList.get(j).getPositionY(), 2) + Math.pow(mBallCrashList.get(i).getPositionX() - mBallCrashList.get(j).getPositionX(), 2))); paint.setAlpha(alpha + 20); Log.i("spiderwebview.java","the alpha is " + alpha); paint.setStrokeWidth(1); // int color = (int) -(Math.random() * (16777216 - 1) + 1); // paint.setColor(color); paint.setAntiAlias(true); canvas.drawLine(mBallCrashList.get(i).getPositionX(), mBallCrashList.get(i).getPositionY(), mBallCrashList.get(j).getPositionX(), mBallCrashList.get(j).getPositionY(), paint); } } } }else { Paint paint = new Paint(); paint.setStyle(Paint.Style.FILL_AND_STROKE); paint.setAlpha(20); paint.setAntiAlias(true); canvas.drawCircle(1/2 * getWidth(), 1/2 * getHeight(), 100,paint); } } public void refreshTheView(ArrayList<SpiderWebBall> mBallCrashList, boolean [][] mDrawLine){ this.mBallCrashList = mBallCrashList; this.drawLines = mDrawLine; invalidate(); } public void setPositionTouchX(int positionTouchX){ this.positionTouchX = positionTouchX; } public void setPositionTouchY(int positionTouchY){ this.positionTouchY = positionTouchY; } }
TestView2Activity.java
public class TestView2Activity extends Activity { SpiderWebView mSpiderWebView; SpiderWebBallDataHelper mDataHelper; Handler mHandler; boolean mStopTheThread = false; Thread mThread; boolean[][] drawLines = new boolean[150][150]; private final String LOG_TAG = "TestView2Activity"; ArrayList<SpiderWebBall> mBallCrashDataList = new ArrayList<>(); @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.test_view2); mSpiderWebView = findViewById(R.id.test_view2_BallCrashView);
//监听视图的触摸移动事件,在手指接触屏幕的地方与较近的小球之间连线 mSpiderWebView.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN){ mSpiderWebView.setPositionTouchX((int) event.getX()); mSpiderWebView.setPositionTouchY((int) event.getY()); }else if (event.getAction() == MotionEvent.ACTION_MOVE){ mSpiderWebView.setPositionTouchX((int) event.getX()); mSpiderWebView.setPositionTouchY((int) event.getY()); }else if (event.getAction() == MotionEvent.ACTION_UP){ mSpiderWebView.setPositionTouchX(-1); mSpiderWebView.setPositionTouchY(-1); } return true; } }); mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { mSpiderWebView.refreshTheView(mBallCrashDataList, drawLines); return false; } }); mThread = new Thread(new Runnable() { @Override public void run() { //睡三秒,否则view没加载出来,传进去的height和width都是0 try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } for (int i = 0; i == 0;){ if (mStopTheThread){ i = 1; } try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } if (mBallCrashDataList.size() == 0 ){ mDataHelper = new SpiderWebBallDataHelper(mSpiderWebView.getHeight(),mSpiderWebView.getWidth()); mBallCrashDataList = mDataHelper.getListData(); }else { mBallCrashDataList = mDataHelper.refreshTheBallData(); drawLines = mDataHelper.refreshTheLinesData(); } mHandler.sendEmptyMessage(0); } } }); mThread.start(); } }
如有bug或建议,欢迎指正。