最近项目需要用到一个如图所述的效果,就是在一定的范围内进行随机添加布局,随机添加的项目不能重复。
然后我抽取了需求列表
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);
}
}