├── 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 |
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 |
--------------------------------------------------------------------------------