package com.mgw.member.ui.widget.randomLayout; import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Random; import java.util.Set; import android.content.Context; import android.graphics.Rect; import android.view.View; import android.view.ViewGroup; public class RandomLayout extends ViewGroup { private Random mRdm; /** X分布规则性,该值越高,子view在x方向的分布越规则、平均。最小值为1。 */ private int mXRegularity; /** Y分布规则性,该值越高,子view在y方向的分布越规则、平均。最小值为1。 */ private int mYRegularity; /** 区域个数 */ private int mAreaCount; /** 区域的二维数组 */ private int[][] mAreaDensity; /** 存放已经确定位置的View */ private Set<View> mFixedViews; /** 提供子View的adapter */ private Adapter mAdapter; /** 记录被回收的View,以便重复利用 */ private List<View> mRecycledViews; /** 是否已经layout */ private boolean mLayouted; /** 计算重叠时候的间距 */ private int mOverlapAdd = 2; /** 构造方法 */ public RandomLayout(Context context) { super(context); init(); } /** 初始化方法 */ private void init() { mLayouted = false; mRdm = new Random(); setRegularity(1, 1); mFixedViews = new HashSet<View>(); mRecycledViews = new LinkedList<View>(); } public boolean hasLayouted() { return mLayouted; } /** 设置mXRegularity和mXRegularity,确定区域的个数 */ public void setRegularity(int xRegularity, int yRegularity) { if (xRegularity > 1) { this.mXRegularity = xRegularity; } else { this.mXRegularity = 1; } if (yRegularity > 1) { this.mYRegularity = yRegularity; } else { this.mYRegularity = 1; } this.mAreaCount = mXRegularity * mYRegularity;//个数等于x方向的个数*y方向的个数 this.mAreaDensity = new int[mYRegularity][mXRegularity];//存放区域的二维数组 } /** 设置数据源 */ public void setAdapter(Adapter adapter) { this.mAdapter = adapter; } /** 重新设置区域,把所有的区域记录都归0 */ private void resetAllAreas() { mFixedViews.clear(); for (int i = 0; i < mYRegularity; i++) { for (int j = 0; j < mXRegularity; j++) { mAreaDensity[i][j] = 0; } } } /** 把复用的View加入集合,新加入的放入集合第一个。 */ private void pushRecycler(View scrapView) { if (null != scrapView) { mRecycledViews.add(0, scrapView); } } /** 取出复用的View,从集合的第一个位置取出 */ private View popRecycler() { final int size = mRecycledViews.size(); if (size > 0) { return mRecycledViews.remove(0); } else { return null; } } /** 产生子View,这个就是listView复用的简化版,但是原理一样 */ private void generateChildren() { if (null == mAdapter) { return; } // 先把子View全部存入集合 final int childCount = super.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { pushRecycler(super.getChildAt(i)); } // 删除所有子View super.removeAllViewsInLayout(); // 得到Adapter中的数据量 final int count = mAdapter.getCount(); for (int i = 0; i < count; i++) { //从集合中取出之前存入的子View View convertView = popRecycler(); //把该子View作为adapter的getView的历史View传入,得到返回的View View newChild = mAdapter.getView(i, convertView); if (newChild != convertView) {//如果发生了复用,那么newChild应该等于convertView // 这说明没发生复用,所以重新把这个没用到的子View存入集合中 pushRecycler(convertView); } //调用父类的方法把子View添加进来 super.addView(newChild, new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); } } /** 重新分配区域 */ public void redistribute() { resetAllAreas();//重新设置区域 requestLayout(); } /** 重新更新子View */ public void refresh() { resetAllAreas();//重新分配区域 generateChildren();//重新产生子View requestLayout(); } /** 重写父类的removeAllViews */ @Override public void removeAllViews() { super.removeAllViews();//先删除所有View resetAllAreas();//重新设置所有区域 } /** 确定子View的位置,这个就是区域分布的关键 */ @Override public void onLayout(boolean changed, int l, int t, int r, int b) { final int count = getChildCount(); // 确定自身的宽高 int thisW = r - l - this.getPaddingLeft() - this.getPaddingRight(); int thisH = b - t - this.getPaddingTop() - this.getPaddingBottom(); // 自身内容区域的右边和下边 int contentRight = r - getPaddingRight(); int contentBottom = b - getPaddingBottom(); // 按照顺序存放把区域存放到集合中 List<Integer> availAreas = new ArrayList<Integer>(mAreaCount); for (int i = 0; i < mAreaCount; i++) { availAreas.add(i); } int areaCapacity = (count + 1) / mAreaCount + 1; //区域密度,表示一个区域内可以放几个View,+1表示至少要放一个 int availAreaCount = mAreaCount; //可用的区域个数 for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() == View.GONE) { // gone掉的view是不参与布局 continue; } if (!mFixedViews.contains(child)) {//mFixedViews用于存放已经确定好位置的View,存到了就没必要再次存放 LayoutParams params = (LayoutParams) child.getLayoutParams(); // 先测量子View的大小 int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredWidth(), MeasureSpec.AT_MOST);//为子View准备测量的参数 int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(this.getMeasuredHeight(), MeasureSpec.AT_MOST); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); // 子View测量之后的宽和高 int childW = child.getMeasuredWidth(); int childH = child.getMeasuredHeight(); // 用自身的高度去除以分配值,可以算出每一个区域的宽和高 float colW = thisW / (float) mXRegularity; float rowH = thisH / (float) mYRegularity; while (availAreaCount > 0) { //如果使用区域大于0,就可以为子View尝试分配 int arrayIdx = mRdm.nextInt(availAreaCount);//随机一个list中的位置 int areaIdx = availAreas.get(arrayIdx);//再根据list中的位置获取一个区域编号 int col = areaIdx % mXRegularity;//计算出在二维数组中的位置 int row = areaIdx / mXRegularity; if (mAreaDensity[row][col] < areaCapacity) {// 区域密度未超过限定,将view置入该区域 int xOffset = (int) colW - childW; //区域宽度 和 子View的宽度差值,差值可以用来做区域内的位置随机 if (xOffset <= 0) { xOffset = 1; } int yOffset = (int) rowH - childH; if (yOffset <= 0) { yOffset = 1; } // 确定左边,等于区域宽度*左边的区域 params.mLeft = getPaddingLeft() + (int) (colW * col + mRdm.nextInt(xOffset)); int rightEdge = contentRight - childW; if (params.mLeft > rightEdge) {//加上子View的宽度后不能超出右边界 params.mLeft = rightEdge; } params.mRight = params.mLeft + childW; params.mTop = getPaddingTop() + (int) (rowH * row + mRdm.nextInt(yOffset)); int bottomEdge = contentBottom - childH; if (params.mTop > bottomEdge) {//加上子View的宽度后不能超出右边界 params.mTop = bottomEdge; } params.mBottom = params.mTop + childH; if (!isOverlap(params)) {//判断是否和别的View重叠了 mAreaDensity[row][col]++;//没有重叠,把该区域的密度加1 child.layout(params.mLeft, params.mTop, params.mRight, params.mBottom);//布局子View mFixedViews.add(child);//添加到已经布局的集合中 break; } else {//如果重叠了,把该区域移除, availAreas.remove(arrayIdx); availAreaCount--; } } else {// 区域密度超过限定,将该区域从可选区域中移除 availAreas.remove(arrayIdx); availAreaCount--; } } } } mLayouted = true; } /** 计算两个View是否重叠,如果重叠,那么他们之间一定有一个矩形区域是共有的 */ private boolean isOverlap(LayoutParams params) { int l = params.mLeft - mOverlapAdd; int t = params.mTop - mOverlapAdd; int r = params.mRight + mOverlapAdd; int b = params.mBottom + mOverlapAdd; Rect rect = new Rect(); for (View v : mFixedViews) { int vl = v.getLeft() - mOverlapAdd; int vt = v.getTop() - mOverlapAdd; int vr = v.getRight() + mOverlapAdd; int vb = v.getBottom() + mOverlapAdd; rect.left = Math.max(l, vl); rect.top = Math.max(t, vt); rect.right = Math.min(r, vr); rect.bottom = Math.min(b, vb); if (rect.right >= rect.left && rect.bottom >= rect.top) { return true; } } return false; } /** 内部类、接口 */ public static interface Adapter { public abstract int getCount(); public abstract View getView(int position, View convertView); } public static class LayoutParams extends ViewGroup.LayoutParams { private int mLeft; private int mRight; private int mTop; private int mBottom; public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(int w, int h) { super(w, h); } } }