├── .gitignore ├── LICENSE.txt ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ru │ │ └── astrocode │ │ └── sample │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ru │ │ │ └── astrocode │ │ │ └── sample │ │ │ └── ActivityMain.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ ├── ic_close.xml │ │ ├── ic_launcher_background.xml │ │ ├── shape_chips.xml │ │ └── shape_chips_close_btn.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── ru │ └── astrocode │ └── sample │ └── ExampleUnitTest.java ├── build.gradle ├── flow-layout-manager ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── ru │ │ └── astrocode │ │ └── flm │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── ru │ │ │ └── astrocode │ │ │ └── flm │ │ │ └── FLMFlowLayoutManager.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── ru │ └── astrocode │ └── flm │ └── ExampleUnitTest.java ├── flow_layout_manager_vertical.gif ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | 5 | /.idea/modules.xml 6 | /.idea/libraries/ 7 | /.idea/gradle.xml 8 | /.idea/workspace.xml 9 | /.idea/tasks.xml 10 | /.idea/.name 11 | /.idea/compiler.xml 12 | /.idea/copyright/profiles_settings.xml 13 | /.idea/encodings.xml 14 | /.idea/misc.xml 15 | /.idea/modules.xml 16 | /.idea/scopes/scope_settings.xml 17 | /.idea/vcs.xml 18 | /.idea/runConfigurations.xml 19 | /.idea/codeStyles/Project.xml 20 | /.idea/caches/build_file_checksums.ser 21 | /.idea/markdown-navigator.xml 22 | /.idea/markdown-navigator/profiles_settings.xml 23 | *.iml 24 | .DS_Store 25 | /build 26 | /captures 27 | .externalNativeBuild 28 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FlowLayoutManager 2 | 3 | Flow layout manager for RecyclerView.Supports **API 10(Android 2.3)** and above. 4 | 5 | ![Example vertical](flow_layout_manager_vertical.gif) 6 | 7 | ## Features 8 | 9 | 1. Vertical/Horizontal orientation; 10 | 2. Custom gravity; 11 | 3. Maximum items in line; 12 | 4. Spaces between items and/or lines; 13 | 5. Stores position during orientation changes; 14 | 6. Scroll/Smooth scroll. 15 | 16 | ## Usage 17 | 18 | Instead standard layout manager use FLMFlowLayoutManager: 19 | 20 | ... 21 | 22 | FLMFlowLayoutManager layoutManager = new FLMFlowLayoutManager(FLMFlowLayoutManager.VERTICAL); 23 | 24 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 25 | recyclerView.setLayoutManager(layoutManager); 26 | recyclerView.setAdapter(new Adapter(this)); 27 | 28 | ... 29 | 30 | To change gravity of the layout manager: 31 | 32 | ... 33 | 34 | layoutManager.setGravity(Gravity.START|Gravity.CENTER_HORIZONTAL); 35 | 36 | ... 37 | 38 | To change maximum items count in line: 39 | 40 | ... 41 | 42 | layoutManager.setMaxItemsInLine(4); 43 | 44 | ... 45 | 46 | To change spaces between items and lines: 47 | 48 | ... 49 | 50 | layoutManager.setSpacingBetweenItems(10); 51 | layoutManager.setSpacingBetweenLines(10); 52 | 53 | ... 54 | 55 | ## License 56 | 57 | Copyright 2018 Astrocode011235813 58 | 59 | Licensed under the Apache License, Version 2.0 (the "License"); 60 | you may not use this file except in compliance with the License. 61 | You may obtain a copy of the License at 62 | 63 | http://www.apache.org/licenses/LICENSE-2.0 64 | 65 | Unless required by applicable law or agreed to in writing, software 66 | distributed under the License is distributed on an "AS IS" BASIS, 67 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 68 | See the License for the specific language governing permissions and 69 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | defaultConfig { 6 | applicationId "ru.astrocode.sample" 7 | minSdkVersion 10 8 | targetSdkVersion 25 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | vectorDrawables.useSupportLibrary = true 13 | 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | buildToolsVersion '27.0.3' 22 | productFlavors { 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation fileTree(include: ['*.jar'], dir: 'libs') 28 | implementation 'com.android.support.constraint:constraint-layout:1.1.1' 29 | testImplementation 'junit:junit:4.12' 30 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 31 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 32 | implementation project(':flow-layout-manager') 33 | implementation 'com.android.support:appcompat-v7:25.4.0' 34 | implementation 'com.android.support:recyclerview-v7:25.4.0' 35 | implementation 'com.android.support:design:25.4.0' 36 | } 37 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/ru/astrocode/sample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.sample; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ru.astrocode.sample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/ru/astrocode/sample/ActivityMain.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.sample; 2 | 3 | import android.content.Context; 4 | import android.graphics.Color; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.constraint.ConstraintLayout; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.AppCompatImageButton; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.TypedValue; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.widget.ImageButton; 16 | import android.widget.ImageView; 17 | import android.widget.TextView; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | 22 | import ru.astrocode.flm.FLMFlowLayoutManager; 23 | 24 | public class ActivityMain extends AppCompatActivity { 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_main); 30 | 31 | int item_spacing = getResources().getDimensionPixelSize(R.dimen.spacing_between_items); 32 | int lines_spacing = getResources().getDimensionPixelSize(R.dimen.spacing_between_lines); 33 | 34 | final Adapter adapter = new Adapter(this); 35 | final FLMFlowLayoutManager layoutManager = 36 | new FLMFlowLayoutManager(FLMFlowLayoutManager.VERTICAL,Gravity.CENTER,item_spacing,lines_spacing); 37 | 38 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerView); 39 | recyclerView.setLayoutManager(layoutManager); 40 | recyclerView.setAdapter(adapter); 41 | } 42 | 43 | private final class Adapter extends RecyclerView.Adapter { 44 | private final static int sBtnCloseSize = 16; 45 | private final static int sBtnCloseLeftMargin = 5; 46 | 47 | private final Context mContext; 48 | private final int mBtnCloseSize,mBtnCloseLeftMargin; 49 | 50 | private ArrayList mData; 51 | 52 | 53 | public Adapter(Context context) { 54 | mContext = context; 55 | mData = new ArrayList<>(Arrays.asList(context.getResources().getStringArray(R.array.Countries))); 56 | mBtnCloseSize = Math.round(sBtnCloseSize*context.getResources().getDisplayMetrics().density); 57 | mBtnCloseLeftMargin = Math.round(sBtnCloseLeftMargin*context.getResources().getDisplayMetrics().density); 58 | } 59 | 60 | @NonNull 61 | @Override 62 | public Adapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 63 | 64 | ConstraintLayout view = new ConstraintLayout(mContext); 65 | view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 66 | ViewGroup.LayoutParams.WRAP_CONTENT)); 67 | view.setBackgroundResource(R.drawable.shape_chips); 68 | 69 | TextView title = new TextView(mContext); 70 | title.setGravity(Gravity.CENTER); 71 | title.setTextColor(Color.BLACK); 72 | title.setTextSize(TypedValue.COMPLEX_UNIT_SP,12); 73 | title.setId(R.id.textView); 74 | 75 | ConstraintLayout.LayoutParams lp = new ConstraintLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 76 | ViewGroup.LayoutParams.WRAP_CONTENT); 77 | 78 | view.addView(title,lp); 79 | 80 | AppCompatImageButton imageButton = new AppCompatImageButton(mContext); 81 | imageButton.setBackgroundResource(R.drawable.shape_chips_close_btn); 82 | imageButton.setImageResource(R.drawable.ic_close); 83 | imageButton.setScaleType(ImageView.ScaleType.FIT_XY); 84 | imageButton.setId(R.id.imageButton); 85 | 86 | lp = new ConstraintLayout.LayoutParams(mBtnCloseSize,mBtnCloseSize); 87 | lp.leftMargin = mBtnCloseLeftMargin; 88 | lp.leftToRight = R.id.textView; 89 | 90 | view.addView(imageButton,lp); 91 | 92 | return new ViewHolder(view); 93 | } 94 | 95 | @Override 96 | public void onBindViewHolder(@NonNull Adapter.ViewHolder holder, int position) { 97 | holder.mText.setText(mData.get(position)); 98 | } 99 | 100 | @Override 101 | public int getItemCount() { 102 | return mData.size(); 103 | } 104 | 105 | final class ViewHolder extends RecyclerView.ViewHolder { 106 | TextView mText; 107 | ImageButton mDeleteButton; 108 | 109 | public ViewHolder(View itemView) { 110 | super(itemView); 111 | 112 | mText = (TextView)itemView.findViewById(R.id.textView); 113 | 114 | mDeleteButton = (ImageButton)itemView.findViewById(R.id.imageButton); 115 | mDeleteButton.setVisibility(View.VISIBLE); 116 | mDeleteButton.setOnClickListener(mListener); 117 | } 118 | 119 | private final View.OnClickListener mListener = new View.OnClickListener() { 120 | @Override 121 | public void onClick(View view) { 122 | mData.remove(getAdapterPosition()); 123 | notifyItemRemoved(getAdapterPosition()); 124 | } 125 | }; 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_chips.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_chips_close_btn.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | #E0E0E0 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10dp 4 | 10dp 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FlowLayoutManager 3 | 4 | Afghanistan 5 | Albania 6 | Algeria 7 | Andorra 8 | Angola 9 | Antigua and Barbuda 10 | Argentina 11 | Armenia 12 | Australia 13 | Austria 14 | Azerbaijan 15 | Bahamas 16 | Bahrain 17 | Bangladesh 18 | Barbados 19 | Belarus 20 | Belgium 21 | Belize 22 | Benin 23 | Bhutan 24 | Bolivia 25 | Bosnia and Herzegovina 26 | Botswana 27 | Brazil 28 | Brunei 29 | Bulgaria 30 | Burkina Faso 31 | Burundi 32 | Cabo Verde 33 | Cambodia 34 | Cameroon 35 | Canada 36 | Central African Republic 37 | Chad 38 | Chile 39 | China 40 | Colombia 41 | Comoros 42 | Congo 43 | Costa Rica 44 | Cote d\'Ivoire 45 | Croatia 46 | Cuba 47 | Cyprus 48 | Czech Republic 49 | Denmark 50 | Djibouti 51 | Dominica 52 | Dominican Republic 53 | Ecuador 54 | Egypt 55 | El Salvador 56 | Equatorial Guinea 57 | Eritrea 58 | Estonia 59 | Ethiopia 60 | Fiji 61 | Finland 62 | France 63 | Gabon 64 | Gambia 65 | Georgia 66 | Germany 67 | Ghana 68 | Greece 69 | Grenada 70 | Guatemala 71 | Guinea 72 | Guinea-Bissau 73 | Guyana 74 | Haiti 75 | Honduras 76 | Hungary 77 | Iceland 78 | India 79 | Indonesia 80 | Iran 81 | Iraq 82 | Ireland 83 | Israel 84 | Italy 85 | Jamaica 86 | Japan 87 | Jordan 88 | Kazakhstan 89 | Kenya 90 | Kiribati 91 | Kosovo 92 | Kuwait 93 | Kyrgyzstan 94 | Laos 95 | Latvia 96 | Lebanon 97 | Lesotho 98 | Liberia 99 | Libya 100 | Liechtenstein 101 | Lithuania 102 | Luxembourg 103 | Macedonia 104 | Madagascar 105 | Malawi 106 | Malaysia 107 | Maldives 108 | Mali 109 | Malta 110 | Marshall Islands 111 | Mauritania 112 | Mauritius 113 | Mexico 114 | Micronesia 115 | Moldova 116 | Monaco 117 | Mongolia 118 | Montenegro 119 | Morocco 120 | Mozambique 121 | Myanmar 122 | Namibia 123 | Nauru 124 | Nepal 125 | Netherlands 126 | New Zealand 127 | Nicaragua 128 | Niger 129 | Nigeria 130 | North Korea 131 | Norway 132 | Oman 133 | Pakistan 134 | Palau 135 | Palestine 136 | Panama 137 | Papua New Guinea 138 | Paraguay 139 | Peru 140 | Philippines 141 | Poland 142 | Portugal 143 | Qatar 144 | Romania 145 | Russia 146 | Rwanda 147 | Saint Kitts and Nevis 148 | Saint Lucia 149 | Saint Vincent and the Grenadines 150 | Samoa 151 | San Marino 152 | Sao Tome and Principe 153 | Saudi Arabia 154 | Senegal 155 | Serbia 156 | Seychelles 157 | Sierra Leone 158 | Singapore 159 | Slovakia 160 | Slovenia 161 | Solomon Islands 162 | Somalia 163 | South Africa 164 | South Korea 165 | South Sudan 166 | Spain 167 | Sri Lanka 168 | Sudan 169 | Suriname 170 | Swaziland 171 | Sweden 172 | Switzerland 173 | Syria 174 | Taiwan 175 | Tajikistan 176 | Tanzania 177 | Thailand 178 | Timor-Leste 179 | Togo 180 | Tonga 181 | Trinidad and Tobago 182 | Tunisia 183 | Turkey 184 | Turkmenistan 185 | Tuvalu 186 | Uganda 187 | Ukraine 188 | United Arab Emirates 189 | United Kingdom 190 | United States of America 191 | Uruguay 192 | Uzbekistan 193 | Vanuatu 194 | Vatican City 195 | Venezuela 196 | Vietnam 197 | Yemen 198 | Zambia 199 | Zimbabwe 200 | 201 | 202 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/ru/astrocode/sample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.sample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.4' 11 | 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /flow-layout-manager/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /flow-layout-manager/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 25 5 | defaultConfig { 6 | minSdkVersion 10 7 | targetSdkVersion 25 8 | versionCode 1 9 | versionName "1.0" 10 | 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | productFlavors { 21 | } 22 | } 23 | 24 | dependencies { 25 | implementation fileTree(include: ['*.jar'], dir: 'libs') 26 | testImplementation 'junit:junit:4.12' 27 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 28 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 29 | implementation 'com.android.support:recyclerview-v7:25.4.0' 30 | } 31 | -------------------------------------------------------------------------------- /flow-layout-manager/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /flow-layout-manager/src/androidTest/java/ru/astrocode/flm/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.flm; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("ru.astrocode.flm.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /flow-layout-manager/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /flow-layout-manager/src/main/java/ru/astrocode/flm/FLMFlowLayoutManager.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.flm; 2 | 3 | import android.graphics.PointF; 4 | import android.os.Bundle; 5 | import android.os.Parcelable; 6 | import android.support.annotation.NonNull; 7 | import android.support.v4.view.GravityCompat; 8 | import android.support.v7.widget.LinearSmoothScroller; 9 | import android.support.v7.widget.OrientationHelper; 10 | import android.support.v7.widget.RecyclerView; 11 | import android.util.Log; 12 | import android.view.Gravity; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | 16 | import java.util.ArrayList; 17 | 18 | /** 19 | * Created by Astrocode on 26.05.18. 20 | */ 21 | public class FLMFlowLayoutManager extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider { 22 | 23 | public final static int VERTICAL = OrientationHelper.VERTICAL, HORIZONTAL = OrientationHelper.HORIZONTAL; 24 | public final static int DEFAULT_COUNT_ITEM_IN_LINE = -1; 25 | 26 | private final static String TAG_FIRST_ITEM_ADAPTER_INDEX = "TAG_FIRST_ITEM_ADAPTER_INDEX"; 27 | private final static String TAG_FIRST_LINE_START_POSITION = "TAG_FIRST_LINE_START_POSITION"; 28 | 29 | private final static String ERROR_UNKNOWN_ORIENTATION = "Unknown orientation!"; 30 | private final static String ERROR_BAD_ARGUMENT = "Inappropriate field value!"; 31 | 32 | private int mGravity; 33 | private int mOrientation; 34 | 35 | private int mMaxItemsInLine; 36 | 37 | private int mSpacingBetweenItems; 38 | private int mSpacingBetweenLines; 39 | 40 | private FLMLayoutManagerHelper mLayoutManagerHelper; 41 | 42 | private ArrayList mCurrentLines; 43 | 44 | private int mFirstItemAdapterIndex; 45 | private int mFirstLineStartPosition; 46 | 47 | public FLMFlowLayoutManager(int orientation) { 48 | this(orientation, Gravity.START, DEFAULT_COUNT_ITEM_IN_LINE, 0, 0); 49 | } 50 | 51 | public FLMFlowLayoutManager(int orientation, int gravity) { 52 | this(orientation, gravity, DEFAULT_COUNT_ITEM_IN_LINE, 0, 0); 53 | } 54 | 55 | public FLMFlowLayoutManager(int orientation, int gravity, int spacingBetweenItems, int spacingBetweenLines) { 56 | this(orientation, gravity, DEFAULT_COUNT_ITEM_IN_LINE, spacingBetweenItems, spacingBetweenLines); 57 | } 58 | 59 | public FLMFlowLayoutManager(int orientation, int gravity, int maxItemsInLine, int spacingBetweenItems, int spacingBetweenLines) { 60 | mCurrentLines = new ArrayList<>(); 61 | 62 | mGravity = gravity; 63 | 64 | mFirstItemAdapterIndex = 0; 65 | mFirstLineStartPosition = -1; 66 | 67 | if (maxItemsInLine == 0 || maxItemsInLine < -1) { 68 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 69 | } 70 | 71 | mMaxItemsInLine = maxItemsInLine; 72 | 73 | if (mSpacingBetweenItems < 0) { 74 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 75 | } 76 | 77 | mSpacingBetweenItems = spacingBetweenItems; 78 | 79 | if (mSpacingBetweenLines < 0) { 80 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 81 | } 82 | 83 | mSpacingBetweenLines = spacingBetweenLines; 84 | 85 | if (orientation != HORIZONTAL && orientation != VERTICAL) { 86 | throw new IllegalArgumentException(ERROR_UNKNOWN_ORIENTATION); 87 | } 88 | mOrientation = orientation; 89 | 90 | mLayoutManagerHelper = FLMLayoutManagerHelper.createLayoutManagerHelper(this, orientation, mGravity); 91 | } 92 | 93 | @Override 94 | public RecyclerView.LayoutParams generateDefaultLayoutParams() { 95 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 96 | ViewGroup.LayoutParams.WRAP_CONTENT); 97 | } 98 | 99 | @Override 100 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { 101 | Line currentLine = null; 102 | 103 | if (mFirstLineStartPosition == -1) { 104 | mFirstLineStartPosition = mLayoutManagerHelper.getStartAfterPadding(); 105 | } 106 | 107 | int topOrLeft = mFirstLineStartPosition; 108 | 109 | detachAndScrapAttachedViews(recycler); 110 | mCurrentLines.clear(); 111 | 112 | for (int i = mFirstItemAdapterIndex; i < getItemCount(); i += currentLine.mItemsCount) { 113 | currentLine = addLineToEnd(i, topOrLeft, recycler); 114 | 115 | mCurrentLines.add(currentLine); 116 | 117 | topOrLeft = mSpacingBetweenLines + currentLine.mEndValueOfTheHighestItem; 118 | 119 | if (currentLine.mEndValueOfTheHighestItem > mLayoutManagerHelper.getEndAfterPadding()) { 120 | break; 121 | } 122 | } 123 | 124 | if (mFirstItemAdapterIndex > 0 && currentLine != null) { 125 | int availableOffset = currentLine.mEndValueOfTheHighestItem - mLayoutManagerHelper.getEnd() + mLayoutManagerHelper.getEndPadding(); 126 | 127 | if (availableOffset < 0) { 128 | if (mOrientation == VERTICAL) { 129 | scrollVerticallyBy(availableOffset, recycler, state); 130 | } else { 131 | scrollHorizontallyBy(availableOffset, recycler, state); 132 | } 133 | } 134 | } 135 | 136 | } 137 | 138 | @Override 139 | public void onRestoreInstanceState(Parcelable state) { 140 | Bundle data = (Bundle) state; 141 | 142 | mFirstItemAdapterIndex = data.getInt(TAG_FIRST_ITEM_ADAPTER_INDEX); 143 | mFirstLineStartPosition = data.getInt(TAG_FIRST_LINE_START_POSITION); 144 | } 145 | 146 | @Override 147 | public Parcelable onSaveInstanceState() { 148 | Bundle data = new Bundle(); 149 | 150 | data.putInt(TAG_FIRST_ITEM_ADAPTER_INDEX, mFirstItemAdapterIndex); 151 | data.putInt(TAG_FIRST_LINE_START_POSITION, mFirstLineStartPosition); 152 | 153 | return data; 154 | } 155 | 156 | /** 157 | * Change orientation of the layout manager 158 | * 159 | * @param orientation New orientation. 160 | */ 161 | 162 | public void setOrientation(int orientation) { 163 | 164 | if (orientation != HORIZONTAL && orientation != VERTICAL) { 165 | throw new IllegalArgumentException(ERROR_UNKNOWN_ORIENTATION); 166 | } 167 | 168 | if (orientation != mOrientation) { 169 | assertNotInLayoutOrScroll(null); 170 | 171 | mOrientation = orientation; 172 | mLayoutManagerHelper = FLMLayoutManagerHelper.createLayoutManagerHelper(this, orientation, mGravity); 173 | 174 | requestLayout(); 175 | } 176 | } 177 | 178 | public void setMaxItemsInLine(int maxItemsInLine) { 179 | 180 | if (maxItemsInLine <= 0) { 181 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 182 | } 183 | 184 | assertNotInLayoutOrScroll(null); 185 | 186 | mMaxItemsInLine = maxItemsInLine; 187 | 188 | requestLayout(); 189 | } 190 | 191 | public void setSpacingBetweenItems(int spacingBetweenItems) { 192 | 193 | if (spacingBetweenItems < 0) { 194 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 195 | } 196 | 197 | assertNotInLayoutOrScroll(null); 198 | 199 | mSpacingBetweenItems = spacingBetweenItems; 200 | 201 | requestLayout(); 202 | } 203 | 204 | public void setSpacingBetweenLines(int spacingBetweenLines) { 205 | 206 | if (spacingBetweenLines < 0) { 207 | throw new IllegalArgumentException(ERROR_BAD_ARGUMENT); 208 | } 209 | 210 | assertNotInLayoutOrScroll(null); 211 | 212 | mSpacingBetweenLines = spacingBetweenLines; 213 | 214 | requestLayout(); 215 | } 216 | 217 | /** 218 | * Return current orientation of the layout manager. 219 | */ 220 | public int getOrientation() { 221 | return mOrientation; 222 | } 223 | 224 | /** 225 | * Change gravity of the layout manager. 226 | * 227 | * @param gravity New gravity. 228 | */ 229 | public void setGravity(int gravity) { 230 | assertNotInLayoutOrScroll(null); 231 | 232 | if (gravity != mGravity) { 233 | mGravity = gravity; 234 | mLayoutManagerHelper.setGravity(gravity); 235 | 236 | requestLayout(); 237 | } 238 | } 239 | 240 | /** 241 | * Return current gravity of the layout manager. 242 | */ 243 | public int getGravity() { 244 | return mGravity; 245 | } 246 | 247 | /** 248 | * Add one line to the end of recyclerView. 249 | * 250 | * @param startAdapterIndex Adapter index of first item of new line. 251 | * @param start Start position(Top - if orientation is VERTICAL or Left - if orientation is HORIZONTAL) of the new line. 252 | * @return New line. 253 | */ 254 | @NonNull 255 | private Line addLineToEnd(int startAdapterIndex, int start, RecyclerView.Recycler recycler) { 256 | boolean isEndOfLine = false; 257 | 258 | int currentAdapterIndex = startAdapterIndex; 259 | int currentItemsSize = 0; 260 | int currentMaxValue = 0; 261 | 262 | Line line = new Line(); 263 | line.mStartValueOfTheHighestItem = start; 264 | 265 | while (!isEndOfLine && currentAdapterIndex < getItemCount()) { 266 | final View view = recycler.getViewForPosition(currentAdapterIndex); 267 | 268 | addView(view); 269 | 270 | measureChildWithMargins(view, 0, 0); 271 | 272 | final int widthOrHeight = mLayoutManagerHelper.getDecoratedMeasurementInOther(view); 273 | final int heightOrWidth = mLayoutManagerHelper.getDecoratedMeasurement(view); 274 | 275 | if (line.mItemsCount == mMaxItemsInLine || (currentItemsSize + widthOrHeight) >= mLayoutManagerHelper.getLineSize()) { 276 | isEndOfLine = true; 277 | 278 | if (currentItemsSize == 0) { 279 | currentMaxValue = heightOrWidth; 280 | 281 | line.mEndValueOfTheHighestItem = line.mStartValueOfTheHighestItem + currentMaxValue; 282 | line.mItemsCount++; 283 | } else { 284 | detachAndScrapView(view, recycler); 285 | continue; 286 | } 287 | } else { 288 | if (heightOrWidth > currentMaxValue) { 289 | currentMaxValue = heightOrWidth; 290 | line.mEndValueOfTheHighestItem = line.mStartValueOfTheHighestItem + currentMaxValue; 291 | } 292 | line.mItemsCount++; 293 | } 294 | 295 | currentItemsSize += widthOrHeight + mSpacingBetweenItems; 296 | 297 | currentAdapterIndex++; 298 | } 299 | 300 | layoutItemsToEnd(currentItemsSize - mSpacingBetweenItems, currentMaxValue, line.mItemsCount, line.mStartValueOfTheHighestItem); 301 | 302 | return line; 303 | } 304 | 305 | /** 306 | * Arrange of views from start to end. 307 | * 308 | * @param itemsSize Size(width - if orientation is VERTICAL or height - if orientation is HORIZONTAL) of all items(include spacing) in line. 309 | * @param maxItemHeightOrWidth Max item height(if VERTICAL) or width(if HORIZONTAL) in line. 310 | * @param itemsInLine Item count in line. 311 | * @param startValueOfTheHighestItem Start position(Top - if orientation is VERTICAL or Left - if orientation is HORIZONTAL) of the line. 312 | */ 313 | private void layoutItemsToEnd(int itemsSize, int maxItemHeightOrWidth, int itemsInLine, int startValueOfTheHighestItem) { 314 | int currentStart = mLayoutManagerHelper.getStartPositionOfFirstItem(itemsSize); 315 | 316 | int i = itemsInLine; 317 | int childCount = getChildCount(); 318 | int currentStartValue; 319 | 320 | while (i > 0) { 321 | final View view = getChildAt(childCount - i); 322 | 323 | final int widthOrHeight = mLayoutManagerHelper.getDecoratedMeasurementInOther(view); 324 | final int heightOrWidth = mLayoutManagerHelper.getDecoratedMeasurement(view); 325 | 326 | currentStartValue = startValueOfTheHighestItem + mLayoutManagerHelper.getPositionOfCurrentItem(maxItemHeightOrWidth, heightOrWidth); 327 | 328 | if (mOrientation == VERTICAL) { 329 | layoutDecoratedWithMargins(view, currentStart, currentStartValue, 330 | currentStart + widthOrHeight, currentStartValue + heightOrWidth); 331 | } else { 332 | layoutDecoratedWithMargins(view, currentStartValue, currentStart, 333 | currentStartValue + heightOrWidth, currentStart + widthOrHeight); 334 | } 335 | 336 | currentStart += widthOrHeight + mSpacingBetweenItems; 337 | 338 | i--; 339 | } 340 | } 341 | 342 | /** 343 | * Add one line to the start of recyclerView. 344 | * 345 | * @param startAdapterIndex Adapter index of first item of new line. 346 | * @param end End position(Bottom - if orientation is VERTICAL or Right - if orientation is HORIZONTAL) of the new line. 347 | * @return New line. 348 | */ 349 | @NonNull 350 | private Line addLineToStart(int startAdapterIndex, int end, RecyclerView.Recycler recycler) { 351 | boolean isEndOfLine = false; 352 | 353 | int currentAdapterIndex = startAdapterIndex; 354 | int currentItemsSize = 0; 355 | int currentMaxValue = 0; 356 | 357 | Line line = new Line(); 358 | line.mEndValueOfTheHighestItem = end; 359 | 360 | while (!isEndOfLine && currentAdapterIndex >= 0) { 361 | final View view = recycler.getViewForPosition(currentAdapterIndex); 362 | 363 | addView(view, 0); 364 | 365 | measureChildWithMargins(view, 0, 0); 366 | 367 | final int widthOrHeight = mLayoutManagerHelper.getDecoratedMeasurementInOther(view); 368 | final int heightOrWidth = mLayoutManagerHelper.getDecoratedMeasurement(view); 369 | 370 | if (line.mItemsCount == mMaxItemsInLine || (currentItemsSize + widthOrHeight) >= mLayoutManagerHelper.getLineSize()) { 371 | isEndOfLine = true; 372 | 373 | if (currentItemsSize == 0) { 374 | currentMaxValue = heightOrWidth; 375 | 376 | line.mStartValueOfTheHighestItem = line.mEndValueOfTheHighestItem - currentMaxValue; 377 | line.mItemsCount++; 378 | } else { 379 | detachAndScrapView(view, recycler); 380 | continue; 381 | } 382 | } else { 383 | if (heightOrWidth > currentMaxValue) { 384 | currentMaxValue = heightOrWidth; 385 | line.mStartValueOfTheHighestItem = line.mEndValueOfTheHighestItem - currentMaxValue; 386 | } 387 | line.mItemsCount++; 388 | } 389 | 390 | currentItemsSize += widthOrHeight + mSpacingBetweenItems; 391 | 392 | currentAdapterIndex--; 393 | } 394 | 395 | layoutItemsToStart(currentItemsSize - mSpacingBetweenItems, currentMaxValue, line.mItemsCount, line.mStartValueOfTheHighestItem); 396 | 397 | return line; 398 | } 399 | 400 | /** 401 | * Arrange of views from end to start. 402 | * 403 | * @param itemsSize Size(width - if orientation is VERTICAL or height - if orientation is HORIZONTAL) of all items(include spacing) in line. 404 | * @param maxItemHeightOrWidth Max item height(if VERTICAL) or width(if HORIZONTAL) in line. 405 | * @param itemsInLine Item count in line. 406 | * @param startValueOfTheHighestItem Start position(Top - if orientation is VERTICAL or Left - if orientation is HORIZONTAL) of the line. 407 | */ 408 | private void layoutItemsToStart(int itemsSize, int maxItemHeightOrWidth, int itemsInLine, int startValueOfTheHighestItem) { 409 | int currentStart = mLayoutManagerHelper.getStartPositionOfFirstItem(itemsSize); 410 | 411 | int i = 0; 412 | int currentStartValue; 413 | 414 | while (i < itemsInLine) { 415 | final View view = getChildAt(i); 416 | 417 | final int widthOrHeight = mLayoutManagerHelper.getDecoratedMeasurementInOther(view); 418 | final int heightOrWidth = mLayoutManagerHelper.getDecoratedMeasurement(view); 419 | 420 | currentStartValue = startValueOfTheHighestItem + mLayoutManagerHelper.getPositionOfCurrentItem(maxItemHeightOrWidth, heightOrWidth); 421 | 422 | if (mOrientation == VERTICAL) { 423 | layoutDecoratedWithMargins(view, currentStart, currentStartValue, 424 | currentStart + widthOrHeight, currentStartValue + heightOrWidth); 425 | 426 | } else { 427 | layoutDecoratedWithMargins(view, currentStartValue, currentStart, 428 | currentStartValue + heightOrWidth, currentStart + widthOrHeight); 429 | } 430 | 431 | currentStart += widthOrHeight + mSpacingBetweenItems; 432 | 433 | i++; 434 | } 435 | } 436 | 437 | /** 438 | * Adds to start (and delete from end) of the recyclerView the required number of lines depending on the offset. 439 | * 440 | * @param offset Original offset. 441 | * @param recycler 442 | * @return Real offset. 443 | */ 444 | private int addLinesToStartAndDeleteFromEnd(int offset, RecyclerView.Recycler recycler) { 445 | Line line = mCurrentLines.get(0); 446 | 447 | final int availableOffset = line.mStartValueOfTheHighestItem - mLayoutManagerHelper.getStartAfterPadding(); 448 | 449 | int currentOffset = availableOffset < offset ? offset : availableOffset; 450 | int adapterViewIndex = getPosition(getChildAt(0)) - 1; 451 | 452 | int startValueOfNewLine = line.mStartValueOfTheHighestItem - mSpacingBetweenLines; 453 | 454 | while (adapterViewIndex >= 0) { 455 | 456 | if (currentOffset <= offset) { 457 | deleteLinesFromEnd(offset, recycler); 458 | break; 459 | } else { 460 | deleteLinesFromEnd(currentOffset, recycler); 461 | } 462 | 463 | line = addLineToStart(adapterViewIndex, startValueOfNewLine, recycler); 464 | mCurrentLines.add(0, line); 465 | 466 | startValueOfNewLine = line.mStartValueOfTheHighestItem - mSpacingBetweenLines; 467 | 468 | currentOffset = line.mStartValueOfTheHighestItem; 469 | adapterViewIndex -= line.mItemsCount; 470 | } 471 | 472 | return currentOffset < offset ? offset : currentOffset; 473 | } 474 | 475 | /** 476 | * Removes lines from the end. The number of deleted lines depends on the offset. 477 | * 478 | * @param offset Current offset. 479 | * @param recycler 480 | */ 481 | private void deleteLinesFromEnd(int offset, RecyclerView.Recycler recycler) { 482 | Line lineToDel = mCurrentLines.get(mCurrentLines.size() - 1); 483 | 484 | while (lineToDel != null) { 485 | if (lineToDel.mStartValueOfTheHighestItem - offset > 486 | mLayoutManagerHelper.getEndAfterPadding()) { 487 | for (int i = 0; i < lineToDel.mItemsCount; i++) { 488 | removeAndRecycleView(getChildAt(getChildCount() - 1), recycler); 489 | } 490 | mCurrentLines.remove(lineToDel); 491 | lineToDel = mCurrentLines.get(mCurrentLines.size() - 1); 492 | } else { 493 | lineToDel = null; 494 | } 495 | } 496 | } 497 | 498 | /** 499 | * Adds to end (and delete from start) of the recyclerView the required number of lines depending on the offset. 500 | * 501 | * @param offset Original offset. 502 | * @param recycler 503 | * @return Real offset. 504 | */ 505 | private int addLinesToEndAndDeleteFromStart(int offset, RecyclerView.Recycler recycler) { 506 | Line line = mCurrentLines.get(mCurrentLines.size() - 1); 507 | 508 | int availableOffset = line.mEndValueOfTheHighestItem - mLayoutManagerHelper.getEnd() + mLayoutManagerHelper.getEndPadding(); 509 | 510 | int currentOffset = availableOffset > offset ? offset : availableOffset; 511 | int adapterViewIndex = getPosition(getChildAt(getChildCount() - 1)) + 1; 512 | 513 | int startValueOfNewLine = line.mEndValueOfTheHighestItem + mSpacingBetweenLines; 514 | 515 | while (adapterViewIndex < getItemCount()) { 516 | 517 | if (currentOffset >= offset) { 518 | deleteLinesFromStart(offset, recycler); 519 | break; 520 | } else { 521 | deleteLinesFromStart(currentOffset, recycler); 522 | } 523 | 524 | line = addLineToEnd(adapterViewIndex, startValueOfNewLine, recycler); 525 | mCurrentLines.add(line); 526 | 527 | startValueOfNewLine = line.mEndValueOfTheHighestItem + mSpacingBetweenLines; 528 | 529 | currentOffset = line.mEndValueOfTheHighestItem - mLayoutManagerHelper.getEnd(); 530 | adapterViewIndex += line.mItemsCount; 531 | } 532 | 533 | 534 | return currentOffset > offset ? offset : currentOffset; 535 | } 536 | 537 | /** 538 | * Removes lines from the start. The number of deleted lines depends on the offset. 539 | * 540 | * @param offset Current offset. 541 | * @param recycler 542 | */ 543 | private void deleteLinesFromStart(int offset, RecyclerView.Recycler recycler) { 544 | Line lineToDel = mCurrentLines.get(0); 545 | 546 | while (lineToDel != null) { 547 | if (lineToDel.mEndValueOfTheHighestItem - offset < 548 | mLayoutManagerHelper.getStartAfterPadding()) { 549 | for (int i = 0; i < lineToDel.mItemsCount; i++) { 550 | removeAndRecycleView(getChildAt(0), recycler); 551 | } 552 | mCurrentLines.remove(lineToDel); 553 | // mItemsInLines.add(lineToDel.mItemsCount); 554 | 555 | lineToDel = mCurrentLines.get(0); 556 | } else { 557 | lineToDel = null; 558 | } 559 | } 560 | } 561 | 562 | @Override 563 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { 564 | int offset = 0; 565 | 566 | if (getChildCount() > 0 && dy != 0) { 567 | 568 | if (dy > 0) { 569 | offset = addLinesToEndAndDeleteFromStart(dy, recycler); 570 | } else { 571 | offset = addLinesToStartAndDeleteFromEnd(dy, recycler); 572 | } 573 | 574 | if (offset != 0) { 575 | for (int i = 0; i < mCurrentLines.size(); i++) { 576 | mCurrentLines.get(i).offset(-offset); 577 | } 578 | offsetChildrenVertical(-offset); 579 | } 580 | 581 | final View firstView = getChildAt(0); 582 | 583 | mFirstLineStartPosition = mLayoutManagerHelper.getDecoratedStart(firstView); 584 | mFirstItemAdapterIndex = getPosition(firstView); 585 | } 586 | 587 | return offset; 588 | } 589 | 590 | @Override 591 | public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) { 592 | int offset = 0; 593 | 594 | if (getChildCount() > 0 && dx != 0) { 595 | 596 | if (dx > 0) { 597 | offset = addLinesToEndAndDeleteFromStart(dx, recycler); 598 | } else { 599 | offset = addLinesToStartAndDeleteFromEnd(dx, recycler); 600 | } 601 | 602 | if (offset != 0) { 603 | for (int i = 0; i < mCurrentLines.size(); i++) { 604 | mCurrentLines.get(i).offset(-offset); 605 | } 606 | offsetChildrenHorizontal(-offset); 607 | } 608 | 609 | final View firstView = getChildAt(0); 610 | 611 | mFirstLineStartPosition = mLayoutManagerHelper.getDecoratedStart(firstView); 612 | mFirstItemAdapterIndex = getPosition(firstView); 613 | } 614 | 615 | return offset; 616 | } 617 | 618 | @Override 619 | public boolean canScrollVertically() { 620 | return mOrientation == VERTICAL; 621 | } 622 | 623 | @Override 624 | public boolean canScrollHorizontally() { 625 | return mOrientation == HORIZONTAL; 626 | } 627 | 628 | @Override 629 | public void scrollToPosition(int position) { 630 | if (position >= 0 && position <= getItemCount() - 1) { 631 | mFirstItemAdapterIndex = position; 632 | mFirstLineStartPosition = -1; 633 | requestLayout(); 634 | } 635 | } 636 | 637 | @Override 638 | public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { 639 | LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()); 640 | linearSmoothScroller.setTargetPosition(position); 641 | 642 | startSmoothScroll(linearSmoothScroller); 643 | } 644 | 645 | @Override 646 | public PointF computeScrollVectorForPosition(int targetPosition) { 647 | if (getChildCount() == 0) { 648 | return null; 649 | } 650 | 651 | final int firstChildPos = getPosition(getChildAt(0)); 652 | final int direction = targetPosition < firstChildPos ? -1 : 1; 653 | 654 | if (mOrientation == HORIZONTAL) { 655 | return new PointF(direction, 0); 656 | } else { 657 | return new PointF(0, direction); 658 | } 659 | } 660 | 661 | /** 662 | * Representation of line in RecyclerView. 663 | */ 664 | private final static class Line { 665 | 666 | int mStartValueOfTheHighestItem; 667 | int mEndValueOfTheHighestItem; 668 | 669 | int mItemsCount; 670 | 671 | void offset(int offset) { 672 | mStartValueOfTheHighestItem += offset; 673 | mEndValueOfTheHighestItem += offset; 674 | } 675 | } 676 | 677 | /** 678 | * Orientation and gravity helper. 679 | */ 680 | private static abstract class FLMLayoutManagerHelper { 681 | 682 | RecyclerView.LayoutManager mLayoutManager; 683 | int mGravity; 684 | 685 | private FLMLayoutManagerHelper(RecyclerView.LayoutManager layoutManager, int gravity) { 686 | mLayoutManager = layoutManager; 687 | mGravity = gravity; 688 | } 689 | 690 | public void setGravity(int gravity) { 691 | mGravity = gravity; 692 | } 693 | 694 | public abstract int getEnd(); 695 | 696 | public abstract int getEndPadding(); 697 | 698 | public abstract int getLineSize(); 699 | 700 | public abstract int getEndAfterPadding(); 701 | 702 | public abstract int getStartAfterPadding(); 703 | 704 | public abstract int getDecoratedStart(View view); 705 | 706 | public abstract int getDecoratedMeasurement(View view); 707 | 708 | public abstract int getDecoratedMeasurementInOther(View view); 709 | 710 | public abstract int getStartPositionOfFirstItem(int itemsSize); 711 | 712 | public abstract int getPositionOfCurrentItem(int itemMaxSize, int itemSize); 713 | 714 | public static FLMLayoutManagerHelper createLayoutManagerHelper(RecyclerView.LayoutManager layoutManager, int orientation, int gravity) { 715 | switch (orientation) { 716 | case VERTICAL: 717 | return createVerticalLayoutManagerHelper(layoutManager, gravity); 718 | case HORIZONTAL: 719 | return createHorizontalLayoutManagerHelper(layoutManager, gravity); 720 | default: 721 | throw new IllegalArgumentException(ERROR_UNKNOWN_ORIENTATION); 722 | } 723 | } 724 | 725 | private static FLMLayoutManagerHelper createVerticalLayoutManagerHelper(final RecyclerView.LayoutManager layoutManager, int gravity) { 726 | return new FLMLayoutManagerHelper(layoutManager, gravity) { 727 | @Override 728 | public int getEnd() { 729 | return mLayoutManager.getHeight(); 730 | } 731 | 732 | @Override 733 | public int getEndPadding() { 734 | return mLayoutManager.getPaddingBottom(); 735 | } 736 | 737 | @Override 738 | public int getLineSize() { 739 | return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() - mLayoutManager.getPaddingRight(); 740 | } 741 | 742 | @Override 743 | public int getEndAfterPadding() { 744 | return mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom(); 745 | } 746 | 747 | @Override 748 | public int getStartAfterPadding() { 749 | return mLayoutManager.getPaddingTop(); 750 | } 751 | 752 | @Override 753 | public int getDecoratedStart(View view) { 754 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 755 | 756 | return this.mLayoutManager.getDecoratedTop(view) - params.topMargin; 757 | } 758 | 759 | @Override 760 | public int getDecoratedMeasurement(View view) { 761 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 762 | 763 | return mLayoutManager.getDecoratedMeasuredHeight(view) + params.topMargin + params.bottomMargin; 764 | } 765 | 766 | @Override 767 | public int getDecoratedMeasurementInOther(View view) { 768 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 769 | 770 | return mLayoutManager.getDecoratedMeasuredWidth(view) + params.leftMargin + params.rightMargin; 771 | } 772 | 773 | @Override 774 | public int getStartPositionOfFirstItem(int itemsSize) { 775 | int horizontalGravity = GravityCompat.getAbsoluteGravity(mGravity, mLayoutManager.getLayoutDirection()); 776 | int startPosition; 777 | 778 | switch (horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 779 | case Gravity.RIGHT: 780 | startPosition = mLayoutManager.getWidth() - mLayoutManager.getPaddingRight() - itemsSize; 781 | break; 782 | case Gravity.CENTER_HORIZONTAL: 783 | startPosition = (mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft() - mLayoutManager.getPaddingRight()) / 2 784 | - itemsSize / 2; 785 | break; 786 | default: 787 | startPosition = mLayoutManager.getPaddingLeft(); 788 | break; 789 | } 790 | 791 | return startPosition; 792 | } 793 | 794 | @Override 795 | public int getPositionOfCurrentItem(int itemMaxSize, int itemSize) { 796 | int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 797 | int currentPosition; 798 | 799 | switch (verticalGravity) { 800 | case Gravity.CENTER_VERTICAL: 801 | currentPosition = itemMaxSize / 2 - itemSize / 2; 802 | break; 803 | case Gravity.BOTTOM: 804 | currentPosition = itemMaxSize - itemSize; 805 | break; 806 | default: 807 | currentPosition = 0; 808 | break; 809 | } 810 | return currentPosition; 811 | } 812 | 813 | }; 814 | } 815 | 816 | private static FLMLayoutManagerHelper createHorizontalLayoutManagerHelper(RecyclerView.LayoutManager layoutManager, int gravity) { 817 | return new FLMLayoutManagerHelper(layoutManager, gravity) { 818 | 819 | @Override 820 | public int getEnd() { 821 | return mLayoutManager.getWidth(); 822 | } 823 | 824 | @Override 825 | public int getEndPadding() { 826 | return mLayoutManager.getPaddingRight(); 827 | } 828 | 829 | @Override 830 | public int getLineSize() { 831 | return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() - mLayoutManager.getPaddingBottom(); 832 | } 833 | 834 | @Override 835 | public int getEndAfterPadding() { 836 | return mLayoutManager.getWidth() - mLayoutManager.getPaddingRight(); 837 | } 838 | 839 | @Override 840 | public int getStartAfterPadding() { 841 | return mLayoutManager.getPaddingLeft(); 842 | } 843 | 844 | @Override 845 | public int getDecoratedStart(View view) { 846 | RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); 847 | 848 | return this.mLayoutManager.getDecoratedLeft(view) - params.leftMargin; 849 | } 850 | 851 | @Override 852 | public int getDecoratedMeasurement(View view) { 853 | return mLayoutManager.getDecoratedMeasuredWidth(view); 854 | } 855 | 856 | @Override 857 | public int getDecoratedMeasurementInOther(View view) { 858 | return mLayoutManager.getDecoratedMeasuredHeight(view); 859 | } 860 | 861 | @Override 862 | public int getStartPositionOfFirstItem(int itemsSize) { 863 | int verticalGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK; 864 | int startPosition; 865 | 866 | switch (verticalGravity) { 867 | case Gravity.CENTER_VERTICAL: 868 | startPosition = (mLayoutManager.getHeight() - mLayoutManager.getPaddingTop() - mLayoutManager.getPaddingBottom()) / 2 869 | - itemsSize / 2; 870 | break; 871 | case Gravity.BOTTOM: 872 | startPosition = mLayoutManager.getHeight() - mLayoutManager.getPaddingBottom() - itemsSize; 873 | break; 874 | default: 875 | startPosition = mLayoutManager.getPaddingTop(); 876 | break; 877 | } 878 | 879 | return startPosition; 880 | } 881 | 882 | @Override 883 | public int getPositionOfCurrentItem(int itemMaxSize, int itemSize) { 884 | int horizontalGravity = GravityCompat.getAbsoluteGravity(mGravity, mLayoutManager.getLayoutDirection()); 885 | int currentPosition; 886 | 887 | switch (horizontalGravity & Gravity.HORIZONTAL_GRAVITY_MASK) { 888 | case Gravity.CENTER_HORIZONTAL: 889 | currentPosition = itemMaxSize / 2 - itemSize / 2; 890 | break; 891 | case Gravity.RIGHT: 892 | currentPosition = itemMaxSize - itemSize; 893 | break; 894 | default: 895 | currentPosition = 0; 896 | break; 897 | } 898 | return currentPosition; 899 | } 900 | }; 901 | } 902 | } 903 | } -------------------------------------------------------------------------------- /flow-layout-manager/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | FlowLayoutManager 3 | 4 | -------------------------------------------------------------------------------- /flow-layout-manager/src/test/java/ru/astrocode/flm/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package ru.astrocode.flm; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /flow_layout_manager_vertical.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Astrocode011235813/FlowLayoutManager/d1cef697e9458c6651e7f40d438d270b2bc6d709/flow_layout_manager_vertical.gif -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 26 10:28:15 MSK 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':flow-layout-manager' 2 | --------------------------------------------------------------------------------