Repository: alibaba/vlayout Branch: master Commit: 00d6be0a1fa8 Files: 87 Total size: 818.4 KB Directory structure: gitextract_l0k2lwjr/ ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README-ch.md ├── README.md ├── build.gradle ├── docs/ │ ├── ATTRIBUTES-ch.md │ ├── ATTRIBUTES.md │ └── VLayoutFAQ.md ├── examples/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── android/ │ │ └── vlayout/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── alibaba/ │ │ └── android/ │ │ └── vlayout/ │ │ └── example/ │ │ ├── DebugActivity.java │ │ ├── MainActivity.java │ │ ├── OnePlusNLayoutActivity.java │ │ ├── RootActivity.java │ │ ├── TestActivity.java │ │ └── VLayoutActivity.java │ └── res/ │ ├── drawable/ │ │ ├── border_bg.xml │ │ └── item_background.xml │ ├── layout/ │ │ ├── card_item.xml │ │ ├── item.xml │ │ ├── main_activity.xml │ │ └── view_pager.xml │ └── values/ │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── gradle/ │ └── wrapper/ │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── gradlew ├── gradlew.bat ├── jcenterDeploy.gradle ├── jcenterInstall.gradle ├── settings.gradle └── vlayout/ ├── .gitignore ├── DESIGN.md ├── build.gradle ├── jacoco.gradle ├── proguard-rules.pro └── src/ ├── androidTest/ │ └── java/ │ └── com/ │ └── alibaba/ │ └── android/ │ └── vlayout/ │ ├── ViewHolderHelper.java │ └── VirtualLayoutManagerTest.java └── main/ ├── AndroidManifest.xml ├── java/ │ └── com/ │ └── alibaba/ │ └── android/ │ └── vlayout/ │ ├── Cantor.java │ ├── DelegateAdapter.java │ ├── ExposeLinearLayoutManagerEx.java │ ├── LayoutHelper.java │ ├── LayoutHelperFinder.java │ ├── LayoutManagerHelper.java │ ├── LayoutView.java │ ├── LayoutViewFactory.java │ ├── MismatchChildCountException.java │ ├── OrientationHelperEx.java │ ├── Range.java │ ├── RangeLayoutHelperFinder.java │ ├── RecyclablePagerAdapter.java │ ├── SortedList.java │ ├── VirtualLayoutAdapter.java │ ├── VirtualLayoutManager.java │ ├── extend/ │ │ ├── InnerRecycledViewPool.java │ │ ├── LayoutManagerCanScrollListener.java │ │ ├── PerformanceMonitor.java │ │ ├── ViewLifeCycleHelper.java │ │ └── ViewLifeCycleListener.java │ └── layout/ │ ├── AbstractFullFillLayoutHelper.java │ ├── BaseLayoutHelper.java │ ├── ColumnLayoutHelper.java │ ├── DefaultLayoutHelper.java │ ├── FixAreaAdjuster.java │ ├── FixAreaLayoutHelper.java │ ├── FixLayoutHelper.java │ ├── FloatLayoutHelper.java │ ├── GridLayoutHelper.java │ ├── LayoutChunkResult.java │ ├── LinearLayoutHelper.java │ ├── MarginLayoutHelper.java │ ├── OnePlusNLayoutHelper.java │ ├── OnePlusNLayoutHelperEx.java │ ├── RangeGridLayoutHelper.java │ ├── RangeStyle.java │ ├── ScrollFixLayoutHelper.java │ ├── SingleLayoutHelper.java │ ├── StaggeredGridLayoutHelper.java │ └── StickyLayoutHelper.java └── res/ └── values/ └── ids.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Built application files *.apk *.ap_ # Files for the Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties .classpath .project .settings/ # Proguard folder generated by Eclipse proguard/ #Log Files *.log # OS X .DS_Store # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.ear *.iml # IDEA Files .idea/ out/ # MAVEN COMPILE Files target/ lint.xml deploy.gradle #jcenterDeploy.gradle #jcenterInstall.gradle ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing Guide Thank you for your attention to this project. Any bug, doc, examples and suggestion is appreciated. Here are some suggestions for you to create Pull Requests or open Issues. ## Branch Management ``` master ↑ develop <--- PR(bugfix/typo/3rd-PR) ↑ PR {type}/{description} ``` Branches * `master` branch * `master` is the latest (pre-)release branch. * `develop` branch * `develop` is the stable developing branch. [Github Release](https://help.github.com/articles/creating-releases/) is used to publish a (pre-)release version to `master` branch. * ***It's RECOMMENDED to commit bugfix or feature PR to `develop`***. * `{action}/{description}` branch * The branch for a developing or bugfix *. **DO NOT commit any PR to such a branch**. ## Branch Name ``` {action}/{description} ``` * `{action}`: * `feature`: used for developing a new feature. * `bugfix`: used for fixing bugs. * for example: `feature/add_flex_layouthelper` ## Commit Log ``` {action} {description} ``` * `{action}` * `add` * `update` or `bugfix` * `remove` * ... * `{description}` * It's ***RECOMMENDED*** to close issue with syntax `#123`, see [the doc](https://help.github.com/articles/closing-issues-via-commit-messages/) for more detail. It's useful for responding issues and release flow. for example: * `add new layout helper` * `fix #123, make compatible to recyclervew 25.2.0` * `remove abc` ## Issue * Please apply a proper label to an issue. * Suggested to use English. * Provide sufficient instructions to be able to reproduce the issue and make the issues clear. Such as phone model, system version, sdk version, crash logs and screen captures. ## Pull Request And Contributor License Agreement [Create Pull Requests](https://github.com/alibaba/vlayout/compare) here. In order to contribute code to vlayout, you (or the legal entity you represent) must sign the Contributor License Agreement (CLA). You can read and sign the [Alibaba CLA](https://cla-assistant.io/alibaba/vlayout) online. For CLA assistant service works properly, please make sure you have added email address that your commits linked to GitHub account. ## Code Style Guide ### Java & Android * Use [Google Java Style](https://google.github.io/styleguide/javaguide.html) as basic guidelines of java code. * Follow [AOSP Code Style](https://source.android.com/source/code-style.html) for rest of android related code style. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2016 Alibaba Group Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README-ch.md ================================================ # 注意,该项目停止维护!!! # vlayout [English Document](README.md) ## Tangram 相关开源库 ### Android + [Tangram-Android](https://github.com/alibaba/Tangram-Android) + [Virtualview-Android](https://github.com/alibaba/Virtualview-Android) + [vlayout](https://github.com/alibaba/vlayout) + [UltraViewPager](https://github.com/alibaba/UltraViewPager) ### iOS + [Tangram-iOS](https://github.com/alibaba/Tangram-iOS) + [Virtualview-iOS](https://github.com/alibaba/VirtualView-iOS) + [LazyScrollView](https://github.com/alibaba/lazyscrollview) VirtualLayout是一个针对RecyclerView的LayoutManager扩展, 主要提供一整套布局方案和布局间的组件复用的问题。 ## 设计思路 通过定制化的LayoutManager,接管整个RecyclerView的布局逻辑;LayoutManager管理了一系列LayoutHelper,LayoutHelper负责具体布局逻辑实现的地方;每一个LayoutHelper负责页面某一个范围内的组件布局;不同的LayoutHelper可以做不同的布局逻辑,因此可以在一个RecyclerView页面里提供异构的布局结构,这就能比系统自带的LinearLayoutManager、GridLayoutManager等提供更加丰富的能力。同时支持扩展LayoutHelper来提供更多的布局能力。 ## 主要功能 * 默认通用布局实现,解耦所有的View和布局之间的关系: Linear, Grid, 吸顶, 浮动, 固定位置等。 * LinearLayoutHelper: 线性布局 * GridLayoutHelper: Grid布局, 支持横向的colspan * FixLayoutHelper: 固定布局,始终在屏幕固定位置显示 * ScrollFixLayoutHelper: 固定布局,但之后当页面滑动到该图片区域才显示, 可以用来做返回顶部或其他书签等 * FloatLayoutHelper: 浮动布局,可以固定显示在屏幕上,但用户可以拖拽其位置 * ColumnLayoutHelper: 栏格布局,都布局在一排,可以配置不同列之间的宽度比值 * SingleLayoutHelper: 通栏布局,只会显示一个组件View * OnePlusNLayoutHelper: 一拖N布局,可以配置1-5个子元素 * StickyLayoutHelper: stikcy布局, 可以配置吸顶或者吸底 * StaggeredGridLayoutHelper: 瀑布流布局,可配置间隔高度/宽度 * 上述默认实现里可以大致分为两类:一是非fix类型布局,像线性、Grid、栏格等,它们的特点是布局在整个页面流里,随页面滚动而滚动;另一类就是fix类型的布局,它们的子节点往往不随页面滚动而滚动。 * 所有除布局外的组件复用,VirtualLayout将用来管理大的模块布局组合,扩展了RecyclerView,使得同一RecyclerView内的组件可以复用,减少View的创建和销毁过程。 ## 使用 **虽然 vlayout 布局灵活,然而 API 相对原始,手工维护数据及 LayoutHelper 比较麻烦,强烈建议大家使用 [Tangram-Android](https://github.com/alibaba/Tangram-Android) 来间接使用 vlayout,Tangram 具备 vlayout 里所有的功能,且隐藏了细节,通过数据配置即可搭建页面,能避免绝大多数 Issue 里提到的问题,而且重大更新维护主要基于 Tangram,包括局部刷新、响应式接口等。** 版本请参考 [release 说明](https://github.com/alibaba/vlayout/releases)里的最新版本,最新的 aar 都会发布到 jcenter 和 MavenCentral 上,确保配置了这两个仓库源,然后引入aar依赖: ``` gradle compile ('com.alibaba.android:vlayout:1.2.8@aar') { transitive = true } ``` 或者maven: pom.xml ``` xml com.alibaba.android vlayout 1.2.8 aar ``` 初始化```LayoutManager``` ``` java final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this); recyclerView.setLayoutManager(layoutManager); ``` 设置回收复用池大小,(如果一屏内相同类型的 View 个数比较多,需要设置一个合适的大小,防止来回滚动时重新创建 View): ``` java RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); viewPool.setMaxRecycledViews(0, 10); ``` **注意:上述示例代码里只针对type=0的item设置了复用池的大小,如果你的页面有多种type,需要为每一种类型的分别调整复用池大小参数。** 加载数据时有两种方式: * 一种是使用 ```DelegateAdapter```, 可以像平常一样写继承自```DelegateAdapter.Adapter```的Adapter, 只比之前的Adapter需要多重载```onCreateLayoutHelper```方法。 其他的和默认Adapter一样。 ``` java DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, hasConsistItemType); recycler.setAdapter(delegateAdapter); // 之后可以通过 setAdapters 或 addAdapter方法添加DelegateAdapter.Adapter delegateAdapter.setAdapters(adapters); // or CustomAdapter adapter = new CustomAdapter(data, new GridLayoutHelper()); delegateAdapter.addAdapter(adapter); // 如果数据有变化,调用自定义 adapter 的 notifyDataSetChanged() adapter.notifyDataSetChanged(); ``` **注意:当hasConsistItemType=true的时候,不论是不是属于同一个子adapter,相同类型的item都能复用。表示它们共享一个类型。 当hasConsistItemType=false的时候,不同子adapter之间的类型不共享** * 另一种是当业务有自定义的复杂需求的时候, 可以继承自```VirtualLayoutAdapter```, 实现自己的Adapter ``` java public class MyAdapter extends VirtualLayoutAdapter { ...... } MyAdapter myAdapter = new MyAdapter(layoutManager); //构造 layoutHelper 列表 List helpers = new LinkedList<>(); GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(4); gridLayoutHelper.setItemCount(25); helpers.add(gridLayoutHelper); GridLayoutHelper gridLayoutHelper2 = new GridLayoutHelper(2); gridLayoutHelper2.setItemCount(25); helpers.add(gridLayoutHelper2); //将 layoutHelper 列表传递给 adapter myAdapter.setLayoutHelpers(helpers); //将 adapter 设置给 recyclerView recycler.setAdapter(myAdapter); ``` 在这种情况下,需要使用者注意在当```LayoutHelpers```的结构或者数据数量等会影响到布局的元素变化时,需要主动调用```setLayoutHelpers```去更新布局模式。 另外如果你的应用有混淆配置,请为vlayout添加一下防混淆配置: ``` -keepattributes InnerClasses -keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; } -keep class android.support.v7.widget.RecyclerView$LayoutParams { *; } -keep class android.support.v7.widget.RecyclerView$ViewHolder { *; } -keep class android.support.v7.widget.ChildHelper { *; } -keep class android.support.v7.widget.ChildHelper$Bucket { *; } -keep class android.support.v7.widget.RecyclerView$LayoutManager { *; } ``` # Demo ![](http://img3.tbcdn.cn/L1/461/1/1b9bfb42009047f75cee08ae741505de2c74ac0a) [Demo工程](https://github.com/alibaba/vlayout/tree/master/examples) # FAQ 使用之前或者碰到问题的时候,建议先看看其他[FAQ](docs/VLayoutFAQ.md)。 # 布局属性 每一种layoutHelper都有自己的布局属性来控制布局样式,详情请参考[文档](docs/ATTRIBUTES-ch.md)。 # 贡献代码 在提 Issue 或者 PR 之前,建议先阅读[Contributing Guide](CONTRIBUTING.md)。按照规范提建议。 # 开源许可证 vlayout遵循MIT开源许可证协议。 ================================================ FILE: README.md ================================================ # Attention. This project is not maintained any more !!! # vlayout [中文文档](README-ch.md) ## Projects of Tangram ### Android + [Tangram-Android](https://github.com/alibaba/Tangram-Android) + [Virtualview-Android](https://github.com/alibaba/Virtualview-Android) + [vlayout](https://github.com/alibaba/vlayout) + [UltraViewPager](https://github.com/alibaba/UltraViewPager) ### iOS + [Tangram-iOS](https://github.com/alibaba/Tangram-iOS) + [Virtualview-iOS](https://github.com/alibaba/VirtualView-iOS) + [LazyScrollView](https://github.com/alibaba/lazyscrollview) Project `vlayout` is a powerful LayoutManager extension for RecyclerView, it provides a group of layouts for RecyclerView. Make it able to handle a complicate situation when grid, list and other layouts in the same recyclerview. ## Design By providing a custom LayoutManager to RecyclerView, VirtualLayout is able to layout child views with different style at single view elegantly. The custom LayoutManager manages a serial of layoutHelpers where each one implements the specific layout logic for a certain position range items. By the way, implementing your custom layoutHelper and provding it to the framework is also supported. ## Main Feature * Provide default common layout implementation, decouple the View and Layout. Default layout implementations are: * LinearLayoutHelper: provide linear layout as LinearLayoutManager. * GridLayoutHelper: provide grid layout as GridLayoutManager, but with more feature. * FixLayoutHelper: fix the view at certain position of screen, the view does not scroll with whole page. * ScrollFixLayoutHelper: fix the view at certain position of screen, but the view does not show until it scrolls to it position. * FloatLayoutHelper: float the view on top of page, user can drag and drop it. * ColumnLayoutHelper: perform like GridLayoutHelper but layouts all child views in one line. * SingleLayoutHelper: contain only one child view. * OnePlusNLayoutHelper: a custom layout with one child view layouted at left and the others at right, you may not need this. * StickyLayoutHelper: scroll the view when its position is inside the screen, but fix the view at start or end when its position is outside the screen. * StaggeredGridLayoutHelper: provide waterfall like layout as StaggeredGridLayoutManager. * LayoutHelpers provided by default can be generally divided into two categories. One is non-fix LayoutHelper such as LinearLayoutHelper, GridLayoutHelper, etc which means the children of these LayoutHelper will be layouted in the flow of parent container and will be scrolled with the container scrolling. While the other is fix LayoutHelper which means the child of these is always fix in parent container. ## Usage ### Import Library Please find the latest version in [release notes](https://github.com/alibaba/vlayout/releases). The newest version has been upload to jcenter and MavenCentral, make sure you have added at least one of these repositories. As follow: For gradle: ``` gradle compile ('com.alibaba.android:vlayout:1.2.8@aar') { transitive = true } ``` Or in maven: pom.xml ``` xml com.alibaba.android vlayout 1.2.8 aar ``` ### Initialize LayoutManager ``` java final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view); final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this); recyclerView.setLayoutManager(layoutManager); ``` ### Initialize recycled pool's size Provide a reasonable recycled pool's size to your recyclerView, since the default value may not meet your situation and cause re-create views when scrolling. ``` java RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); viewPool.setMaxRecycledViews(0, 10); ``` **Attention: the demo code above only modify the recycle pool size of item with type = 0, it you has more than one type in your adapter, you should update recycle pool size for each type.** ### Set Adapters * You can use `DelegateAdapter` for as a root adapter to make combination of your own adapters. Just make it extend ```DelegateAdapter.Adapter``` and overrides ```onCreateLayoutHelper``` method. ``` java DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, hasConsistItemType); recycler.setAdapter(delegateAdapter); // Then you can set sub- adapters delegateAdapter.setAdapters(adapters); // or CustomAdapter adapter = new CustomAdapter(data, new GridLayoutHelper()); delegateAdapter.addAdapter(adapter); // call notify change when data changes adapter.notifyDataSetChanged(); ``` **Attention: When `hasConsistItemType = true`, items with same type value in different sub-adapters share the same type, their view would be reused during scroll. When `hasConsistItemType = false`, items with same type value in different sub-adapters do not share the same type internally.** * The other way to set adapter is extending ```VirtualLayoutAdapter``` and implementing it to make deep combination to your business code. ``` java public class MyAdapter extends VirtualLayoutAdapter { ...... } MyAdapter myAdapter = new MyAdapter(layoutManager); //create layoutHelper list List helpers = new LinkedList<>(); GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(4); gridLayoutHelper.setItemCount(25); helpers.add(gridLayoutHelper); GridLayoutHelper gridLayoutHelper2 = new GridLayoutHelper(2); gridLayoutHelper2.setItemCount(25); helpers.add(gridLayoutHelper2); //set layoutHelper list to adapter myAdapter.setLayoutHelpers(helpers); //set adapter to recyclerView recycler.setAdapter(myAdapter); ``` In this way, one thing you should note is that you should call ```setLayoutHelpers``` when the data of Adapter changes. ### Config proguard Add following configs in your proguard file if your app is released with proguard. ``` -keepattributes InnerClasses -keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; } -keep class android.support.v7.widget.RecyclerView$LayoutParams { *; } -keep class android.support.v7.widget.RecyclerView$ViewHolder { *; } -keep class android.support.v7.widget.ChildHelper { *; } -keep class android.support.v7.widget.ChildHelper$Bucket { *; } -keep class android.support.v7.widget.RecyclerView$LayoutManager { *; } ``` # Demo ![](http://img3.tbcdn.cn/L1/461/1/1b9bfb42009047f75cee08ae741505de2c74ac0a) [Demo Project](https://github.com/alibaba/vlayout/tree/master/examples) # FAQ Read FAQ(In Chinese language only now) before submitting issue: [FAQ](docs/VLayoutFAQ.md)。 # Layout Attributes Each layoutHelper has a few attributes to control its layout style. See [this](docs/ATTRIBUTES.md) to read more. # Contributing Before you open an issue or create a pull request, please read [Contributing Guide](CONTRIBUTING.md) first. # LICENSE Vlayout is available under the MIT license. ================================================ FILE: build.gradle ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url "http://oss.jfrog.org/oss-snapshot-local/" } mavenCentral() jcenter() } dependencies { classpath 'com.android.tools.build:gradle:2.2.2' classpath 'com.github.xfumihiro.view-inspector:view-inspector-plugin:0.1.1' classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.4' classpath 'org.jfrog.buildinfo:build-info-extractor-gradle:4.0.0' classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' } } allprojects { repositories { maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' } maven { url "http://oss.jfrog.org/oss-snapshot-local/" } jcenter() mavenLocal() } } ================================================ FILE: docs/ATTRIBUTES-ch.md ================================================ 为了提供丰富的布局能力,我们为`LayoutHelper`设计了一系列布局属性,用来控制布局逻辑和样式。这里介绍这些属性的概念和用法。 [Englist Document](ATTRIBUTES.md) # margin, padding Margin, padding就是外边距、内边距,概念与Android系统的margin, padding一样,但也有不同的地方: + 它不是整个`RecyclerView`页面的margin和padding,它是每一块`LayoutHelper`所负责的区域的margin和padding。 + 一个页面里可以有多个`LayoutHelper`,意味着不同`LayoutHelper`可以设置不同的margin和padding。 + `LayoutHelper`的margin和padding与页面`RecyclerView`的margin和padding可以共存。 + 目前主要针对非fix类型的`LayoutHelper`实现了margin和padding,fix类型`LayoutHelper`内部没有相对位置关系,不处理边距。 ![margin-padding](images/MarginPadding.png) ### 接口 对于`LayoutHelper`,调用 `public void setPadding(int leftPadding, int topPadding, int rightPadding, int bottomPadding)` `public void setMargin(int leftMargin, int topMargin, int rightMargin, int bottomMargin)` # bgColor, bgImg 背景颜色或者背景图,这其实不是布局属性,但是由于在vlayout对视图进行了直接布局,不同区域的视图的父节点都是`RecyclerView`,如果想要针对某一块区域单独绘制背景,就很难做到了。vlayout框架对此做了特殊处理,对于非fix、非float类型的`LayoutHelper`,支持配置背景色或背景图。同样目前主要针对非fix类型的`LayoutHelper`实现这个特性。 ![background](images/Background.png) ### 接口 使用背景色 `public void setBgColor(int bgColor)` 使用背景图 首先为`LayoutManager`提供一个`ImageView`简单工厂 ``` this.mLayoutManager.setLayoutViewFactory(new LayoutViewFactory() { @Override public opinion generateLayoutView(@NonNull Context context) { return new XXImageView(context); } }); ``` 再为`LayoutHelper`提设置图片加载的`Listener` ``` baseHelper.setLayoutViewBindListener(new BindListener(imgUrl)); baseHelper.setLayoutViewUnBindListener(new UnbindListener(imgUrl)); private static class BindListener implements BaseLayoutHelper.LayoutViewBindListener { private String imgUrl; public BindListener(String imgUrl) { this.imgUrl = imgUrl; } @Override public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //loading image } } private static class UnbindListener implements BaseLayoutHelper.LayoutViewUnBindListener { private String imgUrl; public UnbindListener(String imgUrl) { this. imgUrl = imgUrl; } @Override public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //cancel loading image } } ``` # aspectRatio 为了保证布局过程中视图的高度一致,我们设计了aspectRatio属性,它是宽与高的比例,`LayoutHelper`里有aspectRatio属性,通过vlayout添加的视图的`LayoutParams`也有aspectRatio属性,后者的优先级比前者高,但含义不一样。 + `LayoutHelper`定义的aspectRatio,指的是一行视图整体的宽度与高度之比,当然整体的宽度是减去了`RecyclerView和`对应的`LayoutHelper`的margin, padding。 + 视图的`LayoutParams`定义的aspectRatio,指的是在`LayoutHelper`计算出视图宽度之后,用来确定视图高度时使用的,它会覆盖通过`LayoutHelper`的aspectRatio计算出来的视图高度,因此具备更高优先级。 ![aspectRatio](images/AspectRatio.png) ### 接口 对于`LayoutHelper`,调用 `public void setAspectRatio(float aspectRatio)` 对于`LayoutParams`,调用 `((VirutalLayoutManager.LayoutParams) layoutParams).mAspectRatio` # dividerHeight `LinearLayoutHelper`的属性,`LinearLayoutHelper`是像`ListView`一样的线性布局,dividerHeight就是每个组件之间的间距。 ![dividerHeight](images/DividerHeight.png) ### 接口 对于`LinearLayoutHelper`,调用 `public void setDividerHeight(int dividerHeight)` # weights `ColumnLayoutHelper`, `GridLayoutHelper`的属性,它们都是提供网格状的布局能力,**建议使用`GridLayoutHelper`**,它的能力更加强大,参考下文介绍。默认情况下,每个网格中每一列的宽度是一样的,通过weights属性,可以指定让每一列的宽度成比例分配,就像`LinearLayout`的weight属性一样。 weights属性是一个float数组,每一项代表某一列占父容器宽度的百分比,总和建议是100,否则布局会超出容器宽度;如果布局中有4列,那么weights的长度也应该是4;长度大于4,多出的部分不参与宽度计算;如果小于4,不足的部分默认平分剩余的空间。 ![weights](images/Weights.png) ### 接口 对于`ColumnLayoutHelper`, `GridLayoutHelper`,调用 `public void setWeights(float[] weights)` # vGap, hGap `GridLayoutHelper`与`StaggeredGridLayoutHelper`都有这两个属性,分别控制视图之间的垂直间距和水平间距。 ![vgap-hgap](images/HGapVGap.png) ### 接口 对于`GridLayoutHelper`, `StaggeredGridLayoutHelper`,调用 `public void setHGap(int hGap)` `public void setVGap(int vGap)` # spanCount, spanSizeLookup `GridLayoutHelper`的属性,参考于系统的`GridLayoutManager`,spanCount表示网格的列数,默认情况下每一个视图都占用一个网格区域,但通过提供自定义的spanSizeLookUp,可以指定某个位置的视图占用多个网格区域。 ![spanCount-spanSize](images/SpanCountSpanSize.png) ### 接口 使用spanCount调用 `public void setSpanCount(int spanCount)` 使用spanSizeLookup `public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup)` # autoExpand `GridLayoutHelper`的属性,当一行里视图的个数少于spanCount值的时候,如果autoExpand为true,视图的总宽度会填满可用区域;否则会在屏幕上留空白区域。 ![autoExpand](images/AutoExpand.png) ### 接口 调用 `public void setAutoExpand(boolean isAutoExpand)` # lane `StaggeredGridLayoutHelper`中有这个属性,与`GridLayoutHelper`里的spanCount类似,控制瀑布流的列数。 ### 接口 调用 `public void setLane(int lane)` # fixAreaAdjuster fix类型的`LayoutHelper`,在可能需要设置一个相对父容器四个边的偏移量,比如整个页面里有一个固定的标题栏添加在vlayout容器上,vlayout内部的fix类型视图不希望与外部的标题有所重叠,那么就可以设置一个fixAreaAdjuster来做偏移。 ![fixAreaAdjuster](images/FixAreaAdjuster.png) ### 接口 调用 `public void setAdjuster(FixAreaAdjuster adjuster)` # alignType, x, y `FixLayoutHelper`, `ScrollFixLayoutHelper`, `FloatLayoutHelper`的属性,表示吸边时的基准位置,有四个取值,分别是`TOP_LEFT`, `TOP_RIGHT`, `BOTTOM_LEFT`, `BOTTOM_RIGHT`。`x`和`y`是相对这四个位置的偏移量,最终的偏移量还要受上述的fixAreaAdjuster影响。 + `TOP_LEFT`:基准位置是左上角,`x`是视图左边相对父容器的左边距偏移量,`y`是视图顶边相对父容器的上边距偏移量; + `TOP_RIGHT`:基准位置是右上角,`x`是视图右边相对父容器的右边距偏移量,`y`是视图顶边相对父容器的上边距偏移量; + `BOTTOM_LEFT`:基准位置是左下角,`x`是视图左边相对父容器的左边距偏移量,`y`是视图底边相对父容器的下边距偏移量; + `BOTTOM_RIGHT`:基准位置是右下角,`x`是视图右边相对父容器的右边距偏移量,`y`是视图底边相对父容器的下边距偏移量; ![alignTypeXY](images/AlignTypeXY.png) ### 接口 设置基准调用 `public void setAlignType(int alignType)` 设置偏移量调用 `public void setX(int x)` `public void setY(int y)` # showType `ScrollFixLayoutHelper`的属性,取值有`SHOW_ALWAYS`, `SHOW_ON_ENTER`, `SHOW_ON_LEAVE`。 + `SHOW_ALWAYS`:与`FixLayoutHelper`的行为一致,固定在某个位置; + `SHOW_ON_ENTER`:默认不显示视图,当页面滚动到这个视图的位置的时候,才显示; + `SHOW_ON_LEAVE`:默认不显示视图,当页面滚出这个视图的位置的时候显示; ![showType](images/ShowType.png) 调用 `public void setShowType(int showType)` # stickyStart, offset `StickyLayoutHelper`的属性,当视图的位置在屏幕范围内时,视图会随页面滚动而滚动;当视图的位置滑出屏幕时,`StickyLayoutHelper`会将视图固定在顶部(`stickyStart = true`)或者底部(`stickyStart = false`),固定的位置支持设置偏移量offset。 ![stickyStart](images/StickyStart.png) 调用 `public void setStickyStart(boolean stickyStart)` `public void setOffset(int offset)` ================================================ FILE: docs/ATTRIBUTES.md ================================================ Vlayout provides rich layout features, which are presented in the form of a serial of layout attributes technically. Here we introduce these attributes. [中文文档](ATTRIBUTES-ch.md) # margin, padding Margin, padding have the similar concepts as normal Android system's margin and padding. There are several difference you should know: + Here we say margin and padding, specifically mean ```LayoutHelper```'s margin and padding, not the ```RecyclerView```'s. + A ```RecyclerView``` could contains more than one layoutHelpers, which means each ```LayoutHelper``` is able to host its own margin and padding value. + ```LayoutHelper```'s margin and padding can coexist with ```RecyclerView```'s margin and padding. + Margin and padding is mainly supported by non-fix LayoutHelper. ![margin-padding](images/MarginPadding.png) ### API For ```LayoutHelper```,call ```public void setPadding(int leftPadding, int topPadding, int rightPadding, int bottomPadding)``` ```public void setMargin(int leftMargin, int topMargin, int rightMargin, int bottomMargin)``` # bgColor, bgImg Actually, background color or background image has nothing to do with layout logic. But in our framework, all child views has the same parent which is ```RecyclerView```. If you want to fill background to a certain area with a color or image, it's a hard job. Vlayout has done some effort to make it possible for non-fix ```LayoutHelper```, so it's explained here. ![background](images/Background.png) ### API Fill background with color, call ```public void setBgColor(int bgColor)``` Fill background with image, First, provide a simple factory to produce ```ImageView``` to ```LayoutManager```, ``` this.mLayoutManager.setLayoutViewFactory(new LayoutViewFactory() { @Override public View generateLayoutView(@NonNull Context context) { return new XXImageView(context); } }); ``` Second, set image load listeners to ```LayoutHelper```, ``` baseHelper.setLayoutViewBindListener(new BindListener(imgUrl)); baseHelper.setLayoutViewUnBindListener(new UnbindListener(imgUrl)); private static class BindListener implements BaseLayoutHelper.LayoutViewBindListener { private String imgUrl; public BindListener(String imgUrl) { this.imgUrl = imgUrl; } @Override public void onBind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //loading image } } private static class UnbindListener implements BaseLayoutHelper.LayoutViewUnBindListener { private String imgUrl; public UnbindListener(String imgUrl) { this. imgUrl = imgUrl; } @Override public void onUnbind(View layoutView, BaseLayoutHelper baseLayoutHelper) { //cancel loading image } } ``` # aspectRatio In order to uniform child view's height during layout, vlayout introduces aspectRatio. Generally the view's width is decided by ```LayoutHelper``` while its height is decided by the view itself, using aspectRatio allow ```LayoutHelper``` to decide view's height. Both ```LayoutHelper``` and ```LayoutParams``` of the view inside vlayout have this attribute. It generally means width/height ratio, but has slight difference in these two situations. The latter has a higher priority than the former. + The aspectRatio defined by ```LayoutHelper``` means the ratio of width of one entire row's view to height of this row's height. Here ```RecyclerView```'s margin padding and ```LayoutHelper```'s margin padding is not included in the width of entire row's view. + The aspectRatio defined in ```VirtualLayoutManager.LayoutParams``` means the ratio of width of this view to height of this view. The height calculated by this way will override ```LayoutHelper```'s calculation result, so it has higher priority. ![aspectRatio](images/AspectRatio.png) ### API For ```LayoutHelper```, call ```public void setAspectRatio(float aspectRatio)``` For ```LayoutParams```, call ```((VirutalLayoutManager.LayoutParams) layoutParams).mAspectRatio``` # dividerHeight ```LinearLayoutHelper```'s attribute. ```LinearLayoutHelper``` performs like ```ListView```, so as you know, dividerHeight is the gap between items. ![dividerHeight](images/DividerHeight.png) ### API For ```LinearLayoutHelper```, call ```public void setDividerHeight(int dividerHeight)``` # weights ```ColumnLayoutHelper``` and ```GridLayoutHelper```'s attribute, they both provide grid layout ability like ```GridLayoutManager```. **You are suggested to use ```GirdLayoutHelper```** since it is more powerful. By default, each column's width is equal and shares the available space. Setting weights allows each column's width occupies a certain percentage of available space. Just sounds like weight in ```LinearLayout```. This attribute is a float array, each item in array means the percentage of a column width. The sum of item value in array is 100, otherwise child view may be layouted outside the box. If there are four columns in this layout, the suggested length of weights is four, too. If length of weights array is larger than 4, the extra weight provided is ignored. If length of weights array is less than 4, the remaining views with no weight assigned share the remaining available space. ![weights](images/Weights.png) ### API For ```ColumnLayoutHelper```, ```GridLayoutHelper```, call ```public void setWeights(float[] weights)``` # vGap, hGap ```GridLayoutHelper``` and ```StaggeredGridLayoutHelper```'s attribute, vGap defines the vertical gap between child views, hGap defines the horizontal gap between child views. ![vgap-hgap](images/HGapVGap.png) ### API For ```GridLayoutHelper```, ```StaggeredGridLayoutHelper```, call ```public void setHGap(int hGap)``` ```public void setVGap(int vGap)``` # spanCount, spanSizeLookup ```GridLayoutHelper```'s attribute, concept borrowed from system's ```GridLayoutManager```. SpanCount specifies the column count, or grid count each line in other word. By default each item view in ```GridLayoutHelper``` occupies one grid, but with custom spanSizeLookup it allows one item view could occupy more than one grid space. ![spanCount-spanSize](images/SpanCountSpanSize.png) ### API Setting spanCount call ```public void setSpanCount(int spanCount)``` Setting spanSizeLookup call ```public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup)``` # autoExpand ```GridLayoutHelper```'s attribute. On the occasion of one line view's count is less than spanCount, if autoExpand is set to be true, these view will expand to fill all available space, otherwise they will leave some blank area. ![autoExpand](images/AutoExpand.png) ### API ```public void setAutoExpand(boolean isAutoExpand)``` # lane ```StaggeredGridLayoutHelper```'s attribute, performs like spanCount in ```GridLayoutHelper```. ### API ```public void setLane(int lane)``` # fixAreaAdjuster ```LayoutHelper``` with fix behavior may need an offset to four edges of parent container. For example, an activity has a vlayout container with fullscreen size, there's an title bar on top of vlayout's container which is not part of vlayout. While a view inside vlayout wants to be fixed at top of vlayout, so it needs an offset to top edge to avoid overlapping with title bar outside. ![fixAreaAdjuster](images/FixAreaAdjuster.png) ### API ```public void setAdjuster(FixAreaAdjuster adjuster)``` # alignType, x, y ```FixLayoutHelper```, ```ScrollFixLayoutHelper```, ```FloatLayoutHelper```'s attributes, alignType defines the base position of fix area, there are four available values: ```TOP_LEFT```, ```TOP_RIGHT```, ```BOTTOM_LEFT```, ```BOTTOM_RIGHT```. ```x``` and ```y``` are the horizontal offset and vertical offset to this base position. The final offset is calculated with fixAreaAdjuster in addition as former mentioned. + ```TOP_LEFT```: base position is top left corner of parent container, ```x``` is the offset from child view's left edge to parent's left edge, ```y``` is the offset from child view's top edge to parent's top edge; + ```TOP_RIGHT```: base position is top right corner of parent container, ```x``` is the offset from child view's right edge to parent's right edge, ```y``` is the offset from child view's top edge to parent's top edge; + ```BOTTOM_LEFT```: base position is bottom left corner of parent container, ```x``` is the offset from child view's left edge to parent's left edge, ```y``` is the offset from child view's bottom edge to parent's bottom edge; + ```BOTTOM_RIGHT```: base position is bottom right corner of parent container, ```x``` is the offset from child view's right edge to parent's right edge, ```y``` is the offset from child view's bottom edge to parent's bottom edge; ![alignTypeXY](images/AlignTypeXY.png) ### API Setting alignType call ```public void setAlignType(int alignType)``` Setting offset call ```public void setX(int x)``` ```public void setY(int y)``` # showType ```ScrollFixLayoutHelper```'s attribute, there are three available values: ```SHOW_ALWAYS```, ```SHOW_ON_ENTER```, ```SHOW_ON_LEAVE```. + ```SHOW_ALWAYS```: in this way, ```ScrollFixLayoutHelper``` performs the same as ```FixLayoutHelper```; + ```SHOW_ON_ENTER```: in this way, the child view in ```LayoutHelper``` does not show by default. It shows only when the page scroll to its position. + ```SHOW_ON_LEAVE```: in this way, the child view in ```LayoutHelper``` does not show by default, either. It shows only when the page scroll over its position. ![showType](images/ShowType.png) ### API ```public void setShowType(int showType)``` # stickyStart, offset ```StickyLayoutHelper```'s attribute, when child view's position is inside the screen visible area, the child view scrll with whole page; when child view position scrolls outside the screen, this ```LayoutHelper``` will fix the at top(```stickyStart = true```) or bottom(```stickyStart = false```). The fix position is also affected by offset value. ![stickyStart](images/StickyStart.png) ### API ```public void setStickyStart(boolean stickyStart)``` ```public void setOffset(int offset)``` ================================================ FILE: docs/VLayoutFAQ.md ================================================ # VLayout FAQ ## 组件复用的问题 比如碰到卡顿、类型转换异常等等,都有可能是复用的问题引起的。 在使用 `DelegateAdapter` 的时候,每一个 `LayoutHelper` 都对应于一个 `DelegateAdapter.Adapter`,一般情况下使用方只需要提供自定义的 `DelegateAdapter.Adapter`,然后按照正常的使用方式使用。 但这里有个问题,不同的 `DelegateAdapter.Adapter` 之间,他们的 itemType 是不是一样的?这里有一个选择:在 `DelegateAdapter` 的构造函数里有个 `hasConsistItemType` 参数(默认是 false ): 当 `hasConsistItemType=false` 的时候,即使不同 `DelegateAdapter.Adapter` 里返回的相同的 itemType,对于 `DelegateAdapter` 也会将它转换成不同的值,对于 `RecyclerView` 来说它们是不同的类型。 当 `hasConsistItemType=true` 的时候,不同的 `DelegateAdapter.Adapter` 之间返回相同的 itemType 的时候,他们之间是可以复用的。 因此如果没有处理好这一点,会导致 `ViewHolder` 的类型转换异常等 bug。有一篇更加详细的资料可参考:[PairFunction](http://pingguohe.net/2017/05/03/the-beauty-of-math-in-vlayout.html) 补充:后来发现一个 bug,当 `hasConsistItemType=false`,在同一位置数据变化,前后构造了不一样的 Adapter,它们返回的 itemType 一样,也会导致类型转换出错,详见:[#182](https://github.com/alibaba/vlayout/issues/182),目前采用人工保证返回不同的 itemType 来规避。 ## 设置每种类型回收复用池的大小 在 README 里写了这么一段 demo:`viewPool.setMaxRecycledViews(0, 10);`,很多人误以为只要这么设置就可以了,实际上有多少种类型的 itemType,就得为它们分别设置复用池大小。比如: ``` viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); viewPool.setMaxRecycledViews(0, 5); viewPool.setMaxRecycledViews(1, 5); viewPool.setMaxRecycledViews(2, 5); viewPool.setMaxRecycledViews(3, 10); viewPool.setMaxRecycledViews(4, 10); viewPool.setMaxRecycledViews(5, 10); ... ``` ## 混淆问题 如果碰到release包(混淆过)无法正常运行,debug包(一般未混淆)可正常运行,检查一下混淆配置是否完整: ``` -keepattributes InnerClasses -keep class com.alibaba.android.vlayout.ExposeLinearLayoutManagerEx { *; } -keep class android.support.v7.widget.RecyclerView$LayoutParams { *; } -keep class android.support.v7.widget.RecyclerView$ViewHolder { *; } -keep class android.support.v7.widget.ChildHelper { *; } -keep class android.support.v7.widget.ChildHelper$Bucket { *; } -keep class android.support.v7.widget.RecyclerView$LayoutManager { *; } ``` ## 下拉刷新和加载更多 VLayout 只负责布局,下拉刷新和加载更多需要业务方自己处理,当然可能存在和一些下拉刷新控件不兼容的 bug。 下拉刷新,有很多框架是通过判断 `RecyclerView` 的第一个 view 的 top 是否为 0 来触发下拉动作。VLayout 里在处理背景、悬浮态的时候加入了一些对 `LayoutManager` 不可见的 View,但又真实存在与 `RecyclerView` 的视图树里,建议使用 `layoutManager.getChildAt(0)` 来获取第一个 view。 加载更多,可以通过 recyclerView 的滚动状态来触发 load-more 事件,需要使用方注册一个 `OnScrollListener`: ``` RecyclerView.OnScrollListener onScrollListener = new RecyclerView.OnScrollListener() { public void onScrollStateChanged(RecyclerView recyclerView, int newState) { } public void onScrolled(RecyclerView recyclerView, int dx, int dy) { //hasMore: status of current page, means if there's more data, you have to maintain this status if(hasMore) { VirtualLayoutManager lm = (VirtualLayoutManager)recyclerView.getLayoutManager(); int first=0, last=0, total=0; first = ((LinearLayoutManager)lm).findFirstVisibleItemPosition(); last = ((LinearLayoutManager)lm).findLastVisibleItemPosition(); total = recyclerView.getAdapter().getItemCount(); if(last > 0 && last >= total - earlyCountForAutoLoad) { //earlyCountForAutoLoad: help to trigger load more listener earlier //TODO trigger loadmore listener } } } } ``` ## 横向滑动 没有实现横向滚动的 `LayoutHelper` ,因为 `LayoutHelper` 目前只能做静态的布局,对于跟数据绑定的动态横向滚动布局,比如 `ViewPager` 或者 `RecyclerView` ,建议使用组件的形式提供。也就是一个 `LinearLayoutHelper` 包一个 Item,这个 Item 是 `ViewPager` 或者横向滚动的 `RecyclerView`,且它们是可以和整个页面的 `RecyclerView` 共用一个回收复用池的,参考 Demo 里的第一个组件的使用方法。 ## 设置背景图后触发循环布局 给 `LayoutHelper` 设置背景图的时候,由于这个过程是在布局 view 的阶段,设置了图片会触发一次新的 layout,从而又导致触发一次背景图设置,最终进入死循环,因此需要使用方在设置背景图的时候判断当前图片是否已经加载过一次并且成功,如果绑定过一次就不需要再设置图片了,阻断死循环的路径。 具体做法是: 在 `BaseLayoutHelper.LayoutViewBindListener` 的 `onBind()` 方法里判断是否成功绑定过该背景图。 在 `BaseLayoutHelper.LayoutViewUnBindListener` 的 `onUnbind()` 方法里清楚绑定成功与否的状态。 在使用方的图片加载成功回调函数里设置一下图片加载成功的状态,可以自行维护一个 map 或者给 View 设置一个 tag 标记。 我们提供了一个简单的 `DefaultLayoutViewHelper` 封装了这个逻辑,可以参考使用。 ## 在可滚动区域里嵌套使用 vlayout 的 `RecyclerView` 不太建议嵌套滚动,除非手势不冲突;如果要完全展开 vlayout 里的内容,牺牲滚动复用,可以调用 `VirtualLayoutManager` 的 `setNoScrolling(true);` 方法设置一下。 ## 为 `GridLayoutHelper` 的设置自定义 `SpanSizeLookup` 在 `SpanSizeLookup` 中,`public int getSpanSize(int position)` 方法参数的 position 是整个页面的 position 信息,需要获取当前 layoutHelper 内的相对位置,需要减去一个偏移量,即 `position - getStartPosition()`。 ## 获取 `DelegateAdapter` 里数据的相对位置 在 `DelegateAdapter` 里有 `findOffsetPosition(int absolutePosition)` 方法,传入整个页面的绝对位置,获取相对位置。 或者用 ``` public static abstract class Adapter extends RecyclerView.Adapter { public abstract LayoutHelper onCreateLayoutHelper(); protected void onBindViewHolderWithOffset(VH holder, int position, int offsetTotal) { } } ``` 中的 `onBindViewHolderWithOffset()` 方法代替传统的 `onBindViewHolder()` 方法,其中的 `position` 参数也是相对位置。 ## `StickyLayoutHelper`里的 item 被其他 item 覆盖 `StickyLayoutHelper`里的 item 在 sticky 状态时是会被添加到 `RecyclerView` 的最顶层,如果它被覆盖,很有可能是其他 item 里设置了一个 z 参数(>= 5.0 系统)或者是被调整了 drawingOrder(`RecyclerView` 有 `setChildDrawingOrderCallback` 接口调整绘制顺序),一个典型的场景是使用了 `CardView`。解决方法是给它设置一个更大的 z 参数(>= 5.0 系统),或者检查一下有没有调整 drawingOrder 的地方。 ## 背景图在滑动过程中变形 layoutHelper 根据 item 元素的位置和大小确定整块背景的大小,当 layoutHelper 在有未显示元素时,不清楚自己的区域到底有多大,在可见元素变化时会动态计算区域大小,并调节背景 view 的大小,于是就导致了背景 view 中图片会根据 view 的大小去调整自己的显示。通过把背景 imageview 的 scaleType 设置为 matrix,这样就不会跟随imageview的宽高进行变化,同时根据不同手机的 dpi 提前调整好背景图片的尺寸后,再放入 imageview 中就能解决问题。[#275](https://github.com/alibaba/vlayout/issues/275) ## 判断 `StickyLayoutHelper` 里的 item 是否到达顶部 通过 `virtualLayoutManager.findFirstVisibleItemPosition()`,如果大于 `StickyLayoutHelper` 里的 item 的位置,说明已经到顶部。[#277](https://github.com/alibaba/vlayout/issues/277) ## 滚动到某个 item 位置,并带偏移一个距离 有时候自带的 scrollToPosition 方法或者 smoothScrollToPosition 方法不满足需求,可以尝试自己用动画驱动做一个滚动,下面是一种参考实现,可以基于此调整动画参数; ``` public class RecyclerViewFlinger implements Runnable { private static final String TAG = "Flinger"; private static final float MILLISECONDS_PER_INCH = 25.0F; private RecyclerView mRecyclerView; private int targetPosition; private int offset; private int direction = 1; private ScrollFinishedListener mFinishedListener; private int lastTop; private int step; public RecyclerViewFlinger(RecyclerView recyclerView, int targetPosition, int offset, ScrollFinishedListener finishedListener) {        this.mRecyclerView = recyclerView;        this.targetPosition = targetPosition; //targetPosition 目标item的位置        this.offset = offset;//offset 是目标 item 距离顶部的偏移量        this.mFinishedListener = finishedListener;//可以设置一个滚动回调 if (mRecyclerView != null) { int firstVisibleItemPosition = mRecyclerView.getFirstVisiblePosition(); direction = firstVisibleItemPosition < targetPosition ? 1 : -1; }        this.step = mRecyclerView.getMeasuredHeight() / 2; //滚动步长,时间等都可以细调 } @Override public void run() { if (mRecyclerView != null) { int firstVisibleItemPosition = mRecyclerView.getFirstVisiblePosition(); int lastVisibleItemPosition = mRecyclerView.getLastVisiblePosition(); boolean inscreen = targetPosition >= firstVisibleItemPosition && targetPosition <= lastVisibleItemPosition; if (inscreen) { View targetView = mRecyclerView.getLayoutManager().findViewByPosition(targetPosition); if (targetView != null) { int top = targetView.getTop(); int dy = top - offset; mRecyclerView.smoothScrollBy(0, dy); if (lastTop == top) { if (mFinishedListener != null) { mFinishedListener.onPostExecute(targetView); } } else { lastTop = top; postOnAnimation(); } } } else { mRecyclerView.smoothScrollBy(0, step * direction); postOnAnimation(); } } } public void postOnAnimation() { if (mRecyclerView == null) { return; } ViewCompat.postOnAnimation(mRecyclerView, this); } public void stop() { mFinishedListener = null; if (mRecyclerView == null) { return; } mRecyclerView.removeCallbacks(this); } public interface ScrollFinishedListener { void onPostExecute(View view); } } ``` ================================================ FILE: examples/.gitignore ================================================ # Built application files *.apk *.ap_ # Files for the Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties .classpath .project .settings/ # Proguard folder generated by Eclipse proguard/ #Log Files *.log # OS X .DS_Store # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.war *.ear *.iml # IDEA Files .idea/ out/ # MAVEN COMPILE Files target/ lint.xml ================================================ FILE: examples/build.gradle ================================================ apply plugin: 'com.android.application' /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ // apply plugin: 'view-inspector' android { compileSdkVersion Integer.parseInt(System.properties['compileSdkVersion']) buildToolsVersion System.properties['buildToolsVersion'] defaultConfig { applicationId "com.alibaba.android.vlayout.example" minSdkVersion 14 targetSdkVersion Integer.parseInt(System.properties['targetSdkVersion']) versionCode 1 versionName "1.0" } buildTypes { debug { minifyEnabled false debuggable true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile project(':vlayout') compile 'com.crittercism.dexmaker:dexmaker:1.4' compile 'com.squareup.picasso:picasso:2.5.2' compile 'com.android.support:appcompat-v7:21.0.0' compile 'com.android.support:support-annotations:21.0.0' compile 'com.android.support:cardview-v7:23.1.1' } ================================================ FILE: examples/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/villadora/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} -keepattributes InnerClasses -keep class android.support.v7.widget.RecyclerView$LayoutParams { *; } -keep class android.support.v7.widget.RecyclerView$ViewHolder { *; } -keep class android.support.v7.widget.ChildHelper { *; } -keep class android.support.v7.widget.RecyclerView$LayoutManager { *; } -dontwarn ** ================================================ FILE: examples/src/androidTest/java/com/alibaba/android/vlayout/ApplicationTest.java ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.alibaba.android.vlayout; import android.app.Application; import android.test.ApplicationTestCase; /** * Testing Fundamentals */ public class ApplicationTest extends ApplicationTestCase { public ApplicationTest() { super(Application.class); } } ================================================ FILE: examples/src/main/AndroidManifest.xml ================================================ ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/DebugActivity.java ================================================ package com.alibaba.android.vlayout.example; import java.util.ArrayList; import java.util.List; import com.alibaba.android.vlayout.DelegateAdapter; import com.alibaba.android.vlayout.DelegateAdapter.Adapter; import com.alibaba.android.vlayout.LayoutHelper; import com.alibaba.android.vlayout.VirtualLayoutManager; import com.alibaba.android.vlayout.layout.FixLayoutHelper; import com.alibaba.android.vlayout.layout.GridLayoutHelper; import com.alibaba.android.vlayout.layout.LinearLayoutHelper; import com.alibaba.android.vlayout.layout.StickyLayoutHelper; import android.app.Activity; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import static com.alibaba.android.vlayout.layout.FixLayoutHelper.TOP_RIGHT; /** * Created by longerian on 2017/11/14. * * @author longerian * @date 2017/11/14 */ public class DebugActivity extends Activity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); RecyclerView recyclerView = (RecyclerView)findViewById(R.id.main_view); VirtualLayoutManager virtualLayoutManager = new VirtualLayoutManager(this); DelegateAdapter delegateAdapter = new DelegateAdapter(virtualLayoutManager); List adapterList = new ArrayList<>(); adapterList.add(new SubAdapter(new LinearLayoutHelper(20), 20)); adapterList.add(new SubAdapter(new StickyLayoutHelper(true), 1)); adapterList.add(new SubAdapter(new LinearLayoutHelper(20), 20)); adapterList.add(new SubAdapter(new GridLayoutHelper(4), 80)); // adapterList.add(new SubAdapter(new FixLayoutHelper(0, 0), 1)); adapterList.add(new SubAdapter(new FixLayoutHelper(TOP_RIGHT, 0, 0), 1)); delegateAdapter.addAdapters(adapterList); recyclerView.setLayoutManager(virtualLayoutManager); recyclerView.setAdapter(delegateAdapter); } private static class SubAdapter extends DelegateAdapter.Adapter { private LayoutHelper mLayoutHelper; private int mItemCount; private SubAdapter(LayoutHelper layoutHelper, int itemCount) { mLayoutHelper = layoutHelper; mItemCount = itemCount; } @Override public LayoutHelper onCreateLayoutHelper() { return mLayoutHelper; } @Override public SubViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); return new SubViewHolder(inflater.inflate(R.layout.item, parent, false)); } @Override public void onBindViewHolder(SubViewHolder holder, int position) { // do nothing } @Override protected void onBindViewHolderWithOffset(SubViewHolder holder, int position, int offsetTotal) { super.onBindViewHolderWithOffset(holder, position, offsetTotal); holder.setText(String.valueOf(offsetTotal)); holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); } @Override public int getItemCount() { return mItemCount; } } private static class SubViewHolder extends RecyclerView.ViewHolder { public static volatile int existing = 0; public static int createdTimes = 0; public SubViewHolder(View itemView) { super(itemView); createdTimes++; existing++; } public void setText(String title) { ((TextView) itemView.findViewById(R.id.title)).setText(title); } @Override protected void finalize() throws Throwable { existing--; super.finalize(); } } } ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/MainActivity.java ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.alibaba.android.vlayout.example; import android.app.Activity; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.alibaba.android.vlayout.LayoutHelper; import com.alibaba.android.vlayout.VirtualLayoutAdapter; import com.alibaba.android.vlayout.VirtualLayoutManager; import com.alibaba.android.vlayout.layout.DefaultLayoutHelper; import com.alibaba.android.vlayout.layout.FixLayoutHelper; import com.alibaba.android.vlayout.layout.GridLayoutHelper; import com.alibaba.android.vlayout.layout.ScrollFixLayoutHelper; import java.util.LinkedList; import java.util.List; /** * Created by villadora on 15/8/3. */ public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); VirtualLayoutManager layoutManager = new VirtualLayoutManager(this); recyclerView.setLayoutManager(layoutManager); //layoutManager.setReverseLayout(true); recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.set(10, 10, 10, 10); } }); final List helpers = new LinkedList<>(); final GridLayoutHelper gridLayoutHelper = new GridLayoutHelper(4); gridLayoutHelper.setItemCount(25); final ScrollFixLayoutHelper scrollFixLayoutHelper = new ScrollFixLayoutHelper(FixLayoutHelper.TOP_RIGHT, 100, 100); helpers.add(DefaultLayoutHelper.newHelper(7)); helpers.add(scrollFixLayoutHelper); helpers.add(DefaultLayoutHelper.newHelper(2)); helpers.add(gridLayoutHelper); layoutManager.setLayoutHelpers(helpers); recyclerView.setAdapter( new VirtualLayoutAdapter(layoutManager) { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MainViewHolder(new TextView(MainActivity.this)); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { VirtualLayoutManager.LayoutParams layoutParams = new VirtualLayoutManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 300); holder.itemView.setLayoutParams(layoutParams); ((TextView) holder.itemView).setText(Integer.toString(position)); if (position == 7) { layoutParams.height = 60; layoutParams.width = 60; } else if (position > 35) { layoutParams.height = 200 + (position - 30) * 100; } if (position > 35) { holder.itemView.setBackgroundColor(0x66cc0000 + (position - 30) * 128); } else if (position % 2 == 0) { holder.itemView.setBackgroundColor(0xaa00ff00); } else { holder.itemView.setBackgroundColor(0xccff00ff); } } @Override public int getItemCount() { List helpers = getLayoutHelpers(); if (helpers == null) { return 0; } int count = 0; for (int i = 0, size = helpers.size(); i < size; i++) { count += helpers.get(i).getItemCount(); } return count; } }); new Handler(Looper.getMainLooper()).postDelayed(new Runnable() { @Override public void run() { recyclerView.scrollToPosition(7); recyclerView.getAdapter().notifyDataSetChanged(); } }, 6000); } static class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View itemView) { super(itemView); } } } ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/OnePlusNLayoutActivity.java ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.alibaba.android.vlayout.example; import android.app.Activity; import android.content.Context; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.support.v4.view.ViewPager; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; import com.alibaba.android.vlayout.DelegateAdapter; import com.alibaba.android.vlayout.LayoutHelper; import com.alibaba.android.vlayout.RecyclablePagerAdapter; import com.alibaba.android.vlayout.VirtualLayoutManager; import com.alibaba.android.vlayout.VirtualLayoutManager.LayoutParams; import com.alibaba.android.vlayout.layout.FixLayoutHelper; import com.alibaba.android.vlayout.layout.GridLayoutHelper; import com.alibaba.android.vlayout.layout.LinearLayoutHelper; import com.alibaba.android.vlayout.layout.OnePlusNLayoutHelper; import com.alibaba.android.vlayout.layout.OnePlusNLayoutHelperEx; import com.alibaba.android.vlayout.layout.ScrollFixLayoutHelper; import com.alibaba.android.vlayout.layout.StickyLayoutHelper; import java.util.LinkedList; import java.util.List; /** * @author villadora */ public class OnePlusNLayoutActivity extends Activity { private static final boolean BANNER_LAYOUT = true; private static final boolean LINEAR_LAYOUT = true; private static final boolean ONEN_LAYOUT = true; private static final boolean GRID_LAYOUT = true; private static final boolean STICKY_LAYOUT = true; private static final boolean HORIZONTAL_SCROLL_LAYOUT = true; private static final boolean SCROLL_FIX_LAYOUT = true; private TextView mFirstText; private TextView mLastText; private TextView mCountText; private TextView mTotalOffsetText; private Runnable trigger; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); mFirstText = (TextView) findViewById(R.id.first); mLastText = (TextView) findViewById(R.id.last); mCountText = (TextView) findViewById(R.id.count); mTotalOffsetText = (TextView) findViewById(R.id.total_offset); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText position = (EditText) findViewById(R.id.position); if (!TextUtils.isEmpty(position.getText())) { try { int pos = Integer.parseInt(position.getText().toString()); recyclerView.scrollToPosition(pos); } catch (Exception e) { Log.e("VlayoutActivity", e.getMessage(), e); } } else { recyclerView.requestLayout(); } } }); final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this); recyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) { } @Override public void onScrolled(RecyclerView recyclerView, int i, int i2) { mFirstText.setText("First: " + layoutManager.findFirstVisibleItemPosition()); mLastText.setText("Existing: " + MainViewHolder.existing + " Created: " + MainViewHolder.createdTimes); mCountText.setText("Count: " + recyclerView.getChildCount()); mTotalOffsetText.setText("Total Offset: " + layoutManager.getOffsetToStart()); } }); recyclerView.setLayoutManager(layoutManager); // layoutManager.setReverseLayout(true); RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() { public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = ((LayoutParams) view.getLayoutParams()).getViewPosition(); outRect.set(4, 4, 4, 4); } }; final RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); // recyclerView.addItemDecoration(itemDecoration); viewPool.setMaxRecycledViews(0, 20); final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true); recyclerView.setAdapter(delegateAdapter); List adapters = new LinkedList<>(); if (BANNER_LAYOUT) { adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 1) { @Override public void onViewRecycled(MainViewHolder holder) { if (holder.itemView instanceof ViewPager) { ((ViewPager) holder.itemView).setAdapter(null); } } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 1) return new MainViewHolder( LayoutInflater.from(OnePlusNLayoutActivity.this).inflate(R.layout.view_pager, parent, false)); return super.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(int position) { return 1; } @Override protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { } @Override public void onBindViewHolder(MainViewHolder holder, int position) { if (holder.itemView instanceof ViewPager) { ViewPager viewPager = (ViewPager) holder.itemView; viewPager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200)); // from position to get adapter viewPager.setAdapter(new PagerAdapter(this, viewPool)); } } }); } if (GRID_LAYOUT) { GridLayoutHelper layoutHelper; layoutHelper = new GridLayoutHelper(4); layoutHelper.setMargin(0, 10, 0, 10); layoutHelper.setHGap(3); layoutHelper.setAspectRatio(4f); adapters.add(new SubAdapter(this, layoutHelper, 8)); } if (HORIZONTAL_SCROLL_LAYOUT) { } if (GRID_LAYOUT) { GridLayoutHelper layoutHelper; layoutHelper = new GridLayoutHelper(2); layoutHelper.setMargin(0, 10, 0, 10); layoutHelper.setHGap(3); layoutHelper.setAspectRatio(3f); adapters.add(new SubAdapter(this, layoutHelper, 2)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xff876384); helper.setMargin(10, 10, 10, 10); helper.setPadding(10, 10, 10, 10); adapters.add(new SubAdapter(this, helper, 3) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); // LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); // layoutParams.leftMargin = 10; // layoutParams.topMargin = 10; // layoutParams.rightMargin = 10; // layoutParams.bottomMargin = 10; // holder.itemView.setLayoutParams(layoutParams); } }); } if (ONEN_LAYOUT) { OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 4)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 5)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 5)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); helper.setColWeights(new float[]{40f, 45f, 15f, 60f, 0f}); adapters.add(new SubAdapter(this, helper, 5)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); helper.setColWeights(new float[]{20f, 80f, 0f, 60f, 20f}); helper.setAspectRatio(4); adapters.add(new SubAdapter(this, helper, 5)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 6)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 7)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xff876384); helper.setMargin(0, 10, 0, 10); helper.setColWeights(new float[]{40f, 45f, 15f, 60f, 0f, 30f, 30f}); adapters.add(new SubAdapter(this, helper, 7)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelperEx helper = new OnePlusNLayoutHelperEx(); helper.setBgColor(0xffed7612); // helper.setMargin(10, 10, 10, 10); // helper.setPadding(10, 10, 10, 10); helper.setColWeights(new float[]{30f, 20f, 50f, 40f, 30f, 35f, 35f}); adapters.add(new SubAdapter(this, helper, 7) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); // LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); // layoutParams.leftMargin = 10; // layoutParams.topMargin = 10; // layoutParams.rightMargin = 10; // layoutParams.bottomMargin = 10; // holder.itemView.setLayoutParams(layoutParams); } }); } if (STICKY_LAYOUT) { StickyLayoutHelper layoutHelper = new StickyLayoutHelper(); layoutHelper.setAspectRatio(4); adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100))); } if (SCROLL_FIX_LAYOUT) { ScrollFixLayoutHelper layoutHelper = new ScrollFixLayoutHelper(FixLayoutHelper.BOTTOM_RIGHT, 20, 20); layoutHelper.setShowType(ScrollFixLayoutHelper.SHOW_ON_LEAVE); adapters.add(new SubAdapter(this, layoutHelper, 1) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams layoutParams = new LayoutParams(50, 50); holder.itemView.setLayoutParams(layoutParams); } }); } if (LINEAR_LAYOUT) adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 100)); delegateAdapter.setAdapters(adapters); final Handler mainHandler = new Handler(Looper.getMainLooper()); trigger = new Runnable() { @Override public void run() { // recyclerView.scrollToPosition(22); // recyclerView.getAdapter().notifyDataSetChanged(); recyclerView.requestLayout(); // mainHandler.postDelayed(trigger, 1000); } }; mainHandler.postDelayed(trigger, 1000); } // RecyclableViewPager static class PagerAdapter extends RecyclablePagerAdapter { public PagerAdapter(SubAdapter adapter, RecyclerView.RecycledViewPool pool) { super(adapter, pool); } @Override public int getCount() { return 6; } @Override public void onBindViewHolder(MainViewHolder viewHolder, int position) { // only vertical viewHolder.itemView.setLayoutParams( new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((TextView) viewHolder.itemView.findViewById(R.id.title)).setText("Banner: " + position); } @Override public int getItemViewType(int position) { return 0; } } static class SubAdapter extends DelegateAdapter.Adapter { private Context mContext; private LayoutHelper mLayoutHelper; private LayoutParams mLayoutParams; private int mCount = 0; public SubAdapter(Context context, LayoutHelper layoutHelper, int count) { this(context, layoutHelper, count, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); } public SubAdapter(Context context, LayoutHelper layoutHelper, int count, @NonNull LayoutParams layoutParams) { this.mContext = context; this.mLayoutHelper = layoutHelper; this.mCount = count; this.mLayoutParams = layoutParams; } @Override public LayoutHelper onCreateLayoutHelper() { return mLayoutHelper; } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false)); } @Override public void onBindViewHolder(MainViewHolder holder, int position) { // only vertical holder.itemView.setLayoutParams( new LayoutParams(mLayoutParams)); } @Override protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal)); } @Override public int getItemCount() { return mCount; } } static class MainViewHolder extends RecyclerView.ViewHolder { public static volatile int existing = 0; public static int createdTimes = 0; public MainViewHolder(View itemView) { super(itemView); createdTimes++; existing++; } @Override protected void finalize() throws Throwable { existing--; super.finalize(); } } } ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/RootActivity.java ================================================ package com.alibaba.android.vlayout.example; import android.app.ListActivity; import android.content.Intent; import android.os.Bundle; import android.support.annotation.Nullable; import android.view.View; import android.widget.ArrayAdapter; import android.widget.ListView; /** * Created by J!nl!n on 2017/3/9. */ public class RootActivity extends ListActivity { private String[] mTitles = new String[]{ VLayoutActivity.class.getSimpleName(), MainActivity.class.getSimpleName(), TestActivity.class.getSimpleName(), OnePlusNLayoutActivity.class.getSimpleName(), DebugActivity.class.getSimpleName() }; private Class[] mActivities = new Class[]{ VLayoutActivity.class, MainActivity.class, TestActivity.class, OnePlusNLayoutActivity.class, DebugActivity.class }; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, mTitles)); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { startActivity(new Intent(this, mActivities[position])); } } ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/TestActivity.java ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.alibaba.android.vlayout.example; import android.app.Activity; import android.graphics.Rect; import android.os.Bundle; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.text.TextUtils; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.TextView; /** * Created by villadora on 15/8/3. */ public class TestActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); StaggeredGridLayoutManager layoutManager = new StaggeredGridLayoutManager(3, StaggeredGridLayoutManager.VERTICAL); recyclerView.setLayoutManager(layoutManager); findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText position = (EditText) findViewById(R.id.position); if (!TextUtils.isEmpty(position.getText())) { try { int pos = Integer.parseInt(position.getText().toString()); recyclerView.scrollToPosition(pos); } catch (Exception e) { Log.e("VlayoutActivity", e.getMessage(), e); } } } }); recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() { public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { outRect.set(4, 4, 4, 4); } }); recyclerView.setAdapter( new RecyclerView.Adapter() { @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // TextView view = (TextView) LayoutInflater.from(TestActivity.this).inflate(R.layout.item, parent, false); // FrameLayout frameLayout = new FrameLayout(TestActivity.this); FrameLayout frameLayout = (FrameLayout) LayoutInflater.from(TestActivity.this).inflate(R.layout.item, parent, false);; // frameLayout.addView(view); return new MainViewHolder(frameLayout); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, 300); layoutParams.height = (int) (200 + (position % 15) * 10); holder.itemView.findViewById(R.id.title).setLayoutParams(layoutParams); if (position == 30) { StaggeredGridLayoutManager.LayoutParams lp = new StaggeredGridLayoutManager.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); lp.setFullSpan(true); holder.itemView.setLayoutParams(lp); } else { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp instanceof StaggeredGridLayoutManager.LayoutParams) { ((StaggeredGridLayoutManager.LayoutParams) lp).setFullSpan(false); } } ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(position)); } @Override public int getItemCount() { return 60; } }); } static class MainViewHolder extends RecyclerView.ViewHolder { public MainViewHolder(View itemView) { super(itemView); } } } ================================================ FILE: examples/src/main/java/com/alibaba/android/vlayout/example/VLayoutActivity.java ================================================ /* * MIT License * * Copyright (c) 2016 Alibaba Group * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. */ package com.alibaba.android.vlayout.example; import com.alibaba.android.vlayout.DelegateAdapter; import com.alibaba.android.vlayout.LayoutHelper; import com.alibaba.android.vlayout.RecyclablePagerAdapter; import com.alibaba.android.vlayout.VirtualLayoutManager; import com.alibaba.android.vlayout.VirtualLayoutManager.LayoutParams; import com.alibaba.android.vlayout.extend.LayoutManagerCanScrollListener; import com.alibaba.android.vlayout.extend.PerformanceMonitor; import com.alibaba.android.vlayout.extend.ViewLifeCycleListener; import com.alibaba.android.vlayout.layout.ColumnLayoutHelper; import com.alibaba.android.vlayout.layout.FixLayoutHelper; import com.alibaba.android.vlayout.layout.FloatLayoutHelper; import com.alibaba.android.vlayout.layout.GridLayoutHelper; import com.alibaba.android.vlayout.layout.LinearLayoutHelper; import com.alibaba.android.vlayout.layout.OnePlusNLayoutHelper; import com.alibaba.android.vlayout.layout.RangeGridLayoutHelper; import com.alibaba.android.vlayout.layout.RangeGridLayoutHelper.GridRangeStyle; import com.alibaba.android.vlayout.layout.ScrollFixLayoutHelper; import com.alibaba.android.vlayout.layout.SingleLayoutHelper; import com.alibaba.android.vlayout.layout.StaggeredGridLayoutHelper; import com.alibaba.android.vlayout.layout.StickyLayoutHelper; import android.app.Activity; import android.content.Context; import android.graphics.Color; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.support.annotation.NonNull; import android.support.v4.view.ViewPager; import android.support.v4.widget.SwipeRefreshLayout; import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver.OnGlobalLayoutListener; import android.widget.EditText; import android.widget.TextView; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; /** * @author villadora */ public class VLayoutActivity extends Activity { private static final boolean BANNER_LAYOUT = true; private static final boolean FIX_LAYOUT = true; private static final boolean LINEAR_LAYOUT = true; private static final boolean SINGLE_LAYOUT = true; private static final boolean FLOAT_LAYOUT = true; private static final boolean ONEN_LAYOUT = true; private static final boolean COLUMN_LAYOUT = true; private static final boolean GRID_LAYOUT = true; private static final boolean STICKY_LAYOUT = true; private static final boolean STAGGER_LAYOUT = true; private SwipeRefreshLayout mSwipeRefreshLayout; private TextView mFirstText; private TextView mLastText; private TextView mCountText; private TextView mTotalOffsetText; private Runnable trigger; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); mSwipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container); ; mFirstText = (TextView) findViewById(R.id.first); mLastText = (TextView) findViewById(R.id.last); mCountText = (TextView) findViewById(R.id.count); mTotalOffsetText = (TextView) findViewById(R.id.total_offset); final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); final VirtualLayoutManager layoutManager = new VirtualLayoutManager(this); layoutManager.setPerformanceMonitor(new PerformanceMonitor() { long start; long end; @Override public void recordStart(String phase, View view) { start = System.currentTimeMillis(); } @Override public void recordEnd(String phase, View view) { end = System.currentTimeMillis(); Log.d("VLayoutActivity", view.getClass().getName() + " " + (end - start)); } }); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int scrollState) { } @Override public void onScrolled(RecyclerView recyclerView, int i, int i2) { mFirstText.setText("First: " + layoutManager.findFirstVisibleItemPosition()); mLastText.setText("Existing: " + MainViewHolder.existing + " Created: " + MainViewHolder.createdTimes); mCountText.setText("Count: " + recyclerView.getChildCount()); mTotalOffsetText.setText("Total Offset: " + layoutManager.getOffsetToStart()); } }); recyclerView.setLayoutManager(layoutManager); // layoutManager.setReverseLayout(true); RecyclerView.ItemDecoration itemDecoration = new RecyclerView.ItemDecoration() { public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { int position = ((LayoutParams) view.getLayoutParams()).getViewPosition(); outRect.set(4, 4, 4, 4); } }; final RecyclerView.RecycledViewPool viewPool = new RecyclerView.RecycledViewPool(); recyclerView.setRecycledViewPool(viewPool); // recyclerView.addItemDecoration(itemDecoration); viewPool.setMaxRecycledViews(0, 20); layoutManager.setRecycleOffset(300); // viewLifeCycleListener should be used with setRecycleOffset() layoutManager.setViewLifeCycleListener(new ViewLifeCycleListener() { @Override public void onAppearing(View view) { // Log.e("ViewLifeCycleTest", "onAppearing: " + view); } @Override public void onDisappearing(View view) { // Log.e("ViewLifeCycleTest", "onDisappearing: " + view); } @Override public void onAppeared(View view) { // Log.e("ViewLifeCycleTest", "onAppeared: " + view); } @Override public void onDisappeared(View view) { // Log.e("ViewLifeCycleTest", "onDisappeared: " + view); } }); layoutManager.setLayoutManagerCanScrollListener(new LayoutManagerCanScrollListener() { @Override public boolean canScrollVertically() { Log.i("vlayout", "canScrollVertically: "); return true; } @Override public boolean canScrollHorizontally() { Log.i("vlayout", "canScrollHorizontally: "); return true; } }); final DelegateAdapter delegateAdapter = new DelegateAdapter(layoutManager, true); recyclerView.setAdapter(delegateAdapter); final List adapters = new LinkedList<>(); if (BANNER_LAYOUT) { adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 1) { @Override public void onViewRecycled(MainViewHolder holder) { if (holder.itemView instanceof ViewPager) { ((ViewPager) holder.itemView).setAdapter(null); } } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (viewType == 1) return new MainViewHolder( LayoutInflater.from(VLayoutActivity.this).inflate(R.layout.view_pager, parent, false)); return super.onCreateViewHolder(parent, viewType); } @Override public int getItemViewType(int position) { return 1; } @Override protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { } @Override public void onBindViewHolder(MainViewHolder holder, int position) { if (holder.itemView instanceof ViewPager) { ViewPager viewPager = (ViewPager) holder.itemView; viewPager.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200)); // from position to get adapter viewPager.setAdapter(new PagerAdapter(this, viewPool)); } } }); } //{ // GridLayoutHelper helper = new GridLayoutHelper(10); // helper.setAspectRatio(4f); // helper.setGap(10); // adapters.add(new SubAdapter(this, helper, 80)); //} if (FLOAT_LAYOUT) { FloatLayoutHelper layoutHelper = new FloatLayoutHelper(); layoutHelper.setAlignType(FixLayoutHelper.BOTTOM_RIGHT); layoutHelper.setDefaultLocation(100, 400); LayoutParams layoutParams = new LayoutParams(150, 150); adapters.add(new SubAdapter(this, layoutHelper, 1, layoutParams)); } if (LINEAR_LAYOUT) { LinearLayoutHelper layoutHelper1 = new LinearLayoutHelper(); layoutHelper1.setBgColor(Color.YELLOW); layoutHelper1.setAspectRatio(2.0f); layoutHelper1.setMargin(10, 10, 10, 10); layoutHelper1.setPadding(10, 10, 10, 10); LinearLayoutHelper layoutHelper2 = new LinearLayoutHelper(); layoutHelper2.setAspectRatio(4.0f); layoutHelper2.setDividerHeight(10); layoutHelper2.setMargin(10, 0, 10, 10); layoutHelper2.setPadding(10, 0, 10, 10); layoutHelper2.setBgColor(0xFFF5A623); final Handler mainHandler = new Handler(Looper.getMainLooper()); adapters.add(new SubAdapter(this, layoutHelper1, 1) { @Override public void onBindViewHolder(final MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); final SubAdapter subAdapter = this; //mainHandler.postDelayed(new Runnable() { // @Override // public void run() { // //delegateAdapter.removeAdapter(subAdapter); // //notifyItemRemoved(1); // holder.itemView.setVisibility(View.GONE); // notifyItemChanged(1); // layoutManager.runAdjustLayout(); // } //}, 2000L); } }); adapters.add(new SubAdapter(this, layoutHelper2, 6) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { if (position % 2 == 0) { LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); layoutParams.mAspectRatio = 5; holder.itemView.setLayoutParams(layoutParams); } } }); } // { // RangeGridLayoutHelper layoutHelper = new RangeGridLayoutHelper(4); // layoutHelper.setBgColor(Color.GREEN); // layoutHelper.setWeights(new float[]{20f, 26.665f}); // layoutHelper.setPadding(15, 15, 15, 15); // layoutHelper.setMargin(15, 50, 15, 150); // layoutHelper.setHGap(10); // layoutHelper.setVGap(10); // GridRangeStyle rangeStyle = new GridRangeStyle(); // rangeStyle.setBgColor(Color.RED); // rangeStyle.setSpanCount(2); // rangeStyle.setWeights(new float[]{46.665f}); // rangeStyle.setPadding(15, 15, 15, 15); // rangeStyle.setMargin(15, 15, 15, 15); // rangeStyle.setHGap(5); // rangeStyle.setVGap(5); // layoutHelper.addRangeStyle(0, 7, rangeStyle); // // GridRangeStyle rangeStyle1 = new GridRangeStyle(); // rangeStyle1.setBgColor(Color.YELLOW); // rangeStyle1.setSpanCount(2); // rangeStyle1.setWeights(new float[]{46.665f}); // rangeStyle1.setPadding(15, 15, 15, 15); // rangeStyle1.setMargin(15, 15, 15, 15); // rangeStyle1.setHGap(5); // rangeStyle1.setVGap(5); // layoutHelper.addRangeStyle(8, 15, rangeStyle1); // // GridRangeStyle rangeStyle2 = new GridRangeStyle(); // rangeStyle2.setBgColor(Color.CYAN); // rangeStyle2.setSpanCount(2); // rangeStyle2.setWeights(new float[]{46.665f}); // rangeStyle2.setPadding(15, 15, 15, 15); // rangeStyle2.setMargin(15, 15, 15, 15); // rangeStyle2.setHGap(5); // rangeStyle2.setVGap(5); // layoutHelper.addRangeStyle(16, 22, rangeStyle2); // GridRangeStyle rangeStyle3 = new GridRangeStyle(); // rangeStyle3.setBgColor(Color.DKGRAY); // rangeStyle3.setSpanCount(1); // rangeStyle3.setWeights(new float[]{46.665f}); // rangeStyle3.setPadding(15, 15, 15, 15); // rangeStyle3.setMargin(15, 15, 15, 15); // rangeStyle3.setHGap(5); // rangeStyle3.setVGap(5); // rangeStyle2.addChildRangeStyle(0, 2, rangeStyle3); // GridRangeStyle rangeStyle4 = new GridRangeStyle(); // rangeStyle4.setBgColor(Color.BLUE); // rangeStyle4.setSpanCount(2); // rangeStyle4.setWeights(new float[]{46.665f}); // rangeStyle4.setPadding(15, 15, 15, 15); // rangeStyle4.setMargin(15, 15, 15, 15); // rangeStyle4.setHGap(5); // rangeStyle4.setVGap(5); // rangeStyle2.addChildRangeStyle(3, 6, rangeStyle4); // // GridRangeStyle rangeStyle5 = new GridRangeStyle(); // rangeStyle5.setBgColor(Color.RED); // rangeStyle5.setSpanCount(2); // rangeStyle5.setPadding(15, 15, 15, 15); // rangeStyle5.setMargin(15, 15, 15, 15); // rangeStyle5.setHGap(5); // rangeStyle5.setVGap(5); // layoutHelper.addRangeStyle(23, 30, rangeStyle5); // GridRangeStyle rangeStyle6 = new GridRangeStyle(); // rangeStyle6.setBgColor(Color.MAGENTA); // rangeStyle6.setSpanCount(2); // rangeStyle6.setPadding(15, 15, 15, 15); // rangeStyle6.setMargin(15, 15, 15, 15); // rangeStyle6.setHGap(5); // rangeStyle6.setVGap(5); // rangeStyle5.addChildRangeStyle(0, 7, rangeStyle6); // // adapters.add(new SubAdapter(this, layoutHelper, 23)); // } { SingleLayoutHelper layoutHelper = new SingleLayoutHelper(); layoutHelper.setBgColor(Color.BLUE); layoutHelper.setMargin(0, 30, 0, 200); adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100))); } if (STICKY_LAYOUT) { StickyLayoutHelper layoutHelper = new StickyLayoutHelper(); //layoutHelper.setOffset(100); layoutHelper.setAspectRatio(4); adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100))); } //{ // final StaggeredGridLayoutHelper helper = new StaggeredGridLayoutHelper(3, 10); // helper.setBgColor(0xFF86345A); // adapters.add(new SubAdapter(this, helper, 4) { // // @Override // public void onBindViewHolder(MainViewHolder holder, int position) { // super.onBindViewHolder(holder, position); // LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); // if (position % 2 == 0) { // layoutParams.mAspectRatio = 1.0f; // } else { // layoutParams.height = 340 + position % 7 * 20; // } // holder.itemView.setLayoutParams(layoutParams); // } // }); //} { final GridLayoutHelper helper = new GridLayoutHelper(3, 4); helper.setBgColor(0xFF86345A); adapters.add(new SubAdapter(this, helper, 4) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); holder.itemView.setLayoutParams(layoutParams); } }); } { RangeGridLayoutHelper layoutHelper = new RangeGridLayoutHelper(4); layoutHelper.setBgColor(Color.GREEN); layoutHelper.setWeights(new float[]{20f, 26.665f}); layoutHelper.setPadding(15, 15, 15, 15); layoutHelper.setMargin(15, 15, 15, 15); layoutHelper.setHGap(10); layoutHelper.setVGap(10); GridRangeStyle rangeStyle = new GridRangeStyle(); rangeStyle.setBgColor(Color.RED); rangeStyle.setSpanCount(2); rangeStyle.setWeights(new float[]{46.665f}); rangeStyle.setPadding(15, 15, 15, 15); rangeStyle.setMargin(15, 15, 15, 15); rangeStyle.setHGap(5); rangeStyle.setVGap(5); layoutHelper.addRangeStyle(4, 7, rangeStyle); GridRangeStyle rangeStyle1 = new GridRangeStyle(); rangeStyle1.setBgColor(Color.YELLOW); rangeStyle1.setSpanCount(2); rangeStyle1.setWeights(new float[]{46.665f}); rangeStyle1.setPadding(15, 15, 15, 15); rangeStyle1.setMargin(15, 15, 15, 15); rangeStyle1.setHGap(5); rangeStyle1.setVGap(5); layoutHelper.addRangeStyle(8, 11, rangeStyle1); adapters.add(new SubAdapter(this, layoutHelper, 16)); } if (SINGLE_LAYOUT) { SingleLayoutHelper layoutHelper = new SingleLayoutHelper(); layoutHelper.setBgColor(Color.rgb(135, 225, 90)); layoutHelper.setAspectRatio(4); layoutHelper.setMargin(10, 20, 10, 20); layoutHelper.setPadding(10, 10, 10, 10); adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100))); } if (COLUMN_LAYOUT) { ColumnLayoutHelper layoutHelper = new ColumnLayoutHelper(); layoutHelper.setBgColor(0xff00f0f0); layoutHelper.setWeights(new float[]{40.0f, Float.NaN, 40}); adapters.add(new SubAdapter(this, layoutHelper, 5) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { if (position == 0) { LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); layoutParams.mAspectRatio = 4; holder.itemView.setLayoutParams(layoutParams); } else { LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300); layoutParams.mAspectRatio = Float.NaN; holder.itemView.setLayoutParams(layoutParams); } } }); } if (ONEN_LAYOUT) { OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xff876384); helper.setAspectRatio(4.0f); helper.setColWeights(new float[]{40f, 45f}); helper.setMargin(10, 20, 10, 20); helper.setPadding(10, 10, 10, 10); adapters.add(new SubAdapter(this, helper, 2)); } if (ONEN_LAYOUT) { OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xffef8ba3); helper.setAspectRatio(2.0f); helper.setColWeights(new float[]{40f}); helper.setRowWeight(30f); helper.setMargin(10, 20, 10, 20); helper.setPadding(10, 10, 10, 10); adapters.add(new SubAdapter(this, helper, 4) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams(); if (position == 0) { lp.rightMargin = 1; } else if (position == 1) { } else if (position == 2) { lp.topMargin = 1; lp.rightMargin = 1; } } }); } if (ONEN_LAYOUT) { adapters.add(new SubAdapter(this, new OnePlusNLayoutHelper(), 0)); OnePlusNLayoutHelper helper = new OnePlusNLayoutHelper(); helper.setBgColor(0xff87e543); helper.setAspectRatio(1.8f); helper.setColWeights(new float[]{33.33f, 50f, 40f}); helper.setMargin(10, 20, 10, 20); helper.setPadding(10, 10, 10, 10); LayoutParams lp = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); adapters.add(new SubAdapter(this, helper, 3, lp) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams(); if (position == 0) { lp.rightMargin = 1; } } }); } if (COLUMN_LAYOUT) { adapters.add(new SubAdapter(this, new ColumnLayoutHelper(), 0)); adapters.add(new SubAdapter(this, new ColumnLayoutHelper(), 4)); } if (FIX_LAYOUT) { FixLayoutHelper layoutHelper = new FixLayoutHelper(10, 10); adapters.add(new SubAdapter(this, layoutHelper, 0)); layoutHelper = new FixLayoutHelper(FixLayoutHelper.TOP_RIGHT, 20, 20); adapters.add(new SubAdapter(this, layoutHelper, 1) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200); holder.itemView.setLayoutParams(layoutParams); } }); } //if (STICKY_LAYOUT) { // StickyLayoutHelper layoutHelper = new StickyLayoutHelper(false); // adapters.add(new SubAdapter(this, layoutHelper, 0)); // layoutHelper = new StickyLayoutHelper(false); // layoutHelper.setOffset(100); // adapters.add(new SubAdapter(this, layoutHelper, 1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 100))); //} if (GRID_LAYOUT) { GridLayoutHelper layoutHelper = new GridLayoutHelper(2); layoutHelper.setMargin(7, 0, 7, 0); layoutHelper.setWeights(new float[]{46.665f}); layoutHelper.setHGap(3); adapters.add(new SubAdapter(this, layoutHelper, 2)); layoutHelper = new GridLayoutHelper(4); layoutHelper.setWeights(new float[]{20f, 26.665f}); layoutHelper.setMargin(7, 0, 7, 0); layoutHelper.setHGap(3); adapters.add(new SubAdapter(this, layoutHelper, 8)); } if (GRID_LAYOUT) { adapters.add(new SubAdapter(this, new GridLayoutHelper(4), 0)); GridLayoutHelper helper = new GridLayoutHelper(4); helper.setAspectRatio(4f); //helper.setColWeights(new float[]{40, 20, 30, 30}); // helper.setMargin(0, 10, 0, 10); helper.setGap(10); adapters.add(new SubAdapter(this, helper, 80) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams(); // lp.bottomMargin = 1; // lp.rightMargin = 1; } }); } if (FIX_LAYOUT) { adapters.add(new SubAdapter(this, new ScrollFixLayoutHelper(20, 20), 1) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams layoutParams = new LayoutParams(200, 200); holder.itemView.setLayoutParams(layoutParams); } }); } if (LINEAR_LAYOUT) adapters.add(new SubAdapter(this, new LinearLayoutHelper(), 10)); if (GRID_LAYOUT) { GridLayoutHelper helper = new GridLayoutHelper(3); helper.setMargin(0, 10, 0, 10); adapters.add(new SubAdapter(this, helper, 3)); } if (STAGGER_LAYOUT) { // adapters.add(new SubAdapter(this, new StaggeredGridLayoutHelper(2, 0), 0)); final StaggeredGridLayoutHelper helper = new StaggeredGridLayoutHelper(2, 10); helper.setMargin(20, 10, 10, 10); helper.setPadding(10, 10, 20, 10); helper.setBgColor(0xFF86345A); adapters.add(new SubAdapter(this, helper, 27) { @Override public void onBindViewHolder(MainViewHolder holder, int position) { super.onBindViewHolder(holder, position); LayoutParams layoutParams = new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 200); if (position % 2 == 0) { layoutParams.mAspectRatio = 1.0f; } else { layoutParams.height = 340 + position % 7 * 20; } holder.itemView.setLayoutParams(layoutParams); } }); } if (COLUMN_LAYOUT) { // adapters.add(new SubAdapter(this, new ColumnLayoutHelper(), 3)); } if (GRID_LAYOUT) { // adapters.add(new SubAdapter(this, new GridLayoutHelper(4), 24)); } adapters.add( new FooterAdapter(recyclerView, VLayoutActivity.this, new GridLayoutHelper(1), 1)); delegateAdapter.setAdapters(adapters); final Handler mainHandler = new Handler(Looper.getMainLooper()); trigger = new Runnable() { @Override public void run() { //recyclerView.scrollToPosition(22); //recyclerView.getAdapter().notifyDataSetChanged(); //mainHandler.postDelayed(trigger, 1000); //List newAdapters = new ArrayList<>(); //newAdapters.add((new SubAdapter(VLayoutActivity.this, new ColumnLayoutHelper(), 3))); //newAdapters.add((new SubAdapter(VLayoutActivity.this, new GridLayoutHelper(4), 24))); //delegateAdapter.addAdapter(0, new SubAdapter(VLayoutActivity.this, new ColumnLayoutHelper(), 3)); //delegateAdapter.addAdapter(1, new SubAdapter(VLayoutActivity.this, new GridLayoutHelper(4), 24)); //delegateAdapter.notifyDataSetChanged(); } }; findViewById(R.id.jump).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { EditText position = (EditText) findViewById(R.id.position); if (!TextUtils.isEmpty(position.getText())) { try { int pos = Integer.parseInt(position.getText().toString()); recyclerView.scrollToPosition(pos); } catch (Exception e) { Log.e("VlayoutActivity", e.getMessage(), e); } } else { recyclerView.requestLayout(); } //FooterAdapter footer = (FooterAdapter)adapters.get(adapters.size() - 1); //footer.toggleFoot(); } }); mainHandler.postDelayed(trigger, 1000); mSwipeRefreshLayout.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { mainHandler.postDelayed(new Runnable() { @Override public void run() { mSwipeRefreshLayout.setRefreshing(false); } }, 2000L); } }); setListenerToRootView(); } boolean isOpened = false; public void setListenerToRootView() { final View activityRootView = getWindow().getDecorView().findViewById(android.R.id.content); activityRootView.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int heightDiff = activityRootView.getRootView().getHeight() - activityRootView.getHeight(); if (heightDiff > 100) { // 99% of the time the height diff will be due to a keyboard. if (isOpened == false) { //Do two things, make the view top visible and the editText smaller } isOpened = true; } else if (isOpened == true) { isOpened = false; final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.main_view); recyclerView.getAdapter().notifyDataSetChanged(); } } }); } static class FooterAdapter extends DelegateAdapter.Adapter { private RecyclerView mRecyclerView; private Context mContext; private LayoutHelper mLayoutHelper; private LayoutParams mLayoutParams; private int mCount = 0; private boolean showFooter = false; public FooterAdapter(RecyclerView recyclerView, Context context, LayoutHelper layoutHelper, int count) { this(recyclerView, context, layoutHelper, count, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); } public FooterAdapter(RecyclerView recyclerView, Context context, LayoutHelper layoutHelper, int count, @NonNull LayoutParams layoutParams) { this.mRecyclerView = recyclerView; this.mContext = context; this.mLayoutHelper = layoutHelper; this.mCount = count; this.mLayoutParams = layoutParams; } @Override public int getItemViewType(int position) { return 100; } @Override public LayoutHelper onCreateLayoutHelper() { return mLayoutHelper; } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false)); } @Override public void onBindViewHolder(MainViewHolder holder, int position) { LayoutParams lp = (LayoutParams) holder.itemView.getLayoutParams(); if (showFooter) { lp.height = 300; } else { lp.height = 0; } holder.itemView.setLayoutParams(lp); } @Override protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal)); } @Override public int getItemCount() { return mCount; } public void toggleFoot() { this.showFooter = !this.showFooter; mRecyclerView.getAdapter().notifyItemChanged(205); mRecyclerView.post(new Runnable() { @Override public void run() { mRecyclerView.scrollToPosition(205); mRecyclerView.requestLayout(); } }); } } // RecyclableViewPager static class PagerAdapter extends RecyclablePagerAdapter { public PagerAdapter(SubAdapter adapter, RecyclerView.RecycledViewPool pool) { super(adapter, pool); } @Override public int getCount() { return 6; } @Override public void onBindViewHolder(MainViewHolder viewHolder, int position) { // only vertical viewHolder.itemView.setLayoutParams( new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); ((TextView) viewHolder.itemView.findViewById(R.id.title)).setText("Banner: " + position); } @Override public int getItemViewType(int position) { return 0; } } static class SubAdapter extends DelegateAdapter.Adapter { private Context mContext; private LayoutHelper mLayoutHelper; private LayoutParams mLayoutParams; private int mCount = 0; public SubAdapter(Context context, LayoutHelper layoutHelper, int count) { this(context, layoutHelper, count, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 300)); } public SubAdapter(Context context, LayoutHelper layoutHelper, int count, @NonNull LayoutParams layoutParams) { this.mContext = context; this.mLayoutHelper = layoutHelper; this.mCount = count; this.mLayoutParams = layoutParams; } @Override public LayoutHelper onCreateLayoutHelper() { return mLayoutHelper; } @Override public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return new MainViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item, parent, false)); } @Override public void onBindViewHolder(MainViewHolder holder, int position) { // only vertical holder.itemView.setLayoutParams( new LayoutParams(mLayoutParams)); } @Override protected void onBindViewHolderWithOffset(MainViewHolder holder, int position, int offsetTotal) { ((TextView) holder.itemView.findViewById(R.id.title)).setText(Integer.toString(offsetTotal)); } @Override public int getItemCount() { return mCount; } } static class MainViewHolder extends RecyclerView.ViewHolder { public static volatile int existing = 0; public static int createdTimes = 0; public MainViewHolder(View itemView) { super(itemView); createdTimes++; existing++; } @Override protected void finalize() throws Throwable { existing--; super.finalize(); } } } ================================================ FILE: examples/src/main/res/drawable/border_bg.xml ================================================ ================================================ FILE: examples/src/main/res/drawable/item_background.xml ================================================ ================================================ FILE: examples/src/main/res/layout/card_item.xml ================================================ ================================================ FILE: examples/src/main/res/layout/item.xml ================================================ ================================================ FILE: examples/src/main/res/layout/main_activity.xml ================================================