├── .circleci └── config.yml ├── .github └── workflows │ └── codeql-analysis.yml ├── .gitignore ├── .idea ├── caches │ └── build_file_checksums.ser ├── codeStyles │ └── Project.xml ├── gradle.xml ├── misc.xml ├── runConfigurations.xml └── vcs.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── release │ ├── app-release.apk │ └── output.json └── src │ ├── androidTest │ └── java │ │ └── easyadapter │ │ └── dc │ │ └── com │ │ └── easyadapter │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── easyadapter │ │ └── dc │ │ └── com │ │ └── easyadapter │ │ ├── Category.kt │ │ ├── CategoryAdapter.kt │ │ ├── CategoryAdapterJava.java │ │ ├── CountryListActivity.java │ │ ├── MainActivity.kt │ │ ├── MainActivityJava.java │ │ ├── Region.java │ │ ├── TestActivity.java │ │ ├── TransBean.java │ │ └── VideoActivity.java │ └── res │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── dropdown_arrow.png │ ├── edittext_bg_selector.xml │ ├── error.xml │ ├── gradient_drawable.xml │ ├── ic_launcher_background.xml │ └── rect_background.xml │ ├── layout │ ├── activity_country.xml │ ├── activity_main.xml │ ├── activity_test.xml │ ├── activity_video.xml │ ├── inflater_category.xml │ ├── inflater_category_name.xml │ └── layout_progress.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── dclogo.jpg ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── easyadapter-01.png ├── recyclerview-adapter0.png ├── recyclerview-adapter1.png ├── recyclerview-adapter3.png └── recyclerview-adapter4.png ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── easyadapter │ │ └── dc │ │ └── com │ │ └── library │ │ └── ExampleInstrumentedTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── easyadapter │ │ └── dc │ │ └── com │ │ └── library │ │ ├── EasyAdapter.java │ │ ├── EasyArrayAdapter.java │ │ ├── EasyAutoComplete.java │ │ ├── EasySpinner.java │ │ ├── SwipeOpenItemTouchHelper.java │ │ └── SwipeOpenViewHolder.java │ └── res │ ├── anim │ ├── down_in.xml │ ├── down_out.xml │ ├── up_in.xml │ └── up_out.xml │ ├── layout │ └── layout_load_more.xml │ └── values │ ├── ids.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | # Java Gradle CircleCI 2.0 configuration file 2 | # 3 | # Check https://circleci.com/docs/2.0/language-java/ for more details 4 | # 5 | version: 2 6 | jobs: 7 | build: 8 | docker: 9 | # specify the version you desire here 10 | - image: circleci/openjdk:8-jdk 11 | 12 | # Specify service dependencies here if necessary 13 | # CircleCI maintains a library of pre-built images 14 | # documented at https://circleci.com/docs/2.0/circleci-images/ 15 | # - image: circleci/postgres:9.4 16 | 17 | working_directory: ~/repo 18 | docker: 19 | - image: circleci/android:api-27-alpha 20 | environment: 21 | # Customize the JVM maximum heap limit 22 | JVM_OPTS: -Xmx3200m 23 | TERM: dumb 24 | 25 | steps: 26 | - checkout 27 | 28 | # Download and cache dependencies 29 | - restore_cache: 30 | keys: 31 | - v1-dependencies-{{ checksum "build.gradle" }} 32 | # fallback to using the latest cache if no exact match is found 33 | - v1-dependencies- 34 | 35 | - run: 36 | name: Download Dependencies 37 | command: ./gradlew androidDependencies 38 | 39 | - save_cache: 40 | paths: 41 | - ~/.gradle 42 | key: v1-dependencies-{{ checksum "build.gradle" }} 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '43 13 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 15 | 16 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 27 | 28 | 29 | 30 | 31 | 32 | 34 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # EasyAdapter Based on [**Android Data Binding**](https://developer.android.com/topic/libraries/data-binding/index.html) 2 | 3 | ![](images/easyadapter-01.png) 4 | 5 | ### Create your recyclerview adapter in just 3 lines. 6 | 7 | > RecyclerView is mostly used android widgets in the Android Project, for that you have to implement an Adapter which provides the items for the view. In most cases it require the same base logic, but require you to write everything again and again.so here is sollution for it. 8 | 9 | [![N|Solid](https://img.shields.io/badge/Android%20Arsenal-EasyAdapter-brightgreen.svg)](https://android-arsenal.com/details/1/6950) 10 | [![Download](https://api.bintray.com/packages/mkrupal09/EasyAdapter/EasyAdapter/images/download.svg) ](https://bintray.com/mkrupal09/EasyAdapter/EasyAdapter/_latestVersion) 11 | [![Circle 12 | CI](https://circleci.com/gh/mkrupal09/EasyAdapter.svg?style=svg)](https://circleci.com/gh/mkrupal09/EasyAdapter) 13 | 14 | - Reduce Boilerplate code to create adapter and holder. 15 | - you can filter adapter without coding much. 16 | - You wil have load more feature with progress bar at bottom. 17 | - includes swipe to action. 18 | - includes View Events callbacks (ClickEvent,CheckChangeEvent) 19 | - and many more.. 20 | 21 | RecyclerView Adapter LibraryRecyclerView Adapter LibraryRecyclerView Adapter LibraryRecyclerView Adapter Library 22 | 23 | Download 24 | -------- 25 | 26 | app > build.gradle 27 | ```groovy 28 | implementation 'com.github.mkrupal09:EasyAdapter:v2.0.5' 29 | ``` 30 | 31 | root > build.gradle, add jitpack.io maven url in allprojects>repositories 32 | ```groovy 33 | allprojects { 34 | repositories { 35 | ... 36 | maven { url 'https://jitpack.io' } 37 | } 38 | } 39 | ``` 40 | 41 | To enable data binding 42 | ------------------------- 43 | 44 | inside app build.gradle add buildFeatures block(if not) and add dataBinding flag as mentioned below 45 | ```groovy 46 | android { 47 | buildFeatures { 48 | dataBinding true 49 | } 50 | } 51 | ``` 52 | 53 | ## How? 54 | ``` java 55 | adapter = new EasyAdapter(R.layout.inflater_category) { 56 | @Override 57 | public void onBind(@NonNull InflaterCategoryBinding binding, @NonNull Category model) { 58 | binding.tvName.setText(model.name); 59 | } 60 | } 61 | 62 | ``` 63 | 64 | ## Usage 65 | 66 | 67 | ``` kotlin 68 | class CategoryAdapter() :EasyAdapter(R.layout.inflater_category) { 69 | override fun onBind(binding: InflaterCategoryBinding, model: Category) { 70 | binding.apply { 71 | tvName.text = model.name 72 | cbCategory.isChecked = model.isSelected 73 | } 74 | } 75 | } 76 | ``` 77 | ###java 78 | ``` java 79 | public CategoryAdapter() { 80 | super(R.layout.inflater_category); 81 | } 82 | 83 | @Override 84 | public void onBind(@NonNull InflaterCategoryBinding binding, @NonNull Category model) { 85 | binding.tvName.setText(model.name); 86 | } 87 | ``` 88 | 89 | #### 1) To Handle recycler View item Events 90 | 91 | ``` kotlin 92 | //Override in Adapter 93 | override fun onCreatingHolder(binding: InflaterCategoryBinding, easyHolder: EasyHolder) { 94 | super.onCreatingHolder(binding, easyHolder) 95 | binding.root.setOnClickListener(easyHolder.clickListener) 96 | } 97 | ``` 98 | 99 | ``` kotlin 100 | adapter.setRecyclerViewItemClick { itemView, model -> 101 | //Perform Operation here 102 | } 103 | ``` 104 | 105 | #### 2) Filter (Search,etc..) 106 | ``` kotlin 107 | adapter.performFilter(newText,filter) 108 | 109 | val filter= object : EasyAdapter.OnFilter { 110 | override fun onFilterApply(filter: Any, model: Category): Boolean { 111 | return model.name.toLowerCase().contains(filter.toString().toLowerCase()) 112 | } 113 | 114 | override fun onFilterResult(filteredList: ArrayList?) { 115 | adapter.clear(false) 116 | adapter.addAll(filteredList, false) 117 | adapter.notifyDataSetChanged() 118 | } 119 | } 120 | 121 | ``` 122 | 123 | #### 3) Load More 124 | ``` kotlin 125 | adapter.setLoadMoreRes(R.layout.layout_progress) 126 | adapter.setOnLoadMoreListener(binding.recyclerView, EasyAdapter.OnLoadMoreListener { 127 | if (paging != -1) { 128 | requestLoadMore() //Your Method 129 | return@OnLoadMoreListener true // Returns True if you have more data 130 | } 131 | return@OnLoadMoreListener false // Return false if you don't have more data 132 | }) 133 | 134 | ``` 135 | 136 | #### 4) Swipe Action 137 | 138 | ``` kotlin 139 | adapter.enableSwipeAction(binding.recyclerView) 140 | ``` 141 | 142 | ``` kotlin 143 | override fun onCreatingHolder(binding: InflaterCategoryBinding, easyHolder: EasyHolder) { 144 | binding.llDelete.post { 145 | easyHolder.setEnableSwipeToDelete(binding.llCategory, 0, binding.llDelete.measuredWidth) 146 | } 147 | } 148 | 149 | ``` 150 | ``` xml 151 | 154 | 155 | //Swipe Reveal Layout 156 | 163 | 164 | 168 | 169 | 170 | //Your container 171 | 179 | 180 | 181 | ``` 182 | 183 | #### 5) Data Observe 184 | 185 | ``` kotlin 186 | adapter.setOnDataUpdateListener { 187 | if (it.size <= 0) { 188 | Toast.makeText(this@MainActivity, "No Data Found", Toast.LENGTH_SHORT).show() 189 | } 190 | } 191 | ``` 192 | 193 | 194 | Support with spinner 195 | ----------------------------- 196 | 197 | We've Created EasySpinner where you can use your EasyAdapter
198 | No need to write ArrayAdapter or CustomArrayAdapter and you'll have lots of features from EasyAdapter. 199 | 200 | ``` xml 201 | 202 | 212 | 213 | ``` 214 | 215 | Setting adapter to EasySpinner 216 | 217 | ``` kotlin 218 | binding.easyspinner.setAdapter(adapter) 219 | ``` 220 | 221 | you can use spinner as autocomplete textview by using 222 | 223 | ``` kotlin 224 | binding.easyspinner.enableAutoCompleteMode { easySpinner, text -> 225 | adapter.performFilter(text, filter) 226 | } 227 | 228 | ``` 229 | 230 | ******** 231 | 232 | That's it... you will have spinner adapter. 233 | 234 | #### Pro Tips 235 | 236 | 237 | Use tools attribute for previewing Layout, so you don't need to always run application 238 | 239 | **recyclerview** 240 | 241 | ``` xml 242 | tools:listitem="@layout/inflater_category" 243 | tools:itemCount="5" 244 | tools:orientation="horizontal" 245 | app:layoutManager="android.support.v7.widget.GridLayoutManager" 246 | 247 | ``` 248 | 249 | **layout** 250 | 251 | ``` xml 252 | tools:text="Sample Text" 253 | tools:visibility="VISIBLE" 254 | tools:background="@color/colorPrimary" 255 | ``` 256 | 257 | **android predefine sample data** 258 | 259 | ``` xml 260 | tools:text="@tools:sample/cities,first_names,us_phones,lorem,lorem/random" 261 | tools:background="@tools:sample/backgrounds/scenic" 262 | tools:src="@tools/avatars" 263 | ``` 264 | 265 | **custom sample data** 266 | 267 | To create your fake/sample data folder, 268 | just right click on the “app” folder then “new > Sample Data directory”
269 | create new file with "filename" and write each text by new lines 270 | 271 | file contains - 272 | 273 | Georgia
274 | Illinois
275 | Paris
276 | London
277 | 278 | so it will randomly pick names and display in layout by 279 | 280 | ``` xml 281 | tools:text="@sample/filename" 282 | ``` 283 | 284 | [ Changelog ](https://github.com/mkrupal09/EasyAdapter/releases) 285 | 286 | 287 | ### [Special Thanks to]
288 | 289 | https://github.com/alex-townsend/SwipeOpenItemTouchHelper 290 |
291 | without this person cannot achieve swipe action in recyclerview 292 | 293 | 294 | https://android.jlelse.eu/android-tools-attributes-listitem-sample-data-rocks-bbf49aaa9f07
295 | for sharing knowledge of Android Tools attributes 296 | 297 | * Buy me a Beer. :beer: 298 | 299 | 300 | 301 | License 302 | ======= 303 | 304 | Copyright 2013 DC, Inc. 305 | 306 | Licensed under the Apache License, Version 2.0 (the "License"); 307 | you may not use this file except in compliance with the License. 308 | You may obtain a copy of the License at 309 | 310 | http://www.apache.org/licenses/LICENSE-2.0 311 | 312 | Unless required by applicable law or agreed to in writing, software 313 | distributed under the License is distributed on an "AS IS" BASIS, 314 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 315 | See the License for the specific language governing permissions and 316 | limitations under the License. 317 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-kapt' 4 | 5 | android { 6 | compileSdkVersion 28 7 | defaultConfig { 8 | applicationId "easyadapter.dc.com.easyadapter" 9 | minSdkVersion 16 10 | targetSdkVersion 26 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | dataBinding { 22 | enabled = true 23 | } 24 | /* compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | }*/ 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(include: ['*.jar'], dir: 'libs') 36 | implementation 'com.android.support:appcompat-v7:27.1.1' 37 | implementation 'com.android.support.constraint:constraint-layout:1.1.0' 38 | testImplementation 'junit:junit:4.12' 39 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 40 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 41 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 42 | kapt 'com.android.databinding:compiler:3.1.2' 43 | implementation 'com.android.support:recyclerview-v7:27.1.1' 44 | implementation 'com.github.warkiz.widget:indicatorseekbar:2.1.0' 45 | implementation project(':library') 46 | 47 | implementation 'com.android.support:cardview-v7:28.0.0' 48 | implementation 'com.github.bumptech.glide:glide:4.8.0' 49 | annotationProcessor 'com.github.bumptech.glide:compiler:4.8.0' 50 | /*implementation "com.github.mkrupal09:EasyAdapter:9be4f31"*/ 51 | /*implementation 'com.dc.easyadapter:easyadapter:1.2'*/ 52 | 53 | implementation 'com.devbrackets.android:exomedia:4.3.0' 54 | 55 | 56 | } 57 | repositories { 58 | mavenCentral() 59 | maven { url "https://jitpack.io" } 60 | } 61 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/release/app-release.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/release/app-release.apk -------------------------------------------------------------------------------- /app/release/output.json: -------------------------------------------------------------------------------- 1 | [{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":1,"versionName":"1.0","enabled":true,"outputFile":"app-release.apk","fullName":"release","baseName":"release"},"path":"app-release.apk","properties":{}}] -------------------------------------------------------------------------------- /app/src/androidTest/java/easyadapter/dc/com/easyadapter/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("easyadapter.dc.com.easyadapter", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 15 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/Category.kt: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter 2 | 3 | import java.io.Serializable 4 | 5 | /** 6 | * Created by HB on 21/3/18. 7 | */ 8 | 9 | 10 | public class Category : Serializable { 11 | 12 | 13 | var name: String = "" 14 | var id: String = "" 15 | var parentId: String = "" 16 | var image: String = "" 17 | var isSelected: Boolean = false 18 | 19 | companion object { 20 | val BUN_SEL_CATEGORY_LIST = "SEL_CATEGORY_LIST" 21 | const val SEND_OBJECT = "category" 22 | const val SEND_LIST = "category_list" 23 | 24 | /*const val SEND_PARENT_OBJECT = "parent_category"*/ 25 | fun createDummy(name: String): Category { 26 | val category = Category() 27 | category.name = name 28 | category.id = "1" 29 | return category 30 | } 31 | 32 | fun createDummy(name: String, parentId: String, categoryId: String): Category { 33 | val category = Category() 34 | category.name = name 35 | category.parentId = parentId 36 | category.id = categoryId 37 | return category 38 | } 39 | 40 | } 41 | 42 | override fun equals(other: Any?): Boolean { 43 | if (other == null || other !is Category) 44 | return false 45 | return name == other.name && id == other.id 46 | } 47 | 48 | } -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/CategoryAdapter.kt: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter 2 | 3 | import android.widget.ImageView 4 | import com.bumptech.glide.Glide 5 | import easyadapter.dc.com.easyadapter.databinding.InflaterCategoryBinding 6 | import easyadapter.dc.com.library.EasyAdapter 7 | 8 | /** 9 | * Created by Krupal on 21/3/18. 10 | */ 11 | class CategoryAdapter(val enableSwipToDelete: Boolean) : 12 | EasyAdapter(R.layout.inflater_category) { 13 | 14 | override fun onCreatingHolder(binding: InflaterCategoryBinding, baseHolder: EasyHolder) { 15 | super.onCreatingHolder(binding, baseHolder) 16 | binding.cbCategory.setOnCheckedChangeListener(baseHolder.checkedChangeListener) 17 | binding.root.setOnClickListener(baseHolder.clickListener) 18 | if (enableSwipToDelete) { 19 | binding.llDelete.post { 20 | baseHolder.setEnableSwipeToDelete(binding.llCategory, 0, binding.llDelete.measuredWidth) 21 | } 22 | } 23 | 24 | } 25 | 26 | override fun onBind(binding: InflaterCategoryBinding, model: Category) { 27 | } 28 | 29 | override fun onBind(binding: InflaterCategoryBinding, model: Category, holder: EasyHolder?) { 30 | super.onBind(binding, model, holder) 31 | binding.apply { 32 | tvName.text = model.name 33 | tvName.isSelected = model.isSelected 34 | } 35 | binding.ivCategoryIcon.setImageResource(R.drawable.abc_ic_ab_back_material) 36 | 37 | 38 | if (data.indexOf(model) % 2 == 0) { 39 | binding.ivCategoryIcon.layoutParams.height = 100 40 | binding.ivCategoryIcon.scaleType = ImageView.ScaleType.FIT_CENTER 41 | } else { 42 | binding.ivCategoryIcon.layoutParams.height = 500 43 | binding.ivCategoryIcon.scaleType = ImageView.ScaleType.CENTER_CROP 44 | } 45 | 46 | Glide.with(binding.ivCategoryIcon).load("https://www.healthywage.com/wp-content/uploads/2015/11/Kristin-W-BeforeAfter2-540x345.jpg").into(binding.ivCategoryIcon); 47 | helper.startSwipe(holder) 48 | } 49 | } -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/CategoryAdapterJava.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.support.annotation.NonNull; 4 | 5 | import easyadapter.dc.com.easyadapter.databinding.InflaterCategoryBinding; 6 | import easyadapter.dc.com.library.EasyAdapter; 7 | 8 | /** 9 | * Created by HB on 1/10/18. 10 | */ 11 | public class CategoryAdapterJava extends EasyAdapter { 12 | public CategoryAdapterJava() { 13 | super(R.layout.inflater_category); 14 | } 15 | 16 | @Override 17 | public void onBind(@NonNull InflaterCategoryBinding binding, @NonNull Category model) { 18 | binding.tvName.setText(model.getName()); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/CountryListActivity.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import android.support.annotation.Nullable; 7 | import android.support.v7.app.AlertDialog; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.text.TextUtils; 10 | import android.view.View; 11 | 12 | import java.util.ArrayList; 13 | 14 | import easyadapter.dc.com.easyadapter.databinding.ActivityCountryBinding; 15 | import easyadapter.dc.com.easyadapter.databinding.InflaterCategoryNameBinding; 16 | import easyadapter.dc.com.library.EasyAdapter; 17 | import easyadapter.dc.com.library.EasyArrayAdapter; 18 | import easyadapter.dc.com.library.EasyAutoComplete; 19 | 20 | /** 21 | * Created by HB on 11/7/18. 22 | */ 23 | public class CountryListActivity extends AppCompatActivity { 24 | 25 | private ActivityCountryBinding binding; 26 | private Region selectedCountry, selectedCity, selectedState; 27 | private EasyArrayAdapter countryAdapter, stateAdapter, cityAdapter; 28 | 29 | @Override 30 | protected void onCreate(@Nullable Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | binding = DataBindingUtil.setContentView(this, R.layout.activity_country); 33 | 34 | initCountryAdapter(); 35 | 36 | initStateAdapter(); 37 | 38 | initCityAdapter(); 39 | 40 | cityAdapter.addAll(getCities(), true); 41 | stateAdapter.addAll(getStates(), true); 42 | countryAdapter.addAll(getCountries(), true); 43 | 44 | 45 | binding.autocompleteState.enableAutoComplete(true); 46 | binding.autocompleteCity.enableAutoComplete(false); 47 | binding.autocompleteCountry.enableAutoComplete(true); 48 | } 49 | 50 | 51 | private void initCountryAdapter() { 52 | binding.autocompleteCountry.setAdapter(countryAdapter = new EasyArrayAdapter(this, R.layout.inflater_category_name, new EasyAdapter.OnFilter() { 53 | @Override 54 | public boolean onFilterApply(@Nullable Object filter, @NonNull Region model) { 55 | return true; 56 | } 57 | 58 | @Override 59 | public void onFilterResult(ArrayList filteredList) { 60 | 61 | } 62 | }) { 63 | @Override 64 | public void onBind(@NonNull InflaterCategoryNameBinding binding, @NonNull Region model) { 65 | binding.tvName.setText(model.name); 66 | } 67 | }); 68 | binding.autocompleteCountry.setItemSelectionCallback(new EasyAutoComplete.OnItemCallback() { 69 | @Override 70 | public void onItemCallback(int position, View view) { 71 | selectedCountry = countryAdapter.getData().get(position); 72 | binding.autocompleteCountry.setText(selectedCountry.name); 73 | 74 | selectedState = null; 75 | selectedCity = null; 76 | binding.autocompleteState.setText(""); 77 | binding.autocompleteCity.setText(""); 78 | } 79 | }); 80 | } 81 | 82 | private void initStateAdapter() { 83 | binding.autocompleteState.setAdapter(stateAdapter = new EasyArrayAdapter(this, R.layout.inflater_category_name, new EasyAdapter.OnFilter() { 84 | @Override 85 | public boolean onFilterApply(@Nullable Object filter, @NonNull Region model) { 86 | if (model.parentId.equalsIgnoreCase(selectedCountry.id)) { 87 | 88 | if (filter != null && !TextUtils.isEmpty(filter.toString())) { 89 | return model.name.contains(filter.toString()); 90 | } 91 | return true; 92 | } 93 | return false; 94 | } 95 | 96 | @Override 97 | public void onFilterResult(ArrayList filteredList) { 98 | 99 | } 100 | }) { 101 | @Override 102 | public void onBind(@NonNull InflaterCategoryNameBinding binding, @NonNull Region model) { 103 | binding.tvName.setText(model.name); 104 | } 105 | }); 106 | 107 | binding.autocompleteState.setItemSelectionCallback(new EasyAutoComplete.OnItemCallback() { 108 | @Override 109 | public void onItemCallback(int position, View view) { 110 | selectedState = stateAdapter.getData().get(position); 111 | binding.autocompleteState.setText(selectedState.name); 112 | selectedCity = null; 113 | binding.autocompleteCity.setText(""); 114 | 115 | } 116 | }); 117 | } 118 | 119 | private void initCityAdapter() { 120 | binding.autocompleteCity.setAdapter(cityAdapter = new EasyArrayAdapter(this, R.layout.inflater_category_name, new EasyAdapter.OnFilter() { 121 | @Override 122 | public boolean onFilterApply(@Nullable Object filter, @NonNull Region model) { 123 | if (model.parentId.equalsIgnoreCase(selectedState.id)) { 124 | if (filter != null && !TextUtils.isEmpty(filter.toString())) { 125 | return model.name.contains(filter.toString()); 126 | } 127 | return true; 128 | } 129 | return false; 130 | } 131 | 132 | @Override 133 | public void onFilterResult(ArrayList filteredList) { 134 | 135 | } 136 | }) { 137 | @Override 138 | public void onBind(@NonNull InflaterCategoryNameBinding binding, @NonNull Region model) { 139 | binding.tvName.setText(model.name); 140 | } 141 | }); 142 | binding.autocompleteCity.setItemSelectionCallback(new EasyAutoComplete.OnItemCallback() { 143 | @Override 144 | public void onItemCallback(int position, View view) { 145 | selectedCity = cityAdapter.getData().get(position); 146 | binding.autocompleteCity.setText(selectedCity.name); 147 | } 148 | }); 149 | } 150 | 151 | 152 | public ArrayList getCountries() { 153 | ArrayList regions = new ArrayList<>(); 154 | regions.add(Region.createDummy("1", "India", "0")); 155 | regions.add(Region.createDummy("2", "Canada", "0")); 156 | regions.add(Region.createDummy("3", "USA", "0")); 157 | regions.add(Region.createDummy("4", "Australia", "0")); 158 | return regions; 159 | } 160 | 161 | public ArrayList getStates() { 162 | ArrayList regions = new ArrayList<>(); 163 | regions.add(Region.createDummy("1", "Gujarat", "1")); 164 | regions.add(Region.createDummy("2", "Maharashtra", "1")); 165 | regions.add(Region.createDummy("3", "Uttar Pradesh", "1")); 166 | regions.add(Region.createDummy("4", "California", "3")); 167 | return regions; 168 | } 169 | 170 | public ArrayList getCities() { 171 | ArrayList regions = new ArrayList<>(); 172 | regions.add(Region.createDummy("1", "Ahmedabad", "1")); 173 | regions.add(Region.createDummy("2", "Baroda", "1")); 174 | regions.add(Region.createDummy("3", "Mumbai", "2")); 175 | regions.add(Region.createDummy("4", "Los Angeles", "4")); 176 | regions.add(Region.createDummy("5", "San Diego", "4")); 177 | regions.add(Region.createDummy("6", "San Francisco", "4")); 178 | return regions; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter 2 | 3 | import android.content.BroadcastReceiver 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.IntentFilter 7 | import android.databinding.DataBindingUtil 8 | import android.os.Bundle 9 | import android.support.v4.content.ContextCompat 10 | import android.support.v4.content.LocalBroadcastManager 11 | import android.support.v7.app.AppCompatActivity 12 | import android.support.v7.widget.LinearLayoutManager 13 | import android.support.v7.widget.SearchView 14 | import android.util.Log 15 | import android.view.View 16 | import android.webkit.WebView 17 | import android.webkit.WebViewClient 18 | import android.widget.Toast 19 | import easyadapter.dc.com.easyadapter.databinding.ActivityMainBinding 20 | import easyadapter.dc.com.easyadapter.databinding.InflaterCategoryNameBinding 21 | import easyadapter.dc.com.library.EasyAdapter 22 | import easyadapter.dc.com.library.EasyArrayAdapter 23 | import easyadapter.dc.com.library.EasySpinner 24 | 25 | 26 | class MainActivity : AppCompatActivity() { 27 | 28 | private lateinit var binding: ActivityMainBinding 29 | private lateinit var adapter: CategoryAdapter 30 | private lateinit var spinnerAdapter: CategoryAdapter 31 | 32 | private val names: List 33 | get() { 34 | val temp = ArrayList() 35 | temp.add(Category.createDummy("Krupal Mehta")) 36 | temp.add(Category.createDummy("Aagam Mehta")) 37 | temp.add(Category.createDummy("Anand Patel")) 38 | temp.add(Category.createDummy("Sagar Panchal")) 39 | temp.add(Category.createDummy("Pankaj Sharma")) 40 | temp.add(Category.createDummy("Darshak jani")) 41 | temp.add(Category.createDummy("Sanket Chauhan")) 42 | temp.add(Category.createDummy("Dhruv")) 43 | temp.add(Category.createDummy("Sagar Panchal")) 44 | temp.add(Category.createDummy("Pankaj Sharma")) 45 | temp.add(Category.createDummy("Darshak jani")) 46 | temp.add(Category.createDummy("Sanket Chauhan")) 47 | temp.add(Category.createDummy("Dhruv")) 48 | temp.add(Category.createDummy("Krupal Mehta")) 49 | temp.add(Category.createDummy("Aagam Mehta")) 50 | temp.add(Category.createDummy("Anand Patel")) 51 | temp.add(Category.createDummy("Sagar Panchal")) 52 | temp.add(Category.createDummy("Pankaj Sharma")) 53 | temp.add(Category.createDummy("Darshak jani")) 54 | temp.add(Category.createDummy("Sanket Chauhan")) 55 | temp.add(Category.createDummy("Dhruv")) 56 | temp.add(Category.createDummy("Sagar Panchal")) 57 | temp.add(Category.createDummy("Pankaj Sharma")) 58 | temp.add(Category.createDummy("Darshak jani")) 59 | temp.add(Category.createDummy("Sanket Chauhan")) 60 | temp.add(Category.createDummy("Dhruv")) 61 | temp.add(Category.createDummy("Krupal Mehta")) 62 | temp.add(Category.createDummy("Aagam Mehta")) 63 | temp.add(Category.createDummy("Anand Patel")) 64 | temp.add(Category.createDummy("Sagar Panchal")) 65 | temp.add(Category.createDummy("Pankaj Sharma")) 66 | temp.add(Category.createDummy("Darshak jani")) 67 | temp.add(Category.createDummy("Sanket Chauhan")) 68 | temp.add(Category.createDummy("Dhruv")) 69 | temp.add(Category.createDummy("Sagar Panchal")) 70 | temp.add(Category.createDummy("Pankaj Sharma")) 71 | temp.add(Category.createDummy("Darshak jani")) 72 | temp.add(Category.createDummy("Sanket Chauhan")) 73 | temp.add(Category.createDummy("Dhruv")) 74 | return temp 75 | } 76 | 77 | private val categories: List 78 | get() { 79 | val list = ArrayList() 80 | list.add(Category.createDummy("Android Developer", "1", "1")) 81 | list.add(Category.createDummy("Java Developer", "4", "2")) 82 | list.add(Category.createDummy("Python Developer", "1", "3")) 83 | list.add(Category.createDummy("Php Developer", "3", "4")) 84 | list.add(Category.createDummy("ROR Developer", "2", "5")) 85 | 86 | 87 | return list 88 | } 89 | 90 | override fun onCreate(savedInstanceState: Bundle?) { 91 | super.onCreate(savedInstanceState) 92 | 93 | Log.d("MainActivityLog", "true"); 94 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main) 95 | binding.recyclerView.isNestedScrollingEnabled = false 96 | 97 | adapterExample() 98 | spinnerExample() 99 | autocomplete() 100 | test() 101 | 102 | } 103 | 104 | private fun autocomplete() { 105 | 106 | val adapter = object : EasyArrayAdapter(this@MainActivity, 107 | R.layout.inflater_category_name, object : EasyAdapter.OnFilter { 108 | override fun onFilterResult(filteredList: java.util.ArrayList?) { 109 | } 110 | 111 | override fun onFilterApply(filter: Any?, model: Category): Boolean { 112 | return if (filter != null) model.parentId.equals("1") else false 113 | } 114 | }) { 115 | override fun onBind(binding: InflaterCategoryNameBinding, model: Category) { 116 | binding.tvName.text = model.name 117 | } 118 | } 119 | adapter.addAll(categories, true) 120 | binding.autcomplete.setItemSelectionCallback { position: Int, view: View -> 121 | binding.autcomplete.setText(adapter.data[position].name) 122 | } 123 | 124 | binding.autcomplete.setAdapter(adapter) 125 | 126 | } 127 | 128 | private fun adapterExample() { 129 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 130 | adapter = CategoryAdapter(true) 131 | binding.recyclerView.adapter = adapter 132 | 133 | adapter.addAll(names, true) 134 | adapter.notifyDataSetChanged() 135 | 136 | 137 | //Filter 138 | binding.searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { 139 | override fun onQueryTextSubmit(query: String): Boolean { 140 | return false 141 | } 142 | 143 | override fun onQueryTextChange(newText: String): Boolean { 144 | adapter.performFilter(newText, filter) 145 | return false 146 | } 147 | }) 148 | 149 | //Load More 150 | adapter.setLoadMoreRes(R.layout.layout_progress) 151 | /*adapter.setOnLoadMoreListener(binding.recyclerView) { true }*/ 152 | 153 | //Item View Event callback 154 | adapter.setRecyclerViewItemCheckChange { view, isCheck, model -> 155 | Toast.makeText(this@MainActivity, isCheck.toString(), Toast.LENGTH_SHORT).show() 156 | } 157 | 158 | 159 | //Swipe Action 160 | adapter.enableSwipeAction(binding.recyclerView) 161 | 162 | //Observe Data change (so you can show no data view if there is no data to display) 163 | adapter.addOnDataUpdateListener { 164 | if (it.size <= 0) { 165 | /*Toast.makeText(this@MainActivity, "No Data Found", Toast.LENGTH_SHORT).show()*/ 166 | } 167 | } 168 | } 169 | 170 | private fun spinnerExample() { 171 | //Spinner Configuration 172 | 173 | binding.spRecyclerView.setPopupBackground(ContextCompat.getDrawable(this, R.drawable.rect_background)) 174 | binding.spRecyclerView.setPopupType(EasySpinner.POPUP_TYPE_DROP_DOWN) 175 | binding.spRecyclerView.setPopupWidth(800) 176 | binding.spRecyclerView.setAnimation(R.style.Popwindow_Anim_Down) 177 | 178 | //Adapter Configuration 179 | spinnerAdapter = CategoryAdapter(false) 180 | spinnerAdapter.addAll(categories, true) 181 | spinnerAdapter.notifyDataSetChanged() 182 | spinnerAdapter.setRecyclerViewItemClick { view, model -> 183 | binding.spRecyclerView.setText(model.name) 184 | binding.spRecyclerView.hide() 185 | } 186 | 187 | binding.spRecyclerView.setAdapter(spinnerAdapter) 188 | binding.spRecyclerView.enableAutoCompleteMode { easySpinner, text -> 189 | spinnerAdapter.performFilter(text, spinnerFilter) 190 | } 191 | binding.spRecyclerView.setOnDropDownVisibilityListener { 192 | /* if(it) 193 | binding.spRecyclerView.setText("")*/ 194 | } 195 | } 196 | 197 | val filter = object : EasyAdapter.OnFilter { 198 | override fun onFilterApply(filter: Any?, model: Category): Boolean { 199 | return model.name.toLowerCase().contains(filter.toString().toLowerCase()) 200 | } 201 | 202 | override fun onFilterResult(filteredList: ArrayList?) { 203 | adapter.clear(false) 204 | adapter.addAll(filteredList, false) 205 | adapter.notifyDataSetChanged() 206 | } 207 | } 208 | 209 | private val spinnerFilter = object : EasyAdapter.OnFilter { 210 | override fun onFilterApply(filter: Any?, model: Category): Boolean { 211 | return model.name.toLowerCase().contains(filter.toString().toLowerCase()) 212 | } 213 | 214 | override fun onFilterResult(filteredList: ArrayList?) { 215 | spinnerAdapter.clear(false) 216 | spinnerAdapter.addAll(filteredList, false) 217 | spinnerAdapter.notifyDataSetChanged() 218 | } 219 | } 220 | 221 | private fun test() { 222 | val intentFilter = IntentFilter() 223 | intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED) 224 | intentFilter.addAction(Intent.ACTION_PACKAGE_INSTALL) 225 | intentFilter.addDataScheme("package") 226 | LocalBroadcastManager.getInstance(this).registerReceiver(object : BroadcastReceiver() { 227 | override fun onReceive(context: Context, intent: Intent) { 228 | Log.d("App Installed", "true") 229 | } 230 | }, intentFilter) 231 | 232 | } 233 | } 234 | 235 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/MainActivityJava.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.databinding.ViewDataBinding; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.RecyclerView; 10 | 11 | import easyadapter.dc.com.library.EasyAdapter; 12 | 13 | /** 14 | * Created by HB on 15/10/18. 15 | */ 16 | public class MainActivityJava extends AppCompatActivity { 17 | 18 | @Override 19 | protected void onCreate(@Nullable Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/Region.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | /** 4 | * Created by HB on 11/7/18. 5 | */ 6 | public class Region { 7 | 8 | public String id; 9 | public String name; 10 | public String parentId; 11 | 12 | public static Region createDummy(String id, String name, String parentId) { 13 | Region region = new Region(); 14 | region.id = id; 15 | region.name = name; 16 | region.parentId = parentId; 17 | return region; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/TestActivity.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.app.AppCompatActivity; 6 | import android.support.v7.widget.LinearLayoutManager; 7 | import android.support.v7.widget.RecyclerView; 8 | 9 | /** 10 | * Created by HB on 28/9/18. 11 | */ 12 | public class TestActivity extends AppCompatActivity { 13 | @Override 14 | protected void onCreate(@Nullable Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_test); 17 | RecyclerView recyclerView = findViewById(R.id.recyclerView); 18 | CategoryAdapter categoryAdapter = new CategoryAdapter(false); 19 | recyclerView.setAdapter(categoryAdapter); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/TransBean.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.os.Parcel; 4 | import android.os.Parcelable; 5 | 6 | /** 7 | * Created by HB on 3/1/19. 8 | */ 9 | public class TransBean implements Parcelable { 10 | protected TransBean(Parcel in) { 11 | } 12 | 13 | @Override 14 | public void writeToParcel(Parcel dest, int flags) { 15 | } 16 | 17 | @Override 18 | public int describeContents() { 19 | return 0; 20 | } 21 | 22 | public static final Creator CREATOR = new Creator() { 23 | @Override 24 | public TransBean createFromParcel(Parcel in) { 25 | return new TransBean(in); 26 | } 27 | 28 | @Override 29 | public TransBean[] newArray(int size) { 30 | return new TransBean[size]; 31 | } 32 | }; 33 | } 34 | -------------------------------------------------------------------------------- /app/src/main/java/easyadapter/dc/com/easyadapter/VideoActivity.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.easyadapter; 2 | 3 | import android.net.Uri; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.support.annotation.Nullable; 7 | import android.support.v4.app.NavUtils; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.view.View; 10 | import android.view.WindowInsets; 11 | import android.view.WindowManager; 12 | 13 | import com.devbrackets.android.exomedia.core.video.scale.ScaleType; 14 | import com.devbrackets.android.exomedia.listener.OnPreparedListener; 15 | import com.devbrackets.android.exomedia.ui.widget.VideoView; 16 | import com.google.android.exoplayer2.ExoPlayer; 17 | import com.google.android.exoplayer2.SimpleExoPlayer; 18 | import com.google.android.exoplayer2.source.ClippingMediaSource; 19 | import com.google.android.exoplayer2.source.ExtractorMediaSource; 20 | import com.google.android.exoplayer2.source.MediaPeriod; 21 | import com.google.android.exoplayer2.source.MediaSource; 22 | 23 | /** 24 | * Created by HB on 7/12/18. 25 | */ 26 | public class VideoActivity extends AppCompatActivity implements OnPreparedListener { 27 | 28 | VideoView videoView; 29 | 30 | @Override 31 | protected void onCreate(@Nullable Bundle savedInstanceState) { 32 | super.onCreate(savedInstanceState); 33 | 34 | 35 | 36 | 37 | getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, 38 | WindowManager.LayoutParams.FLAG_FULLSCREEN); // or add true in the theme 39 | getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); 40 | 41 | setContentView(R.layout.activity_video); 42 | 43 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { 44 | WindowManager.LayoutParams attrib = getWindow().getAttributes(); 45 | attrib.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES; 46 | } 47 | 48 | videoView = (VideoView) findViewById(R.id.video_view); 49 | videoView.setOnPreparedListener(this); 50 | 51 | 52 | 53 | videoView.setScaleType(ScaleType.CENTER_CROP); 54 | 55 | //For now we just picked an arbitrary item to play 56 | videoView.setVideoURI(Uri.parse("http://pcock.com/public/upload/twilio_media/VIDEO_20181205_140947-20181205084109450389.mp4")); 57 | } 58 | 59 | @Override 60 | public void onPrepared() { 61 | videoView.start(); 62 | } 63 | 64 | @Override 65 | public void onBackPressed() { 66 | NavUtils.navigateUpFromSameTask(this); 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dropdown_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/drawable/dropdown_arrow.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/edittext_bg_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/error.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/gradient_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rect_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_country.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 20 | 21 | 30 | 31 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 15 | 16 | 20 | 21 | 30 | 31 | 44 | 45 | 54 | 55 | 56 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_test.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 13 | 14 | 18 | 19 | 22 | 23 | 29 | 30 | 31 | 35 | 36 | 37 | 44 | 45 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_video.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/inflater_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 17 | 18 | 22 | 23 | 24 | 32 | 33 | 38 | 39 | 48 | 49 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /app/src/main/res/layout/inflater_category_name.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_progress.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | EasyAdapter 3 | Select Name 4 | Select Category 5 | 6 | Krupal 7 | Dhruv 8 | Mehta 9 | Patel 10 | Aagu 11 | Virti 12 | Googoo 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.31' 5 | 6 | repositories { 7 | google() 8 | jcenter() 9 | /*maven { 10 | url "http://dl.bintray.com/mkrupal09/EasyAdapter" 11 | }*/ 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:3.1.2' 15 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 16 | 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | google() 25 | jcenter() 26 | /*maven { 27 | url "http://dl.bintray.com/mkrupal09/EasyAdapter" 28 | }*/ 29 | } 30 | } 31 | 32 | task clean(type: Delete) { 33 | delete rootProject.buildDir 34 | } 35 | -------------------------------------------------------------------------------- /dclogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/dclogo.jpg -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon May 21 10:54:25 IST 2018 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-4.4-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 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 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /images/easyadapter-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/images/easyadapter-01.png -------------------------------------------------------------------------------- /images/recyclerview-adapter0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/images/recyclerview-adapter0.png -------------------------------------------------------------------------------- /images/recyclerview-adapter1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/images/recyclerview-adapter1.png -------------------------------------------------------------------------------- /images/recyclerview-adapter3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/images/recyclerview-adapter3.png -------------------------------------------------------------------------------- /images/recyclerview-adapter4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mkrupal09/EasyAdapter/e1a255113056b84ce172742c77a36b6cc89d0aef/images/recyclerview-adapter4.png -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | ext { 4 | PUBLISH_GROUP_ID = 'com.dc.easyadapter' 5 | PUBLISH_ARTIFACT_ID = 'easyadapter' 6 | PUBLISH_VERSION = '2.0.5' 7 | } 8 | 9 | android { 10 | compileSdkVersion 27 11 | 12 | defaultConfig { 13 | minSdkVersion 15 14 | targetSdkVersion 27 15 | versionCode 1 16 | versionName "1.0" 17 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | dataBinding { 27 | enabled = true 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation 'com.android.support:appcompat-v7:27.1.1' 34 | implementation "com.android.support:recyclerview-v7:27.1.1" 35 | } 36 | 37 | //./gradlew clean build generateRelease 38 | apply from: 'https://raw.githubusercontent.com/blundell/release-android-library/master/android-release-aar.gradle' 39 | 40 | -------------------------------------------------------------------------------- /library/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /library/src/androidTest/java/easyadapter/dc/com/library/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("easyadapter.dc.com.library.test", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/EasyAdapter.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.content.Context; 4 | import android.databinding.DataBindingUtil; 5 | import android.databinding.ViewDataBinding; 6 | import android.support.annotation.LayoutRes; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.support.v7.widget.GridLayoutManager; 10 | import android.support.v7.widget.LinearLayoutManager; 11 | import android.support.v7.widget.RecyclerView; 12 | import android.support.v7.widget.StaggeredGridLayoutManager; 13 | import android.view.Gravity; 14 | import android.view.LayoutInflater; 15 | import android.view.View; 16 | import android.view.ViewGroup; 17 | import android.widget.CompoundButton; 18 | import android.widget.FrameLayout; 19 | import android.widget.ProgressBar; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | /** 25 | * Created by HB on 20/3/18. 26 | */ 27 | 28 | public abstract class EasyAdapter extends RecyclerView.Adapter { 29 | 30 | private final int VIEW_ITEM = 1; 31 | private final int VIEW_PROGRESS = 0; 32 | private boolean loading = false; 33 | private boolean isLoadMoreEnabled = false; 34 | private int loadMoreRes = R.layout.layout_load_more; 35 | private final ArrayList data; 36 | private final ArrayList temp; 37 | private int layout; 38 | private OnRecyclerViewItemClick recyclerViewItemClick; 39 | private OnRecyclerViewItemCheckChange recyclerViewItemCheckChange; 40 | private ArrayList> onDataUpdateArrayList; 41 | 42 | public interface OnRecyclerViewItemClick { 43 | void onRecyclerViewItemClick(View view, M model); 44 | } 45 | 46 | public interface OnRecyclerViewItemCheckChange { 47 | void onRecyclerViewItemCheckChange(View view, boolean isCheck, M model); 48 | } 49 | 50 | public interface OnHolderItemClick { 51 | void onHolderItemClick(View view, int position); 52 | } 53 | 54 | public interface OnHolderItemCheckChange { 55 | void onHolderItemCheckChange(View view, boolean isCheck, int position); 56 | } 57 | 58 | public interface OnFilter { 59 | boolean onFilterApply(@Nullable Object filter, @NonNull M model); 60 | 61 | void onFilterResult(ArrayList filteredList); 62 | } 63 | 64 | public interface OnLoadMoreListener { 65 | boolean onLoadMore(); 66 | } 67 | 68 | public interface OnDataUpdate { 69 | public void onDataUpdate(ArrayList data); 70 | } 71 | 72 | 73 | public EasyAdapter(@LayoutRes int layout) { 74 | data = new ArrayList<>(); 75 | temp = new ArrayList<>(); 76 | temp.addAll(data); 77 | enableDataObserver(); 78 | this.layout = layout; 79 | onDataUpdateArrayList = new ArrayList<>(); 80 | } 81 | 82 | public void onCreatingHolder(@NonNull B binding, @NonNull EasyHolder holder) { 83 | 84 | } 85 | 86 | public abstract void onBind(@NonNull B binding, @NonNull M model); 87 | 88 | public void onBind(@NonNull B binding, @NonNull M model, EasyHolder holder) { 89 | } 90 | 91 | public void onBind(@NonNull B binding, @NonNull M model, @NonNull List payloads) { 92 | } 93 | 94 | 95 | public final ArrayList getData() { 96 | return data; 97 | } 98 | 99 | public final ArrayList getTemp() { 100 | return temp; 101 | } 102 | 103 | public void clear(boolean deepClean) { 104 | data.clear(); 105 | if (deepClean) { 106 | temp.clear(); 107 | } 108 | } 109 | 110 | 111 | private void clearFilter() { 112 | data.clear(); 113 | data.addAll(temp); 114 | } 115 | 116 | public void add(M model) { 117 | data.add(model); 118 | temp.add(model); 119 | notifyDataSetChanged(); 120 | } 121 | 122 | public int addOnly(M model) { 123 | data.add(model); 124 | temp.add(model); 125 | return data.size() - 1; 126 | /*notifyDataSetChanged();*/ 127 | } 128 | 129 | 130 | @Deprecated 131 | public void remove(M model) { 132 | data.remove(model); 133 | temp.remove(model); 134 | notifyDataSetChanged(); 135 | } 136 | 137 | public void removeOnly(M model) { 138 | data.remove(model); 139 | temp.remove(model); 140 | } 141 | 142 | public void removeOnly(int pos) { 143 | M model = data.get(pos); 144 | data.remove(model); 145 | temp.remove(model); 146 | } 147 | 148 | public void addAll(List addAll, boolean deepCopy) { 149 | data.addAll(addAll); 150 | if (deepCopy) { 151 | temp.addAll(addAll); 152 | } 153 | /*notifyDataSetChanged();*/ 154 | } 155 | 156 | public EasyAdapter setRecyclerViewItemClick(OnRecyclerViewItemClick recyclerViewItemClick) { 157 | this.recyclerViewItemClick = recyclerViewItemClick; 158 | return this; 159 | } 160 | 161 | public EasyAdapter setRecyclerViewItemCheckChange(OnRecyclerViewItemCheckChange recyclerViewItemCheckChange) { 162 | this.recyclerViewItemCheckChange = recyclerViewItemCheckChange; 163 | return this; 164 | } 165 | 166 | 167 | public void onItemClick(View view, M model) { 168 | if (recyclerViewItemClick != null) 169 | recyclerViewItemClick.onRecyclerViewItemClick(view, model); 170 | } 171 | 172 | public void onItemCheckChange(View view, boolean isCheck, M model) { 173 | if (recyclerViewItemCheckChange != null) 174 | recyclerViewItemCheckChange.onRecyclerViewItemCheckChange(view, isCheck, model); 175 | } 176 | 177 | 178 | public void performFilter(Object text, OnFilter onFilter) { 179 | ArrayList result = new ArrayList<>(); 180 | if (text.toString().length() <= 0) { 181 | result.addAll(temp); 182 | } else { 183 | result.clear(); 184 | for (M d : temp) { 185 | if (d instanceof OnFilter) { 186 | onFilter = (OnFilter) d; 187 | } 188 | if (onFilter != null) { 189 | if (onFilter.onFilterApply(text, d)) { 190 | result.add(d); 191 | } 192 | } 193 | } 194 | } 195 | if (onFilter != null) { 196 | onFilter.onFilterResult(result); 197 | } 198 | } 199 | 200 | 201 | @Override 202 | public final EasyHolder onCreateViewHolder(ViewGroup parent, int viewType) { 203 | if (viewType == VIEW_ITEM) { 204 | EasyHolder easyHolder = new EasyHolder(DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), 205 | layout, parent, false)); 206 | onCreatingHolder((B) easyHolder.binding, easyHolder); 207 | easyHolder.setHolderItemClick(new OnHolderItemClick() { 208 | @Override 209 | public void onHolderItemClick(View view, int position) { 210 | if (position != -1) 211 | onItemClick(view, data.get(position)); 212 | } 213 | }); 214 | easyHolder.setHolderItemCheckChange(new OnHolderItemCheckChange() { 215 | @Override 216 | public void onHolderItemCheckChange(View view, boolean isCheck, int position) { 217 | if (position != -1) { 218 | onItemCheckChange(view, isCheck, data.get(position)); 219 | } 220 | } 221 | }); 222 | return easyHolder; 223 | } else { 224 | View view; 225 | if (loadMoreRes == -1) { 226 | view = getProgressView(parent.getContext()); 227 | } else { 228 | view = LayoutInflater.from(parent.getContext()).inflate(loadMoreRes, parent, false); 229 | } 230 | return new ProgressViewHolder(view); 231 | } 232 | } 233 | 234 | @Override 235 | public final void onBindViewHolder(@NonNull EasyHolder holder, int position) { 236 | if (!holder.isLoadingView) { 237 | onBind((B) holder.binding, data.get(position)); 238 | onBind((B) holder.binding, data.get(position), holder); 239 | } 240 | } 241 | 242 | @Override 243 | public final void onBindViewHolder(@NonNull EasyHolder holder, int position, @NonNull List payloads) { 244 | super.onBindViewHolder(holder, position, payloads); 245 | if (!holder.isLoadingView) { 246 | onBind((B) holder.binding, data.get(position), payloads); 247 | } 248 | } 249 | 250 | @Override 251 | public int getItemCount() { 252 | if (data == null) return 0; 253 | if (isLoadMoreEnabled && loading) 254 | return data.size() + 1; 255 | return data.size(); 256 | } 257 | 258 | @Override 259 | public int getItemViewType(int position) { 260 | if (isLoadMoreEnabled && loading) { 261 | if (position == getItemCount() - 1) { 262 | return VIEW_PROGRESS; 263 | } else return VIEW_ITEM; 264 | } 265 | return VIEW_ITEM; 266 | } 267 | 268 | private View getProgressView(Context context) { 269 | View view = new FrameLayout(context); 270 | view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); 271 | 272 | ProgressBar progressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleSmall); 273 | FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 274 | lp.gravity = Gravity.CENTER_HORIZONTAL; 275 | progressBar.setLayoutParams(lp); 276 | ((ViewGroup) view).addView(progressBar); 277 | return view; 278 | } 279 | 280 | 281 | public void setLoadMoreComplete() { 282 | loading = false; 283 | notifyDataSetChanged(); 284 | } 285 | 286 | public void setLoadMoreRes(int loadMoreRes) { 287 | this.loadMoreRes = loadMoreRes; 288 | } 289 | 290 | public EasyAdapter setOnLoadMoreListener(RecyclerView recyclerView, final OnLoadMoreListener onLoadMoreListener) { 291 | if (recyclerView != null && onLoadMoreListener != null) { 292 | 293 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 294 | 295 | if (layoutManager instanceof GridLayoutManager) { 296 | ((GridLayoutManager) layoutManager).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { 297 | @Override 298 | public int getSpanSize(int position) { 299 | if (getItemViewType(position) == VIEW_PROGRESS) 300 | return ((GridLayoutManager) layoutManager).getSpanCount(); 301 | return 1; 302 | } 303 | }); 304 | } 305 | 306 | recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { 307 | @Override 308 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 309 | super.onScrolled(recyclerView, dx, dy); 310 | 311 | int totalItemCount = layoutManager.getItemCount(); 312 | int lastVisibleItem = 0; 313 | 314 | if (layoutManager instanceof StaggeredGridLayoutManager) { 315 | int[] lastVisibleItemPositions = ((StaggeredGridLayoutManager) layoutManager).findLastVisibleItemPositions(null); 316 | lastVisibleItem = getLastVisibleItem(lastVisibleItemPositions); 317 | } else if (layoutManager instanceof GridLayoutManager) { 318 | lastVisibleItem = ((GridLayoutManager) layoutManager).findLastVisibleItemPosition(); 319 | } else if (layoutManager instanceof LinearLayoutManager) { 320 | lastVisibleItem = ((LinearLayoutManager) layoutManager).findLastVisibleItemPosition(); 321 | } 322 | if (!loading && totalItemCount <= (lastVisibleItem + 2)) { 323 | new android.os.Handler().post(new Runnable() { 324 | @Override 325 | public void run() { 326 | boolean previous = loading; 327 | loading = onLoadMoreListener.onLoadMore(); 328 | if (loading != previous) { 329 | if (!previous) { 330 | notifyItemInserted(getItemCount() - 1); 331 | } else if (previous == true && loading == false) { 332 | notifyItemRemoved(getItemCount() - 1); 333 | } 334 | } 335 | } 336 | }); 337 | } 338 | } 339 | }); 340 | 341 | isLoadMoreEnabled = true; 342 | } 343 | return this; 344 | } 345 | 346 | 347 | private int getLastVisibleItem(int[] lastVisibleItemPositions) { 348 | int maxSize = 0; 349 | for (int i = 0; i < lastVisibleItemPositions.length; i++) { 350 | if (i == 0) { 351 | maxSize = lastVisibleItemPositions[i]; 352 | } else if (lastVisibleItemPositions[i] > maxSize) { 353 | maxSize = lastVisibleItemPositions[i]; 354 | } 355 | } 356 | return maxSize; 357 | } 358 | 359 | 360 | public static class EasyHolder extends RecyclerView.ViewHolder implements SwipeOpenViewHolder { 361 | 362 | private ViewDataBinding binding; 363 | boolean isLoadingView; 364 | private OnHolderItemClick holderItemClick; 365 | private OnHolderItemCheckChange holderItemCheckChange; 366 | public View swipeView; 367 | public int startViewSize = 0, endViewSize = 0; 368 | 369 | public Object object; 370 | 371 | public void setTag(Object object) { 372 | this.object = object; 373 | } 374 | 375 | public Object getTag() { 376 | return object; 377 | } 378 | 379 | public EasyHolder(ViewDataBinding itemView) { 380 | super(itemView.getRoot()); 381 | binding = itemView; 382 | 383 | } 384 | 385 | public EasyHolder(View view) { 386 | super(view); 387 | } 388 | 389 | void setHolderItemClick(OnHolderItemClick holderItemClick) { 390 | this.holderItemClick = holderItemClick; 391 | } 392 | 393 | public void setHolderItemCheckChange(OnHolderItemCheckChange holderItemCheckChange) { 394 | this.holderItemCheckChange = holderItemCheckChange; 395 | } 396 | 397 | private View.OnClickListener mOnClickListener = new View.OnClickListener() { 398 | @Override 399 | public void onClick(View view) { 400 | holderItemClick.onHolderItemClick(view, getAdapterPosition()); 401 | } 402 | }; 403 | 404 | public View.OnClickListener getClickListener() { 405 | return mOnClickListener; 406 | } 407 | 408 | public CompoundButton.OnCheckedChangeListener checkedChangeListener = new CompoundButton.OnCheckedChangeListener() { 409 | @Override 410 | public void onCheckedChanged(CompoundButton compoundButton, boolean b) { 411 | holderItemCheckChange.onHolderItemCheckChange(compoundButton, b, getAdapterPosition()); 412 | } 413 | }; 414 | 415 | public CompoundButton.OnCheckedChangeListener getCheckedChangeListener() { 416 | return checkedChangeListener; 417 | } 418 | 419 | public void setEnableSwipeToDelete(View swipeView, int startViewSize, int endViewSize) { 420 | this.swipeView = swipeView; 421 | this.startViewSize = startViewSize; 422 | this.endViewSize = endViewSize; 423 | } 424 | 425 | @NonNull 426 | @Override 427 | public View getSwipeView() { 428 | return swipeView; 429 | } 430 | 431 | @NonNull 432 | @Override 433 | public RecyclerView.ViewHolder getViewHolder() { 434 | return this; 435 | } 436 | 437 | @Override 438 | public float getEndHiddenViewSize() { 439 | return endViewSize; 440 | } 441 | 442 | @Override 443 | public float getStartHiddenViewSize() { 444 | return startViewSize; 445 | } 446 | 447 | @Override 448 | public void notifyStartOpen() { 449 | 450 | } 451 | 452 | @Override 453 | public void notifyEndOpen() { 454 | 455 | } 456 | 457 | public final void setOnClickListeners(View... views) { 458 | for (View view : views) { 459 | view.setOnClickListener(getClickListener()); 460 | } 461 | } 462 | 463 | public final void setOnCheckChangeListeners(CompoundButton... compoundButtons) { 464 | for (CompoundButton view : compoundButtons) { 465 | view.setOnCheckedChangeListener(getCheckedChangeListener()); 466 | } 467 | } 468 | } 469 | 470 | private class ProgressViewHolder extends EasyHolder { 471 | ProgressViewHolder(View v) { 472 | super(v); 473 | isLoadingView = true; 474 | swipeView = v; 475 | startViewSize = 0; 476 | endViewSize = 0; 477 | setEnableSwipeToDelete(v, 0, 0); 478 | } 479 | } 480 | 481 | public SwipeOpenItemTouchHelper helper = new SwipeOpenItemTouchHelper(new SwipeOpenItemTouchHelper.SimpleCallback( 482 | SwipeOpenItemTouchHelper.START | SwipeOpenItemTouchHelper.END)); 483 | 484 | public void enableSwipeAction(RecyclerView recyclerView) { 485 | 486 | 487 | helper.attachToRecyclerView(recyclerView); 488 | } 489 | 490 | 491 | public EasyAdapter addOnDataUpdateListener(OnDataUpdate onDataUpdate) { 492 | this.onDataUpdateArrayList.add(onDataUpdate); 493 | return this; 494 | } 495 | 496 | private void onDataUpdate() { 497 | for (OnDataUpdate onDataUpdate : onDataUpdateArrayList) { 498 | onDataUpdate.onDataUpdate(getData()); 499 | } 500 | } 501 | 502 | public final void disableDataObserver() { 503 | unregisterAdapterDataObserver(dataChangeObs); 504 | } 505 | 506 | public final void enableDataObserver() { 507 | registerAdapterDataObserver(dataChangeObs); 508 | } 509 | 510 | private RecyclerView.AdapterDataObserver dataChangeObs = new RecyclerView.AdapterDataObserver() { 511 | @Override 512 | public void onChanged() { 513 | super.onChanged(); 514 | onDataUpdate(); 515 | } 516 | 517 | @Override 518 | public void onItemRangeChanged(int positionStart, int itemCount) { 519 | super.onItemRangeChanged(positionStart, itemCount); 520 | onDataUpdate(); 521 | } 522 | 523 | @Override 524 | public void onItemRangeChanged(int positionStart, int itemCount, @Nullable Object payload) { 525 | super.onItemRangeChanged(positionStart, itemCount, payload); 526 | onDataUpdate(); 527 | } 528 | 529 | @Override 530 | public void onItemRangeInserted(int positionStart, int itemCount) { 531 | super.onItemRangeInserted(positionStart, itemCount); 532 | onDataUpdate(); 533 | } 534 | 535 | @Override 536 | public void onItemRangeRemoved(int positionStart, int itemCount) { 537 | super.onItemRangeRemoved(positionStart, itemCount); 538 | onDataUpdate(); 539 | } 540 | 541 | @Override 542 | public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) { 543 | super.onItemRangeMoved(fromPosition, toPosition, itemCount); 544 | onDataUpdate(); 545 | } 546 | }; 547 | 548 | 549 | public void invalidateObserver() { 550 | onDataUpdate(); 551 | } 552 | 553 | } -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/EasyArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.databinding.DataBindingUtil; 6 | import android.databinding.ViewDataBinding; 7 | import android.support.annotation.NonNull; 8 | import android.support.annotation.Nullable; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ArrayAdapter; 13 | import android.widget.Filter; 14 | import android.widget.Filterable; 15 | import android.widget.SpinnerAdapter; 16 | 17 | import java.util.ArrayList; 18 | import java.util.Collection; 19 | import java.util.List; 20 | 21 | /** 22 | * Created by HB on 6/7/18. 23 | */ 24 | public abstract class EasyArrayAdapter extends ArrayAdapter implements Filterable, SpinnerAdapter { 25 | 26 | private final ArrayList data; 27 | private final ArrayList temp; 28 | private int layout; 29 | private EasyAdapter.OnFilter onFilter; 30 | 31 | public EasyArrayAdapter(@NonNull Context context, int resource, EasyAdapter.OnFilter onFilter) { 32 | super(context, resource); 33 | data = new ArrayList<>(); 34 | temp = new ArrayList<>(); 35 | this.onFilter = onFilter; 36 | this.layout = resource; 37 | } 38 | 39 | @NonNull 40 | @Override 41 | public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 42 | return makeView(position, parent); 43 | } 44 | 45 | @Override 46 | public View getDropDownView(int position, @Nullable View convertView, @NonNull ViewGroup parent) { 47 | return makeView(position, parent); 48 | } 49 | 50 | @NonNull 51 | private View makeView(final int position, @NonNull ViewGroup parent) { 52 | @SuppressLint("ViewHolder") ViewDataBinding view = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), layout, parent, false); 53 | onBind((B) view, data.get(position)); 54 | return view.getRoot(); 55 | } 56 | 57 | @Nullable 58 | @Override 59 | public M getItem(int position) { 60 | return data.get(position); 61 | } 62 | 63 | @Override 64 | public long getItemId(int position) { 65 | return position; 66 | } 67 | 68 | public abstract void onBind(@NonNull B binding, @NonNull M model); 69 | 70 | 71 | @Override 72 | public int getCount() { 73 | return data.size(); 74 | } 75 | 76 | public ArrayList performFilter(Object text) { 77 | ArrayList result = new ArrayList<>(); 78 | result.clear(); 79 | for (M d : temp) { 80 | if (onFilter.onFilterApply(text, d)) { 81 | result.add(d); 82 | } 83 | } 84 | return result; 85 | } 86 | 87 | public void clear(boolean deepClean) { 88 | data.clear(); 89 | if (deepClean) { 90 | temp.clear(); 91 | } 92 | } 93 | 94 | public void remove(M model) { 95 | data.remove(model); 96 | temp.remove(model); 97 | notifyDataSetChanged(); 98 | } 99 | 100 | 101 | private void clearFilter() { 102 | data.clear(); 103 | data.addAll(temp); 104 | } 105 | 106 | public void add(M model) { 107 | data.add(model); 108 | temp.add(model); 109 | notifyDataSetChanged(); 110 | } 111 | 112 | public void addAll(List addAll, boolean deepCopy) { 113 | data.addAll(addAll); 114 | if (deepCopy) { 115 | temp.addAll(addAll); 116 | } 117 | notifyDataSetChanged(); 118 | } 119 | 120 | @NonNull 121 | @Override 122 | public Filter getFilter() { 123 | return filter; 124 | } 125 | 126 | private Filter filter = new Filter() { 127 | @Override 128 | protected FilterResults performFiltering(CharSequence constraint) { 129 | 130 | ArrayList filtered = performFilter(constraint); 131 | FilterResults filterResults = new FilterResults(); 132 | filterResults.count = filtered.size(); 133 | filterResults.values = filtered; 134 | return filterResults; 135 | } 136 | 137 | @Override 138 | protected void publishResults(CharSequence constraint, FilterResults results) { 139 | data.clear(); 140 | if (results != null && results.values!=null) { 141 | data.addAll((Collection) results.values); 142 | } 143 | notifyDataSetChanged(); 144 | } 145 | }; 146 | 147 | 148 | public final ArrayList getData() { 149 | return data; 150 | } 151 | 152 | public final ArrayList getTemp() { 153 | return temp; 154 | } 155 | 156 | 157 | } 158 | -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/EasyAutoComplete.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.content.Context; 4 | import android.graphics.Rect; 5 | import android.text.method.KeyListener; 6 | import android.util.AttributeSet; 7 | import android.view.View; 8 | import android.widget.AdapterView; 9 | 10 | /** 11 | * Created by HB on 9/7/18. 12 | */ 13 | public class EasyAutoComplete extends android.support.v7.widget.AppCompatAutoCompleteTextView { 14 | 15 | private OnItemCallback onItemCallback; 16 | private boolean enableAutoComplete; 17 | private KeyListener keyListener; 18 | 19 | 20 | public interface OnItemCallback { 21 | public void onItemCallback(int position, View view); 22 | } 23 | 24 | 25 | public EasyAutoComplete(Context context) { 26 | super(context); 27 | init(); 28 | } 29 | 30 | public EasyAutoComplete(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | init(); 33 | } 34 | 35 | public EasyAutoComplete(Context context, AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | init(); 38 | 39 | } 40 | 41 | private void init() { 42 | setThreshold(0); 43 | keyListener = getKeyListener(); 44 | enableAutoComplete(true); 45 | setOnClickListener(new OnClickListener() { 46 | @Override 47 | public void onClick(View v) { 48 | if (getAdapter() != null) { 49 | showDropDown(); 50 | } 51 | } 52 | }); 53 | setOnItemClickListener(new AdapterView.OnItemClickListener() { 54 | @Override 55 | public void onItemClick(AdapterView parent, View view, int position, long id) { 56 | onItemCallback.onItemCallback(position, EasyAutoComplete.this); 57 | } 58 | }); 59 | } 60 | 61 | 62 | @Override 63 | public boolean enoughToFilter() { 64 | return true; 65 | } 66 | 67 | @Override 68 | protected void onFocusChanged(boolean focused, int direction, 69 | Rect previouslyFocusedRect) { 70 | super.onFocusChanged(focused, direction, previouslyFocusedRect); 71 | if (focused && getAdapter() != null) { 72 | performFiltering(getText(), 0); 73 | post(new Runnable() { 74 | @Override 75 | public void run() { 76 | showDropDown(); 77 | } 78 | }); 79 | } 80 | } 81 | 82 | public void setItemSelectionCallback(OnItemCallback onItemCallback) { 83 | this.onItemCallback = onItemCallback; 84 | } 85 | 86 | 87 | public void enableAutoComplete(boolean enableAutoComplete) { 88 | this.enableAutoComplete = enableAutoComplete; 89 | makeEditable(enableAutoComplete); 90 | } 91 | 92 | private void makeEditable(boolean editable) { 93 | if (editable) { 94 | setCursorVisible(true); 95 | setKeyListener(keyListener); 96 | setSelectAllOnFocus(true); 97 | } else { 98 | setKeyListener(null); 99 | setCursorVisible(false); 100 | setInputType(0); 101 | setSelectAllOnFocus(false); 102 | } 103 | } 104 | 105 | @Override 106 | protected void performFiltering(CharSequence text, int keyCode) { 107 | super.performFiltering(!enableAutoComplete ? "" : text, keyCode); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/EasySpinner.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.databinding.ViewDataBinding; 6 | import android.graphics.drawable.Drawable; 7 | import android.os.Build; 8 | import android.support.annotation.Nullable; 9 | import android.support.annotation.StyleRes; 10 | import android.support.v4.content.ContextCompat; 11 | import android.support.v4.widget.PopupWindowCompat; 12 | import android.support.v7.widget.AppCompatEditText; 13 | import android.support.v7.widget.LinearLayoutManager; 14 | import android.support.v7.widget.RecyclerView; 15 | import android.text.Editable; 16 | import android.text.TextWatcher; 17 | import android.text.method.KeyListener; 18 | import android.util.AttributeSet; 19 | import android.view.Gravity; 20 | import android.view.KeyEvent; 21 | import android.view.MotionEvent; 22 | import android.view.View; 23 | import android.view.WindowManager; 24 | import android.view.inputmethod.InputMethodManager; 25 | import android.widget.EditText; 26 | import android.widget.LinearLayout; 27 | import android.widget.PopupWindow; 28 | 29 | import java.util.ArrayList; 30 | 31 | /** 32 | * Created by HB on 25/5/18. 33 | */ 34 | public class EasySpinner extends AppCompatEditText { 35 | 36 | public static final int POPUP_WIDTH_VIEW = -1; 37 | public static final int POPUP_TYPE_DROP_DOWN = 1; 38 | public static final int POPUP_TYPE_DIALOG = 2; 39 | private PopupWindow popupWindow; 40 | private RecyclerView recyclerView; 41 | private KeyListener keyListener; 42 | private OnTextChange onTextChange; 43 | private OnDropDownVisibilityListener onDropDownVisibilityListener; 44 | private int MAX_SIZE = 0; 45 | private Drawable background; 46 | private int animation; 47 | private int popupWidth = POPUP_WIDTH_VIEW; 48 | private int popupType = POPUP_TYPE_DROP_DOWN; 49 | 50 | private LinearLayout linearLayout; 51 | 52 | public interface OnDropDownVisibilityListener { 53 | public void onDropDownVisibilityChange(boolean show); 54 | } 55 | 56 | public interface OnTextChange { 57 | public void onTextChange(EasySpinner easySpinner, String text); 58 | } 59 | 60 | public EasySpinner(Context context) { 61 | super(context); 62 | init(); 63 | } 64 | 65 | public EasySpinner(Context context, @Nullable AttributeSet attrs) { 66 | super(context, attrs); 67 | init(); 68 | } 69 | 70 | public EasySpinner(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 71 | super(context, attrs, defStyleAttr); 72 | init(); 73 | } 74 | 75 | /*private EditText editTextInternal;*/ 76 | 77 | private void init() { 78 | //Make Edit text as TextView First 79 | keyListener = getKeyListener(); 80 | makeEditable(false); 81 | 82 | addTextChangedListener(textWatcher); 83 | 84 | recyclerView = new RecyclerView(getContext()); 85 | recyclerView.setNestedScrollingEnabled(false); 86 | linearLayout = new LinearLayout(getContext()); 87 | linearLayout.setOrientation(LinearLayout.VERTICAL); 88 | 89 | 90 | /* editTextInternal = new EditText(getContext()); 91 | editTextInternal.setHint(getHint()); 92 | editTextInternal.setId(R.id.edtHint);*/ 93 | /*editTextInternal.addTextChangedListener(textWatcherInternal);*/ 94 | 95 | 96 | /*linearLayout.addView(editTextInternal);*/ 97 | linearLayout.addView(recyclerView); 98 | 99 | setPopupBackground(ContextCompat.getDrawable(getContext(), android.R.drawable.dialog_holo_light_frame)); 100 | setOnClickListener(onClickListener); 101 | setOnFocusChangeListener(onFocusChangeListener); 102 | } 103 | 104 | 105 | /*private TextWatcher textWatcherInternal = new TextWatcher() { 106 | @Override 107 | public void beforeTextChanged(CharSequence s, int start, int count, int after) { 108 | 109 | } 110 | 111 | @Override 112 | public void onTextChanged(CharSequence s, int start, int before, int count) { 113 | 114 | onTextChange(editTextInternal); 115 | } 116 | 117 | @Override 118 | public void afterTextChanged(Editable s) { 119 | 120 | } 121 | }; 122 | */ 123 | public void setPopupBackground(Drawable drawable) { 124 | background = drawable; 125 | } 126 | 127 | public void setPopupType(int popupType) { 128 | this.popupType = popupType; 129 | } 130 | 131 | public void setPopupWidth(int width) { 132 | popupWidth = width; 133 | } 134 | 135 | public void setAnimation(@StyleRes int animationStyle) { 136 | this.animation = animationStyle; 137 | } 138 | 139 | 140 | public void setOnDropDownVisibilityListener(OnDropDownVisibilityListener onDropDownVisibilityListener) { 141 | this.onDropDownVisibilityListener = onDropDownVisibilityListener; 142 | } 143 | 144 | public void hideKeyboard(View view) { 145 | InputMethodManager inputManager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); 146 | if (inputManager != null) 147 | inputManager.hideSoftInputFromWindow(view.getWindowToken(), 0); 148 | } 149 | 150 | public void stopAutoCompleteObserve() { 151 | removeTextChangedListener(textWatcher); 152 | /*editTextInternal.removeTextChangedListener(textWatcherInternal);*/ 153 | } 154 | 155 | public void startAutoCompleteObserve() { 156 | addTextChangedListener(textWatcher); 157 | /*editTextInternal.addTextChangedListener(textWatcherInternal);*/ 158 | } 159 | 160 | 161 | public void setAdapter(EasyAdapter adapter) { 162 | setAdapter(new LinearLayoutManager(getContext()), adapter); 163 | } 164 | 165 | public void setAdapter(RecyclerView.LayoutManager layoutManager, EasyAdapter adapter) { 166 | recyclerView.setLayoutManager(layoutManager); 167 | recyclerView.setAdapter(adapter); 168 | adapter.addOnDataUpdateListener(new EasyAdapter.OnDataUpdate() { 169 | @Override 170 | public void onDataUpdate(ArrayList data) { 171 | /*if (popupWindow != null) { 172 | if (popupType == POPUP_TYPE_DROP_DOWN) { 173 | popupWindow.update(EasySpinner.this, getPopupWidth(), getListHeight()); 174 | } 175 | }*/ 176 | } 177 | }); 178 | } 179 | 180 | private View.OnFocusChangeListener onFocusChangeListener = new OnFocusChangeListener() { 181 | @Override 182 | public void onFocusChange(View view, boolean hasFocus) { 183 | if (!hasFocus) { 184 | hide(); 185 | } else { 186 | show(); 187 | } 188 | } 189 | }; 190 | 191 | private View.OnClickListener onClickListener = new OnClickListener() { 192 | @Override 193 | public void onClick(View view) { 194 | if (!isShowing()) { 195 | show(); 196 | } 197 | } 198 | }; 199 | 200 | 201 | private boolean isShowing() { 202 | if (popupWindow != null) { 203 | return popupWindow.isShowing(); 204 | } 205 | return false; 206 | } 207 | 208 | @Override 209 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 210 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 211 | MAX_SIZE = getScreenHeight() / 3; 212 | } 213 | 214 | private int getScreenHeight() { 215 | return Resources.getSystem().getDisplayMetrics().heightPixels; 216 | } 217 | 218 | private PopupWindow buildPopupWindow(int width, int height) { 219 | final PopupWindow popupWindow = new PopupWindow(recyclerView, width, height); 220 | popupWindow.setAnimationStyle(animation); 221 | popupWindow.setOutsideTouchable(true); 222 | popupWindow.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); 223 | popupWindow.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); 224 | popupWindow.setBackgroundDrawable(background); 225 | popupWindow.setContentView(linearLayout); 226 | /*popupWindow.setTouchInterceptor(new OnTouchListener() { 227 | @Override 228 | public boolean onTouch(View v, MotionEvent event) { 229 | int[] loca=new int[2]; 230 | getLocationInWindow(loca); 231 | int starty=loca[1]-getHeight(); 232 | int endy=loca[1]; 233 | if (event.getAction() == MotionEvent.ACTION_OUTSIDE && event.getY()endy) { 234 | popupWindow.dismiss(); 235 | return true; 236 | } 237 | return false; 238 | } 239 | });*/ 240 | return popupWindow; 241 | } 242 | 243 | 244 | public void show() { 245 | if (getKeyListener() == null) { 246 | hideKeyboard(this); 247 | } 248 | 249 | //Testing purpose 250 | /*if (System.currentTimeMillis() % 2 == 0) { 251 | popupType = POPUP_TYPE_DROP_DOWN; 252 | } else { 253 | popupType = POPUP_TYPE_DIALOG; 254 | }*/ 255 | 256 | popupWindow = buildPopupWindow(getPopupWidth(), getListHeight()); 257 | 258 | /* EditText edtHint = popupWindow.getContentView().findViewById(R.id.edtHint); 259 | edtHint.setVisibility(onTextChange != null && 260 | popupType == POPUP_TYPE_DIALOG ? View.VISIBLE : View.GONE); 261 | edtHint.setText("");*/ 262 | 263 | if (popupType == POPUP_TYPE_DROP_DOWN) { 264 | //show as dropdown 265 | PopupWindowCompat.setOverlapAnchor(popupWindow, false); 266 | PopupWindowCompat.showAsDropDown(popupWindow, this, 0, 0, Gravity.NO_GRAVITY); 267 | /* popupWindow.update(this, getPopupWidth(), getListHeight());*/ 268 | } else { 269 | //Show as dialog 270 | /*popupWindow.showAtLocation(this, Gravity.CENTER, 0, 0); 271 | dimPopupWindow(popupWindow);*/ 272 | } 273 | 274 | if (onDropDownVisibilityListener != null) 275 | onDropDownVisibilityListener.onDropDownVisibilityChange(true); 276 | } 277 | 278 | /*private int[] location() { 279 | 280 | final int measuredH = getListHeight(); 281 | 282 | final int[] anchorLocation = new int[2]; 283 | getLocationInWindow(anchorLocation); 284 | int x = anchorLocation[0]; 285 | int y = anchorLocation[1] + getHeight(); 286 | }*/ 287 | 288 | public static void dimPopupWindow(PopupWindow popupWindow) { 289 | View container; 290 | if (popupWindow.getBackground() == null) { 291 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 292 | container = (View) popupWindow.getContentView().getParent(); 293 | } else { 294 | container = popupWindow.getContentView(); 295 | } 296 | } else { 297 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 298 | container = (View) popupWindow.getContentView().getParent().getParent(); 299 | } else { 300 | container = (View) popupWindow.getContentView().getParent(); 301 | } 302 | } 303 | Context context = popupWindow.getContentView().getContext(); 304 | WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 305 | WindowManager.LayoutParams p = (WindowManager.LayoutParams) container.getLayoutParams(); 306 | p.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND; 307 | p.dimAmount = 0.3f; 308 | wm.updateViewLayout(container, p); 309 | } 310 | 311 | public void hide() { 312 | if (popupWindow != null && popupWindow.isShowing()) { 313 | popupWindow.dismiss(); 314 | } 315 | if (onDropDownVisibilityListener != null) 316 | onDropDownVisibilityListener.onDropDownVisibilityChange(false); 317 | 318 | hideKeyboard(this); 319 | } 320 | 321 | public RecyclerView getRecyclerView() { 322 | return recyclerView; 323 | } 324 | 325 | public void enableAutoCompleteMode(OnTextChange onTextChange) { 326 | this.onTextChange = onTextChange; 327 | makeEditable(true); 328 | stopAutoCompleteObserve(); 329 | startAutoCompleteObserve(); 330 | } 331 | 332 | 333 | private void makeEditable(boolean editable) { 334 | if (editable) { 335 | setCursorVisible(true); 336 | setKeyListener(keyListener); 337 | } else { 338 | setKeyListener(null); 339 | setCursorVisible(false); 340 | setInputType(0); 341 | } 342 | } 343 | 344 | private TextWatcher textWatcher = new TextWatcher() { 345 | @Override 346 | public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { 347 | 348 | 349 | } 350 | 351 | @Override 352 | public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { 353 | onTextChange(EasySpinner.this); 354 | } 355 | 356 | @Override 357 | public void afterTextChanged(Editable editable) { 358 | 359 | } 360 | }; 361 | 362 | 363 | private int getListHeight() { 364 | /*recyclerView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED); 365 | 366 | int recyclerViewHeight = recyclerView.getMeasuredHeight(); 367 | if (recyclerViewHeight > 0) 368 | recyclerViewHeight += background.getIntrinsicHeight() / 2;*/ 369 | return MAX_SIZE; 370 | } 371 | 372 | private int getPopupWidth() { 373 | return popupWidth == -1 ? getWidth() : popupWidth; 374 | } 375 | 376 | /* private Dialog createDialog(View contentView) { 377 | Dialog dialog = new Dialog(getContext()); 378 | dialog.setCancelable(true); 379 | 380 | dialog.getWindow().getAttributes().windowAnimations = animation; 381 | dialog.getWindow().setBackgroundDrawable(background); 382 | 383 | dialog.setContentView(contentView); 384 | return dialog; 385 | }*/ 386 | 387 | private void onTextChange(EditText editText) { 388 | if (onTextChange != null) { 389 | onTextChange.onTextChange(EasySpinner.this, editText.getText().toString()); 390 | } 391 | } 392 | 393 | @Override 394 | public boolean onKeyPreIme(int keyCode, KeyEvent event) { 395 | if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) { 396 | hide(); 397 | return true; 398 | } 399 | return super.onKeyPreIme(keyCode, event); 400 | } 401 | 402 | @Override 403 | protected void onDetachedFromWindow() { 404 | super.onDetachedFromWindow(); 405 | hide(); 406 | } 407 | 408 | } 409 | 410 | 411 | -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/SwipeOpenItemTouchHelper.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.content.res.Resources; 6 | import android.graphics.Canvas; 7 | import android.graphics.Rect; 8 | import android.os.Bundle; 9 | import android.os.Parcel; 10 | import android.os.Parcelable; 11 | import android.support.annotation.Nullable; 12 | import android.support.v4.view.ViewCompat; 13 | import android.support.v7.widget.RecyclerView; 14 | import android.support.v7.widget.helper.ItemTouchHelper; 15 | import android.util.Log; 16 | import android.util.SparseArray; 17 | import android.view.MotionEvent; 18 | import android.view.VelocityTracker; 19 | import android.view.View; 20 | import android.view.ViewConfiguration; 21 | import android.view.ViewParent; 22 | 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | 26 | /* 27 | * Adapted from Google's android.support.v7.widget.helper.ItemTouchHelper 28 | * https://github.com/android/platform_frameworks_support/blob/master/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java 29 | * 30 | * Copyright (C) 2015 The Android Open Source Project 31 | * 32 | * Licensed under the Apache License, Version 2.0 (the "License"); 33 | * you may not use this file except in compliance with the License. 34 | * You may obtain a copy of the License at 35 | * 36 | * http://www.apache.org/licenses/LICENSE-2.0 37 | * 38 | * Unless required by applicable law or agreed to in writing, software 39 | * distributed under the License is distributed on an "AS IS" BASIS, 40 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 41 | * See the License for the specific language governing permissions and 42 | * limitations under the License. 43 | */ 44 | 45 | /** 46 | * Helper class to allow for swiping open hidden views of a RecyclerView. 47 | * Adapted from and based off of Google's {@link android.support.v7.widget.helper.ItemTouchHelper} 48 | */ 49 | public class SwipeOpenItemTouchHelper extends RecyclerView.ItemDecoration 50 | implements RecyclerView.OnChildAttachStateChangeListener { 51 | 52 | private static final String OPENED_STATES = "opened_states"; 53 | 54 | /** 55 | * Up direction, used for swipe to open 56 | */ 57 | public static final int UP = 1; 58 | 59 | /** 60 | * Down direction, used for swipe to open 61 | */ 62 | public static final int DOWN = 1 << 1; 63 | 64 | /** 65 | * Left direction, used for swipe to open 66 | */ 67 | public static final int LEFT = 1 << 2; 68 | 69 | /** 70 | * Right direction, used for swipe to open 71 | */ 72 | public static final int RIGHT = 1 << 3; 73 | 74 | // If you change these relative direction values, update Callback#convertToAbsoluteDirection, 75 | // Callback#convertToRelativeDirection. 76 | /** 77 | * Horizontal start direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout 78 | * direction 79 | */ 80 | public static final int START = LEFT << 2; 81 | 82 | /** 83 | * Horizontal end direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout 84 | * direction 85 | */ 86 | public static final int END = RIGHT << 2; 87 | 88 | /** 89 | * SwipeOpenItemTouchHelper is in idle state. At this state, either there is no related motion 90 | * event by the user or latest motion events have not yet triggered a swipe or drag. 91 | */ 92 | public static final int ACTION_STATE_IDLE = 0; 93 | 94 | /** 95 | * A View is currently being swiped. 96 | */ 97 | public static final int ACTION_STATE_SWIPE = 1; 98 | 99 | /** 100 | * Animation type for views which are swiped and will animate back to an open or closed position 101 | */ 102 | public static final int ANIMATION_TYPE_SWIPE = 1 << 2; 103 | 104 | private static final String TAG = "SwipeOpenHelper"; 105 | 106 | private static final boolean DEBUG = false; 107 | 108 | private static final int ACTIVE_POINTER_ID_NONE = -1; 109 | 110 | private static final int DIRECTION_FLAG_COUNT = 8; 111 | 112 | private static final int ACTION_MODE_IDLE_MASK = (1 << DIRECTION_FLAG_COUNT) - 1; 113 | 114 | private static final int ACTION_MODE_SWIPE_MASK = ACTION_MODE_IDLE_MASK << DIRECTION_FLAG_COUNT; 115 | 116 | /** 117 | * Re-use array to calculate dx dy for a ViewHolder 118 | */ 119 | private final float[] tmpPosition = new float[2]; 120 | 121 | /** 122 | * Currently selected view holder 123 | */ 124 | private SwipeOpenViewHolder selected = null; 125 | 126 | /** 127 | * Initial touch point for swipe 128 | */ 129 | float initialTouchX; 130 | float initialTouchY; 131 | 132 | /** 133 | * The diff between the last event and initial touch. 134 | */ 135 | float dX; 136 | float dY; 137 | 138 | /** 139 | * The coordinates of the selected view at the time it is selected. We record these values 140 | * when action starts so that we can consistently position it even if LayoutManager moves the 141 | * View. 142 | */ 143 | float selectedStartX; 144 | 145 | float selectedStartY; 146 | 147 | /** 148 | * The pointer we are tracking. 149 | */ 150 | int activePointerId = ACTIVE_POINTER_ID_NONE; 151 | 152 | /** 153 | * Developer callback which controls the behavior of ItemTouchHelper. 154 | */ 155 | Callback callback; 156 | 157 | /** 158 | * Current mode. 159 | */ 160 | int actionState = ACTION_STATE_IDLE; 161 | 162 | /** 163 | * The direction flags obtained from unmasking 164 | * {@link Callback#getAbsMovementFlags(RecyclerView, RecyclerView.ViewHolder)} for the 165 | * current 166 | * action state. 167 | */ 168 | int selectedFlags; 169 | 170 | /** 171 | * When a View is swiped and needs to return to an open or closed position, we create a Recover 172 | * Animation and animate it to its location using this custom Animator, instead of using 173 | * framework Animators. 174 | * Using framework animators has the side effect of clashing with ItemAnimator, creating 175 | * jumpy UIs. 176 | */ 177 | private List recoverAnimations = new ArrayList<>(); 178 | 179 | private int slop; 180 | 181 | private RecyclerView recyclerView; 182 | 183 | private boolean isRtl; 184 | 185 | /** 186 | * Flag for if a the SwipeOpenItemTouchHelper should prevent swipe-to-opens of Start or End Views 187 | * that 188 | * have a size of 0 189 | * DEFAULT: false 190 | */ 191 | private boolean preventZeroSizeViewSwipes = false; 192 | 193 | /** 194 | * Flag for if any open SwipeOpenViewHolders should be close when the view is scrolled or if 195 | * a new view holder is swiped 196 | * DEFAULT: true 197 | */ 198 | private boolean closeOnAction = true; 199 | 200 | private SwipeOpenViewHolder prevSelected; 201 | 202 | /** 203 | * Used for detecting fling swipe 204 | */ 205 | private VelocityTracker velocityTracker; 206 | 207 | private SparseArray openedPositions = new SparseArray<>(); 208 | 209 | /** 210 | * Data Observer that allow us to remove any opened positions when something is removed from the 211 | * adapter 212 | */ 213 | private final RecyclerView.AdapterDataObserver adapterDataObserver = 214 | new RecyclerView.AdapterDataObserver() { 215 | 216 | @Override 217 | public void onChanged() { 218 | // if notifyDataSetChanged is used we cannot know if opened holders should stay open, 219 | // so close all of them 220 | openedPositions.clear(); 221 | } 222 | 223 | @Override 224 | public void onItemRangeRemoved(int positionStart, int itemCount) { 225 | // if an item is removed, we need to remove the opened position 226 | for (int i = positionStart; i < positionStart + itemCount; i++) { 227 | openedPositions.remove(i); 228 | } 229 | } 230 | }; 231 | 232 | private final RecyclerView.OnItemTouchListener mOnItemTouchListener = 233 | new RecyclerView.OnItemTouchListener() { 234 | @Override 235 | public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent event) { 236 | if (DEBUG) { 237 | Log.d(TAG, "intercept: x:" + event.getX() + ",y:" + event.getY() + ", " + event); 238 | } 239 | final int action = event.getAction(); 240 | if (action == MotionEvent.ACTION_DOWN) { 241 | activePointerId = event.getPointerId(0); 242 | initialTouchX = event.getX(); 243 | initialTouchY = event.getY(); 244 | obtainVelocityTracker(); 245 | if (selected == null) { 246 | final RecoverAnimation animation = findAnimation(event); 247 | if (animation != null) { 248 | initialTouchX -= animation.x; 249 | initialTouchY -= animation.y; 250 | endRecoverAnimation(animation.viewHolder); 251 | select(animation.viewHolder, animation.actionState); 252 | updateDxDy(event, selectedFlags, 0); 253 | } 254 | } 255 | } else if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { 256 | activePointerId = ACTIVE_POINTER_ID_NONE; 257 | select(null, ACTION_STATE_IDLE); 258 | } else if (activePointerId != ACTIVE_POINTER_ID_NONE) { 259 | // in a non scroll orientation, if distance change is above threshold, we 260 | // can select the item 261 | final int index = event.findPointerIndex(activePointerId); 262 | if (DEBUG) { 263 | Log.d(TAG, "pointer index " + index); 264 | } 265 | if (index >= 0) { 266 | checkSelectForSwipe(action, event, index); 267 | } 268 | } 269 | if (velocityTracker != null) { 270 | velocityTracker.addMovement(event); 271 | } 272 | return selected != null; 273 | } 274 | 275 | @Override 276 | public void onTouchEvent(RecyclerView recyclerView, MotionEvent event) { 277 | if (DEBUG) { 278 | Log.d(TAG, "on touch: x:" + initialTouchX + ",y:" + initialTouchY + ", :" + event); 279 | } 280 | if (velocityTracker != null) { 281 | velocityTracker.addMovement(event); 282 | } 283 | if (activePointerId == ACTIVE_POINTER_ID_NONE) { 284 | return; 285 | } 286 | final int action = event.getActionMasked(); 287 | final int activePointerIndex = event.findPointerIndex(activePointerId); 288 | if (activePointerIndex >= 0) { 289 | checkSelectForSwipe(action, event, activePointerIndex); 290 | } 291 | if (selected == null) { 292 | return; 293 | } 294 | switch (action) { 295 | case MotionEvent.ACTION_MOVE: { 296 | // Find the index of the active pointer and fetch its position 297 | if (activePointerIndex >= 0) { 298 | updateDxDy(event, selectedFlags, activePointerIndex); 299 | SwipeOpenItemTouchHelper.this.recyclerView.invalidate(); 300 | } 301 | break; 302 | } 303 | case MotionEvent.ACTION_CANCEL: 304 | if (velocityTracker != null) { 305 | velocityTracker.clear(); 306 | } 307 | // fall through 308 | case MotionEvent.ACTION_UP: 309 | select(null, ACTION_STATE_IDLE); 310 | activePointerId = ACTIVE_POINTER_ID_NONE; 311 | break; 312 | case MotionEvent.ACTION_POINTER_UP: { 313 | final int pointerIndex = event.getActionIndex(); 314 | final int pointerId = event.getPointerId(pointerIndex); 315 | if (pointerId == activePointerId) { 316 | // This was our active pointer going up. Choose a new 317 | // active pointer and adjust accordingly. 318 | final int newPointerIndex = pointerIndex == 0 ? 1 : 0; 319 | activePointerId = event.getPointerId(newPointerIndex); 320 | updateDxDy(event, selectedFlags, pointerIndex); 321 | } 322 | break; 323 | } 324 | } 325 | } 326 | 327 | @Override 328 | public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) { 329 | if (!disallowIntercept) { 330 | return; 331 | } 332 | select(null, ACTION_STATE_IDLE); 333 | } 334 | }; 335 | 336 | private final RecyclerView.OnScrollListener scrollListener = new RecyclerView.OnScrollListener() { 337 | @Override 338 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 339 | if (closeOnAction && (dx != 0 || dy != 0)) { 340 | if (prevSelected != null && (Math.abs(prevSelected.getSwipeView().getTranslationX()) > 0 341 | || Math.abs(prevSelected.getSwipeView().getTranslationY()) > 0)) { 342 | closeOpenHolder(prevSelected); 343 | prevSelected = null; 344 | } 345 | // if we've got any open positions saved from a rotation, close those 346 | if (openedPositions.size() > 0) { 347 | for (int i = 0; i < openedPositions.size(); i++) { 348 | RecyclerView.ViewHolder holder = 349 | recyclerView.findViewHolderForAdapterPosition(openedPositions.keyAt(i)); 350 | if (holder instanceof SwipeOpenViewHolder) { 351 | closeOpenHolder((SwipeOpenViewHolder) holder); 352 | } 353 | openedPositions.removeAt(i); 354 | } 355 | } 356 | } 357 | } 358 | }; 359 | 360 | /** 361 | * Creates an SwipeOpenItemTouchHelper that will work with the given Callback. 362 | *

363 | * You can attach SwipeOpenItemTouchHelper to a RecyclerView via 364 | * {@link #attachToRecyclerView(RecyclerView)}. Upon attaching, it will add an item decoration, 365 | * an onItemTouchListener and a Child attach / detach listener to the RecyclerView. 366 | * 367 | * @param callback The Callback which controls the behavior of this touch helper. 368 | */ 369 | public SwipeOpenItemTouchHelper(Callback callback) { 370 | this.callback = callback; 371 | } 372 | 373 | private static boolean hitTest(View child, float x, float y, float left, float top) { 374 | return x >= left && x <= left + child.getWidth() && y >= top && y <= top + child.getHeight(); 375 | } 376 | 377 | /** 378 | * Attaches the SwipeOpenItemTouchHelper to the provided RecyclerView. If the helper is already 379 | * attached to a RecyclerView, it will first detach from the previous one. You can call this 380 | * method with {@code null} to detach it from the current RecyclerView. 381 | * NOTE: RecyclerView must have an adapter set in order to allow adapter data observing to 382 | * correctly save opened positions state. 383 | * 384 | * @param recyclerView The RecyclerView instance to which you want to add this helper or 385 | * {@code null} if you want to remove SwipeOpenItemTouchHelper from the current 386 | * RecyclerView. 387 | */ 388 | public void attachToRecyclerView(@Nullable RecyclerView recyclerView) { 389 | if (this.recyclerView == recyclerView) { 390 | return; // nothing to do 391 | } 392 | if (this.recyclerView != null) { 393 | destroyCallbacks(); 394 | } 395 | this.recyclerView = recyclerView; 396 | if (this.recyclerView != null) { 397 | setupCallbacks(); 398 | } 399 | } 400 | 401 | /** 402 | * Flag to determine if any open SwipeViewHolders are closed when the RecyclerView is scrolled, 403 | * or when a new view holder is swiped. 404 | * Default value is true. 405 | * 406 | * @param closeOnAction true to close on an action, false to keep them open 407 | */ 408 | public void setCloseOnAction(boolean closeOnAction) { 409 | this.closeOnAction = closeOnAction; 410 | } 411 | 412 | /** 413 | * Flag to prevent SwipeOpenItemTouchHelper from swiping open zero-sized Start or End views. 414 | * 415 | * @param preventZeroSizeViewSwipes true to prevent swiping open zero sized views, false to allow 416 | */ 417 | public void setPreventZeroSizeViewSwipes(boolean preventZeroSizeViewSwipes) { 418 | this.preventZeroSizeViewSwipes = preventZeroSizeViewSwipes; 419 | } 420 | 421 | private void setupCallbacks() { 422 | ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext()); 423 | slop = vc.getScaledTouchSlop(); 424 | recyclerView.addItemDecoration(this); 425 | recyclerView.addOnItemTouchListener(mOnItemTouchListener); 426 | recyclerView.addOnChildAttachStateChangeListener(this); 427 | recyclerView.addOnScrollListener(scrollListener); 428 | Resources resources = recyclerView.getContext().getResources(); 429 | isRtl = false; 430 | if (recyclerView.getAdapter() == null) { 431 | throw new IllegalStateException( 432 | "SwipeOpenItemTouchHelper.attachToRecyclerView must be called after " 433 | + "the RecyclerView's adapter has been set."); 434 | } else { 435 | recyclerView.getAdapter().registerAdapterDataObserver(adapterDataObserver); 436 | } 437 | } 438 | 439 | private void destroyCallbacks() { 440 | recyclerView.removeItemDecoration(this); 441 | recyclerView.removeOnItemTouchListener(mOnItemTouchListener); 442 | recyclerView.removeOnChildAttachStateChangeListener(this); 443 | if (recyclerView.getAdapter() != null) { 444 | recyclerView.getAdapter().unregisterAdapterDataObserver(adapterDataObserver); 445 | } 446 | 447 | // clean all attached 448 | final int recoverAnimSize = recoverAnimations.size(); 449 | for (int i = recoverAnimSize - 1; i >= 0; i--) { 450 | final RecoverAnimation recoverAnimation = recoverAnimations.get(0); 451 | callback.clearView(recyclerView, recoverAnimation.viewHolder); 452 | } 453 | recoverAnimations.clear(); 454 | releaseVelocityTracker(); 455 | isRtl = false; 456 | } 457 | 458 | private void getSelectedDxDy(float[] outPosition) { 459 | if ((selectedFlags & (LEFT | RIGHT)) != 0) { 460 | outPosition[0] = selectedStartX + dX - selected.getSwipeView().getLeft(); 461 | } else { 462 | outPosition[0] = selected.getSwipeView().getTranslationX(); 463 | } 464 | if ((selectedFlags & (UP | DOWN)) != 0) { 465 | outPosition[1] = selectedStartY + dY - selected.getSwipeView().getTop(); 466 | } else { 467 | outPosition[1] = selected.getSwipeView().getTranslationY(); 468 | } 469 | } 470 | 471 | @Override 472 | public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { 473 | float dx = 0, dy = 0; 474 | if (selected != null) { 475 | getSelectedDxDy(tmpPosition); 476 | dx = tmpPosition[0]; 477 | dy = tmpPosition[1]; 478 | } 479 | callback.onDrawOver(c, parent, selected, recoverAnimations, actionState, dx, dy); 480 | } 481 | 482 | @Override 483 | public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { 484 | 485 | float dx = 0, dy = 0; 486 | if (selected != null) { 487 | getSelectedDxDy(tmpPosition); 488 | dx = tmpPosition[0]; 489 | dy = tmpPosition[1]; 490 | } 491 | 492 | // checks if we need to prevent zero-size swipe-to-opens 493 | if (selected != null && preventZeroSizeViewSwipes) { 494 | if (preventHorizontalAction(selected, dx)) { 495 | dx = 0; 496 | } else if (preventVerticalAction(selected, dy)) { 497 | dy = 0; 498 | } 499 | } 500 | callback.onDraw(c, parent, selected, recoverAnimations, actionState, dx, dy, isRtl); 501 | } 502 | 503 | /** 504 | * Checks if we need to prevent a horizontal swipe action for a view holder -- this is used when 505 | * we have preventZeroSizeViewSwipes set to true and we need to check if we're preventing a 506 | * zero-size swipe 507 | * 508 | * @param holder the view holder 509 | * @param translationX the new translation x of the holder 510 | * @return true if we need to prevent the action, false if not 511 | */ 512 | private boolean preventHorizontalAction(final SwipeOpenViewHolder holder, 513 | final float translationX) { 514 | if (translationX > 0f && ((!isRtl && holder.getStartHiddenViewSize() == 0f) ^ (isRtl 515 | && holder.getEndHiddenViewSize() == 0f))) { 516 | return true; 517 | } else if (translationX < 0f && ((!isRtl && holder.getEndHiddenViewSize() == 0f) ^ (isRtl 518 | && holder.getStartHiddenViewSize() == 0f))) { 519 | return true; 520 | } 521 | return false; 522 | } 523 | 524 | private boolean preventVerticalAction(final SwipeOpenViewHolder holder, final float dy) { 525 | if (dy > 0f && holder.getStartHiddenViewSize() == 0f) { 526 | return true; 527 | } else if (dy < 0f && holder.getEndHiddenViewSize() == 0f) { 528 | return true; 529 | } 530 | return false; 531 | } 532 | 533 | /** 534 | * Starts dragging or swiping the given View. Call with null if you want to clear it. 535 | * 536 | * @param selected The ViewHolder to swipe. Can be null if you want to cancel the 537 | * current action 538 | * @param actionState The type of action 539 | */ 540 | private void select(SwipeOpenViewHolder selected, int actionState) { 541 | if (selected == this.selected && actionState == this.actionState) { 542 | return; 543 | } 544 | final int prevActionState = this.actionState; 545 | // prevent duplicate animations 546 | endRecoverAnimation(selected); 547 | this.actionState = actionState; 548 | int actionStateMask = (1 << (DIRECTION_FLAG_COUNT + DIRECTION_FLAG_COUNT * actionState)) - 1; 549 | boolean preventLayout = false; 550 | 551 | // close the previously selected view holder if we're swiping a new one and the flag is true 552 | if (closeOnAction && selected != null && prevSelected != null && selected != prevSelected) { 553 | closeOpenHolder(prevSelected); 554 | prevSelected = null; 555 | preventLayout = true; 556 | } 557 | 558 | // if we've got any opened positions, and closeOnAction is true, close them 559 | // NOTE: only real way for this to happen is to have a view opened during configuration change 560 | // that then has its' state saved 561 | if (closeOnAction && openedPositions.size() > 0) { 562 | for (int i = 0; i < openedPositions.size(); i++) { 563 | RecyclerView.ViewHolder holder = 564 | recyclerView.findViewHolderForAdapterPosition(openedPositions.keyAt(i)); 565 | // if our selected isn't the opened position, close it 566 | if (holder instanceof SwipeOpenViewHolder && (selected == null 567 | || holder.getAdapterPosition() != selected.getViewHolder().getAdapterPosition())) { 568 | closeOpenHolder((SwipeOpenViewHolder) holder); 569 | } 570 | openedPositions.removeAt(i); 571 | } 572 | } 573 | 574 | if (this.selected != null) { 575 | prevSelected = this.selected; 576 | // we've changed selection, we need to animate it back 577 | if (prevSelected.getViewHolder().itemView.getParent() != null) { 578 | final int swipeDir = checkPreviousSwipeDirection(prevSelected.getViewHolder()); 579 | releaseVelocityTracker(); 580 | // find where we should animate to 581 | final float targetTranslateX, targetTranslateY; 582 | getSelectedDxDy(tmpPosition); 583 | 584 | final float currentTranslateX = tmpPosition[0]; 585 | final float currentTranslateY = tmpPosition[1]; 586 | // only need to check if we need a recover animation for non-zero translation views 587 | if (prevSelected.getSwipeView().getTranslationX() != 0 588 | || prevSelected.getSwipeView().getTranslationY() != 0) { 589 | final float absTranslateX = Math.abs(currentTranslateX); 590 | final float absTranslateY = Math.abs(currentTranslateY); 591 | final SavedOpenState state; 592 | switch (swipeDir) { 593 | case LEFT: 594 | case START: 595 | targetTranslateY = 0; 596 | // check if we need to close or go to the open position 597 | if (absTranslateX > prevSelected.getEndHiddenViewSize() / 2) { 598 | targetTranslateX = prevSelected.getEndHiddenViewSize() * Math.signum(dX); 599 | state = SavedOpenState.END_OPEN; 600 | } else { 601 | targetTranslateX = 0; 602 | state = null; 603 | } 604 | break; 605 | case RIGHT: 606 | case END: 607 | targetTranslateY = 0; 608 | if (absTranslateX > prevSelected.getStartHiddenViewSize() / 2) { 609 | targetTranslateX = prevSelected.getStartHiddenViewSize() * Math.signum(dX); 610 | state = SavedOpenState.START_OPEN; 611 | } else { 612 | targetTranslateX = 0; 613 | state = null; 614 | } 615 | break; 616 | case UP: 617 | targetTranslateX = 0; 618 | if (absTranslateY > prevSelected.getEndHiddenViewSize() / 2) { 619 | targetTranslateY = prevSelected.getEndHiddenViewSize() * Math.signum(dY); 620 | state = SavedOpenState.END_OPEN; 621 | } else { 622 | targetTranslateY = 0; 623 | state = null; 624 | } 625 | break; 626 | case DOWN: 627 | targetTranslateX = 0; 628 | if (absTranslateY > prevSelected.getStartHiddenViewSize() / 2) { 629 | targetTranslateY = prevSelected.getStartHiddenViewSize() * Math.signum(dY); 630 | state = SavedOpenState.START_OPEN; 631 | } else { 632 | targetTranslateY = 0; 633 | state = null; 634 | } 635 | break; 636 | default: 637 | state = null; 638 | targetTranslateX = 0; 639 | targetTranslateY = 0; 640 | } 641 | // if state == null, we're closing it 642 | if (state == null) { 643 | openedPositions.remove(prevSelected.getViewHolder().getAdapterPosition()); 644 | } else { 645 | openedPositions.put(prevSelected.getViewHolder().getAdapterPosition(), state); 646 | } 647 | 648 | final RecoverAnimation rv = 649 | new RecoverAnimation(prevSelected, prevActionState, currentTranslateX, 650 | currentTranslateY, targetTranslateX, targetTranslateY); 651 | final long duration = callback.getAnimationDuration(recyclerView, ANIMATION_TYPE_SWIPE, 652 | targetTranslateX - currentTranslateX, targetTranslateY - currentTranslateY); 653 | rv.setDuration(duration); 654 | recoverAnimations.add(rv); 655 | rv.start(); 656 | preventLayout = true; 657 | } else { 658 | // if both translations are 0, it's closed 659 | openedPositions.remove(prevSelected.getViewHolder().getAdapterPosition()); 660 | } 661 | } else { 662 | callback.clearView(recyclerView, prevSelected); 663 | } 664 | this.selected = null; 665 | } 666 | if (selected != null) { 667 | selectedFlags = 668 | (callback.getAbsMovementFlags(recyclerView, selected.getViewHolder()) & actionStateMask) 669 | >> (this.actionState * DIRECTION_FLAG_COUNT); 670 | selectedStartX = 671 | selected.getViewHolder().itemView.getLeft() + selected.getSwipeView().getTranslationX(); 672 | selectedStartY = 673 | selected.getViewHolder().itemView.getTop() + selected.getSwipeView().getTranslationY(); 674 | this.selected = selected; 675 | } 676 | final ViewParent rvParent = recyclerView.getParent(); 677 | if (rvParent != null) { 678 | rvParent.requestDisallowInterceptTouchEvent(this.selected != null); 679 | } 680 | if (!preventLayout) { 681 | recyclerView.getLayoutManager().requestSimpleAnimationsInNextLayout(); 682 | } 683 | callback.onSelectedChanged(this.selected, this.actionState); 684 | recyclerView.invalidate(); 685 | } 686 | 687 | @Override 688 | public void onChildViewAttachedToWindow(View view) { 689 | final RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view); 690 | if (holder == null || !(holder instanceof SwipeOpenViewHolder)) { 691 | return; 692 | } 693 | // check if the view we are about to attach had previously saved open state, 694 | // and then open it based off that 695 | if (openedPositions.get(holder.getAdapterPosition(), null) != null) { 696 | final SwipeOpenViewHolder swipeHolder = (SwipeOpenViewHolder) holder; 697 | final SavedOpenState state = openedPositions.get(holder.getAdapterPosition()); 698 | 699 | if (recyclerView.getLayoutManager().canScrollVertically()) { 700 | int rtlFlipStart = isRtl ? -1 : 1; 701 | int rtlFlipEnd = isRtl ? 1 : -1; 702 | 703 | // if we're in an opened state and both view sizes are 0, then we're attempting 704 | // to restore the opened position before the view has measured, so we need to measure it 705 | if (swipeHolder.getStartHiddenViewSize() == 0 && swipeHolder.getEndHiddenViewSize() == 0) { 706 | final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 707 | final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); 708 | swipeHolder.getViewHolder().itemView.measure(widthSpec, heightSpec); 709 | } 710 | 711 | swipeHolder.getSwipeView().setTranslationX( 712 | state == SavedOpenState.START_OPEN ? swipeHolder.getStartHiddenViewSize() * rtlFlipStart 713 | : swipeHolder.getEndHiddenViewSize() * rtlFlipEnd); 714 | } else { 715 | swipeHolder.getSwipeView().setTranslationY( 716 | state == SavedOpenState.START_OPEN ? swipeHolder.getStartHiddenViewSize() 717 | : swipeHolder.getEndHiddenViewSize() * -1); 718 | } 719 | } 720 | } 721 | 722 | /** 723 | * When a View is detached from the RecyclerView it is either because the item has been deleted, 724 | * or the View is being detached/recycled because it is no longer visible (e.g. RecyclerView has 725 | * been scrolled) 726 | * 727 | * @param view the view being detached 728 | */ 729 | @Override 730 | public void onChildViewDetachedFromWindow(View view) { 731 | final RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(view); 732 | if (holder == null || !(holder instanceof SwipeOpenViewHolder)) { 733 | return; 734 | } 735 | final SwipeOpenViewHolder swipeHolder = (SwipeOpenViewHolder) holder; 736 | 737 | if (prevSelected == swipeHolder) { 738 | prevSelected = null; 739 | } 740 | if (selected != null && swipeHolder == selected) { 741 | select(null, ACTION_STATE_IDLE); 742 | } else { 743 | callback.clearView(recyclerView, swipeHolder); 744 | endRecoverAnimation(swipeHolder); 745 | } 746 | } 747 | 748 | private void endRecoverAnimation(SwipeOpenViewHolder viewHolder) { 749 | final int recoverAnimSize = recoverAnimations.size(); 750 | for (int i = recoverAnimSize - 1; i >= 0; i--) { 751 | final RecoverAnimation anim = recoverAnimations.get(i); 752 | if (anim.viewHolder == viewHolder) { 753 | if (!anim.ended) { 754 | anim.cancel(); 755 | } 756 | recoverAnimations.remove(i); 757 | } 758 | } 759 | } 760 | 761 | /** 762 | * Opens the position of the START hidden view for a given position 763 | * 764 | * @param position the position 765 | */ 766 | public void openPositionStart(final int position) { 767 | openPosition(position, SavedOpenState.START_OPEN); 768 | } 769 | 770 | /** 771 | * Opens the position of the END hidden view for a given position 772 | * 773 | * @param position the position 774 | */ 775 | public void openPositionEnd(final int position) { 776 | openPosition(position, SavedOpenState.END_OPEN); 777 | } 778 | 779 | private void openPosition(final int position, final SavedOpenState direction) { 780 | if (recyclerView == null) { 781 | return; 782 | } 783 | // attempt to close any open positions 784 | if (closeOnAction) { 785 | closeAllOpenPositions(); 786 | } 787 | 788 | RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(position); 789 | if (holder instanceof SwipeOpenViewHolder) { 790 | // check that the view holder is attached to a parent 791 | if (((SwipeOpenViewHolder) holder).getViewHolder().itemView.getParent() != null) { 792 | // end any current animations for the view holder 793 | endRecoverAnimation((SwipeOpenViewHolder) holder); 794 | openHolder((SwipeOpenViewHolder) holder, direction); 795 | recyclerView.invalidate(); 796 | } 797 | } 798 | // add open position to our saved positions 799 | openedPositions.put(position, direction); 800 | } 801 | 802 | /** 803 | * Closes the given SwipeOpenViewHolder at the given position if there is one. 804 | * If the position is not currently attached to the RecyclerView (e.g. off-screen), then 805 | * the opened position will just be removed and the holder will appear in a closed position 806 | * when it is next created/bound. 807 | * 808 | * @param position the position to close 809 | */ 810 | public void closeOpenPosition(final int position) { 811 | if (recyclerView == null) { 812 | return; 813 | } 814 | RecyclerView.ViewHolder holder = recyclerView.findViewHolderForAdapterPosition(position); 815 | if (holder instanceof SwipeOpenViewHolder) { 816 | // check that the view holder is attached to a parent 817 | if (((SwipeOpenViewHolder) holder).getViewHolder().itemView.getParent() != null) { 818 | // end any current animations for the view holder 819 | endRecoverAnimation((SwipeOpenViewHolder) holder); 820 | closeOpenHolder((SwipeOpenViewHolder) holder); 821 | recyclerView.invalidate(); 822 | } 823 | } 824 | // remove the position if we have not already 825 | openedPositions.remove(position); 826 | } 827 | 828 | /** 829 | * Closes all currently opened SwipeOpenViewHolders for the currently attached RecyclerView 830 | */ 831 | public void closeAllOpenPositions() { 832 | if (recyclerView == null) { 833 | return; 834 | } 835 | for (int i = openedPositions.size() - 1; i >= 0; i--) { 836 | closeOpenPosition(openedPositions.keyAt(i)); 837 | } 838 | // remove all positions in case one was not removed 839 | openedPositions.clear(); 840 | } 841 | 842 | /** 843 | * Closes a SwipeOpenHolder that has been previously opened 844 | * 845 | * @param holder the holder 846 | */ 847 | private void closeOpenHolder(SwipeOpenViewHolder holder) { 848 | final View swipeView = holder.getSwipeView(); 849 | final float translationX = swipeView.getTranslationX(); 850 | final float translationY = swipeView.getTranslationY(); 851 | final RecoverAnimation rv = new RecoverAnimation(holder, 0, translationX, translationY, 0, 0); 852 | final long duration = 853 | callback.getAnimationDuration(recyclerView, ANIMATION_TYPE_SWIPE, translationX, 854 | translationY); 855 | rv.setDuration(duration); 856 | recoverAnimations.add(rv); 857 | rv.start(); 858 | // remove it from our open positions if we've got it 859 | openedPositions.remove(holder.getViewHolder().getAdapterPosition()); 860 | } 861 | 862 | /** 863 | * Opens a SwipeOpenHolder in a given direction 864 | * 865 | * @param holder the holder 866 | * @param direction the direction 867 | * @return true if the view was opened, false if not 868 | */ 869 | private void openHolder(SwipeOpenViewHolder holder, SavedOpenState direction) { 870 | final View swipeView = holder.getSwipeView(); 871 | final float translationX = swipeView.getTranslationX(); 872 | final float translationY = swipeView.getTranslationY(); 873 | final float openSize = direction == SavedOpenState.START_OPEN ? holder.getStartHiddenViewSize() 874 | : holder.getEndHiddenViewSize(); 875 | 876 | final RecoverAnimation rv; 877 | if (recyclerView.getLayoutManager().canScrollVertically()) { 878 | int rtlFlipStart = isRtl ? -1 : 1; 879 | int rtlFlipEnd = isRtl ? 1 : -1; 880 | 881 | float targetDx = 882 | direction == SavedOpenState.START_OPEN ? openSize * rtlFlipStart : openSize * rtlFlipEnd; 883 | rv = new RecoverAnimation(holder, 0, translationX, translationY, targetDx, 0); 884 | } else { 885 | float targetDx = direction == SavedOpenState.START_OPEN ? openSize * -1 : openSize; 886 | rv = new RecoverAnimation(holder, 0, translationX, translationY, 0, targetDx); 887 | } 888 | final long duration = 889 | callback.getAnimationDuration(recyclerView, ANIMATION_TYPE_SWIPE, translationX, 890 | translationY); 891 | rv.setDuration(duration); 892 | recoverAnimations.add(rv); 893 | rv.start(); 894 | } 895 | 896 | @Override 897 | public void getItemOffsets(Rect outRect, View view, RecyclerView parent, 898 | RecyclerView.State state) { 899 | outRect.setEmpty(); 900 | } 901 | 902 | private void obtainVelocityTracker() { 903 | if (velocityTracker != null) { 904 | velocityTracker.recycle(); 905 | } 906 | velocityTracker = VelocityTracker.obtain(); 907 | } 908 | 909 | private void releaseVelocityTracker() { 910 | if (velocityTracker != null) { 911 | velocityTracker.recycle(); 912 | velocityTracker = null; 913 | } 914 | } 915 | 916 | private RecyclerView.ViewHolder findSwipedView(MotionEvent motionEvent) { 917 | final RecyclerView.LayoutManager lm = recyclerView.getLayoutManager(); 918 | if (activePointerId == ACTIVE_POINTER_ID_NONE) { 919 | return null; 920 | } 921 | final int pointerIndex = motionEvent.findPointerIndex(activePointerId); 922 | final float dx = motionEvent.getX(pointerIndex) - initialTouchX; 923 | final float dy = motionEvent.getY(pointerIndex) - initialTouchY; 924 | final float absDx = Math.abs(dx); 925 | final float absDy = Math.abs(dy); 926 | 927 | if (absDx < slop && absDy < slop) { 928 | return null; 929 | } 930 | if (absDx > absDy && lm.canScrollHorizontally()) { 931 | return null; 932 | } else if (absDy > absDx && lm.canScrollVertically()) { 933 | return null; 934 | } 935 | View child = findChildView(motionEvent); 936 | if (child == null) { 937 | return null; 938 | } 939 | RecyclerView.ViewHolder holder = recyclerView.getChildViewHolder(child); 940 | if (holder instanceof SwipeOpenViewHolder) { 941 | return holder; 942 | } 943 | return null; 944 | } 945 | 946 | /** 947 | * Checks whether we should select a View for swiping. 948 | */ 949 | private boolean checkSelectForSwipe(int action, MotionEvent motionEvent, int pointerIndex) { 950 | if (selected != null || action != MotionEvent.ACTION_MOVE) { 951 | return false; 952 | } 953 | if (recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING) { 954 | return false; 955 | } 956 | final RecyclerView.ViewHolder vh = findSwipedView(motionEvent); 957 | if (vh == null) { 958 | return false; 959 | } 960 | 961 | final int movementFlags = callback.getAbsMovementFlags(recyclerView, vh); 962 | 963 | final int swipeFlags = 964 | (movementFlags & ACTION_MODE_SWIPE_MASK) >> (DIRECTION_FLAG_COUNT * ACTION_STATE_SWIPE); 965 | 966 | if (swipeFlags == 0) { 967 | return false; 968 | } 969 | 970 | // dX and dY are only set in allowed directions. We use custom x/y here instead of 971 | // updateDxDy to avoid swiping if user moves more in the other direction 972 | final float x = motionEvent.getX(pointerIndex); 973 | final float y = motionEvent.getY(pointerIndex); 974 | 975 | // Calculate the distance moved 976 | final float dx = x - initialTouchX; 977 | final float dy = y - initialTouchY; 978 | // swipe target is chose w/o applying flags so it does not really check if swiping in that 979 | // direction is allowed. This why here, we use dX dY to check slope value again. 980 | final float absDx = Math.abs(dx); 981 | final float absDy = Math.abs(dy); 982 | 983 | if (absDx < slop && absDy < slop) { 984 | return false; 985 | } 986 | if (absDx > absDy) { 987 | if (dx < 0 && (swipeFlags & LEFT) == 0) { 988 | return false; 989 | } 990 | if (dx > 0 && (swipeFlags & RIGHT) == 0) { 991 | return false; 992 | } 993 | } else { 994 | if (dy < 0 && (swipeFlags & UP) == 0) { 995 | return false; 996 | } 997 | if (dy > 0 && (swipeFlags & DOWN) == 0) { 998 | return false; 999 | } 1000 | } 1001 | dX = dY = 0f; 1002 | activePointerId = motionEvent.getPointerId(0); 1003 | select((SwipeOpenViewHolder) vh, ACTION_STATE_SWIPE); 1004 | return true; 1005 | } 1006 | 1007 | private View findChildView(MotionEvent event) { 1008 | // first check elevated views, if none, then call RV 1009 | final float x = event.getX(); 1010 | final float y = event.getY(); 1011 | if (selected != null) { 1012 | final View selectedView = selected.getViewHolder().itemView; 1013 | if (hitTest(selectedView, x, y, selectedStartX + dX, selectedStartY + dY)) { 1014 | return selectedView; 1015 | } 1016 | } 1017 | for (int i = recoverAnimations.size() - 1; i >= 0; i--) { 1018 | final RecoverAnimation anim = recoverAnimations.get(i); 1019 | final View view = anim.viewHolder.getViewHolder().itemView; 1020 | if (hitTest(view, x, y, anim.x, anim.y)) { 1021 | return view; 1022 | } 1023 | } 1024 | return recyclerView.findChildViewUnder(x, y); 1025 | } 1026 | 1027 | /** 1028 | * Starts swiping the provided ViewHolder. 1029 | * See {@link android.support.v7.widget.helper.ItemTouchHelper#startSwipe(RecyclerView.ViewHolder)} 1030 | * 1031 | * @param viewHolder The ViewHolder to start swiping. It must be a direct child of 1032 | * RecyclerView. 1033 | */ 1034 | public void startSwipe(SwipeOpenViewHolder viewHolder) { 1035 | if (viewHolder.getViewHolder().itemView.getParent() != recyclerView) { 1036 | Log.e(TAG, "Start swipe has been called with a view holder which is not a child of " 1037 | + "the RecyclerView controlled by this SwipeOpenItemTouchHelper."); 1038 | return; 1039 | } 1040 | obtainVelocityTracker(); 1041 | dX = dY = 0f; 1042 | select(viewHolder, ACTION_STATE_SWIPE); 1043 | } 1044 | 1045 | private RecoverAnimation findAnimation(MotionEvent event) { 1046 | if (recoverAnimations.isEmpty()) { 1047 | return null; 1048 | } 1049 | View target = findChildView(event); 1050 | for (int i = recoverAnimations.size() - 1; i >= 0; i--) { 1051 | final RecoverAnimation anim = recoverAnimations.get(i); 1052 | if (anim.viewHolder.getViewHolder().itemView == target) { 1053 | return anim; 1054 | } 1055 | } 1056 | return null; 1057 | } 1058 | 1059 | private void updateDxDy(MotionEvent ev, int directionFlags, int pointerIndex) { 1060 | final float x = ev.getX(pointerIndex); 1061 | final float y = ev.getY(pointerIndex); 1062 | // Calculate the distance moved 1063 | dX = x - initialTouchX; 1064 | dY = y - initialTouchY; 1065 | if ((directionFlags & LEFT) == 0) { 1066 | dX = Math.max(0, dX); 1067 | } 1068 | if ((directionFlags & RIGHT) == 0) { 1069 | dX = Math.min(0, dX); 1070 | } 1071 | if ((directionFlags & UP) == 0) { 1072 | dY = Math.max(0, dY); 1073 | } 1074 | if ((directionFlags & DOWN) == 0) { 1075 | dY = Math.min(0, dY); 1076 | } 1077 | } 1078 | 1079 | private int checkPreviousSwipeDirection(RecyclerView.ViewHolder viewHolder) { 1080 | final int originalMovementFlags = callback.getMovementFlags(recyclerView, viewHolder); 1081 | final int absoluteMovementFlags = callback.convertToAbsoluteDirection(originalMovementFlags, 1082 | ViewCompat.getLayoutDirection(recyclerView)); 1083 | final int flags = (absoluteMovementFlags & ACTION_MODE_SWIPE_MASK) >> (ACTION_STATE_SWIPE 1084 | * DIRECTION_FLAG_COUNT); 1085 | if (flags == 0) { 1086 | return 0; 1087 | } 1088 | final int originalFlags = (originalMovementFlags & ACTION_MODE_SWIPE_MASK) >> ( 1089 | ACTION_STATE_SWIPE 1090 | * DIRECTION_FLAG_COUNT); 1091 | int swipeDir; 1092 | if (Math.abs(dX) > Math.abs(dY)) { 1093 | if ((swipeDir = checkHorizontalSwipe(flags)) > 0) { 1094 | // if swipe dir is not in original flags, it should be the relative direction 1095 | if ((originalFlags & swipeDir) == 0) { 1096 | // convert to relative 1097 | return Callback.convertToRelativeDirection(swipeDir, 1098 | ViewCompat.getLayoutDirection(recyclerView)); 1099 | } 1100 | return swipeDir; 1101 | } 1102 | if ((swipeDir = checkVerticalSwipe(flags)) > 0) { 1103 | return swipeDir; 1104 | } 1105 | } else { 1106 | if ((swipeDir = checkVerticalSwipe(flags)) > 0) { 1107 | return swipeDir; 1108 | } 1109 | if ((swipeDir = checkHorizontalSwipe(flags)) > 0) { 1110 | // if swipe dir is not in original flags, it should be the relative direction 1111 | if ((originalFlags & swipeDir) == 0) { 1112 | // convert to relative 1113 | return Callback.convertToRelativeDirection(swipeDir, 1114 | ViewCompat.getLayoutDirection(recyclerView)); 1115 | } 1116 | return swipeDir; 1117 | } 1118 | } 1119 | return 0; 1120 | } 1121 | 1122 | private int checkHorizontalSwipe(int flags) { 1123 | if ((flags & (LEFT | RIGHT)) != 0) { 1124 | return dX > 0 ? RIGHT : LEFT; 1125 | } 1126 | return 0; 1127 | } 1128 | 1129 | private int checkVerticalSwipe(int flags) { 1130 | if ((flags & (UP | DOWN)) != 0) { 1131 | return dY > 0 ? DOWN : UP; 1132 | } 1133 | return 0; 1134 | } 1135 | 1136 | public void onSaveInstanceState(Bundle outState) { 1137 | outState.putSparseParcelableArray(OPENED_STATES, openedPositions); 1138 | } 1139 | 1140 | public void restoreInstanceState(Bundle savedInstanceState) { 1141 | openedPositions = savedInstanceState.getSparseParcelableArray(OPENED_STATES); 1142 | if (openedPositions == null) { 1143 | openedPositions = new SparseArray<>(); 1144 | } 1145 | } 1146 | 1147 | /** 1148 | * Base Callback class that extends off of {@link ItemTouchHelper.Callback} 1149 | */ 1150 | @SuppressWarnings("UnusedParameters") 1151 | public abstract static class Callback 1152 | extends ItemTouchHelper.Callback { 1153 | 1154 | @Override 1155 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, 1156 | RecyclerView.ViewHolder target) { 1157 | // do not use 1158 | return false; 1159 | } 1160 | 1161 | @Override 1162 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { 1163 | // do not use 1164 | } 1165 | 1166 | /** 1167 | * Convenience method to create movement flags. 1168 | *

1169 | * For instance, if you want to let your items be drag & dropped vertically and swiped 1170 | * left to be dismissed, you can call this method with: 1171 | * makeMovementFlags(UP | DOWN, LEFT); 1172 | * 1173 | * @param swipeFlags The directions in which the item can be swiped. 1174 | * @return Returns an integer composed of the given drag and swipe flags. 1175 | */ 1176 | public static int makeMovementFlags(int swipeFlags) { 1177 | return makeFlag(ACTION_STATE_IDLE, swipeFlags) | makeFlag(ACTION_STATE_SWIPE, swipeFlags); 1178 | } 1179 | 1180 | final int getAbsMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 1181 | final int flags = getMovementFlags(recyclerView, viewHolder); 1182 | return convertToAbsoluteDirection(flags, ViewCompat.getLayoutDirection(recyclerView)); 1183 | } 1184 | 1185 | /** 1186 | * Called when the ViewHolder is changed. 1187 | *

1188 | * If you override this method, you should call super. 1189 | * 1190 | * @param viewHolder The new ViewHolder that is being swiped. Might be null if 1191 | * it is cleared. 1192 | * @param actionState One of {@link SwipeOpenItemTouchHelper#ACTION_STATE_IDLE}, 1193 | * {@link SwipeOpenItemTouchHelper#ACTION_STATE_SWIPE} 1194 | * @see #clearView(RecyclerView, SwipeOpenViewHolder) 1195 | */ 1196 | public void onSelectedChanged(SwipeOpenViewHolder viewHolder, int actionState) { 1197 | if (viewHolder != null) { 1198 | getDefaultUIUtil().onSelected(viewHolder.getSwipeView()); 1199 | } 1200 | } 1201 | 1202 | private void onDraw(Canvas c, RecyclerView parent, SwipeOpenViewHolder selected, 1203 | List recoverAnimationList, int actionState, float dX, float dY, 1204 | boolean isRtl) { 1205 | final int recoverAnimSize = recoverAnimationList.size(); 1206 | for (int i = 0; i < recoverAnimSize; i++) { 1207 | final RecoverAnimation anim = recoverAnimationList.get(i); 1208 | anim.update(); 1209 | final int count = c.save(); 1210 | onChildDraw(c, parent, anim.viewHolder, anim.x, anim.y, false); 1211 | c.restoreToCount(count); 1212 | } 1213 | if (selected != null) { 1214 | final int count = c.save(); 1215 | notifySwipeDirections(selected, isRtl, dX, dY); 1216 | onChildDraw(c, parent, selected, dX, dY, true); 1217 | c.restoreToCount(count); 1218 | } 1219 | } 1220 | 1221 | /** 1222 | * Notifies the SwipeOpenHolder when one of its hidden views has become visible. 1223 | * 1224 | * @param holder the holder 1225 | * @param isRtl if the layout is RTL or not 1226 | * @param dX the new dX of the swiped view 1227 | * @param dY the new dY of the swiped view 1228 | */ 1229 | private void notifySwipeDirections(SwipeOpenViewHolder holder, boolean isRtl, float dX, 1230 | float dY) { 1231 | // check if we are about to start a swipe to open start or open end positions 1232 | View swipeView = holder.getSwipeView(); 1233 | // 0 or negative translationX, heading to positive translationX 1234 | if (swipeView.getTranslationX() <= 0 && dX > 0) { 1235 | if (isRtl) { 1236 | holder.notifyEndOpen(); 1237 | } else { 1238 | holder.notifyStartOpen(); 1239 | } 1240 | // 0 or positive translationX, heading to negative translationX 1241 | } else if (swipeView.getTranslationX() >= 0 && dX < 0) { 1242 | if (isRtl) { 1243 | holder.notifyStartOpen(); 1244 | } else { 1245 | holder.notifyEndOpen(); 1246 | } 1247 | // 0 or positive translationY, heading to negative translationY 1248 | } else if (swipeView.getTranslationY() >= 0 && dY < 0) { 1249 | holder.notifyEndOpen(); 1250 | } else if (swipeView.getTranslationY() <= 0 && dY > 0) { 1251 | holder.notifyStartOpen(); 1252 | } 1253 | } 1254 | 1255 | private void onDrawOver(Canvas c, RecyclerView parent, SwipeOpenViewHolder selected, 1256 | List recoverAnimationList, int actionState, float dX, float dY) { 1257 | final int recoverAnimSize = recoverAnimationList.size(); 1258 | boolean hasRunningAnimation = false; 1259 | for (int i = recoverAnimSize - 1; i >= 0; i--) { 1260 | final RecoverAnimation anim = recoverAnimationList.get(i); 1261 | if (anim.ended) { 1262 | recoverAnimationList.remove(i); 1263 | } else { 1264 | hasRunningAnimation = true; 1265 | } 1266 | } 1267 | if (hasRunningAnimation) { 1268 | parent.invalidate(); 1269 | } 1270 | } 1271 | 1272 | public void clearView(RecyclerView recyclerView, SwipeOpenViewHolder viewHolder) { 1273 | getDefaultUIUtil().clearView(viewHolder.getSwipeView()); 1274 | } 1275 | 1276 | public void onChildDraw(Canvas c, RecyclerView recyclerView, SwipeOpenViewHolder viewHolder, 1277 | float dX, float dY, boolean isCurrentlyActive) { 1278 | // handle the draw 1279 | getDefaultUIUtil().onDraw(c, recyclerView, viewHolder.getSwipeView(), dX, dY, 1280 | ACTION_STATE_SWIPE, isCurrentlyActive); 1281 | } 1282 | 1283 | public long getAnimationDuration(RecyclerView recyclerView, int animationType, float animateDx, 1284 | float animateDy) { 1285 | final RecyclerView.ItemAnimator itemAnimator = recyclerView.getItemAnimator(); 1286 | if (itemAnimator == null) { 1287 | return DEFAULT_SWIPE_ANIMATION_DURATION; 1288 | } else { 1289 | return itemAnimator.getMoveDuration(); 1290 | } 1291 | } 1292 | } 1293 | 1294 | /** 1295 | * Simple callback class that defines the swipe directions allowed and delegates everything else 1296 | * to the base class 1297 | */ 1298 | @SuppressWarnings("UnusedParameters") 1299 | public static class SimpleCallback extends Callback { 1300 | 1301 | private int mDefaultSwipeDirs; 1302 | 1303 | public SimpleCallback(int swipeDirs) { 1304 | mDefaultSwipeDirs = swipeDirs; 1305 | } 1306 | 1307 | public void setDefaultSwipeDirs(int defaultSwipeDirs) { 1308 | mDefaultSwipeDirs = defaultSwipeDirs; 1309 | } 1310 | 1311 | public int getSwipeDirs(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 1312 | return mDefaultSwipeDirs; 1313 | } 1314 | 1315 | @Override 1316 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { 1317 | return makeMovementFlags(getSwipeDirs(recyclerView, viewHolder)); 1318 | } 1319 | } 1320 | 1321 | private static class RecoverAnimation implements Animator.AnimatorListener { 1322 | 1323 | final float startDx; 1324 | 1325 | final float startDy; 1326 | 1327 | final float targetX; 1328 | 1329 | final float targetY; 1330 | 1331 | final SwipeOpenViewHolder viewHolder; 1332 | 1333 | final int actionState; 1334 | 1335 | private final ValueAnimator valueAnimator; 1336 | 1337 | float x; 1338 | 1339 | float y; 1340 | 1341 | private boolean ended = false; 1342 | 1343 | private float fraction; 1344 | 1345 | public RecoverAnimation(SwipeOpenViewHolder viewHolder, int actionState, float startDx, 1346 | float startDy, float targetX, float targetY) { 1347 | this.actionState = actionState; 1348 | this.viewHolder = viewHolder; 1349 | this.startDx = startDx; 1350 | this.startDy = startDy; 1351 | this.targetX = targetX; 1352 | this.targetY = targetY; 1353 | valueAnimator = ValueAnimator.ofFloat(0, 1); 1354 | valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 1355 | @Override 1356 | public void onAnimationUpdate(ValueAnimator animation) { 1357 | setFraction(animation.getAnimatedFraction()); 1358 | } 1359 | }); 1360 | valueAnimator.setTarget(viewHolder.getViewHolder().itemView); 1361 | valueAnimator.addListener(this); 1362 | setFraction(0f); 1363 | } 1364 | 1365 | public void setDuration(long duration) { 1366 | valueAnimator.setDuration(duration); 1367 | } 1368 | 1369 | public void start() { 1370 | viewHolder.getViewHolder().setIsRecyclable(false); 1371 | valueAnimator.start(); 1372 | } 1373 | 1374 | public void cancel() { 1375 | valueAnimator.cancel(); 1376 | } 1377 | 1378 | public void setFraction(float fraction) { 1379 | this.fraction = fraction; 1380 | } 1381 | 1382 | /** 1383 | * We run updates on onDraw method but use the fraction from animator callback. 1384 | * This way, we can sync translate x/y values w/ the animators to avoid one-off frames. 1385 | */ 1386 | public void update() { 1387 | if (startDx == targetX) { 1388 | x = viewHolder.getSwipeView().getTranslationX(); 1389 | } else { 1390 | x = startDx + fraction * (targetX - startDx); 1391 | } 1392 | if (startDy == targetY) { 1393 | y = viewHolder.getSwipeView().getTranslationY(); 1394 | } else { 1395 | y = startDy + fraction * (targetY - startDy); 1396 | } 1397 | } 1398 | 1399 | @Override 1400 | public void onAnimationStart(Animator animator) { 1401 | 1402 | } 1403 | 1404 | @Override 1405 | public void onAnimationEnd(Animator animator) { 1406 | if (!ended) { 1407 | viewHolder.getViewHolder().setIsRecyclable(true); 1408 | } 1409 | ended = true; 1410 | } 1411 | 1412 | @Override 1413 | public void onAnimationCancel(Animator animator) { 1414 | setFraction(1f); //make sure we recover the view's state. 1415 | } 1416 | 1417 | @Override 1418 | public void onAnimationRepeat(Animator animator) { 1419 | 1420 | } 1421 | } 1422 | 1423 | /** 1424 | * Enum for saving the opened state of the view holders 1425 | */ 1426 | private enum SavedOpenState implements Parcelable { 1427 | START_OPEN, END_OPEN; 1428 | 1429 | @Override 1430 | public int describeContents() { 1431 | return 0; 1432 | } 1433 | 1434 | @Override 1435 | public void writeToParcel(Parcel dest, int flags) { 1436 | dest.writeInt(ordinal()); 1437 | } 1438 | 1439 | public static final Creator CREATOR = 1440 | new Creator() { 1441 | @Override 1442 | public SavedOpenState createFromParcel(Parcel source) { 1443 | return SavedOpenState.values()[source.readInt()]; 1444 | } 1445 | 1446 | @Override 1447 | public SavedOpenState[] newArray(int size) { 1448 | return new SavedOpenState[size]; 1449 | } 1450 | }; 1451 | 1452 | } 1453 | } -------------------------------------------------------------------------------- /library/src/main/java/easyadapter/dc/com/library/SwipeOpenViewHolder.java: -------------------------------------------------------------------------------- 1 | package easyadapter.dc.com.library; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | 7 | /** 8 | * Interface for interacting with a swipe open ViewHolder. 9 | * ViewHolders that are to be swiped must implement this interface 10 | */ 11 | public interface SwipeOpenViewHolder { 12 | 13 | /** 14 | * Returns the {@link View} that will be swiped opened and closed. 15 | * 16 | * @return a non-null view to swipe 17 | */ 18 | @NonNull 19 | View getSwipeView(); 20 | 21 | /** 22 | * Returns the {@link RecyclerView.ViewHolder} that contains the Swipe View 23 | * 24 | * @return the view holder 25 | */ 26 | @NonNull 27 | RecyclerView.ViewHolder getViewHolder(); 28 | 29 | /** 30 | * Size of the hidden view at the END of the SwipeOpenViewHolder. 31 | * This will be the view at the RIGHT/END of the holder when horizontal swiping is supported, 32 | * and will be BOTTOM/DOWN when vertical swiping is supported. 33 | * 34 | * @return the width (if horizontal swiping) or height (if vertical swiping) of the view to reveal, 35 | * Return 0 if you want to return to a closed position after every swipe in that direciton 36 | */ 37 | float getEndHiddenViewSize(); 38 | 39 | /** 40 | * Size of the hidden view at the START of the SwipeOpenViewHolder. 41 | * This will be the view at the LEFT/START of the holder when horizontal swiping is supported, 42 | * and will be TOP/UP when vertical swiping is supported. 43 | * 44 | * @return the width (if horizontal swiping) or height (if vertical swiping) of the view to reveal. 45 | * Return 0 if you want to return to a closed position after every swipe in that direciton 46 | */ 47 | float getStartHiddenViewSize(); 48 | 49 | /** 50 | * Notify the SwipeOpenHolder that the START view has become visible from a swipe. 51 | * Ex: This could be used to set a background color to the underlying view so that it matches your 52 | * hidden view during an over-swipe 53 | */ 54 | void notifyStartOpen(); 55 | 56 | /** 57 | * Notify the SwipeOpenHolder that the END View has become visible from a swipe 58 | * Ex: This could be used to set a background color to the underlying view so that it matches your 59 | * hidden view during an over-swipe 60 | */ 61 | void notifyEndOpen(); 62 | 63 | } -------------------------------------------------------------------------------- /library/src/main/res/anim/down_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /library/src/main/res/anim/down_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /library/src/main/res/anim/up_in.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /library/src/main/res/anim/up_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 14 | -------------------------------------------------------------------------------- /library/src/main/res/layout/layout_load_more.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /library/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /library/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | 3 | --------------------------------------------------------------------------------