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.androidvlayout1.2.8aar
```
初始化```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

[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.androidvlayout1.2.8aar
```
### 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

[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`内部没有相对位置关系,不处理边距。

### 接口
对于`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`实现这个特性。

### 接口
使用背景色
`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计算出来的视图高度,因此具备更高优先级。

### 接口
对于`LayoutHelper`,调用
`public void setAspectRatio(float aspectRatio)`
对于`LayoutParams`,调用
`((VirutalLayoutManager.LayoutParams) layoutParams).mAspectRatio`
# dividerHeight
`LinearLayoutHelper`的属性,`LinearLayoutHelper`是像`ListView`一样的线性布局,dividerHeight就是每个组件之间的间距。

### 接口
对于`LinearLayoutHelper`,调用
`public void setDividerHeight(int dividerHeight)`
# weights
`ColumnLayoutHelper`, `GridLayoutHelper`的属性,它们都是提供网格状的布局能力,**建议使用`GridLayoutHelper`**,它的能力更加强大,参考下文介绍。默认情况下,每个网格中每一列的宽度是一样的,通过weights属性,可以指定让每一列的宽度成比例分配,就像`LinearLayout`的weight属性一样。
weights属性是一个float数组,每一项代表某一列占父容器宽度的百分比,总和建议是100,否则布局会超出容器宽度;如果布局中有4列,那么weights的长度也应该是4;长度大于4,多出的部分不参与宽度计算;如果小于4,不足的部分默认平分剩余的空间。

### 接口
对于`ColumnLayoutHelper`, `GridLayoutHelper`,调用
`public void setWeights(float[] weights)`
# vGap, hGap
`GridLayoutHelper`与`StaggeredGridLayoutHelper`都有这两个属性,分别控制视图之间的垂直间距和水平间距。

### 接口
对于`GridLayoutHelper`, `StaggeredGridLayoutHelper`,调用
`public void setHGap(int hGap)`
`public void setVGap(int vGap)`
# spanCount, spanSizeLookup
`GridLayoutHelper`的属性,参考于系统的`GridLayoutManager`,spanCount表示网格的列数,默认情况下每一个视图都占用一个网格区域,但通过提供自定义的spanSizeLookUp,可以指定某个位置的视图占用多个网格区域。

### 接口
使用spanCount调用
`public void setSpanCount(int spanCount)`
使用spanSizeLookup
`public void setSpanSizeLookup(SpanSizeLookup spanSizeLookup)`
# autoExpand
`GridLayoutHelper`的属性,当一行里视图的个数少于spanCount值的时候,如果autoExpand为true,视图的总宽度会填满可用区域;否则会在屏幕上留空白区域。

### 接口
调用
`public void setAutoExpand(boolean isAutoExpand)`
# lane
`StaggeredGridLayoutHelper`中有这个属性,与`GridLayoutHelper`里的spanCount类似,控制瀑布流的列数。
### 接口
调用
`public void setLane(int lane)`
# fixAreaAdjuster
fix类型的`LayoutHelper`,在可能需要设置一个相对父容器四个边的偏移量,比如整个页面里有一个固定的标题栏添加在vlayout容器上,vlayout内部的fix类型视图不希望与外部的标题有所重叠,那么就可以设置一个fixAreaAdjuster来做偏移。

### 接口
调用
`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`是视图底边相对父容器的下边距偏移量;

### 接口
设置基准调用
`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`:默认不显示视图,当页面滚出这个视图的位置的时候显示;

调用
`public void setShowType(int showType)`
# stickyStart, offset
`StickyLayoutHelper`的属性,当视图的位置在屏幕范围内时,视图会随页面滚动而滚动;当视图的位置滑出屏幕时,`StickyLayoutHelper`会将视图固定在顶部(`stickyStart = true`)或者底部(`stickyStart = false`),固定的位置支持设置偏移量offset。

调用
`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.

### 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.

### 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.

### 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.

### 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.

### 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.

### 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.

### 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.

### 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.

### 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;

### 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.

### 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.

### 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
================================================
================================================
FILE: examples/src/main/res/layout/view_pager.xml
================================================
================================================
FILE: examples/src/main/res/values/colors.xml
================================================
#666666#22EEEEEE#CCCCCC#77CEEE
================================================
FILE: examples/src/main/res/values/strings.xml
================================================
Example for VirtualLayout
================================================
FILE: examples/src/main/res/values/styles.xml
================================================
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
#
# 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.
#
#Tue Nov 29 17:56:32 CST 2016
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
================================================
FILE: gradle.properties
================================================
#
# 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.
#
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
# Default value: -Xmx10248m -XX:MaxPermSize=256m
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
GROUP=com.alibaba.android
ARTIFACT=vlayout
VERSION=1
VERSION_NAME=1.2.39
PACKAGING_TYPE=aar
useNewSupportLibrary=true
systemProp.compileSdkVersion=26
systemProp.targetSdkVersion=26
systemProp.buildToolsVersion=26.0.2
================================================
FILE: gradlew
================================================
#!/usr/bin/env bash
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# For Cygwin, ensure paths are in UNIX format before anything is touched.
if $cygwin ; then
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
fi
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >&-
APP_HOME="`pwd -P`"
cd "$SAVED" >&-
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin, switch paths to Windows format before running java
if $cygwin ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=$((i+1))
done
case $i in
(0) set -- ;;
(1) set -- "$args0" ;;
(2) set -- "$args0" "$args1" ;;
(3) set -- "$args0" "$args1" "$args2" ;;
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
function splitJvmOpts() {
JVM_OPTS=("$@")
}
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
================================================
FILE: gradlew.bat
================================================
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto init
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto init
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:init
@rem Get command-line arguments, handling Windowz variants
if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
set CMD_LINE_ARGS=
set _SKIP=2
:win9xME_args_slurp
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: jcenterDeploy.gradle
================================================
/*
* MIT License
*
* Copyright (c) 2017 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: 'com.jfrog.bintray'
apply plugin: 'com.jfrog.artifactory'
version = libraryVersion
if (project.hasProperty('deployVersion')) {
version = project.getProperty('deployVersion')
}
if (project.hasProperty("android")) { // Android libraries
task sourcesJar(type: Jar) {
classifier = 'sources'
from android.sourceSets.main.java.srcDirs
}
// task javadoc(type: Javadoc) {
// source = android.sourceSets.main.java.srcDirs
// classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
// android.libraryVariants.all { variant ->
// println variant.javaCompile.classpath.files
// if(variant.name == 'release') { //我们只需 release 的 javadoc
// task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
// // title = ''
// // description = ''
// source = variant.javaCompile.source
// classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath())
// options {
// encoding "utf-8"
// links "http://docs.oracle.com/javase/7/docs/api/"
// linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"
// }
// exclude '**/BuildConfig.java'
// exclude '**/R.java'
// }
// task("javadoc${variant.name.capitalize()}Jar", type: Jar, dependsOn: "generate${variant.name.capitalize()}Javadoc") {
// classifier = 'javadoc'
// from tasks.getByName("generate${variant.name.capitalize()}Javadoc").destinationDir
// }
// artifacts {
// archives tasks.getByName("javadoc${variant.name.capitalize()}Jar")
// }
// }
// }
//
// }
} else { // Java libraries
task sourcesJar(type: Jar, dependsOn: classes) {
classifier = 'sources'
from sourceSets.main.allSource
}
}
//task javadocJar(type: Jar, dependsOn: javadoc) {
// classifier = 'javadoc'
// from javadoc.destinationDir
//}
artifacts {
// archives javadocJar
archives sourcesJar
}
// Bintray
Properties properties = new Properties()
File localProperties = project.rootProject.file('local.properties')
if (localProperties.exists()) {
properties.load(project.rootProject.file('local.properties').newDataInputStream())
}
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
if (!user) {
user = project.hasProperty('bintrayUser') ? project.getProperty('bintrayUser') : ""
}
if (!key) {
key = project.hasProperty('bintrayApikey') ? project.getProperty('bintrayApikey') : ""
}
configurations = ['archives']
pkg {
repo = bintrayRepo
name = bintrayName
desc = libraryDescription
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = allLicenses
publish = true
publicDownloadNumbers = true
version {
desc = libraryDescription
gpg {
sign = true //Determines whether to GPG sign the files. The default is false
passphrase = properties.getProperty("bintray.gpg.password")
if (!passphrase) {
passphrase = project.hasProperty('bintrayGPG') ? project.getProperty('bintrayGPG') : ""
}
//Optional. The passphrase for GPG signing'
}
mavenCentralSync {
sync = true
user = properties.getProperty('bintray.oss.user')
if (!user) {
user = project.hasProperty('bintray.oss.user') ? project.getProperty('bintray.oss.user') : ""
}
password = properties.getProperty('bintray.oss.password')
if (!password) {
password = project.hasProperty('bintray.oss.password') ? project.getProperty('bintray.oss.password') : ""
}
close = '1'
}
}
}
}
artifactory {
contextUrl = 'http://oss.jfrog.org/artifactory' //The base Artifactory URL if not overridden by the publisher/resolver
resolve {
repository {
repoKey = 'libs-release'
}
}
publish {
repository {
repoKey = 'oss-snapshot-local' //The Artifactory repository key to publish to
username = bintray.user
password = bintray.key
maven = true
}
defaults {
// the name is the same with that defined in bintray.configurations
publishConfigs('archives')
}
}
}
bintrayUpload.onlyIf {
!version.endsWith("-SNAPSHOT")
}
artifactoryPublish.onlyIf {
version.endsWith("-SNAPSHOT")
}
task deploy(dependsOn: ['install', 'bintrayUpload', 'artifactoryPublish']) << {
println "deploy ...."
}
================================================
FILE: jcenterInstall.gradle
================================================
/*
* MIT License
*
* Copyright (c) 2017 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: 'com.github.dcendents.android-maven'
group = GROUP
install {
repositories.mavenInstaller {
// This generates POM.xml with proper parameters
pom {
project {
packaging 'aar'
groupId publishedGroupId
artifactId artifact
// Add your description here
name libraryName
description libraryDescription
url siteUrl
// Set your license
licenses {
license {
name licenseName
url licenseUrl
}
}
developers {
developer {
id developerId
name developerName
email developerEmail
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
================================================
FILE: settings.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.
*/
include ':vlayout'
include ':examples'
================================================
FILE: vlayout/.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: vlayout/DESIGN.md
================================================
# VirtualLayout 设计说明
# VirtualLayout Design Note
================================================
FILE: vlayout/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.
*/
apply plugin: 'com.android.library'
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.3.0'
}
}
repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }
maven { url "http://oss.jfrog.org/oss-snapshot-local/" }
jcenter()
mavenLocal()
}
ext {
bintrayRepo = 'Tangram'
bintrayName = 'vlayout'
publishedGroupId = project.hasProperty('GROUP') ? GROUP : ''
libraryName = project.hasProperty('ARTIFACT') ? ARTIFACT : ''
artifact = project.hasProperty('ARTIFACT') ? ARTIFACT : ''
libraryDescription = 'Project vlayout is a powerfull 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.'
siteUrl = 'https://github.com/alibaba/vlayout'
gitUrl = 'https://github.com/alibaba/vlayout.git'
libraryVersion = project.hasProperty('VERSION_NAME') ? VERSION_NAME : ''
developerId = 'longerian'
developerName = 'longerian'
developerEmail = 'xlhongultimate@gmail.com'
licenseName = 'MIT'
licenseUrl = 'https://opensource.org/licenses/MIT'
allLicenses = ["MIT"]
}
def VERSION = System.properties['version'] ?: '10'
def VERSION_NAME = System.properties['versionName'] ?: '0.1.0'
android {
compileSdkVersion Integer.parseInt(System.properties['compileSdkVersion'] ?: '22')
buildToolsVersion System.properties['buildToolsVersion']
defaultConfig {
minSdkVersion 14
targetSdkVersion Integer.parseInt(System.properties['targetSdkVersion'] ?: '14')
versionCode Integer.parseInt(VERSION ?: '10')
versionName VERSION_NAME
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
lintOptions {
abortOnError false
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
// compile project(':extension')
if (project.hasProperty('useNewSupportLibrary')) {
compile 'com.android.support:recyclerview-v7:25.2.0@aar'
compile('com.android.support:support-v4:25.2.0@aar')
compile 'com.android.support:support-annotations:25.2.0'
compile 'com.android.support:support-compat:25.2.0'
compile 'com.android.support:support-core-ui:25.2.0'
} else {
compile 'com.android.support:recyclerview-v7:23.1.1@aar'
compile('com.android.support:support-v4:23.1.1@aar') {
exclude group: 'com.android.support', module: 'support-annotations'
}
compile 'com.android.support:support-annotations:23.1.1'
}
androidTestCompile "org.robolectric:robolectric:3.0"
}
File deployConfig = rootProject.file('deploy.gradle')
if (deployConfig.exists()) {
apply from: rootProject.file('deploy.gradle')
}
deployConfig = rootProject.file('jcenterInstall.gradle')
if (deployConfig.exists()) {
apply from: rootProject.file('jcenterInstall.gradle')
}
deployConfig = rootProject.file('jcenterDeploy.gradle')
if (deployConfig.exists()) {
apply from: rootProject.file('jcenterDeploy.gradle')
}
================================================
FILE: vlayout/jacoco.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.
*/
apply plugin: 'jacoco'
jacoco {
toolVersion = "0.7.1.201405082137"
reportsDir = file("$buildDir/reports/jacoco")
}
jacocoTestReport {
reports {
xml.enabled false
csv.enabled false
html.destination "${buildDir}/jacocoHtml"
}
}
android.testOptions.unitTests.all {
// configure the set of classes for JUnit tests
include 'com/alibaba/android/**/*Test.class'
//exclude '**/espresso/**/*.class'
// configure max heap size of the test JVM
maxHeapSize = "2048m"
}
def coverageSourceDirs = [
'../app/src/main/java'
]
task jacocoTestReport(type: JacocoReport, dependsOn: "testDebug") {
group = "Reporting"
description = "Generate Jacoco coverage reports"
classDirectories = fileTree(
dir: '../app/build/intermediates/classes/debug',
excludes: ['**/R.class',
'**/R$*.class',
'**/*$ViewInjector*.*',
'**/BuildConfig.*',
'**/Manifest*.*']
)
additionalSourceDirs = files(coverageSourceDirs)
sourceDirectories = files(coverageSourceDirs)
executionData = files('../app/build/jacoco/testDebug.exec')
reports {
xml.enabled = false
html.enabled = true
}
}
================================================
FILE: vlayout/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 {
*;
}
-keep class android.support.v7.widget.LinearLayoutManager {
void ensureLayoutState();
void resolveShouldLayoutReverse();
}
================================================
FILE: vlayout/src/androidTest/java/com/alibaba/android/vlayout/ViewHolderHelper.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.support.v7.widget.RecyclerView;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
/**
* Created by villadora on 16/2/24.
*/
public class ViewHolderHelper {
private static Field vhField = null;
public static void setField(RecyclerView.ViewHolder holder, String fieldName, Object value) {
try {
Field f = RecyclerView.LayoutParams.class.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(holder, value);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
public static RecyclerView.ViewHolder getViewHolder(RecyclerView.LayoutParams params) {
try {
if (vhField == null) {
vhField = RecyclerView.LayoutParams.class.getDeclaredField("mViewHolder");
vhField.setAccessible(true);
}
//vhField.set(params, holder);
return (RecyclerView.ViewHolder) vhField.get(params);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
private static Method vhSetFlags;
public static void addViewHolderFlag(RecyclerView.ViewHolder holder, int flag) {
try {
if (vhSetFlags == null) {
vhSetFlags = RecyclerView.ViewHolder.class.getDeclaredMethod("addFlags", int.class);
vhSetFlags.setAccessible(true);
}
vhSetFlags.invoke(holder, flag);
} catch (Exception e) {
}
}
}
================================================
FILE: vlayout/src/androidTest/java/com/alibaba/android/vlayout/VirtualLayoutManagerTest.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.Activity;
import android.content.Context;
import android.graphics.Rect;
import android.os.Looper;
import android.support.v7.widget.RecyclerView;
import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static com.alibaba.android.vlayout.VirtualLayoutManager.HORIZONTAL;
import static com.alibaba.android.vlayout.VirtualLayoutManager.VERTICAL;
/**
* Created by villadora on 16/2/24.
*/
public class VirtualLayoutManagerTest extends ActivityInstrumentationTestCase2 {
private static final String TAG = "VLMTest";
private static final boolean DEBUG = false;
protected RecyclerView mRecyclerView;
protected Throwable mainThreadException;
protected WrappedLinearLayoutManager mLayoutManager;
private TestAdapter mTestAdapter;
public VirtualLayoutManagerTest() {
super("com.tmall.wireless.tangram", Activity.class);
}
@Override
protected void setUp() throws Exception {
super.setUp();
}
public void testGetFirstLastChildrenTest() throws Throwable {
getFirstLastChildrenTest(new Config().orientation(VERTICAL));
}
public void getFirstLastChildrenTest(final Config config) throws Throwable {
setupByConfig(config, true);
Runnable viewInBoundsTest = new Runnable() {
@Override
public void run() {
VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
final String boundsLog = mLayoutManager.getBoundsLog();
assertEquals(config + ":\nfirst visible child should match traversal result\n"
+ boundsLog, visibleChildren.firstVisiblePosition,
mLayoutManager.findFirstVisibleItemPosition()
);
assertEquals(
config + ":\nfirst fully visible child should match traversal result\n"
+ boundsLog, visibleChildren.firstFullyVisiblePosition,
mLayoutManager.findFirstCompletelyVisibleItemPosition()
);
assertEquals(config + ":\nlast visible child should match traversal result\n"
+ boundsLog, visibleChildren.lastVisiblePosition,
mLayoutManager.findLastVisibleItemPosition()
);
assertEquals(
config + ":\nlast fully visible child should match traversal result\n"
+ boundsLog, visibleChildren.lastFullyVisiblePosition,
mLayoutManager.findLastCompletelyVisibleItemPosition()
);
}
};
runTestOnUiThread(viewInBoundsTest);
// smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
// case
final int scrollPosition = config.mStackFromEnd ? 0 : mTestAdapter.getItemCount();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.smoothScrollToPosition(scrollPosition);
}
});
while (mLayoutManager.isSmoothScrolling() ||
mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
runTestOnUiThread(viewInBoundsTest);
Thread.sleep(400);
}
// delete all items
mLayoutManager.expectLayouts(2);
mTestAdapter.deleteAndNotify(0, mTestAdapter.getItemCount());
mLayoutManager.waitForLayout(2);
// test empty case
runTestOnUiThread(viewInBoundsTest);
// set a new adapter with huge items to test full bounds check
mLayoutManager.expectLayouts(1);
final int totalSpace = mLayoutManager.mOrientationHelper.getTotalSpace();
final TestAdapter newAdapter = new TestAdapter(100) {
@Override
public void onBindViewHolder(TestViewHolder holder,
int position) {
super.onBindViewHolder(holder, position);
if (config.mOrientation == HORIZONTAL) {
holder.itemView.setMinimumWidth(totalSpace + 5);
} else {
holder.itemView.setMinimumHeight(totalSpace + 5);
}
}
};
runTestOnUiThread(new Runnable() {
@Override
public void run() {
mRecyclerView.setAdapter(newAdapter);
}
});
mLayoutManager.waitForLayout(2);
runTestOnUiThread(viewInBoundsTest);
}
void setupByConfig(Config config, boolean waitForFirstLayout) throws Throwable {
mRecyclerView = new RecyclerView(getActivity());
mRecyclerView.setHasFixedSize(true);
mTestAdapter = config.mTestAdapter == null ? new TestAdapter(config.mItemCount)
: config.mTestAdapter;
mRecyclerView.setAdapter(mTestAdapter);
mLayoutManager = new WrappedLinearLayoutManager(getActivity(), config.mOrientation,
config.mReverseLayout);
mLayoutManager.setStackFromEnd(config.mStackFromEnd);
mLayoutManager.setRecycleChildrenOnDetach(config.mRecycleChildrenOnDetach);
mRecyclerView.setLayoutManager(mLayoutManager);
if (waitForFirstLayout) {
waitForFirstLayout();
}
}
void postExceptionToInstrumentation(Throwable t) {
if (DEBUG) {
Log.e(TAG, "captured exception on main thread", t);
}
if (mainThreadException != null) {
Log.e(TAG, "receiving another main thread exception. dropping.", t);
} else {
mainThreadException = t;
}
if (mRecyclerView != null && mRecyclerView
.getLayoutManager() instanceof WrappedLinearLayoutManager) {
WrappedLinearLayoutManager lm = (WrappedLinearLayoutManager) mRecyclerView.getLayoutManager();
// finish all layouts so that we get the correct exception
while (lm.layoutLatch.getCount() > 0) {
lm.layoutLatch.countDown();
}
}
}
private void waitForFirstLayout() throws Throwable {
mLayoutManager.expectLayouts(1);
setRecyclerView(mRecyclerView);
mLayoutManager.waitForLayout(2);
}
public void setRecyclerView(final RecyclerView recyclerView) throws Throwable {
setRecyclerView(recyclerView, true);
}
public void setRecyclerView(final RecyclerView recyclerView, boolean assignDummyPool)
throws Throwable {
mRecyclerView = recyclerView;
if (assignDummyPool) {
RecyclerView.RecycledViewPool pool = new RecyclerView.RecycledViewPool() {
@Override
public RecyclerView.ViewHolder getRecycledView(int viewType) {
RecyclerView.ViewHolder viewHolder = super.getRecycledView(viewType);
if (viewHolder == null) {
return null;
}
ViewHolderHelper.addViewHolderFlag(viewHolder, 1); // RecyclerView.ViewHolder.FLAG_BOUND
ViewHolderHelper.setField(viewHolder, "mPosition", 200);
ViewHolderHelper.setField(viewHolder, "mOldPosition", 300);
ViewHolderHelper.setField(viewHolder, "mPreLayoutPosition", 500);
return viewHolder;
}
@Override
public void putRecycledView(RecyclerView.ViewHolder scrap) {
super.putRecycledView(scrap);
}
};
mRecyclerView.setRecycledViewPool(pool);
}
// mAdapterHelper = recyclerView.mAdapterHelper;
runTestOnUiThread(new Runnable() {
@Override
public void run() {
((ViewGroup) getActivity().findViewById(android.R.id.content)).addView(recyclerView);
}
});
}
class TestViewHolder extends RecyclerView.ViewHolder {
Item mBindedItem;
public TestViewHolder(View itemView) {
super(itemView);
itemView.setFocusable(true);
}
@Override
public String toString() {
return super.toString() + " item:" + mBindedItem;
}
}
static class Item {
final static AtomicInteger idCounter = new AtomicInteger(0);
final public int mId = idCounter.incrementAndGet();
int mAdapterIndex;
final String mText;
Item(int adapterIndex, String text) {
mAdapterIndex = adapterIndex;
mText = text;
}
@Override
public String toString() {
return "Item{" +
"mId=" + mId +
", originalIndex=" + mAdapterIndex +
", text='" + mText + '\'' +
'}';
}
}
class WrappedLinearLayoutManager extends VirtualLayoutManager {
CountDownLatch layoutLatch;
OnLayoutListener mOnLayoutListener;
public WrappedLinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
super(context, orientation, reverseLayout);
}
public void expectLayouts(int count) {
layoutLatch = new CountDownLatch(count);
}
public void waitForLayout(long timeout) throws InterruptedException {
waitForLayout(timeout, TimeUnit.SECONDS);
}
@Override
public void removeAndRecycleView(View child, RecyclerView.Recycler recycler) {
if (DEBUG) {
Log.d(TAG, "recycling view " + mRecyclerView.getChildViewHolder(child));
}
super.removeAndRecycleView(child, recycler);
}
@Override
public void removeAndRecycleViewAt(int index, RecyclerView.Recycler recycler) {
if (DEBUG) {
Log.d(TAG, "recycling view at" + mRecyclerView.getChildViewHolder(getChildAt(index)));
}
super.removeAndRecycleViewAt(index, recycler);
}
private void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
layoutLatch.await(timeout * (DEBUG ? 100 : 1), timeUnit);
assertEquals("all expected layouts should be executed at the expected time",
0, layoutLatch.getCount());
getInstrumentation().waitForIdleSync();
}
public String getBoundsLog() {
StringBuilder sb = new StringBuilder();
sb.append("view bounds:[start:").append(mOrientationHelper.getStartAfterPadding())
.append(",").append(" end").append(mOrientationHelper.getEndAfterPadding());
sb.append("\nchildren bounds\n");
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
sb.append("child (ind:").append(i).append(", pos:").append(getPosition(child))
.append("[").append("start:").append(
mOrientationHelper.getDecoratedStart(child)).append(", end:")
.append(mOrientationHelper.getDecoratedEnd(child)).append("]\n");
}
return sb.toString();
}
public void waitForAnimationsToEnd(int timeoutInSeconds) throws InterruptedException {
RecyclerView.ItemAnimator itemAnimator = mRecyclerView.getItemAnimator();
if (itemAnimator == null) {
return;
}
final CountDownLatch latch = new CountDownLatch(1);
final boolean running = itemAnimator.isRunning(
new RecyclerView.ItemAnimator.ItemAnimatorFinishedListener() {
@Override
public void onAnimationsFinished() {
latch.countDown();
}
}
);
if (running) {
latch.await(timeoutInSeconds, TimeUnit.SECONDS);
}
}
public VisibleChildren traverseAndFindVisibleChildren() {
int childCount = getChildCount();
final VisibleChildren visibleChildren = new VisibleChildren();
final int start = mOrientationHelper.getStartAfterPadding();
final int end = mOrientationHelper.getEndAfterPadding();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
final int childStart = mOrientationHelper.getDecoratedStart(child);
final int childEnd = mOrientationHelper.getDecoratedEnd(child);
final boolean fullyVisible = childStart >= start && childEnd <= end;
final boolean hidden = childEnd <= start || childStart >= end;
if (hidden) {
continue;
}
final int position = getPosition(child);
if (fullyVisible) {
if (position < visibleChildren.firstFullyVisiblePosition ||
visibleChildren.firstFullyVisiblePosition == RecyclerView.NO_POSITION) {
visibleChildren.firstFullyVisiblePosition = position;
}
if (position > visibleChildren.lastFullyVisiblePosition) {
visibleChildren.lastFullyVisiblePosition = position;
}
}
if (position < visibleChildren.firstVisiblePosition ||
visibleChildren.firstVisiblePosition == RecyclerView.NO_POSITION) {
visibleChildren.firstVisiblePosition = position;
}
if (position > visibleChildren.lastVisiblePosition) {
visibleChildren.lastVisiblePosition = position;
}
}
return visibleChildren;
}
Rect getViewBounds(View view) {
if (getOrientation() == HORIZONTAL) {
return new Rect(
mOrientationHelper.getDecoratedStart(view),
mSecondaryOrientationHelper.getDecoratedStart(view),
mOrientationHelper.getDecoratedEnd(view),
mSecondaryOrientationHelper.getDecoratedEnd(view));
} else {
return new Rect(
mSecondaryOrientationHelper.getDecoratedStart(view),
mOrientationHelper.getDecoratedStart(view),
mSecondaryOrientationHelper.getDecoratedEnd(view),
mOrientationHelper.getDecoratedEnd(view));
}
}
Map collectChildCoordinates() throws Throwable {
final Map items = new LinkedHashMap();
runTestOnUiThread(new Runnable() {
@Override
public void run() {
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child
.getLayoutParams();
TestViewHolder vh = (TestViewHolder) ViewHolderHelper.getViewHolder(lp);
items.put(vh.mBindedItem, getViewBounds(child));
}
}
});
return items;
}
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
try {
if (mOnLayoutListener != null) {
mOnLayoutListener.before(recycler, state);
}
super.onLayoutChildren(recycler, state);
if (mOnLayoutListener != null) {
mOnLayoutListener.after(recycler, state);
}
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
layoutLatch.countDown();
}
}
static class OnLayoutListener {
void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
}
void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
}
}
static class VisibleChildren {
int firstVisiblePosition = RecyclerView.NO_POSITION;
int firstFullyVisiblePosition = RecyclerView.NO_POSITION;
int lastVisiblePosition = RecyclerView.NO_POSITION;
int lastFullyVisiblePosition = RecyclerView.NO_POSITION;
@Override
public String toString() {
return "VisibleChildren{" +
"firstVisiblePosition=" + firstVisiblePosition +
", firstFullyVisiblePosition=" + firstFullyVisiblePosition +
", lastVisiblePosition=" + lastVisiblePosition +
", lastFullyVisiblePosition=" + lastFullyVisiblePosition +
'}';
}
}
static class Config implements Cloneable {
private static final int DEFAULT_ITEM_COUNT = 100;
private boolean mStackFromEnd;
TestAdapter mTestAdapter = null;
int mOrientation = VERTICAL;
boolean mReverseLayout = false;
boolean mRecycleChildrenOnDetach = false;
int mItemCount = DEFAULT_ITEM_COUNT;
// TestAdapter mTestAdapter;
Config(int orientation, boolean reverseLayout, boolean stackFromEnd) {
mOrientation = orientation;
mReverseLayout = reverseLayout;
mStackFromEnd = stackFromEnd;
}
public Config() {
}
Config adapter(TestAdapter adapter) {
mTestAdapter = adapter;
return this;
}
Config recycleChildrenOnDetach(boolean recycleChildrenOnDetach) {
mRecycleChildrenOnDetach = recycleChildrenOnDetach;
return this;
}
Config orientation(int orientation) {
mOrientation = orientation;
return this;
}
Config stackFromBottom(boolean stackFromBottom) {
mStackFromEnd = stackFromBottom;
return this;
}
Config reverseLayout(boolean reverseLayout) {
mReverseLayout = reverseLayout;
return this;
}
public Config itemCount(int itemCount) {
mItemCount = itemCount;
return this;
}
// required by convention
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Config{" +
"mStackFromEnd=" + mStackFromEnd +
", mOrientation=" + mOrientation +
", mReverseLayout=" + mReverseLayout +
", mRecycleChildrenOnDetach=" + mRecycleChildrenOnDetach +
", mItemCount=" + mItemCount +
'}';
}
}
class TestAdapter extends RecyclerView.Adapter {
List mItems;
TestAdapter(int count) {
mItems = new ArrayList(count);
for (int i = 0; i < count; i++) {
mItems.add(new Item(i, "Item " + i));
}
}
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
return new TestViewHolder(new TextView(parent.getContext()));
}
@Override
public void onBindViewHolder(TestViewHolder holder, int position) {
final Item item = mItems.get(position);
((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
holder.mBindedItem = item;
}
public void deleteAndNotify(final int start, final int count) throws Throwable {
deleteAndNotify(new int[]{start, count});
}
/**
* Deletes items in the given ranges.
*
* Note that each operation affects the one after so you should offset them properly.
*
* For example, if adapter has 5 items (A,B,C,D,E), and then you call this method with
* [1, 2],[2, 1], it will first delete items B,C and the new adapter will be
* A D E. Then it will delete 2,1 which means it will delete E.
*/
public void deleteAndNotify(final int[]... startCountTuples) throws Throwable {
for (int[] tuple : startCountTuples) {
tuple[1] = -tuple[1];
}
new AddRemoveRunnable(startCountTuples).runOnMainThread();
}
@Override
public long getItemId(int position) {
return hasStableIds() ? mItems.get(position).mId : super.getItemId(position);
}
public void offsetOriginalIndices(int start, int offset) {
for (int i = start; i < mItems.size(); i++) {
mItems.get(i).mAdapterIndex += offset;
}
}
/**
* @param start inclusive
* @param end exclusive
* @param offset
*/
public void offsetOriginalIndicesBetween(int start, int end, int offset) {
for (int i = start; i < end && i < mItems.size(); i++) {
mItems.get(i).mAdapterIndex += offset;
}
}
public void addAndNotify(final int start, final int count) throws Throwable {
addAndNotify(new int[]{start, count});
}
public void addAndNotify(final int[]... startCountTuples) throws Throwable {
new AddRemoveRunnable(startCountTuples).runOnMainThread();
}
public void dispatchDataSetChanged() throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
notifyDataSetChanged();
}
});
}
public void changeAndNotify(final int start, final int count) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
notifyItemRangeChanged(start, count);
}
});
}
public void changePositionsAndNotify(final int... positions) throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < positions.length; i += 1) {
TestAdapter.super.notifyItemRangeChanged(positions[i], 1);
}
}
});
}
/**
* Similar to other methods but negative count means delete and position count means add.
*
* For instance, calling this method with [1,1], [2,-1] it will first add an
* item to index 1, then remove an item from index 2 (updated index 2)
*/
public void addDeleteAndNotify(final int[]... startCountTuples) throws Throwable {
new AddRemoveRunnable(startCountTuples).runOnMainThread();
}
@Override
public int getItemCount() {
return mItems.size();
}
public void moveItems(boolean notifyChange, int[]... fromToTuples) throws Throwable {
for (int i = 0; i < fromToTuples.length; i += 1) {
int[] tuple = fromToTuples[i];
moveItem(tuple[0], tuple[1], false);
}
if (notifyChange) {
dispatchDataSetChanged();
}
}
public void moveItem(final int from, final int to, final boolean notifyChange)
throws Throwable {
runTestOnUiThread(new Runnable() {
@Override
public void run() {
Item item = mItems.remove(from);
mItems.add(to, item);
offsetOriginalIndices(from, to - 1);
item.mAdapterIndex = to;
if (notifyChange) {
notifyDataSetChanged();
}
}
});
}
private class AddRemoveRunnable implements Runnable {
final int[][] mStartCountTuples;
public AddRemoveRunnable(int[][] startCountTuples) {
mStartCountTuples = startCountTuples;
}
public void runOnMainThread() throws Throwable {
if (Looper.myLooper() == Looper.getMainLooper()) {
run();
} else {
runTestOnUiThread(this);
}
}
@Override
public void run() {
for (int[] tuple : mStartCountTuples) {
if (tuple[1] < 0) {
delete(tuple);
} else {
add(tuple);
}
}
}
private void add(int[] tuple) {
// offset others
offsetOriginalIndices(tuple[0], tuple[1]);
for (int i = 0; i < tuple[1]; i++) {
mItems.add(tuple[0], new Item(i, "new item " + i));
}
notifyItemRangeInserted(tuple[0], tuple[1]);
}
private void delete(int[] tuple) {
final int count = -tuple[1];
offsetOriginalIndices(tuple[0] + count, tuple[1]);
for (int i = 0; i < count; i++) {
mItems.remove(tuple[0]);
}
notifyItemRangeRemoved(tuple[0], count);
}
}
}
}
================================================
FILE: vlayout/src/main/AndroidManifest.xml
================================================
================================================
FILE: vlayout/src/main/java/com/alibaba/android/vlayout/Cantor.java
================================================
package com.alibaba.android.vlayout;
/**
* Created by longerian on 2017/12/10.
*
* @author longerian
* @date 2017/12/10
*/
public class Cantor {
/**
* @param k1
* @param k2
* @return cantor pair for k1 and k2
*/
public static long getCantor(long k1, long k2) {
return (k1 + k2) * (k1 + k2 + 1) / 2 + k2;
}
/**
* reverse cantor pair to origin number k1 and k2, k1 is stored in result[0], and k2 is stored in result[1]
* @param cantor a computed cantor number
* @param result the array to store output values
*/
public static void reverseCantor(long cantor, long[] result) {
if (result == null || result.length < 2) {
result = new long[2];
}
// reverse Cantor Function
long w = (long) (Math.floor(Math.sqrt(8 * cantor + 1) - 1) / 2);
long t = (w * w + w) / 2;
long k2 = cantor - t;
long k1 = w - k2;
result[0] = k1;
result[1] = k2;
}
}
================================================
FILE: vlayout/src/main/java/com/alibaba/android/vlayout/DelegateAdapter.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.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v7.widget.RecyclerView;
import android.util.Pair;
import android.util.SparseArray;
import android.view.View;
import android.view.ViewGroup;
import com.alibaba.android.vlayout.layout.SingleLayoutHelper;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import static android.support.v7.widget.RecyclerView.NO_ID;
/**
* Adapter delegates its responsibility to sub adapters
*
* @author villadora
* @since 1.0.0
*/
public class DelegateAdapter extends VirtualLayoutAdapter {
@Nullable
private AtomicInteger mIndexGen;
private int mIndex = 0;
private final boolean mHasConsistItemType;
private SparseArray mItemTypeAry = new SparseArray<>();
@NonNull
private final List> mAdapters = new ArrayList<>();
private int mTotal = 0;
private final SparseArray> mIndexAry = new SparseArray<>();
private long[] cantorReverse = new long[2];
/**
* Delegate Adapter merge multi sub adapters, default is thread-unsafe
*
* @param layoutManager layoutManager
*/
public DelegateAdapter(VirtualLayoutManager layoutManager) {
this(layoutManager, false, false);
}
/**
* @param layoutManager layoutManager
* @param hasConsistItemType whether sub adapters itemTypes are consistent
*/
public DelegateAdapter(VirtualLayoutManager layoutManager, boolean hasConsistItemType) {
this(layoutManager, hasConsistItemType, false);
}
/**
* @param layoutManager layoutManager
* @param hasConsistItemType whether sub adapters itemTypes are consistent
* @param threadSafe tell whether your adapter is thread-safe or not
*/
DelegateAdapter(VirtualLayoutManager layoutManager, boolean hasConsistItemType, boolean threadSafe) {
super(layoutManager);
if (threadSafe) {
mIndexGen = new AtomicInteger(0);
}
mHasConsistItemType = hasConsistItemType;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (mHasConsistItemType) {
Adapter adapter = mItemTypeAry.get(viewType);
if (adapter != null) {
return adapter.onCreateViewHolder(parent, viewType);
}
return null;
}
// reverse Cantor Function
Cantor.reverseCantor(viewType, cantorReverse);
int index = (int)cantorReverse[1];
int subItemType = (int)cantorReverse[0];
Adapter adapter = findAdapterByIndex(index);
if (adapter == null) {
return null;
}
return adapter.onCreateViewHolder(parent, subItemType);
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
Pair pair = findAdapterByPosition(position);
if (pair == null) {
return;
}
pair.second.onBindViewHolder(holder, position - pair.first.mStartPosition);
pair.second.onBindViewHolderWithOffset(holder, position - pair.first.mStartPosition, position);
}
@SuppressWarnings("unchecked")
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position, List