├── .gitignore
├── LICENSE.txt
├── README.md
├── app
├── build.gradle
├── proguard-rules.txt
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── org
│ │ └── solovyev
│ │ └── android
│ │ └── views
│ │ └── llm
│ │ └── app
│ │ ├── DialogListActivity.java
│ │ ├── DialogListAdapter.java
│ │ ├── DialogListFragment.java
│ │ └── RandomListActivity.java
│ └── res
│ ├── drawable-xxxhdpi
│ ├── ic_pause_grey600_48dp.png
│ ├── ic_play_arrow_grey600_48dp.png
│ └── ic_refresh_grey600_48dp.png
│ ├── drawable
│ └── divider.xml
│ ├── layout
│ ├── activity_dialog_list.xml
│ ├── activity_random_list.xml
│ ├── dialog.xml
│ ├── dialog_list_item.xml
│ └── list_item.xml
│ └── values
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── lib
├── build.gradle
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── org
│ └── solovyev
│ └── android
│ └── views
│ └── llm
│ ├── DividerItemDecoration.java
│ └── LinearLayoutManager.java
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | ### Android template
3 | # Built application files
4 | *.apk
5 | *.ap_
6 |
7 | # Files for the Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 |
17 | # Gradle files
18 | .gradle/
19 | build/
20 | /*/build/
21 |
22 | # Local configuration file (sdk path, etc)
23 | local.properties
24 |
25 | # Proguard folder generated by Eclipse
26 | proguard/
27 |
28 | # Log Files
29 | *.log
30 |
31 | .idea
32 | **/*.iml
33 |
34 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright 2014 serso aka se.solovyev
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
14 |
15 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 |
17 | Contact details
18 |
19 | Email: se.solovyev@gmail.com
20 | Site: http://se.solovyev.org
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Linear Layout Manager
2 |
3 | ## DEPRECATED
4 | RecyclerView supports WRAP_CONTENT starting from Android Support Library 23.2. More details here: http://android-developers.blogspot.se/2016/02/android-support-library-232.html
5 |
6 | ## Description
7 | Implementation of [LinearLayoutManager](https://developer.android.com/reference/android/support/v7/widget/LinearLayoutManager.html) which wraps its contents.
8 |
9 | Usage example:
10 |
final LinearLayoutManager layoutManager = new org.solovyev.android.views.llm.LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
11 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
12 | recyclerView.setLayoutManager(layoutManager);
13 | recyclerView.addItemDecoration(new DividerItemDecoration(this, null));
14 | recyclerView.setAdapter(adapter);
15 |
16 | Note that if the child views in your RecyclerView have the fixed size LinearLayoutManager#setChildSize should be used
17 | to avoid unnecessary measuring.
18 |
19 | ## Installation
20 | Gradle dependency:
21 | compile 'org.solovyev.android.views:linear-layout-manager:0.5@aar'
22 | Maven dependency:
23 | ```xml
24 |
25 | org.solovyev.android.views
26 | linear-layout-manager
27 | 0.5
28 | apklib
29 |
30 | ```
31 |
32 | ## License
33 | Library is distributed under Apache 2.0 license, see LICENSE.txt
34 |
35 | ## Applications
36 |
37 | The following applications use this library:
38 | * [Sample app](https://oss.sonatype.org/content/repositories/releases/org/solovyev/android/views/linear-layout-manager-app/)
39 | * [Say it right!](https://play.google.com/store/apps/details?id=org.solovyev.android.dictionary.forvo)
40 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'maven'
26 | apply plugin: 'signing'
27 |
28 | android {
29 | compileSdkVersion android_sdk_version()
30 | buildToolsVersion android_build_tools_version()
31 | defaultConfig {
32 | minSdkVersion android_min_sdk_version()
33 | targetSdkVersion android_sdk_version()
34 | versionCode version_code()
35 | versionName version_name()
36 | }
37 | buildTypes {
38 | release {
39 | minifyEnabled true
40 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
41 | zipAlignEnabled true
42 | }
43 | }
44 | }
45 |
46 | dependencies {
47 | compile fileTree(dir: 'libs', include: ['*.jar'])
48 | compile project(':lib')
49 | compile 'org.solovyev.android:material:0.1.3@aar'
50 | compile 'com.google.code.findbugs:jsr305:2.0.3'
51 | }
52 |
53 | task androidJavadocs(type: Javadoc) {
54 | source = android.sourceSets.main.java.srcDirs
55 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
56 | configurations.compile.each { File file -> classpath += project.files(file.path) }
57 | }
58 |
59 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
60 | classifier = 'javadoc'
61 | from androidJavadocs.destinationDir
62 | }
63 |
64 | task androidSourcesJar(type: Jar) {
65 | classifier = 'sources'
66 | from android.sourceSets.main.java.srcDirs
67 | }
68 |
69 | artifacts {
70 | archives androidSourcesJar
71 | archives file: file('build/outputs/apk/linear-layout-manager-release.apk'), name: 'linear-layout-manager-app', type: 'apk'
72 | archives file: file('build/outputs/mapping/release/mapping.txt'), name: 'linear-layout-manager-app', classifier: 'proguard', type: 'txt'
73 | }
74 |
75 | signing {
76 | sign configurations.archives
77 | }
78 |
79 | group = "org.solovyev.android.views"
80 | archivesBaseName = "linear-layout-manager-app"
81 | version = version_name()
82 |
83 | uploadArchives {
84 | doFirst {
85 | // for some reason Gradle :uploadArchives tries to upload null.txt.asc and null.map artifacts.
86 | // As we don't want those let's filter them now (note that these files are not included in the
87 | // list of project artifacts, so their origin is not clear)
88 | configurations.archives.artifacts.removeAll { artifact ->
89 | def removing = artifact.classifier == null && (artifact.type == 'txt.asc' || artifact.type == 'map')
90 | if (removing) {
91 | println "Removing artifacts: ${artifact.name}-${artifact.classifier}.${artifact.type}"
92 | }
93 | return removing
94 | }
95 | }
96 | repositories {
97 | mavenDeployer {
98 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
99 |
100 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
101 | authentication(userName: ossrhUsername, password: ossrhPassword)
102 | }
103 |
104 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
105 | authentication(userName: ossrhUsername, password: ossrhPassword)
106 | }
107 |
108 | pom.project {
109 | name 'Linear Layout Manager App'
110 | packaging 'apk'
111 | description 'Linear Layout Manager sample application'
112 | url 'https://github.com/serso/android-linear-layout-manager'
113 |
114 | scm {
115 | url 'https://github.com/serso/android-linear-layout-manager'
116 | connection 'scm:https://serso@github.com/serso/android-linear-layout-manager.git'
117 | developerConnection 'scm:git://github.com/serso/android-linear-layout-manager.git'
118 | }
119 |
120 | licenses {
121 | license {
122 | name 'The Apache Software License, Version 2.0'
123 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
124 | distribution 'repo'
125 | }
126 | }
127 |
128 | developers {
129 | developer {
130 | id 'se.solovyev'
131 | name 'Sergey Solovyev'
132 | email 'se.solovyev@gmail.com'
133 | }
134 | }
135 | }
136 | }
137 | }
138 | }
139 |
--------------------------------------------------------------------------------
/app/proguard-rules.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serso/android-linear-layout-manager/27278eac50b73ae16bf895ed57ba28722a1d5fda/app/proguard-rules.txt
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
38 |
39 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/app/src/main/java/org/solovyev/android/views/llm/app/DialogListActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm.app;
25 |
26 | import android.os.Bundle;
27 | import android.support.v4.app.FragmentActivity;
28 | import android.support.v4.app.FragmentManager;
29 |
30 | public class DialogListActivity extends FragmentActivity {
31 |
32 | @Override
33 | protected void onCreate(Bundle savedInstanceState) {
34 | super.onCreate(savedInstanceState);
35 |
36 | setContentView(R.layout.activity_dialog_list);
37 |
38 | if(savedInstanceState == null) {
39 | showListDialog();
40 | }
41 | }
42 |
43 | private void showListDialog() {
44 | FragmentManager fragmentManager = getSupportFragmentManager();
45 | if (fragmentManager.findFragmentByTag(DialogListFragment.DIALOG_TAG) != null) {
46 | return;
47 | }
48 | DialogListFragment dialogFragment = new DialogListFragment();
49 | dialogFragment.show(fragmentManager, DialogListFragment.DIALOG_TAG);
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/org/solovyev/android/views/llm/app/DialogListAdapter.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm.app;
25 |
26 | import android.support.v7.widget.RecyclerView;
27 | import android.view.LayoutInflater;
28 | import android.view.View;
29 | import android.view.ViewGroup;
30 | import android.widget.TextView;
31 |
32 | import java.util.List;
33 |
34 | public class DialogListAdapter extends RecyclerView.Adapter {
35 |
36 | private final DialogListFragment fragment;
37 | private final List items;
38 |
39 | public DialogListAdapter(DialogListFragment fragment, List items) {
40 | this.fragment = fragment;
41 | this.items = items;
42 | }
43 |
44 | public List getItems() {
45 | return items;
46 | }
47 |
48 | @Override
49 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int position) {
50 | View rootLayout =
51 | LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.dialog_list_item, viewGroup, false);
52 | return new ViewHolder(rootLayout);
53 | }
54 |
55 | @Override
56 | public void onBindViewHolder(ViewHolder viewHolder, final int position) {
57 | viewHolder.itemView.setOnClickListener(new View.OnClickListener() {
58 | @Override
59 | public void onClick(View v) {
60 | fragment.onClick(position);
61 | }
62 | });
63 | viewHolder.text.setText(items.get(position));
64 | }
65 |
66 | @Override
67 | public int getItemCount() {
68 | return items.size();
69 | }
70 |
71 | public class ViewHolder extends RecyclerView.ViewHolder {
72 |
73 | TextView text;
74 |
75 | public ViewHolder(View itemView) {
76 | super(itemView);
77 | this.text = (TextView) itemView.findViewById(R.id.text);
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/app/src/main/java/org/solovyev/android/views/llm/app/DialogListFragment.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm.app;
25 |
26 | import android.app.Dialog;
27 | import android.content.DialogInterface;
28 | import android.os.Bundle;
29 | import android.os.Handler;
30 | import android.os.Message;
31 | import android.support.v4.app.DialogFragment;
32 | import android.support.v7.widget.RecyclerView;
33 | import android.view.LayoutInflater;
34 | import android.view.View;
35 | import android.view.ViewGroup;
36 | import android.view.ViewTreeObserver;
37 | import android.view.Window;
38 |
39 | import org.solovyev.android.views.llm.LinearLayoutManager;
40 |
41 | import java.util.ArrayList;
42 | import java.util.Iterator;
43 | import java.util.List;
44 |
45 | public class DialogListFragment extends DialogFragment {
46 |
47 | public static final String DIALOG_TAG = DialogListFragment.class.getSimpleName();
48 | public static final String INSERT_TEST = "> Insert Test";
49 | private final MessageHandler timeoutHandler = new MessageHandler();
50 | private DialogListAdapter adapter;
51 | private List listItems = new ArrayList<>();
52 | private RecyclerView recyclerView;
53 |
54 | @Override
55 | public Dialog onCreateDialog(Bundle savedInstanceState) {
56 | final Dialog dialog = super.onCreateDialog(savedInstanceState);
57 | dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
58 | return dialog;
59 | }
60 |
61 | @Override
62 | public View onCreateView(LayoutInflater inflater, ViewGroup container,
63 | Bundle savedInstanceState) {
64 | View view = inflater.inflate(R.layout.dialog, container);
65 | fillTestListItems();
66 | restrictHeight(view);
67 | initFields(view);
68 | initRecyclerView();
69 | return view;
70 | }
71 |
72 | private void fillTestListItems() {
73 | listItems.add("One");
74 | listItems.add("Two");
75 | listItems.add("Three");
76 | listItems.add("Four");
77 | listItems.add("Five");
78 | listItems.add("Six");
79 | listItems.add("Seven");
80 | listItems.add("Eight");
81 | }
82 |
83 | private void restrictHeight(final View view) {
84 | view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
85 | @Override
86 | public boolean onPreDraw() {
87 | view.getViewTreeObserver().removeOnPreDrawListener(this);
88 | int dialogMaxHeight = getResources().getDimensionPixelSize(R.dimen.dialog_max_height);
89 | if (view.getHeight() > dialogMaxHeight) {
90 | ViewGroup.LayoutParams params = view.getLayoutParams();
91 | params.height = dialogMaxHeight;
92 | view.setLayoutParams(params);
93 | }
94 | return true;
95 | }
96 | });
97 | }
98 |
99 | private void initFields(View view) {
100 | recyclerView = (RecyclerView) view.findViewById(R.id.list);
101 | }
102 |
103 | private void initRecyclerView() {
104 | RecyclerView.LayoutManager layoutManager =
105 | new LinearLayoutManager(recyclerView, LinearLayoutManager.VERTICAL, false);
106 | recyclerView.setLayoutManager(layoutManager);
107 | adapter = new DialogListAdapter(this, listItems);
108 | recyclerView.setAdapter(adapter);
109 | }
110 |
111 | @Override
112 | public void onDismiss(DialogInterface dialog) {
113 | super.onDismiss(dialog);
114 | removeTestItemsFromList();
115 | }
116 |
117 | public void onClick(int position) {
118 | String selectedItem = listItems.get(position);
119 | if (selectedItem.equals(INSERT_TEST)) {
120 | return;
121 | }
122 | int insertPosition = position + 1;
123 | removeTestItemsFromList();
124 | addTestItemForPosition(insertPosition);
125 | }
126 |
127 | private void addTestItemForPosition(int position) {
128 | listItems.add(position, INSERT_TEST);
129 | adapter.notifyItemInserted(position);
130 | timeoutHandler.sendMessageDelayed(timeoutHandler.obtainMessage(), 3000);
131 | }
132 |
133 | private void removeTestItemsFromList() {
134 | if (adapter == null) {
135 | return;
136 | }
137 | List items = adapter.getItems();
138 | int index = 0;
139 | for (Iterator iterator = items.listIterator(); iterator.hasNext(); index++) {
140 | String text = iterator.next();
141 | if (text.equals(INSERT_TEST)) {
142 | iterator.remove();
143 | adapter.notifyItemRemoved(index);
144 | break;
145 | }
146 | }
147 | }
148 |
149 | private class MessageHandler extends Handler {
150 | @Override
151 | public void handleMessage(Message msg) {
152 | super.handleMessage(msg);
153 | removeTestItemsFromList();
154 | }
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/app/src/main/java/org/solovyev/android/views/llm/app/RandomListActivity.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm.app;
25 |
26 | import android.os.Bundle;
27 | import android.os.Handler;
28 | import android.support.v4.app.FragmentActivity;
29 | import android.support.v4.view.ViewCompat;
30 | import android.support.v7.widget.RecyclerView;
31 | import android.view.LayoutInflater;
32 | import android.view.View;
33 | import android.view.ViewGroup;
34 | import android.widget.ImageView;
35 | import android.widget.TextView;
36 | import org.solovyev.android.views.llm.DividerItemDecoration;
37 | import org.solovyev.android.views.llm.LinearLayoutManager;
38 |
39 | import java.util.ArrayList;
40 | import java.util.List;
41 | import java.util.Random;
42 |
43 | public class RandomListActivity extends FragmentActivity {
44 |
45 | private final Handler handler = new Handler();
46 | private Updater updater;
47 |
48 | @Override
49 | protected void onCreate(Bundle savedInstanceState) {
50 | super.onCreate(savedInstanceState);
51 |
52 | setContentView(R.layout.activity_random_list);
53 |
54 | final MyAdapter adapter = new MyAdapter();
55 |
56 | final TextView counter = (TextView) findViewById(R.id.counter);
57 | adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
58 | @Override
59 | public void onItemRangeInserted(int positionStart, int itemCount) {
60 | updateCounter(counter, adapter);
61 | }
62 |
63 | @Override
64 | public void onItemRangeRemoved(int positionStart, int itemCount) {
65 | updateCounter(counter, adapter);
66 | }
67 | });
68 |
69 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
70 | final LinearLayoutManager layoutManager = new LinearLayoutManager(recyclerView, LinearLayoutManager.VERTICAL, false);
71 | layoutManager.setOverScrollMode(ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS);
72 | recyclerView.setLayoutManager(layoutManager);
73 | recyclerView.addItemDecoration(new DividerItemDecoration(this, null));
74 | recyclerView.setAdapter(adapter);
75 |
76 | final ImageView rotate = (ImageView) findViewById(R.id.rotate);
77 | rotate.setOnClickListener(new View.OnClickListener() {
78 | @Override
79 | public void onClick(View v) {
80 | final ViewGroup.LayoutParams lp = recyclerView.getLayoutParams();
81 | final int orientation = layoutManager.getOrientation();
82 | if (orientation == LinearLayoutManager.VERTICAL) {
83 | layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
84 | lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
85 | lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
86 | } else {
87 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
88 | lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
89 | lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
90 | }
91 | recyclerView.setLayoutParams(lp);
92 | }
93 | });
94 |
95 | final ImageView playPause = (ImageView) findViewById(R.id.playpause);
96 | playPause.setOnClickListener(new View.OnClickListener() {
97 | @Override
98 | public void onClick(View v) {
99 | if (updater.running) {
100 | updater.stop();
101 | playPause.setImageResource(R.drawable.ic_play_arrow_grey600_48dp);
102 | } else {
103 | updater.start();
104 | playPause.setImageResource(R.drawable.ic_pause_grey600_48dp);
105 | }
106 | }
107 | });
108 |
109 | updater = new Updater(adapter);
110 | updater.start();
111 | }
112 |
113 | private void updateCounter(TextView counter, MyAdapter adapter) {
114 | counter.setText("Items: " + adapter.getItemCount());
115 | }
116 |
117 | private static class MyAdapter extends RecyclerView.Adapter {
118 |
119 | private final List items = new ArrayList<>();
120 |
121 | @Override
122 | public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
123 | final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
124 | final TextView itemView = (TextView) inflater.inflate(R.layout.list_item, parent, false);
125 | return new MyViewHolder(itemView);
126 | }
127 |
128 | @Override
129 | public void onBindViewHolder(MyViewHolder holder, int position) {
130 | ((TextView) holder.itemView).setText(items.get(position));
131 | }
132 |
133 | @Override
134 | public int getItemCount() {
135 | return items.size();
136 | }
137 |
138 | public void add(int count) {
139 | final int sizeBefore = items.size();
140 | for (int i = 0; i < count; i++) {
141 | items.add("Item #" + (sizeBefore + i + 1));
142 | }
143 | notifyItemRangeInserted(sizeBefore, count);
144 | }
145 |
146 | public void removeLast(int count) {
147 | final int sizeBefore = items.size();
148 | if (count >= items.size()) {
149 | items.clear();
150 | notifyItemRangeRemoved(0, sizeBefore);
151 | } else {
152 | for (int i = 0; i < count; i++) {
153 | items.remove(items.size() - 1);
154 | }
155 | notifyItemRangeRemoved(items.size(), count);
156 | }
157 | }
158 | }
159 |
160 | private static class MyViewHolder extends RecyclerView.ViewHolder {
161 | public MyViewHolder(TextView textView) {
162 | super(textView);
163 | }
164 | }
165 |
166 | private class Updater implements Runnable {
167 | private final MyAdapter adapter;
168 | private final Random random = new Random();
169 | private boolean running;
170 |
171 | public Updater(MyAdapter adapter) {
172 | this.adapter = adapter;
173 | }
174 |
175 | @Override
176 | public void run() {
177 | final boolean canRemove = adapter.getItemCount() > 0;
178 | final boolean remove = canRemove && random.nextBoolean();
179 | final int count = random.nextInt(10);
180 | if (remove) {
181 | adapter.removeLast(count);
182 | } else {
183 | adapter.add(count);
184 | }
185 | handler.postDelayed(this, 2000L);
186 | }
187 |
188 |
189 | void start() {
190 | if (!running) {
191 | running = true;
192 | handler.post(this);
193 | }
194 | }
195 |
196 | void stop() {
197 | if (running) {
198 | running = false;
199 | handler.removeCallbacks(this);
200 | }
201 | }
202 | }
203 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_pause_grey600_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serso/android-linear-layout-manager/27278eac50b73ae16bf895ed57ba28722a1d5fda/app/src/main/res/drawable-xxxhdpi/ic_pause_grey600_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serso/android-linear-layout-manager/27278eac50b73ae16bf895ed57ba28722a1d5fda/app/src/main/res/drawable-xxxhdpi/ic_play_arrow_grey600_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_48dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serso/android-linear-layout-manager/27278eac50b73ae16bf895ed57ba28722a1d5fda/app/src/main/res/drawable-xxxhdpi/ic_refresh_grey600_48dp.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/divider.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_dialog_list.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_random_list.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
28 |
29 |
30 |
31 |
35 |
36 |
45 |
46 |
47 |
48 |
53 |
57 |
58 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
30 |
31 |
37 |
38 |
45 |
46 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/dialog_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
28 |
29 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/list_item.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 | 350dp
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 | Linear Layout Manager
27 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
24 |
25 |
26 |
35 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | public int version_code() {
4 | return 5
5 | }
6 |
7 | public String version_name() {
8 | return '0.5'
9 | }
10 |
11 | public int android_sdk_version() {
12 | return 22
13 | }
14 |
15 | public String android_build_tools_version() {
16 | return '22.0.1'
17 | }
18 |
19 | public String android_min_sdk_version() {
20 | return 8
21 | }
22 |
23 | buildscript {
24 |
25 | repositories {
26 | mavenCentral()
27 | }
28 | dependencies {
29 | classpath 'com.android.tools.build:gradle:1.2.3'
30 | }
31 | }
32 |
33 | allprojects {
34 | repositories {
35 | maven {
36 | url "file://${System.env.ANDROID_HOME}/extras/android/m2repository/"
37 | }
38 | mavenCentral()
39 | }
40 |
41 | if (!project.hasProperty("ossrhUsername")) {
42 | ext.ossrhUsername = "no-username"
43 | }
44 |
45 | if (!project.hasProperty("ossrhPassword")) {
46 | ext.ossrhPassword = "no-password"
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/serso/android-linear-layout-manager/27278eac50b73ae16bf895ed57ba28722a1d5fda/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Apr 06 22:57:17 CEST 2015
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-2.2.1-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 |
--------------------------------------------------------------------------------
/lib/build.gradle:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | apply plugin: 'com.android.library'
25 | apply plugin: 'maven'
26 | apply plugin: 'signing'
27 |
28 | android {
29 | compileSdkVersion android_sdk_version()
30 | buildToolsVersion android_build_tools_version()
31 | defaultConfig {
32 | minSdkVersion android_min_sdk_version()
33 | targetSdkVersion android_sdk_version()
34 | versionCode version_code()
35 | versionName version_name()
36 | }
37 | }
38 |
39 | dependencies {
40 | compile fileTree(dir: 'libs', include: ['*.jar'])
41 | compile 'com.android.support:recyclerview-v7:22.0.0'
42 | }
43 |
44 | task androidJavadocs(type: Javadoc) {
45 | source = android.sourceSets.main.java.srcDirs
46 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
47 | configurations.compile.each { File file -> classpath += project.files(file.path) }
48 | }
49 |
50 | task androidJavadocsJar(type: Jar, dependsOn: androidJavadocs) {
51 | classifier = 'javadoc'
52 | from androidJavadocs.destinationDir
53 | }
54 |
55 | task androidSourcesJar(type: Jar) {
56 | classifier = 'sources'
57 | from android.sourceSets.main.java.srcDirs
58 | }
59 |
60 | task apklib(type: Zip) {
61 | appendix = extension = 'apklib'
62 |
63 | from 'src/main/AndroidManifest.xml'
64 | into('res') {
65 | from android.sourceSets.main.res.srcDirs
66 | }
67 | into('src') {
68 | from android.sourceSets.main.java.srcDirs
69 | }
70 | into('src') {
71 | from android.sourceSets.main.aidl.srcDirs
72 | }
73 | }
74 |
75 |
76 | artifacts {
77 | archives androidSourcesJar
78 | archives androidJavadocsJar
79 | archives apklib
80 | }
81 |
82 | signing {
83 | sign configurations.archives
84 | }
85 |
86 | group = "org.solovyev.android.views"
87 | archivesBaseName = "linear-layout-manager"
88 | version = version_name()
89 |
90 | uploadArchives {
91 | repositories {
92 | mavenDeployer {
93 | beforeDeployment { MavenDeployment deployment -> signing.signPom(deployment) }
94 |
95 | repository(url: "https://oss.sonatype.org/service/local/staging/deploy/maven2/") {
96 | authentication(userName: ossrhUsername, password: ossrhPassword)
97 | }
98 |
99 | snapshotRepository(url: "https://oss.sonatype.org/content/repositories/snapshots/") {
100 | authentication(userName: ossrhUsername, password: ossrhPassword)
101 | }
102 |
103 | pom.project {
104 | name 'Linear Layout Manager'
105 | packaging 'aar'
106 | description 'Linear Layout Manager which supports WRAP_CONTENT'
107 | url 'https://github.com/serso/android-linear-layout-manager'
108 |
109 | scm {
110 | url 'https://github.com/serso/android-linear-layout-manager'
111 | connection 'scm:https://serso@github.com/serso/android-linear-layout-manager.git'
112 | developerConnection 'scm:git://github.com/serso/android-linear-layout-manager.git'
113 | }
114 |
115 | licenses {
116 | license {
117 | name 'The Apache Software License, Version 2.0'
118 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
119 | distribution 'repo'
120 | }
121 | }
122 |
123 | developers {
124 | developer {
125 | id 'se.solovyev'
126 | name 'Sergey Solovyev'
127 | email 'se.solovyev@gmail.com'
128 | }
129 | }
130 | }
131 | }
132 | }
133 | }
134 |
--------------------------------------------------------------------------------
/lib/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/solovyev/android/views/llm/DividerItemDecoration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm;
25 |
26 | import android.content.Context;
27 | import android.content.res.TypedArray;
28 | import android.graphics.Canvas;
29 | import android.graphics.Rect;
30 | import android.graphics.drawable.Drawable;
31 | import android.support.v7.widget.LinearLayoutManager;
32 | import android.support.v7.widget.RecyclerView;
33 | import android.util.AttributeSet;
34 | import android.view.View;
35 |
36 | public class DividerItemDecoration extends RecyclerView.ItemDecoration {
37 |
38 | private Drawable divider;
39 | private int dividerHeight;
40 | private int dividerWidth;
41 | private boolean first = false;
42 | private boolean last = false;
43 |
44 | @SuppressWarnings("UnusedDeclaration")
45 | public DividerItemDecoration(Context context, AttributeSet attrs) {
46 | final TypedArray a = context.obtainStyledAttributes(attrs, new int[]{android.R.attr.listDivider});
47 | setDivider(a.getDrawable(0));
48 | a.recycle();
49 | }
50 |
51 | private void setDivider(Drawable divider) {
52 | this.divider = divider;
53 | this.dividerHeight = divider == null ? 0 : divider.getIntrinsicHeight();
54 | this.dividerWidth = divider == null ? 0 : divider.getIntrinsicWidth();
55 | }
56 |
57 | @SuppressWarnings("UnusedDeclaration")
58 | public DividerItemDecoration(Context context, AttributeSet attrs, boolean showFirstDivider,
59 | boolean showLastDivider) {
60 | this(context, attrs);
61 | first = showFirstDivider;
62 | last = showLastDivider;
63 | }
64 |
65 | @SuppressWarnings("UnusedDeclaration")
66 | public DividerItemDecoration(Drawable divider) {
67 | setDivider(divider);
68 | }
69 |
70 | @SuppressWarnings("UnusedDeclaration")
71 | public DividerItemDecoration(Drawable divider, boolean showFirstDivider,
72 | boolean showLastDivider) {
73 | this(divider);
74 | first = showFirstDivider;
75 | last = showLastDivider;
76 | }
77 |
78 | @Override
79 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
80 | RecyclerView.State state) {
81 | if (divider == null) {
82 | super.getItemOffsets(outRect, view, parent, state);
83 | return;
84 | }
85 |
86 | final int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewLayoutPosition();
87 | final boolean firstItem = position == 0;
88 | final boolean lastItem = position == parent.getAdapter().getItemCount() - 1;
89 | final boolean dividerBefore = first || !firstItem;
90 | final boolean dividerAfter = last && lastItem;
91 |
92 | if (getOrientation(parent) == LinearLayoutManager.VERTICAL) {
93 | outRect.top = dividerBefore ? dividerHeight : 0;
94 | outRect.bottom = dividerAfter ? dividerHeight : 0;
95 | } else {
96 | outRect.left = dividerBefore ? dividerWidth : 0;
97 | outRect.right = dividerAfter ? dividerWidth : 0;
98 | }
99 | }
100 |
101 | @Override
102 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
103 | if (divider == null) {
104 | super.onDraw(c, parent, state);
105 | return;
106 | }
107 |
108 | int left = 0;
109 | int right = 0;
110 | int top = 0;
111 | int bottom = 0;
112 |
113 | final int orientation = getOrientation(parent);
114 | final int childCount = parent.getChildCount();
115 |
116 | final RecyclerView.Adapter adapter = parent.getAdapter();
117 | final int adapterCount = adapter != null ? adapter.getItemCount() : 0;
118 |
119 | final boolean vertical = orientation == LinearLayoutManager.VERTICAL;
120 | final int size;
121 | if (vertical) {
122 | size = dividerHeight;
123 | left = parent.getPaddingLeft();
124 | right = parent.getWidth() - parent.getPaddingRight();
125 | } else {
126 | size = dividerWidth;
127 | top = parent.getPaddingTop();
128 | bottom = parent.getHeight() - parent.getPaddingBottom();
129 | }
130 |
131 | for (int i = 0; i < childCount; i++) {
132 | final View child = parent.getChildAt(i);
133 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
134 | final int position = params.getViewLayoutPosition();
135 | if (position == 0 && !first) {
136 | continue;
137 | }
138 | if (vertical) {
139 | top = child.getTop() - params.topMargin - size;
140 | bottom = top + size;
141 | } else {
142 | left = child.getLeft() - params.leftMargin - size;
143 | right = left + size;
144 | }
145 | divider.setBounds(left, top, right, bottom);
146 | divider.draw(c);
147 | }
148 |
149 | if (last && childCount > 0) {
150 | final View child = parent.getChildAt(childCount - 1);
151 | final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams();
152 | final int position = params.getViewLayoutPosition();
153 | if (position == adapterCount - 1) {
154 | if (vertical) {
155 | top = child.getBottom() + params.bottomMargin;
156 | bottom = top + size;
157 | } else {
158 | left = child.getRight() + params.rightMargin;
159 | right = left + size;
160 | }
161 | divider.setBounds(left, top, right, bottom);
162 | divider.draw(c);
163 | }
164 | }
165 | }
166 |
167 | private int getOrientation(RecyclerView parent) {
168 | final RecyclerView.LayoutManager lm = parent.getLayoutManager();
169 | if (lm instanceof LinearLayoutManager) {
170 | return ((LinearLayoutManager) lm).getOrientation();
171 | } else {
172 | throw new IllegalStateException("DividerItemDecoration can only be used with a LinearLayoutManager");
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/lib/src/main/java/org/solovyev/android/views/llm/LinearLayoutManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2015 serso aka se.solovyev
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | *
16 | * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17 | *
18 | * Contact details
19 | *
20 | * Email: se.solovyev@gmail.com
21 | * Site: http://se.solovyev.org
22 | */
23 |
24 | package org.solovyev.android.views.llm;
25 |
26 | import android.content.Context;
27 | import android.graphics.Rect;
28 | import android.support.v4.view.ViewCompat;
29 | import android.support.v7.widget.RecyclerView;
30 | import android.util.Log;
31 | import android.view.View;
32 |
33 | import java.lang.reflect.Field;
34 |
35 | /**
36 | * {@link android.support.v7.widget.LinearLayoutManager} which wraps its content. Note that this class will always
37 | * wrap the content regardless of {@link android.support.v7.widget.RecyclerView} layout parameters.
38 | *
39 | * Now it's impossible to run add/remove animations with child views which have arbitrary dimensions (height for
40 | * VERTICAL orientation and width for HORIZONTAL). However if child views have fixed dimensions
41 | * {@link #setChildSize(int)} method might be used to let the layout manager know how big they are going to be.
42 | * If animations are not used at all then a normal measuring procedure will run and child views will be measured during
43 | * the measure pass.
44 | */
45 | public class LinearLayoutManager extends android.support.v7.widget.LinearLayoutManager {
46 |
47 | private static boolean canMakeInsetsDirty = true;
48 | private static Field insetsDirtyField = null;
49 |
50 | private static final int CHILD_WIDTH = 0;
51 | private static final int CHILD_HEIGHT = 1;
52 | private static final int DEFAULT_CHILD_SIZE = 100;
53 |
54 | private final int[] childDimensions = new int[2];
55 | private final RecyclerView view;
56 |
57 | private int childSize = DEFAULT_CHILD_SIZE;
58 | private boolean hasChildSize;
59 | private int overScrollMode = ViewCompat.OVER_SCROLL_ALWAYS;
60 | private final Rect tmpRect = new Rect();
61 |
62 | @SuppressWarnings("UnusedDeclaration")
63 | public LinearLayoutManager(Context context) {
64 | super(context);
65 | this.view = null;
66 | }
67 |
68 | @SuppressWarnings("UnusedDeclaration")
69 | public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
70 | super(context, orientation, reverseLayout);
71 | this.view = null;
72 | }
73 |
74 | @SuppressWarnings("UnusedDeclaration")
75 | public LinearLayoutManager(RecyclerView view) {
76 | super(view.getContext());
77 | this.view = view;
78 | this.overScrollMode = ViewCompat.getOverScrollMode(view);
79 | }
80 |
81 | @SuppressWarnings("UnusedDeclaration")
82 | public LinearLayoutManager(RecyclerView view, int orientation, boolean reverseLayout) {
83 | super(view.getContext(), orientation, reverseLayout);
84 | this.view = view;
85 | this.overScrollMode = ViewCompat.getOverScrollMode(view);
86 | }
87 |
88 | public void setOverScrollMode(int overScrollMode) {
89 | if (overScrollMode < ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode > ViewCompat.OVER_SCROLL_NEVER)
90 | throw new IllegalArgumentException("Unknown overscroll mode: " + overScrollMode);
91 | if (this.view == null) throw new IllegalStateException("view == null");
92 | this.overScrollMode = overScrollMode;
93 | ViewCompat.setOverScrollMode(view, overScrollMode);
94 | }
95 |
96 | public static int makeUnspecifiedSpec() {
97 | return View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
98 | }
99 |
100 | @Override
101 | public void onMeasure(RecyclerView.Recycler recycler, RecyclerView.State state, int widthSpec, int heightSpec) {
102 | final int widthMode = View.MeasureSpec.getMode(widthSpec);
103 | final int heightMode = View.MeasureSpec.getMode(heightSpec);
104 |
105 | final int widthSize = View.MeasureSpec.getSize(widthSpec);
106 | final int heightSize = View.MeasureSpec.getSize(heightSpec);
107 |
108 | final boolean hasWidthSize = widthMode != View.MeasureSpec.UNSPECIFIED;
109 | final boolean hasHeightSize = heightMode != View.MeasureSpec.UNSPECIFIED;
110 |
111 | final boolean exactWidth = widthMode == View.MeasureSpec.EXACTLY;
112 | final boolean exactHeight = heightMode == View.MeasureSpec.EXACTLY;
113 |
114 | final int unspecified = makeUnspecifiedSpec();
115 |
116 | if (exactWidth && exactHeight) {
117 | // in case of exact calculations for both dimensions let's use default "onMeasure" implementation
118 | super.onMeasure(recycler, state, widthSpec, heightSpec);
119 | return;
120 | }
121 |
122 | final boolean vertical = getOrientation() == VERTICAL;
123 |
124 | initChildDimensions(widthSize, heightSize, vertical);
125 |
126 | int width = 0;
127 | int height = 0;
128 |
129 | // it's possible to get scrap views in recycler which are bound to old (invalid) adapter entities. This
130 | // happens because their invalidation happens after "onMeasure" method. As a workaround let's clear the
131 | // recycler now (it should not cause any performance issues while scrolling as "onMeasure" is never
132 | // called whiles scrolling)
133 | recycler.clear();
134 |
135 | final int stateItemCount = state.getItemCount();
136 | final int adapterItemCount = getItemCount();
137 | // adapter always contains actual data while state might contain old data (f.e. data before the animation is
138 | // done). As we want to measure the view with actual data we must use data from the adapter and not from the
139 | // state
140 | for (int i = 0; i < adapterItemCount; i++) {
141 | if (vertical) {
142 | if (!hasChildSize) {
143 | if (i < stateItemCount) {
144 | // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
145 | // we will use previously calculated dimensions
146 | measureChild(recycler, i, widthSize, unspecified, childDimensions);
147 | } else {
148 | logMeasureWarning(i);
149 | }
150 | }
151 | height += childDimensions[CHILD_HEIGHT];
152 | if (i == 0) {
153 | width = childDimensions[CHILD_WIDTH];
154 | }
155 | if (hasHeightSize && height >= heightSize) {
156 | break;
157 | }
158 | } else {
159 | if (!hasChildSize) {
160 | if (i < stateItemCount) {
161 | // we should not exceed state count, otherwise we'll get IndexOutOfBoundsException. For such items
162 | // we will use previously calculated dimensions
163 | measureChild(recycler, i, unspecified, heightSize, childDimensions);
164 | } else {
165 | logMeasureWarning(i);
166 | }
167 | }
168 | width += childDimensions[CHILD_WIDTH];
169 | if (i == 0) {
170 | height = childDimensions[CHILD_HEIGHT];
171 | }
172 | if (hasWidthSize && width >= widthSize) {
173 | break;
174 | }
175 | }
176 | }
177 |
178 | if (exactWidth) {
179 | width = widthSize;
180 | } else {
181 | width += getPaddingLeft() + getPaddingRight();
182 | if (hasWidthSize) {
183 | width = Math.min(width, widthSize);
184 | }
185 | }
186 |
187 | if (exactHeight) {
188 | height = heightSize;
189 | } else {
190 | height += getPaddingTop() + getPaddingBottom();
191 | if (hasHeightSize) {
192 | height = Math.min(height, heightSize);
193 | }
194 | }
195 |
196 | setMeasuredDimension(width, height);
197 |
198 | if (view != null && overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS) {
199 | final boolean fit = (vertical && (!hasHeightSize || height < heightSize))
200 | || (!vertical && (!hasWidthSize || width < widthSize));
201 |
202 | ViewCompat.setOverScrollMode(view, fit ? ViewCompat.OVER_SCROLL_NEVER : ViewCompat.OVER_SCROLL_ALWAYS);
203 | }
204 | }
205 |
206 | private void logMeasureWarning(int child) {
207 | if (BuildConfig.DEBUG) {
208 | Log.w("LinearLayoutManager", "Can't measure child #" + child + ", previously used dimensions will be reused." +
209 | "To remove this message either use #setChildSize() method or don't run RecyclerView animations");
210 | }
211 | }
212 |
213 | private void initChildDimensions(int width, int height, boolean vertical) {
214 | if (childDimensions[CHILD_WIDTH] != 0 || childDimensions[CHILD_HEIGHT] != 0) {
215 | // already initialized, skipping
216 | return;
217 | }
218 | if (vertical) {
219 | childDimensions[CHILD_WIDTH] = width;
220 | childDimensions[CHILD_HEIGHT] = childSize;
221 | } else {
222 | childDimensions[CHILD_WIDTH] = childSize;
223 | childDimensions[CHILD_HEIGHT] = height;
224 | }
225 | }
226 |
227 | @Override
228 | public void setOrientation(int orientation) {
229 | // might be called before the constructor of this class is called
230 | //noinspection ConstantConditions
231 | if (childDimensions != null) {
232 | if (getOrientation() != orientation) {
233 | childDimensions[CHILD_WIDTH] = 0;
234 | childDimensions[CHILD_HEIGHT] = 0;
235 | }
236 | }
237 | super.setOrientation(orientation);
238 | }
239 |
240 | public void clearChildSize() {
241 | hasChildSize = false;
242 | setChildSize(DEFAULT_CHILD_SIZE);
243 | }
244 |
245 | public void setChildSize(int childSize) {
246 | hasChildSize = true;
247 | if (this.childSize != childSize) {
248 | this.childSize = childSize;
249 | requestLayout();
250 | }
251 | }
252 |
253 | private void measureChild(RecyclerView.Recycler recycler, int position, int widthSize, int heightSize, int[] dimensions) {
254 | final View child;
255 | try {
256 | child = recycler.getViewForPosition(position);
257 | } catch (IndexOutOfBoundsException e) {
258 | if (BuildConfig.DEBUG) {
259 | Log.w("LinearLayoutManager", "LinearLayoutManager doesn't work well with animations. Consider switching them off", e);
260 | }
261 | return;
262 | }
263 |
264 | final RecyclerView.LayoutParams p = (RecyclerView.LayoutParams) child.getLayoutParams();
265 |
266 | final int hPadding = getPaddingLeft() + getPaddingRight();
267 | final int vPadding = getPaddingTop() + getPaddingBottom();
268 |
269 | final int hMargin = p.leftMargin + p.rightMargin;
270 | final int vMargin = p.topMargin + p.bottomMargin;
271 |
272 | // we must make insets dirty in order calculateItemDecorationsForChild to work
273 | makeInsetsDirty(p);
274 | // this method should be called before any getXxxDecorationXxx() methods
275 | calculateItemDecorationsForChild(child, tmpRect);
276 |
277 | final int hDecoration = getRightDecorationWidth(child) + getLeftDecorationWidth(child);
278 | final int vDecoration = getTopDecorationHeight(child) + getBottomDecorationHeight(child);
279 |
280 | final int childWidthSpec = getChildMeasureSpec(widthSize, hPadding + hMargin + hDecoration, p.width, canScrollHorizontally());
281 | final int childHeightSpec = getChildMeasureSpec(heightSize, vPadding + vMargin + vDecoration, p.height, canScrollVertically());
282 |
283 | child.measure(childWidthSpec, childHeightSpec);
284 |
285 | dimensions[CHILD_WIDTH] = getDecoratedMeasuredWidth(child) + p.leftMargin + p.rightMargin;
286 | dimensions[CHILD_HEIGHT] = getDecoratedMeasuredHeight(child) + p.bottomMargin + p.topMargin;
287 |
288 | // as view is recycled let's not keep old measured values
289 | makeInsetsDirty(p);
290 | recycler.recycleView(child);
291 | }
292 |
293 | private static void makeInsetsDirty(RecyclerView.LayoutParams p) {
294 | if (!canMakeInsetsDirty) {
295 | return;
296 | }
297 | try {
298 | if (insetsDirtyField == null) {
299 | insetsDirtyField = RecyclerView.LayoutParams.class.getDeclaredField("mInsetsDirty");
300 | insetsDirtyField.setAccessible(true);
301 | }
302 | insetsDirtyField.set(p, true);
303 | } catch (NoSuchFieldException e) {
304 | onMakeInsertDirtyFailed();
305 | } catch (IllegalAccessException e) {
306 | onMakeInsertDirtyFailed();
307 | }
308 | }
309 |
310 | private static void onMakeInsertDirtyFailed() {
311 | canMakeInsetsDirty = false;
312 | if (BuildConfig.DEBUG) {
313 | Log.w("LinearLayoutManager", "Can't make LayoutParams insets dirty, decorations measurements might be incorrect");
314 | }
315 | }
316 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':lib'
2 | include ':app'
3 |
--------------------------------------------------------------------------------