Android自定义随机项目布局View

10 篇文章 0 订阅

最近项目需要用到一个如图所述的效果,就是在一定的范围内进行随机添加布局,随机添加的项目不能重复。

然后我抽取了需求列表

1、项目随机

2、区间限制

3、禁止覆盖

实现思路:

一、我们需要随机一个XY坐标,这个坐标有以下几个条件

1、范围被限制在随机布局内,不能越界

2、XY坐标必须处理布局有填充的情况,不然会被覆盖

3、XY坐标必须考虑到随机View项目的面积问题

由此总结计算公式

WH = 宽高

注意,宽高必须先计算,例如 

view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);

当随机项目View宽高计算完成后,才可以进行随机坐标的获取,不然将会获得错误的计算结果!

XY = RANDOM(LayoutWH - ViewWH - PADDING)

其中RANDOM()是标准区间随机函数,我们只需要计算X到LEFT以及Y到BOTTOM的MAX值就行

如上图指向,从左上角顶点计算XY时

( X坐标向左 + 项目View的Width )  必须小于 随机Layout - PaddingStart - PaddingEnd的值,不然会越界

Y轴坐标的计算思路也一样。

处理完了随机坐标,我们需要防止覆盖,这里不能使用 contains(int x, int y)判断是否包含(x,y)点

应当使用 

Rect.intersects(v1Rect, v2Rect)

此静态方法进行矩形碰撞的测试,contains() 方法仅能单向判断A的XY是否包含了B的XY,也就

,如果我们需要判断碰撞,则不能使用此方法,因为此方法会忽略不完全包含但是碰撞存在的情况,这明显不符合我们的需求。

 

*********************************************************************************************************************************************

问题总结:

1、View的长宽确定很重要,记得先测量宽度,再进行XY获取与设置操作。

2、Rect类提供了强大的函数支持,但是一定要读懂API。

3、View的加载具有一定的延迟,记得在View加载完毕后再进行相关操作。

 

实现后的源码:

/**
 * 可以添加随机位置View的布局!
 * 主要的思路是:
 * 1、位置随机
 * 2、防止覆盖
 */
public class RandomLayout<T> extends RelativeLayout {
    /**
     * 此列表用于保存随机的View视图
     * 在添加随机View的时候应当判断此视图是否有覆盖的
     * 有的话应该重新进行随机!
     */
    private ArrayList<View> randomViewList = new ArrayList<>();
    private OnRandomItemClickListener<T> onRandomItemClickListener;
    private OnRandomItemLongClickListener<T> onRandomItemLongClickListener;
    private boolean itemClickable = true;
    private boolean itemLongClickable = true;

    public RandomLayout(Context context) {
        super(context);
    }

    public RandomLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /**
     * 添加到一个随机的XY位置,且不重复。
     */
    public void addViewAtRandomXY(View view, T t) {
        if (view == null) return;
        view.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED);
        post(new Runnable() {
            @Override
            public void run() {
                randomViewList.remove(view);
                // 100次随机上限
                for (int i = 0; i < 100; i++) {
                    int[] xy = createXY(
                            view.getMeasuredHeight(),
                            view.getMeasuredWidth()
                    );
                    if (randomViewList.size() == 0) {
                        addViewAndSetXY(view, xy[0], xy[1], t);
                    } else {
                        boolean isRepeat = false;
                        // 迭代已经存在的View,判断是否重叠!
                        for (View subView : randomViewList) {
                            // 得到XY
                            int x = (int) subView.getX();
                            int y = (int) subView.getY();
                            int width = subView.getMeasuredWidth();
                            int height = subView.getMeasuredHeight();
                            // 创建矩形
                            Rect v1Rect = new Rect(x, y, width + x, height + y);
                            Rect v2Rect = new Rect(
                                    xy[0], xy[1],
                                    view.getMeasuredWidth() + xy[0],
                                    view.getMeasuredHeight() + xy[1]
                            );
                            if (Rect.intersects(v1Rect, v2Rect)) {
                                isRepeat = true;
                                break;
                            }
                        }
                        if (!isRepeat) {
                            addViewAndSetXY(view, xy[0], xy[1], t);
                            return;
                        }
                    }
                }
            }
        });
    }

    private void addViewAndSetXY(View view, int x, int y, T t) {
        removeView(view);
        addView(view);
        randomViewList.add(view);
        view.setX(x);
        view.setY(y);
        // 设置单击事件!
        view.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                if (onRandomItemClickListener != null && isItemClickable()) {
                    onRandomItemClickListener.onRandomItemClick(v, t);
                }
            }
        });
        // 设置长按事件!
        view.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View v) {
                if (onRandomItemLongClickListener != null && itemLongClickable)
                    return onRandomItemLongClickListener.onRandomItemLongClick(v, t);
                return false;
            }
        });
    }

    /**
     * 添加一个View到随机列表中,以此达到防止覆盖的效果!
     */
    public void addViewToRandomList(View view) {
        randomViewList.add(view);
    }

    /**
     * 清除所有的随机视图!
     */
    public void removeAllRandomView() {
        for (View v : randomViewList) {
            removeView(v);
        }
        randomViewList.clear();
    }

    /**
     * 从列表中移除一个随机视图!
     */
    public void removeRandomViewFromList(View view) {
        randomViewList.remove(view);
    }

    /**
     * 随机生成一个 0 到指定区间的值!
     *
     * @param max 0到max但是不包括max
     * @return 同上
     */
    private int random(int max) {
        // LogUtils.d("Max是:" + max);
        return new Random().nextInt(max);
    }

    /**
     * 根据传入的宽和高返回一个随机的坐标!
     */
    private int[] createXY(int height, int width) {
        int[] xyRet = new int[]{0, 0};
        // 初始化我们当前布局的屏幕XY!
        int layoutHeight = getMeasuredHeight();
        int layoutWidth = getMeasuredWidth();
        // 先随机一个X,注意一下就是,X轴是从View的左向右延申的
        // 注意,要减去内部填充!!!
        // LogUtils.d("paddingEnd: " + paddingEnd);
        xyRet[0] = random(
                layoutWidth - (
                        width + getPaddingStart() + getPaddingEnd()
                )
        );
        // LogUtils.d(" 布局宽度:" + layoutWidth + ",X轴:" + xyRet[0] + ",最终宽度:" + (xyRet[0] + width + paddingEnd + paddingStart));
        // 然后从Y是从View的上向下延申,所以我们需要进行下限值限制,避免越界!
        xyRet[1] = random(
                layoutHeight - (
                        height + getPaddingBottom() + getPaddingTop()
                )
        );
        return xyRet;
    }

    public boolean isItemClickable() {
        return itemClickable;
    }

    public void setItemClickable(boolean itemClickable) {
        this.itemClickable = itemClickable;
    }

    public boolean isItemLongClickable() {
        return itemLongClickable;
    }

    public void setItemLongClickable(boolean itemLongClickable) {
        this.itemLongClickable = itemLongClickable;
    }

    public OnRandomItemClickListener getOnRandomItemClickListener() {
        return onRandomItemClickListener;
    }

    public void setOnRandomItemClickListener(OnRandomItemClickListener<T> onRandomItemClickListener) {
        this.onRandomItemClickListener = onRandomItemClickListener;
    }

    public OnRandomItemLongClickListener<T> getOnRandomItemLongClickListener() {
        return onRandomItemLongClickListener;
    }

    public void setOnRandomItemLongClickListener(OnRandomItemLongClickListener<T> onRandomItemLongClickListener) {
        this.onRandomItemLongClickListener = onRandomItemLongClickListener;
    }

    public interface OnRandomItemClickListener<T> {
        void onRandomItemClick(View view, T t);
    }

    public interface OnRandomItemLongClickListener<T> {
        boolean onRandomItemLongClick(View view, T t);
    }
}

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值