├── .gitignore ├── AndroidManifest.xml ├── README.md ├── assets └── GIF.gif ├── ic_launcher-web.png ├── libs ├── android-support-v4.jar └── nineoldandroids-2.4.0.jar ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── drawable-xxhdpi │ └── ic_launcher.png ├── layout │ └── activity_main.xml ├── menu │ └── main.xml ├── values-sw600dp │ └── dimens.xml ├── values-sw720dp-land │ └── dimens.xml ├── values-v11 │ └── styles.xml ├── values-v14 │ └── styles.xml └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── src └── com └── special └── animatedrandomlayout ├── .gitignore ├── activity ├── ColorUtil.java ├── LogUtil.java ├── MainActivity.java └── ToastUtil.java └── random_layout ├── AnimatedRandomLayout.java ├── AnimatorUtil.java ├── ChildViewBound.java └── GeometryUtil.java /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | /bin 4 | /gen -------------------------------------------------------------------------------- /AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AnimatedRandomLayout 2 | 3 | 本布局实现了在屏幕上随机生成可供操作的子控件控件,并完成向中心移动的随控件出现位置, 4 | 动态设定的动画效果。 5 | 6 | #

SimpleExample: 7 | 8 | 9 | #

可操控参数: 10 | 11 | 随机生成的周期 setLooperDuration(int mLooperDuration) 12 | 最大动画时长 setDefaultDruation(int mDefaultDruation) 13 | 同一时刻随机生成控件的最大个数 setItemShowCount(int itemShowCount) 14 | 随机控件分布网格空间大小 setRegularity(int xRegularity, int yRgularity) 15 | 随机控件总数和显示(类似Adapter)setOnCreateItemViewListener(OnCreateItemViewListener itemViewListener) 16 | 17 | #

子控件特点: 18 | 19 | 对于子控件没有确切的要求,只要是控件父类为 View 类,就可以使用本随机布局添加显示。 20 | 21 | #

注意: 22 | 23 | 如果期望修改动画,可以先自定义动画,随后修改com.special.simplecloudview.random_layout 24 | 文档中CloudRandomLayout.java(即,布局的所在文件)的startZoomAnimation方法即可。 25 | 26 | -------------------------------------------------------------------------------- /assets/GIF.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/assets/GIF.gif -------------------------------------------------------------------------------- /ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/ic_launcher-web.png -------------------------------------------------------------------------------- /libs/android-support-v4.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/libs/android-support-v4.jar -------------------------------------------------------------------------------- /libs/nineoldandroids-2.4.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/libs/nineoldandroids-2.4.0.jar -------------------------------------------------------------------------------- /proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-18 15 | -------------------------------------------------------------------------------- /res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Windsander/AnimatedRandomLayout/130322248d4fe5cd8d134cac44fe321fa15ce3a2/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | -------------------------------------------------------------------------------- /res/menu/main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /res/values-sw720dp-land/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 128dp 8 | 9 | -------------------------------------------------------------------------------- /res/values-v11/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /res/values-v14/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 16dp 5 | 16dp 6 | 7 | -------------------------------------------------------------------------------- /res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | AnimatedRandomLayout 5 | Settings 6 | Hello world! 7 | 8 | -------------------------------------------------------------------------------- /res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 14 | 15 | 16 | 19 | 20 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/.gitignore: -------------------------------------------------------------------------------- 1 | /early_backup 2 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/activity/ColorUtil.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.activity; 2 | 3 | import java.util.Random; 4 | 5 | import android.graphics.Color; 6 | 7 | public class ColorUtil { 8 | /** 9 | * 随机生成漂亮的颜色 10 | * @return 11 | */ 12 | public static int randomColor(){ 13 | Random random = new Random(); 14 | //如果值太大,会偏白,太小则会偏黑,所以需要对颜色的值进行范围限定 15 | int red = random.nextInt(150)+50;//50-199 16 | int green = random.nextInt(150)+50;//50-199 17 | int blue = random.nextInt(150)+50;//50-199 18 | return Color.rgb(red, green, blue);//根据rgb混合生成一种新的颜色 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/activity/LogUtil.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.activity; 2 | 3 | import android.util.Log; 4 | 5 | public class LogUtil { 6 | 7 | private static boolean isDebug = true; 8 | 9 | public static void LOGW(String tag, String str){ 10 | if(isDebug){ 11 | Log.w(tag, str); 12 | } 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.activity; 2 | 3 | import java.util.Arrays; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import android.app.Activity; 8 | import android.os.Bundle; 9 | import android.util.TypedValue; 10 | import android.view.View; 11 | import android.view.View.OnClickListener; 12 | import android.view.Window; 13 | import android.widget.TextView; 14 | 15 | import com.special.animatedrandomlayout.random_layout.AnimatedRandomLayout; 16 | import com.special.animatedrandomlayout.random_layout.AnimatedRandomLayout.OnCreateItemViewListener; 17 | import com.special.animatedrandomlayout.R; 18 | 19 | public class MainActivity extends Activity { 20 | 21 | private List list; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | requestWindowFeature(Window.FEATURE_NO_TITLE); 27 | setContentView(R.layout.activity_main); 28 | AnimatedRandomLayout cloudRandomLayout = (AnimatedRandomLayout) findViewById(R.id.rl_cloud); 29 | 30 | 31 | String[] str = {"1","2","3","4","5","6","7","8","9","10","a","b","c","d","e","f","g","h","i", 32 | "j","k","l","m","n","o","p","r","s","u","v","w","x","y","z"}; 33 | list = Arrays.asList(str); 34 | 35 | 36 | cloudRandomLayout.setRegularity(15, 15); 37 | cloudRandomLayout.setItemShowCount(2); 38 | cloudRandomLayout.setLooperDuration(10); 39 | cloudRandomLayout.setDefaultDruation(20000); 40 | cloudRandomLayout.setOnCreateItemViewListener(new OnCreateItemViewListener() { 41 | 42 | @Override 43 | public int getCount() { 44 | return list.size(); 45 | } 46 | 47 | @Override 48 | public View createItemView(int position, View convertView) { 49 | final TextView textView = new TextView(getApplicationContext()); 50 | //1.设置文本数据 51 | int listPosition = position; 52 | textView.setText(list.get(listPosition) + ""); 53 | //2.设置随机的字体 54 | Random random = new Random(); 55 | textView.setTextSize(TypedValue.COMPLEX_UNIT_SP,random.nextInt(8)+24);//14-21 56 | //3.上色,设置随机字体颜色 57 | textView.setTextColor(ColorUtil.randomColor()); 58 | //4.设置点击事件 59 | textView.setOnClickListener(new OnClickListener() { 60 | @Override 61 | public void onClick(View v) { 62 | ToastUtil.showToast(getApplicationContext(), textView.getText().toString()); 63 | } 64 | }); 65 | 66 | return textView; 67 | } 68 | }); 69 | 70 | cloudRandomLayout.start(); 71 | 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/activity/ToastUtil.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.activity; 2 | 3 | import android.content.Context; 4 | import android.widget.Toast; 5 | 6 | public class ToastUtil { 7 | private static Toast toast; 8 | public static void showToast(Context context, String text){ 9 | if(toast==null){ 10 | toast = Toast.makeText(context,text,Toast.LENGTH_SHORT); 11 | } 12 | toast.setText(text); 13 | toast.show(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/random_layout/AnimatedRandomLayout.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.random_layout; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | 7 | import android.annotation.SuppressLint; 8 | import android.content.Context; 9 | import android.graphics.Point; 10 | import android.os.Handler; 11 | import android.os.Message; 12 | import android.util.AttributeSet; 13 | import android.view.View; 14 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 15 | import android.widget.FrameLayout; 16 | 17 | import com.nineoldandroids.animation.Animator; 18 | import com.nineoldandroids.animation.AnimatorListenerAdapter; 19 | import com.nineoldandroids.view.ViewPropertyAnimator; 20 | import com.special.animatedrandomlayout.activity.LogUtil; 21 | 22 | /** 23 | * RandomLayout,其主要目的是为了按要求实现随机布局。 24 | * @Using-Step 25 | * setRegularity
26 | * -> setItemShowCount
27 | * -> setLooperDuration
28 | * -> setDefaultDruation
29 | * -> setOnCreateItemViewListener( getCount ; createItemView ) 30 | * @attention 31 | * 使用此随机布局,必须重写{@link OnCreateItemViewListener}回调接口的{@link createItemView} 32 | * 方法,来定义用于随机布局的子控件 33 | * @author Windsander 34 | * 35 | */ 36 | @SuppressLint("HandlerLeak") 37 | public class AnimatedRandomLayout extends FrameLayout { 38 | 39 | //参数声明/**************************************************************************************/ 40 | /** 用于生成随机偏移量的Random对象 */ 41 | private Random mRandom; 42 | 43 | //矩阵 与 布局参数计算相关变量=================================================== 44 | /** 区域的二维数组,即密度矩阵 */ 45 | private int[][] mAreaDensity; 46 | /** X分布规则性,该值越高,子view在x方向的分布越规则、平均。最小值为1。 */ 47 | private int mXRegularity; 48 | /** Y分布规则性,该值越高,子view在y方向的分布越规则、平均。最小值为1。 */ 49 | private int mYRegularity; 50 | /** 记录当前密度下,用于放置View的区块数量 */ 51 | private int mAreaNum; 52 | 53 | //当前布局子控件的动画控制参数=================================================== 54 | /** 记录需要开启动画的新加入的子控件 */ 55 | private List justInitChilds; 56 | /** 记录当前布局中心位置 */ 57 | private Point mCenter; 58 | /** 记录当前布局对角线半径 */ 59 | private float mDiagonalLength; 60 | /** 记录动画最长持续时间 ,默认为 2000*/ 61 | private int mDefaultDruation = 2000; 62 | /** 记录子控件自动生成时间间隔,默认为 1000 */ 63 | private int mLooperDuration = 1000; 64 | 65 | //当前布局子控件的细节控制参数=================================================== 66 | /** 同一时刻,被展示到控件上的 子View 个数最大值 */ 67 | private int mItemShowCount = 1; 68 | /** 计算重叠时候的子控件安全间距 */ 69 | private int mOverlapAdd = 2; 70 | /** 存放打算让云布局显示的子控件总数 */ 71 | private int mTotalViewNum; 72 | 73 | //当前显示记录器 与 缓存复用记录器================================================ 74 | /** 用于存放以分配了位置显示的 View,仅仅用于检测是否显示 */ 75 | private List mFixedViews; 76 | /** 存放可用区块ID */ 77 | private List availAreas; 78 | /** 用于存放被回收了的View,便于复用 */ 79 | private List mRecycledViews; 80 | /** 布局完成状态记录 */ 81 | private boolean mIsLayout = false; 82 | 83 | //Handler循环生成当前时刻子控件================================================ 84 | private Handler handler; 85 | { 86 | handler = new Handler(){ 87 | @Override 88 | public void handleMessage(Message msg) { 89 | super.handleMessage(msg); 90 | loopChild(); 91 | } 92 | }; 93 | } 94 | 95 | 96 | //构造方法/**************************************************************************************/ 97 | public AnimatedRandomLayout(Context context) { 98 | this(context, null); 99 | } 100 | 101 | public AnimatedRandomLayout(Context context, AttributeSet attrs) { 102 | this(context, attrs, -1); 103 | } 104 | 105 | public AnimatedRandomLayout(Context context, AttributeSet attrs, int defStyle) { 106 | super(context, attrs, defStyle); 107 | init(); 108 | } 109 | 110 | //初始化方法/*************************************************************************************/ 111 | private void init() { 112 | mRandom = new Random(); 113 | setRegularity(1, 1); //避免 NullPointerException 114 | 115 | mFixedViews = new ArrayList(); 116 | mRecycledViews = new ArrayList(); 117 | 118 | availAreas = new ArrayList(mAreaNum); 119 | resetAvailAreas(); 120 | 121 | mCenter = new Point(); 122 | 123 | } 124 | 125 | //测量与构建/*************************************************************************************/ 126 | /** 127 | * 用于开启循环展示 128 | */ 129 | public void start(){ 130 | removeAllViews(); 131 | mTotalViewNum = onCreateItemViewListener.getCount(); 132 | justInitChilds = new ArrayList(); 133 | //定义子控件出现的时间间隔 134 | loopChild(); 135 | } 136 | 137 | private void loopChild() { 138 | //初始化布局界面 139 | resetPanelForChild(); 140 | //生成子控件 141 | generateChild(); 142 | //在生成孩子布局完成后,开始动画 143 | getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 144 | @SuppressWarnings("deprecation") 145 | @Override 146 | public void onGlobalLayout() { 147 | getViewTreeObserver().removeGlobalOnLayoutListener(this); 148 | 149 | startZoomAnimation(); 150 | } 151 | }); 152 | handler.sendEmptyMessageDelayed(0, mLooperDuration); 153 | } 154 | 155 | /** 156 | * 根据设定生成 展示用子View,根据设定的同时可出现的子控件上限,来动态生成子控件 157 | */ 158 | private void generateChild(){ 159 | if(onCreateItemViewListener == null){ 160 | return; 161 | } 162 | //fixedViewCount用于存放已经显示在当前布局的View的个数 163 | int fixedViewCount = mFixedViews.size(); 164 | int count = fixedViewCount + mRandom.nextInt(mItemShowCount); 165 | LogUtil.LOGW("tag", "count:"+count); //TODO 166 | for (int i = count-1; i >= fixedViewCount; i--) { 167 | View convertView = popRecycler(); 168 | View newChild = onCreateItemViewListener.createItemView(i % mTotalViewNum, convertView); 169 | //判断是否发生复用,如果没发生,则存入当前View 170 | if(newChild != convertView){ 171 | pushRecycler(convertView); 172 | } 173 | ChildViewBound params = new ChildViewBound( 174 | LayoutParams.WRAP_CONTENT, 175 | LayoutParams.WRAP_CONTENT); 176 | newChild.setLayoutParams(params); 177 | //因为我们使用的是 TextView 作为子控件,因此,使用其自带的测量方法即可 178 | addView(newChild); 179 | justInitChilds.add(newChild); 180 | } 181 | 182 | } 183 | 184 | /* @Override 185 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 186 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 187 | //测量每个子控件,以便于后续使用 188 | int childNum = this.getChildCount(); 189 | for (int i = 0; i < childNum; i++) { 190 | int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(widthMeasureSpec), MeasureSpec.AT_MOST); 191 | int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(MeasureSpec.getSize(heightMeasureSpec), MeasureSpec.AT_MOST); 192 | this.getChildAt(i).measure(childWidthMeasureSpec, childHeightMeasureSpec); 193 | } 194 | }*/ 195 | 196 | @Override 197 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 198 | int childNum = this.getChildCount(); 199 | //确定可供显示的区域大小 200 | int thisW = r - l - this.getPaddingLeft() - this.getPaddingRight(); 201 | int thisH = b - t - this.getPaddingTop() - this.getPaddingBottom(); 202 | //计算当前布局中心 203 | mCenter.x = thisW / 2; 204 | mCenter.y = thisH / 2; 205 | //计算当前布局对角线长度 206 | mDiagonalLength = GeometryUtil.getDistanceBetween2Points( 207 | l - this.getPaddingLeft(), b - this.getPaddingBottom(), 208 | mCenter); 209 | //获取每个区块的宽高 210 | float blockW = thisW / (float) mXRegularity; 211 | float blockH = thisH / (float) mYRegularity; 212 | 213 | //重置可用控件列表 214 | resetAvailAreas(); 215 | 216 | //计算区块容积,容积至少唯一,当区块数小于子控件数时,容积 > 1 217 | int blockCapacity = ((childNum + 1) / mAreaNum) + 1 ; 218 | 219 | int availAreaNum = mAreaNum; 220 | for (int i = 0; i < childNum; i++) { 221 | //获取子控件,并检测显示情况,当为GONE时,则不用安排布局 222 | View child = this.getChildAt(i); 223 | child.measure(0, 0); 224 | 225 | if(child.getVisibility() == View.GONE){ 226 | continue; 227 | } 228 | //检测子控件是否已经分配了位置,没有时才进行位置分配 229 | if(!mFixedViews.contains(child)){ 230 | int childW = child.getMeasuredWidth(); 231 | int childH = child.getMeasuredHeight(); 232 | // 求得子控件左上角的取值上限 233 | int leftEdge = r - getPaddingRight() - childW; 234 | int topEdge = b - getPaddingBottom() - childH; 235 | 236 | //位置分配:直到确实没有空间可供使用之前,随机寻找子控件存放位置 237 | while(availAreaNum > 0){ 238 | //计算随机块,用于存放当前View 239 | int availId = mRandom.nextInt(availAreaNum); //从可用区块列表中,获取随机值对应的区块编号 240 | int childPositionId = availAreas.get(availId); 241 | int pRow = childPositionId / mXRegularity; 242 | int pCol = childPositionId % mXRegularity; 243 | 244 | //为了保证每个区块充分使用,进行容量判断 245 | if(mAreaDensity[pCol][pRow] < blockCapacity){ 246 | //计算区块空余 247 | int xOffset = childInBlockOffestX((int) blockW, childW); 248 | int yOffset = childInBlockOffsetY((int) blockH, childH); 249 | 250 | //这里的 LayoutParams 仅仅为自定义,仅仅是为了在满足要求下,能对应保存参数数据 251 | ChildViewBound newChildBound = (ChildViewBound) child.getLayoutParams(); 252 | int childLeft = (int) (pCol * blockW) + this.getPaddingLeft() + xOffset; 253 | childLeft = Math.min(childLeft, leftEdge); 254 | int childTop = (int) (pRow * blockH) + this.getPaddingTop() + yOffset; 255 | childTop = Math.min(childTop, topEdge); 256 | int childRight = childLeft + childW; 257 | int childBottom = childTop + childH; 258 | newChildBound.setChildViewBound( 259 | childLeft, childTop, childRight, childBottom); 260 | 261 | //判断是否发生重叠,如果没有发生重叠,则布局并记录,否则重新计算位置 262 | // if(!isOverLap(newChildBound)){ 263 | child.setLayoutParams(newChildBound); 264 | child.layout(childLeft, childTop, childRight, childBottom); 265 | LogUtil.LOGW("tag", "layout!!!!!!!!!!!!!" + childLeft + 266 | " availAreaNum:" + availAreaNum);//TODO 267 | mFixedViews.add(child); 268 | mAreaDensity[pCol][pRow]++; 269 | //已完成当前View的布局,跳出随机布局循环 270 | break; 271 | // } 272 | // else{ 273 | // availAreas.remove((Integer)childPositionId); 274 | // availAreaNum--; 275 | // } 276 | }else{ 277 | availAreas.remove((Integer)childPositionId); 278 | availAreaNum--; 279 | } 280 | } 281 | } 282 | } 283 | //已经完成布局 284 | mIsLayout = true; 285 | } 286 | 287 | /** 288 | * 判断当前 View 布局位置是否与已经显示的View有重叠 289 | * @param newChildBound 需要被检测的 View 290 | * @return true:表示重叠; false:表示不重叠 291 | */ 292 | @SuppressWarnings("unused") 293 | private boolean isOverLap(ChildViewBound newChildBound){ 294 | for (View preChild : mFixedViews) { 295 | //计算重叠空间 296 | ChildViewBound preChildBound = (ChildViewBound) preChild.getLayoutParams(); 297 | 298 | int left = Math.max(newChildBound.getChildLeft() - mOverlapAdd, 299 | preChildBound.getChildLeft() - mOverlapAdd); 300 | int top = Math.max(newChildBound.getChildTop() - mOverlapAdd, 301 | preChildBound.getChildTop() - mOverlapAdd); 302 | int right = Math.min(newChildBound.getChildRight() + mOverlapAdd, 303 | preChildBound.getChildRight() + mOverlapAdd); 304 | int bottom = Math.min(newChildBound.getChildBottom() + mOverlapAdd, 305 | preChildBound.getChildBottom() + mOverlapAdd); 306 | 307 | if((right - left) > 0 || (bottom - top) > 0){ 308 | return true; 309 | } 310 | } 311 | return false; 312 | } 313 | 314 | /** 315 | * 使得自动生成子控件并填装步骤,在用户切出界面后,不再执行,避免占用CPU资源 316 | */ 317 | @Override 318 | protected void onDetachedFromWindow() { 319 | handler.removeCallbacksAndMessages(null); 320 | super.onDetachedFromWindow(); 321 | } 322 | 323 | 324 | //工具包方法/**************************************************************************************/ 325 | //设置初始属性============================================================= 326 | /** 327 | * 设定当前密度矩阵行列数,同时计算相关 密度矩阵,及总区块数 328 | * @param xRegularity 设定密度矩阵行数 329 | * @param yRegularity 设定密度矩阵列数 330 | */ 331 | public void setRegularity(int xRegularity, int yRegularity){ 332 | this.mXRegularity = (xRegularity > 1) ? xRegularity : 1; 333 | this.mYRegularity = (yRegularity > 1) ? yRegularity : 1; 334 | //按设置,计算区块总数 335 | this.mAreaNum = mXRegularity * mYRegularity; 336 | initAreaDensity(); 337 | 338 | } 339 | 340 | /** 341 | * 按设置,初始化密度矩阵 342 | */ 343 | private void initAreaDensity() { 344 | this.mAreaDensity = new int[mXRegularity][mYRegularity]; 345 | resetAreasDensity(); 346 | } 347 | 348 | /** 349 | * 设置同一时刻,被展示到控件上的 子View 个数最大值,默认为 1 350 | * @param itemShowCount 个数最大值 351 | */ 352 | public void setItemShowCount(int itemShowCount){ 353 | this.mItemShowCount = (itemShowCount > 1) ?itemShowCount : 1; 354 | } 355 | 356 | /** 357 | * 设置子控件自动生成时间间隔 358 | */ 359 | public void setLooperDuration(int mLooperDuration) { 360 | this.mLooperDuration = mLooperDuration; 361 | } 362 | 363 | /** 364 | * 设置动画最长持续时间 365 | */ 366 | public void setDefaultDruation(int mDefaultDruation) { 367 | this.mDefaultDruation = mDefaultDruation; 368 | } 369 | 370 | //计算关键差值============================================================= 371 | /** 372 | * 计算区块和子控件宽度大小之间的大小差值 373 | * @param blockW 区块宽度 374 | * @param childWidth 子控件宽度 375 | * @return 宽度差值 376 | */ 377 | private int childInBlockOffestX(int blockW, int childWidth){ 378 | int xOffset = blockW - childWidth; 379 | if(xOffset <= 0){ 380 | xOffset = 1; 381 | } 382 | return mRandom.nextInt(xOffset); 383 | } 384 | 385 | /** 386 | * 计算区块和子控件高度大小之间的大小差值 387 | * @param blockH 区块高度 388 | * @param childHeight 子控件高度 389 | * @return 高度差值 390 | */ 391 | private int childInBlockOffsetY(int blockH, int childHeight){ 392 | int yOffset = blockH - childHeight; 393 | if(yOffset <= 0){ 394 | yOffset = 1; 395 | } 396 | return mRandom.nextInt(yOffset); 397 | } 398 | 399 | //重置关键参数============================================================= 400 | /** 401 | * 初始化可用区块 402 | */ 403 | private void resetAvailAreas() { 404 | availAreas.clear(); 405 | for (int i = 0; i < mAreaNum; i++) { 406 | availAreas.add(i); 407 | } 408 | } 409 | 410 | /** 411 | * 重置密度矩阵 {@link mAreaDensity} 412 | */ 413 | private void resetAreasDensity(){ 414 | if(mAreaDensity != null){ 415 | for (int i = 0; i < mXRegularity; i++) { 416 | for (int j = 0; j < mYRegularity; j++) { 417 | mAreaDensity[i][j] = 0; 418 | } 419 | } 420 | } 421 | } 422 | 423 | /** 424 | * 清空复用缓存列表 425 | */ 426 | private void resetRecycler(){ 427 | if(mRecycledViews != null){ 428 | mRecycledViews.clear(); 429 | } 430 | } 431 | 432 | /** 433 | * 生成并布局子控件之前,先初始化布局环境记录 434 | */ 435 | private void resetPanelForChild(){ 436 | resetAreasDensity(); 437 | resetRecycler(); 438 | } 439 | 440 | //复用缓存列表操作=========================================================== 441 | /** 442 | * 把复用的View加入复用列表栈顶,FILO 443 | * @param scrapView 要添加入复用列表的View 444 | */ 445 | private void pushRecycler(View scrapView){ 446 | if (null != scrapView) { 447 | mRecycledViews.add(0, scrapView); 448 | } 449 | } 450 | 451 | /** 452 | * 取出缓存复用列表保有的 View,FILO 453 | * @return 栈顶 View 454 | */ 455 | private View popRecycler(){ 456 | final int size = mRecycledViews.size(); 457 | if(size > 0){ 458 | return mRecycledViews.remove(0); 459 | }else{ 460 | return null; 461 | } 462 | } 463 | 464 | //开启 子控件 动画=========================================================== 465 | private void startZoomAnimation(){ 466 | for (final View justInitChild : justInitChilds) { 467 | ChildViewBound params = (ChildViewBound) justInitChild.getLayoutParams(); 468 | //计算控件动画动态配置参数,用于设置动画持续时间 469 | float distance = GeometryUtil.getDistanceBetween2Points(params, mCenter); 470 | float percent = distance / mDiagonalLength; 471 | //动态设置子控件动画实际持续时间,越靠近中心,消失的越快 472 | int duration = (int) (mDefaultDruation * percent + 0.5f); 473 | //计算控件移动方向与距离 474 | float dx = GeometryUtil.caculateDx(params, mCenter) ; 475 | float dy = GeometryUtil.caculateDy(params, mCenter) ; 476 | LogUtil.LOGW("tag", "x:" + mCenter.x + " y:" + mCenter.y); //TODO 477 | 478 | AnimatorUtil animatorUtils = new AnimatorUtil(justInitChild, duration); 479 | animatorUtils.addAlphaAnimationBy(-1.0f) 480 | .addTranslationAnimationBy(dx, dy) 481 | .addScaleAnimationBy(-0.8f) 482 | .startAnimator(); 483 | ViewPropertyAnimator animate = animatorUtils.getAnimate(); 484 | animate.setListener(new AnimatorListenerAdapter() { 485 | @Override 486 | public void onAnimationEnd(Animator animation) { 487 | super.onAnimationEnd(animation); 488 | justInitChild.clearAnimation(); 489 | mFixedViews.remove(justInitChild); 490 | pushRecycler(justInitChild); 491 | removeView(justInitChild); 492 | } 493 | }); 494 | } 495 | justInitChilds.clear(); 496 | 497 | } 498 | 499 | //阶段完成情况获取=========================================================== 500 | /** 501 | * 返回当前 RandomLayout 是否已经执行 onLayout 502 | * @return 当前布局状态标识 503 | */ 504 | public boolean isLayout(){ 505 | return mIsLayout; 506 | } 507 | 508 | //对外暴露方法============================================================= 509 | /** 510 | * 重写父类的removeAllViews 511 | */ 512 | @Override 513 | public void removeAllViews() { 514 | super.removeAllViews();//先删除所有View 515 | resetAreasDensity();//重新设置所有区域的区域密度 516 | resetRecycler();//清空缓存列表 517 | } 518 | 519 | /** 520 | * 请求刷新当前View显示,会重新分配布局 521 | */ 522 | public void refreshView(){ 523 | resetAreasDensity(); 524 | requestLayout(); 525 | } 526 | 527 | public int getLooperDuration() { 528 | return mLooperDuration; 529 | } 530 | 531 | public int getDefaultDruation() { 532 | return mDefaultDruation; 533 | } 534 | 535 | //回调函数定义/*************************************************************************************/ 536 | private OnCreateItemViewListener onCreateItemViewListener; 537 | 538 | /** 539 | * 用于监听布局生成用于显示的子控件,使用布局,必须重写该监听的 {@link createItemView} 方法 540 | */ 541 | public static interface OnCreateItemViewListener{ 542 | public int getCount(); //设置用于显示的子控件数目 543 | public View createItemView(int position, View convertView); //用于获取指定位置的子控件 544 | } 545 | 546 | public void setOnCreateItemViewListener(OnCreateItemViewListener createItemViewListener){ 547 | this.onCreateItemViewListener = createItemViewListener; 548 | 549 | } 550 | 551 | } 552 | 553 | 554 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/random_layout/AnimatorUtil.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.random_layout; 2 | 3 | import android.view.View; 4 | 5 | import com.nineoldandroids.view.ViewPropertyAnimator; 6 | 7 | 8 | public class AnimatorUtil { 9 | 10 | private ViewPropertyAnimator animate; 11 | private int duration; 12 | private View view; 13 | 14 | public AnimatorUtil(View view, int duration) { 15 | super(); 16 | this.view = view; 17 | this.duration = duration; 18 | animate = ViewPropertyAnimator.animate(view); 19 | } 20 | 21 | 22 | public ViewPropertyAnimator getAnimate() { 23 | return animate; 24 | } 25 | 26 | 27 | public AnimatorUtil addScaleAnimationBy(float value){ 28 | animate.scaleXBy(value).scaleYBy(value) 29 | .setDuration(duration); 30 | return this; 31 | } 32 | 33 | public AnimatorUtil addTranslationAnimationBy(float valueX, float valueY){ 34 | animate.translationXBy(valueX).translationYBy(valueY) 35 | .setDuration(duration); 36 | return this; 37 | } 38 | 39 | public AnimatorUtil addRotationAnimationBy(float degree){ 40 | animate.rotationBy(degree) 41 | .setDuration(duration); 42 | return this; 43 | } 44 | 45 | public AnimatorUtil addAlphaAnimationBy(float value){ 46 | animate.alphaBy(value) 47 | .setDuration(duration); 48 | return this; 49 | } 50 | 51 | /** 52 | * 此方法用于供使用云布局的编程人员,实现自定义特效 53 | * @attention 54 | * 内部移除了默认特效,使用时必须内部调用父类方法{@link resetAnimation} 55 | */ 56 | public void setSelfAnimator(){ 57 | 58 | } 59 | 60 | /** 61 | * 清除当前定义到指定 view 上的所有特效,并重新初始化 62 | */ 63 | public void resetAnimation(){ 64 | animate.cancel(); 65 | view.clearAnimation(); 66 | animate = ViewPropertyAnimator.animate(view); 67 | } 68 | 69 | public void startAnimator(){ 70 | animate.start(); 71 | } 72 | 73 | } 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/random_layout/ChildViewBound.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.random_layout; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.view.ViewGroup; 6 | import android.widget.FrameLayout; 7 | 8 | public class ChildViewBound extends FrameLayout.LayoutParams{ 9 | private int childLeft ; 10 | private int childTop ; 11 | private int childRight ; 12 | private int childBottom ; 13 | 14 | public ChildViewBound(Context arg0, AttributeSet arg1) { 15 | super(arg0, arg1); 16 | } 17 | public ChildViewBound(int w, int h) { 18 | super(w, h); 19 | } 20 | public ChildViewBound(ViewGroup.LayoutParams source) { 21 | super(source); 22 | } 23 | 24 | public int getChildLeft() { 25 | return childLeft; 26 | } 27 | public void setChildLeft(int childLeft) { 28 | this.childLeft = childLeft; 29 | } 30 | public int getChildTop() { 31 | return childTop; 32 | } 33 | public void setChildTop(int childTop) { 34 | this.childTop = childTop; 35 | } 36 | public int getChildRight() { 37 | return childRight; 38 | } 39 | public void setChildRight(int childRight) { 40 | this.childRight = childRight; 41 | } 42 | public int getChildBottom() { 43 | return childBottom; 44 | } 45 | public void setChildBottom(int childBottom) { 46 | this.childBottom = childBottom; 47 | } 48 | 49 | public void clear(){ 50 | this.childLeft = 0; 51 | this.childTop = 0; 52 | this.childRight = 0; 53 | this.childBottom = 0; 54 | } 55 | 56 | 57 | public void setChildViewBound(int childLeft, int childTop, int childRight, 58 | int childBottom) { 59 | this.childLeft = childLeft; 60 | this.childTop = childTop; 61 | this.childRight = childRight; 62 | this.childBottom = childBottom; 63 | } 64 | 65 | @Override 66 | public String toString() { 67 | return "ChildViewBound [childLeft=" + childLeft + ", childTop=" 68 | + childTop + ", childRight=" + childRight + ", childBottom=" 69 | + childBottom + "]"; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/com/special/animatedrandomlayout/random_layout/GeometryUtil.java: -------------------------------------------------------------------------------- 1 | package com.special.animatedrandomlayout.random_layout; 2 | 3 | import android.graphics.Point; 4 | 5 | /** 6 | * 几何图形工具 7 | */ 8 | public class GeometryUtil { 9 | 10 | /** 11 | * As meaning of method name. 12 | * 获得两点之间的距离 13 | * @param x1 14 | * @param y1 15 | * @param x2 16 | * @param y2 17 | * @return 18 | */ 19 | public static float getDistanceBetween2Points(ChildViewBound params, Point p1) { 20 | int x1 = params.getChildLeft(); 21 | int y1 = params.getChildTop(); 22 | Point p0 = new Point(x1, y1); 23 | float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2)); 24 | return distance; 25 | } 26 | 27 | public static float getDistanceBetween2Points(int x, int y, Point p1) { 28 | Point p0 = new Point(x, y); 29 | float distance = (float) Math.sqrt(Math.pow(p0.y - p1.y, 2) + Math.pow(p0.x - p1.x, 2)); 30 | return distance; 31 | } 32 | 33 | /** 34 | * 根据分度值,计算从start到end中,fraction位置的值。fraction范围为0 -> 1 35 | * @param fraction 36 | * @param start 37 | * @param end 38 | * @return 39 | */ 40 | public static float evaluateValue(float fraction, Number start, Number end){ 41 | return start.floatValue() + (end.floatValue() - start.floatValue()) * fraction; 42 | } 43 | 44 | public static float caculateDx(ChildViewBound params, Point point){ 45 | int x1 = params.getChildLeft(); 46 | int x2 = point.x; 47 | float dx = (float)(x2 - x1); 48 | return dx; 49 | } 50 | 51 | public static float caculateDy(ChildViewBound params, Point point){ 52 | int y1 = params.getChildTop(); 53 | int y2 = point.y; 54 | float dy = (float)(y2 - y1); 55 | return dy; 56 | } 57 | 58 | } --------------------------------------------------------------------------------