├── .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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------