├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTORS.md ├── FlowLayout ├── .gitignore ├── build.gradle ├── gradle.properties ├── proguard-rules.txt └── src │ ├── androidTest │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── wefika │ │ │ └── flowlayout │ │ │ ├── FlowLayoutStubActivity.java │ │ │ └── FlowLayoutTest.java │ └── res │ │ └── layout │ │ └── stub_layout.xml │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── wefika │ │ └── flowlayout │ │ └── FlowLayout.java │ └── res │ └── values │ └── attr.xml ├── FlowLayoutExample ├── .gitignore ├── build.gradle ├── proguard-rules.txt ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── wefika │ │ │ └── flowlayout │ │ │ └── example │ │ │ ├── AllInOneActivity.java │ │ │ ├── BasicActivity.java │ │ │ ├── MainActivity.java │ │ │ ├── ScrollActivity.java │ │ │ └── VisibilityActivity.java │ │ └── res │ │ ├── drawable-hdpi │ │ └── ic_launcher.png │ │ ├── drawable-mdpi │ │ └── ic_launcher.png │ │ ├── drawable-xhdpi │ │ └── ic_launcher.png │ │ ├── drawable-xxhdpi │ │ └── ic_launcher.png │ │ ├── layout │ │ ├── activity_all_in_one.xml │ │ ├── activity_basic.xml │ │ ├── activity_main.xml │ │ ├── activity_scroll.xml │ │ └── activity_visibility.xml │ │ ├── values-v11 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml └── web_hi_res_512.png ├── LICENSE ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images └── framed_example_screenshot.png ├── maven_push.gradle ├── settings.gradle └── test ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── com │ └── wefika │ └── flowlayout │ └── test │ └── ApplicationTest.java └── main ├── AndroidManifest.xml └── res ├── drawable-hdpi └── ic_launcher.png ├── drawable-mdpi └── ic_launcher.png ├── drawable-xhdpi └── ic_launcher.png ├── drawable-xxhdpi └── ic_launcher.png └── values ├── strings.xml └── styles.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | 15 | # Local configuration file (sdk path, etc) 16 | local.properties 17 | 18 | # Eclipse project files 19 | .classpath 20 | .project 21 | 22 | # Intellij 23 | *.iml 24 | *.ipr 25 | *.iws 26 | .idea/ 27 | 28 | # MAC 29 | .DS_Store 30 | 31 | # Gradle 32 | .gradle/ 33 | build/ 34 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: oraclejdk7 3 | sudo: false 4 | 5 | env: 6 | matrix: 7 | - ANDROID_TARGET=android-23 ANDROID_ABI=armeabi-v7a 8 | # - ANDROID_TARGET=android-9 ANDROID_ABI=armeabi 9 | 10 | android: 11 | components: 12 | - build-tools-23.0.1 13 | - android-23 14 | # - android-9 15 | 16 | before_install: 17 | # Emulator 18 | - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI 19 | - emulator -avd test -no-skin -no-audio -no-window & 20 | - echo "disablePreDex" >> gradle.properties 21 | 22 | before_script: 23 | - android-wait-for-emulator 24 | - adb shell input keyevent 82 & 25 | 26 | after_success: 27 | - ./gradlew coveralls 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Change Log 2 | ========== 3 | 4 | Version 0.5.0 *?* 5 | * Fixed source and docs jar included in maven repo 6 | * Verifying if `LayoutParams` are valid and failing early if not. 7 | 8 | Version 0.4.1 *(2015-09-06)* 9 | ---------------------------- 10 | * Removing `allowBackup` flag (by [RyanRamchandar](https://github.com/RyanRamchandar)) 11 | 12 | Version 0.4.0 *(2014-12-26)* 13 | ---------------------------- 14 | * Fixing issues with layout height. 15 | * Fixing issue where firs child in row was not shown. 16 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # Contributors to CollapseCalendarView project 2 | 3 | ## Creator & Maintainer 4 | 5 | * Blaž Šolar 6 | 7 | ## Contributors 8 | 9 | In chronological order: 10 | 11 | * [Your name or handle] <[email or website]> 12 | * [Brief summary of your changes] 13 | -------------------------------------------------------------------------------- /FlowLayout/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /FlowLayout/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | mavenCentral() 4 | mavenLocal() 5 | maven { url "http://dl.bintray.com/blazsolar/gradle-plugins" } 6 | } 7 | 8 | dependencies { 9 | classpath "com.github.blazsolar.gradle:coveralls-gradle-plugin:2.0.1" 10 | } 11 | } 12 | 13 | apply plugin: 'com.android.library' 14 | apply plugin: 'com.github.blazsolar.coveralls' 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | compile 'com.intellij:annotations:12.0' 22 | } 23 | 24 | android { 25 | compileSdkVersion 23 26 | buildToolsVersion "23.0.1" 27 | 28 | defaultConfig { 29 | minSdkVersion 9 30 | targetSdkVersion 23 31 | versionCode Integer.parseInt(project.VERSION_CODE) 32 | versionName project.VERSION_NAME 33 | } 34 | 35 | buildTypes { 36 | debug { 37 | testCoverageEnabled = true 38 | } 39 | 40 | release { 41 | minifyEnabled false 42 | } 43 | } 44 | 45 | } 46 | 47 | coveralls { 48 | jacocoReportPath = 'build/outputs/reports/coverage/debug/report.xml' 49 | } 50 | 51 | apply from: '../maven_push.gradle' 52 | -------------------------------------------------------------------------------- /FlowLayout/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=FlowLayout 2 | POM_ARTIFACT_ID=flowlayout 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /FlowLayout/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /FlowLayout/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /FlowLayout/src/androidTest/java/com/wefika/flowlayout/FlowLayoutStubActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | /** 23 | * Created by Blaz Solar on 17/07/14. 24 | */ 25 | public class FlowLayoutStubActivity extends Activity { 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(com.wefika.flowlayout.test.R.layout.stub_layout); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowLayout/src/androidTest/java/com/wefika/flowlayout/FlowLayoutTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout; 18 | 19 | import android.app.Activity; 20 | import android.content.Intent; 21 | import android.content.res.TypedArray; 22 | import android.os.Build; 23 | import android.test.ActivityInstrumentationTestCase2; 24 | import android.test.ActivityUnitTestCase; 25 | import android.test.AndroidTestCase; 26 | import android.util.AttributeSet; 27 | import android.util.Log; 28 | import android.util.Xml; 29 | import android.view.Gravity; 30 | import android.view.ViewGroup; 31 | 32 | import junit.framework.Assert; 33 | 34 | import org.xmlpull.v1.XmlPullParser; 35 | 36 | /** 37 | * Created by Blaz Solar on 01/04/14. 38 | */ 39 | public class FlowLayoutTest extends ActivityUnitTestCase { 40 | 41 | private FlowLayout mLayout; 42 | 43 | public FlowLayoutTest() { 44 | super(FlowLayoutStubActivity.class); 45 | } 46 | 47 | @Override 48 | public void setUp() throws Exception { 49 | super.setUp(); 50 | 51 | } 52 | 53 | public void testGenerateDefaultLayoutParams() throws Exception { 54 | 55 | init(); 56 | 57 | FlowLayout.LayoutParams params = mLayout.generateDefaultLayoutParams(); 58 | Assert.assertEquals(FlowLayout.LayoutParams.MATCH_PARENT, params.width); 59 | Assert.assertEquals(FlowLayout.LayoutParams.MATCH_PARENT, params.height); 60 | 61 | } 62 | 63 | public void testGenerateLayoutParams() throws Exception { 64 | 65 | init(); 66 | 67 | ViewGroup.LayoutParams params = new FlowLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 68 | 69 | FlowLayout.LayoutParams generated = mLayout.generateLayoutParams(params); 70 | 71 | assertEquals(-1, generated.gravity); 72 | assertEquals(FlowLayout.LayoutParams.MATCH_PARENT, generated.width); 73 | assertEquals(FlowLayout.LayoutParams.MATCH_PARENT, generated.height); 74 | 75 | } 76 | 77 | public void testGravity() throws Exception { 78 | 79 | init(); 80 | 81 | mLayout.setGravity(Gravity.BOTTOM | Gravity.RIGHT); 82 | 83 | assertEquals(Gravity.BOTTOM | Gravity.RIGHT, mLayout.getGravity()); 84 | 85 | } 86 | 87 | public void testGravityDefault() throws Exception { 88 | 89 | init(); 90 | 91 | mLayout.setGravity(Gravity.BOTTOM | Gravity.RIGHT); 92 | 93 | mLayout.setGravity(0); 94 | 95 | int horizontal = isIcs() ? Gravity.START : Gravity.LEFT; 96 | 97 | assertEquals(horizontal | Gravity.TOP, mLayout.getGravity()); 98 | 99 | } 100 | 101 | private void init() { 102 | 103 | Intent intent = new Intent(getInstrumentation().getTargetContext(), FlowLayoutStubActivity.class); 104 | startActivity(intent, null, null); 105 | 106 | mLayout = (FlowLayout) getActivity().findViewById(com.wefika.flowlayout.test.R.id.layout); 107 | 108 | } 109 | 110 | private static boolean isIcs() { 111 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /FlowLayout/src/androidTest/res/layout/stub_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 24 | 25 | -------------------------------------------------------------------------------- /FlowLayout/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /FlowLayout/src/main/java/com/wefika/flowlayout/FlowLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout; 18 | 19 | import android.annotation.TargetApi; 20 | import android.content.Context; 21 | import android.content.res.TypedArray; 22 | import android.os.Build; 23 | import android.util.AttributeSet; 24 | import android.view.Gravity; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.view.ViewGroup.LayoutParams; 28 | 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | 32 | /** 33 | * FlowLayout will arrange child elements horizontally one next to another. If there is not enough 34 | * space for next view new line will be added. 35 | * 36 | * User: Blaz Solar 37 | * Date: 5/6/13 38 | * Time: 8:17 PM 39 | */ 40 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 41 | public class FlowLayout extends ViewGroup { 42 | 43 | private int mGravity = (isIcs() ? Gravity.START : Gravity.LEFT) | Gravity.TOP; 44 | 45 | private final List> mLines = new ArrayList>(); 46 | private final List mLineHeights = new ArrayList(); 47 | private final List mLineMargins = new ArrayList(); 48 | 49 | public FlowLayout(Context context) { 50 | this(context, null); 51 | } 52 | 53 | public FlowLayout(Context context, AttributeSet attrs) { 54 | this(context, attrs, 0); 55 | } 56 | 57 | public FlowLayout(Context context, AttributeSet attrs, int defStyle) { 58 | super(context, attrs, defStyle); 59 | 60 | TypedArray a = context.obtainStyledAttributes(attrs, 61 | R.styleable.FlowLayout, defStyle, 0); 62 | 63 | try { 64 | int index = a.getInt(R.styleable.FlowLayout_android_gravity, -1); 65 | if(index > 0) { 66 | setGravity(index); 67 | } 68 | } finally { 69 | a.recycle(); 70 | } 71 | 72 | } 73 | 74 | /** 75 | * {@inheritDoc} 76 | */ 77 | @Override 78 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 79 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 80 | 81 | int sizeWidth = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); 82 | int sizeHeight = MeasureSpec.getSize(heightMeasureSpec); 83 | 84 | int modeWidth = MeasureSpec.getMode(widthMeasureSpec); 85 | int modeHeight = MeasureSpec.getMode(heightMeasureSpec); 86 | 87 | int width = 0; 88 | int height = getPaddingTop() + getPaddingBottom(); 89 | 90 | int lineWidth = 0; 91 | int lineHeight = 0; 92 | 93 | int childCount = getChildCount(); 94 | 95 | for(int i = 0; i < childCount; i++) { 96 | 97 | View child = getChildAt(i); 98 | boolean lastChild = i == childCount - 1; 99 | 100 | if(child.getVisibility() == View.GONE) { 101 | 102 | if(lastChild) { 103 | width = Math.max(width, lineWidth); 104 | height += lineHeight; 105 | } 106 | 107 | continue; 108 | } 109 | 110 | measureChildWithMargins(child, widthMeasureSpec, lineWidth, heightMeasureSpec, height); 111 | 112 | LayoutParams lp = (LayoutParams) child.getLayoutParams(); 113 | 114 | int childWidthMode = MeasureSpec.AT_MOST; 115 | int childWidthSize = sizeWidth; 116 | 117 | int childHeightMode = MeasureSpec.AT_MOST; 118 | int childHeightSize = sizeHeight; 119 | 120 | if(lp.width == LayoutParams.MATCH_PARENT) { 121 | childWidthMode = MeasureSpec.EXACTLY ; 122 | childWidthSize -= lp.leftMargin + lp.rightMargin; 123 | } else if(lp.width >= 0) { 124 | childWidthMode = MeasureSpec.EXACTLY; 125 | childWidthSize = lp.width; 126 | } 127 | 128 | if(lp.height >= 0) { 129 | childHeightMode = MeasureSpec.EXACTLY; 130 | childHeightSize = lp.height; 131 | } else if (modeHeight == MeasureSpec.UNSPECIFIED) { 132 | childHeightMode = MeasureSpec.UNSPECIFIED; 133 | childHeightSize = 0; 134 | } 135 | 136 | child.measure( 137 | MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode), 138 | MeasureSpec.makeMeasureSpec(childHeightSize, childHeightMode) 139 | ); 140 | 141 | int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; 142 | 143 | if(lineWidth + childWidth > sizeWidth) { 144 | 145 | width = Math.max(width, lineWidth); 146 | lineWidth = childWidth; 147 | 148 | height += lineHeight; 149 | lineHeight = child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; 150 | 151 | } else { 152 | lineWidth += childWidth; 153 | lineHeight = Math.max(lineHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); 154 | } 155 | 156 | if(lastChild) { 157 | width = Math.max(width, lineWidth); 158 | height += lineHeight; 159 | } 160 | 161 | } 162 | 163 | width += getPaddingLeft() + getPaddingRight(); 164 | 165 | setMeasuredDimension( 166 | (modeWidth == MeasureSpec.EXACTLY) ? sizeWidth : width, 167 | (modeHeight == MeasureSpec.EXACTLY) ? sizeHeight : height); 168 | } 169 | 170 | /** 171 | * {@inheritDoc} 172 | */ 173 | @Override 174 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 175 | 176 | mLines.clear(); 177 | mLineHeights.clear(); 178 | mLineMargins.clear(); 179 | 180 | int width = getWidth(); 181 | int height = getHeight(); 182 | 183 | int linesSum = getPaddingTop(); 184 | 185 | int lineWidth = 0; 186 | int lineHeight = 0; 187 | List lineViews = new ArrayList(); 188 | 189 | float horizontalGravityFactor; 190 | switch ((mGravity & Gravity.HORIZONTAL_GRAVITY_MASK)) { 191 | case Gravity.LEFT: 192 | default: 193 | horizontalGravityFactor = 0; 194 | break; 195 | case Gravity.CENTER_HORIZONTAL: 196 | horizontalGravityFactor = .5f; 197 | break; 198 | case Gravity.RIGHT: 199 | horizontalGravityFactor = 1; 200 | break; 201 | } 202 | 203 | for(int i = 0; i < getChildCount(); i++) { 204 | 205 | View child = getChildAt(i); 206 | 207 | if(child.getVisibility() == View.GONE) { 208 | continue; 209 | } 210 | 211 | LayoutParams lp = (LayoutParams) child.getLayoutParams(); 212 | 213 | int childWidth = child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin; 214 | int childHeight = child.getMeasuredHeight() + lp.bottomMargin + lp.topMargin; 215 | 216 | if(lineWidth + childWidth > width) { 217 | mLineHeights.add(lineHeight); 218 | mLines.add(lineViews); 219 | mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft()); 220 | 221 | linesSum += lineHeight; 222 | 223 | lineHeight = 0; 224 | lineWidth = 0; 225 | lineViews = new ArrayList(); 226 | } 227 | 228 | lineWidth += childWidth; 229 | lineHeight = Math.max(lineHeight, childHeight); 230 | lineViews.add(child); 231 | } 232 | 233 | mLineHeights.add(lineHeight); 234 | mLines.add(lineViews); 235 | mLineMargins.add((int) ((width - lineWidth) * horizontalGravityFactor) + getPaddingLeft()); 236 | 237 | linesSum += lineHeight; 238 | 239 | int verticalGravityMargin = 0; 240 | switch ((mGravity & Gravity.VERTICAL_GRAVITY_MASK) ) { 241 | case Gravity.TOP: 242 | default: 243 | break; 244 | case Gravity.CENTER_VERTICAL: 245 | verticalGravityMargin = (height - linesSum) / 2; 246 | break; 247 | case Gravity.BOTTOM: 248 | verticalGravityMargin = height - linesSum; 249 | break; 250 | } 251 | 252 | int numLines = mLines.size(); 253 | 254 | int left; 255 | int top = getPaddingTop(); 256 | 257 | for(int i = 0; i < numLines; i++) { 258 | 259 | lineHeight = mLineHeights.get(i); 260 | lineViews = mLines.get(i); 261 | left = mLineMargins.get(i); 262 | 263 | int children = lineViews.size(); 264 | 265 | for(int j = 0; j < children; j++) { 266 | 267 | View child = lineViews.get(j); 268 | 269 | if(child.getVisibility() == View.GONE) { 270 | continue; 271 | } 272 | 273 | LayoutParams lp = (LayoutParams) child.getLayoutParams(); 274 | 275 | // if height is match_parent we need to remeasure child to line height 276 | if(lp.height == LayoutParams.MATCH_PARENT) { 277 | int childWidthMode = MeasureSpec.AT_MOST; 278 | int childWidthSize = lineWidth; 279 | 280 | if(lp.width == LayoutParams.MATCH_PARENT) { 281 | childWidthMode = MeasureSpec.EXACTLY; 282 | } else if(lp.width >= 0) { 283 | childWidthMode = MeasureSpec.EXACTLY; 284 | childWidthSize = lp.width; 285 | } 286 | 287 | child.measure( 288 | MeasureSpec.makeMeasureSpec(childWidthSize, childWidthMode), 289 | MeasureSpec.makeMeasureSpec(lineHeight - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY) 290 | ); 291 | } 292 | 293 | int childWidth = child.getMeasuredWidth(); 294 | int childHeight = child.getMeasuredHeight(); 295 | 296 | int gravityMargin = 0; 297 | 298 | if(Gravity.isVertical(lp.gravity)) { 299 | switch (lp.gravity) { 300 | case Gravity.TOP: 301 | default: 302 | break; 303 | case Gravity.CENTER_VERTICAL: 304 | case Gravity.CENTER: 305 | gravityMargin = (lineHeight - childHeight - lp.topMargin - lp.bottomMargin) / 2 ; 306 | break; 307 | case Gravity.BOTTOM: 308 | gravityMargin = lineHeight - childHeight - lp.topMargin - lp.bottomMargin; 309 | break; 310 | } 311 | } 312 | 313 | child.layout(left + lp.leftMargin, 314 | top + lp.topMargin + gravityMargin + verticalGravityMargin, 315 | left + childWidth + lp.leftMargin, 316 | top + childHeight + lp.topMargin + gravityMargin + verticalGravityMargin); 317 | 318 | left += childWidth + lp.leftMargin + lp.rightMargin; 319 | 320 | } 321 | 322 | top += lineHeight; 323 | } 324 | 325 | } 326 | 327 | @Override 328 | protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { 329 | return new LayoutParams(p); 330 | } 331 | 332 | /** 333 | * {@inheritDoc} 334 | */ 335 | @Override 336 | public LayoutParams generateLayoutParams(AttributeSet attrs) { 337 | return new LayoutParams(getContext(), attrs); 338 | } 339 | 340 | /** 341 | * {@inheritDoc} 342 | */ 343 | @Override 344 | protected LayoutParams generateDefaultLayoutParams() { 345 | return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); 346 | } 347 | 348 | @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 349 | return super.checkLayoutParams(p) && p instanceof LayoutParams; 350 | } 351 | 352 | @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) 353 | public void setGravity(int gravity) { 354 | if(mGravity != gravity) { 355 | if((gravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { 356 | gravity |= isIcs() ? Gravity.START : Gravity.LEFT; 357 | } 358 | 359 | if((gravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { 360 | gravity |= Gravity.TOP; 361 | } 362 | 363 | mGravity = gravity; 364 | requestLayout(); 365 | } 366 | } 367 | 368 | public int getGravity() { 369 | return mGravity; 370 | } 371 | 372 | /** 373 | * @return true if device is running ICS or grater version of Android. 374 | */ 375 | private static boolean isIcs() { 376 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; 377 | } 378 | 379 | public static class LayoutParams extends MarginLayoutParams { 380 | 381 | public int gravity = -1; 382 | 383 | public LayoutParams(Context c, AttributeSet attrs) { 384 | super(c, attrs); 385 | 386 | TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout); 387 | 388 | try { 389 | gravity = a.getInt(R.styleable.FlowLayout_Layout_android_layout_gravity, -1); 390 | } finally { 391 | a.recycle(); 392 | } 393 | } 394 | 395 | public LayoutParams(int width, int height) { 396 | super(width, height); 397 | } 398 | 399 | public LayoutParams(ViewGroup.LayoutParams source) { 400 | super(source); 401 | } 402 | 403 | } 404 | 405 | } 406 | -------------------------------------------------------------------------------- /FlowLayout/src/main/res/values/attr.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /FlowLayoutExample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /FlowLayoutExample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | repositories { 4 | mavenCentral() 5 | } 6 | 7 | android { 8 | compileSdkVersion 23 9 | buildToolsVersion "23.0.1" 10 | 11 | defaultConfig { 12 | minSdkVersion 9 13 | targetSdkVersion 23 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | compile project(':FlowLayout') 28 | } 29 | -------------------------------------------------------------------------------- /FlowLayoutExample/proguard-rules.txt: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the ProGuard 5 | # include property in project.properties. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/java/com/wefika/flowlayout/example/AllInOneActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout.example; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | /** 23 | * Created by Blaz Solar on 18/01/14. 24 | */ 25 | public class AllInOneActivity extends Activity { 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_all_in_one); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/java/com/wefika/flowlayout/example/BasicActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout.example; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | /** 23 | * Created by Blaz Solar on 18/01/14. 24 | */ 25 | public class BasicActivity extends Activity { 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_basic); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/java/com/wefika/flowlayout/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout.example; 18 | 19 | import android.app.Activity; 20 | import android.app.ListActivity; 21 | import android.content.Context; 22 | import android.content.Intent; 23 | import android.os.Bundle; 24 | import android.view.LayoutInflater; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.widget.ArrayAdapter; 28 | import android.widget.ListView; 29 | import android.widget.TextView; 30 | 31 | public class MainActivity extends ListActivity { 32 | 33 | private ExamplesAdapter mAdapter; 34 | 35 | @Override 36 | protected void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | setContentView(R.layout.activity_main); 39 | 40 | mAdapter = new ExamplesAdapter(this); 41 | setListAdapter(mAdapter); 42 | } 43 | 44 | @Override 45 | protected void onListItemClick(ListView l, View v, int position, long id) { 46 | Intent intent = new Intent(this, mAdapter.getItem(position).activityClass); 47 | startActivity(intent); 48 | } 49 | 50 | private class ExamplesAdapter extends ArrayAdapter { 51 | 52 | private Item[] mItems = { 53 | new Item<>(R.string.activity_basic, BasicActivity.class), 54 | new Item<>(R.string.activity_all_in_one, AllInOneActivity.class), 55 | new Item<>(R.string.activity_visibility, VisibilityActivity.class), 56 | new Item<>(R.string.activity_scroll, ScrollActivity.class) 57 | }; 58 | 59 | private LayoutInflater mInflater; 60 | 61 | private ExamplesAdapter(Context context) { 62 | super(context, 0); 63 | 64 | mInflater = LayoutInflater.from(context); 65 | } 66 | 67 | @Override 68 | public int getCount() { 69 | return mItems.length; 70 | } 71 | 72 | @Override 73 | public Item getItem(int position) { 74 | return mItems[position]; 75 | } 76 | 77 | @Override 78 | public View getView(int position, View convertView, ViewGroup parent) { 79 | View v = convertView; 80 | if(v == null) { 81 | v = mInflater.inflate(android.R.layout.simple_list_item_1, parent, false); 82 | } 83 | 84 | TextView text = (TextView) v.findViewById(android.R.id.text1); 85 | text.setText(getItem(position).name); 86 | 87 | return v; 88 | } 89 | 90 | private class Item { 91 | 92 | private int name; 93 | private Class activityClass; 94 | 95 | public Item(int name, Class activityClass) { 96 | super(); 97 | this.name = name; 98 | this.activityClass = activityClass; 99 | } 100 | 101 | } 102 | 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/java/com/wefika/flowlayout/example/ScrollActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout.example; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | 22 | /** 23 | * Created by Blaz Solar on 28/03/14. 24 | */ 25 | public class ScrollActivity extends Activity { 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | setContentView(R.layout.activity_scroll); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/java/com/wefika/flowlayout/example/VisibilityActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Blaz Solar 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.wefika.flowlayout.example; 18 | 19 | import android.app.Activity; 20 | import android.os.Bundle; 21 | import android.view.View; 22 | 23 | import com.wefika.flowlayout.FlowLayout; 24 | 25 | /** 26 | * Created by Blaz Solar on 05/02/14. 27 | */ 28 | public class VisibilityActivity extends Activity { 29 | 30 | private FlowLayout mFlowLayout; 31 | 32 | @Override 33 | protected void onCreate(Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | setContentView(R.layout.activity_visibility); 36 | 37 | mFlowLayout = (FlowLayout) findViewById(R.id.flow); 38 | } 39 | 40 | public void addItem(View view) { 41 | 42 | int color = getResources().getColor(R.color.holo_blue_dark); 43 | 44 | View newView = new View(this); 45 | newView.setBackgroundColor(color); 46 | 47 | FlowLayout.LayoutParams params = new FlowLayout.LayoutParams(100, 100); 48 | params.rightMargin = 10; 49 | newView.setLayoutParams(params); 50 | 51 | mFlowLayout.addView(newView); 52 | } 53 | 54 | public void removeItem(View view) { 55 | 56 | mFlowLayout.removeView(getLastView()); 57 | 58 | } 59 | 60 | public void toggleItem(View view) { 61 | 62 | View last = getLastView(); 63 | 64 | if(last.getVisibility() == View.VISIBLE) { 65 | last.setVisibility(View.GONE); 66 | } else { 67 | last.setVisibility(View.VISIBLE); 68 | } 69 | 70 | } 71 | 72 | private View getLastView() { 73 | return mFlowLayout.getChildAt(mFlowLayout.getChildCount() - 1); 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazsolar/FlowLayout/60b98cda8782e46d338bb5afb7b3901b890f8fd5/FlowLayoutExample/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazsolar/FlowLayout/60b98cda8782e46d338bb5afb7b3901b890f8fd5/FlowLayoutExample/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazsolar/FlowLayout/60b98cda8782e46d338bb5afb7b3901b890f8fd5/FlowLayoutExample/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/blazsolar/FlowLayout/60b98cda8782e46d338bb5afb7b3901b890f8fd5/FlowLayoutExample/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /FlowLayoutExample/src/main/res/layout/activity_all_in_one.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 18 | 19 | 20 | 25 | 26 |