├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── LICENCE ├── README.md ├── ScrollHandleExample.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── werdpressed │ │ └── partisan │ │ └── scrollhandleexample │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── ic_cheeses_app_bar-web.png │ ├── java │ └── com │ │ └── werdpressed │ │ └── partisan │ │ └── scrollhandleexample │ │ ├── AppBarManager.java │ │ ├── Cheeses.java │ │ ├── ConfigurableRecyclerView.java │ │ ├── MainActivity.java │ │ ├── RecyclerFragment.java │ │ ├── RecyclerFragmentAdapter.java │ │ └── RecyclerLayoutManager.java │ └── res │ ├── drawable-hdpi │ ├── ic_lock_open_white_24dp.png │ └── ic_lock_outline_white_24dp.png │ ├── drawable-mdpi │ ├── ic_lock_open_white_24dp.png │ └── ic_lock_outline_white_24dp.png │ ├── drawable-xhdpi │ ├── ic_lock_open_white_24dp.png │ └── ic_lock_outline_white_24dp.png │ ├── drawable-xxhdpi │ ├── ic_lock_open_white_24dp.png │ └── ic_lock_outline_white_24dp.png │ ├── drawable │ ├── cheeses.jpg │ └── drop_shadow.xml │ ├── layout │ ├── activity_main.xml │ ├── rv_fragment.xml │ └── rv_row.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 │ ├── mipmap-xxxhdpi │ └── ic_cheeses_app_bar.png │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | ScrollHandleExample -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Tom Calver 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ScrollHandleExample 2 | 3 | [![YouTube: Scroll Handle Example](http://img.youtube.com/vi/YgcmQS_zPTY/0.jpg)](http://www.youtube.com/watch?v=YgcmQS_zPTY "YouTube: Scroll Handle Example") 4 | 5 | Demonstrates how to progammatically scroll a `RecyclerView` by manipulating a `View` with a `TouchListener`. Although only a `RecyclerView` is shown in the code, the same method works just as well with `NestedScrollView`. 6 | 7 | The method can be especially useful when constructing layouts that make use of `CollapsingToolbarLayout` from the Android Design Support Library. This is because layout elements beyond the `RecyclerView` can be added below the collapsing toolbar, yet still cause it to collapse and expand based on touch events in the same way as a `RecyclerView` or `NestedScrollView`. 8 | 9 | A demonstration video is on [YouTube](https://www.youtube.com/watch?v=YgcmQS_zPTY&feature=youtu.be "YoutTube: Scroll Handle Example") and a blog post at [Partisan Apps](http://partisanapps.com/2015/10/trigger-nested-scrolls-with-custom-views/ "Partisan Apps: Blog") that goes into more detail. 10 | -------------------------------------------------------------------------------- /ScrollHandleExample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 23 5 | buildToolsVersion "23.0.1" 6 | 7 | defaultConfig { 8 | applicationId "com.werdpressed.partisan.scrollhandleexample" 9 | minSdkVersion 15 10 | targetSdkVersion 23 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 | compile 'com.android.support:appcompat-v7:23.4.0' 25 | compile 'com.android.support:recyclerview-v7:23.4.0' 26 | compile 'com.android.support:design:23.4.0' 27 | } 28 | -------------------------------------------------------------------------------- /app/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/tom/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/werdpressed/partisan/scrollhandleexample/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/ic_cheeses_app_bar-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/ic_cheeses_app_bar-web.png -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/AppBarManager.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | /** 4 | * Inspired by http://novoda.com/blog/fixing-hiding-appbarlayout-android-tv/ 5 | */ 6 | 7 | public interface AppBarManager { 8 | 9 | void collapseAppBar(); 10 | void expandAppBar(); 11 | int getVisibleHeightForRecyclerViewInPx(); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/Cheeses.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | public final class Cheeses { 4 | 5 | private Cheeses() { throw new AssertionError(); } 6 | 7 | public static final String[] CHEESES = { 8 | "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi", 9 | "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale", 10 | "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese", 11 | "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell", 12 | "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc", 13 | "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss", 14 | "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon", 15 | "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase", 16 | "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese", 17 | "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy", 18 | "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille", 19 | "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore", 20 | "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)", 21 | "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves", 22 | "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur", 23 | "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon", 24 | "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin", 25 | "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)", 26 | "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine", 27 | "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza", 28 | "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)", 29 | "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta", 30 | "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie", 31 | "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat", 32 | "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano", 33 | "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain", 34 | "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou", 35 | "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar", 36 | "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno", 37 | "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack", 38 | "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper", 39 | "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)", 40 | "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese", 41 | "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza", 42 | "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley", 43 | "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino", 44 | "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina", 45 | "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby", 46 | "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin", 47 | "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester", 48 | "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue", 49 | "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz", 50 | "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich", 51 | "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue", 52 | "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle", 53 | "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia", 54 | "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis", 55 | "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus", 56 | "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison", 57 | "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois", 58 | "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse", 59 | "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese", 60 | "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise", 61 | "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra", 62 | "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola", 63 | "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost", 64 | "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel", 65 | "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve", 66 | "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi", 67 | "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti", 68 | "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve", 69 | "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster", 70 | "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg", 71 | "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa", 72 | "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine", 73 | "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese", 74 | "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere", 75 | "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire", 76 | "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou", 77 | "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger", 78 | "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings", 79 | "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse", 80 | "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam", 81 | "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego", 82 | "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin", 83 | "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)", 84 | "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse", 85 | "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda", 86 | "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte", 87 | "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio", 88 | "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne", 89 | "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)", 90 | "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster", 91 | "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel", 92 | "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca", 93 | "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre", 94 | "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty", 95 | "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela", 96 | "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano", 97 | "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage", 98 | "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry", 99 | "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid", 100 | "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn", 101 | "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse", 102 | "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin", 103 | "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin", 104 | "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre", 105 | "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone", 106 | "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark", 107 | "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit", 108 | "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia", 109 | "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)", 110 | "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna", 111 | "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera", 112 | "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou", 113 | "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder", 114 | "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort", 115 | "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr", 116 | "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin", 117 | "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre", 118 | "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss", 119 | "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela", 120 | "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda", 121 | "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain", 122 | "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese", 123 | "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale", 124 | "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie", 125 | "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri", 126 | "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar", 127 | "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance", 128 | "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes", 129 | "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet", 130 | "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe", 131 | "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa", 132 | "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois", 133 | "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue", 134 | "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington", 135 | "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou", 136 | "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue", 137 | "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano" 138 | }; 139 | } 140 | 141 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/ConfigurableRecyclerView.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | 8 | public class ConfigurableRecyclerView extends RecyclerView { 9 | 10 | private boolean isScrollingActive; 11 | 12 | private int y; 13 | 14 | public ConfigurableRecyclerView(Context context) { 15 | super(context); 16 | } 17 | 18 | public ConfigurableRecyclerView(Context context, AttributeSet attrs) { 19 | super(context, attrs); 20 | } 21 | 22 | @Override 23 | public boolean onTouchEvent(MotionEvent e) { 24 | return isScrollingActive || super.onTouchEvent(e); 25 | } 26 | 27 | //Enables/Disables scrolling through touch interaction with the RecyclerView directly 28 | public void setScrollingActive(boolean isScrollingActive) { 29 | this.isScrollingActive = isScrollingActive; 30 | } 31 | 32 | //Responsible for starting a programmatic scroll 33 | public boolean dispatchHandlerScroll(MotionEvent e) { 34 | switch (e.getAction()) { 35 | case MotionEvent.ACTION_DOWN: 36 | y = (int) e.getY(); 37 | startNestedScroll(2); 38 | break; 39 | case MotionEvent.ACTION_MOVE: 40 | int dY = y - ((int)e.getY()); 41 | dispatchNestedPreScroll(0, dY, null, null); 42 | dispatchNestedScroll(0, 0, 0, dY, null); 43 | break; 44 | case MotionEvent.ACTION_UP: 45 | stopNestedScroll(); 46 | break; 47 | } 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.AppBarLayout; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.Toolbar; 7 | import android.view.Menu; 8 | import android.view.MenuItem; 9 | import android.view.MotionEvent; 10 | import android.widget.Toast; 11 | 12 | public class MainActivity extends AppCompatActivity implements AppBarManager{ 13 | 14 | private Toolbar mToolbar; 15 | private RecyclerFragment mRecyclerFragment; 16 | private AppBarLayout mAppBarLayout; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | setContentView(R.layout.activity_main); 23 | 24 | mToolbar = (Toolbar) findViewById(R.id.toolbar); 25 | setSupportActionBar(mToolbar); 26 | 27 | mAppBarLayout = (AppBarLayout) findViewById(R.id.app_bar); 28 | 29 | if (getSupportFragmentManager().findFragmentById(R.id.rv_container) == null) { 30 | mRecyclerFragment = RecyclerFragment.newInstance(); 31 | getSupportFragmentManager() 32 | .beginTransaction() 33 | .add(R.id.rv_container, mRecyclerFragment, RecyclerFragment.TAG) 34 | .commit(); 35 | } 36 | } 37 | 38 | @Override 39 | public boolean onCreateOptionsMenu(Menu menu) { 40 | getMenuInflater().inflate(R.menu.menu_main, menu); 41 | return true; 42 | } 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | 47 | if (mRecyclerFragment == null) { 48 | mRecyclerFragment = 49 | (RecyclerFragment) getSupportFragmentManager() 50 | .findFragmentByTag(RecyclerFragment.TAG); 51 | } 52 | 53 | int id = item.getItemId(); 54 | switch (id) { 55 | case R.id.action_lock: 56 | makeToast(getString(R.string.action_lock)); 57 | mRecyclerFragment.getConfigurableRecyclerView().setScrollingActive(true); 58 | break; 59 | case R.id.action_unlock: 60 | makeToast(getString(R.string.action_unlock)); 61 | mRecyclerFragment.getConfigurableRecyclerView().setScrollingActive(false); 62 | break; 63 | } 64 | 65 | return super.onOptionsItemSelected(item); 66 | } 67 | 68 | @Override 69 | public boolean dispatchTouchEvent(MotionEvent event) { 70 | if (event.getAction() == MotionEvent.ACTION_UP) { 71 | float per = Math.abs(mAppBarLayout.getY()) / mAppBarLayout.getTotalScrollRange(); 72 | boolean setExpanded = (per <= 0.5F); 73 | mAppBarLayout.setExpanded(setExpanded, true); 74 | } 75 | return super.dispatchTouchEvent(event); 76 | } 77 | 78 | private void makeToast(String content) { 79 | Toast.makeText(this, content, Toast.LENGTH_SHORT).show(); 80 | } 81 | 82 | @Override 83 | public void collapseAppBar() { 84 | mAppBarLayout.setExpanded(false, true); 85 | } 86 | 87 | @Override 88 | public void expandAppBar() { 89 | mAppBarLayout.setExpanded(true, true); 90 | } 91 | 92 | @Override 93 | public int getVisibleHeightForRecyclerViewInPx() { 94 | 95 | if (mRecyclerFragment == null) mRecyclerFragment = 96 | (RecyclerFragment) getSupportFragmentManager().findFragmentByTag(RecyclerFragment.TAG); 97 | 98 | int windowHeight, appBarHeight, headerViewHeight; 99 | windowHeight = getWindow().getDecorView().getHeight(); 100 | appBarHeight = mAppBarLayout.getHeight(); 101 | headerViewHeight = mRecyclerFragment.getHeaderView().getHeight(); 102 | return windowHeight - (appBarHeight + headerViewHeight); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/RecyclerFragment.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.support.v7.widget.LinearLayoutManager; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.Log; 10 | import android.view.KeyEvent; 11 | import android.view.LayoutInflater; 12 | import android.view.MotionEvent; 13 | import android.view.View; 14 | import android.view.ViewGroup; 15 | import android.view.inputmethod.EditorInfo; 16 | import android.widget.Button; 17 | import android.widget.EditText; 18 | import android.widget.TextView; 19 | import android.widget.Toast; 20 | 21 | public class RecyclerFragment extends Fragment { 22 | 23 | public static final String TAG = "RecyclerFragment"; 24 | 25 | private View rootView; 26 | private View headerView; 27 | 28 | private ConfigurableRecyclerView mRecyclerView; 29 | private RecyclerFragmentAdapter mAdapter; 30 | private RecyclerLayoutManager mLayoutManager; 31 | 32 | private int maxAdapterPosition; 33 | 34 | private TextView mDragTextView; 35 | private EditText mScrollToEntry; 36 | private Button mScrollToBtn; 37 | 38 | public static RecyclerFragment newInstance() { 39 | return new RecyclerFragment(); 40 | } 41 | 42 | @Nullable 43 | @Override 44 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 45 | 46 | rootView = inflater.inflate(R.layout.rv_fragment, container, false); 47 | 48 | headerView = rootView.findViewById(R.id.rvf_header_parent); 49 | 50 | mRecyclerView = (ConfigurableRecyclerView) rootView.findViewById(R.id.rvf_recycler_view); 51 | mAdapter = new RecyclerFragmentAdapter(); 52 | mLayoutManager = new RecyclerLayoutManager(getActivity()); 53 | mRecyclerView.setAdapter(mAdapter); 54 | mRecyclerView.setLayoutManager(mLayoutManager); 55 | mRecyclerView.setHasFixedSize(true); 56 | 57 | mLayoutManager.setAppBarManager((AppBarManager) getActivity()); 58 | 59 | maxAdapterPosition = mAdapter.getItemCount() - 1; 60 | 61 | mDragTextView = (TextView) rootView.findViewById(R.id.rvf_drag_text_view); 62 | mDragTextView.setOnTouchListener(new View.OnTouchListener() { 63 | @Override 64 | public boolean onTouch(View v, MotionEvent event) { 65 | TextView tv = (TextView) v; 66 | 67 | switch (event.getAction()) { 68 | case MotionEvent.ACTION_DOWN: 69 | tv.setBackgroundColor(Color.LTGRAY); 70 | tv.setText(getString(R.string.drag_message_active)); 71 | break; 72 | case MotionEvent.ACTION_UP: 73 | v.setBackgroundColor(0); 74 | tv.setText(getString(R.string.drag_message_idle)); 75 | break; 76 | } 77 | return mRecyclerView.dispatchHandlerScroll(event); 78 | } 79 | }); 80 | 81 | String scrollToEntryHintText = getString(R.string.rvf_scroll_to_value_et, maxAdapterPosition); 82 | mScrollToEntry = (EditText) rootView.findViewById(R.id.rvf_et); 83 | mScrollToEntry.setHint(scrollToEntryHintText); 84 | mScrollToEntry.setOnEditorActionListener(new TextView.OnEditorActionListener() { 85 | @Override 86 | public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { 87 | if (actionId == EditorInfo.IME_ACTION_GO) { 88 | mScrollToBtn.performClick(); 89 | } 90 | return false; 91 | } 92 | }); 93 | 94 | mScrollToBtn = (Button) rootView.findViewById(R.id.rvf_scroll_to_btn); 95 | mScrollToBtn.setOnClickListener(new View.OnClickListener() { 96 | @Override 97 | public void onClick(View v) { 98 | int target; 99 | 100 | try { 101 | target = Integer.valueOf(mScrollToEntry.getText().toString()); 102 | } catch (NumberFormatException e) { 103 | makeToast("Entry must be of type int"); 104 | return; 105 | } 106 | 107 | if ((target < 0) || (target > mAdapter.getItemCount() - 1)) { 108 | makeToast("Entry must range from 0 to " + maxAdapterPosition); 109 | return; 110 | } 111 | 112 | mRecyclerView.smoothScrollToPosition(target); 113 | } 114 | }); 115 | 116 | return rootView; 117 | } 118 | 119 | public View getHeaderView() { 120 | return headerView; 121 | } 122 | 123 | public ConfigurableRecyclerView getConfigurableRecyclerView() { 124 | return mRecyclerView; 125 | } 126 | 127 | private void makeToast(String content) { 128 | Toast.makeText(getActivity(), content, Toast.LENGTH_SHORT).show(); 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/RecyclerFragmentAdapter.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.support.design.widget.AppBarLayout; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | public class RecyclerFragmentAdapter extends RecyclerView.Adapter { 11 | 12 | private final String[] data; 13 | 14 | public RecyclerFragmentAdapter() { 15 | data = Cheeses.CHEESES; 16 | } 17 | 18 | @Override 19 | public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) { 20 | View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.rv_row, viewGroup, false); 21 | return new ViewHolder(v); 22 | } 23 | 24 | @Override 25 | public void onBindViewHolder(ViewHolder viewHolder, int i) { 26 | viewHolder.mTextView.setText(data[i]); 27 | } 28 | 29 | @Override 30 | public int getItemCount() { 31 | return data.length; 32 | } 33 | 34 | public static class ViewHolder extends RecyclerView.ViewHolder { 35 | 36 | private TextView mTextView; 37 | 38 | public ViewHolder(View itemView) { 39 | super(itemView); 40 | mTextView = (TextView) itemView.findViewById(R.id.rvr_text); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/werdpressed/partisan/scrollhandleexample/RecyclerLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.werdpressed.partisan.scrollhandleexample; 2 | 3 | import android.content.Context; 4 | import android.graphics.PointF; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.LinearSmoothScroller; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.util.Log; 9 | import android.view.View; 10 | 11 | public class RecyclerLayoutManager extends LinearLayoutManager { 12 | 13 | private AppBarManager mAppBarManager; 14 | private int visibleHeightForRecyclerView; 15 | 16 | public RecyclerLayoutManager(Context context) { 17 | super(context); 18 | } 19 | 20 | @Override 21 | public void smoothScrollToPosition(RecyclerView recyclerView, RecyclerView.State state, int position) { 22 | View firstVisibleChild = recyclerView.getChildAt(0); 23 | final int childHeight = firstVisibleChild.getHeight(); 24 | int distanceInPixels = ((findFirstVisibleItemPosition() - position) * childHeight); 25 | if (distanceInPixels == 0) { 26 | distanceInPixels = (int) Math.abs(firstVisibleChild.getY()); 27 | } 28 | //Called Once 29 | if (visibleHeightForRecyclerView == 0) { 30 | visibleHeightForRecyclerView = mAppBarManager.getVisibleHeightForRecyclerViewInPx(); 31 | } 32 | //Subtract one as adapter position 0 based 33 | final int visibleChildCount = visibleHeightForRecyclerView/childHeight - 1; 34 | 35 | if (position <= visibleChildCount) { 36 | //Scroll to the very top and expand the app bar 37 | position = 0; 38 | mAppBarManager.expandAppBar(); 39 | } else { 40 | mAppBarManager.collapseAppBar(); 41 | } 42 | 43 | SmoothScroller smoothScroller = new SmoothScroller(recyclerView.getContext(), Math.abs(distanceInPixels), 1000); 44 | smoothScroller.setTargetPosition(position); 45 | startSmoothScroll(smoothScroller); 46 | } 47 | 48 | public void setAppBarManager(AppBarManager appBarManager) { 49 | mAppBarManager = appBarManager; 50 | } 51 | 52 | private class SmoothScroller extends LinearSmoothScroller { 53 | private static final int TARGET_SEEK_SCROLL_DISTANCE_PX = 10000; 54 | private final float distanceInPixels; 55 | private final float duration; 56 | 57 | public SmoothScroller(Context context, int distanceInPixels, int duration) { 58 | super(context); 59 | this.distanceInPixels = distanceInPixels; 60 | float millisecondsPerPx = calculateSpeedPerPixel(context.getResources().getDisplayMetrics()); 61 | this.duration = distanceInPixels < TARGET_SEEK_SCROLL_DISTANCE_PX ? 62 | (int) (Math.abs(distanceInPixels) * millisecondsPerPx) : duration; 63 | } 64 | 65 | @Override 66 | public PointF computeScrollVectorForPosition(int targetPosition) { 67 | return RecyclerLayoutManager.this 68 | .computeScrollVectorForPosition(targetPosition); 69 | } 70 | 71 | @Override 72 | protected int calculateTimeForScrolling(int dx) { 73 | float proportion = (float) dx / distanceInPixels; 74 | return (int) (duration * proportion); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-hdpi/ic_lock_open_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-hdpi/ic_lock_outline_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-mdpi/ic_lock_open_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-mdpi/ic_lock_outline_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-xhdpi/ic_lock_open_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-xhdpi/ic_lock_outline_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-xxhdpi/ic_lock_open_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable-xxhdpi/ic_lock_outline_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/cheeses.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PPartisan/ScrollHandleExample/dc0beb9ecd7535ef7de352c036618f7298a0523b/app/src/main/res/drawable/cheeses.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/drop_shadow.xml: -------------------------------------------------------------------------------- 1 | 3 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 14 | 15 | 21 | 22 | 29 | 30 | 31 | 39 | 40 | 41 | 42 | 43 | 44 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /app/src/main/res/layout/rv_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | 21 | 22 | 27 | 28 |