实现碰撞ViewGroup,任何其中的子view都可以实现碰撞移动

本文介绍如何创建一个自定义的ViewGroup,使得其中的每个子view都能实现碰撞后自动移动的效果。通过分析碰撞原理,创建Helper类存储子view的移动参数,并在ViewGroup的onLayout中不断更新子view的位置信息。最后展示了XML布局文件的使用方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

上一篇博客已经实现了一个固定view来展示碰撞算法—简单碰撞算法及其demo(屏幕气泡原理),今天做了一个相对负责的ViewGroup,即放在其中的每个view都可以自动去”飘动”!为其子view实现碰撞算法!
下面先看一下实现后的效果!
这里写图片描述

原理分析:
1.需自定义一个ViewGroup来存放所有的子view,但是每个子view的移动角度移动速度移动边界等信息是不同的,所以需要自定义一个helper类来存储每一个子view的移动范围,速度大小和速度方向以及位置信息!
2.根据子view的个数,在ViewGroup中新建helper对象.
3.不断更新每个子view的位置信息,在ViewGroup的onlayout中不断更新位置

代码实现过程:
1.新建一个ViewGroup来存放所有的子view

package com.example.automoveviewgroup;

import java.util.ArrayList;
import java.util.List;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
import android.util.AttributeSet;
import android.view.View;
import android.widget.RelativeLayout;

@SuppressLint("DrawAllocation")
public class AutoMoveViewGroup extends RelativeLayout {

    private List<AutoMoveHelper> mAutoMoveHelpers;// 用于存放每一个子view对应的helper类

    public AutoMoveViewGroup(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init();
    }

    public AutoMoveViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public AutoMoveViewGroup(Context context) {
        this(context, null);
    }

    private void init() {
        mAutoMoveHelpers = new ArrayList<>();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int wideMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);

        int width, height;
        if (wideMode == MeasureSpec.AT_MOST) {
            width = 600;
        } else {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }

        if (heightMode == MeasureSpec.AT_MOST) {
            height = 600;
        } else {
            height = MeasureSpec.getSize(heightMeasureSpec);
        }

        setMeasuredDimension(width, height);
    }

    @Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        requestLayout();
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        int childCount = getChildCount();
        if (mAutoMoveHelpers.size() != childCount) {
            mAutoMoveHelpers.clear();
            for (int i = 0; i < childCount; i++) {
                View view = getChildAt(i);
                mAutoMoveHelpers.add(new AutoMoveHelper(l, t, r - view.getWidth(), b - view.getHeight(), 0, null));
            }

        }

        for (int i = 0; i < childCount; i++) {
            View view = getChildAt(i);
            AutoMoveHelper helper = mAutoMoveHelpers.get(i);
            Point point = helper.getPoint();
            view.layout(point.x, point.y, point.x + view.getWidth(), point.y + view.getHeight());
            helper.updatePoint();
        }
    }
}

代码很简单,新建了一个list用于存放每一个子view对应的helper对象,然后在onMeasure中设置ViewGroup的大小(当宽高值为wrap_content时设置对应值为600像素),在onlayout中,①判断子view的个数是否和helper的数量一致,如果不一致则根据子view的个数新建helper并存储在list对象中.②根据子view对应的helper中的位置信息point来确定当前子view的v位置,取完位置信息后更新下一次的位置信息

2.新建helper类,存储子view对应的信息

package com.example.automoveviewgroup;

import java.util.Random;

import android.graphics.Point;

public class AutoMoveHelper {

    private int left, top, right, bottom;// 该边界表示子view的左上角的边界值
    private Point point;// 该值表示对应子view的当前的左上角的值
    private double speedX, speedY;// 表示子view分别在X,Y轴上的移动速度(speed*100/s)
    private boolean isXUp, isYUp;// 用来表示在X,Y轴上的移动方向

    public AutoMoveHelper(int l, int t, int r, int b, int speed, Point p) {
        Random random = new Random();

        if (l > r || t > b) {
            throw new RuntimeException("set wrong bounds");
        }

        this.left = l;
        this.right = r;
        this.top = t;
        this.bottom = b;

        /**
         * 确定view的移动速度
         */

        // 给x,y轴上的速度赋值
        if (speed <= 0) {
            speed = random.nextInt(3) + 1;
        }
        // 确定速度的角度
        double nextDouble = random.nextDouble() * 50 + 20;//速度角度定在20-70度之前
        this.speedX = speed * Math.cos(nextDouble * 2 * Math.PI / 360) + 1;
        this.speedY = speed * Math.sin(nextDouble * 2 * Math.PI / 360) + 1;

        if (p != null && p.x > left && p.x < r && p.y > t && p.y < b) {
            this.point = p;
        } else {
            this.point = new Point();
            point.x = random.nextInt(r - l) + l;
            point.y = random.nextInt(b - t) + t;
        }

        int nextInt1 = random.nextInt(2);
        if (nextInt1 == 0) {
            isXUp = false;
        } else {
            isXUp = true;
        }

        int nextInt2 = random.nextInt(2);
        if (nextInt2 == 0) {
            isYUp = false;
        } else {
            isYUp = true;
        }

    }

    public AutoMoveHelper() {
        this(0, 0, 0, 0, 5, null);
    }

    public void setBounds(int l, int t, int r, int b) {
        this.left = l;
        this.right = r;
        this.top = t;
        this.bottom = b;
    }

    public int getLeft() {
        return left;
    }

    public void setLeft(int left) {
        this.left = left;
    }

    public int getTop() {
        return top;
    }

    public void setTop(int top) {
        this.top = top;
    }

    public int getRight() {
        return right;
    }

    public void setRight(int right) {
        this.right = right;
    }

    public int getBottom() {
        return bottom;
    }

    public void setBottom(int bottom) {
        this.bottom = bottom;
    }

    public Point getPoint() {
        return point;
    }

    public void setPoint(Point point) {
        this.point = point;
    }

    public double getSpeedX() {
        return speedX;
    }

    public void setSpeedX(int speedX) {
        this.speedX = speedX;
    }

    public double getSpeedY() {
        return speedY;
    }

    public void setSpeedY(int speedY) {
        this.speedY = speedY;
    }

    public boolean isXUp() {
        return isXUp;
    }

    public void setXUp(boolean isXUp) {
        this.isXUp = isXUp;
    }

    public boolean isYUp() {
        return isYUp;
    }

    public void setYUp(boolean isYUp) {
        this.isYUp = isYUp;
    }

    public void updatePoint() {//根据速度值更新下一次的位置信息
        if (point.x <= left) {
            isXUp = true;
        } else if (point.x >= right) {
            isXUp = false;
        }
        if (isXUp) {
            point.x = (int) (point.x + speedX);
        } else {
            point.x = (int) (point.x - speedX);
        }

        // 判断y轴方向上的坐标
        if (point.y <= top) {
            isYUp = true;
        } else if (point.y >= bottom) {
            isYUp = false;
        }

        if (isYUp) {
            point.y = (int) (point.y + speedY);
        } else {
            point.y = (int) (point.y - speedX);
        }
    }

}

该部分代码处理逻辑基本上跟上一篇博客内容相似,不做解释了,唯一不同这里对初始位置和速度信息做了一个随机生成

3.然后就可以使用我们做好的ViewGroup了
xml文件

<com.example.automoveviewgroup.AutoMoveViewGroup xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/white"
    tools:context="com.example.automoveviewgroup.MainActivity"
    tools:ignore="MergeRootFrame" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:text="textview1" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@android:color/darker_gray"
        android:text="textview2" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima1" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima1" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima2" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima2" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima3" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima3" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima4" />

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/ima4" />

    <ImageView
        android:id="@+id/run"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/run" />

</com.example.automoveviewgroup.AutoMoveViewGroup>

源码点击下载

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值