├── settings.gradle ├── app ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── styles.xml │ │ │ ├── drawable │ │ │ │ ├── scrnshot.png │ │ │ │ ├── gradient.xml │ │ │ │ └── ic_launcher_background.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 │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item_animal_section.xml │ │ │ │ └── item_animal.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── nestedrecyclersample │ │ │ │ ├── ui │ │ │ │ ├── adapter │ │ │ │ │ ├── base │ │ │ │ │ │ ├── BaseViewHolder.kt │ │ │ │ │ │ ├── BaseListAdapter.kt │ │ │ │ │ │ └── BaseAdapter.kt │ │ │ │ │ ├── AnimalAdapter.kt │ │ │ │ │ └── AnimalSectionAdapter.kt │ │ │ │ └── MainActivity.kt │ │ │ │ ├── data │ │ │ │ ├── domain │ │ │ │ │ └── Models.kt │ │ │ │ └── DataSource.kt │ │ │ │ └── utils │ │ │ │ └── ViewExtensions.kt │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── nestedrecyclersample │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── com │ │ └── example │ │ └── nestedrecyclersample │ │ └── ExampleInstrumentedTest.kt ├── .gitignore ├── proguard-rules.pro └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── README.md ├── .gitignore ├── gradle.properties ├── gradlew.bat └── gradlew /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | rootProject.name = "NestedRecyclerSample" -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | NestedRecyclerSample 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/scrnshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/drawable/scrnshot.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/minarja1/NestedRecyclerSample/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 108dp 5 | 144dp 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Feb 22 12:30:35 CET 2021 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-6.5-bin.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NestedRecyclerSample 2 | A sample app for building nested RecyclerViews. 3 | 4 | Refer to [this Medium article ](https://jakub-minarik.medium.com/nested-recycler-in-android-done-right-b101744e2a9a) for tutorial. 5 | 6 | NestedRecyclerSample screenshot 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/adapter/base/BaseViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui.adapter.base 2 | 3 | import android.view.View 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | abstract class BaseViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { 7 | abstract fun bind(item: T, position: Int) 8 | } 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/data/domain/Models.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.data.domain 2 | 3 | import java.util.* 4 | 5 | data class AnimalSection( 6 | val id: String = UUID.randomUUID().toString(), 7 | val title: String, 8 | val animals: List = mutableListOf(), 9 | ) 10 | 11 | data class Animal( 12 | val name: String, 13 | val image: String, 14 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable/gradient.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 12 | 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | .idea/ 6 | .idea/misc.xml 7 | .history/ 8 | .DS_Store 9 | .settings/ 10 | /build 11 | /captures 12 | /dev 13 | /release 14 | /app/build 15 | /covert/build 16 | .externalNativeBuild 17 | .cxx 18 | *.apk 19 | *.ap_ 20 | *.aab 21 | output.json 22 | # Files for the ART/Dalvik VM 23 | *.dex 24 | 25 | # Java class files 26 | *.class 27 | *.classpath 28 | 29 | *.pepk 30 | 31 | # Generated files 32 | bin/ 33 | gen/ 34 | out/ 35 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | .idea/* 5 | .idea/ 6 | .idea/misc.xml 7 | .history/ 8 | .DS_Store 9 | .settings/ 10 | /build 11 | /captures 12 | /dev 13 | /release 14 | /app/build 15 | /covert/build 16 | .externalNativeBuild 17 | .cxx 18 | *.apk 19 | *.ap_ 20 | *.aab 21 | output.json 22 | # Files for the ART/Dalvik VM 23 | *.dex 24 | 25 | # Java class files 26 | *.class 27 | *.classpath 28 | 29 | *.pepk 30 | 31 | # Generated files 32 | bin/ 33 | gen/ 34 | out/ 35 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/nestedrecyclersample/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFBB86FC 4 | #FF6200EE 5 | #FF3700B3 6 | #FF03DAC5 7 | #FF018786 8 | #FF000000 9 | #FFFFFFFF 10 | 11 | #26FFFFFF 12 | #121212 13 | #FFF 14 | #BFFFFFFF 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/nestedrecyclersample/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample 2 | 3 | import androidx.test.platform.app.InstrumentationRegistry 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 22 | assertEquals("com.example.nestedrecyclersample", appContext.packageName) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/adapter/AnimalAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui.adapter 2 | 3 | import android.view.View 4 | import android.widget.ImageView 5 | import android.widget.TextView 6 | import coil.load 7 | import com.example.nestedrecyclersample.R 8 | import com.example.nestedrecyclersample.data.domain.Animal 9 | import com.example.nestedrecyclersample.ui.adapter.base.BaseAdapter 10 | 11 | class AnimalAdapter( 12 | items: List = emptyList(), 13 | ) : BaseAdapter( 14 | R.layout.item_animal, 15 | items, 16 | ) { 17 | override fun bind(itemView: View, item: Animal, position: Int, viewHolder: BaseViewHolderImp) { 18 | itemView.run { 19 | findViewById(R.id.titleTextView)?.text = item.name 20 | findViewById(R.id.imageView)?.load(item.image) 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /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=-Xmx2048m -Dfile.encoding=UTF-8 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 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | # Kotlin code style for this project: "official" or "obsolete": 21 | kotlin.code.style=official -------------------------------------------------------------------------------- /app/src/main/res/layout/item_animal_section.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 19 | 20 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/adapter/base/BaseListAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui.adapter.base 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.DiffUtil 7 | import androidx.recyclerview.widget.ListAdapter 8 | 9 | abstract class BaseListAdapter( 10 | private val itemLayoutRes: Int, 11 | diffCallback: DiffUtil.ItemCallback, 12 | ) : ListAdapter>(diffCallback) { 13 | 14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { 15 | val view = LayoutInflater.from(parent.context).inflate(itemLayoutRes, parent, false) 16 | return BaseViewHolderImp(view) 17 | } 18 | 19 | override fun getItemViewType(position: Int): Int { 20 | return itemLayoutRes 21 | } 22 | 23 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 24 | holder.bind(getItem(position), position) 25 | } 26 | 27 | inner class BaseViewHolderImp(itemView: View) : BaseViewHolder(itemView) { 28 | override fun bind(item: T, position: Int) { 29 | this@BaseListAdapter.bind(itemView, item, position, this) 30 | } 31 | } 32 | 33 | abstract fun bind(itemView: View, item: T, position: Int, viewHolder: BaseViewHolderImp) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/adapter/base/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui.adapter.base 2 | 3 | import android.view.LayoutInflater 4 | import android.view.View 5 | import android.view.ViewGroup 6 | import androidx.recyclerview.widget.RecyclerView 7 | 8 | abstract class BaseAdapter( 9 | private val itemLayoutRes: Int, 10 | var items: List, 11 | ) : RecyclerView.Adapter>() { 12 | 13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder { 14 | val view = LayoutInflater.from(parent.context).inflate(itemLayoutRes, parent, false) 15 | return BaseViewHolderImp(view) 16 | } 17 | 18 | override fun getItemCount(): Int { 19 | return items.size 20 | } 21 | 22 | override fun getItemViewType(position: Int): Int { 23 | return itemLayoutRes 24 | } 25 | 26 | override fun onBindViewHolder(holder: BaseViewHolder, position: Int) { 27 | holder.bind(items[position], position) 28 | } 29 | 30 | inner class BaseViewHolderImp(itemView: View) : BaseViewHolder(itemView) { 31 | override fun bind(item: T, position: Int) { 32 | this@BaseAdapter.bind(itemView, item, position, this) 33 | } 34 | } 35 | 36 | abstract fun bind(itemView: View, item: T, position: Int, viewHolder: BaseViewHolderImp) 37 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 13 | 14 | 19 | 20 | 25 | 26 | 30 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'kotlin-android' 4 | id 'kotlin-kapt' 5 | } 6 | 7 | android { 8 | compileSdkVersion 30 9 | buildToolsVersion "30.0.3" 10 | 11 | defaultConfig { 12 | applicationId "com.example.nestedrecyclersample" 13 | minSdkVersion 21 14 | targetSdkVersion 30 15 | versionCode 1 16 | versionName "1.0" 17 | 18 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 19 | } 20 | 21 | buildTypes { 22 | release { 23 | minifyEnabled false 24 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 25 | } 26 | } 27 | compileOptions { 28 | sourceCompatibility JavaVersion.VERSION_1_8 29 | targetCompatibility JavaVersion.VERSION_1_8 30 | } 31 | kotlinOptions { 32 | jvmTarget = '1.8' 33 | } 34 | 35 | buildFeatures { 36 | dataBinding true 37 | } 38 | } 39 | 40 | dependencies { 41 | 42 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 43 | implementation 'androidx.core:core-ktx:1.3.2' 44 | implementation 'androidx.appcompat:appcompat:1.2.0' 45 | implementation 'com.google.android.material:material:1.3.0' 46 | testImplementation 'junit:junit:4.+' 47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2' 48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' 49 | implementation("io.coil-kt:coil:1.1.1") 50 | implementation 'androidx.recyclerview:recyclerview:1.2.0-beta02' 51 | 52 | api "com.squareup.moshi:moshi-kotlin:1.8.0" 53 | 54 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/adapter/AnimalSectionAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui.adapter 2 | 3 | import android.os.Parcelable 4 | import android.view.View 5 | import android.widget.TextView 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.example.nestedrecyclersample.R 9 | import com.example.nestedrecyclersample.data.domain.AnimalSection 10 | import com.example.nestedrecyclersample.ui.adapter.base.BaseAdapter 11 | import com.example.nestedrecyclersample.ui.adapter.base.BaseViewHolder 12 | 13 | class AnimalSectionAdapter( 14 | items: List = emptyList(), 15 | ) : BaseAdapter( 16 | R.layout.item_animal_section, 17 | items, 18 | ) { 19 | private val scrollStates: MutableMap = mutableMapOf() 20 | 21 | private val viewPool = RecyclerView.RecycledViewPool() 22 | 23 | private fun getSectionID(position: Int): String { 24 | return items[position].id 25 | } 26 | 27 | override fun onViewRecycled(holder: BaseViewHolder) { 28 | super.onViewRecycled(holder) 29 | 30 | //save horizontal scroll state 31 | val key = getSectionID(holder.layoutPosition) 32 | scrollStates[key] = 33 | holder.itemView.findViewById(R.id.titledSectionRecycler).layoutManager?.onSaveInstanceState() 34 | } 35 | 36 | override fun bind( 37 | itemView: View, 38 | item: AnimalSection, 39 | position: Int, 40 | viewHolder: BaseViewHolderImp 41 | ) { 42 | itemView.findViewById(R.id.sectionTitleTextView)?.text = item.title 43 | val layoutManager = 44 | LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false) 45 | 46 | layoutManager.initialPrefetchItemCount = 4 47 | 48 | val titledSectionRecycler = itemView.findViewById(R.id.titledSectionRecycler) 49 | titledSectionRecycler?.run { 50 | this.setRecycledViewPool(viewPool) 51 | this.layoutManager = layoutManager 52 | this.adapter = AnimalAdapter(item.animals) 53 | } 54 | 55 | //restore horizontal scroll state 56 | val key = getSectionID(viewHolder.layoutPosition) 57 | val state = scrollStates[key] 58 | if (state != null) { 59 | titledSectionRecycler.layoutManager?.onRestoreInstanceState(state) 60 | } else { 61 | titledSectionRecycler.layoutManager?.scrollToPosition(0) 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_animal.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 15 | 16 | 23 | 24 | 25 | 29 | 30 | 37 | 38 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/utils/ViewExtensions.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.utils 2 | 3 | import android.view.MotionEvent 4 | import androidx.recyclerview.widget.RecyclerView 5 | 6 | /** 7 | * https://bladecoder.medium.com/fixing-recyclerview-nested-scrolling-in-opposite-direction-f587be5c1a04 8 | */ 9 | fun RecyclerView.enforceSingleScrollDirection() { 10 | val enforcer = SingleScrollDirectionEnforcer() 11 | addOnItemTouchListener(enforcer) 12 | addOnScrollListener(enforcer) 13 | } 14 | 15 | private class SingleScrollDirectionEnforcer : RecyclerView.OnScrollListener(), RecyclerView.OnItemTouchListener { 16 | 17 | private var scrollState = RecyclerView.SCROLL_STATE_IDLE 18 | private var scrollPointerId = -1 19 | private var initialTouchX = 0 20 | private var initialTouchY = 0 21 | private var dx = 0 22 | private var dy = 0 23 | 24 | override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean { 25 | when (e.actionMasked) { 26 | MotionEvent.ACTION_DOWN -> { 27 | scrollPointerId = e.getPointerId(0) 28 | initialTouchX = (e.x + 0.5f).toInt() 29 | initialTouchY = (e.y + 0.5f).toInt() 30 | } 31 | MotionEvent.ACTION_POINTER_DOWN -> { 32 | val actionIndex = e.actionIndex 33 | scrollPointerId = e.getPointerId(actionIndex) 34 | initialTouchX = (e.getX(actionIndex) + 0.5f).toInt() 35 | initialTouchY = (e.getY(actionIndex) + 0.5f).toInt() 36 | } 37 | MotionEvent.ACTION_MOVE -> { 38 | val index = e.findPointerIndex(scrollPointerId) 39 | if (index >= 0 && scrollState != RecyclerView.SCROLL_STATE_DRAGGING) { 40 | val x = (e.getX(index) + 0.5f).toInt() 41 | val y = (e.getY(index) + 0.5f).toInt() 42 | dx = x - initialTouchX 43 | dy = y - initialTouchY 44 | } 45 | } 46 | } 47 | return false 48 | } 49 | 50 | override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {} 51 | 52 | override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} 53 | 54 | override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { 55 | val oldState = scrollState 56 | scrollState = newState 57 | if (oldState == RecyclerView.SCROLL_STATE_IDLE && newState == RecyclerView.SCROLL_STATE_DRAGGING) { 58 | recyclerView.layoutManager?.let { layoutManager -> 59 | val canScrollHorizontally = layoutManager.canScrollHorizontally() 60 | val canScrollVertically = layoutManager.canScrollVertically() 61 | if (canScrollHorizontally != canScrollVertically) { 62 | if ((canScrollHorizontally && Math.abs(dy) > Math.abs(dx)) 63 | || (canScrollVertically && Math.abs(dx) > Math.abs(dy))) { 64 | recyclerView.stopScroll() 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.ui 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.recyclerview.widget.ConcatAdapter 6 | import androidx.recyclerview.widget.LinearLayoutManager 7 | import androidx.recyclerview.widget.RecyclerView 8 | import com.example.nestedrecyclersample.data.DataSource 9 | import com.example.nestedrecyclersample.data.domain.AnimalSection 10 | import com.example.nestedrecyclersample.databinding.ActivityMainBinding 11 | import com.example.nestedrecyclersample.ui.adapter.AnimalSectionAdapter 12 | import com.example.nestedrecyclersample.utils.enforceSingleScrollDirection 13 | import com.squareup.moshi.JsonAdapter 14 | import com.squareup.moshi.Moshi 15 | import com.squareup.moshi.Types 16 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory 17 | 18 | class MainActivity : AppCompatActivity() { 19 | 20 | private lateinit var binding: ActivityMainBinding 21 | private lateinit var sectionAdapter: AnimalSectionAdapter 22 | private var sections: List? = null 23 | 24 | companion object { 25 | const val sectionsKey = "sectionsKey" 26 | } 27 | 28 | private val moshi = 29 | Moshi.Builder() 30 | .add(KotlinJsonAdapterFactory()) 31 | .build() 32 | 33 | private val animalSectionJsonAdapter: JsonAdapter> = 34 | moshi.adapter(Types.newParameterizedType(List::class.java, AnimalSection::class.java)) 35 | 36 | override fun onCreate(savedInstanceState: Bundle?) { 37 | super.onCreate(savedInstanceState) 38 | binding = ActivityMainBinding.inflate(layoutInflater) 39 | val view = binding.root 40 | setContentView(view) 41 | initViews(savedInstanceState) 42 | } 43 | 44 | private fun initViews(savedInstanceState: Bundle?) { 45 | //restore state if possible 46 | //IRL this would most likely be persisted inside a viewModel and you wouldn't need to worry about it 47 | val savedSections = savedInstanceState?.getString(sectionsKey) 48 | if (sections == null && savedSections != null) { 49 | sections = animalSectionJsonAdapter.fromJson(savedSections) 50 | } 51 | 52 | if (sections == null) { 53 | //create a populated list of sections 54 | //IRL you'd most likely be getting the data from a server on a background thread inside a viewModel 55 | sections = DataSource.createSections(numberOfSections = 50, itemsPerSection = 25) 56 | } 57 | //create an instance of ConcatAdapter 58 | val concatAdapter = ConcatAdapter() 59 | 60 | //create AnimalSectionAdapter for the sections and add to ConcatAdapter 61 | sectionAdapter = AnimalSectionAdapter(sections ?: mutableListOf()) 62 | concatAdapter.addAdapter(sectionAdapter) 63 | 64 | //setup the recycler 65 | val linearLayoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) 66 | binding.recyclerView.run { 67 | layoutManager = linearLayoutManager 68 | adapter = concatAdapter 69 | enforceSingleScrollDirection() 70 | } 71 | } 72 | 73 | override fun onSaveInstanceState(outState: Bundle) { 74 | outState.putString(sectionsKey, animalSectionJsonAdapter.toJson(sections)) 75 | super.onSaveInstanceState(outState) 76 | } 77 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/nestedrecyclersample/data/DataSource.kt: -------------------------------------------------------------------------------- 1 | package com.example.nestedrecyclersample.data 2 | 3 | import androidx.recyclerview.widget.ConcatAdapter 4 | import com.example.nestedrecyclersample.data.domain.Animal 5 | import com.example.nestedrecyclersample.data.domain.AnimalSection 6 | 7 | object DataSource { 8 | 9 | private val cats = listOf( 10 | Animal("Cat1", "https://icatcare.org/app/uploads/2018/07/Thinking-of-getting-a-cat.png"), 11 | Animal( 12 | "Cat2", 13 | "https://i.guim.co.uk/img/media/26392d05302e02f7bf4eb143bb84c8097d09144b/446_167_3683_2210/master/3683.jpg?width=445&quality=45&auto=format&fit=max&dpr=2&s=42132184edabf489cb379824f3da6f61" 14 | ), 15 | Animal( 16 | "Cat3", 17 | "https://static.scientificamerican.com/sciam/cache/file/32665E6F-8D90-4567-9769D59E11DB7F26_source.jpg?w=590&h=800&7E4B4CAD-CAE1-4726-93D6A160C2B068B2" 18 | ), 19 | Animal( 20 | "Cat4", 21 | "https://images.theconversation.com/files/350865/original/file-20200803-24-50u91u.jpg?ixlib=rb-1.1.0&rect=37%2C29%2C4955%2C3293&q=45&auto=format&w=926&fit=clip" 22 | ), 23 | ) 24 | 25 | private val capybaras = listOf( 26 | Animal( 27 | "Capybara1", 28 | "https://i.pinimg.com/originals/3e/bf/fe/3ebffe6c1725f083ca6f3d5a1cb17bbb.jpg" 29 | ), 30 | Animal( 31 | "Capybara2", 32 | "https://i1.wp.com/www.thesun.co.uk/wp-content/uploads/2019/08/NINTCHDBPICT000515260574-e1566509448716.jpg?crop=0px%2C142px%2C611px%2C407px&resize=1200%2C800&ssl=1" 33 | ), 34 | Animal( 35 | "Capybara3", 36 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSbxCLHH-Y03iCxX75DrA7mJ2SNd7752PgMGg&usqp=CAU" 37 | ), 38 | Animal( 39 | "Capybara4", 40 | "https://i1.wp.com/blog.workman.com/wp-content/uploads/2015/02/Baby-Capybara-and-mama.jpg" 41 | ), 42 | ) 43 | 44 | private val pandas = listOf( 45 | Animal( 46 | "Panda1", 47 | "https://i.pinimg.com/originals/e0/3d/5b/e03d5b812b2734826f76960eca5b5541.jpg" 48 | ), 49 | Animal( 50 | "Panda2", 51 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcS5eApPfjVWxjb8wFr-18wIay0mzty9njSRqA&usqp=CAU" 52 | ), 53 | Animal( 54 | "Panda3", 55 | "https://static01.nyt.com/images/2020/08/16/reader-center/14-panda-baby/14-panda-baby-facebookJumbo.jpg" 56 | ), 57 | Animal( 58 | "Panda4", 59 | "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSsER4Lqp3KpVkbVBEfXChpOGGcPUY3BHDo3Q&usqp=CAU" 60 | ), 61 | ) 62 | 63 | private val pikas = listOf( 64 | Animal("Pika1", "https://i.ytimg.com/vi/eQ0XSifoNOI/maxresdefault.jpg"), 65 | Animal( 66 | "Pika2", 67 | "https://static.scientificamerican.com/blogs/cache/file/E2B7050D-8D18-43AE-8CC12F1BF07119BD_source.jpg?w=590&h=800&45B1350F-D680-4253-B4FCEC542384D084" 68 | ), 69 | Animal( 70 | "Pika3", 71 | "https://www.treehugger.com/thmb/WGb-3uM3NvLvG3uiAizSa61hAIo=/768x0/filters:no_upscale():max_bytes(150000):strip_icc()/__opt__aboutcom__coeus__resources__content_migration__mnn__images__2017__08__american-pika-grass-2993b02a0caf4249a0a6124929b6b53c.jpg" 72 | ), 73 | Animal( 74 | "Pika4", 75 | "https://s.abcnews.com/images/International/ht_ili_pika_cute_chinese_mammal_jc_150325_4x3t_608.jpg" 76 | ), 77 | ) 78 | 79 | private val titles = listOf("Cats", "Pandas", "Capybaras", "Pikas") 80 | 81 | private val titlesToAnimals: HashMap> = hashMapOf( 82 | titles[0] to cats, 83 | titles[1] to pandas, 84 | titles[2] to capybaras, 85 | titles[3] to pikas, 86 | ) 87 | 88 | 89 | /** 90 | * Creates and populates given number of sections with given number of items. 91 | * 92 | * [numberOfSections] the size of returned list of Sections 93 | * [itemsPerSection] the number of items inside each section 94 | */ 95 | fun createSections(numberOfSections: Int, itemsPerSection: Int): MutableList { 96 | val sections = mutableListOf() 97 | 98 | for (i in 0 until numberOfSections) { 99 | val animals = mutableListOf() 100 | val title = titles.random() 101 | val section = AnimalSection(title = title, animals = animals) 102 | for (j in 0 until itemsPerSection) { 103 | animals.add(titlesToAnimals[title]!!.random()) 104 | } 105 | sections.add(section) 106 | } 107 | 108 | return sections 109 | } 110 | 111 | } -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | --------------------------------------------------------------------------------