├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── styles.xml │ │ │ │ └── themes.xml │ │ │ ├── drawable │ │ │ │ ├── ball.jpg │ │ │ │ ├── burger.jpg │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── anim │ │ │ │ ├── layout_animation_fall_down.xml │ │ │ │ ├── layout_animation_from_left.xml │ │ │ │ ├── layout_animation_from_bottom.xml │ │ │ │ ├── layout_animation_from_right.xml │ │ │ │ ├── item_animation_fall_down.xml │ │ │ │ ├── item_animation_from_left.xml │ │ │ │ ├── item_animation_from_right.xml │ │ │ │ └── item_animation_from_bottom.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── layout │ │ │ │ ├── activity_carousel_layout.xml │ │ │ │ ├── type_main_vertical.xml │ │ │ │ ├── single_row_main.xml │ │ │ │ ├── single_row_main_vertical.xml │ │ │ │ ├── activity_main.xml │ │ │ │ └── activity_recyclerview.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ ├── values-night │ │ │ │ └── themes.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── shahin │ │ │ │ └── recyclerviewexample │ │ │ │ ├── views │ │ │ │ ├── CarouselLayoutActivity.java │ │ │ │ └── RecyclerviewActivity.java │ │ │ │ ├── AppConstants.java │ │ │ │ ├── carousel │ │ │ │ ├── ItemTransformationCustom.java │ │ │ │ ├── CenterScrollListenerCustom.java │ │ │ │ ├── DefaultChildSelectionListenerCustom.java │ │ │ │ ├── CarouselChildSelectionListenerCustom.java │ │ │ │ ├── CarouselZoomPostLayoutListenerCustom.java │ │ │ │ └── CarouselLayoutManagerCustom.java │ │ │ │ ├── Utils.java │ │ │ │ ├── adapter │ │ │ │ ├── CarouselAdapter.java │ │ │ │ ├── MainAdapter.java │ │ │ │ ├── CustomAnimatedAdapter.java │ │ │ │ └── AnimatedAdapter.java │ │ │ │ └── MainActivity.java │ │ └── AndroidManifest.xml │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── shahin │ │ │ └── recyclerviewexample │ │ │ └── ExampleUnitTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── shahin │ │ └── recyclerviewexample │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── .idea ├── .name ├── .gitignore ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── compiler.xml ├── vcs.xml ├── misc.xml └── gradle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── settings.gradle ├── README.md ├── gradle.properties ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Recyclerview Example -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Recyclerview Example 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/ball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/drawable/ball.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/burger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/drawable/burger.jpg -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/md-shahin-miah/Horizontal-and-Vertical-Carousel-layout---Recyclerview-/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Dec 28 11:05:04 BDT 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation_fall_down.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | .cxx 15 | local.properties 16 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/anim/layout_animation_from_right.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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | google() 5 | mavenCentral() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | rootProject.name = "Recyclerview Example" 16 | include ':app' 17 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | #FFBB86FC 6 | #FF6200EE 7 | #FF3700B3 8 | #FF03DAC5 9 | #FF018786 10 | #FF000000 11 | #FFFFFFFF 12 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_carousel_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/test/java/com/shahin/recyclerviewexample/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | @Test 14 | public void addition_isCorrect() { 15 | assertEquals(4, 2 + 2); 16 | } 17 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RecyclerviewExample 2 | 3 | #### Animated recyclerview 4 | ![animated](https://user-images.githubusercontent.com/68494371/209796283-46d0b571-a64e-4129-9883-00d01be0803f.PNG) 5 | #### Customisable Carousel recyclerview 6 | ![carousel](https://user-images.githubusercontent.com/68494371/209796352-6f794c43-0478-4955-991f-ceae24c0d31c.PNG) 7 | #### Custom animated recyclerview 8 | 9 | ![Customanimated](https://user-images.githubusercontent.com/68494371/209796650-9c683d12-3796-491d-83aa-9b5c942ffd2c.PNG) 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/anim/item_animation_fall_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/item_animation_from_left.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/anim/item_animation_from_right.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/views/CarouselLayoutActivity.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.views; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | 5 | import android.os.Bundle; 6 | 7 | import com.shahin.recyclerviewexample.R; 8 | 9 | public class CarouselLayoutActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | setContentView(R.layout.activity_carousel_layout); 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/AppConstants.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample; 2 | 3 | public class AppConstants { 4 | 5 | 6 | 7 | public static final String CAROUSEL_RECYCLERVIEW = "Carousel RecyclerView"; 8 | public static final String CUSTOM_ANIMATED_RECYCLERVIEW = "Custom Animated RecyclerView"; 9 | public static final String ANIMATED_ON_ADAPTER_RECYCLERVIEW = "Animated on adapter RecyclerView"; 10 | 11 | 12 | 13 | 14 | public static final String FROM = "FROM"; 15 | 16 | 17 | 18 | 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/anim/item_animation_from_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/ItemTransformationCustom.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.carousel; 2 | /* 3 | *created at 25/09/22 4 | *Created by @shahin 5 | */ 6 | public class ItemTransformationCustom { 7 | public final float mScaleX; 8 | public final float mScaleY; 9 | public final float mTranslationX; 10 | public final float mTranslationY; 11 | 12 | public ItemTransformationCustom(final float scaleX, final float scaleY, final float translationX, final float translationY) { 13 | mScaleX = scaleX; 14 | mScaleY = scaleY; 15 | mTranslationX = translationX; 16 | mTranslationY = translationY; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/Utils.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class Utils { 7 | 8 | public static List getList() { 9 | List list = new ArrayList<>(); 10 | 11 | for (int i = 0; i < 20; i++) { 12 | list.add("Item " + i); 13 | } 14 | 15 | return list; 16 | } 17 | public static List getListOfRecyclerviewType() { 18 | List list = new ArrayList<>(); 19 | 20 | list.add(AppConstants.CAROUSEL_RECYCLERVIEW); 21 | list.add(AppConstants.ANIMATED_ON_ADAPTER_RECYCLERVIEW); 22 | list.add(AppConstants.CUSTOM_ANIMATED_RECYCLERVIEW); 23 | 24 | 25 | return list; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /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/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/values-night/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/shahin/recyclerviewexample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.test.platform.app.InstrumentationRegistry; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | import static org.junit.Assert.*; 12 | 13 | /** 14 | * Instrumented test, which will execute on an Android device. 15 | * 16 | * @see Testing documentation 17 | */ 18 | @RunWith(AndroidJUnit4.class) 19 | public class ExampleInstrumentedTest { 20 | @Test 21 | public void useAppContext() { 22 | // Context of the app under test. 23 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 24 | assertEquals("com.yotech.recyclerviewexample", appContext.getPackageName()); 25 | } 26 | } -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | } 4 | 5 | android { 6 | namespace 'com.yotech.recyclerviewexample' 7 | compileSdk 32 8 | 9 | defaultConfig { 10 | applicationId "com.yotech.recyclerviewexample" 11 | minSdk 21 12 | targetSdk 32 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | 33 | implementation 'androidx.appcompat:appcompat:1.5.1' 34 | implementation 'com.google.android.material:material:1.7.0' 35 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 36 | testImplementation 'junit:junit:4.13.2' 37 | androidTestImplementation 'androidx.test.ext:junit:1.1.4' 38 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0' 39 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/type_main_vertical.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /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 | # Enables namespacing of each library's R class so that its R class includes only the 19 | # resources declared in the library itself and none from the library's dependencies, 20 | # thereby reducing the size of the R class for that library 21 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /app/src/main/res/layout/single_row_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 24 | 25 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/res/layout/single_row_main_vertical.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 23 | 24 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 15 | 18 | 21 | 22 | 25 | 28 | 29 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/CenterScrollListenerCustom.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.carousel; 2 | 3 | 4 | import android.view.animation.DecelerateInterpolator; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.recyclerview.widget.RecyclerView; 8 | 9 | /* 10 | *created at 25/09/22 11 | *Created by @shahin 12 | */ 13 | 14 | /** 15 | * Class for centering items after scroll event.
16 | * This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center. 17 | */ 18 | public class CenterScrollListenerCustom extends RecyclerView.OnScrollListener { 19 | 20 | private boolean mAutoSet = true; 21 | 22 | @Override 23 | public void onScrollStateChanged(@NonNull final RecyclerView recyclerView, final int newState) { 24 | super.onScrollStateChanged(recyclerView, newState); 25 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager(); 26 | if (!(layoutManager instanceof CarouselLayoutManagerCustom)) { 27 | mAutoSet = true; 28 | return; 29 | } 30 | 31 | final CarouselLayoutManagerCustom lm = (CarouselLayoutManagerCustom) layoutManager; 32 | if (!mAutoSet) { 33 | if (RecyclerView.SCROLL_STATE_IDLE == newState) { 34 | final int scrollNeeded = lm.getOffsetCenterView(); 35 | if (CarouselLayoutManagerCustom.HORIZONTAL == lm.getOrientation()) { 36 | recyclerView.smoothScrollBy(scrollNeeded, 0); 37 | } else { 38 | recyclerView.smoothScrollBy(0, scrollNeeded,new DecelerateInterpolator()); 39 | } 40 | mAutoSet = true; 41 | } 42 | } 43 | if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) { 44 | mAutoSet = false; 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/DefaultChildSelectionListenerCustom.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.carousel; 2 | /* 3 | *created at 25/09/22 4 | *Created by @shahin 5 | */ 6 | 7 | import android.view.View; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | 13 | public class DefaultChildSelectionListenerCustom extends CarouselChildSelectionListenerCustom { 14 | 15 | @NonNull 16 | private final OnCenterItemClickListener mOnCenterItemClickListener; 17 | 18 | protected DefaultChildSelectionListenerCustom(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager) { 19 | super(recyclerView, carouselLayoutManager); 20 | 21 | mOnCenterItemClickListener = onCenterItemClickListener; 22 | } 23 | 24 | @Override 25 | protected void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager, @NonNull final View v) { 26 | mOnCenterItemClickListener.onCenterItemClicked(recyclerView, carouselLayoutManager, v); 27 | } 28 | 29 | @Override 30 | protected void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager, @NonNull final View v) { 31 | recyclerView.smoothScrollToPosition(carouselLayoutManager.getPosition(v)); 32 | } 33 | 34 | public static DefaultChildSelectionListenerCustom initCenterItemListener(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager) { 35 | return new DefaultChildSelectionListenerCustom(onCenterItemClickListener, recyclerView, carouselLayoutManager); 36 | } 37 | 38 | public interface OnCenterItemClickListener { 39 | 40 | void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager, @NonNull final View v); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/adapter/CarouselAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.adapter; 2 | 3 | import static android.content.ContentValues.TAG; 4 | 5 | import android.content.Context; 6 | import android.util.Log; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ImageView; 11 | import android.widget.TextView; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.recyclerview.widget.RecyclerView; 15 | 16 | import com.shahin.recyclerviewexample.R; 17 | 18 | import java.util.List; 19 | 20 | public class CarouselAdapter extends RecyclerView.Adapter { 21 | private Context context; 22 | 23 | List listOfItem; 24 | 25 | public CarouselAdapter(Context context,List list) { 26 | this.context = context; 27 | this.listOfItem=list; 28 | } 29 | 30 | @NonNull 31 | @Override 32 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 33 | 34 | 35 | int resourceId = R.layout.single_row_main; 36 | final LayoutInflater mInflater; 37 | mInflater = LayoutInflater.from(context); 38 | View view = mInflater.inflate(resourceId, parent, false); 39 | 40 | return new ViewHolder(view); 41 | 42 | } 43 | 44 | @Override 45 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 46 | 47 | 48 | Log.d(TAG, "onBindViewHolder: position "+listOfItem.get(position)); 49 | 50 | holder.tvHeader.setText(listOfItem.get(position)); 51 | 52 | } 53 | 54 | @Override 55 | public int getItemCount() { 56 | return listOfItem.size(); 57 | } 58 | 59 | 60 | public static class ViewHolder extends RecyclerView.ViewHolder { 61 | 62 | 63 | TextView tvHeader; 64 | ImageView imageView; 65 | 66 | public ViewHolder(@NonNull View itemView) { 67 | super(itemView); 68 | 69 | imageView = itemView.findViewById(R.id.imageView); 70 | tvHeader = itemView.findViewById(R.id.header); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.shahin.recyclerviewexample.adapter.MainAdapter; 11 | import com.shahin.recyclerviewexample.views.RecyclerviewActivity; 12 | import com.yotech.recyclerviewexample.R; 13 | 14 | public class MainActivity extends AppCompatActivity implements MainAdapter.MainItemClickListener { 15 | 16 | private RecyclerView vertical_recyclerview; 17 | 18 | @Override 19 | protected void onCreate(Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | setContentView(R.layout.activity_main); 22 | vertical_recyclerview = findViewById(R.id.vertical_recyclerview); 23 | 24 | 25 | vertical_recyclerview.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 26 | MainAdapter adapter = new MainAdapter(this, Utils.getListOfRecyclerviewType(), this); 27 | vertical_recyclerview.setAdapter(adapter); 28 | } 29 | 30 | @Override 31 | public void onItemClick(String typeOfRecyclerview) { 32 | 33 | if (typeOfRecyclerview.equalsIgnoreCase(AppConstants.CAROUSEL_RECYCLERVIEW)) { 34 | 35 | intentForCarousel(AppConstants.CAROUSEL_RECYCLERVIEW); 36 | 37 | } else if (typeOfRecyclerview.equalsIgnoreCase(AppConstants.CUSTOM_ANIMATED_RECYCLERVIEW)) { 38 | intentForCarousel(AppConstants.CUSTOM_ANIMATED_RECYCLERVIEW); 39 | 40 | } else if (typeOfRecyclerview.equalsIgnoreCase(AppConstants.ANIMATED_ON_ADAPTER_RECYCLERVIEW)) { 41 | intentForCarousel(AppConstants.ANIMATED_ON_ADAPTER_RECYCLERVIEW); 42 | 43 | } 44 | 45 | //shahin 46 | //added new for pull req 47 | //hello bangladesh 48 | } 49 | 50 | private void intentForCarousel(String value) { 51 | Intent intent = new Intent(this, RecyclerviewActivity.class); 52 | intent.putExtra(AppConstants.FROM, value); 53 | startActivity(intent); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/CarouselChildSelectionListenerCustom.java: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | *created at 25/09/22 4 | *Created by @shahin 5 | */ 6 | 7 | package com.shahin.recyclerviewexample.carousel; 8 | 9 | 10 | import android.view.View; 11 | 12 | import androidx.annotation.NonNull; 13 | import androidx.recyclerview.widget.RecyclerView; 14 | 15 | 16 | public abstract class CarouselChildSelectionListenerCustom { 17 | 18 | @NonNull 19 | private final RecyclerView mRecyclerView; 20 | @NonNull 21 | private final CarouselLayoutManagerCustom mCarouselLayoutManager; 22 | 23 | private final View.OnClickListener mOnClickListener = new View.OnClickListener() { 24 | @Override 25 | public void onClick(final View v) { 26 | final RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v); 27 | final int position = holder.getAdapterPosition(); 28 | 29 | if (position == mCarouselLayoutManager.getCenterItemPosition()) { 30 | onCenterItemClicked(mRecyclerView, mCarouselLayoutManager, v); 31 | } else { 32 | onBackItemClicked(mRecyclerView, mCarouselLayoutManager, v); 33 | } 34 | } 35 | }; 36 | 37 | protected CarouselChildSelectionListenerCustom(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager) { 38 | mRecyclerView = recyclerView; 39 | mCarouselLayoutManager = carouselLayoutManager; 40 | 41 | mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() { 42 | @Override 43 | public void onChildViewAttachedToWindow(@NonNull final View view) { 44 | view.setOnClickListener(mOnClickListener); 45 | } 46 | 47 | @Override 48 | public void onChildViewDetachedFromWindow(@NonNull final View view) { 49 | view.setOnClickListener(null); 50 | } 51 | }); 52 | } 53 | 54 | protected abstract void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager, @NonNull final View v); 55 | 56 | protected abstract void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final CarouselLayoutManagerCustom carouselLayoutManager, @NonNull final View v); 57 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/adapter/MainAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.adapter; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.TextView; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.shahin.recyclerviewexample.R; 14 | 15 | import java.util.List; 16 | 17 | public class MainAdapter extends RecyclerView.Adapter { 18 | private Context context; 19 | private Boolean aBoolean; 20 | List listOfItem; 21 | MainItemClickListener mainItemClickListener; 22 | 23 | public MainAdapter(Context context, List list,MainItemClickListener mainItemClickListener) { 24 | this.context = context; 25 | this.listOfItem = list; 26 | this.mainItemClickListener=mainItemClickListener; 27 | } 28 | 29 | 30 | 31 | @NonNull 32 | @Override 33 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 34 | 35 | 36 | View view = LayoutInflater.from(context).inflate(R.layout.type_main_vertical, parent, false); 37 | return new ViewHolder(view); 38 | 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(@NonNull ViewHolder holder, @SuppressLint("RecyclerView") int position) { 43 | 44 | holder.typeOfRecyclerview.setText(listOfItem.get(position)); 45 | 46 | holder.itemView.setOnClickListener(new View.OnClickListener() { 47 | @Override 48 | public void onClick(View view) { 49 | 50 | if (mainItemClickListener!=null){ 51 | mainItemClickListener.onItemClick(listOfItem.get(position)); 52 | } 53 | 54 | } 55 | }); 56 | 57 | 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | return listOfItem.size(); 63 | } 64 | 65 | public class ViewHolder extends RecyclerView.ViewHolder { 66 | TextView typeOfRecyclerview; 67 | 68 | public ViewHolder(@NonNull View itemView) { 69 | super(itemView); 70 | typeOfRecyclerview = itemView.findViewById(R.id.tv_typeOfRv); 71 | } 72 | } 73 | 74 | 75 | 76 | 77 | public interface MainItemClickListener{ 78 | void onItemClick(String s); 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/adapter/CustomAnimatedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.shahin.recyclerviewexample.R; 13 | 14 | import java.util.List; 15 | 16 | public class CustomAnimatedAdapter extends RecyclerView.Adapter { 17 | private Context context; 18 | private Boolean aBoolean; 19 | private List listOfItem; 20 | 21 | public CustomAnimatedAdapter(Context context, Boolean aBoolean, List list) { 22 | this.context = context; 23 | this.aBoolean = aBoolean; 24 | this.listOfItem = list; 25 | } 26 | // private void setAnimation(View viewToAnimate) { 27 | // ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.1f, 1.0f 28 | // , Animation.RELATIVE_TO_PARENT, 0.5f, 29 | // Animation.RELATIVE_TO_PARENT, 0.5f 30 | // ); 31 | // anim.setDuration(1000); 32 | // viewToAnimate.startAnimation(anim); 33 | // 34 | // } 35 | @NonNull 36 | @Override 37 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 38 | 39 | 40 | // if (aBoolean == true) { 41 | // View view = LayoutInflater.from(context).inflate(R.layout.single_row_main, parent, false); 42 | // return new ViewHolder(view); 43 | // } else { 44 | View view = LayoutInflater.from(context).inflate(R.layout.single_row_main_vertical, parent, false); 45 | return new ViewHolder(view); 46 | // } 47 | } 48 | 49 | @Override 50 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 51 | // setAnimation(holder.itemView); 52 | 53 | holder.headerText.setText(listOfItem.get(position)); 54 | 55 | 56 | } 57 | 58 | @Override 59 | public int getItemCount() { 60 | return listOfItem.size(); 61 | } 62 | 63 | public class ViewHolder extends RecyclerView.ViewHolder { 64 | 65 | TextView headerText; 66 | 67 | public ViewHolder(@NonNull View itemView) { 68 | super(itemView); 69 | headerText=itemView.findViewById(R.id.header); 70 | 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/adapter/AnimatedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.adapter; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.view.animation.Animation; 8 | import android.view.animation.ScaleAnimation; 9 | import android.widget.TextView; 10 | 11 | import androidx.annotation.NonNull; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | 14 | import com.shahin.recyclerviewexample.R; 15 | 16 | import java.util.List; 17 | 18 | public class AnimatedAdapter extends RecyclerView.Adapter { 19 | private Context context; 20 | private Boolean aBoolean; 21 | private List listOfItem; 22 | 23 | public AnimatedAdapter(Context context, Boolean aBoolean,List list) { 24 | this.context = context; 25 | this.aBoolean = aBoolean; 26 | this.listOfItem = list; 27 | } 28 | private void setAnimation(View viewToAnimate) { 29 | ScaleAnimation anim = new ScaleAnimation(0.0f, 1.0f, 0.1f, 1.0f 30 | , Animation.RELATIVE_TO_PARENT, 0.5f, 31 | Animation.RELATIVE_TO_PARENT, 0.5f 32 | ); 33 | anim.setDuration(1000); 34 | viewToAnimate.startAnimation(anim); 35 | 36 | } 37 | @NonNull 38 | @Override 39 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 40 | 41 | 42 | if (aBoolean == true) { 43 | View view = LayoutInflater.from(context).inflate(R.layout.single_row_main, parent, false); 44 | return new ViewHolder(view); 45 | } else { 46 | View view = LayoutInflater.from(context).inflate(R.layout.single_row_main_vertical, parent, false); 47 | return new ViewHolder(view); 48 | } 49 | } 50 | 51 | @Override 52 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 53 | setAnimation(holder.itemView); 54 | 55 | holder.headerText.setText(listOfItem.get(position)); 56 | 57 | 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | return listOfItem.size(); 63 | } 64 | 65 | public class ViewHolder extends RecyclerView.ViewHolder { 66 | 67 | TextView headerText; 68 | 69 | public ViewHolder(@NonNull View itemView) { 70 | super(itemView); 71 | headerText=itemView.findViewById(R.id.header); 72 | 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 71 | 72 | 73 | 74 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 18 | 19 | 30 | 31 | 41 | 42 | 43 | 44 | 57 | 58 | 64 | 65 | 74 | 75 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/CarouselZoomPostLayoutListenerCustom.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.carousel; 2 | 3 | import android.util.Log; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | 9 | 10 | /* 11 | *created at 25/09/22 12 | *Created by @shahin 13 | */ 14 | public class CarouselZoomPostLayoutListenerCustom extends CarouselLayoutManagerCustom.PostLayoutListener { 15 | 16 | private static final String TAG = "CarouselZoomPostLayoutL"; 17 | private final float mScaleMultiplier; 18 | 19 | public CarouselZoomPostLayoutListenerCustom() { 20 | this(0.10f); 21 | } 22 | 23 | public CarouselZoomPostLayoutListenerCustom(final float scaleMultiplier) { 24 | mScaleMultiplier = scaleMultiplier; 25 | } 26 | 27 | @Override 28 | public ItemTransformationCustom transformChild(@NonNull final View child, final float itemPositionToCenterDiff, final int orientation) { 29 | // final float scale = 1.0f - mScaleMultiplier * Math.abs(itemPositionToCenterDiff); 30 | // 31 | // // because scaling will make view smaller in its center, then we should move this item to the top or bottom to make it visible 32 | // final float translateY; 33 | // final float translateX; 34 | // if (CarouselLayoutManagerCustom.VERTICAL == orientation) { 35 | //// final float translateYGeneral = child.getMeasuredHeight() * (1 - scale) / 2f; 36 | // final float translateYGeneral = child.getMeasuredWidth() * (1 - scale) / 0.8f; 37 | // 38 | // 39 | // translateY = Math.signum(itemPositionToCenterDiff) * translateYGeneral; 40 | // translateX = 0; 41 | // } else { 42 | // final float translateXGeneral = child.getMeasuredWidth() * (1 - scale) / 2f; 43 | // translateX = Math.signum(itemPositionToCenterDiff) * translateXGeneral; 44 | // translateY = 0; 45 | // } 46 | // 47 | // return new ItemTransformationCustom(scale, scale, translateX, translateY); 48 | 49 | final float scale = 1.0f - 0.09f * Math.abs(itemPositionToCenterDiff); 50 | Log.d(TAG, "transformChild: scale "+scale); 51 | Log.d(TAG, "transformChild: center diff "+itemPositionToCenterDiff); 52 | 53 | // because scaling will make view smaller in its center, then we should move this item to the top or bottom to make it visible 54 | final float translateY; 55 | final float translateX; 56 | if (CarouselLayoutManagerCustom.VERTICAL == orientation) { 57 | Log.d(TAG, "transformChild: one "); 58 | // final float translateYGeneral = child.getMeasuredHeight() * (1 - scale) / 3f; 59 | final float translateYGeneral = child.getMeasuredWidth() * (1 - scale) / 3f; 60 | translateY = Math.signum(itemPositionToCenterDiff) * translateYGeneral; 61 | translateX = 0; 62 | } else { 63 | Log.d(TAG, "transformChild: two "); 64 | 65 | final float translateXGeneral = child.getMeasuredWidth() * (1 - scale) / 2f; 66 | translateX = Math.signum(itemPositionToCenterDiff-1) * translateXGeneral; 67 | translateY = 0; 68 | } 69 | 70 | Log.d(TAG, "transformChild: translateY "+translateY); 71 | 72 | return new ItemTransformationCustom(scale, scale, translateX, translateY); 73 | 74 | } 75 | } -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 127 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/views/RecyclerviewActivity.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.views; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.util.Log; 6 | import android.view.View; 7 | import android.view.animation.AnimationUtils; 8 | import android.view.animation.LayoutAnimationController; 9 | import android.widget.AdapterView; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.RelativeLayout; 12 | import android.widget.Spinner; 13 | import android.widget.TextView; 14 | 15 | import androidx.appcompat.app.AppCompatActivity; 16 | import androidx.recyclerview.widget.LinearLayoutManager; 17 | import androidx.recyclerview.widget.RecyclerView; 18 | 19 | import com.shahin.recyclerviewexample.AppConstants; 20 | import com.shahin.recyclerviewexample.R; 21 | import com.shahin.recyclerviewexample.Utils; 22 | import com.shahin.recyclerviewexample.adapter.AnimatedAdapter; 23 | import com.shahin.recyclerviewexample.adapter.CarouselAdapter; 24 | import com.shahin.recyclerviewexample.adapter.CustomAnimatedAdapter; 25 | import com.shahin.recyclerviewexample.carousel.CarouselLayoutManagerCustom; 26 | import com.shahin.recyclerviewexample.carousel.CarouselZoomPostLayoutListenerCustom; 27 | import com.shahin.recyclerviewexample.carousel.CenterScrollListenerCustom; 28 | import com.shahin.recyclerviewexample.carousel.DefaultChildSelectionListenerCustom; 29 | 30 | import java.util.Locale; 31 | 32 | public class RecyclerviewActivity extends AppCompatActivity implements AdapterView.OnItemSelectedListener { 33 | private static final String TAG = "RecyclerviewActivity"; 34 | 35 | RecyclerView recyclerViewHorizontal, recyclerViewVertical; 36 | 37 | TextView textViewHorizontal; 38 | 39 | Spinner spinner; 40 | 41 | RelativeLayout relativeLayoutVertical,relativeLayoutHorizontal; 42 | Intent intent; 43 | String from; 44 | 45 | String[] animationType = {"layout_animation_fall_down", "layout_animation_from_bottom", "layout_animation_from_left", "layout_animation_from_right"}; 46 | // "item_animation_fall_down", "item_animation_from_bottom", "item_animation_from_left", "item_animation_from_right" 47 | 48 | @Override 49 | protected void onCreate(Bundle savedInstanceState) { 50 | super.onCreate(savedInstanceState); 51 | setContentView(R.layout.activity_recyclerview); 52 | recyclerViewHorizontal = findViewById(R.id.recyclerview_horizontal); 53 | recyclerViewVertical = findViewById(R.id.recyclerview_vertical); 54 | textViewHorizontal = findViewById(R.id.horizontal_text); 55 | relativeLayoutVertical = findViewById(R.id.vertical_RelativeLayout); 56 | relativeLayoutHorizontal = findViewById(R.id.horizontal_RelativeLayout); 57 | spinner = findViewById(R.id.spinner); 58 | 59 | 60 | 61 | 62 | 63 | try { 64 | intent = getIntent(); 65 | 66 | from = intent.getStringExtra(AppConstants.FROM); 67 | 68 | 69 | } catch (Exception e) { 70 | e.printStackTrace(); 71 | Log.e("getStringExtra_EX", e + ""); 72 | } 73 | 74 | 75 | Log.d(TAG, "onCreate: from " + from); 76 | 77 | if (from.equalsIgnoreCase(AppConstants.CAROUSEL_RECYCLERVIEW)) { 78 | carouselVertical(); 79 | carouselHorizontal(); 80 | } else if (from.equalsIgnoreCase(AppConstants.ANIMATED_ON_ADAPTER_RECYCLERVIEW)) { 81 | 82 | animatedOnAdapterSetup(); 83 | 84 | } else if (from.equalsIgnoreCase(AppConstants.CUSTOM_ANIMATED_RECYCLERVIEW)) { 85 | relativeLayoutHorizontal.setVisibility(View.GONE); 86 | spinner.setVisibility(View.VISIBLE); 87 | spinner.setOnItemSelectedListener(this); 88 | 89 | ArrayAdapter aa = new ArrayAdapter(this, android.R.layout.simple_spinner_item, animationType); 90 | aa.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 91 | spinner.setAdapter(aa); 92 | 93 | 94 | 95 | } 96 | 97 | 98 | } 99 | 100 | private void customAnimatedRecyclerview(String type) { 101 | 102 | 103 | 104 | 105 | recyclerViewVertical.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 106 | CustomAnimatedAdapter animatedAdapter = new CustomAnimatedAdapter(this, true, Utils.getList()); 107 | recyclerViewVertical.setAdapter(animatedAdapter); 108 | 109 | LayoutAnimationController animation = AnimationUtils.loadLayoutAnimation(this, getResources().getIdentifier(type, "anim", getPackageName())); 110 | recyclerViewVertical.setLayoutAnimation(animation); 111 | animatedAdapter.notifyDataSetChanged(); 112 | recyclerViewVertical.scheduleLayoutAnimation(); 113 | 114 | 115 | } 116 | 117 | private void animatedOnAdapterSetup() { 118 | // -------------------------VERTICAL------------------------------------- 119 | recyclerViewVertical.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)); 120 | AnimatedAdapter animatedAdapterVertical = new AnimatedAdapter(this, false, Utils.getList()); 121 | recyclerViewVertical.setAdapter(animatedAdapterVertical); 122 | 123 | // -------------------------HORIZONTAL------------------------------------- 124 | 125 | recyclerViewHorizontal.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false)); 126 | AnimatedAdapter animatedAdapterHorizontal = new AnimatedAdapter(this, true, Utils.getList()); 127 | recyclerViewHorizontal.setAdapter(animatedAdapterHorizontal); 128 | 129 | 130 | } 131 | 132 | private void carouselVertical() { 133 | recyclerViewVertical.setVisibility(View.VISIBLE); 134 | final CarouselLayoutManagerCustom layoutManager = new CarouselLayoutManagerCustom(CarouselLayoutManagerCustom.VERTICAL, true); 135 | layoutManager.setPostLayoutListener(new CarouselZoomPostLayoutListenerCustom()); 136 | layoutManager.setMaxVisibleItems(6); 137 | Log.d(TAG, "onCreate: getList " + Utils.getList()); 138 | CarouselAdapter carouselAdapter = new CarouselAdapter(this, Utils.getList()); 139 | recyclerViewVertical.setLayoutManager(layoutManager); 140 | recyclerViewVertical.setHasFixedSize(true); 141 | recyclerViewVertical.setAdapter(carouselAdapter); 142 | recyclerViewVertical.addOnScrollListener(new CenterScrollListenerCustom()); 143 | 144 | 145 | DefaultChildSelectionListenerCustom.initCenterItemListener((recyclerView, carouselLayoutManager, v) -> { 146 | final int position = recyclerView.getChildLayoutPosition(v); 147 | final String msg = String.format(Locale.US, "Item %1$d was clicked", position); 148 | 149 | Log.d(TAG, "rootMenuRecyclerSetUpLeft: position " + position); 150 | }, recyclerViewVertical, layoutManager); 151 | 152 | 153 | layoutManager.addOnItemSelectionListener(adapterPosition -> { 154 | Log.d(TAG, "addOnItemSelectionListener: adapter pos " + adapterPosition + " visibleMainMenu " + adapterPosition); 155 | if (CarouselLayoutManagerCustom.INVALID_POSITION != adapterPosition) { 156 | 157 | Log.d(TAG, "onCenterItemChanged: adapterPosition " + adapterPosition); 158 | 159 | } 160 | }); 161 | 162 | } 163 | 164 | private void carouselHorizontal() { 165 | recyclerViewHorizontal.setVisibility(View.VISIBLE); 166 | 167 | final CarouselLayoutManagerCustom layoutManager = new CarouselLayoutManagerCustom(CarouselLayoutManagerCustom.HORIZONTAL, true); 168 | layoutManager.setPostLayoutListener(new CarouselZoomPostLayoutListenerCustom()); 169 | layoutManager.setMaxVisibleItems(6); 170 | Log.d(TAG, "onCreate: getList " + Utils.getList()); 171 | CarouselAdapter carouselAdapter = new CarouselAdapter(this, Utils.getList()); 172 | recyclerViewHorizontal.setLayoutManager(layoutManager); 173 | recyclerViewHorizontal.setHasFixedSize(true); 174 | recyclerViewHorizontal.setAdapter(carouselAdapter); 175 | recyclerViewHorizontal.addOnScrollListener(new CenterScrollListenerCustom()); 176 | 177 | 178 | DefaultChildSelectionListenerCustom.initCenterItemListener((recyclerView, carouselLayoutManager, v) -> { 179 | final int position = recyclerView.getChildLayoutPosition(v); 180 | final String msg = String.format(Locale.US, "Item %1$d was clicked", position); 181 | 182 | Log.d(TAG, "rootMenuRecyclerSetUpLeft: position " + position); 183 | }, recyclerViewHorizontal, layoutManager); 184 | 185 | 186 | layoutManager.addOnItemSelectionListener(adapterPosition -> { 187 | Log.d(TAG, "addOnItemSelectionListener: adapter pos " + adapterPosition + " visibleMainMenu " + adapterPosition); 188 | if (CarouselLayoutManagerCustom.INVALID_POSITION != adapterPosition) { 189 | 190 | Log.d(TAG, "onCenterItemChanged: adapterPosition " + adapterPosition); 191 | 192 | } 193 | }); 194 | 195 | } 196 | 197 | @Override 198 | public void onItemSelected(AdapterView adapterView, View view, int i, long l) { 199 | 200 | 201 | Log.d(TAG, "onItemSelected: " + animationType[i]); 202 | customAnimatedRecyclerview(animationType[i]); 203 | 204 | } 205 | 206 | @Override 207 | public void onNothingSelected(AdapterView adapterView) { 208 | 209 | } 210 | } -------------------------------------------------------------------------------- /app/src/main/java/com/shahin/recyclerviewexample/carousel/CarouselLayoutManagerCustom.java: -------------------------------------------------------------------------------- 1 | package com.shahin.recyclerviewexample.carousel; 2 | 3 | import android.graphics.PointF; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.util.Log; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | 12 | import androidx.annotation.CallSuper; 13 | import androidx.annotation.NonNull; 14 | import androidx.annotation.Nullable; 15 | import androidx.core.view.ViewCompat; 16 | import androidx.recyclerview.widget.LinearSmoothScroller; 17 | import androidx.recyclerview.widget.OrientationHelper; 18 | import androidx.recyclerview.widget.RecyclerView; 19 | 20 | import java.lang.ref.WeakReference; 21 | import java.util.ArrayList; 22 | import java.util.Iterator; 23 | import java.util.List; 24 | 25 | /* 26 | *created at 25/09/22 27 | *Created by @shahin 28 | */ 29 | public class CarouselLayoutManagerCustom extends RecyclerView.LayoutManager implements RecyclerView.SmoothScroller.ScrollVectorProvider { 30 | 31 | private static final String TAG = "CarouselLayoutManagerCu"; 32 | public static final int HORIZONTAL = OrientationHelper.HORIZONTAL; 33 | public static final int VERTICAL = OrientationHelper.VERTICAL; 34 | 35 | public static final int INVALID_POSITION = -1; 36 | public static final int MAX_VISIBLE_ITEMS = 3; 37 | 38 | private static final boolean CIRCLE_LAYOUT = false; 39 | 40 | private boolean mDecoratedChildSizeInvalid; 41 | private Integer mDecoratedChildWidth; 42 | private Integer mDecoratedChildHeight; 43 | 44 | private final int mOrientation; 45 | private boolean mCircleLayout; 46 | 47 | private int mPendingScrollPosition; 48 | 49 | private final LayoutHelper mLayoutHelper = new LayoutHelper(MAX_VISIBLE_ITEMS); 50 | 51 | private PostLayoutListener mViewPostLayout; 52 | 53 | private final List mOnCenterItemSelectionListeners = new ArrayList<>(); 54 | private int mCenterItemPosition = INVALID_POSITION; 55 | private int mItemsCount; 56 | 57 | @Nullable 58 | private CarouselSavedState mPendingCarouselSavedState; 59 | 60 | /** 61 | * @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL} 62 | */ 63 | @SuppressWarnings("unused") 64 | public CarouselLayoutManagerCustom(final int orientation) { 65 | this(orientation, CIRCLE_LAYOUT); 66 | } 67 | 68 | /** 69 | * If circleLayout is true then all items will be in cycle. Scroll will be infinite on both sides. 70 | * 71 | * @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL} 72 | * @param circleLayout true for enabling circleLayout 73 | */ 74 | @SuppressWarnings("unused") 75 | public CarouselLayoutManagerCustom(final int orientation, final boolean circleLayout) { 76 | if (HORIZONTAL != orientation && VERTICAL != orientation) { 77 | throw new IllegalArgumentException("orientation should be HORIZONTAL or VERTICAL"); 78 | } 79 | mOrientation = orientation; 80 | mCircleLayout = circleLayout; 81 | mPendingScrollPosition = INVALID_POSITION; 82 | } 83 | 84 | /** 85 | * Change circle layout type 86 | */ 87 | @SuppressWarnings("unused") 88 | public void setCircleLayout(final boolean circleLayout) { 89 | if (mCircleLayout != circleLayout) { 90 | mCircleLayout = circleLayout; 91 | requestLayout(); 92 | } 93 | } 94 | 95 | /** 96 | * Setup {@link PostLayoutListener} for this LayoutManager. 97 | * Its methods will be called for each visible view item after general LayoutManager layout finishes.
98 | *
99 | * Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting. 100 | * 101 | * @param postLayoutListener listener for item layout changes. Can be null. 102 | */ 103 | @SuppressWarnings("unused") 104 | public void setPostLayoutListener(@Nullable final PostLayoutListener postLayoutListener) { 105 | mViewPostLayout = postLayoutListener; 106 | requestLayout(); 107 | } 108 | 109 | /** 110 | * Setup maximum visible (layout) items on each side of the center item. 111 | * Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum. 112 | * 113 | * @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown 114 | */ 115 | 116 | @CallSuper 117 | @SuppressWarnings("unused") 118 | public void setMaxVisibleItems(final int maxVisibleItems) { 119 | if (0 > maxVisibleItems) { 120 | throw new IllegalArgumentException("maxVisibleItems can't be less then 0"); 121 | } 122 | mLayoutHelper.mMaxVisibleItems = maxVisibleItems; 123 | requestLayout(); 124 | } 125 | 126 | /** 127 | * @return current setup for maximum visible items. 128 | * @see #setMaxVisibleItems(int) 129 | */ 130 | @SuppressWarnings("unused") 131 | public int getMaxVisibleItems() { 132 | return mLayoutHelper.mMaxVisibleItems; 133 | } 134 | 135 | @Override 136 | public RecyclerView.LayoutParams generateDefaultLayoutParams() { 137 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 138 | } 139 | 140 | /** 141 | * @return current layout orientation 142 | * @see #VERTICAL 143 | * @see #HORIZONTAL 144 | */ 145 | public int getOrientation() { 146 | return mOrientation; 147 | } 148 | 149 | @Override 150 | public boolean canScrollHorizontally() { 151 | return 0 != getChildCount() && HORIZONTAL == mOrientation; 152 | } 153 | 154 | @Override 155 | public boolean canScrollVertically() { 156 | return 0 != getChildCount() && VERTICAL == mOrientation; 157 | } 158 | 159 | /** 160 | * @return current layout center item 161 | */ 162 | public int getCenterItemPosition() { 163 | return mCenterItemPosition; 164 | } 165 | 166 | /** 167 | * @param onCenterItemSelectionListener listener that will trigger when ItemSelectionChanges. can't be null 168 | */ 169 | public void addOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) { 170 | mOnCenterItemSelectionListeners.add(onCenterItemSelectionListener); 171 | } 172 | 173 | /** 174 | * @param onCenterItemSelectionListener listener that was previously added by {@link #addOnItemSelectionListener(OnCenterItemSelectionListener)} 175 | */ 176 | public void removeOnItemSelectionListener(@NonNull final OnCenterItemSelectionListener onCenterItemSelectionListener) { 177 | mOnCenterItemSelectionListeners.remove(onCenterItemSelectionListener); 178 | } 179 | 180 | @SuppressWarnings("RefusedBequest") 181 | @Override 182 | public void scrollToPosition(final int position) { 183 | if (0 >= position) { 184 | throw new IllegalArgumentException("position can't be less then 0. position is : " + position); 185 | } 186 | mPendingScrollPosition = position; 187 | requestLayout(); 188 | } 189 | 190 | @SuppressWarnings("RefusedBequest") 191 | @Override 192 | public void smoothScrollToPosition(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.State state, final int position) { 193 | // RecyclerView.SmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) { 194 | // @Override protected int getVerticalSnapPreference() { 195 | // return LinearSmoothScroller.SNAP_TO_ANY; 196 | // } 197 | // }; 198 | final LinearSmoothScroller linearSmoothScroller = new LinearSmoothScroller(recyclerView.getContext()) { 199 | @Override 200 | public int calculateDyToMakeVisible(final View view, final int snapPreference) { 201 | if (!canScrollVertically()) { 202 | return 0; 203 | } 204 | 205 | return getOffsetForCurrentView(view); 206 | } 207 | 208 | @Override 209 | public int calculateDxToMakeVisible(final View view, final int snapPreference) { 210 | if (!canScrollHorizontally()) { 211 | return 0; 212 | } 213 | return getOffsetForCurrentView(view); 214 | } 215 | }; 216 | linearSmoothScroller.setTargetPosition(position); 217 | startSmoothScroll(linearSmoothScroller); 218 | } 219 | 220 | @Override 221 | @Nullable 222 | public PointF computeScrollVectorForPosition(final int targetPosition) { 223 | if (0 == getChildCount()) { 224 | return null; 225 | } 226 | final float directionDistance = getScrollDirection(targetPosition); 227 | //noinspection NumericCastThatLosesPrecision 228 | final int direction = (int) -Math.signum(directionDistance); 229 | 230 | if (HORIZONTAL == mOrientation) { 231 | return new PointF(direction, 0); 232 | } else { 233 | return new PointF(0, direction); 234 | } 235 | } 236 | 237 | private float getScrollDirection(final int targetPosition) { 238 | final float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount); 239 | 240 | if (mCircleLayout) { 241 | final float t1 = currentScrollPosition - targetPosition; 242 | final float t2 = Math.abs(t1) - mItemsCount; 243 | if (Math.abs(t1) > Math.abs(t2)) { 244 | return Math.signum(t1) * t2; 245 | } else { 246 | return t1; 247 | } 248 | } else { 249 | return currentScrollPosition - targetPosition; 250 | } 251 | } 252 | 253 | @Override 254 | public int scrollVerticallyBy(final int dy, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) { 255 | if (HORIZONTAL == mOrientation) { 256 | return 0; 257 | } 258 | return scrollBy(dy, recycler, state); 259 | } 260 | 261 | @Override 262 | public int scrollHorizontallyBy(final int dx, final RecyclerView.Recycler recycler, final RecyclerView.State state) { 263 | if (VERTICAL == mOrientation) { 264 | return 0; 265 | } 266 | return scrollBy(dx, recycler, state); 267 | } 268 | 269 | /** 270 | * This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and 271 | * {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed.
272 | *
273 | * This method may do relayout work. 274 | * 275 | * @param diff distance that we want to scroll by 276 | * @param recycler Recycler to use for fetching potentially cached views for a position 277 | * @param state Transient state of RecyclerView 278 | * @return distance that we actually scrolled by 279 | */ 280 | @CallSuper 281 | protected int scrollBy(final int diff, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) { 282 | if (null == mDecoratedChildWidth || null == mDecoratedChildHeight) { 283 | return 0; 284 | } 285 | if (0 == getChildCount() || 0 == diff) { 286 | return 0; 287 | } 288 | final int resultScroll; 289 | if (mCircleLayout) { 290 | resultScroll = diff; 291 | 292 | mLayoutHelper.mScrollOffset += resultScroll; 293 | 294 | final int maxOffset = getScrollItemSize() * mItemsCount; 295 | while (0 > mLayoutHelper.mScrollOffset) { 296 | mLayoutHelper.mScrollOffset += maxOffset; 297 | } 298 | while (mLayoutHelper.mScrollOffset > maxOffset) { 299 | mLayoutHelper.mScrollOffset -= maxOffset; 300 | } 301 | 302 | mLayoutHelper.mScrollOffset -= resultScroll; 303 | } else { 304 | final int maxOffset = getMaxScrollOffset(); 305 | 306 | if (0 > mLayoutHelper.mScrollOffset + diff) { 307 | resultScroll = -mLayoutHelper.mScrollOffset; //to make it 0 308 | } else if (mLayoutHelper.mScrollOffset + diff > maxOffset) { 309 | resultScroll = maxOffset - mLayoutHelper.mScrollOffset; //to make it maxOffset 310 | } else { 311 | resultScroll = diff; 312 | } 313 | } 314 | if (0 != resultScroll) { 315 | mLayoutHelper.mScrollOffset += resultScroll; 316 | fillData(recycler, state); 317 | } 318 | return resultScroll; 319 | } 320 | 321 | @Override 322 | public void onMeasure(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state, final int widthSpec, final int heightSpec) { 323 | mDecoratedChildSizeInvalid = true; 324 | 325 | super.onMeasure(recycler, state, widthSpec, heightSpec); 326 | } 327 | 328 | @SuppressWarnings("rawtypes") 329 | @Override 330 | public void onAdapterChanged(final RecyclerView.Adapter oldAdapter, final RecyclerView.Adapter newAdapter) { 331 | super.onAdapterChanged(oldAdapter, newAdapter); 332 | 333 | removeAllViews(); 334 | } 335 | 336 | @SuppressWarnings("RefusedBequest") 337 | @Override 338 | @CallSuper 339 | public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) { 340 | if (0 == state.getItemCount()) { 341 | removeAndRecycleAllViews(recycler); 342 | selectItemCenterPosition(INVALID_POSITION); 343 | return; 344 | } 345 | 346 | detachAndScrapAttachedViews(recycler); 347 | 348 | if (null == mDecoratedChildWidth || mDecoratedChildSizeInvalid) { 349 | final List scrapList = recycler.getScrapList(); 350 | 351 | final boolean shouldRecycle; 352 | final View view; 353 | if (scrapList.isEmpty()) { 354 | shouldRecycle = true; 355 | final int itemsCount = state.getItemCount(); 356 | view = recycler.getViewForPosition( 357 | mPendingScrollPosition == INVALID_POSITION ? 358 | 0 : 359 | Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition)) 360 | ); 361 | addView(view); 362 | } else { 363 | shouldRecycle = false; 364 | view = scrapList.get(0).itemView; 365 | } 366 | measureChildWithMargins(view, 0, 0); 367 | 368 | final int decoratedChildWidth = getDecoratedMeasuredWidth(view); 369 | final int decoratedChildHeight = getDecoratedMeasuredHeight(view); 370 | if (shouldRecycle) { 371 | detachAndScrapView(view, recycler); 372 | } 373 | 374 | if (null != mDecoratedChildWidth && (mDecoratedChildWidth != decoratedChildWidth || mDecoratedChildHeight != decoratedChildHeight)) { 375 | if (INVALID_POSITION == mPendingScrollPosition && null == mPendingCarouselSavedState) { 376 | mPendingScrollPosition = mCenterItemPosition; 377 | } 378 | } 379 | 380 | mDecoratedChildWidth = decoratedChildWidth; 381 | mDecoratedChildHeight = decoratedChildHeight; 382 | mDecoratedChildSizeInvalid = false; 383 | } 384 | 385 | if (INVALID_POSITION != mPendingScrollPosition) { 386 | final int itemsCount = state.getItemCount(); 387 | mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition)); 388 | } 389 | if (INVALID_POSITION != mPendingScrollPosition) { 390 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state); 391 | mPendingScrollPosition = INVALID_POSITION; 392 | mPendingCarouselSavedState = null; 393 | } else if (null != mPendingCarouselSavedState) { 394 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingCarouselSavedState.mCenterItemPosition, state); 395 | mPendingCarouselSavedState = null; 396 | } else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) { 397 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state); 398 | } 399 | 400 | fillData(recycler, state); 401 | } 402 | 403 | private int calculateScrollForSelectingPosition(final int itemPosition, final RecyclerView.State state) { 404 | if (itemPosition == INVALID_POSITION) { 405 | return 0; 406 | } 407 | 408 | final int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1; 409 | return fixedItemPosition * (VERTICAL == mOrientation ? mDecoratedChildHeight : mDecoratedChildWidth); 410 | } 411 | 412 | private void fillData(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) { 413 | final float currentScrollPosition = getCurrentScrollPosition(); 414 | 415 | generateLayoutOrder(currentScrollPosition, state); 416 | detachAndScrapAttachedViews(recycler); 417 | recyclerOldViews(recycler); 418 | 419 | final int width = getWidthNoPadding(); 420 | final int height = getHeightNoPadding(); 421 | if (VERTICAL == mOrientation) { 422 | fillDataVertical(recycler, width, height); 423 | } else { 424 | fillDataHorizontal(recycler, width, height); 425 | } 426 | 427 | recycler.clear(); 428 | 429 | detectOnItemSelectionChanged(currentScrollPosition, state); 430 | } 431 | 432 | private void detectOnItemSelectionChanged(final float currentScrollPosition, final RecyclerView.State state) { 433 | final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount()); 434 | final int centerItem = Math.round(absCurrentScrollPosition); 435 | 436 | if (mCenterItemPosition != centerItem) { 437 | mCenterItemPosition = centerItem; 438 | new Handler(Looper.getMainLooper()).post(new Runnable() { 439 | @Override 440 | public void run() { 441 | selectItemCenterPosition(centerItem); 442 | } 443 | }); 444 | } 445 | } 446 | 447 | private void selectItemCenterPosition(final int centerItem) { 448 | for (final OnCenterItemSelectionListener onCenterItemSelectionListener : mOnCenterItemSelectionListeners) { 449 | onCenterItemSelectionListener.onCenterItemChanged(centerItem); 450 | } 451 | } 452 | 453 | private void fillDataVertical(final RecyclerView.Recycler recycler, final int width, final int height) { 454 | final int start = (width - mDecoratedChildWidth) / 2; 455 | final int end = start + mDecoratedChildWidth; 456 | 457 | final int centerViewTop = (height - mDecoratedChildHeight) / 2; 458 | 459 | for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) { 460 | final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i]; 461 | final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff); 462 | final int top = centerViewTop + offset; 463 | final int bottom = top + mDecoratedChildHeight; 464 | Log.d(TAG, "fillDataVertical: top " + top + " bottom " + bottom + " i " + i); 465 | fillChildItem(start, top, end, bottom, layoutOrder, recycler, i); 466 | } 467 | } 468 | 469 | private void fillDataHorizontal(final RecyclerView.Recycler recycler, final int width, final int height) { 470 | final int top = (height - mDecoratedChildHeight) / 2; 471 | final int bottom = top + mDecoratedChildHeight; 472 | 473 | final int centerViewStart = (width - mDecoratedChildWidth) / 2; 474 | 475 | for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) { 476 | final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i]; 477 | final int offset = getCardOffsetByPositionDiff(layoutOrder.mItemPositionDiff); 478 | final int start = centerViewStart + offset; 479 | final int end = start + mDecoratedChildWidth; 480 | Log.d(TAG, "fillDataHorizontal: top bottom " + top + " " + bottom); 481 | fillChildItem(start, top, end, bottom, layoutOrder, recycler, i); 482 | } 483 | } 484 | 485 | 486 | @SuppressWarnings("MethodWithTooManyParameters") 487 | private void fillChildItem(final int start, final int top, final int end, final int bottom, @NonNull final LayoutOrder layoutOrder, @NonNull final RecyclerView.Recycler recycler, final int i) { 488 | final View view = bindChild(layoutOrder.mItemAdapterPosition, recycler); 489 | ViewCompat.setElevation(view, i); 490 | 491 | ItemTransformationCustom transformation = null; 492 | if (null != mViewPostLayout) { 493 | Log.d(TAG, "fillChildItem: 1"); 494 | transformation = mViewPostLayout.transformChild(view, layoutOrder.mItemPositionDiff, mOrientation, layoutOrder.mItemAdapterPosition); 495 | } 496 | if (null == transformation) { 497 | Log.d(TAG, "fillChildItem: 2"); 498 | view.layout(start, top, end, bottom); 499 | } else { 500 | Log.d(TAG, "fillChildItem: 3"); 501 | view.layout(Math.round(start + transformation.mTranslationX), Math.round(top + transformation.mTranslationY), 502 | Math.round(end + transformation.mTranslationX), Math.round(bottom + transformation.mTranslationY)); 503 | 504 | view.setScaleX(transformation.mScaleX); 505 | view.setScaleY(transformation.mScaleY); 506 | } 507 | } 508 | 509 | /** 510 | * @return current scroll position of center item. this value can be in any range if it is cycle layout. 511 | * if this is not, that then it is in [0, {@link #mItemsCount - 1}] 512 | */ 513 | private float getCurrentScrollPosition() { 514 | final int fullScrollSize = getMaxScrollOffset(); 515 | if (0 == fullScrollSize) { 516 | return 0; 517 | } 518 | return 1.0f * mLayoutHelper.mScrollOffset / getScrollItemSize(); 519 | } 520 | 521 | /** 522 | * @return maximum scroll value to fill up all items in layout. Generally this is only needed for non cycle layouts. 523 | */ 524 | private int getMaxScrollOffset() { 525 | return getScrollItemSize() * (mItemsCount / 2 - 1); 526 | } 527 | 528 | /** 529 | * Because we can support old Android versions, we should layout our children in specific order to make our center view in the top of layout 530 | * (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object. 531 | * This object will be filled by only needed to layout items. Non visible items will not be there. 532 | * 533 | * @param currentScrollPosition current scroll position this is a value that indicates position of center item 534 | * (if this value is int, then center item is really in the center of the layout, else it is near state). 535 | * Be aware that this value can be in any range is it is cycle layout 536 | * @param state Transient state of RecyclerView 537 | * @see #getCurrentScrollPosition() 538 | */ 539 | private void generateLayoutOrder(final float currentScrollPosition, @NonNull final RecyclerView.State state) { 540 | mItemsCount = state.getItemCount(); 541 | final float absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount); 542 | final int centerItem = Math.round(absCurrentScrollPosition); 543 | 544 | if (mCircleLayout && 1 < mItemsCount) { 545 | final int layoutCount = Math.min(mLayoutHelper.mMaxVisibleItems * 2 + 1, mItemsCount); 546 | 547 | mLayoutHelper.initLayoutOrder(layoutCount); 548 | 549 | final int countLayoutHalf = layoutCount / 2; 550 | // before center item 551 | for (int i = 1; i <= countLayoutHalf; ++i) { 552 | final int position = Math.round(absCurrentScrollPosition - i + mItemsCount) % mItemsCount; 553 | 554 | Log.d(TAG, "generateLayoutOrder: centerItem " + centerItem + " absCurrentScrollPosition " + absCurrentScrollPosition + " i " + i); 555 | 556 | mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i - 0.3f); 557 | 558 | // if (i>0) { 559 | // Log.d(TAG, "generateLayoutOrder: i top "+i); 560 | // mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i - 0.3f); 561 | // } else { 562 | // Log.d(TAG, "generateLayoutOrder: i top else "+i); 563 | // 564 | // mLayoutHelper.setLayoutOrder(countLayoutHalf - i, position, centerItem - absCurrentScrollPosition - i+0.1f ); 565 | // 566 | // } 567 | } 568 | // after center item 569 | for (int i = layoutCount - 1; i >= countLayoutHalf + 1; --i) { 570 | final int position = Math.round(absCurrentScrollPosition - i + layoutCount) % mItemsCount; 571 | Log.d(TAG, "generateLayoutOrder: centerItem 2 " + centerItem + " absCurrentScrollPosition " + absCurrentScrollPosition + " i " + i + " layoutCount " + layoutCount); 572 | mLayoutHelper.setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i + 0.3f); 573 | 574 | // if (i-1==layoutCount - 2) { 575 | // mLayoutHelper.setLayoutOrder(i-1, position, centerItem - absCurrentScrollPosition + layoutCount - i+0.8f ); 576 | // 577 | // Log.d(TAG, "generateLayoutOrder: i bottom "+i); 578 | // } else { 579 | // Log.d(TAG, "generateLayoutOrder: i bottom else "+i); 580 | // mLayoutHelper.setLayoutOrder(i - 1, position, centerItem - absCurrentScrollPosition + layoutCount - i +0.2f); 581 | // 582 | // } 583 | 584 | } 585 | mLayoutHelper.setLayoutOrder(layoutCount - 1, centerItem, centerItem - absCurrentScrollPosition); 586 | 587 | } else { 588 | Log.d(TAG, "generateLayoutOrder: else "); 589 | final int firstVisible = Math.max(centerItem - mLayoutHelper.mMaxVisibleItems, 0); 590 | final int lastVisible = Math.min(centerItem + mLayoutHelper.mMaxVisibleItems, mItemsCount - 1); 591 | final int layoutCount = lastVisible - firstVisible + 1; 592 | 593 | mLayoutHelper.initLayoutOrder(layoutCount); 594 | 595 | for (int i = firstVisible; i <= lastVisible; ++i) { 596 | if (i == centerItem) { 597 | mLayoutHelper.setLayoutOrder(layoutCount - 1, i, i - absCurrentScrollPosition); 598 | } else if (i < centerItem) { 599 | mLayoutHelper.setLayoutOrder(i - firstVisible, i, i - absCurrentScrollPosition); 600 | } else { 601 | mLayoutHelper.setLayoutOrder(layoutCount - (i - centerItem) - 1, i, i - absCurrentScrollPosition); 602 | } 603 | } 604 | } 605 | } 606 | 607 | public int getWidthNoPadding() { 608 | return getWidth() - getPaddingStart() - getPaddingEnd(); 609 | } 610 | 611 | public int getHeightNoPadding() { 612 | return getHeight() - getPaddingEnd() - getPaddingStart(); 613 | } 614 | 615 | private View bindChild(final int position, @NonNull final RecyclerView.Recycler recycler) { 616 | final View view = recycler.getViewForPosition(position); 617 | 618 | addView(view); 619 | measureChildWithMargins(view, 0, 0); 620 | 621 | return view; 622 | } 623 | 624 | private void recyclerOldViews(final RecyclerView.Recycler recycler) { 625 | for (RecyclerView.ViewHolder viewHolder : new ArrayList<>(recycler.getScrapList())) { 626 | int adapterPosition = viewHolder.getAdapterPosition(); 627 | boolean found = false; 628 | for (LayoutOrder layoutOrder : mLayoutHelper.mLayoutOrder) { 629 | if (layoutOrder.mItemAdapterPosition == adapterPosition) { 630 | found = true; 631 | break; 632 | } 633 | } 634 | if (!found) { 635 | recycler.recycleView(viewHolder.itemView); 636 | } 637 | } 638 | } 639 | 640 | /** 641 | * Called during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} to calculate item offset from layout center line.
642 | *
643 | * Returns {@link #convertItemPositionDiffToSmoothPositionDiff(float)} * (size off area above center item when it is on the center).
644 | * Sign is: plus if this item is bellow center line, minus if not
645 | *
646 | * ----- - area above it
647 | * ||||| - center item
648 | * ----- - area bellow it (it has the same size as are above center item)
649 | * 650 | * @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line. 651 | * if this is 1 then this item is bellow the layout center line in the full item size distance. 652 | * @return offset in scroll px coordinates. 653 | */ 654 | protected int getCardOffsetByPositionDiff(final float itemPositionDiff) { 655 | final double smoothPosition = convertItemPositionDiffToSmoothPositionDiff(itemPositionDiff); 656 | 657 | final int dimenDiff; 658 | if (VERTICAL == mOrientation) { 659 | dimenDiff = (int) ((getHeightNoPadding() - mDecoratedChildHeight) / 1.7f); 660 | } else { 661 | dimenDiff = (int) ((getWidthNoPadding() - mDecoratedChildWidth) / 1.6f); 662 | } 663 | //noinspection NumericCastThatLosesPrecision 664 | return (int) Math.round(Math.signum(itemPositionDiff) * dimenDiff * smoothPosition); 665 | } 666 | 667 | /** 668 | * Called during {@link #getCardOffsetByPositionDiff(float)} for better item movement.
669 | * Current implementation speed up items that are far from layout center line and slow down items that are close to this line. 670 | * This code is full of maths. If you want to make items move in a different way, probably you should override this method.
671 | * Please see code comments for better explanations. 672 | * 673 | * @param itemPositionDiff current item difference with layout center line. if this is 0, then this item center is in layout center line. 674 | * if this is 1 then this item is bellow the layout center line in the full item size distance. 675 | * @return smooth position offset. needed for scroll calculation and better user experience. 676 | * @see #getCardOffsetByPositionDiff(float) 677 | */ 678 | @SuppressWarnings({"MagicNumber", "InstanceMethodNamingConvention"}) 679 | protected double convertItemPositionDiffToSmoothPositionDiff(final float itemPositionDiff) { 680 | // generally item moves the same way above center and bellow it. So we don't care about diff sign. 681 | final float absIemPositionDiff = Math.abs(itemPositionDiff); 682 | 683 | // we detect if this item is close for center or not. We use (1 / maxVisibleItem) ^ (1/3) as close definer. 684 | if (absIemPositionDiff > StrictMath.pow(1.0f / mLayoutHelper.mMaxVisibleItems, 1.0f / 0.5)) { 685 | // this item is far from center line, so we should make it move like square root function //STandard 1.12 else 686 | return StrictMath.pow(absIemPositionDiff / mLayoutHelper.mMaxVisibleItems, 1 / 1.0f); 687 | } else { 688 | // this item is close from center line. we should slow it down and don't make it speed up very quick. 689 | // so square function in range of [0, (1/maxVisible)^(1/3)] is quite good in it; 690 | return StrictMath.pow(absIemPositionDiff, 1.0f); 691 | } 692 | } 693 | 694 | /** 695 | * @return full item size 696 | */ 697 | protected int getScrollItemSize() { 698 | if (VERTICAL == mOrientation) { 699 | return mDecoratedChildHeight; 700 | } else { 701 | return mDecoratedChildWidth; 702 | } 703 | } 704 | 705 | @Override 706 | public Parcelable onSaveInstanceState() { 707 | if (null != mPendingCarouselSavedState) { 708 | return new CarouselSavedState(mPendingCarouselSavedState); 709 | } 710 | final CarouselSavedState savedState = new CarouselSavedState(super.onSaveInstanceState()); 711 | savedState.mCenterItemPosition = mCenterItemPosition; 712 | return savedState; 713 | } 714 | 715 | @Override 716 | public void onRestoreInstanceState(final Parcelable state) { 717 | if (state instanceof CarouselSavedState) { 718 | mPendingCarouselSavedState = (CarouselSavedState) state; 719 | 720 | super.onRestoreInstanceState(mPendingCarouselSavedState.mSuperState); 721 | } else { 722 | super.onRestoreInstanceState(state); 723 | } 724 | } 725 | 726 | /** 727 | * @return Scroll offset from nearest item from center 728 | */ 729 | protected int getOffsetCenterView() { 730 | return Math.round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper.mScrollOffset; 731 | } 732 | 733 | protected int getOffsetForCurrentView(@NonNull final View view) { 734 | final int targetPosition = getPosition(view); 735 | final float directionDistance = getScrollDirection(targetPosition); 736 | 737 | return Math.round(directionDistance * getScrollItemSize()); 738 | } 739 | 740 | /** 741 | * Helper method that make scroll in range of [0, count). Generally this method is needed only for cycle layout. 742 | * 743 | * @param currentScrollPosition any scroll position range. 744 | * @param count adapter items count 745 | * @return good scroll position in range of [0, count) 746 | */ 747 | private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) { 748 | float absCurrentScrollPosition = currentScrollPosition; 749 | while (0 > absCurrentScrollPosition) { 750 | absCurrentScrollPosition += count; 751 | } 752 | while (Math.round(absCurrentScrollPosition) >= count) { 753 | absCurrentScrollPosition -= count; 754 | } 755 | return absCurrentScrollPosition; 756 | } 757 | 758 | /** 759 | * This interface methods will be called for each visible view item after general LayoutManager layout finishes.
760 | *
761 | * Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting. 762 | */ 763 | @SuppressWarnings("InterfaceNeverImplemented") 764 | public abstract static class PostLayoutListener { 765 | 766 | /** 767 | * Called after child layout finished. Generally you can do any translation and scaling work here. 768 | * 769 | * @param child view that was layout 770 | * @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not 771 | * @param orientation layoutManager orientation {@link #getLayoutDirection()} 772 | * @param itemPositionInAdapter item position inside adapter for this layout pass 773 | */ 774 | public ItemTransformationCustom transformChild( 775 | @NonNull final View child, 776 | final float itemPositionToCenterDiff, 777 | final int orientation, 778 | final int itemPositionInAdapter 779 | ) { 780 | return transformChild(child, itemPositionToCenterDiff, orientation); 781 | } 782 | 783 | /** 784 | * Called after child layout finished. Generally you can do any translation and scaling work here. 785 | * 786 | * @param child view that was layout 787 | * @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not 788 | * @param orientation layoutManager orientation {@link #getLayoutDirection()} 789 | */ 790 | public ItemTransformationCustom transformChild( 791 | @NonNull final View child, 792 | final float itemPositionToCenterDiff, 793 | final int orientation 794 | ) { 795 | throw new IllegalStateException("at least one transformChild should be implemented"); 796 | } 797 | } 798 | 799 | public interface OnCenterItemSelectionListener { 800 | 801 | /** 802 | * Listener that will be called on every change of center item. 803 | * This listener will be triggered on every layout operation if item was changed. 804 | * Do not do any expensive operations in this method since this will effect scroll experience. 805 | * 806 | * @param adapterPosition current layout center item 807 | */ 808 | void onCenterItemChanged(final int adapterPosition); 809 | } 810 | 811 | /** 812 | * Helper class that holds currently visible items. 813 | * Generally this class fills this list.
814 | *
815 | * This class holds all scroll and maxVisible items state. 816 | * 817 | * @see #getMaxVisibleItems() 818 | */ 819 | private static class LayoutHelper { 820 | 821 | private int mMaxVisibleItems; 822 | 823 | private int mScrollOffset; 824 | 825 | private LayoutOrder[] mLayoutOrder; 826 | 827 | private final List> mReusedItems = new ArrayList<>(); 828 | 829 | LayoutHelper(final int maxVisibleItems) { 830 | mMaxVisibleItems = maxVisibleItems; 831 | } 832 | 833 | /** 834 | * Called before any fill calls. Needed to recycle old items and init new array list. Generally this list is an array an it is reused. 835 | * 836 | * @param layoutCount items count that will be layout 837 | */ 838 | void initLayoutOrder(final int layoutCount) { 839 | if (null == mLayoutOrder || mLayoutOrder.length != layoutCount) { 840 | if (null != mLayoutOrder) { 841 | recycleItems(mLayoutOrder); 842 | } 843 | mLayoutOrder = new LayoutOrder[layoutCount]; 844 | fillLayoutOrder(); 845 | } 846 | } 847 | 848 | /** 849 | * Called during layout generation process of filling this list. Should be called only after {@link #initLayoutOrder(int)} method call. 850 | * 851 | * @param arrayPosition position in layout order 852 | * @param itemAdapterPosition adapter position of item for future data filling logic 853 | * @param itemPositionDiff difference of current item scroll position and center item position. 854 | * if this is a center item and it is in real center of layout, then this will be 0. 855 | * if current layout is not in the center, then this value will never be int. 856 | * if this item center is bellow layout center line then this value is greater then 0, 857 | * else less then 0. 858 | */ 859 | void setLayoutOrder(final int arrayPosition, final int itemAdapterPosition, final float itemPositionDiff) { 860 | final LayoutOrder item = mLayoutOrder[arrayPosition]; 861 | item.mItemAdapterPosition = itemAdapterPosition; 862 | item.mItemPositionDiff = itemPositionDiff; 863 | } 864 | 865 | /** 866 | * Checks is this screen Layout has this adapterPosition view in layout 867 | * 868 | * @param adapterPosition adapter position of item for future data filling logic 869 | * @return true is adapterItem is in layout 870 | */ 871 | boolean hasAdapterPosition(final int adapterPosition) { 872 | if (null != mLayoutOrder) { 873 | for (final LayoutOrder layoutOrder : mLayoutOrder) { 874 | if (layoutOrder.mItemAdapterPosition == adapterPosition) { 875 | return true; 876 | } 877 | } 878 | } 879 | return false; 880 | } 881 | 882 | @SuppressWarnings("VariableArgumentMethod") 883 | private void recycleItems(@NonNull final LayoutOrder... layoutOrders) { 884 | for (final LayoutOrder layoutOrder : layoutOrders) { 885 | //noinspection ObjectAllocationInLoop 886 | mReusedItems.add(new WeakReference<>(layoutOrder)); 887 | } 888 | } 889 | 890 | private void fillLayoutOrder() { 891 | for (int i = 0, length = mLayoutOrder.length; i < length; ++i) { 892 | if (null == mLayoutOrder[i]) { 893 | mLayoutOrder[i] = createLayoutOrder(); 894 | } 895 | } 896 | } 897 | 898 | private LayoutOrder createLayoutOrder() { 899 | final Iterator> iterator = mReusedItems.iterator(); 900 | while (iterator.hasNext()) { 901 | final WeakReference layoutOrderWeakReference = iterator.next(); 902 | final LayoutOrder layoutOrder = layoutOrderWeakReference.get(); 903 | iterator.remove(); 904 | if (null != layoutOrder) { 905 | return layoutOrder; 906 | } 907 | } 908 | return new LayoutOrder(); 909 | } 910 | } 911 | 912 | /** 913 | * Class that holds item data. 914 | * This class is filled during {@link #generateLayoutOrder(float, RecyclerView.State)} and used during {@link #fillData(RecyclerView.Recycler, RecyclerView.State)} 915 | */ 916 | private static class LayoutOrder { 917 | 918 | /** 919 | * Item adapter position 920 | */ 921 | private int mItemAdapterPosition; 922 | /** 923 | * Item center difference to layout center. If center of item is bellow layout center, then this value is greater then 0, else it is less. 924 | */ 925 | private float mItemPositionDiff; 926 | } 927 | 928 | protected static class CarouselSavedState implements Parcelable { 929 | 930 | private final Parcelable mSuperState; 931 | private int mCenterItemPosition; 932 | 933 | protected CarouselSavedState(@Nullable final Parcelable superState) { 934 | mSuperState = superState; 935 | } 936 | 937 | private CarouselSavedState(@NonNull final Parcel in) { 938 | mSuperState = in.readParcelable(Parcelable.class.getClassLoader()); 939 | mCenterItemPosition = in.readInt(); 940 | } 941 | 942 | protected CarouselSavedState(@NonNull final CarouselSavedState other) { 943 | mSuperState = other.mSuperState; 944 | mCenterItemPosition = other.mCenterItemPosition; 945 | } 946 | 947 | @Override 948 | public int describeContents() { 949 | return 0; 950 | } 951 | 952 | @Override 953 | public void writeToParcel(final Parcel parcel, final int i) { 954 | parcel.writeParcelable(mSuperState, i); 955 | parcel.writeInt(mCenterItemPosition); 956 | } 957 | 958 | public static final Creator CREATOR 959 | = new Creator() { 960 | @Override 961 | public CarouselSavedState createFromParcel(final Parcel parcel) { 962 | return new CarouselSavedState(parcel); 963 | } 964 | 965 | @Override 966 | public CarouselSavedState[] newArray(final int i) { 967 | return new CarouselSavedState[i]; 968 | } 969 | }; 970 | } 971 | } --------------------------------------------------------------------------------