├── .gitignore
├── README.md
├── build.gradle
├── dbexample
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── tonicartos
│ │ └── superslimdbexample
│ │ └── ApplicationTest.java
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── tonicartos
│ │ └── superslimdbexample
│ │ ├── Adapter.java
│ │ ├── ContentFragment.java
│ │ ├── DataProvider.java
│ │ ├── DatabaseHelper.java
│ │ ├── FirstRunHelper.java
│ │ ├── LinearInterceptManager.java
│ │ ├── MainActivity.java
│ │ └── model
│ │ ├── Country.java
│ │ ├── DbModel.java
│ │ ├── JsonData.java
│ │ ├── Region.java
│ │ └── Subregion.java
│ └── res
│ ├── layout-v17
│ ├── country_item.xml
│ ├── region_header.xml
│ └── subregion_header.xml
│ ├── layout
│ ├── activity_main.xml
│ ├── country_item.xml
│ ├── fragment_main.xml
│ ├── region_header.xml
│ └── subregion_header.xml
│ ├── menu
│ └── menu_main.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── raw
│ └── init_db_data.json
│ ├── values-w820dp
│ └── dimens.xml
│ └── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── doc
├── LayoutManager.html
├── section.png
└── src
│ └── LayoutManager.md
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── library
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── tonicartos
│ │ └── superslim
│ │ └── ApplicationTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── kotlin
│ │ └── com
│ │ │ └── tonicartos
│ │ │ └── superslim
│ │ │ ├── adapter
│ │ │ ├── FooterStyle.java
│ │ │ ├── HeaderStyle.java
│ │ │ ├── adapter.kt
│ │ │ └── graph.kt
│ │ │ ├── extensions.kt
│ │ │ ├── graph.kt
│ │ │ ├── internal
│ │ │ ├── config_helpers.kt
│ │ │ ├── extensions.kt
│ │ │ ├── graph.kt
│ │ │ ├── item_management.kt
│ │ │ ├── layout
│ │ │ │ ├── footer.kt
│ │ │ │ ├── generic.kt
│ │ │ │ ├── header.kt
│ │ │ │ └── padding.kt
│ │ │ └── layout_helpers.kt
│ │ │ ├── layout.kt
│ │ │ ├── layout
│ │ │ ├── flexbox.kt
│ │ │ ├── grid.kt
│ │ │ ├── linear.kt
│ │ │ └── staggered_grid.kt
│ │ │ ├── layout_manager.kt
│ │ │ └── section_data_mixins.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ ├── kotlin
│ └── com
│ │ └── tonicartos
│ │ └── superslim
│ │ ├── grid_layout.kt
│ │ ├── header_layout.kt
│ │ ├── linear_layout.kt
│ │ └── section.kt
│ └── resources
│ └── mockito-extensions
│ └── org.mockito.plugins.MockMaker
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | #/.idea/workspace.xml
4 | #/.idea/libraries
5 | .DS_Store
6 | #.idea/vcs.xml
7 | captures
8 |
9 | .idea/
10 | .gradle
11 | build/
12 | *.iml
13 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # SuperSLiM
2 |
3 | This is the version 5 development branch.
4 |
5 | [Project Plan](https://trello.com/b/g3Wctuey)
6 | [Support me on Patreon](https://www.patreon.com/Tonic)
7 | [Blog](https://branchmerge.blogspot.com)
8 |
9 | ## What is Version 5
10 | Version 5 is the current development branch for SuperSLiM. Once Milestone 3 is reached it will become the main branch for the library. Currently available from this branch are the following things:
11 |
12 | - an adapter implementation backed by a DAG
13 | - a layout manager that can layout a hierarchy of nested sections
14 | - a linear section layout
15 | - predictive animations for item changes
16 | - support for layout configurations; layout direction (LTR, RTL), reverse layout, stack from end, and horizontal and vertical layout configurations.
17 | - an example app
18 | - way too much work for what you see
19 |
20 | What does it not yet have? **Lots**. See the [project plan](https://trello.com/b/g3Wctuey) for more details, and support me on [patreon](https://www.pateron.com/Tonic).
21 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | kotlin_version = '1.1.1'
6 | build_tools_version = '25.0.2'
7 | supportlib_version = '25.3.0'
8 | anko_version = '0.9.1'
9 | compile_sdk = 25
10 | target_sdk = 25
11 | }
12 |
13 | repositories {
14 | jcenter()
15 | mavenCentral()
16 | maven {
17 | url "http://dl.bintray.com/kotlin/kotlin-eap-1.1"
18 | }
19 | }
20 |
21 | dependencies {
22 | classpath 'com.android.tools.build:gradle:2.3.0'
23 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
24 |
25 | // NOTE: Do not place your application dependencies here; they belong
26 | // in the individual module build.gradle files
27 | }
28 | }
29 |
30 | task createTestResources << {
31 | def mockMakerFile = new File("$projectDir/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker")
32 | if (System.env.MOCK_MAKER != null) {
33 | logger.info("Using MockMaker ${System.env.MOCK_MAKER}")
34 | mockMakerFile.parentFile.mkdirs()
35 | mockMakerFile.createNewFile()
36 | mockMakerFile.write(System.env.MOCK_MAKER)
37 | } else {
38 | logger.info("Using default MockMaker")
39 | }
40 | }
41 |
42 | allprojects {
43 | repositories {
44 | jcenter()
45 | mavenCentral()
46 | maven {
47 | url "http://dl.bintray.com/kotlin/kotlin-eap-1.1"
48 | }
49 | }
50 | }
51 |
52 | task clean(type: Delete) {
53 | delete rootProject.buildDir
54 | }
--------------------------------------------------------------------------------
/dbexample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/dbexample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion compile_sdk
5 | buildToolsVersion build_tools_version
6 |
7 | defaultConfig {
8 | applicationId "com.tonicartos.superslimdbexample"
9 | minSdkVersion 11
10 | targetSdkVersion target_sdk
11 | versionCode 1
12 | versionName "1.0"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 |
25 | compile 'com.squareup.moshi:moshi:1.1.0'
26 | compile 'com.squareup.okio:okio:1.6.0'
27 |
28 | compile "com.android.support:recyclerview-v7:$supportlib_version"
29 | compile "com.android.support:appcompat-v7:$supportlib_version"
30 | compile "com.android.support:design:$supportlib_version"
31 | compile project(':library')
32 | }
33 |
--------------------------------------------------------------------------------
/dbexample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/tonic/.local/share/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class mName to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/dbexample/src/androidTest/java/com/tonicartos/superslimdbexample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 |
11 | public ApplicationTest() {
12 | super(Application.class);
13 | }
14 | }
--------------------------------------------------------------------------------
/dbexample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/Adapter.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import com.tonicartos.superslim.SectionConfig;
4 | import com.tonicartos.superslim.adapter.Item;
5 | import com.tonicartos.superslim.adapter.Section;
6 | import com.tonicartos.superslim.adapter.SuperSlimAdapter;
7 | import com.tonicartos.superslim.layout.LinearSectionConfig;
8 |
9 | import android.database.Cursor;
10 | import android.support.annotation.NonNull;
11 | import android.support.design.widget.Snackbar;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.text.TextUtils;
14 | import android.view.LayoutInflater;
15 | import android.view.View;
16 | import android.view.ViewGroup;
17 | import android.widget.TextView;
18 |
19 | class Adapter extends SuperSlimAdapter {
20 |
21 | private final static int VIEW_TYPE_REGION_HEADER = 1;
22 |
23 | private final static int VIEW_TYPE_SUBREGION_HEADER = 2;
24 |
25 | private final static int VIEW_TYPE_COUNTRY_VIEW = 3;
26 |
27 | private final static int VIEW_TYPE_REGION_FOOTER = 4;
28 |
29 | private final static int VIEW_TYPE_SUBREGION_FOOTER = 5;
30 |
31 | private Cursor mCursor;
32 |
33 | Adapter() {
34 | super();
35 | }
36 |
37 | @Override
38 | public void onBindViewHolder(@NonNull ViewHolder holder, @NonNull Item item) {
39 | holder.bind(item);
40 | }
41 |
42 | @Override
43 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
44 | return ViewHolder.make(this, viewGroup, viewType);
45 | }
46 |
47 | void swapCursor(Cursor data) {
48 | mCursor = data;
49 | if (mCursor == null) {
50 | reset();
51 | return;
52 | }
53 |
54 | updateGraph();
55 | }
56 |
57 | private void reset() {
58 | }
59 |
60 | private void updateGraph() {
61 | final int count = mCursor.getCount();
62 | mCursor.moveToFirst();
63 | for (int i = 0; i < count; ) {
64 | final String currentRegionName = mCursor.getString(1);
65 | final Item currentRegion = new Item(VIEW_TYPE_REGION_HEADER, currentRegionName);
66 | final Section regionSection = createSection(currentRegionName,
67 | new LinearSectionConfig(SectionConfig.DEFAULT_GUTTER, SectionConfig.DEFAULT_GUTTER,
68 | SectionConfig.HEADER_INLINE, SectionConfig.FOOTER_STICKY),
69 | currentRegion);
70 |
71 | while (i < count) {
72 | final String region = mCursor.getString(1);
73 | if (!TextUtils.equals(currentRegionName, region)) {
74 | break;
75 | }
76 |
77 | final String currentSubRegionName = mCursor.getString(4);
78 | final Item currentSubRegion = new Item(VIEW_TYPE_SUBREGION_HEADER, currentSubRegionName);
79 | final Section subregionSection = createSection(currentSubRegionName,
80 | new LinearSectionConfig(SectionConfig.DEFAULT_GUTTER, SectionConfig.DEFAULT_GUTTER,
81 | SectionConfig.HEADER_INLINE, SectionConfig.FOOTER_STICKY),
82 | currentSubRegion);
83 | regionSection.add(subregionSection);
84 | for (; i < count; i++) {
85 | final String subRegion = mCursor.getString(4);
86 | if (!TextUtils.equals(currentSubRegionName, subRegion)) {
87 | break;
88 | }
89 |
90 | subregionSection.add(new Item(VIEW_TYPE_COUNTRY_VIEW, mCursor.getString(7)));
91 |
92 | mCursor.moveToNext();
93 | }
94 | subregionSection.setFooter(
95 | new Item(VIEW_TYPE_SUBREGION_FOOTER, subregionSection.getChildCount() + " countries"));
96 | }
97 | regionSection.setFooter(
98 | new Item(VIEW_TYPE_REGION_FOOTER, regionSection.getChildCount() + " sub-regions"));
99 | addSection(regionSection);
100 | }
101 | }
102 |
103 | static class ViewHolder extends RecyclerView.ViewHolder {
104 |
105 | final TextView mTextView;
106 |
107 | final Adapter mAdapter;
108 |
109 | protected String name;
110 |
111 | ViewHolder(View itemView, Adapter adapter) {
112 | super(itemView);
113 | mTextView = (TextView) itemView.findViewById(R.id.text);
114 | mAdapter = adapter;
115 | }
116 |
117 | static ViewHolder make(Adapter adapter, ViewGroup viewGroup, int viewType) {
118 | LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
119 | switch (viewType) {
120 | case VIEW_TYPE_COUNTRY_VIEW:
121 | return new CountryViewHolder(
122 | inflater.inflate(R.layout.country_item, viewGroup, false), adapter);
123 | case VIEW_TYPE_REGION_HEADER:
124 | return new RegionViewHolder(
125 | inflater.inflate(R.layout.region_header, viewGroup, false), adapter);
126 | case VIEW_TYPE_SUBREGION_HEADER:
127 | return new SubregionViewHolder(
128 | inflater.inflate(R.layout.subregion_header, viewGroup, false), adapter);
129 | case VIEW_TYPE_REGION_FOOTER:
130 | return new RegionViewHolder(
131 | inflater.inflate(R.layout.region_header, viewGroup, false), adapter);
132 | case VIEW_TYPE_SUBREGION_FOOTER:
133 | return new RegionViewHolder(
134 | inflater.inflate(R.layout.subregion_header, viewGroup, false), adapter);
135 | default:
136 | throw new RuntimeException("Unknown view type " + viewType);
137 |
138 | }
139 | }
140 |
141 | void bind(Item item) {
142 | Object data = item.getData();
143 | assert data != null;
144 | name = data.toString();
145 | mTextView.setText(name);
146 | }
147 | }
148 |
149 | private static class ClickableHolder extends ViewHolder implements View.OnClickListener {
150 |
151 | ClickableHolder(View view, Adapter adapter) {
152 | super(view, adapter);
153 | view.setOnClickListener(this);
154 | }
155 |
156 | @Override
157 | public void onClick(View v) {
158 | Snackbar.make(itemView,
159 | "Click on item " + getAdapterPosition() + " with text " + mTextView.getText(),
160 | Snackbar.LENGTH_SHORT).show();
161 | }
162 | }
163 |
164 | private static class RegionViewHolder extends ClickableHolder {
165 |
166 | RegionViewHolder(View itemView, Adapter adapter) {
167 | super(itemView, adapter);
168 | }
169 |
170 | @Override
171 | public void onClick(View v) {
172 | final Section section = mAdapter.getSectionWithId(name);
173 | if (section != null) {
174 | section.toggleChildren();
175 | // Snackbar.make(itemView,
176 | // (section.getCollapsed() ? "Collapsed region " : "Expanded region ") + mTextView
177 | // .getText(),
178 | // Snackbar.LENGTH_SHORT).show();
179 | }
180 | }
181 | }
182 |
183 | private static class SubregionViewHolder extends ClickableHolder {
184 |
185 | SubregionViewHolder(View itemView, Adapter adapter) {
186 | super(itemView, adapter);
187 | }
188 |
189 | @Override
190 | public void onClick(View v) {
191 | Section section = mAdapter.getSectionWithId(name);
192 | if (section != null) {
193 | section.toggleChildren();
194 | // Snackbar.make(itemView,
195 | // (section.getCollapsed() ? "Collapsed subregion " : "Expanded subregion ")
196 | // + mTextView.getText(),
197 | // Snackbar.LENGTH_SHORT).show();
198 | }
199 | }
200 | }
201 |
202 | private static class CountryViewHolder extends ClickableHolder {
203 |
204 | CountryViewHolder(View itemView, Adapter adapter) {
205 | super(itemView, adapter);
206 | }
207 |
208 | @Override
209 | public void onClick(View v) {
210 | Snackbar.make(itemView, "Clicked on country " + mTextView.getText(),
211 | Snackbar.LENGTH_SHORT)
212 | .show();
213 | }
214 | }
215 | }
216 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/ContentFragment.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import com.tonicartos.superslim.adapter.Section;
4 |
5 | import android.database.Cursor;
6 | import android.net.Uri;
7 | import android.os.Bundle;
8 | import android.support.v4.app.Fragment;
9 | import android.support.v4.app.LoaderManager;
10 | import android.support.v4.content.CursorLoader;
11 | import android.support.v4.content.Loader;
12 | import android.support.v7.widget.RecyclerView;
13 | import android.view.LayoutInflater;
14 | import android.view.Menu;
15 | import android.view.MenuInflater;
16 | import android.view.MenuItem;
17 | import android.view.View;
18 | import android.view.ViewGroup;
19 |
20 | /**
21 | * A placeholder fragment containing a simple view.
22 | */
23 | public class ContentFragment extends Fragment {
24 |
25 | private static final int LOADER = 0;
26 |
27 | private static final Uri URI = Uri.parse("content://com.tonicartos.superslimdbexample.provider/all");
28 |
29 | Adapter mAdapter;
30 |
31 | private LoaderManager.LoaderCallbacks mLoaderCallbacks
32 | = new LoaderManager.LoaderCallbacks() {
33 |
34 | @Override
35 | public Loader onCreateLoader(int id, Bundle args) {
36 | return new CursorLoader(getActivity(), URI, null, null, null, null);
37 | }
38 |
39 | @Override
40 | public void onLoadFinished(Loader loader, Cursor data) {
41 | mAdapter.swapCursor(data);
42 | // recyclerView.scrollToPosition(1);
43 | }
44 |
45 | @Override
46 | public void onLoaderReset(Loader loader) {
47 | mAdapter.swapCursor(null);
48 | }
49 | };
50 |
51 | private Section removedSection = null;
52 |
53 | private RecyclerView recyclerView;
54 |
55 | public ContentFragment() {
56 | }
57 |
58 | @Override
59 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
60 | Bundle savedInstanceState) {
61 | return inflater.inflate(R.layout.fragment_main, container, false);
62 | }
63 |
64 | @Override
65 | public void onViewCreated(View view, Bundle savedInstanceState) {
66 | super.onViewCreated(view, savedInstanceState);
67 |
68 | recyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
69 | getLoaderManager().restartLoader(LOADER, null, mLoaderCallbacks);
70 |
71 | setHasOptionsMenu(true);
72 |
73 | mAdapter = new Adapter();
74 | recyclerView.setAdapter(mAdapter);
75 | }
76 |
77 | @Override
78 | public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
79 | menu.add("sadf");
80 | }
81 |
82 | @Override
83 | public boolean onOptionsItemSelected(MenuItem item) {
84 | // if (removedSection == null) {
85 | // removedSection = mAdapter.removeSection(1);
86 | // } else {
87 | // mAdapter.insertSection(1, removedSection);
88 | // removedSection = null;
89 | // }
90 | mAdapter.moveSection(0, 1);
91 | return false;
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/DataProvider.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import com.tonicartos.superslimdbexample.model.Country;
4 | import com.tonicartos.superslimdbexample.model.Region;
5 | import com.tonicartos.superslimdbexample.model.Subregion;
6 |
7 | import android.content.ContentProvider;
8 | import android.content.ContentValues;
9 | import android.content.UriMatcher;
10 | import android.database.Cursor;
11 | import android.database.sqlite.SQLiteDatabase;
12 | import android.database.sqlite.SQLiteQueryBuilder;
13 | import android.net.Uri;
14 | import android.text.TextUtils;
15 |
16 | /**
17 | *
18 | */
19 | public class DataProvider extends ContentProvider {
20 |
21 | private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
22 |
23 | private static final String AUTHORITY = "com.tonicartos.superslimdbexample.provider";
24 |
25 | private static final int BIT_SINGLE = 1;
26 |
27 | private static final int BIT_REGION = 2;
28 |
29 | private static final int BIT_SUB_REGION = 4;
30 |
31 | private static final int BIT_COUNTRY = 8;
32 |
33 | private static final int BIT_ALL = 16;
34 |
35 | private DatabaseHelper mDbHelper;
36 |
37 | @Override
38 | public boolean onCreate() {
39 | mDbHelper = new DatabaseHelper(getContext());
40 |
41 | return true;
42 | }
43 |
44 | @Override
45 | public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
46 | String sortOrder) {
47 | SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
48 |
49 | final int bits = sUriMatcher.match(uri);
50 | if ((bits & BIT_SINGLE) == BIT_SINGLE) {
51 | selection = addIdToSelection(selection);
52 | selectionArgs = addUriToSelectionArgs(uri, selectionArgs);
53 | } else {
54 | if (TextUtils.isEmpty(sortOrder)) {
55 | sortOrder = "_id ASC";
56 | }
57 | }
58 |
59 | final String table;
60 | switch (bits & ~BIT_SINGLE) {
61 | case BIT_COUNTRY:
62 | table = Country.TABLE_NAME;
63 | break;
64 | case BIT_REGION:
65 | table = Region.TABLE_NAME;
66 | break;
67 | case BIT_SUB_REGION:
68 | table = Subregion.TABLE_NAME;
69 | break;
70 | case BIT_ALL:
71 | table = Region.TABLE_NAME + " r LEFT OUTER JOIN " + Subregion.TABLE_NAME
72 | + " s ON r._id = s.region_id LEFT OUTER JOIN " + Country.TABLE_NAME
73 | + " c ON s._id = c.sub_region_id";
74 | break;
75 | default:
76 | throw new IllegalArgumentException("Unknown URI: " + uri);
77 | }
78 |
79 | SQLiteDatabase db = mDbHelper.getReadableDatabase();
80 | Cursor cursor = db.query(table, projection, selection, selectionArgs, null, null,
81 | sortOrder);
82 | cursor.setNotificationUri(getContext().getContentResolver(), uri);
83 |
84 | return cursor;
85 | }
86 |
87 | @Override
88 | public String getType(Uri uri) {
89 | final String firstPart;
90 | final int bits = sUriMatcher.match(uri);
91 | if ((bits & BIT_SINGLE) == BIT_SINGLE) {
92 | firstPart = "vnd.android.cursor.item/vnd.com.tonicartos.superslimdbexample.db.";
93 | } else {
94 | firstPart = "vnd.android.cursor.dir/vnd.com.tonicartos.superslimdbexample.db.";
95 | }
96 |
97 | final String secondPart;
98 | switch (bits & ~BIT_SINGLE) {
99 | case BIT_COUNTRY:
100 | secondPart = "country";
101 | break;
102 | case BIT_SUB_REGION:
103 | secondPart = "region";
104 | break;
105 | case BIT_REGION:
106 | secondPart = "sub_region";
107 | break;
108 | case BIT_ALL:
109 | secondPart = "all";
110 | break;
111 | default:
112 | throw new IllegalArgumentException("Unknown URI: " + uri);
113 | }
114 | return firstPart + secondPart;
115 | }
116 |
117 | @Override
118 | public Uri insert(Uri uri, ContentValues values) {
119 | SQLiteDatabase db = mDbHelper.getWritableDatabase();
120 | final int bits = sUriMatcher.match(uri);
121 |
122 | final String table;
123 | switch (bits & ~BIT_SINGLE) {
124 | case BIT_COUNTRY:
125 | table = Country.TABLE_NAME;
126 | break;
127 | case BIT_REGION:
128 | table = Region.TABLE_NAME;
129 | break;
130 | case BIT_SUB_REGION:
131 | table = Subregion.TABLE_NAME;
132 | break;
133 | default:
134 | throw new IllegalArgumentException("Unknown URI: " + uri);
135 | }
136 |
137 | final long id = db.insert(table, null, values);
138 |
139 | if (id == -1) {
140 | return null;
141 | }
142 |
143 | getContext().getContentResolver().notifyChange(uri, null);
144 | getContext().getContentResolver()
145 | .notifyChange(Uri.parse("content://com.tonicartos.superslimdbexample.provider/all"),
146 | null);
147 | return Uri.parse(uri.getPath() + "/" + id);
148 | }
149 |
150 | @Override
151 | public int delete(Uri uri, String selection, String[] selectionArgs) {
152 | final int rowsChanged;
153 |
154 | final int bits = sUriMatcher.match(uri);
155 | if ((bits & BIT_SINGLE) == BIT_SINGLE) {
156 | selection = addIdToSelection(selection);
157 | selectionArgs = addUriToSelectionArgs(uri, selectionArgs);
158 | }
159 |
160 | final String table;
161 | switch (bits & ~BIT_SINGLE) {
162 | case BIT_COUNTRY:
163 | table = Country.TABLE_NAME;
164 | break;
165 | case BIT_SUB_REGION:
166 | table = Subregion.TABLE_NAME;
167 | break;
168 | case BIT_REGION:
169 | table = Region.TABLE_NAME;
170 | break;
171 | default:
172 | throw new IllegalArgumentException("Unknown URI: " + uri);
173 | }
174 |
175 | SQLiteDatabase db = mDbHelper.getWritableDatabase();
176 | rowsChanged = db.delete(table, selection, selectionArgs);
177 |
178 | if (rowsChanged > 0) {
179 | getContext().getContentResolver().notifyChange(uri, null);
180 | getContext().getContentResolver().notifyChange(
181 | Uri.parse("content://com.tonicartos.superslimdbexample.provider/all"), null);
182 | }
183 | return rowsChanged;
184 | }
185 |
186 | @Override
187 | public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
188 | final int rowsChanged;
189 |
190 | final int bits = sUriMatcher.match(uri);
191 | if ((bits & BIT_SINGLE) == BIT_SINGLE) {
192 | selection = addIdToSelection(selection);
193 | selectionArgs = addUriToSelectionArgs(uri, selectionArgs);
194 | }
195 |
196 | final String table;
197 | switch (bits & ~BIT_SINGLE) {
198 | case BIT_COUNTRY:
199 | table = Country.TABLE_NAME;
200 | break;
201 | case BIT_SUB_REGION:
202 | table = Subregion.TABLE_NAME;
203 | break;
204 | case BIT_REGION:
205 | table = Region.TABLE_NAME;
206 | break;
207 | default:
208 | throw new IllegalArgumentException("Unknown URI: " + uri);
209 | }
210 |
211 | SQLiteDatabase db = mDbHelper.getWritableDatabase();
212 | rowsChanged = db.update(table, values, selection, selectionArgs);
213 |
214 | if (rowsChanged > 0) {
215 | getContext().getContentResolver().notifyChange(uri, null);
216 | getContext().getContentResolver().notifyChange(
217 | Uri.parse("content://com.tonicartos.superslimdbexample.provider/all"), null);
218 | }
219 |
220 | return rowsChanged;
221 | }
222 |
223 | private String addIdToSelection(String selection) {
224 | if (TextUtils.isEmpty(selection)) {
225 | selection = "_ID = ?";
226 | } else {
227 | selection = "_ID = ? AND " + selection;
228 | }
229 | return selection;
230 | }
231 |
232 | private String[] addUriToSelectionArgs(Uri uri, String[] selectionArgs) {
233 | if (selectionArgs != null && selectionArgs.length > 0) {
234 | final String[] newSelectionArgs = new String[selectionArgs.length + 1];
235 | System.arraycopy(selectionArgs, 0, newSelectionArgs, 1, selectionArgs.length);
236 | newSelectionArgs[0] = uri.getLastPathSegment();
237 | selectionArgs = newSelectionArgs;
238 | } else {
239 | selectionArgs = new String[]{uri.getLastPathSegment()};
240 | }
241 | return selectionArgs;
242 | }
243 |
244 | static {
245 | sUriMatcher.addURI(AUTHORITY, "all", BIT_ALL);
246 | sUriMatcher.addURI(AUTHORITY, Region.TABLE_NAME, BIT_REGION);
247 | sUriMatcher.addURI(AUTHORITY, Region.TABLE_NAME + "/#", BIT_REGION | BIT_SINGLE);
248 | sUriMatcher.addURI(AUTHORITY, Subregion.TABLE_NAME, BIT_SUB_REGION);
249 | sUriMatcher.addURI(AUTHORITY, Subregion.TABLE_NAME + "/#", BIT_SUB_REGION | BIT_SINGLE);
250 | sUriMatcher.addURI(AUTHORITY, Country.TABLE_NAME, BIT_COUNTRY);
251 | sUriMatcher.addURI(AUTHORITY, Country.TABLE_NAME + "/#", BIT_COUNTRY | BIT_SINGLE);
252 | }
253 | }
254 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/DatabaseHelper.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import com.tonicartos.superslimdbexample.model.Subregion;
4 | import com.tonicartos.superslimdbexample.model.Country;
5 | import com.tonicartos.superslimdbexample.model.Region;
6 |
7 | import android.content.Context;
8 | import android.database.sqlite.SQLiteDatabase;
9 | import android.database.sqlite.SQLiteOpenHelper;
10 |
11 | /**
12 | *
13 | */
14 | public class DatabaseHelper extends SQLiteOpenHelper {
15 |
16 | private static final String DATABASE_NAME = "countries";
17 |
18 | private static final int DATABASE_VERSION = 1;
19 |
20 | private final Context mContext;
21 |
22 | public DatabaseHelper(Context context) {
23 | super(context, DATABASE_NAME, null, DATABASE_VERSION);
24 | mContext = context;
25 | }
26 |
27 | @Override
28 | public void onCreate(SQLiteDatabase db) {
29 | db.execSQL(Country.CREATE_TABLE);
30 | db.execSQL(Region.CREATE_TABLE);
31 | db.execSQL(Subregion.CREATE_TABLE);
32 |
33 | if (!FirstRunHelper.isInitialised(mContext)) {
34 | FirstRunHelper.setIsInitialised(
35 | FirstRunHelper.performInitialisation(db, mContext), mContext);
36 | }
37 | }
38 |
39 | @Override
40 | public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/FirstRunHelper.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import com.squareup.moshi.JsonAdapter;
4 | import com.squareup.moshi.Moshi;
5 | import com.tonicartos.superslimdbexample.model.Country;
6 | import com.tonicartos.superslimdbexample.model.JsonData;
7 | import com.tonicartos.superslimdbexample.model.Region;
8 | import com.tonicartos.superslimdbexample.model.Subregion;
9 |
10 | import android.content.Context;
11 | import android.content.SharedPreferences;
12 | import android.database.sqlite.SQLiteDatabase;
13 | import android.util.Log;
14 |
15 | import java.io.IOException;
16 |
17 | import okio.Okio;
18 |
19 | /**
20 | * A helper to setup the application on the first run.
21 | */
22 | public class FirstRunHelper {
23 |
24 | private static final String TAG = "First Run Helper";
25 |
26 | private static final String PREFS = "cac";
27 |
28 | private static final String PREF_FIRST_RUN = "is_first_run";
29 |
30 | private static final String PREF_INITIALISED = "initialised";
31 |
32 | /**
33 | * Default mode for accessing shared preferences.
34 | */
35 | private static final int MODE = Context.MODE_PRIVATE;
36 |
37 | public static boolean isFirstRun(Context context) {
38 | SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
39 | return prefs.getBoolean(PREF_FIRST_RUN, true);
40 | }
41 |
42 | public static boolean isInitialised(Context context) {
43 | SharedPreferences prefs = context.getSharedPreferences(PREFS, Context.MODE_PRIVATE);
44 | return prefs.getBoolean(PREF_INITIALISED, false);
45 | }
46 |
47 | public static boolean performInitialisation(SQLiteDatabase db, Context context) {
48 | Moshi moshi = new Moshi.Builder().build();
49 | JsonAdapter jsonAdapter = moshi.adapter(JsonData.class);
50 | JsonData jsonData;
51 | try {
52 | jsonData = jsonAdapter.fromJson(Okio.buffer(
53 | Okio.source(context.getResources().openRawResource(R.raw.init_db_data))));
54 | } catch (IOException e) {
55 | String msg = "Error loading initial database data file.";
56 | Log.e(TAG, msg);
57 | e.printStackTrace();
58 | return false;
59 | }
60 |
61 | db.beginTransaction();
62 | try {
63 | for (JsonData.Region r : jsonData.regions) {
64 | Region region = Region.from(r);
65 | region.createRecord(db);
66 |
67 | for (JsonData.SubRegion s : r.sub_regions) {
68 | Subregion subregion = Subregion.from(s);
69 | subregion.setRegion(region);
70 | subregion.createRecord(db);
71 |
72 | for (JsonData.Country c : s.countries) {
73 | Country country = Country.from(c);
74 | country.setSubRegion(subregion);
75 | country.createRecord(db);
76 | }
77 | }
78 | }
79 |
80 | setIsInitialised(true, context);
81 | db.setTransactionSuccessful();
82 | } finally {
83 | db.endTransaction();
84 | }
85 |
86 | return true;
87 | }
88 |
89 | public static void setFirstRunComplete(boolean value, Context context) {
90 | SharedPreferences.Editor prefsEditor = context.getSharedPreferences(PREFS, MODE).edit();
91 | prefsEditor.putBoolean(PREF_FIRST_RUN, false);
92 | prefsEditor.commit();
93 | }
94 |
95 | public static void setIsInitialised(boolean value, Context context) {
96 | SharedPreferences.Editor prefsEditor = context.getSharedPreferences(PREFS, MODE).edit();
97 | prefsEditor.putBoolean(PREF_INITIALISED, false);
98 | prefsEditor.commit();
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/LinearInterceptManager.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.LinearLayoutManager;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.util.AttributeSet;
7 | import android.util.Log;
8 |
9 | /**
10 | *
11 | */
12 | public class LinearInterceptManager extends LinearLayoutManager {
13 |
14 | public LinearInterceptManager(Context context) {
15 | super(context);
16 | }
17 |
18 | public LinearInterceptManager(Context context, int orientation, boolean reverseLayout) {
19 | super(context, orientation, reverseLayout);
20 | }
21 |
22 | public LinearInterceptManager(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
23 | super(context, attrs, defStyleAttr, defStyleRes);
24 | }
25 |
26 | @Override
27 | public void onItemsChanged(RecyclerView recyclerView) {
28 | super.onItemsChanged(recyclerView);
29 | }
30 |
31 | @Override
32 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
33 | // if (state.isPreLayout()) {
34 | // Log.d("asdf", "Pre-layout children");
35 | // } else {
36 | // Log.d("asdf", "Post-layout children");
37 | // }
38 | super.onLayoutChildren(recycler, state);
39 | }
40 |
41 | @Override
42 | public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
43 | // Log.d("asdf", "Items added - positionStart: " + positionStart + " itemCount: " + itemCount);
44 | super.onItemsAdded(recyclerView, positionStart, itemCount);
45 | }
46 |
47 | @Override
48 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
49 | // Log.d("asdf", "Items removed - positionStart: " + positionStart + " itemCount: " + itemCount);
50 | super.onItemsRemoved(recyclerView, positionStart, itemCount);
51 | }
52 |
53 | @Override
54 | public void onItemsUpdated(RecyclerView recyclerView, int positionStart, int itemCount) {
55 | // Log.d("asdf", "Items updated - positionStart: " + positionStart + " itemCount: " + itemCount);
56 | super.onItemsUpdated(recyclerView, positionStart, itemCount);
57 | }
58 |
59 | @Override
60 | public void onItemsMoved(RecyclerView recyclerView, int from, int to, int itemCount) {
61 | // Log.d("asdf", "Items moved - from: " + from + " to:" + to + " itemCount: " + itemCount);
62 | super.onItemsMoved(recyclerView, from, to, itemCount);
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample;
2 |
3 | import android.os.Bundle;
4 | import android.support.design.widget.AppBarLayout;
5 | import android.support.design.widget.CoordinatorLayout;
6 | import android.support.v7.app.AppCompatActivity;
7 | import android.support.v7.widget.Toolbar;
8 | import android.view.Menu;
9 | import android.view.MenuItem;
10 | import android.view.MotionEvent;
11 |
12 |
13 | public class MainActivity extends AppCompatActivity {
14 |
15 | @Override
16 | public boolean onCreateOptionsMenu(Menu menu) {
17 | // Inflate the menu; this adds items to the action bar if it is present.
18 | getMenuInflater().inflate(R.menu.menu_main, menu);
19 | return true;
20 | }
21 |
22 | @Override
23 | public boolean onOptionsItemSelected(MenuItem item) {
24 | // Handle action bar item clicks here. The action bar will
25 | // automatically handle clicks on the Home/Up button, so long
26 | // as you specify a parent activity in AndroidManifest.xml.
27 | int id = item.getItemId();
28 |
29 | //noinspection SimplifiableIfStatement
30 | if (id == R.id.action_settings) {
31 | return true;
32 | }
33 |
34 | return super.onOptionsItemSelected(item);
35 | }
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | setContentView(R.layout.activity_main);
41 | setSupportActionBar((Toolbar) findViewById(R.id.toolbar));
42 |
43 | // FIXME: Remove hack around bug in coordinator layout 23.0.0 when fix is released.
44 | ((CoordinatorLayout.LayoutParams) findViewById(R.id.appbar).getLayoutParams())
45 | .setBehavior(new AppBarLayoutBehavior());
46 | }
47 |
48 | public class AppBarLayoutBehavior extends AppBarLayout.Behavior {
49 |
50 | @Override
51 | public boolean onInterceptTouchEvent(CoordinatorLayout parent, AppBarLayout child,
52 | MotionEvent ev) {
53 | return !(parent != null && child != null && ev != null) || super
54 | .onInterceptTouchEvent(parent, child, ev);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/model/Country.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample.model;
2 |
3 | import android.content.ContentValues;
4 |
5 | /**
6 | * Country database model.
7 | */
8 | public class Country extends DbModel {
9 |
10 | private static final String KEY_SUB_REGION_ID = "sub_region_id";
11 |
12 | private static final String KEY_NAME = "name";
13 |
14 | public static final String TABLE_NAME = "country";
15 |
16 | public static final java.lang.String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
17 | KEY_ID + " integer primary key, " +
18 | KEY_SUB_REGION_ID + " integer, " +
19 | KEY_NAME + " text " +
20 | ");";
21 |
22 | protected long mSubRegionId;
23 |
24 | protected String mName;
25 |
26 | public static Country from(JsonData.Country source) {
27 | Country c = new Country();
28 | c.mName = source.name;
29 | return c;
30 | }
31 |
32 | public static Country from(ContentValues values) {
33 | Country c = new Country();
34 |
35 | if (values.containsKey(KEY_ID)) {
36 | c.mId = values.getAsLong(KEY_ID);
37 | }
38 |
39 | if (values.containsKey(KEY_NAME)) {
40 | c.mName = values.getAsString(KEY_NAME);
41 | }
42 |
43 | if (values.containsKey(KEY_SUB_REGION_ID)) {
44 | c.mSubRegionId = values.getAsLong(KEY_SUB_REGION_ID);
45 | }
46 |
47 | return c;
48 | }
49 |
50 | public String getName() {
51 | return mName;
52 | }
53 |
54 | public void setName(String name) {
55 | mName = name;
56 | markAsDirty();
57 | }
58 |
59 | public long getSubRegionId() {
60 | return mSubRegionId;
61 | }
62 |
63 | public void setSubRegionId(long subRegionId) {
64 | mSubRegionId = subRegionId;
65 | markAsDirty();
66 | }
67 |
68 | @Override
69 | public ContentValues getValues() {
70 | ContentValues values = new ContentValues();
71 | values.put(KEY_SUB_REGION_ID, mSubRegionId);
72 | values.put(KEY_NAME, mName);
73 | return values;
74 | }
75 |
76 | @Override
77 | protected String getTableName() {
78 | return TABLE_NAME;
79 | }
80 |
81 | public void setSubRegion(Subregion subregion) {
82 | mSubRegionId = subregion.getId();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/model/DbModel.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample.model;
2 |
3 | import android.content.ContentValues;
4 | import android.database.sqlite.SQLiteDatabase;
5 |
6 | /**
7 | *
8 | */
9 | public abstract class DbModel {
10 |
11 | protected static final String KEY_ID = "_id";
12 |
13 | protected long mId = -1;
14 |
15 | /**
16 | * Flag to tell if the object matches the database version.
17 | */
18 | private boolean mIsDirty = true;
19 |
20 | public void createRecord(SQLiteDatabase db) {
21 | mId = db.insert(getTableName(), null, getValues());
22 | }
23 |
24 | public void deleteRecord(SQLiteDatabase db) {
25 | db.delete(getTableName(), "WHERE _id = " + mId, null);
26 | }
27 |
28 | public long getId() {
29 | return mId;
30 | }
31 |
32 | public abstract ContentValues getValues();
33 |
34 | public boolean isDirty() {
35 | return mIsDirty;
36 | }
37 |
38 | public void markAsClean() {
39 | mIsDirty = false;
40 | }
41 |
42 | public void markAsDirty() {
43 | mIsDirty = true;
44 | }
45 |
46 | public void updateRecord(SQLiteDatabase db) {
47 | if (mIsDirty) {
48 | db.update(getTableName(), getValues(), "WHERE _id = " + mId, null);
49 | }
50 | }
51 |
52 | protected abstract String getTableName();
53 | }
54 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/model/JsonData.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample.model;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * Quick model for json data to initialise the database with.
7 | */
8 | public class JsonData {
9 |
10 | public List regions;
11 |
12 | public static class Region {
13 |
14 | public String name;
15 |
16 | public List sub_regions;
17 | }
18 |
19 | public static class SubRegion {
20 |
21 | public String name;
22 |
23 | public List countries;
24 | }
25 |
26 | public static class Country {
27 |
28 | public String name;
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/model/Region.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample.model;
2 |
3 | import android.content.ContentValues;
4 |
5 | /**
6 | * Region db model
7 | */
8 | public class Region extends DbModel {
9 |
10 | private static final String KEY_NAME = "name";
11 |
12 | public static final String TABLE_NAME = "region";
13 |
14 | public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "( " +
15 | KEY_ID + " integer primary key, " +
16 | KEY_NAME + " text " +
17 | ");";
18 |
19 | protected String mName;
20 |
21 | public static Region from(JsonData.Region source) {
22 | Region r = new Region();
23 | r.mName = source.name;
24 | return r;
25 | }
26 |
27 | public static Region from(ContentValues values) {
28 | Region r = new Region();
29 | r.mId = values.getAsLong(KEY_ID);
30 | r.mName = values.getAsString(KEY_NAME);
31 | return r;
32 | }
33 |
34 | public String getName() {
35 | return mName;
36 | }
37 |
38 | public void setName(String name) {
39 | mName = name;
40 | markAsDirty();
41 | }
42 |
43 | @Override
44 | public ContentValues getValues() {
45 | ContentValues values = new ContentValues();
46 | values.put(KEY_NAME, mName);
47 | return values;
48 | }
49 |
50 | @Override
51 | protected String getTableName() {
52 | return TABLE_NAME;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/dbexample/src/main/java/com/tonicartos/superslimdbexample/model/Subregion.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslimdbexample.model;
2 |
3 | import android.content.ContentValues;
4 |
5 | /**
6 | *
7 | */
8 | public class Subregion extends DbModel {
9 |
10 | private static final String KEY_ID = "_id";
11 |
12 | private static final String KEY_NAME = "name";
13 |
14 | private static final String KEY_REGION_ID = "region_id";
15 |
16 | public static final String TABLE_NAME = "subregion";
17 |
18 | public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "( " +
19 | KEY_ID + " integer primary key, " +
20 | KEY_REGION_ID + " integer, " +
21 | KEY_NAME + " text " +
22 | ");";
23 |
24 |
25 | protected String mName;
26 |
27 | private long mRegionId;
28 |
29 | public static Subregion from(JsonData.SubRegion source) {
30 | Subregion s = new Subregion();
31 | s.mName = source.name;
32 | return s;
33 | }
34 |
35 | public static Subregion from(ContentValues values) {
36 | Subregion s = new Subregion();
37 | if (values.containsKey(KEY_ID)) {
38 | s.mId = values.getAsLong(KEY_ID);
39 | }
40 |
41 | if (values.containsKey(KEY_NAME)) {
42 | s.mName = values.getAsString(KEY_NAME);
43 | }
44 |
45 | if (values.containsKey(KEY_REGION_ID)) {
46 | s.mRegionId = values.getAsLong(KEY_REGION_ID);
47 | }
48 | return s;
49 | }
50 |
51 | public String getName() {
52 | return mName;
53 | }
54 |
55 | public void setName(String name) {
56 | mName = name;
57 | markAsDirty();
58 | }
59 |
60 | public long getRegionId() {
61 | return mRegionId;
62 | }
63 |
64 | public void setRegionId(long regionId) {
65 | mRegionId = regionId;
66 | }
67 |
68 | @Override
69 | public ContentValues getValues() {
70 | ContentValues values = new ContentValues();
71 | values.put(KEY_REGION_ID, mRegionId);
72 | values.put(KEY_NAME, mName);
73 | return values;
74 | }
75 |
76 | @Override
77 | protected String getTableName() {
78 | return TABLE_NAME;
79 | }
80 |
81 | public void setRegion(Region region) {
82 | mRegionId = region.getId();
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout-v17/country_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout-v17/region_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout-v17/subregion_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
10 |
11 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout/country_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
20 |
21 |
26 |
27 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout/region_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/layout/subregion_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/menu/menu_main.xml:
--------------------------------------------------------------------------------
1 |
7 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/dbexample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/dbexample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/dbexample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/dbexample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/dbexample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/dbexample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/dbexample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/dbexample/src/main/res/raw/init_db_data.json:
--------------------------------------------------------------------------------
1 | {
2 | "regions": [
3 | {
4 | "name": "Africa",
5 | "sub_regions": [
6 | {
7 | "name": "Eastern Africa",
8 | "countries": [
9 | {"name": "Burundi"},
10 | {"name": "Comoros"},
11 | {"name": "Djibouti"},
12 | {"name": "Eritrea"},
13 | {"name": "Ethiopia"},
14 | {"name": "Kenya"},
15 | {"name": "Madagascar"},
16 | {"name": "Malawi"},
17 | {"name": "Mauritius"},
18 | {"name": "Mayotte"},
19 | {"name": "Mozambique"},
20 | {"name": "Réunion"},
21 | {"name": "Rwanda"},
22 | {"name": "Seychelles"},
23 | {"name": "Somalia"},
24 | {"name": "South Sudan"},
25 | {"name": "Uganda"},
26 | {"name": "United Republic of Tanzania"},
27 | {"name": "Zambia"},
28 | {"name": "Zimbabwe"}
29 | ]
30 | },
31 | {
32 | "name": "Middle Africa",
33 | "countries": [
34 | {"name": "Angola"},
35 | {"name": "Cameroon"},
36 | {"name": "Central African Republic"},
37 | {"name": "Chad"},
38 | {"name": "Congo"},
39 | {"name": "Democratic Republic of the Congo"},
40 | {"name": "Equatorial Guinea"},
41 | {"name": "Gabon"},
42 | {"name": "Sao Tome and Principe"}
43 | ]
44 | },
45 | {
46 | "name": "Northern Africa",
47 | "countries": [
48 | {"name": "Algeria"},
49 | {"name": "Egypt"},
50 | {"name": "Libya"},
51 | {"name": "Morocco"},
52 | {"name": "Sudan"},
53 | {"name": "Tunisia"},
54 | {"name": "Western Sahara"}
55 | ]
56 | },
57 | {
58 | "name": "Southern Africa",
59 | "countries": [
60 | {"name": "Botswana"},
61 | {"name": "Lesotho"},
62 | {"name": "Namibia"},
63 | {"name": "South Africa"},
64 | {"name": "Swaziland"}
65 | ]
66 | },
67 | {
68 | "name": "Western Africa",
69 | "countries": [
70 | {"name": "Benin"},
71 | {"name": "Burkina Faso"},
72 | {"name": "Cabo Verde"},
73 | {"name": "Cote d'Ivoire"},
74 | {"name": "Gambia"},
75 | {"name": "Ghana"},
76 | {"name": "Guinea"},
77 | {"name": "Guinea-Bissau"},
78 | {"name": "Liberia"},
79 | {"name": "Mali"},
80 | {"name": "Mauritania"},
81 | {"name": "Niger"},
82 | {"name": "Nigeria"},
83 | {"name": "Saint Helena"},
84 | {"name": "Senegal"},
85 | {"name": "Sierra Leone"},
86 | {"name": "Togo "}
87 | ]
88 | }
89 | ]
90 | },
91 | {
92 | "name": "Americas",
93 | "sub_regions": [
94 | {
95 | "name": "Latin America and the Caribbean",
96 | "countries": [
97 | {"name": "Caribbean"},
98 | {"name": "Anguilla"},
99 | {"name": "Antigua and Barbuda"},
100 | {"name": "Aruba"},
101 | {"name": "Bahamas"},
102 | {"name": "Barbados"},
103 | {"name": "Bonaire, Sint Eustatius and Saba"},
104 | {"name": "British Virgin Islands"},
105 | {"name": "Cayman Islands"},
106 | {"name": "Cuba"},
107 | {"name": "Curaçao"},
108 | {"name": "Dominica"},
109 | {"name": "Dominican Republic"},
110 | {"name": "Grenada"},
111 | {"name": "Guadeloupe"},
112 | {"name": "Haiti"},
113 | {"name": "Jamaica"},
114 | {"name": "Martinique"},
115 | {"name": "Montserrat"},
116 | {"name": "Puerto Rico"},
117 | {"name": "Saint-Barthélemy"},
118 | {"name": "Saint Kitts and Nevis"},
119 | {"name": "Saint Lucia"},
120 | {"name": "Saint Martin (French part)"},
121 | {"name": "Saint Vincent and the Grenadines"},
122 | {"name": "Sint Maarten (Dutch part)"},
123 | {"name": "Trinidad and Tobago"},
124 | {"name": "Turks and Caicos Islands"},
125 | {"name": "United States Virgin Islands"}
126 | ]
127 | },
128 | {
129 | "name": "Central America",
130 | "countries": [
131 | {"name": "Belize"},
132 | {"name": "Costa Rica"},
133 | {"name": "El Salvador"},
134 | {"name": "Guatemala"},
135 | {"name": "Honduras"},
136 | {"name": "Mexico"},
137 | {"name": "Nicaragua"},
138 | {"name": "Panama"}
139 | ]
140 | },
141 | {
142 | "name": "South America",
143 | "countries": [
144 | {"name": "Argentina"},
145 | {"name": "Bolivia (Plurinational State of)"},
146 | {"name": "Brazil"},
147 | {"name": "Chile"},
148 | {"name": "Colombia"},
149 | {"name": "Ecuador"},
150 | {"name": "Falkland Islands (Malvinas)"},
151 | {"name": "French Guiana"},
152 | {"name": "Guyana"},
153 | {"name": "Paraguay"},
154 | {"name": "Peru"},
155 | {"name": "Suriname"},
156 | {"name": "Uruguay"},
157 | {"name": "Venezuela (Bolivarian Republic of)"}
158 | ]
159 | },
160 | {
161 | "name": "Northern America",
162 | "countries": [
163 | {"name": "Bermuda"},
164 | {"name": "Canada"},
165 | {"name": "Greenland"},
166 | {"name": "Saint Pierre and Miquelon"},
167 | {"name": "United States of America"}
168 | ]
169 | }
170 | ]
171 | },
172 | {
173 | "name": "Asia",
174 | "sub_regions": [
175 | {
176 | "name": "Central Asia",
177 | "countries": [
178 | {"name": "Kazakhstan"},
179 | {"name": "Kyrgyzstan"},
180 | {"name": "Tajikistan"},
181 | {"name": "Turkmenistan"},
182 | {"name": "Uzbekistan"}
183 | ]
184 | },
185 | {
186 | "name": "Eastern Asia",
187 | "countries": [
188 | {"name": "China"},
189 | {"name": "China, Hong Kong Special Administrative Region"},
190 | {"name": "China, Macao Special Administrative Region"},
191 | {"name": "Democratic People's Republic of Korea"},
192 | {"name": "Japan"},
193 | {"name": "Mongolia"},
194 | {"name": "Republic of Korea"}
195 | ]
196 | },
197 | {
198 | "name": "Southern Asia",
199 | "countries": [
200 | {"name": "Afghanistan"},
201 | {"name": "Bangladesh"},
202 | {"name": "Bhutan"},
203 | {"name": "India"},
204 | {"name": "Iran (Islamic Republic of)"},
205 | {"name": "Maldives"},
206 | {"name": "Nepal"},
207 | {"name": "Pakistan"},
208 | {"name": "Sri Lanka"}
209 | ]
210 | },
211 | {
212 | "name": "South-Eastern Asia",
213 | "countries": [
214 | {"name": "Brunei Darussalam"},
215 | {"name": "Cambodia"},
216 | {"name": "Indonesia"},
217 | {"name": "Lao People's Democratic Republic"},
218 | {"name": "Malaysia"},
219 | {"name": "Myanmar"},
220 | {"name": "Philippines"},
221 | {"name": "Singapore"},
222 | {"name": "Thailand"},
223 | {"name": "Timor-Leste"},
224 | {"name": "Viet Nam"}
225 | ]
226 | },
227 | {
228 | "name": "Western Asia",
229 | "countries": [
230 | {"name": "Armenia"},
231 | {"name": "Azerbaijan"},
232 | {"name": "Bahrain"},
233 | {"name": "Cyprus"},
234 | {"name": "Georgia"},
235 | {"name": "Iraq"},
236 | {"name": "Israel"},
237 | {"name": "Jordan"},
238 | {"name": "Kuwait"},
239 | {"name": "Lebanon"},
240 | {"name": "Oman"},
241 | {"name": "Qatar"},
242 | {"name": "Saudi Arabia"},
243 | {"name": "State of Palestine"},
244 | {"name": "Syrian Arab Republic"},
245 | {"name": "Turkey"},
246 | {"name": "United Arab Emirates"},
247 | {"name": "Yemen "}
248 | ]
249 | }
250 | ]
251 | },
252 | {
253 | "name": "Europe",
254 | "sub_regions": [
255 | {
256 | "name": "Eastern Europe",
257 | "countries": [
258 | {"name": "Belarus"},
259 | {"name": "Bulgaria"},
260 | {"name": "Czech Republic"},
261 | {"name": "Hungary"},
262 | {"name": "Poland"},
263 | {"name": "Republic of Moldova"},
264 | {"name": "Romania"},
265 | {"name": "Russian Federation"},
266 | {"name": "Slovakia"},
267 | {"name": "Ukraine"}
268 | ]
269 | },
270 | {
271 | "name": "Northern Europe",
272 | "countries": [
273 | {"name": "Åland Islands"},
274 | {"name": "Channel Islands"},
275 | {"name": "Denmark"},
276 | {"name": "Estonia"},
277 | {"name": "Faeroe Islands"},
278 | {"name": "Finland"},
279 | {"name": "Guernsey"},
280 | {"name": "Iceland"},
281 | {"name": "Ireland"},
282 | {"name": "Isle of Man"},
283 | {"name": "Jersey"},
284 | {"name": "Latvia"},
285 | {"name": "Lithuania"},
286 | {"name": "Norway"},
287 | {"name": "Sark"},
288 | {"name": "Svalbard and Jan Mayen Islands"},
289 | {"name": "Sweden"},
290 | {"name": "United Kingdom of Great Britain and Northern Ireland"}
291 | ]
292 | },
293 | {
294 | "name": "Southern Europe",
295 | "countries": [
296 | {"name": "Albania"},
297 | {"name": "Andorra"},
298 | {"name": "Bosnia and Herzegovina"},
299 | {"name": "Croatia"},
300 | {"name": "Gibraltar"},
301 | {"name": "Greece"},
302 | {"name": "Holy See"},
303 | {"name": "Italy"},
304 | {"name": "Malta"},
305 | {"name": "Montenegro"},
306 | {"name": "Portugal"},
307 | {"name": "San Marino"},
308 | {"name": "Serbia"},
309 | {"name": "Slovenia"},
310 | {"name": "Spain"},
311 | {"name": "The former Yugoslav Republic of Macedonia"}
312 | ]
313 | },
314 | {
315 | "name": "Western Europe",
316 | "countries": [
317 | {"name": "Austria"},
318 | {"name": "Belgium"},
319 | {"name": "France"},
320 | {"name": "Germany"},
321 | {"name": "Liechtenstein"},
322 | {"name": "Luxembourg"},
323 | {"name": "Monaco"},
324 | {"name": "Netherlands"},
325 | {"name": "Switzerland"}
326 | ]
327 | }
328 | ]
329 | },
330 | {
331 | "name": "Oceania",
332 | "sub_regions": [
333 | {
334 | "name": "Australia and New Zealand",
335 | "countries": [
336 | {"name": "Australia"},
337 | {"name": "New Zealand"},
338 | {"name": "Norfolk Island"}
339 | ]
340 | },
341 | {
342 | "name": "Melanesia",
343 | "countries": [
344 | {"name": "Fiji"},
345 | {"name": "New Caledonia"},
346 | {"name": "Papua New Guinea"},
347 | {"name": "Solomon Islands"},
348 | {"name": "Vanuatu"}
349 | ]
350 | },
351 | {
352 | "name": "Micronesia",
353 | "countries": [
354 | {"name": "Guam"},
355 | {"name": "Kiribati"},
356 | {"name": "Marshall Islands"},
357 | {"name": "Micronesia (Federated States of)"},
358 | {"name": "Nauru"},
359 | {"name": "Northern Mariana Islands"},
360 | {"name": "Palau"}
361 | ]
362 | },
363 | {
364 | "name": "Polynesia",
365 | "countries": [
366 | {"name": "American Samoa"},
367 | {"name": "Cook Islands"},
368 | {"name": "French Polynesia"},
369 | {"name": "Niue"},
370 | {"name": "Pitcairn"},
371 | {"name": "Samoa"},
372 | {"name": "Tokelau"},
373 | {"name": "Tonga"},
374 | {"name": "Tuvalu"},
375 | {"name": "Wallis and Futuna Islands"}
376 | ]
377 | }
378 | ]
379 | }
380 | ]
381 | }
--------------------------------------------------------------------------------
/dbexample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #8BC34A
4 | #689F38
5 | #DCEDC8
6 | #FFC107
7 | #212121
8 | #727272
9 | #212121
10 | #B6B6B6
11 |
12 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SuperSLiM DB Example
3 | Hello world!
4 | Settings
5 |
6 |
--------------------------------------------------------------------------------
/dbexample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
13 |
14 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/doc/section.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/doc/section.png
--------------------------------------------------------------------------------
/doc/src/LayoutManager.md:
--------------------------------------------------------------------------------
1 | # LayoutManager
2 | [TOC]
3 | #Concepts
4 | ## General
5 | Change animation
6 | : The recycler view's animation for changes in the data set. The initial conditions are set by the pre-layout pass, and the final state of each view change animation is given by the real-layout pass.
7 |
8 | Pre-layout
9 | : `RecyclerView.State.isPreLayout() == true`
10 | : The role of the pre-layout pass is to set up the initial conditions of the change animation. The pre-layout pass lays-out regular on-screen views, on-screen disappearing views, and off-screen appearing views.
11 |
12 | Real-layout
13 | : `RecyclerView.State.isPreLayout() == false`
14 | : The second layout pass which gives the final position of views after any animations have run. The real-layout pass lays-out regular on-screen views, on-screen appearing views, and off-screen disappearing views.
15 |
16 | Added views
17 | : Added views are available for laying out in the real-layout pass.
18 |
19 | Removed views
20 | : Removed views are returned from the scrap list in the pre-layout pass to help with correctly laying out the initial position of views affected by the change animation, especially appearing views. Removed views should not be added to the child list, they are only a positional aid.
21 |
22 | Disappearing view
23 | : A view that is on-screen before being animated off-screen.
24 | : A disappearing view is laid out on-screen in the pre-layout pass, and is laid out off-screen in the real-layout.
25 | : In the post-layout pass the recycler view must be told the view is disappearing by calling `addDisappearingView()` on the layout manager.
26 | : Disappearing views are a result of either; the view being moved, or views being added before it, thus pushing it off-screen.
27 |
28 | Appearing view
29 | : A view that is off-screen before being animated onto the screen.
30 | : An appearing view is laid out off-screen in the pre-layout pass, and is laid out on-screen in the real-layout.
31 | : An appearing view can be discovered by detecting views that will be removed after pre-layout, laying them out, but not accounting for the removed views in determining the visual limit of the layout pass; i.e., for a linear layout removed views count for zero height when being laid out, this is to the effect that 100 pixels of removed views results in at least 100 pixels of space being available for appearing views.
32 |
33 | Scrap views
34 | : Views attached to the recycler view which are marked for removal or reuse.
35 | : When layout starts SuperSLiM removes and scraps all child views of the recycler view. When SLMs get views they may be reused from the scrap list or populated from the adapter. Scrap views are automatically removed from the scrap list when they are added back to the recycler view.
36 | : Scrap views remaining after the regular post-layout pass are views that were on-screen in the pre-layout pass. These scrap views are now either removed and should be ignored, or are disappearing views and are to be laid out off-screen.
37 |
38 | Item decorator
39 | : Item decorators can inset child views and add extra content without complicating layout passes. Handling decorators correctly requires using helper methods with the child view as an argument rather than interacting with the child view directly.
40 |
41 | Item changes
42 | : Changes to the data set are notified to the recycler view through a listener interface. The changes are propagated through to the layout manager by calls to `onItemsAdded()`, `onItemsUpdated()`, `onItemsChanged()`, `onItemsMoved()`, and `onItemsRemoved()`. The affected position and ranges are passed in these calls. If the change is outside of the currently laid out area the calls are made before the pre-layout pass.
43 |
44 | Scroll event
45 | : A call to one to the scroll methods `scrollVerticallyBy()` or `scrollHorizontallyBy()`. Scroll distance is given in pixels and the result indicates overscroll.
46 |
47 | Anchor position
48 | : Logically, the current position that the user has scrolled to. For touch scrolling, this is the adapter position of the view that is logically and visually closest to the reference edge. Alternatively, for focus based scrolling, which is assistive or keyboard driven, this is the position of the focused view.
49 | : The anchor position is updated after each scroll or focus event, or is directly modified by calls to *scroll to* or *smooth scroll to* a position.
50 | : The current position is retained through configuration changes and used to re-layout the content with that position as an anchor.
51 |
52 | Anchor position offset
53 | : The distance from the reference edge that the view indicated by the anchor position is located. Use of this value allows for the logical recreation of the scrolled view after configuration changes.
54 | : This offset is updated each time the anchor position is updated.
55 |
56 | Over scroll
57 | : When a layout pass displays less content than should have been. The visual effect is where the scrolled content is displayed with its bottom edge not aligned with the bottom padding edge or view edge, but further up the view instead. This is caused when items from the anchor position to the last item are laid out, but there is still space below the content and there are items before the anchor position. The solution is to detect this situation after a layout pass, adjust all the laid out views down to the correct position, and then fill in the revealed space.
58 |
59 | ## SuperSLiM
60 |
61 | Scroll layout
62 | : Scrolling in SuperSLiM is handled by moving all content on the screen and then laying out revealed content on the leading edge as a special scroll layout pass.
63 |
64 | Scroll pre-trim
65 | : Before the scroll trim is performed, a scroll pre-trim pass is made on sections intersecting the trailing edge. Any special positioning of views after the scroll event can be made here. The base `SectionLayoutManager` implementation uses this to correctly reposition stickied headers on the up edge.
66 |
67 | Scroll trim
68 | : After a scroll layout, any content wholly outside the trailing edge is trimmed.
69 |
70 | Trim notification
71 | : When a view is trimmed due to a scroll, a notification is passed down the section hierarchy so that section layout managers can update any cached data about a section.
72 |
73 | Section coordinate space
74 | : Each section has its own virtual space. Values in this space are transformed up the section graph to the root coordinate space used by the layout manager and the recycler view.
75 |
76 | Config transformation
77 | : The layout manager is configured by the locale and user set values. A config transformation is applied to coordinates coming into and out of the recycler view. In this manner, it is easy to support different layout directions without custom logic in each section layout manager implementation.
78 |
79 | Start edge
80 | : The '*left edge*' of the layout managed area. For a RTL locale, this is actually the right edge.
81 |
82 | End edge
83 | : The '*right edge*' of the layout managed area. For a RTL locale, this is actually the left edge.
84 |
85 | Reverse layout
86 | : Layout is typically performed from top left to bottom right, translated for locale differences. When reversed, the layout is performed from the bottom right to the top right.
87 |
88 | Orientation
89 | : The orientation can be horizontal or vertical.
90 |
91 | Stack from end
92 | : Views are laid out from the bottom to the top. The bottom edge is the reference edge for determining current position. Headers still appear at the top of the section.
93 |
94 | Headers at end
95 | : Headers are positioned after the section content. This makes for a reversal of the sticky logic. Instead of sticking to the logical top of the section, they will stick to the logical bottom of the section.
96 | : Handled inside `BaseSectionLayoutManager`.
97 |
98 | Configuration transformer
99 | : A function that transforms one coordinate system into another; i.e., turning the LTR, TTB coordinate system used by section layout managers, into the coordinate system used by a RTL recycler view - a configuration derived from the users locale.
100 |
101 | Semi-real coordinate
102 | : A coordinate that has been transformed into a virtual coordinate by a configuration transformation, but is also not nested within another virtual coordinate space. In this manner the top edge of this space can be called the semi-real top, though it could actually be the left edge of a horizontally scrolling recycler view.
103 |
104 | # Layout transformation
105 | Section layout managers work in their own virtual coordinate space that is transformed into the window coordinates. As such, section layout managers behave as though they are always operating in a vertical scrolling, top-to-bottom, left-to-right coordinate space. The width of the section space is defined by the parent section, and may have an invisible offset as defined by the parent.
106 |
107 | **Writes**
108 | ```sequence
109 | section->helper: layoutChild(view, values)
110 | helper->helper: apply inverse subspace transformation
111 | helper->helper parent: layoutChild(view, values)
112 | helper parent->helper parent: apply inverse subspace transformation
113 | Note over helper parent: recurse layoutChild up graph ancestry
114 | helper parent->layout manager: layoutChild(view, values)
115 | layout manager->layout manager: apply inverse configuration transformation
116 | layout manager->recycler view: layoutChild(view, values)
117 | ```
118 |
119 | **Reads**
120 | ```sequence
121 | section->helper: request
122 | helper->helper parent: request
123 | Note over helper parent: recurse request up graph ancestry
124 | helper parent->layout manager: request
125 | layout manager->recycler view: request
126 | recycler view-->layout manager: result
127 | layout manager->layout manager: apply configuration transformation
128 | layout manager-->helper parent: result
129 | helper parent->helper parent: apply subspace transformation
130 | helper parent-->helper: result
131 | helper->helper: apply subspace transformation
132 | helper-->section: result
133 | ```
134 |
135 | The above diagrams give the logical effect of the behaviour of the helper. However, transformations can be combined together and be expressed
136 |
137 | **Writes**
138 | ```sequence
139 | section->helper: write values
140 | helper->helper: apply inverse subspace transformation
141 | helper->layout manager: write values in layout managers coords
142 | layout manager->layout manager: apply inverse configuration transformation
143 | layout manager->recycler view: write values in recycler views coords
144 | ```
145 |
146 | **Reads**
147 | ```sequence
148 | section->helper: request
149 | helper->layout manager: request
150 | layout manager->recycler view: request
151 | recycler view-->layout manager: result in recycler views coords
152 | layout manager->layout manager: apply configuration transformation
153 | layout manager-->helper: result in layout managers coords
154 | helper->helper: apply subspace transformation
155 | helper-->section: result in subsections coords
156 | ```
157 |
158 | ## Configuration transformations
159 | The `LayoutHelper` has a number of `ConfigTransformation` s.
160 | > w = width of recycler view, h = height of recycler view.
161 | *All of the config transformations are involutory, so can be applied to transform the coordinate space in either direction.*
162 |
163 | LTR
164 | : A no-op transformation.
165 | $$\text{ltr}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}l\\t\\r\\b\end{pmatrix}$$
166 |
167 | RTL
168 | : Mirrors the horizontal values.
169 | $$\text{rtl}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}w-r\\t\\w-l\\b\end{pmatrix}$$
170 |
171 | Stack from end
172 | : Mirrors the vertical values.
173 | $$\text{sfe}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}l\\h-b\\r\\h-t\end{pmatrix}$$
174 |
175 | Reverse
176 | : Mirrors both the horizontal and vertical values.
177 | $$\text{rev}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}w - r\\h-b\\w-l\\h-t\end{pmatrix}$$
178 |
179 | Vertical orientation
180 | : A no-op transformation.
181 | $$\text{ver}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}l\\t\\r\\b\end{pmatrix}$$
182 |
183 | Horizontal orientation
184 | : Swaps the top and left values, and the bottom and right values. Effectively mirroring along a line drawn from the NW corner to the SE corner.
185 | $$\text{hor}\begin{pmatrix}l\\t\\r\\b\end{pmatrix} = \begin{pmatrix}t\\l\\b\\r\end{pmatrix}$$
186 |
187 | : It is important to note that for horizontal scrolling configurations, width and height measurement calls are transposed.
188 |
189 | ## Nesting
190 | Subsections have their virtual space inset inside the parent section. When creating the subsection layout the parent SLM gives the available width, and the point of the top left corner. The derivative values for the subsections total x and y offsets within the root coordinate system are calculated and calls from the subsection helper can be made directly to the root interface.
191 |
192 | # Tracking graph changes
193 | ~~Recycler view maintains its own state for tracking what items are available for layout or not. When items changes are notified, the layout manager gets informed through the data change callbacks - *onItems\*()*. These events happen at a time determined by the recycler view, either before layout or after pre-layout. Superslim layout manager also receives events for changes to the section graph direct from the adapter. The events from the adapter are stored pending the events from the recycler view. Once matching events, one from each source, are received, they are reconciled and the appropriate change is made to the internal section graph.~~
194 |
195 | To keep the internal section graph consistent with the recycler view model, the layout manager has to reconcile recycler views reordered item change notifications with changes that happened before that to the adapter section graph. To do this, the layout manager receives item change callbacks from the recycler view, and updates an internal section graph accordingly. When needed, the layout manager can refer to the section graph in the adapter to fetch additional data about the event; e.g., is the item being added, in the callback, a member of a new or already existing section?
196 |
197 | To track sections, an internal id is used separate from the user given section id.
198 |
199 | ## Section graph events
200 | Section level events need additional section data passed to the layout manager. This needs to be paired with the appropriate callback from recycler view before updating the internal graph.
201 |
202 | Add section
203 | : Add section -> add items to item manager -> notify items added
204 |
205 | Remove section
206 | : Remove section -> remove items from item manager -> notify items removed
207 |
208 | Move section
209 | : Move section -> move items in item manager -> notify items moved one by one
210 |
211 | Update section
212 | : Change section configuration -> update items in item manager -> notify items updated
213 |
214 | Item level events are simple and don't involve extra data.
215 |
216 | Add item or header
217 | : Add item or header -> add item to item manager -> notify item added
218 |
219 | Remove item or header
220 | : Remove item or header -> remove item in item manager -> notify item removed
221 |
222 | Move item
223 | : Move item -> move item in item manager -> notify item moved
224 |
225 | Update item
226 | : Update item -> update item in item manager -> notify item updated
227 |
228 |
229 | # Layout manager code paths
230 |
231 | onLayoutChild
232 | scrollViewBy
233 |
234 | # Adapter
235 | sectionLookup: map of T to section
236 | internalLookup: map of integer to section
237 | root: section
238 |
239 | #Section graph
240 | ##Section
241 | userId: T
242 | hasHeader: boolean
243 | totalChildren: integer
244 | : constraint: totalChildren >= subsections.size()
245 |
246 | subsections: list of sections
247 |
248 | # Internal section graph
249 | ## Section
250 | adapterPosition: integer
251 | : Position of section in adapter. If the section is not empty, this is also the adapter position of the first item in the section.
252 |
253 | hasHeader: boolean
254 | totalChildren: integer
255 | : **constraint:** totalChildren >= subsections.size() + 1 if hasHeader is true
256 |
257 | subsections: list of sections
258 |
259 | # Section layout manager
260 | ## Laying out children
261 | The difference between items and subsections is hidden from the SLM. Children can be measured as per the usual layout manager pattern, however subsections always fill the available width and have an undefined height, items behave as normal. To layout and add the child, the SLM defines the bounds and position of the child and the abstraction handles the rest. Laying out a subsection cascades into a layout call for that section and SLM with the given layout area.
262 |
263 | ## Pre-layout child handling
264 | A child object abstracts the difference between subsections and items. The layout helper
265 |
266 | ```
267 | child = getChild(i)
268 | measure(child)
269 | layoutAndAdd(child, left, top, right, bottom)
270 | child.done()
271 | ```
272 |
273 | ## Child abstraction
274 | ```Kotlin
275 | interface Child {
276 | val measuredWidth
277 | val measuredHeight
278 | val left
279 | val top
280 | val right
281 | val bottom
282 | }
283 | ```
284 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | org.gradle.jvmargs=-Xms512m -Xmx1024m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 | kotlin.incremental=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TonicArtos/SuperSLiM/b21bb218455bc75ac34470e3d594a83c1c8ff30c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Mar 02 23:03:18 GMT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # For Cygwin, ensure paths are in UNIX format before anything is touched.
46 | if $cygwin ; then
47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
48 | fi
49 |
50 | # Attempt to set APP_HOME
51 | # Resolve links: $0 may be a link
52 | PRG="$0"
53 | # Need this for relative symlinks.
54 | while [ -h "$PRG" ] ; do
55 | ls=`ls -ld "$PRG"`
56 | link=`expr "$ls" : '.*-> \(.*\)$'`
57 | if expr "$link" : '/.*' > /dev/null; then
58 | PRG="$link"
59 | else
60 | PRG=`dirname "$PRG"`"/$link"
61 | fi
62 | done
63 | SAVED="`pwd`"
64 | cd "`dirname \"$PRG\"`/" >&-
65 | APP_HOME="`pwd -P`"
66 | cd "$SAVED" >&-
67 |
68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
69 |
70 | # Determine the Java command to use to start the JVM.
71 | if [ -n "$JAVA_HOME" ] ; then
72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
73 | # IBM's JDK on AIX uses strange locations for the executables
74 | JAVACMD="$JAVA_HOME/jre/sh/java"
75 | else
76 | JAVACMD="$JAVA_HOME/bin/java"
77 | fi
78 | if [ ! -x "$JAVACMD" ] ; then
79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
80 |
81 | Please set the JAVA_HOME variable in your environment to match the
82 | location of your Java installation."
83 | fi
84 | else
85 | JAVACMD="java"
86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
87 |
88 | Please set the JAVA_HOME variable in your environment to match the
89 | location of your Java installation."
90 | fi
91 |
92 | # Increase the maximum file descriptors if we can.
93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
94 | MAX_FD_LIMIT=`ulimit -H -n`
95 | if [ $? -eq 0 ] ; then
96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
97 | MAX_FD="$MAX_FD_LIMIT"
98 | fi
99 | ulimit -n $MAX_FD
100 | if [ $? -ne 0 ] ; then
101 | warn "Could not set maximum file descriptor limit: $MAX_FD"
102 | fi
103 | else
104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
105 | fi
106 | fi
107 |
108 | # For Darwin, add options to specify how the application appears in the dock
109 | if $darwin; then
110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
111 | fi
112 |
113 | # For Cygwin, switch paths to Windows format before running java
114 | if $cygwin ; then
115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
158 | function splitJvmOpts() {
159 | JVM_OPTS=("$@")
160 | }
161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
163 |
164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
165 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 |
4 | android {
5 | compileSdkVersion compile_sdk
6 | buildToolsVersion build_tools_version
7 |
8 | defaultConfig {
9 | minSdkVersion 9
10 | targetSdkVersion target_sdk
11 | versionCode 5
12 | versionName "0.5"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | buildConfigField("boolean", "UNIT_TEST", "false")
18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
19 | debuggable true
20 | }
21 | debug {
22 | buildConfigField("boolean", "UNIT_TEST", "true")
23 | }
24 | unitTest {
25 | buildConfigField("boolean", "UNIT_TEST", "true")
26 | debuggable true
27 | }
28 | }
29 |
30 | // Always show the result of every unit test, even if it passes.
31 | testOptions.unitTests.all {
32 | testLogging {
33 | events 'passed', 'skipped', 'failed', 'standardOut', 'standardError'
34 | }
35 | }
36 |
37 | sourceSets {
38 | main.java.srcDirs += 'src/main/kotlin'
39 | test.java.srcDirs += 'src/test/kotlin'
40 | }
41 | }
42 |
43 | dependencies {
44 | testCompile 'junit:junit:4.12'
45 | testCompile 'org.hamcrest:hamcrest-all:1.3'
46 | testCompile "com.nhaarman:mockito-kotlin:1.3.0"
47 |
48 |
49 | compile fileTree(dir: 'libs', include: ['*.jar'])
50 | compile "com.android.support:appcompat-v7:$supportlib_version"
51 | compile "com.android.support:support-v4:$supportlib_version"
52 | compile "com.android.support:recyclerview-v7:$supportlib_version"
53 |
54 | compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
55 | compile "org.jetbrains.anko:anko-sdk15:$anko_version"
56 | }
57 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /home/tonic/.local/share/android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/com/tonicartos/superslim/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 |
11 | public ApplicationTest() {
12 | super(Application.class);
13 | }
14 | }
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/adapter/FooterStyle.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.adapter;
2 |
3 | import com.tonicartos.superslim.SectionConfig;
4 |
5 | import android.support.annotation.IntDef;
6 |
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 |
10 | @IntDef({SectionConfig.FOOTER_END,
11 | SectionConfig.FOOTER_STICKY,
12 | SectionConfig.FOOTER_INLINE,
13 | SectionConfig.FOOTER_START})
14 | @Retention(RetentionPolicy.SOURCE)
15 | public @interface FooterStyle {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/adapter/HeaderStyle.java:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.adapter;
2 |
3 | import com.tonicartos.superslim.SectionConfig;
4 |
5 | import android.support.annotation.IntDef;
6 |
7 | import java.lang.annotation.Retention;
8 | import java.lang.annotation.RetentionPolicy;
9 |
10 | @IntDef({SectionConfig.HEADER_END,
11 | SectionConfig.HEADER_STICKY,
12 | SectionConfig.HEADER_INLINE,
13 | SectionConfig.HEADER_START})
14 | @Retention(RetentionPolicy.SOURCE)
15 | public @interface HeaderStyle {
16 |
17 | }
18 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/adapter/adapter.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.adapter
2 |
3 | import android.support.annotation.IntDef
4 | import android.support.v7.widget.RecyclerView
5 | import com.tonicartos.superslim.*
6 | import com.tonicartos.superslim.layout.LinearSectionConfig
7 | import java.util.*
8 |
9 | interface Graph {
10 | fun getNumSections(): Int
11 | fun getSection(position: Int): Section
12 | fun addSection(section: Section)
13 | fun insertSection(position: Int, section: Section)
14 | fun removeSection(position: Int): Section
15 | fun moveSection(from: Int, to: Int)
16 | }
17 |
18 | abstract class SuperSlimAdapter, VH : RecyclerView.ViewHolder>
19 | private constructor(internal val graph: GraphImpl, internal val itemManager: ItemManager,
20 | private val adapterContract: AdapterContractImpl) : RecyclerView.Adapter(),
21 | AdapterContract by adapterContract,
22 | Graph by graph {
23 | init {
24 | graph.init(itemManager)
25 | itemManager.init(this)
26 | adapterContract.init(this)
27 | }
28 |
29 | constructor() : this(GraphImpl(), ItemManager(), AdapterContractImpl())
30 |
31 | abstract fun onBindViewHolder(holder: VH, item: Item)
32 | final override fun onBindViewHolder(holder: VH, position: Int) {
33 | onBindViewHolder(holder, itemManager[position])
34 | }
35 |
36 | final override fun getItemCount() = itemManager.itemCount
37 | final override fun getItemViewType(position: Int) = itemManager[position].type
38 |
39 | /****************************************************
40 | * Section registry
41 | ****************************************************/
42 |
43 | internal val sectionLookup = HashMap()
44 |
45 | fun getSectionWithId(id: ID): Section? = sectionLookup[id]
46 |
47 | @JvmOverloads
48 | fun createSection(id: ID, config: SectionConfig, header: Item? = null, footer: Item? = null): Section {
49 | val section = Section()
50 | section.header = header
51 | section.footer = footer
52 | section.configuration = config
53 | registerSection(id, section)
54 | return section
55 | }
56 |
57 | @JvmOverloads
58 | fun createSection(id: ID, header: Item? = null, footer: Item? = null): Section
59 | = createSection(id, LinearSectionConfig(), header, footer)
60 |
61 | /**
62 | * Register a [section] to [id]. Descendant sections will be registered if [cascade] is true.
63 | */
64 | @JvmOverloads fun registerSection(id: ID, section: Section, cascade: Boolean = true) {
65 | sectionLookup[id]?.let { if (it !== section) deregister(id) }
66 |
67 | sectionLookup[id] = section
68 | section.registration = Registration(id, this)
69 | if (cascade) {
70 | // If there are any descendants
71 | section.subsections.forEach { subsection ->
72 | @Suppress("UNCHECKED_CAST")
73 | val reusableRegistration = subsection.registration as? Registration
74 | if (reusableRegistration == null) {
75 | // Can't understand registration, and since it can't be used, ditch it.
76 | subsection.registration = null
77 | } else {
78 | registerSection(reusableRegistration.id, subsection, cascade)
79 | }
80 | }
81 | }
82 | }
83 |
84 | @JvmOverloads fun deregister(id: ID, stripIds: Boolean = true, cascade: Boolean = true): Section? {
85 | return sectionLookup.remove(id)?.apply {
86 | if (stripIds) {
87 | registration = null
88 | }
89 | if (cascade) {
90 | subsections.forEach { it.registration?.deregister(stripIds, cascade) }
91 | }
92 | }
93 | }
94 | }
95 |
96 | private class AdapterContractImpl> : AdapterContract {
97 | private lateinit var adapter: SuperSlimAdapter
98 |
99 | fun init(adapter: SuperSlimAdapter) {
100 | this.adapter = adapter
101 | }
102 |
103 | override fun onLayoutManagerAttached(layoutManager: SuperSlimLayoutManager) {
104 | if (adapter.graph.contract != null) throw OnlySupportsOneRecyclerViewException()
105 | val contract = DataChangeContract(adapter, layoutManager)
106 | adapter.graph.contract = contract
107 | }
108 |
109 | override fun onLayoutManagerDetached(layoutManager: SuperSlimLayoutManager) {
110 | adapter.graph.contract = null
111 | }
112 |
113 |
114 | override fun getRoot() = adapter.graph.root.configuration
115 |
116 | override fun setRootId(id: Int) {
117 | adapter.graph.root.id = id
118 | }
119 |
120 | override fun populateRoot(out: SectionData) {
121 | out.adapterPosition = 0
122 | out.hasHeader = false
123 | out.itemCount = adapter.graph.root.itemCount
124 | out.childCount = adapter.graph.root.childCount
125 | out.subsectionsById = adapter.graph.root.subsections.map { it.id }
126 | }
127 |
128 | override fun getSections() = adapter.sectionLookup.mapValues { it.value.configuration }
129 |
130 | override fun setSectionIds(idMap: Map<*, Int>) {
131 | idMap.forEach {
132 | val section = adapter.sectionLookup[it.key] ?: throw IllegalArgumentException(
133 | "unknown id \"${it.key}\" for section lookup")
134 | section.id = it.value
135 | }
136 | }
137 |
138 | override fun populateSection(data: Pair<*, SectionData>) {
139 | val section = adapter.sectionLookup[data.first] ?: throw IllegalArgumentException(
140 | "unknown id \"${data.first}\" for section lookup")
141 | data.second.adapterPosition = section.positionInAdapter
142 | data.second.hasHeader = section.header != null
143 | data.second.itemCount = section.itemCount
144 | data.second.childCount = section.childCount
145 | data.second.subsectionsById = section.subsections.map { it.id }
146 | }
147 |
148 | override fun getData(position: Int): AdapterContract.Data {
149 | val item = adapter.itemManager[position]
150 | val parent = item.parent!!
151 | return AdapterContract.data.pack(parent.id, item.positionInParent, when (item) {
152 | parent.header -> AdapterContract.Data.HEADER
153 | parent.footer -> AdapterContract.Data.FOOTER
154 | else -> AdapterContract.Data.OTHER
155 | })
156 | }
157 | }
158 |
159 |
160 | internal interface SectionContract {
161 | fun notifySectionInserted(section: Section): Int
162 | fun notifySectionRemoved(section: Section)
163 | fun notifySectionUpdated(section: Section)
164 | }
165 |
166 | private class DataChangeContract(val adapter: SuperSlimAdapter<*, *>,
167 | val layoutManager: SuperSlimLayoutManager) : SectionContract {
168 | override fun notifySectionInserted(
169 | section: Section): Int = layoutManager.notifySectionAdded(section.parent!!.id, section.positionInParent,
170 | section.configuration)
171 |
172 | override fun notifySectionRemoved(section: Section) {
173 | layoutManager.notifySectionRemoved(section.id, section.parent!!.id)
174 | }
175 |
176 | //final override fun notifySectionMoved(section: Section, toParent: Section, toPosition: Int) {
177 | // layoutManager.notifySectionMoved(section.id, section.parent!!.id, section.positionInParent, toParent.id, toPosition)
178 | //}
179 |
180 | override fun notifySectionUpdated(section: Section) {
181 | layoutManager.notifySectionUpdated(section.id, section.configuration)
182 | if (section.itemCount > 0) {
183 | adapter.notifyItemRangeChanged(section.positionInAdapter, section.itemCount)
184 | }
185 | }
186 | }
187 |
188 | internal class ItemManager {
189 | fun init(adapter: SuperSlimAdapter<*, *>) {
190 | this.adapter = adapter
191 | }
192 |
193 | private var adapter: SuperSlimAdapter<*, *>? = null
194 |
195 | private val items = ArrayList- ()
196 |
197 | val itemCount: Int
198 | get() = items.size
199 |
200 | operator fun get(position: Int) = items[position]
201 |
202 | fun insert(position: Int, item: Item) {
203 | items.add(position, item)
204 | if (BuildConfig.UNIT_TEST) return
205 | adapter?.notifyItemInserted(position)
206 | }
207 |
208 | fun insert(start: Int, items: List
- ) {
209 | this.items.addAll(start, items)
210 | if (BuildConfig.UNIT_TEST) return
211 | adapter?.notifyItemRangeInserted(start, items.size)
212 | }
213 |
214 | fun move(from: Int, to: Int) {
215 | items.add(to, items.removeAt(from))
216 | if (BuildConfig.UNIT_TEST) return
217 | adapter?.notifyItemMoved(from, to)
218 | }
219 |
220 | fun remove(position: Int) {
221 | items.removeAt(position)
222 | if (BuildConfig.UNIT_TEST) return
223 | adapter?.notifyItemRemoved(position)
224 | }
225 |
226 | fun removeRange(start: Int, range: Int) {
227 | for (i in start until start + range) {
228 | items.removeAt(start)
229 | }
230 | if (BuildConfig.UNIT_TEST) return
231 | adapter?.notifyItemRangeRemoved(start, range)
232 | }
233 |
234 | operator fun set(position: Int, value: Item) {
235 | items[position] = value
236 | if (BuildConfig.UNIT_TEST) return
237 | adapter?.notifyItemChanged(position)
238 | }
239 | }
240 |
241 | internal class Registration>(val id: ID, val manager: SuperSlimAdapter) {
242 | @JvmOverloads fun deregister(stripIds: Boolean = true, cascade: Boolean = true) {
243 | manager.deregister(id, stripIds, cascade)
244 | }
245 | }
246 |
247 | class OnlySupportsOneRecyclerViewException : RuntimeException(
248 | "SuperSLiM currently only supports a single recycler view per adapter.")
249 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/adapter/graph.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.adapter
2 |
3 | import android.util.Log
4 | import com.tonicartos.superslim.AdapterContract
5 | import com.tonicartos.superslim.SectionConfig
6 | import com.tonicartos.superslim.layout.LinearSectionConfig
7 | import java.util.*
8 |
9 | sealed class Node {
10 | var positionInParent: Int = 0
11 | val positionInAdapter: Int
12 | get() = (parent?.positionInAdapter ?: 0) + peersItemsBeforeThis
13 |
14 | var parent: Section? = null
15 | internal set
16 |
17 | var itemCount: Int = 0
18 | protected set
19 | open val childCount: Int get() = 0
20 |
21 | fun removeFromParent() = parent?.remove(positionInParent)
22 |
23 | /**
24 | * Number of items held by other nodes before this one in the same section.
25 | */
26 | internal var peersItemsBeforeThis: Int = 0
27 | open internal var itemManager: ItemManager? = null
28 | internal abstract fun insertItemsToAdapter()
29 | internal abstract fun removeItemsFromAdapter()
30 |
31 | open internal fun init(positionInParent: Int, itemsBeforeThis: Int, parent: Section, itemManager: ItemManager?) {
32 | this.itemManager = itemManager
33 | this.parent = parent
34 | this.peersItemsBeforeThis = itemsBeforeThis
35 | this.positionInParent = positionInParent
36 | }
37 |
38 | open internal fun reset() {
39 | parent = null
40 | itemManager = null
41 | }
42 |
43 | abstract class ItemNode : Node() {
44 | init {
45 | itemCount = 1
46 | }
47 | }
48 |
49 | abstract class SectionNode : Node() {
50 | }
51 | }
52 |
53 | /**
54 | * An item in the graph. Used in [SuperSlimAdapter.onBindViewHolder] so any packaged data can be used for the
55 | * binding or to lookup data elsewhere.
56 | */
57 | open class Item(val type: Int = 0, val data: Any? = null) : Node.ItemNode() {
58 | override fun insertItemsToAdapter() {
59 | itemManager?.insert(positionInAdapter, this)
60 | }
61 |
62 | override fun removeItemsFromAdapter() {
63 | itemManager?.remove(positionInAdapter)
64 | }
65 | }
66 |
67 | /**
68 | * A section in the graph.
69 | */
70 | class Section internal constructor(contract: SectionContract? = null) : Node.SectionNode(), Iterable {
71 | private val _contract: SectionContract?
72 | internal val contract: SectionContract?
73 | get() = _contract ?: parent?.contract
74 |
75 | init {
76 | _contract = contract
77 | }
78 |
79 | /**
80 | * An id assigned by the layout manager.
81 | */
82 | internal var id: Int = -1
83 |
84 | internal var registration: Registration<*>? = null
85 | fun deregister() {
86 | registration?.deregister()
87 | }
88 |
89 | var configuration: SectionConfig = LinearSectionConfig()
90 | get() = field
91 | set(value) {
92 | field = value
93 | contract?.notifySectionUpdated(this)
94 | }
95 |
96 | override var itemManager: ItemManager?
97 | get() = super.itemManager
98 | set(value) {
99 | super.itemManager = value
100 | children.forEach { child -> child.itemManager = value }
101 | header?.itemManager = value
102 | footer?.itemManager = value
103 | }
104 |
105 | /*************************
106 | * Header management
107 | *************************/
108 |
109 | var header: Item? = null
110 | set(value) {
111 | if (value == null) {
112 | field?.let {
113 | it.removeItemsFromAdapter()
114 | totalItemsChanged(-1)
115 | children.forEach { child -> child.peersItemsBeforeThis -= 1 }
116 | field = null
117 | }
118 | return
119 | }
120 | initChild(0, value)
121 | value.peersItemsBeforeThis = 0
122 | if (field != null) {
123 | itemManager?.set(positionInAdapter, value)
124 | } else {
125 | itemManager?.insert(positionInAdapter, value)
126 | children.forEach { it.peersItemsBeforeThis += 1 }
127 | footer?.let { it.peersItemsBeforeThis += 1 }
128 | totalItemsChanged(1)
129 | }
130 | field = value
131 | }
132 |
133 | var footer: Item? = null
134 | set(value) {
135 | if (value == null) {
136 | field?.let {
137 | it.removeItemsFromAdapter()
138 | totalItemsChanged(-1)
139 | field = null
140 | }
141 | return
142 | }
143 | initChild(childCount, value)
144 | if (field != null) {
145 | itemManager?.set(value.positionInAdapter, value)
146 | } else {
147 | value.insertItemsToAdapter()
148 | totalItemsChanged(1)
149 | }
150 | field = value
151 | }
152 |
153 | fun removeHeader() {
154 | header = null
155 | }
156 |
157 | fun removeFooter() {
158 | footer = null
159 | }
160 |
161 |
162 | /*************************
163 | * Expandable section stuff
164 | *************************/
165 |
166 | var collapsed: Boolean = false
167 | private set
168 |
169 | fun toggleChildren() = if (collapsed) expandChildren() else collapseChildren()
170 |
171 | private fun expandChildren() {
172 | var numItemsAdded = 0
173 | children.forEachIndexed { i, child ->
174 | initChild(i, child)
175 | child.insertItemsToAdapter()
176 | numItemsAdded += child.itemCount
177 | }
178 | footer?.let { footer ->
179 | initChild(childCount, footer)
180 | footer.insertItemsToAdapter()
181 | numItemsAdded += 1
182 | }
183 | totalItemsChanged(numItemsAdded)
184 | collapsed = false
185 | }
186 |
187 | private fun collapseChildren() {
188 | val headerCount = if (header == null) 0 else 1
189 | val footerCount = if (footer == null) 0 else 1
190 | val numItemsToRemove = itemCount - headerCount
191 | itemManager?.removeRange(positionInAdapter + headerCount, numItemsToRemove)
192 | for (it in children) {
193 | it.reset()
194 | }
195 | totalItemsChanged(-numItemsToRemove)
196 | collapsed = true
197 | }
198 |
199 | /*************************
200 | * Item management
201 | *************************/
202 |
203 | override fun insertItemsToAdapter() {
204 | if (id == -1) {
205 | id = contract?.notifySectionInserted(this) ?: -1
206 | }
207 |
208 | header?.let { header ->
209 | itemManager?.insert(positionInAdapter, header)
210 | }
211 |
212 | if (!collapsed) {
213 | children.forEachIndexed { i, child ->
214 | child.itemManager = itemManager
215 | initChild(i, child)
216 | child.insertItemsToAdapter()
217 | }
218 | footer?.let { footer ->
219 | footer.itemManager = itemManager
220 | initChild(childCount, footer)
221 | footer.insertItemsToAdapter()
222 | }
223 | }
224 |
225 | }
226 |
227 | override fun removeItemsFromAdapter() {
228 | contract?.notifySectionRemoved(this)
229 | id = -1
230 |
231 | itemManager?.removeRange(positionInAdapter, itemCount)
232 | }
233 |
234 | /****************************************************
235 | * Children stuff
236 | ****************************************************/
237 |
238 | private val mutableChildren: ArrayList = arrayListOf()
239 | val children: List
240 | get() = mutableChildren
241 |
242 | override val childCount: Int get() = children.size
243 |
244 | private fun initChild(position: Int, child: Node) {
245 | val numItemsBeforeChild = when {
246 | position > 0 -> children[position - 1].let { it.peersItemsBeforeThis + it.itemCount }
247 | header == null -> 0
248 | else -> 1
249 | }
250 | child.init(position, numItemsBeforeChild, this, itemManager)
251 | }
252 |
253 | /*************************
254 | * Child actions
255 | *************************/
256 |
257 | fun getAdapterPositionOfChild(position: Int): Int = children[position].positionInAdapter
258 |
259 | /**
260 | * Get child at [position]. Throws runtime exception if child isn't of the expected type.
261 | */
262 | @Suppress("UNCHECKED_CAST")
263 | operator fun get(position: Int) = children[position] as T
264 |
265 | /**
266 | * Get child at [position]. Returns null if child isn't of the expected type.
267 | */
268 | @Suppress("UNCHECKED_CAST")
269 | fun getOptional(position: Int) = children[position] as? T
270 |
271 |
272 | /**
273 | * Add child to end of children.
274 | */
275 | fun add(child: Node) {
276 | insert(children.size, child)
277 | }
278 |
279 | /**
280 | * Insert [child] into [position].
281 | */
282 | fun insert(position: Int, child: Node) {
283 | val dest = if (position !in 0..children.size - 1) children.size else position
284 |
285 | if (!collapsed) {
286 | initChild(dest, child)
287 | child.insertItemsToAdapter()
288 |
289 | // Update children after position.
290 | val numItemsAdded = child.itemCount
291 | children.subList(dest, children.size).forEach {
292 | it.peersItemsBeforeThis += numItemsAdded
293 | it.positionInParent += 1
294 | }
295 | totalItemsChanged(numItemsAdded)
296 | }
297 | mutableChildren.add(dest, child)
298 | }
299 |
300 | /**
301 | * Move child found at [from], to [to].
302 | */
303 | fun move(from: Int, to: Int) {
304 | if (from == to) return
305 |
306 | val moving = mutableChildren.removeAt(from)
307 | if (!collapsed) {
308 | moving.removeItemsFromAdapter()
309 |
310 | // Update children between from and to.
311 | val numItemsRemoved = moving.itemCount
312 | if (from < to) {
313 | children.subList(from, to).forEach {
314 | it.peersItemsBeforeThis -= numItemsRemoved
315 | it.positionInParent -= 1
316 | }
317 | } else {
318 | children.subList(to, from).forEach {
319 | it.peersItemsBeforeThis += numItemsRemoved
320 | it.positionInParent += 1
321 | }
322 | }
323 |
324 | // Re-init child in new place.
325 | initChild(to, moving)
326 | moving.insertItemsToAdapter()
327 | }
328 | mutableChildren.add(to, moving)
329 | }
330 |
331 | /**
332 | * Remove child at [position].
333 | */
334 | fun remove(position: Int) {
335 | val removed = mutableChildren.removeAt(position)
336 |
337 | if (!collapsed) {
338 | removed.removeItemsFromAdapter()
339 |
340 | // Update children after the removed child.
341 | val numItemsRemoved = removed.itemCount
342 | children.subList(position, children.size).forEach {
343 | it.peersItemsBeforeThis -= numItemsRemoved
344 | it.positionInParent -= 1
345 | }
346 | totalItemsChanged(-numItemsRemoved)
347 | }
348 | removed.reset()
349 | }
350 |
351 | /**
352 | * Replace child at [position] with [replacement].
353 | */
354 | operator fun set(position: Int, replacement: Node) {
355 | val toRemove = children[position]
356 |
357 | if (!collapsed) {
358 | toRemove.removeItemsFromAdapter()
359 | initChild(position, replacement)
360 | replacement.insertItemsToAdapter()
361 |
362 | // Update children after the target position if there is a change in total items.
363 | val numItemsAdded = replacement.itemCount - toRemove.itemCount
364 | if (numItemsAdded != 0) {
365 | children.subList(position + 1, children.size).forEach {
366 | it.peersItemsBeforeThis += numItemsAdded
367 | }
368 | totalItemsChanged(numItemsAdded)
369 | }
370 | }
371 |
372 | mutableChildren[position] = replacement
373 | toRemove.reset()
374 | }
375 |
376 | /*************************
377 | * Listing and navigating children
378 | *************************/
379 |
380 | override fun iterator(): Iterator = children.iterator()
381 |
382 | /**
383 | * Returns a list containing all items matching the given [predicate].
384 | */
385 | inline fun filterItems(predicate: (Item) -> Boolean): List
- {
386 | return items.filterTo(ArrayList
- (), predicate)
387 | }
388 |
389 | /**
390 | * A list containing all items in the section.
391 | */
392 | @Suppress("WARNINGS")
393 | val items: List
-
394 | get() = children.filterTo(ArrayList(), { it -> it is Item }).map { it as Item }
395 |
396 | /**
397 | * Returns a list containing all sections matching the given [predicate].
398 | */
399 | inline fun filterSubsections(predicate: (Section) -> Boolean): List {
400 | return subsections.filterTo(ArrayList(), predicate)
401 | }
402 |
403 | /**
404 | * A list of all subsections.
405 | */
406 | val subsections: List
407 | get() = children.filterTo(ArrayList(), { it -> it is Section }).map { it as Section }
408 |
409 | /**
410 | * Returns a list containing all elements matching the given [predicate].
411 | */
412 | inline fun filterChildren(predicate: (Node) -> Boolean): List {
413 | return children.filterTo(ArrayList(), predicate)
414 | }
415 |
416 | override fun reset() {
417 | super.reset()
418 | }
419 |
420 | private fun totalItemsChanged(change: Int) {
421 | if (change == 0) return
422 |
423 | itemCount += change
424 | footer?.let { it.peersItemsBeforeThis += change }
425 | parent?.totalItemsChangedInChild(positionInParent, change)
426 | }
427 |
428 | private fun totalItemsChangedInChild(position: Int, change: Int) {
429 | if (change == 0) return
430 |
431 | // Update children after child that called in the change.
432 | children.subList(position + 1, children.size).forEach { it.peersItemsBeforeThis += change }
433 |
434 | totalItemsChanged(change)
435 | }
436 |
437 | }
438 |
439 | internal class GraphImpl() : Graph, SectionContract {
440 | internal val root: Section
441 |
442 | init {
443 | root = Section(this)
444 | }
445 |
446 | private var itemManager: ItemManager?
447 | get() = root.itemManager
448 | set(value) {
449 | root.itemManager = value
450 | root.insertItemsToAdapter()
451 | }
452 |
453 | fun init(itemManager: ItemManager) {
454 | this.itemManager = itemManager
455 | }
456 |
457 | override fun getNumSections() = root.childCount
458 |
459 | override fun getSection(position: Int): Section = root.get(position)
460 |
461 | override fun addSection(section: Section) {
462 | root.add(section)
463 | }
464 |
465 | override fun insertSection(position: Int, section: Section) {
466 | root.insert(position, section)
467 | }
468 |
469 | override fun removeSection(position: Int): Section {
470 | val s = root.get(position)
471 | root.remove(position)
472 | return s
473 | }
474 |
475 | override fun moveSection(from: Int, to: Int) {
476 | root.move(from, to)
477 | }
478 |
479 | var contract: SectionContract? = null
480 |
481 | override fun notifySectionInserted(section: Section) = contract?.notifySectionInserted(section) ?: -1
482 |
483 | override fun notifySectionRemoved(section: Section) {
484 | contract?.notifySectionRemoved(section)
485 | }
486 |
487 | override fun notifySectionUpdated(section: Section) {
488 | contract?.notifySectionUpdated(section)
489 | }
490 |
491 | // override fun notifySectionMoved(section: Section, toParent: Section, toPosition: Int) {
492 | // scw?.notifySectionMoved(section, toParent, toPosition)
493 | // }
494 | }
495 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/extensions.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | import android.os.Parcel
4 | import android.os.Parcelable
5 |
6 | inline fun createParcel(crossinline create: (Parcel) -> T?) = object : Parcelable.Creator {
7 | override fun createFromParcel(source: Parcel) = create(source)
8 | override fun newArray(size: Int) = arrayOfNulls(size)
9 | }
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/graph.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | import com.tonicartos.superslim.adapter.FooterStyle
4 | import com.tonicartos.superslim.adapter.HeaderStyle
5 | import com.tonicartos.superslim.internal.SectionState
6 |
7 | inline fun T.use(block: (T) -> R): R {
8 | var done = false
9 | try {
10 | return block(this)
11 | } catch (e: Exception) {
12 | done = true
13 | this.done()
14 | throw e
15 | } finally {
16 | if (!done) {
17 | this.done()
18 | }
19 | }
20 | }
21 |
22 | // TODO: Make TrimChild version which wraps a section or attached view.
23 | interface Child {
24 | companion object {
25 | const val INVALID = 0
26 | const val ANIM_NONE = 0
27 | const val ANIM_APPEARING = 1
28 | const val ANIM_DISAPPEARING = 2
29 | }
30 |
31 | fun done()
32 |
33 | val numViews get() = 1
34 |
35 | /**
36 | * True if the child is being removed in this layout.
37 | */
38 | val isRemoved: Boolean
39 |
40 | val measuredWidth: Int
41 | val measuredHeight: Int
42 | /**
43 | * Measure child. This should be done after [addToRecyclerView] as some views like ViewPager expect to be attached
44 | * before measurement.
45 | */
46 | fun measure(usedWidth: Int = 0, usedHeight: Int = 0)
47 |
48 | val left: Int
49 | val top: Int
50 | val right: Int
51 | val bottom: Int
52 | /**
53 | * Layout the child. A backing view will have the dimensions specified. A subsection will have bounds defined by
54 | * left, top, and right, however will ignore bottom and may fill any remaining space to the bottom of the viewable
55 | * area.
56 | */
57 | fun layout(left: Int, top: Int, right: Int, bottom: Int, numViewsBefore: Int = 0)
58 |
59 | /**
60 | * Fill distance dy at top of the child. The child will attempt to extend into this space; only if it is a section.
61 | *
62 | * @param dy Distance to fill. Value will be -ve.
63 | * @param left Left edge of area to fill.
64 | * @param top Top edge of area to fill.
65 | * @param right Right edge of area to fill.
66 | * @param bottom Bottom edge of area to fill.
67 | *
68 | * @return How much of dy filled.
69 | */
70 | fun fillTop(dy: Int, left: Int, top: Int, right: Int, bottom: Int, numViewsBefore: Int = 0): Int
71 |
72 | fun trimTop(scrolled: Int, top: Int, helper: LayoutHelper, numViewsBefore: Int = 0): Int
73 |
74 | /**
75 | * Fill distance dy at bottom of the child. The child will attempt to extend into this space; only if it is a section.
76 | *
77 | * @param dy Distance to fill. Value will be +ve.
78 | * @param left Left edge of area to fill.
79 | * @param top Top edge of area to fill.
80 | * @param right Right edge of area to fill.
81 | * @param bottom Bottom edge of area to fill.
82 | *
83 | * @return How much of dy filled.
84 | */
85 | fun fillBottom(dy: Int, left: Int, top: Int, right: Int, bottom: Int, numViewsBefore: Int = 0): Int
86 |
87 | fun trimBottom(scrolled: Int, top: Int, helper: LayoutHelper, numViewsBefore: Int = 0): Int
88 |
89 |
90 | val width: Int
91 | val height: Int
92 | val disappearedHeight: Int
93 |
94 | /**
95 | * Adds child to the recycler view.
96 | */
97 | fun addToRecyclerView() = addToRecyclerView(-1)
98 |
99 | /**
100 | * Adds child to the recycler view.
101 | */
102 | fun addToRecyclerView(i: Int)
103 | }
104 |
105 | /**
106 | * Configuration of a section.
107 | */
108 | abstract class SectionConfig(gutterStart: Int = SectionConfig.DEFAULT_GUTTER,
109 | gutterEnd: Int = SectionConfig.DEFAULT_GUTTER,
110 | @HeaderStyle @JvmField var headerStyle: Int = SectionConfig.DEFAULT_HEADER_STYLE,
111 | @FooterStyle @JvmField var footerStyle: Int = SectionConfig.DEFAULT_FOOTER_STYLE,
112 | paddingStart: Int = 0, paddingTop: Int = 0, paddingEnd: Int = 0, paddingBottom: Int = 0) {
113 | var gutterStart = 0
114 | get() = field
115 | set(value) {
116 | field = if (value < 0) GUTTER_AUTO else value
117 | }
118 | var gutterEnd = 0
119 | get() = field
120 | set(value) {
121 | field = if (value < 0) GUTTER_AUTO else value
122 | }
123 |
124 | var paddingLeft = 0
125 | var paddingTop = 0
126 | var paddingRight = 0
127 | var paddingBottom = 0
128 |
129 | init {
130 | this.gutterStart = gutterStart
131 | this.gutterEnd = gutterEnd
132 | this.paddingLeft = paddingStart
133 | this.paddingTop = paddingTop
134 | this.paddingRight = paddingEnd
135 | this.paddingBottom = paddingBottom
136 | }
137 |
138 | // Remap names since internally left and right are used since section coordinates are LTR, TTB. The start and
139 | // end intention will be applied correctly (from left and right) through the config transformations.
140 | internal var gutterLeft: Int
141 | get() = gutterStart
142 | set(value) {
143 | gutterStart = value
144 | }
145 | internal var gutterRight: Int
146 | get() = gutterEnd
147 | set(value) {
148 | gutterEnd = value
149 | }
150 |
151 | internal fun makeSection(oldState: SectionState? = null) = onMakeSection(oldState)
152 | abstract protected fun onMakeSection(oldState: SectionState?): SectionState
153 |
154 | /**
155 | * Copy the configuration. Section configs are always copied when they are passed to the layout manager.
156 | */
157 | fun copy(): SectionConfig {
158 | return onCopy()
159 | }
160 |
161 | abstract protected fun onCopy(): SectionConfig
162 |
163 | companion object {
164 | /**
165 | * Header is positioned at the head of the section content. Content starts below the header. Sticky headers
166 | * stick to the top of the layout area until the entire area has scrolled off the screen. Use HEADER_INLINE for
167 | * a header style which is otherwise the same without the sticky property.
168 | */
169 | const val HEADER_STICKY = 1
170 |
171 | /**
172 | * Header is positioned at the head of the section content. Content starts below the header, but the header
173 | * never becomes sticky. Linear headers can not float and ignores that flag if set.
174 | */
175 | const val HEADER_INLINE = 1 shl 1
176 |
177 | /**
178 | * Header is placed inside the gutter at the start edge of the section. This is the left for LTR locales.
179 | * Gutter headers are always sticky.
180 | */
181 | const val HEADER_START = 1 shl 2
182 |
183 | /**
184 | * Header is placed inside the gutter at the end edge of the section. This is the right for LTR locales.
185 | * Gutter headers are always sticky.
186 | */
187 | const val HEADER_END = 1 shl 3
188 |
189 | /**
190 | * Footer is positioned at the head of the section content. Content starts below the footer. Sticky footers
191 | * stick to the top of the layout area until the entire area has scrolled off the screen. Use FOOTER_INLINE for
192 | * a footer style which is otherwise the same without the sticky property.
193 | */
194 | const val FOOTER_STICKY = 1
195 |
196 | /**
197 | * Footer is positioned at the head of the section content. Content starts below the footer, but the footer
198 | * never becomes sticky. Linear footers can not float and ignores that flag if set.
199 | */
200 | const val FOOTER_INLINE = 1 shl 1
201 |
202 | /**
203 | * Footer is placed inside the gutter at the start edge of the section. This is the left for LTR locales.
204 | * Gutter footers are always sticky.
205 | */
206 | const val FOOTER_START = 1 shl 2
207 |
208 | /**
209 | * Footer is placed inside the gutter at the end edge of the section. This is the right for LTR locales.
210 | * Gutter footers are always sticky.
211 | */
212 | const val FOOTER_END = 1 shl 3
213 |
214 | const val GUTTER_AUTO = -1
215 |
216 | internal const val DEFAULT_GUTTER = GUTTER_AUTO
217 | internal const val DEFAULT_HEADER_STYLE = HEADER_STICKY
218 | internal const val DEFAULT_FOOTER_STYLE = FOOTER_STICKY
219 | }
220 | }
221 |
222 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/config_helpers.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.View
5 | import com.tonicartos.superslim.SectionConfig
6 |
7 | internal interface ConfigHelper : ReadWriteLayoutHelper {
8 | fun scrollBy(d: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State): Int
9 | }
10 |
11 | //internal class LtrConfigHelper(val base: ReadWriteLayoutHelper) : ReadWriteLayoutHelper by base {}
12 |
13 | internal class RtlConfigHelper(val base: ConfigHelper) : ConfigHelper by base {
14 | override fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int, marginLeft: Int, marginTop: Int,
15 | marginRight: Int, marginBottom: Int)
16 | = base.layout(view, layoutWidth - right, top, layoutWidth - left, bottom, marginRight, marginTop,
17 | marginLeft, marginBottom)
18 |
19 | override fun getLeft(child: View): Int = layoutWidth - base.getRight(child)
20 | override fun getRight(child: View): Int = layoutWidth - base.getLeft(child)
21 |
22 | override val basePaddingLeft: Int get() = base.basePaddingRight
23 | override val basePaddingRight: Int get() = base.basePaddingLeft
24 |
25 | override fun getTransformedPaddingLeft(sectionConfig: SectionConfig) = base.getTransformedPaddingRight(
26 | sectionConfig)
27 |
28 | override fun getTransformedPaddingRight(sectionConfig: SectionConfig) = base.getTransformedPaddingLeft(
29 | sectionConfig)
30 |
31 | override fun offsetChildrenHorizontal(dx: Int) = base.offsetChildrenHorizontal(-dx)
32 |
33 | override fun scrollBy(d: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State)
34 | = -base.scrollBy(-d, recycler, state)
35 |
36 | override fun toString(): String = "RtlConfigHelper(base = $base)"
37 | }
38 |
39 | internal class StackFromEndConfigHelper(val base: ConfigHelper) : ConfigHelper by base {
40 | override fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int, marginLeft: Int, marginTop: Int,
41 | marginRight: Int, marginBottom: Int)
42 | = base.layout(view, left, layoutLimit - bottom, right, layoutLimit - top, marginLeft, marginTop,
43 | marginRight, marginBottom)
44 |
45 | override fun getTop(child: View): Int = layoutLimit - base.getBottom(child)
46 | override fun getBottom(child: View): Int = layoutLimit - base.getTop(child)
47 |
48 | override val basePaddingTop: Int get() = base.basePaddingBottom
49 | override val basePaddingBottom: Int get() = base.basePaddingTop
50 |
51 | override fun getTransformedPaddingTop(sectionConfig: SectionConfig) = base.getTransformedPaddingBottom(
52 | sectionConfig)
53 |
54 | override fun getTransformedPaddingBottom(sectionConfig: SectionConfig) = base.getTransformedPaddingTop(
55 | sectionConfig)
56 |
57 | override fun offsetChildrenVertical(dy: Int) = base.offsetChildrenVertical(-dy)
58 | override fun offsetVertical(view: View, dy: Int) = base.offsetVertical(view, -dy)
59 |
60 | override fun scrollBy(d: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State)
61 | = -base.scrollBy(-d, recycler, state)
62 |
63 | override fun toString(): String = "StackFromEndConfigHelper(base = $base)"
64 | }
65 |
66 | internal class ReverseLayoutConfigHelper(val base: ConfigHelper) : ConfigHelper by base {
67 | override fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int, marginLeft: Int, marginTop: Int,
68 | marginRight: Int, marginBottom: Int)
69 | = base.layout(view, layoutWidth - right, layoutLimit - bottom, layoutWidth - left, layoutLimit - top,
70 | marginRight, marginTop, marginLeft, marginBottom)
71 |
72 | override fun getLeft(child: View): Int = layoutWidth - base.getRight(child)
73 | override fun getTop(child: View): Int = layoutLimit - base.getBottom(child)
74 | override fun getRight(child: View): Int = layoutWidth - base.getLeft(child)
75 | override fun getBottom(child: View): Int = layoutLimit - base.getTop(child)
76 |
77 | override val basePaddingLeft: Int get() = base.basePaddingRight
78 | override val basePaddingRight: Int get() = base.basePaddingLeft
79 | override val basePaddingTop: Int get() = base.basePaddingBottom
80 | override val basePaddingBottom: Int get() = base.basePaddingTop
81 |
82 | override fun getTransformedPaddingLeft(sectionConfig: SectionConfig) = base.getTransformedPaddingRight(
83 | sectionConfig)
84 |
85 | override fun getTransformedPaddingRight(sectionConfig: SectionConfig) = base.getTransformedPaddingLeft(
86 | sectionConfig)
87 |
88 | override fun getTransformedPaddingTop(sectionConfig: SectionConfig) = base.getTransformedPaddingBottom(
89 | sectionConfig)
90 |
91 | override fun getTransformedPaddingBottom(sectionConfig: SectionConfig) = base.getTransformedPaddingTop(
92 | sectionConfig)
93 |
94 | override fun offsetChildrenHorizontal(dx: Int) = base.offsetChildrenHorizontal(-dx)
95 | override fun offsetChildrenVertical(dy: Int) = base.offsetChildrenVertical(-dy)
96 | override fun offsetHorizontal(view: View, dx: Int) = base.offsetHorizontal(view, -dx)
97 | override fun offsetVertical(view: View, dy: Int) = base.offsetVertical(view, -dy)
98 |
99 | override fun scrollBy(d: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State)
100 | = -base.scrollBy(-d, recycler, state)
101 |
102 | override fun toString(): String = "ReverseLayoutConfigHelper(base = $base)"
103 | }
104 |
105 | //internal class VerticalConfigHelper(val base: ReadWriteLayoutHelper) : ReadWriteLayoutHelper by base {}
106 |
107 | internal class HorizontalConfigHelper(val base: ConfigHelper) : ConfigHelper by base {
108 | override fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int, marginLeft: Int, marginTop: Int,
109 | marginRight: Int, marginBottom: Int)
110 | = base.layout(view, top, left, bottom, right, marginLeft, marginTop, marginRight, marginBottom)
111 |
112 | override fun getLeft(child: View): Int = base.getTop(child)
113 | override fun getTop(child: View): Int = base.getLeft(child)
114 | override fun getRight(child: View): Int = base.getBottom(child)
115 | override fun getBottom(child: View): Int = base.getRight(child)
116 | override fun measure(view: View, usedWidth: Int, usedHeight: Int) = base.measure(view, usedHeight, usedWidth)
117 | override fun getMeasuredWidth(child: View): Int = base.getMeasuredHeight(child)
118 | override fun getMeasuredHeight(child: View): Int = base.getMeasuredWidth(child)
119 |
120 | override val basePaddingTop: Int get() = base.basePaddingLeft
121 | override val basePaddingLeft: Int get() = base.basePaddingTop
122 | override val basePaddingBottom: Int get() = base.basePaddingRight
123 | override val basePaddingRight: Int get() = base.basePaddingBottom
124 |
125 | override fun getTransformedPaddingTop(sectionConfig: SectionConfig) = base.getTransformedPaddingLeft(sectionConfig)
126 | override fun getTransformedPaddingLeft(sectionConfig: SectionConfig) = base.getTransformedPaddingTop(sectionConfig)
127 | override fun getTransformedPaddingBottom(sectionConfig: SectionConfig) = base.getTransformedPaddingRight(
128 | sectionConfig)
129 |
130 | override fun getTransformedPaddingRight(sectionConfig: SectionConfig) = base.getTransformedPaddingBottom(
131 | sectionConfig)
132 |
133 | override fun offsetChildrenHorizontal(dx: Int) = base.offsetChildrenVertical(dx)
134 | override fun offsetChildrenVertical(dy: Int) = base.offsetChildrenHorizontal(dy)
135 | override fun offsetHorizontal(view: View, dx: Int) = base.offsetVertical(view, dx)
136 | override fun offsetVertical(view: View, dy: Int) = base.offsetHorizontal(view, dy)
137 |
138 | override val layoutLimit get() = base.layoutWidth
139 | override val layoutWidth get() = base.layoutLimit
140 |
141 | override fun toString(): String = "HorizontalConfigHelper(base = $base)"
142 | }
143 |
144 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/extensions.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.View
5 | import java.util.*
6 |
7 | internal val View.rvLayoutParams: RecyclerView.LayoutParams
8 | get() = layoutParams as RecyclerView.LayoutParams
9 |
10 | inline fun , I, R> T.babushka(block: T.(I) -> R): R {
11 | val item = pop()
12 | val r = block(item)
13 | push(item)
14 | return r
15 | }
16 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/item_management.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import com.tonicartos.superslim.AdapterContract
5 |
6 | internal interface ItemManagement {
7 | fun applyChanges(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler)
8 | fun addItems(start: Int, count: Int)
9 | fun removeItems(start: Int, count: Int)
10 | fun moveItems(from: Int, to: Int, count: Int)
11 | }
12 |
13 | /**
14 | *
15 | */
16 | internal class ItemManager : ItemManagement {
17 | private val changes = ArrayList()
18 |
19 | override fun applyChanges(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler) {
20 | changes.forEach { it.apply(adapter, graph, recycler) }
21 | changes.clear()
22 | }
23 |
24 | override fun addItems(start: Int, count: Int) {
25 | changes.add(Add.acquire(start, count))
26 | }
27 |
28 | override fun removeItems(start: Int, count: Int) {
29 | changes.add(Remove.acquire(start, count))
30 | }
31 |
32 | override fun moveItems(from: Int, to: Int, count: Int) {
33 | changes.add(Move.acquire(from, to, count))
34 | }
35 | }
36 |
37 | private interface Op {
38 | fun apply(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler)
39 | }
40 |
41 | private data class Add(var start: Int, var count: Int) : Op {
42 | companion object {
43 | private val pool = arrayListOf()
44 |
45 | fun acquire(start: Int, count: Int) =
46 | if (pool.isEmpty()) {
47 | Add(start, count)
48 | } else {
49 | pool.removeAt(0).apply {
50 | this.start = start
51 | this.count = count
52 | }
53 | }
54 |
55 | fun release(obj: Add) {
56 | pool.add(obj)
57 | }
58 | }
59 |
60 | override fun apply(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler) {
61 | var currentSection = 0
62 | var currentStart = 0
63 | var currentCount = 0
64 | // Handle in chunks per section.
65 | for (i in start until start + count) {
66 | val about = adapter.getData(recycler.convertPreLayoutPositionToPostLayout(i))
67 | when {
68 | about.isHeader -> graph.addHeader(about.section)
69 | about.isFooter -> graph.addFooter(about.section)
70 | about.section == currentSection -> currentCount += 1
71 | else -> {
72 | graph.addItems(currentSection, currentStart, currentCount)
73 | currentSection = about.section
74 | currentStart = about.position
75 | currentCount = 1
76 | }
77 | }
78 |
79 | if (i == start + count - 1) {
80 | graph.addItems(currentSection, currentStart, currentCount)
81 | }
82 | }
83 | release(this)
84 | }
85 | }
86 |
87 | private data class Remove(var start: Int, var count: Int) : Op {
88 | companion object {
89 | private val pool = arrayListOf()
90 |
91 | fun acquire(start: Int, count: Int) =
92 | if (pool.isEmpty()) {
93 | Remove(start, count)
94 | } else {
95 | pool.removeAt(0).apply {
96 | this.start = start
97 | this.count = count
98 | }
99 | }
100 |
101 | fun release(obj: Remove) {
102 | pool.add(obj)
103 | }
104 | }
105 |
106 | override fun apply(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler) {
107 | graph.root.removeItems(start, count)
108 | release(this)
109 | }
110 | }
111 |
112 | private data class Move(var from: Int, var to: Int, var count: Int) : Op {
113 | companion object {
114 | private val pool = arrayListOf()
115 |
116 | fun acquire(from: Int, to: Int, count: Int) =
117 | if (pool.isEmpty()) {
118 | Move(from, to, count)
119 | } else {
120 | pool.removeAt(0).apply {
121 | this.from = from
122 | this.to = to
123 | this.count = count
124 | }
125 | }
126 |
127 | fun release(obj: Move) {
128 | pool.add(obj)
129 | }
130 | }
131 |
132 | override fun apply(adapter: AdapterContract<*>, graph: GraphManager, recycler: RecyclerView.Recycler) {
133 |
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/layout/generic.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal.layout
2 |
3 | import com.tonicartos.superslim.LayoutHelper
4 | import com.tonicartos.superslim.SectionLayoutManager
5 | import com.tonicartos.superslim.internal.SectionState
6 |
7 | /**
8 | * A Slm that does nothing. Used as a Flm or a Hlm for when there is no footer or header.
9 | */
10 | internal object DoNothingSlm : SectionLayoutManager {
11 | override fun isAtTop(section: SectionState, layoutState: SectionState.LayoutState) = section.atTop
12 |
13 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: SectionState.LayoutState) {
14 | section.layout(helper, section.leftGutter { 0 }, 0, helper.layoutWidth - section.rightGutter { 0 })
15 | layoutState.disappearedOrRemovedHeight += section.disappearedHeight
16 | layoutState.bottom = section.height
17 | layoutState.headPosition = 0
18 | layoutState.tailPosition = 0
19 | }
20 |
21 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState,
22 | layoutState: SectionState.LayoutState): Int {
23 | val filled = Math.min(dy, section.fillTop(dy, section.leftGutter { 0 }, 0,
24 | helper.layoutWidth - section.rightGutter { 0 }, helper))
25 | layoutState.bottom = section.height
26 | layoutState.headPosition = 0
27 | layoutState.tailPosition = 0
28 | return filled
29 | }
30 |
31 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState,
32 | layoutState: SectionState.LayoutState): Int {
33 | val filled = section.fillBottom(dy, section.leftGutter { 0 }, layoutState.bottom - section.height,
34 | helper.layoutWidth - section.rightGutter { 0 }, helper)
35 | layoutState.bottom = section.height
36 | layoutState.headPosition = 0
37 | layoutState.tailPosition = 0
38 | return filled
39 | }
40 |
41 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: SectionState,
42 | layoutState: SectionState.LayoutState)
43 | = section.trimTop(scrolled, 0, helper).also { layoutState.bottom = section.height }
44 |
45 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: SectionState,
46 | layoutState: SectionState.LayoutState)
47 | = section.trimBottom(scrolled, 0, helper).also { layoutState.bottom = section.height }
48 | }
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/layout/header.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal.layout
2 |
3 | import com.tonicartos.superslim.LayoutHelper
4 | import com.tonicartos.superslim.SectionConfig
5 | import com.tonicartos.superslim.SectionLayoutManager
6 | import com.tonicartos.superslim.internal.SectionState
7 | import com.tonicartos.superslim.internal.SectionState.HeaderLayoutState
8 | import com.tonicartos.superslim.internal.SectionState.LayoutState
9 | import com.tonicartos.superslim.internal.insetStickyStart
10 | import com.tonicartos.superslim.use
11 |
12 | private const val ABSENT = 1 shl 0
13 | private const val ADDED = 1 shl 1
14 | private const val FLOATING = 1 shl 2
15 |
16 | internal object HeaderLayoutManager : SectionLayoutManager {
17 | override fun isAtTop(section: SectionState, layoutState: LayoutState)
18 | = selectHeaderLayout(section).isAtTop(section, layoutState)
19 |
20 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: LayoutState)
21 | = selectHeaderLayout(section).onLayout(helper, section, layoutState)
22 |
23 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState)
24 | = selectHeaderLayout(section).onFillTop(dy, helper, section, layoutState)
25 |
26 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState)
27 | = selectHeaderLayout(section).onFillBottom(dy, helper, section, layoutState)
28 |
29 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState)
30 | = selectHeaderLayout(section).onTrimTop(scrolled, helper, section, layoutState)
31 |
32 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState)
33 | = selectHeaderLayout(section).onTrimBottom(scrolled, helper, section, layoutState)
34 |
35 | private fun selectHeaderLayout(section: SectionState): SectionLayoutManager {
36 | return DoNothingSlm.takeUnless { section.hasHeader } ?: when (section.baseConfig.headerStyle) {
37 | SectionConfig.HEADER_INLINE -> InlineHlm
38 | SectionConfig.HEADER_START, SectionConfig.HEADER_END -> GutterHlm
39 | else -> StickyHlm
40 | }
41 | }
42 | }
43 |
44 | private interface BaseHlm : SectionLayoutManager {
45 | override fun isAtTop(section: SectionState, layoutState: LayoutState)
46 | = layoutState.overdraw == 0 && layoutState.headPosition <= 0 && section.atTop
47 | }
48 |
49 | private object InlineHlm : BaseHlm {
50 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: LayoutState) {
51 | val state = layoutState as HeaderLayoutState
52 | var y = -state.overdraw
53 | if (state.headPosition <= 0) {
54 | helper.getHeader(section)?.use { header ->
55 | header.addToRecyclerView()
56 | header.measure()
57 | header.layout(0, y, header.measuredWidth, y + header.measuredHeight)
58 | if (helper.isPreLayout && header.isRemoved) helper.addIgnoredHeight(header.height)
59 | state.disappearedOrRemovedHeight += header.disappearedHeight
60 | y += header.height
61 | helper.filledArea += header.height
62 | state.mode = ADDED
63 | state.headPosition = 0
64 | state.tailPosition = 0
65 | }
66 | } else {
67 | state.mode = ABSENT
68 | state.headPosition = 1
69 | }
70 |
71 | section.layout(helper, section.leftGutter { 0 }, y, helper.layoutWidth - section.rightGutter { 0 },
72 | if (state.mode == ADDED) 1 else 0)
73 | state.disappearedOrRemovedHeight += section.disappearedHeight
74 | y += section.height
75 | helper.filledArea += section.height
76 | if (section.numViews > 0) state.tailPosition = 1
77 |
78 | state.bottom = y
79 | }
80 |
81 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
82 | val state = layoutState as HeaderLayoutState
83 | if (state.headPosition < 0) state.headPosition = 1
84 |
85 | if (state.headPosition == 1) {
86 | // Must fill section children first.
87 | state.overdraw += section.fillTop(Math.max(0, dy - state.overdraw), section.leftGutter { 0 },
88 | 0, helper.layoutWidth - section.rightGutter { 0 }, helper)
89 | state.tailPosition = 1
90 |
91 | // Fill header if there is space left.
92 | if (dy - state.overdraw > 0) {
93 | helper.getHeader(section)?.use { header ->
94 | header.addToRecyclerView(0)
95 | header.measure()
96 | state.overdraw += header.fillTop(dy - state.overdraw, 0,
97 | -state.overdraw - header.measuredHeight,
98 | header.measuredWidth,
99 | -state.overdraw)
100 | state.mode = ADDED
101 | state.headPosition = 0
102 | }
103 | }
104 | }
105 | val filled = Math.min(dy, state.overdraw)
106 | state.overdraw -= filled
107 | state.bottom += filled
108 | return filled
109 | }
110 |
111 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
112 | val state = layoutState as HeaderLayoutState
113 |
114 | var filled = if (state.tailPosition == 0) Math.max(0, state.bottom - helper.layoutLimit) else 0
115 | if (state.headPosition < 0) {
116 | helper.getHeader(section)?.use { header ->
117 | header.addToRecyclerView()
118 | header.measure()
119 | filled += header.fillBottom(dy, 0, -state.overdraw, header.measuredWidth,
120 | -state.overdraw + header.measuredHeight)
121 | state.bottom += header.height
122 | state.mode = ADDED
123 | state.headPosition = 0
124 | }
125 | }
126 |
127 | val before = section.height
128 | filled += section.fillBottom(dy - filled, section.leftGutter { 0 }, state.bottom - section.height,
129 | helper.layoutWidth - section.rightGutter { 0 }, helper,
130 | if (state.mode == ADDED) 1 else 0)
131 | state.bottom += section.height - before
132 | state.tailPosition = 1
133 | return Math.min(dy, filled)
134 | }
135 |
136 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
137 | val state = layoutState as HeaderLayoutState
138 | var removedHeight = 0
139 | var contentTop = 0
140 | if (state.mode == ADDED) {
141 | helper.getAttachedViewAt(0) { header ->
142 | if (header.bottom < 0) {
143 | header.remove()
144 | removedHeight += Math.max(0, header.height - state.overdraw)
145 | state.overdraw = Math.max(0, state.overdraw - header.height)
146 | state.headPosition = 1
147 | state.mode = ABSENT
148 | } else if (header.top < 0) {
149 | val before = state.overdraw
150 | state.overdraw = -header.top
151 | removedHeight += state.overdraw - before
152 | contentTop = header.bottom
153 | }
154 | }
155 | }
156 | removedHeight += section.trimTop(scrolled, contentTop, helper, if (state.mode == ADDED) 1 else 0)
157 | if (helper.numViews == 0) {
158 | state.headPosition = -1
159 | state.headPosition = -1
160 | }
161 | state.bottom -= removedHeight
162 | return removedHeight
163 | }
164 |
165 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: SectionState,
166 | layoutState: LayoutState): Int {
167 | val state = layoutState as HeaderLayoutState
168 | var removed = 0
169 | removed += section.trimBottom(scrolled, state.bottom - section.height, helper,
170 | if (state.mode == ADDED) 1 else 0)
171 | if (section.numViews == 0) {
172 | state.tailPosition = 0
173 | }
174 | if (state.mode == ADDED) {
175 | helper.getAttachedViewAt(0) { header ->
176 | if (header.top >= helper.layoutLimit) {
177 | removed += header.height
178 | header.remove()
179 | state.mode = ABSENT
180 | }
181 | }
182 | }
183 | if (helper.numViews == 0) {
184 | state.headPosition = -1
185 | state.tailPosition = -1
186 | }
187 | state.bottom -= removed
188 | return removed
189 | }
190 | }
191 |
192 | // Sticky headers are always attached after content.
193 | private object StickyHlm : BaseHlm {
194 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: LayoutState) {
195 | val state = layoutState as HeaderLayoutState
196 | var y = -state.overdraw
197 |
198 | helper.getHeader(section)?.use { header ->
199 | header.addToRecyclerView()
200 | header.measure()
201 | header.layout(0, y, header.measuredWidth, y + header.measuredHeight)
202 |
203 | if (state.headPosition <= 0) {
204 | if (helper.isPreLayout && header.isRemoved) helper.addIgnoredHeight(header.height)
205 | state.disappearedOrRemovedHeight += header.disappearedHeight
206 | y += header.height
207 | helper.filledArea += header.height
208 | state.mode = ADDED
209 | state.headPosition = 0
210 | state.tailPosition = 0
211 | }
212 |
213 | helper.insetStickyStart(header.measuredHeight) {
214 | section.layout(helper, section.leftGutter { 0 }, y,
215 | helper.layoutWidth - section.rightGutter { 0 })
216 | state.disappearedOrRemovedHeight += section.disappearedHeight
217 | y += section.height
218 | helper.filledArea += section.height
219 | state.headPosition = 0
220 | state.tailPosition = 0
221 | }
222 |
223 | if (state.mode == ABSENT) {
224 |
225 | }
226 |
227 | // Detect and adjust positioning to sticky or not.
228 | var bottom = y + header.measuredHeight
229 | val limit = helper.layoutLimit - helper.stickyStartInset
230 | val floatOffset = if (bottom > limit) limit - bottom else 0
231 | bottom += floatOffset
232 |
233 | header.layout(0, bottom - header.measuredHeight, header.measuredWidth, bottom, helper.numViews)
234 |
235 | // 100% floating header has 0 height.
236 | // val floatAdjustedHeight = Math.max(0, header.height + floatOffset)
237 | if (helper.isPreLayout && header.isRemoved) helper.addIgnoredHeight(header.height)
238 | helper.filledArea += header.height
239 | state.mode = FLOATING
240 | if (state.headPosition < 0) state.headPosition = 1
241 | state.tailPosition = 1
242 | y += header.height
243 | if (y < helper.layoutLimit) {
244 | state.mode = ADDED
245 | }
246 | }
247 |
248 | state.bottom = y
249 | }
250 |
251 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
252 | TODO("not implemented")
253 | }
254 |
255 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
256 | TODO("not implemented")
257 | }
258 |
259 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper,
260 | section: SectionState,
261 | layoutState: LayoutState): Int {
262 | TODO("not implemented")
263 | }
264 |
265 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper,
266 | section: SectionState,
267 | layoutState: LayoutState): Int {
268 | TODO("not implemented")
269 | }
270 | }
271 |
272 | private object GutterHlm : BaseHlm {
273 | override fun isAtTop(section: SectionState, layoutState: LayoutState) = section.atTop
274 |
275 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: LayoutState) {
276 | TODO("not implemented")
277 | }
278 |
279 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
280 | TODO("not implemented")
281 | }
282 |
283 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
284 | TODO("not implemented")
285 | }
286 |
287 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper,
288 | section: SectionState,
289 | layoutState: LayoutState): Int {
290 | TODO("not implemented")
291 | }
292 |
293 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper,
294 | section: SectionState,
295 | layoutState: LayoutState): Int {
296 | TODO("not implemented")
297 | }
298 | }
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/layout/padding.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal.layout
2 |
3 | import com.tonicartos.superslim.LayoutHelper
4 | import com.tonicartos.superslim.SectionLayoutManager
5 | import com.tonicartos.superslim.internal.SectionState
6 | import com.tonicartos.superslim.internal.SectionState.LayoutState
7 | import com.tonicartos.superslim.internal.SectionState.PaddingLayoutState
8 | import com.tonicartos.superslim.internal.SectionState.PaddingLayoutState.Companion.BOTTOM_ADDED
9 | import com.tonicartos.superslim.internal.SectionState.PaddingLayoutState.Companion.TOP_ADDED
10 |
11 | internal object PaddingLayoutManager : SectionLayoutManager {
12 | override fun isAtTop(section: SectionState, layoutState: LayoutState): Boolean {
13 | val state = layoutState as PaddingLayoutState
14 | return state.overdraw == 0 && state.paddingTop > 0 || section.atTop
15 | }
16 |
17 | override fun onLayout(helper: LayoutHelper, section: SectionState, layoutState: LayoutState) {
18 | val state = layoutState as PaddingLayoutState
19 | state.paddingTop = helper.paddingTop
20 | state.paddingBottom = helper.paddingBottom
21 |
22 | if (state.paddingTop > 0) {
23 | if (state.onScreen && state flagUnset TOP_ADDED && state.overdraw > 0) {
24 | // Must be in a layout pass with requested position.
25 | state set TOP_ADDED
26 | } else if (!state.onScreen) {
27 | state.overdraw = 0
28 | state set TOP_ADDED
29 | }
30 | }
31 | state.onScreen = true
32 |
33 | var y = if (state flagSet TOP_ADDED) state.paddingTop - state.overdraw else 0
34 |
35 | section.layout(helper, section.leftGutter { 0 }, y, helper.layoutWidth - section.rightGutter { 0 })
36 | state.disappearedOrRemovedHeight += section.disappearedHeight
37 | y += section.height
38 | helper.filledArea += section.height
39 |
40 | if (state.paddingBottom > 0 && y < helper.layoutLimit) {
41 | state set BOTTOM_ADDED
42 | y += helper.paddingBottom
43 | }
44 |
45 | state.bottom = y
46 | }
47 |
48 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
49 | val state = layoutState as PaddingLayoutState
50 |
51 | var toFill = dy - state.overdraw
52 |
53 | if (state.paddingBottom > 0 && !state.onScreen) {
54 | state.paddingTop = helper.paddingTop
55 | state.paddingBottom = helper.paddingBottom
56 |
57 | // Add bottom padding.
58 | val filled = state.paddingBottom
59 | state.overdraw += filled
60 | toFill -= filled
61 | state set BOTTOM_ADDED
62 | }
63 | state.onScreen = true
64 |
65 | // Add content.
66 | state.overdraw += section.fillTop(Math.max(0, toFill), section.leftGutter { 0 }, -state.overdraw,
67 | helper.layoutWidth - section.rightGutter { 0 }, helper)
68 |
69 | if (state.paddingTop > 0 && state flagUnset TOP_ADDED && state.overdraw < dy) {
70 | // Add top padding.
71 | state.overdraw += state.paddingTop
72 | state set TOP_ADDED
73 | }
74 |
75 | val filled = Math.min(dy, state.overdraw)
76 | state.overdraw -= filled
77 | state.bottom += filled
78 | return filled
79 | }
80 |
81 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
82 | val state = layoutState as PaddingLayoutState
83 | var filled = 0
84 |
85 | if (state.paddingTop > 0 && !state.onScreen) {
86 | state.paddingBottom = helper.paddingBottom
87 | state.paddingTop = helper.paddingTop
88 |
89 | // Add top padding.
90 | filled += state.paddingTop
91 | state.bottom = state.paddingTop
92 | state.overdraw = 0
93 | state set TOP_ADDED
94 | }
95 | state.onScreen = true
96 |
97 | val y = if (state flagSet TOP_ADDED) state.paddingTop - state.overdraw else 0
98 |
99 | // Add content
100 | val before = section.height
101 | filled += section.fillBottom(dy, section.leftGutter { 0 }, y, helper.layoutWidth - section.rightGutter { 0 },
102 | helper)
103 | state.bottom += section.height - before
104 |
105 | if (state.paddingBottom > 0 && filled < dy) {
106 | if (state flagUnset BOTTOM_ADDED) {
107 | // Add bottom padding.
108 | filled += state.paddingBottom
109 | state.bottom += state.paddingBottom
110 | state set BOTTOM_ADDED
111 | } else {
112 | filled += Math.max(0, state.bottom - helper.layoutLimit)
113 | }
114 | }
115 |
116 | return Math.min(dy, filled)
117 | }
118 |
119 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: SectionState, layoutState: LayoutState): Int {
120 | val state = layoutState as PaddingLayoutState
121 |
122 | var removedHeight = 0
123 | var contentTop = 0
124 |
125 | if (state flagSet TOP_ADDED) {
126 | val before = state.overdraw
127 | state.overdraw = Math.min(state.paddingTop, state.overdraw + scrolled)
128 | removedHeight += state.overdraw - before
129 |
130 | // Do padding top.
131 | if (state.overdraw >= state.paddingTop) {
132 | state.overdraw = 0
133 | state unset TOP_ADDED
134 | } else {
135 | contentTop = state.paddingTop - state.overdraw
136 | }
137 | }
138 |
139 | removedHeight += section.trimTop(scrolled, contentTop, helper)
140 |
141 | if (helper.numViews == 0 && state flagSet BOTTOM_ADDED) {
142 | val before = state.overdraw
143 | state.overdraw = Math.min(state.paddingBottom, state.overdraw + (scrolled - removedHeight))
144 | removedHeight += state.overdraw - before
145 |
146 | // Do padding bottom.
147 | if (state.bottom < 0) {
148 | state.overdraw = 0
149 | state unset BOTTOM_ADDED
150 | state.onScreen = false
151 | }
152 | }
153 |
154 | state.bottom -= removedHeight
155 | return removedHeight
156 | }
157 |
158 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: SectionState,
159 | layoutState: LayoutState): Int {
160 | val state = layoutState as PaddingLayoutState
161 | var removedHeight = 0
162 | if (state flagSet BOTTOM_ADDED) {
163 | // Do padding bottom.
164 | if (state.bottom - state.paddingBottom > helper.layoutLimit) {
165 | removedHeight += state.paddingBottom
166 | state unset BOTTOM_ADDED
167 | }
168 | }
169 |
170 | val contentTop = if (state flagSet TOP_ADDED) state.paddingTop - state.overdraw else 0
171 |
172 | // Do content.
173 | removedHeight += section.trimBottom(scrolled - removedHeight, contentTop, helper)
174 |
175 | if (state flagSet TOP_ADDED) {
176 | // Do padding top.
177 | if (helper.layoutLimit < 0) {
178 | removedHeight += state.paddingBottom
179 | state unset TOP_ADDED
180 | state.onScreen = false
181 | }
182 | }
183 | state.bottom -= removedHeight
184 | return removedHeight
185 | }
186 | }
187 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/internal/layout_helpers.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.internal
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.View
5 | import com.tonicartos.superslim.LayoutHelper
6 | import com.tonicartos.superslim.SectionConfig
7 | import com.tonicartos.superslim.internal.SectionState.LayoutState
8 |
9 | internal class RootLayoutHelper(val manager: ManagerHelper, val config: ReadWriteLayoutHelper,
10 | val recycler: RecyclerHelper, val state: StateHelper) :
11 | BaseLayoutHelper, ManagerHelper by manager, ReadWriteLayoutHelper by config, RecyclerHelper by recycler,
12 | StateHelper by state {
13 | companion object {
14 | private var helperPool = LayoutHelperPool()
15 | }
16 |
17 | internal fun acquireSubsectionHelper(y: Int, left: Int, right: Int, paddingTop: Int, paddingBottom: Int,
18 | viewsBefore: Int, layoutState: LayoutState,
19 | tellParentViewsChangedBy: (Int) -> Unit,
20 | tellParentAboutTemporaryView: (Int) -> Unit)
21 | = helperPool.acquire(this, left, y, right - left, paddingTop, paddingBottom, viewsBefore, layoutState,
22 | tellParentViewsChangedBy, tellParentAboutTemporaryView)
23 |
24 | inline fun useSubsectionHelper(y: Int, left: Int, right: Int, paddingTop: Int, paddingBottom: Int,
25 | viewsBefore: Int, layoutState: LayoutState, block: (LayoutHelper) -> T): T {
26 | val helper = acquireSubsectionHelper(y, left, right, paddingTop, paddingBottom, viewsBefore, layoutState,
27 | {}, {})
28 | val r = block(helper)
29 | helper.release()
30 | return r
31 | }
32 |
33 | fun releaseSubsectionHelper(helper: LayoutHelper) {
34 | helperPool.release(helper)
35 | }
36 |
37 | private var layoutLimitExtension = 0
38 | override val layoutLimit get() = config.layoutLimit + layoutLimitExtension
39 |
40 | override fun addIgnoredHeight(ignoredHeight: Int) {
41 | layoutLimitExtension += ignoredHeight
42 | }
43 |
44 | override var stickyStartInset = 0
45 | override var stickyEndInset = 0
46 |
47 | override fun toString(): String = "RootHelper(ignoredHeight = $layoutLimitExtension, layoutLimit = $layoutLimit, layoutWidth = $layoutWidth, \nconfig = $config,\nstate = $state)\n"
48 | .replace("\n", "\n\t")
49 |
50 | private class LayoutHelperPool {
51 | private val pool = arrayListOf()
52 |
53 | fun acquire(root: RootLayoutHelper, x: Int, y: Int, width: Int, paddingTop: Int, paddingBottom: Int,
54 | viewsBefore: Int, layoutState: LayoutState, tellParentViewsChangedBy: (Int) -> Unit,
55 | tellParentAboutTemporaryView: (Int) -> Unit) =
56 | if (pool.isEmpty()) {
57 | LayoutHelper(root, x, y, width, paddingTop, paddingBottom, viewsBefore, layoutState,
58 | tellParentViewsChangedBy, tellParentAboutTemporaryView)
59 | } else {
60 | pool.removeAt(0).reInit(root, x, y, width, paddingTop, paddingBottom, viewsBefore, layoutState,
61 | tellParentViewsChangedBy, tellParentAboutTemporaryView)
62 | }
63 |
64 | fun release(helper: LayoutHelper) {
65 | pool.add(helper)
66 | }
67 | }
68 | }
69 |
70 | /**
71 | * Encapsulates the recycler into the layout helper chain.
72 | */
73 | internal class RecyclerWrapper : RecyclerHelper {
74 | lateinit var recycler: RecyclerView.Recycler
75 |
76 | fun wrap(recycler: RecyclerView.Recycler): RecyclerWrapper {
77 | this.recycler = recycler
78 | return this
79 | }
80 |
81 | override fun getView(position: Int): View = recycler.getViewForPosition(position)
82 |
83 | override val scrap: List
84 | get() = recycler.scrapList
85 |
86 | override fun removeView(child: View, helper: LayoutHelper) {
87 | helper.removeView(child, recycler)
88 | }
89 | }
90 |
91 | /**
92 | * Encapsulates the recycler view state into the layout helper chain.
93 | */
94 | internal class StateWrapper : StateHelper {
95 | lateinit var state: RecyclerView.State
96 |
97 | fun wrap(state: RecyclerView.State): StateWrapper {
98 | this.state = state
99 | return this
100 | }
101 |
102 | override val hasTargetScrollPosition: Boolean
103 | get() = state.hasTargetScrollPosition()
104 |
105 | override val targetScrollPosition: Int
106 | get() = state.targetScrollPosition
107 |
108 | override val willRunPredictiveAnimations: Boolean
109 | get() = state.willRunPredictiveAnimations()
110 |
111 | override val isPreLayout: Boolean
112 | get() = state.isPreLayout
113 |
114 | override val itemCount: Int
115 | get() = state.itemCount
116 |
117 | override fun toString(): String = "State(itemCount = $itemCount, isPreLayout = $isPreLayout, willRunPredictiveAnimations = $willRunPredictiveAnimations)"
118 | }
119 |
120 | /****************************************************
121 | * This is the set of interfaces which collectively
122 | * express the hierarchy of delegates which make up
123 | * the bulk of [LayoutHelper]'s functionality.
124 | ****************************************************/
125 |
126 | internal interface BaseLayoutHelper : ManagerHelper, StateHelper, RecyclerHelper, ReadWriteLayoutHelper {
127 | fun addIgnoredHeight(ignoredHeight: Int)
128 | }
129 |
130 | internal interface ManagerHelper {
131 | fun addView(child: View)
132 | fun addView(child: View, index: Int)
133 | fun addDisappearingView(child: View)
134 | fun addDisappearingView(child: View, index: Int)
135 | fun addTemporaryView(child: View)
136 | fun addTemporaryView(child: View, index: Int)
137 |
138 | fun removeView(child: View, recycler: RecyclerView.Recycler)
139 |
140 | val supportsPredictiveItemAnimations: Boolean
141 | }
142 |
143 | internal interface StateHelper {
144 | val isPreLayout: Boolean
145 | val willRunPredictiveAnimations: Boolean
146 | val itemCount: Int
147 | val hasTargetScrollPosition: Boolean
148 | val targetScrollPosition: Int
149 | }
150 |
151 | internal interface RecyclerHelper {
152 | fun getView(position: Int): View
153 | val scrap: List
154 | fun removeView(child: View, helper: LayoutHelper)
155 | }
156 |
157 | internal interface ReadWriteLayoutHelper : ReadLayoutHelper, WriteLayoutHelper {
158 | val basePaddingLeft: Int
159 | val basePaddingTop: Int
160 | val basePaddingRight: Int
161 | val basePaddingBottom: Int
162 |
163 | fun attachViewToPosition(position: Int, view: View)
164 | fun detachViewAtPosition(position: Int): View?
165 |
166 | fun getTransformedPaddingLeft(sectionConfig: SectionConfig): Int
167 | fun getTransformedPaddingTop(sectionConfig: SectionConfig): Int
168 | fun getTransformedPaddingRight(sectionConfig: SectionConfig): Int
169 | fun getTransformedPaddingBottom(sectionConfig: SectionConfig): Int
170 | }
171 |
172 | class AttachedView private constructor(private var view: View, private var helper: LayoutHelper) {
173 | companion object {
174 | private val pool = ArrayList()
175 | internal fun wrap(view: View, helper: LayoutHelper) = if (pool.size >= 1) pool.removeAt(0).apply {
176 | this.view = view
177 | this.helper = helper
178 | } else AttachedView(view, helper)
179 | }
180 |
181 | fun done() {
182 | pool.add(this)
183 | }
184 |
185 | val left get() = helper.getLeft(this.view)
186 | val top get() = helper.getTop(this.view)
187 | val right get() = helper.getRight(this.view)
188 | val bottom get() = helper.getBottom(this.view)
189 | val width get() = helper.getMeasuredWidth(this.view)
190 | val height get() = helper.getMeasuredHeight(this.view)
191 |
192 | fun remove() = helper.removeView(this.view, helper)
193 | fun offsetTopAndBottom(offset: Int) = helper.offsetVertical(this.view, offset)
194 | fun offsetLeftAndRight(offset: Int) = helper.offsetHorizontal(this.view, offset)
195 | }
196 |
197 | internal interface ReadLayoutHelper {
198 | fun getLeft(child: View): Int
199 | fun getTop(child: View): Int
200 | fun getRight(child: View): Int
201 | fun getBottom(child: View): Int
202 | fun getMeasuredWidth(child: View): Int
203 | fun getMeasuredHeight(child: View): Int
204 |
205 | fun getAttachedRawView(position: Int): View
206 |
207 | /**
208 | * Width of the layout area.
209 | */
210 | val layoutWidth: Int
211 |
212 | /**
213 | * Y limit that constrains the layout. This is used to know when to stop laying out items, and is nominally the
214 | * maximum height of the visible layout area.
215 | *
216 | * **Warning**: This value can change, and as such, should not be stored.
217 | */
218 | val layoutLimit: Int
219 | var stickyStartInset: Int
220 | var stickyEndInset: Int
221 | }
222 |
223 | internal inline fun ReadLayoutHelper.insetStickyStart(inset: Int, block: () -> R): R {
224 | stickyStartInset += inset
225 | val r = block()
226 | stickyStartInset -= inset
227 | return r
228 | }
229 |
230 | internal inline fun ReadLayoutHelper.insetStickyEnd(inset: Int, block: () -> R): R {
231 | stickyEndInset += inset
232 | val r = block()
233 | stickyEndInset -= inset
234 | return r
235 | }
236 |
237 | internal interface WriteLayoutHelper {
238 | fun measure(view: View, usedWidth: Int = 0, usedHeight: Int = 0)
239 | fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int, marginLeft: Int = 0, marginTop: Int = 0,
240 | marginRight: Int = 0, marginBottom: Int = 0)
241 |
242 | fun offsetVertical(view: View, dy: Int)
243 | fun offsetHorizontal(view: View, dx: Int)
244 | fun offsetChildrenVertical(dy: Int)
245 | fun offsetChildrenHorizontal(dx: Int)
246 | }
247 |
248 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/layout.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.View
5 | import com.tonicartos.superslim.internal.AttachedView
6 | import com.tonicartos.superslim.internal.BaseLayoutHelper
7 | import com.tonicartos.superslim.internal.RootLayoutHelper
8 | import com.tonicartos.superslim.internal.SectionState
9 | import com.tonicartos.superslim.internal.SectionState.LayoutState
10 |
11 | interface SectionLayoutManager {
12 |
13 | /**
14 | * Check if the section is at the top.
15 | */
16 | fun isAtTop(section: T, layoutState: LayoutState): Boolean
17 |
18 | /**
19 | * Layout the section. Layout pass may be pre, post, or normal.
20 | *
21 | * @param helper Layout helper.
22 | * @param section The section to lay out.
23 | * @param layoutState In/out layout state.
24 | */
25 | fun onLayout(helper: LayoutHelper, section: T, layoutState: LayoutState)
26 |
27 | /**
28 | * Fill distance dy at top of the section. The layout state may already contain some filled distance recorded as
29 | * overdraw.
30 | *
31 | * @param dy Distance to fill.
32 | * @param helper Layout helper.
33 | * @param section Section to fill.
34 | * @param layoutState In/out layout state.
35 | *
36 | * @return How much filled. Valid values are 0 to dy.
37 | */
38 | fun onFillTop(dy: Int, helper: LayoutHelper, section: T, layoutState: LayoutState): Int
39 |
40 | /**
41 | * Fill distance dy at bottom of the section. The layout state may already contain some filled distance recorded as
42 | * overdraw.
43 | *
44 | * @param dy Distance to fill.
45 | * @param helper Layout helper.
46 | * @param section Section to fill.
47 | * @param layoutState In/out layout state.
48 | *
49 | * @return How much filled. Valid values are 0 to dy.
50 | */
51 | fun onFillBottom(dy: Int, helper: LayoutHelper, section: T, layoutState: LayoutState): Int
52 |
53 | /**
54 | * Remove views managed by this SectionLayoutManager that are before 0. Remember to update layoutState values accordingly.
55 | *
56 | * @return Height removed from section.
57 | */
58 | fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: T, layoutState: LayoutState): Int
59 |
60 | /**
61 | * Remove views managed by this SectionLayoutManager that are after [LayoutHelper.layoutLimit]. Remember to update
62 | * state values appropriately.
63 | *
64 | * @return Height removed from section.
65 | */
66 | fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: T, layoutState: LayoutState): Int
67 | }
68 |
69 | class LayoutHelper private constructor(private var root: RootLayoutHelper,
70 | private var tellParentViewsChangedBy: (Int) -> Unit,
71 | private var tellParentAboutTemporaryView: (Int) -> Unit) : BaseLayoutHelper {
72 | internal constructor(root: RootLayoutHelper, x: Int, y: Int, width: Int, paddingTop: Int, paddingBottom: Int,
73 | viewsBefore: Int, layoutState: LayoutState, tellParentViewsChanged: (Int) -> Unit,
74 | tellParentAboutTemporaryView: (Int) -> Unit) :
75 | this(root, tellParentViewsChanged, tellParentAboutTemporaryView) {
76 | offset.x = x
77 | offset.y = y
78 | this.width = width
79 | this.paddingTop = paddingTop
80 | this.paddingBottom = paddingBottom
81 | this.viewsBefore = viewsBefore
82 | this.layoutState = layoutState
83 | }
84 |
85 | /*************************
86 | * Init stuff
87 | *************************/
88 |
89 | internal fun reInit(root: RootLayoutHelper, x: Int, y: Int, width: Int, paddingTop: Int, paddingBottom: Int,
90 | viewsBefore: Int, layoutState: LayoutState, tellParentViewsChangedBy: (Int) -> Unit,
91 | tellParentAboutDisappearedView: (Int) -> Unit)
92 | : LayoutHelper {
93 | this.root = root
94 | offset.x = x
95 | offset.y = y
96 | this.width = width
97 | filledArea = 0
98 | this.paddingTop = paddingTop
99 | this.paddingBottom = paddingBottom
100 | this.viewsBefore = viewsBefore
101 | this.layoutState = layoutState
102 | this.tellParentViewsChangedBy = tellParentViewsChangedBy
103 | this.tellParentAboutTemporaryView = tellParentAboutDisappearedView
104 | return this
105 | }
106 |
107 | private fun acquireSubsectionHelper(y: Int, left: Int, right: Int, paddingTop: Int, paddingBottom: Int,
108 | viewsBefore: Int, layoutState: LayoutState): LayoutHelper
109 | = root.acquireSubsectionHelper(offset.y + y, offset.x + left, offset.x + right,
110 | paddingTop, paddingBottom, viewsBefore, layoutState,
111 | this::viewsChangedBy, this::temporaryViewsChangedBy)
112 |
113 | internal fun release() {
114 | root.releaseSubsectionHelper(this)
115 | }
116 |
117 | internal inline fun useSubsectionHelper(y: Int, left: Int, right: Int, paddingTop: Int, paddingBottom: Int,
118 | viewsBefore: Int, layoutState: LayoutState,
119 | block: (LayoutHelper) -> T): T {
120 | val helper = acquireSubsectionHelper(y, left, right, paddingTop, paddingBottom, viewsBefore, layoutState)
121 | val r = block(helper)
122 | helper.release()
123 | return r
124 | }
125 |
126 | /*************************
127 | * layout stuff
128 | *************************/
129 |
130 | private var offset = Offset()
131 | private var width = 0
132 |
133 | internal var paddingTop = 0
134 | private set
135 | internal var paddingBottom = 0
136 | private set
137 |
138 | internal var viewsBefore = 0
139 | private set
140 |
141 | private lateinit var layoutState: LayoutState
142 |
143 | var numViews get() = layoutState.numViews
144 | private set(value) {
145 | viewsChangedBy(value - layoutState.numViews)
146 | }
147 |
148 | var numTemporaryViews get() = layoutState.numTemporaryViews
149 | private set(value) {
150 | temporaryViewsChangedBy(value - layoutState.numTemporaryViews)
151 | }
152 |
153 | internal fun viewsChangedBy(delta: Int) {
154 | layoutState.numViews += delta
155 | tellParentViewsChangedBy(delta)
156 | }
157 |
158 | private fun temporaryViewsChangedBy(delta: Int) {
159 | layoutState.numViews += delta
160 | layoutState.numTemporaryViews += delta
161 | tellParentAboutTemporaryView(delta)
162 | }
163 |
164 | override val layoutWidth get() = width
165 | override val layoutLimit get() = root.layoutLimit - offset.y
166 |
167 | override fun getLeft(child: View): Int = root.getLeft(child) - offset.x
168 | override fun getRight(child: View): Int = root.getRight(child) - offset.x
169 | override fun getTop(child: View): Int = root.getTop(child) - offset.y
170 | override fun getBottom(child: View): Int = root.getBottom(child) - offset.y
171 |
172 | override fun layout(view: View, left: Int, top: Int, right: Int, bottom: Int,
173 | marginLeft: Int, marginTop: Int, marginRight: Int, marginBottom: Int) {
174 | root.layout(view, offset.x + left, offset.y + top, offset.x + right, offset.y + bottom,
175 | marginLeft, marginTop, marginRight, marginBottom)
176 | }
177 |
178 | override fun measure(view: View, usedWidth: Int, usedHeight: Int) {
179 | root.measure(view, usedWidth + root.layoutWidth - width, usedHeight + offset.y)
180 | }
181 |
182 | var filledArea: Int = 0
183 |
184 | private val willCheckForDisappearedItems get() = !isPreLayout && willRunPredictiveAnimations && supportsPredictiveItemAnimations
185 |
186 | internal fun getHeader(section: SectionState): Child? {
187 | return if (filledArea >= layoutLimit && willCheckForDisappearedItems && section.hasDisappearedItemsToLayOut) {
188 | section.getDisappearingHeader(this)
189 | } else {
190 | section.getHeader(this)
191 | }
192 | }
193 |
194 | internal fun getFooter(section: SectionState): Child? {
195 | return if (filledArea >= layoutLimit && willCheckForDisappearedItems && section.hasDisappearedItemsToLayOut) {
196 | section.getDisappearingFooter(this)
197 | } else {
198 | section.getFooter(this)
199 | }
200 | }
201 |
202 | fun getUnfinishedChild(position: Int, section: SectionState) = section.getNonFinalChildAt(this, position)
203 |
204 | fun getChild(currentPosition: Int, section: SectionState): Child? {
205 | if (currentPosition < section.numChildren) {
206 | if (filledArea >= layoutLimit && willCheckForDisappearedItems && section.hasDisappearedItemsToLayOut) {
207 | return section.getDisappearingChildAt(this, currentPosition)
208 | } else {
209 | return section.getChildAt(this, currentPosition)
210 | }
211 | }
212 | return null
213 | }
214 |
215 | fun moreToLayout(currentPosition: Int, section: SectionState): Boolean {
216 | if (currentPosition < section.numChildren) {
217 | if (filledArea >= layoutLimit) {
218 | return willCheckForDisappearedItems && section.hasDisappearedItemsToLayOut
219 | }
220 | return true
221 | }
222 | return false
223 | }
224 |
225 | private val SectionState.hasDisappearedItemsToLayOut: Boolean get() {
226 | return scrap.any { it in this }
227 | }
228 |
229 | internal fun scrapHasPosition(position: Int): Boolean {
230 | return scrap.any { it.layoutPosition == position }
231 | }
232 |
233 | override fun getView(position: Int) = root.getView(position)
234 |
235 | override fun removeView(child: View, recycler: RecyclerView.Recycler) {
236 | root.removeView(child, recycler)
237 | numViews -= 1
238 | }
239 |
240 | override fun addView(child: View) {
241 | addView(child, -1)
242 | }
243 |
244 | override fun addView(child: View, index: Int) {
245 | val dest = if (index == -1) numViews else index
246 | root.addView(child, viewsBefore + dest)
247 | numViews += 1
248 | }
249 |
250 | override fun addDisappearingView(child: View) {
251 | addDisappearingView(child, -1)
252 | }
253 |
254 | override fun addDisappearingView(child: View, index: Int) {
255 | val dest = if (index == -1) numViews else index
256 | root.addDisappearingView(child, viewsBefore + dest)
257 | numTemporaryViews += 1
258 | }
259 |
260 | override fun addTemporaryView(child: View) {
261 | addTemporaryView(child, -1)
262 | }
263 |
264 | override fun addTemporaryView(child: View, index: Int) {
265 | val dest = if (index == -1) numViews else index
266 | root.addTemporaryView(child, viewsBefore + dest)
267 | numTemporaryViews += 1
268 | }
269 |
270 | override fun getAttachedRawView(position: Int) = root.getAttachedRawView(viewsBefore + position)
271 |
272 | fun getAttachedViewAt(position: Int) = AttachedView.wrap(getAttachedRawView(position), this)
273 | inline fun getAttachedViewAt(position: Int, block: (AttachedView) -> R): R
274 | = getAttachedViewAt(position).let { view -> block(view).also { view.done() } }
275 |
276 | override fun attachViewToPosition(position: Int, view: View) {
277 | numViews += 1
278 | root.attachViewToPosition(viewsBefore + position, view)
279 | }
280 |
281 | override fun detachViewAtPosition(position: Int): View? {
282 | numViews -= 1
283 | return root.detachViewAtPosition(viewsBefore + position)
284 | }
285 |
286 | override fun toString(): String = "SubsectionHelper($offset, width = $width, limit = $layoutLimit, views before = $viewsBefore, root = \n$root)".replace(
287 | "\n", "\n\t")
288 |
289 | private data class Offset(var x: Int = 0, var y: Int = 0)
290 |
291 | /********************************************************
292 | * Delegated stuff
293 | *******************************************************/
294 | override fun addIgnoredHeight(ignoredHeight: Int) {
295 | root.addIgnoredHeight(ignoredHeight)
296 | layoutState.disappearedOrRemovedHeight += ignoredHeight
297 | }
298 |
299 | override val supportsPredictiveItemAnimations get() = root.supportsPredictiveItemAnimations
300 | override val isPreLayout: Boolean get() = root.isPreLayout
301 | override val willRunPredictiveAnimations: Boolean get() = root.willRunPredictiveAnimations
302 | override val itemCount get() = root.itemCount
303 | override val hasTargetScrollPosition get() = root.hasTargetScrollPosition
304 | override val targetScrollPosition get() = root.targetScrollPosition
305 | override val scrap get() = root.scrap
306 | override val basePaddingLeft get() = root.basePaddingLeft
307 | override val basePaddingTop get() = root.basePaddingTop
308 | override val basePaddingRight get() = root.basePaddingRight
309 | override val basePaddingBottom get() = root.basePaddingBottom
310 | override fun getTransformedPaddingLeft(sectionConfig: SectionConfig) = root.getTransformedPaddingLeft(sectionConfig)
311 | override fun getTransformedPaddingTop(sectionConfig: SectionConfig) = root.getTransformedPaddingTop(sectionConfig)
312 | override fun getTransformedPaddingRight(sectionConfig: SectionConfig)
313 | = root.getTransformedPaddingRight(sectionConfig)
314 |
315 | override fun getTransformedPaddingBottom(sectionConfig: SectionConfig)
316 | = root.getTransformedPaddingBottom(sectionConfig)
317 |
318 | override fun getMeasuredWidth(child: View): Int = root.getMeasuredWidth(child)
319 | override fun getMeasuredHeight(child: View): Int = root.getMeasuredHeight(child)
320 | override var stickyStartInset get() = root.stickyStartInset
321 | set(value) {
322 | root.stickyStartInset = value
323 | }
324 | override var stickyEndInset get() = root.stickyEndInset
325 | set(value) {
326 | root.stickyEndInset = value
327 | }
328 |
329 | override fun offsetChildrenVertical(dy: Int) = root.offsetChildrenVertical(dy)
330 | override fun offsetChildrenHorizontal(dx: Int) = root.offsetChildrenHorizontal(dx)
331 | override fun offsetHorizontal(view: View, dx: Int) = root.offsetHorizontal(view, dx)
332 | override fun offsetVertical(view: View, dy: Int) = root.offsetVertical(view, dy)
333 | override fun removeView(child: View, helper: LayoutHelper) = root.removeView(child, helper)
334 | }
335 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/layout/flexbox.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.layout
2 |
3 | import com.tonicartos.superslim.LayoutHelper
4 | import com.tonicartos.superslim.SectionConfig
5 | import com.tonicartos.superslim.SectionLayoutManager
6 | import com.tonicartos.superslim.adapter.FooterStyle
7 | import com.tonicartos.superslim.adapter.HeaderStyle
8 | import com.tonicartos.superslim.internal.SectionState
9 | import com.tonicartos.superslim.internal.SectionState.LayoutState
10 |
11 | class FlexboxSectionConfig(gutterStart: Int = SectionConfig.DEFAULT_GUTTER,
12 | gutterEnd: Int = SectionConfig.DEFAULT_GUTTER,
13 | @HeaderStyle headerStyle: Int = SectionConfig.DEFAULT_HEADER_STYLE,
14 | @FooterStyle footerStyle: Int = SectionConfig.DEFAULT_FOOTER_STYLE,
15 | paddingStart: Int = 0, paddingTop: Int = 0, paddingEnd: Int = 0, paddingBottom: Int = 0) :
16 | SectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle, paddingStart, paddingTop, paddingEnd,
17 | paddingBottom) {
18 |
19 | override fun onMakeSection(oldState: SectionState?): SectionState = FlexboxSectionState(this, oldState)
20 |
21 | override fun onCopy(): FlexboxSectionConfig {
22 | return FlexboxSectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle)
23 | }
24 | }
25 |
26 | private class FlexboxSectionState(configuration: FlexboxSectionConfig, oldState: SectionState? = null)
27 | : SectionState(configuration, oldState) {
28 | override fun isAtTop(layoutState: LayoutState) = FlexboxSlm.isAtTop(this, layoutState)
29 |
30 | override fun doLayout(helper: LayoutHelper, layoutState: LayoutState)
31 | = FlexboxSlm.onLayout(helper, this, layoutState)
32 |
33 | override fun doFillTop(dy: Int, helper: LayoutHelper, layoutState: LayoutState)
34 | = FlexboxSlm.onFillTop(dy, helper, this, layoutState)
35 |
36 | override fun doFillBottom(dy: Int, helper: LayoutHelper, layoutState: LayoutState)
37 | = FlexboxSlm.onFillTop(dy, helper, this, layoutState)
38 |
39 | override fun doTrimTop(scrolled: Int, helper: LayoutHelper, layoutState: LayoutState): Int
40 | = FlexboxSlm.onTrimTop(scrolled, helper, this, layoutState)
41 |
42 | override fun doTrimBottom(scrolled: Int, helper: LayoutHelper, layoutState: LayoutState): Int
43 | = FlexboxSlm.onTrimBottom(scrolled, helper, this, layoutState)
44 | }
45 |
46 | private object FlexboxSlm : SectionLayoutManager {
47 | override fun isAtTop(section: FlexboxSectionState, layoutState: LayoutState): Boolean {
48 | TODO("not implemented")
49 | }
50 |
51 | override fun onLayout(helper: LayoutHelper, section: FlexboxSectionState, layoutState: LayoutState) {
52 | TODO("not implemented")
53 | }
54 |
55 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: FlexboxSectionState, layoutState: LayoutState): Int {
56 | TODO("not implemented")
57 | }
58 |
59 | /**
60 | * Fill revealed area where content has been scrolled up the screen by dy.
61 | */
62 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: FlexboxSectionState,
63 | layoutState: LayoutState): Int {
64 | TODO("not implemented")
65 | }
66 |
67 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper,
68 | section: FlexboxSectionState,
69 | layoutState: LayoutState): Int {
70 | TODO("not implemented")
71 | }
72 |
73 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper,
74 | section: FlexboxSectionState,
75 | layoutState: LayoutState): Int {
76 | TODO("not implemented")
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/layout/grid.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.layout
2 |
3 | import com.tonicartos.superslim.*
4 | import com.tonicartos.superslim.adapter.FooterStyle
5 | import com.tonicartos.superslim.adapter.HeaderStyle
6 | import com.tonicartos.superslim.internal.SectionState
7 | import com.tonicartos.superslim.internal.SectionState.LayoutState
8 |
9 | class GridSectionConfig(gutterStart: Int = SectionConfig.DEFAULT_GUTTER, gutterEnd: Int = SectionConfig.DEFAULT_GUTTER,
10 | @HeaderStyle headerStyle: Int = SectionConfig.DEFAULT_HEADER_STYLE,
11 | @FooterStyle footerStyle: Int = SectionConfig.DEFAULT_FOOTER_STYLE,
12 | paddingStart: Int = 0, paddingTop: Int = 0, paddingEnd: Int = 0, paddingBottom: Int = 0) :
13 | SectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle, paddingStart, paddingTop, paddingEnd,
14 | paddingBottom),
15 | ColumnsSectionConfigurationMixin by ColumnsConfiguration() {
16 | override fun onMakeSection(oldState: SectionState?): SectionState = GridSectionState(this, oldState)
17 |
18 | override fun onCopy(): GridSectionConfig {
19 | val copy = GridSectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle)
20 | copy.numColumns = numColumns
21 | copy.columnWidth = columnWidth
22 | return copy
23 | }
24 | }
25 |
26 | private class GridSectionState(var configuration: GridSectionConfig, oldState: SectionState? = null) :
27 | SectionState(configuration, oldState), ColumnsSectionStateMixin by ColumnsState(configuration) {
28 | override fun isAtTop(layoutState: LayoutState) = GridSlm.isAtTop(this, layoutState)
29 |
30 | override fun doLayout(helper: LayoutHelper, layoutState: LayoutState) {
31 | resolveColumns(helper)
32 |
33 | GridSlm.onLayout(helper, this, layoutState)
34 | }
35 |
36 | override fun doFillTop(dy: Int, helper: LayoutHelper, layoutState: LayoutState): Int {
37 | TODO("not implemented")
38 | }
39 |
40 | override fun doFillBottom(dy: Int, helper: LayoutHelper, layoutState: LayoutState): Int {
41 | TODO("not implemented")
42 | }
43 |
44 | override fun doTrimTop(scrolled: Int, helper: LayoutHelper,
45 | layoutState: LayoutState): Int {
46 | TODO("not implemented")
47 | }
48 |
49 | override fun doTrimBottom(scrolled: Int, helper: LayoutHelper,
50 | layoutState: LayoutState): Int {
51 | TODO("not implemented")
52 | }
53 | }
54 |
55 | private object GridSlm : SectionLayoutManager {
56 | override fun isAtTop(section: GridSectionState, layoutState: LayoutState): Boolean {
57 | TODO("not implemented")
58 | }
59 |
60 | override fun onLayout(helper: LayoutHelper, section: GridSectionState, layoutState: LayoutState) {
61 | TODO("not implemented")
62 | }
63 |
64 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: GridSectionState, layoutState: LayoutState): Int {
65 | TODO("not implemented")
66 | }
67 |
68 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: GridSectionState, layoutState: LayoutState): Int {
69 | TODO("not implemented")
70 | }
71 |
72 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper,
73 | section: GridSectionState,
74 | layoutState: LayoutState): Int {
75 | TODO("not implemented")
76 | }
77 |
78 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper,
79 | section: GridSectionState,
80 | layoutState: LayoutState): Int {
81 | TODO("not implemented")
82 | }
83 | }
84 |
85 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/layout/linear.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.layout
2 |
3 | import com.tonicartos.superslim.LayoutHelper
4 | import com.tonicartos.superslim.SectionConfig
5 | import com.tonicartos.superslim.SectionLayoutManager
6 | import com.tonicartos.superslim.adapter.FooterStyle
7 | import com.tonicartos.superslim.adapter.HeaderStyle
8 | import com.tonicartos.superslim.internal.SectionState
9 | import com.tonicartos.superslim.internal.SectionState.LayoutState
10 | import com.tonicartos.superslim.use
11 |
12 | class LinearSectionConfig(gutterStart: Int = SectionConfig.DEFAULT_GUTTER,
13 | gutterEnd: Int = SectionConfig.DEFAULT_GUTTER,
14 | @HeaderStyle headerStyle: Int = SectionConfig.DEFAULT_HEADER_STYLE,
15 | @FooterStyle footerStyle: Int = SectionConfig.DEFAULT_FOOTER_STYLE) :
16 | SectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle, 0, 0, 0, 0) {
17 | override fun onMakeSection(oldState: SectionState?): SectionState = LinearSectionState(this, oldState)
18 |
19 | override fun onCopy(): LinearSectionConfig {
20 | return LinearSectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle)
21 | }
22 | }
23 |
24 | internal class LinearSectionState(configuration: LinearSectionConfig, oldState: SectionState? = null)
25 | : SectionState(configuration, oldState) {
26 | override fun isAtTop(layoutState: LayoutState)
27 | = LinearSlm.isAtTop(this, layoutState)
28 |
29 | override fun doLayout(helper: LayoutHelper, layoutState: LayoutState)
30 | = LinearSlm.onLayout(helper, this, layoutState)
31 |
32 | override fun doFillTop(dy: Int, helper: LayoutHelper, layoutState: LayoutState)
33 | = LinearSlm.onFillTop(dy, helper, this, layoutState)
34 |
35 | override fun doFillBottom(dy: Int, helper: LayoutHelper, layoutState: LayoutState)
36 | = LinearSlm.onFillBottom(dy, helper, this, layoutState)
37 |
38 | override fun doTrimTop(scrolled: Int, helper: LayoutHelper, layoutState: LayoutState)
39 | = LinearSlm.onTrimTop(scrolled, helper, this, layoutState)
40 |
41 | override fun doTrimBottom(scrolled: Int, helper: LayoutHelper, layoutState: LayoutState)
42 | = LinearSlm.onTrimBottom(scrolled, helper, this, layoutState)
43 | }
44 |
45 | internal object LinearSlm : SectionLayoutManager {
46 | override fun isAtTop(section: LinearSectionState, layoutState: LayoutState)
47 | = layoutState.overdraw == 0 && layoutState.headPosition <= 0 &&
48 | section.isChildAtTop(layoutState.headPosition)
49 |
50 | override fun onLayout(helper: LayoutHelper, section: LinearSectionState, layoutState: LayoutState) {
51 | if (layoutState.headPosition < 0) layoutState.headPosition = 0
52 | layoutState.tailPosition = -1
53 | var currentPosition = layoutState.headPosition
54 | var y = -layoutState.overdraw
55 |
56 | while (helper.moreToLayout(currentPosition, section)) {
57 | helper.getChild(currentPosition, section)?.use { child ->
58 | child.addToRecyclerView()
59 | child.measure()
60 | child.layout(0, y, child.measuredWidth, y + child.measuredHeight, helper.numViews)
61 | if (helper.isPreLayout && child.isRemoved) {
62 | helper.addIgnoredHeight(child.height)
63 | }
64 | layoutState.disappearedOrRemovedHeight += child.disappearedHeight
65 | y += child.height
66 | helper.filledArea += child.height
67 | if (child.disappearedHeight < child.height) layoutState.tailPosition = currentPosition
68 | currentPosition += 1
69 | } ?: break
70 | }
71 | layoutState.bottom = y
72 | if (layoutState.tailPosition == -1) layoutState.headPosition = -1
73 | // Log.d("Linear", "${section} --- $layoutState")
74 | }
75 |
76 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: LinearSectionState, layoutState: LayoutState): Int {
77 | if (layoutState.headPosition < 0) {
78 | layoutState.headPosition = section.numChildren
79 | layoutState.tailPosition = section.numChildren - 1
80 | }
81 |
82 | var dyRemaining = dy
83 | var currentPos = layoutState.headPosition
84 | var y = -layoutState.overdraw
85 |
86 | // Fill leading children
87 | if (0 <= currentPos && currentPos < section.numChildren) {
88 | helper.getUnfinishedChild(currentPos, section)?.use { child ->
89 | child.measure()
90 | // Different from fillBottom because overscroll hides the excess from section height.
91 | child.fillTop(dyRemaining, 0, y - child.measuredHeight, child.measuredWidth, y).let {
92 | y -= it
93 | dyRemaining -= it
94 | }
95 | }
96 | }
97 |
98 | // Fill using overdraw
99 | dyRemaining -= layoutState.overdraw
100 |
101 | // Fill remaining dy with remaining content.
102 | while (dyRemaining > 0 && currentPos > 0) {
103 | currentPos -= 1
104 | helper.getChild(currentPos, section)?.use { child ->
105 | child.addToRecyclerView(0)
106 | child.measure()
107 | child.fillTop(dyRemaining, 0, y - child.measuredHeight, child.measuredWidth, y).let {
108 | y -= it
109 | dyRemaining -= it
110 | }
111 | }
112 | }
113 |
114 | val filled = Math.min(dy, dy - dyRemaining) // Cap filled distance at dy. Any left over is overdraw.
115 | layoutState.overdraw = Math.max(0, -dyRemaining) // If dyRemaining is -ve, then overdraw happened.
116 | layoutState.bottom += filled // Section got taller by the filled amount.
117 | layoutState.headPosition = currentPos
118 | return filled
119 | }
120 |
121 | /**
122 | * Fill revealed area where content has been scrolled up the screen by dy.
123 | */
124 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: LinearSectionState,
125 | layoutState: LayoutState): Int {
126 | if (layoutState.headPosition < 0) {
127 | layoutState.headPosition = 0
128 | }
129 |
130 | var y = layoutState.bottom
131 | // Must fill section children first.
132 | var filled = 0
133 | if (layoutState.tailPosition >= 0) {
134 | // Must handle case where not just the trailing child needs updating.
135 | var stepBackHeight = 0
136 | var stepBackViews = 0
137 | for (position in layoutState.tailPosition downTo layoutState.headPosition) {
138 | helper.getUnfinishedChild(position, section)?.use { child ->
139 | child.measure()
140 |
141 | val before = child.height
142 | val fill = child.fillBottom(dy, 0, y - stepBackHeight - child.height, child.measuredWidth, 0,
143 | helper.numViews - stepBackViews - child.numViews)
144 | if (position == layoutState.tailPosition) {
145 | y += child.height - before
146 | filled += fill
147 | }
148 |
149 | stepBackHeight += child.height
150 | stepBackViews += child.numViews
151 | }
152 | if (y - stepBackHeight <= helper.layoutLimit - helper.stickyEndInset) break
153 | }
154 | }
155 |
156 | // Check to see if we have to handle excess.
157 | val excess = if (layoutState.tailPosition >= 0 && filled == 0) {
158 | Math.max(0, layoutState.bottom - helper.layoutLimit)
159 | } else {
160 | 0
161 | }
162 |
163 | // Fill in remaining space
164 | while (filled + excess < dy && layoutState.tailPosition + 1 < section.numChildren) {
165 | helper.getChild(layoutState.tailPosition + 1, section)?.use { child ->
166 | child.addToRecyclerView()
167 | child.measure()
168 | filled += child.fillBottom(dy - filled, 0, y, child.measuredWidth, y + child.measuredHeight,
169 | helper.numViews)
170 | layoutState.tailPosition += 1
171 | y += child.height
172 | }
173 | }
174 |
175 | filled += excess
176 |
177 | layoutState.bottom = y
178 |
179 | return Math.min(dy, filled)
180 | }
181 |
182 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper, section: LinearSectionState,
183 | layoutState: LayoutState): Int {
184 | var removedHeight = 0
185 | while (layoutState.headPosition <= layoutState.tailPosition) {
186 | var childRemoved = false
187 | helper.getUnfinishedChild(layoutState.headPosition, section)?.use { child ->
188 | removedHeight += child.trimTop(scrolled, 0, helper)
189 | childRemoved = child.numViews == 0
190 | // Don't adjust overdraw because section children don't report drawing into the area.
191 | } ?: helper.getAttachedViewAt(0) { child ->
192 | if (child.bottom < 0) {
193 | child.remove()
194 | removedHeight += Math.max(0, child.height - layoutState.overdraw)
195 | layoutState.overdraw = Math.max(0, layoutState.overdraw - child.height)
196 | childRemoved = true
197 | } else if (child.top < 0) {
198 | val before = layoutState.overdraw
199 | layoutState.overdraw = -child.top
200 | removedHeight += layoutState.overdraw - before
201 | }
202 | }
203 |
204 | if (childRemoved) {
205 | layoutState.headPosition += 1
206 | } else {
207 | break
208 | }
209 | }
210 |
211 | if (helper.numViews == 0) {
212 | layoutState.headPosition = -1
213 | layoutState.tailPosition = -1
214 | }
215 | layoutState.bottom -= removedHeight
216 | return removedHeight
217 | }
218 |
219 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper, section: LinearSectionState,
220 | layoutState: LayoutState): Int {
221 | var removedHeight = 0
222 | var y = layoutState.bottom
223 | // Trim child sections back to before the sticky edge. Remove child views after the limit. Track tail position.
224 | var stepBackViews = 0
225 | for (position in layoutState.tailPosition downTo layoutState.headPosition) {
226 | helper.getUnfinishedChild(position, section)?.use { child ->
227 | y -= child.height
228 | removedHeight += child.trimBottom(scrolled, y, helper, helper.numViews - stepBackViews - child.numViews)
229 | stepBackViews += child.numViews
230 | if (child.numViews == 0) layoutState.tailPosition -= 1
231 | } ?: helper.getAttachedViewAt(helper.numViews - 1) { child ->
232 | y -= child.height
233 | stepBackViews += 1
234 | if (child.top > helper.layoutLimit) {
235 | removedHeight += child.height
236 | child.remove()
237 | layoutState.tailPosition -= 1
238 | stepBackViews -= 1
239 | }
240 | }
241 | // Go until marker before sticky edge.
242 | if (y < helper.layoutLimit - helper.stickyEndInset) break
243 | }
244 |
245 | if (helper.numViews == 0) {
246 | layoutState.headPosition = -1
247 | layoutState.tailPosition = -1
248 | }
249 | layoutState.bottom -= removedHeight
250 | return removedHeight
251 | }
252 | }
253 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/layout/staggered_grid.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim.layout
2 |
3 | import com.tonicartos.superslim.*
4 | import com.tonicartos.superslim.adapter.FooterStyle
5 | import com.tonicartos.superslim.adapter.HeaderStyle
6 | import com.tonicartos.superslim.internal.SectionState
7 | import com.tonicartos.superslim.internal.SectionState.LayoutState
8 |
9 | class StaggeredGridSectionConfig(gutterStart: Int = SectionConfig.DEFAULT_GUTTER,
10 | gutterEnd: Int = SectionConfig.DEFAULT_GUTTER,
11 | @HeaderStyle headerStyle: Int = SectionConfig.DEFAULT_HEADER_STYLE,
12 | @FooterStyle footerStyle: Int = SectionConfig.DEFAULT_FOOTER_STYLE,
13 | paddingStart: Int = 0, paddingTop: Int = 0, paddingEnd: Int = 0,
14 | paddingBottom: Int = 0) :
15 | SectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle, paddingStart, paddingTop, paddingEnd,
16 | paddingBottom),
17 | ColumnsSectionConfigurationMixin by ColumnsConfiguration() {
18 | override fun onMakeSection(oldState: SectionState?): SectionState = StaggeredGridSection(this, oldState)
19 |
20 | override fun onCopy(): StaggeredGridSectionConfig {
21 | val copy = StaggeredGridSectionConfig(gutterStart, gutterEnd, headerStyle, footerStyle)
22 | copy.numColumns = numColumns
23 | copy.columnWidth = columnWidth
24 | return copy
25 | }
26 | }
27 |
28 | private class StaggeredGridSection(var configuration: StaggeredGridSectionConfig, oldState: SectionState? = null) :
29 | SectionState(configuration, oldState), ColumnsSectionStateMixin by ColumnsState(configuration) {
30 | override fun isAtTop(layoutState: LayoutState): Boolean {
31 | return StaggeredGridSlm.isAtTop(this, layoutState)
32 | }
33 |
34 | override fun doLayout(helper: LayoutHelper, layoutState: LayoutState) {
35 | resolveColumns(helper)
36 |
37 | StaggeredGridSlm.onLayout(helper, this, layoutState)
38 | }
39 |
40 | override fun doFillTop(dy: Int, helper: LayoutHelper, layoutState: LayoutState): Int {
41 | TODO("not implemented")
42 | }
43 |
44 | override fun doFillBottom(dy: Int, helper: LayoutHelper, layoutState: LayoutState): Int {
45 | TODO("not implemented")
46 | }
47 |
48 | override fun doTrimTop(scrolled: Int, helper: LayoutHelper,
49 | layoutState: LayoutState): Int {
50 | TODO("not implemented")
51 | }
52 |
53 | override fun doTrimBottom(scrolled: Int, helper: LayoutHelper,
54 | layoutState: LayoutState): Int {
55 | TODO("not implemented")
56 | }
57 | }
58 |
59 | private object StaggeredGridSlm : SectionLayoutManager {
60 | override fun isAtTop(section: StaggeredGridSection, layoutState: LayoutState): Boolean {
61 | TODO("not implemented")
62 | }
63 |
64 | override fun onLayout(helper: LayoutHelper, section: StaggeredGridSection, layoutState: LayoutState) {
65 | TODO("not implemented")
66 | }
67 |
68 | override fun onFillTop(dy: Int, helper: LayoutHelper, section: StaggeredGridSection,
69 | layoutState: LayoutState): Int {
70 | TODO("not implemented")
71 | }
72 |
73 | override fun onFillBottom(dy: Int, helper: LayoutHelper, section: StaggeredGridSection,
74 | layoutState: LayoutState): Int {
75 | TODO("not implemented")
76 | }
77 |
78 | override fun onTrimTop(scrolled: Int, helper: LayoutHelper,
79 | section: StaggeredGridSection,
80 | layoutState: LayoutState): Int {
81 | TODO("not implemented")
82 | }
83 |
84 | override fun onTrimBottom(scrolled: Int, helper: LayoutHelper,
85 | section: StaggeredGridSection,
86 | layoutState: LayoutState): Int {
87 | TODO("not implemented")
88 | }
89 | }
90 |
91 |
92 |
--------------------------------------------------------------------------------
/library/src/main/kotlin/com/tonicartos/superslim/section_data_mixins.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | interface ColumnsSectionConfigurationMixin {
4 | var numColumns: Int
5 | var columnWidth: Int
6 | }
7 |
8 | class ColumnsConfiguration : ColumnsSectionConfigurationMixin {
9 | override var numColumns = 0
10 | get() = field
11 | set(value) {
12 | field = value
13 | columnWidth = -1
14 | }
15 |
16 | override var columnWidth: Int = -1
17 | }
18 |
19 | interface ColumnsSectionStateMixin {
20 | var numColumns: Int
21 |
22 | fun resolveColumns(helper: LayoutHelper)
23 | }
24 |
25 | class ColumnsState(configuration: ColumnsSectionConfigurationMixin) : ColumnsSectionStateMixin {
26 | override var numColumns: Int = 0
27 | private var requestedColumnWidth = -1
28 | private var columnsResolved = false
29 |
30 | init {
31 | numColumns = configuration.numColumns
32 | requestedColumnWidth = configuration.columnWidth
33 | }
34 |
35 | override fun resolveColumns(helper: LayoutHelper) {
36 | if (columnsResolved) {
37 | return
38 | }
39 |
40 | if (requestedColumnWidth != -1) {
41 | numColumns = helper.layoutWidth / requestedColumnWidth
42 | }
43 |
44 | if (numColumns == 0) {
45 | numColumns = 1
46 | }
47 |
48 | columnsResolved = true
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | SuperSLiM
3 |
4 |
--------------------------------------------------------------------------------
/library/src/test/kotlin/com/tonicartos/superslim/grid_layout.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | /**
4 | *
5 | */
6 |
--------------------------------------------------------------------------------
/library/src/test/kotlin/com/tonicartos/superslim/header_layout.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 |
4 | class HeaderTests {
5 | //TODO: mock helper
6 | //TODO: mock section state
7 | //TODO: mock layout state
8 | }
9 |
--------------------------------------------------------------------------------
/library/src/test/kotlin/com/tonicartos/superslim/linear_layout.kt:
--------------------------------------------------------------------------------
1 | package com.tonicartos.superslim
2 |
3 | class LinearSlmTests {
4 | }
5 |
--------------------------------------------------------------------------------
/library/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker:
--------------------------------------------------------------------------------
1 | mock-maker-inline
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':dbexample', ':library'
2 |
--------------------------------------------------------------------------------