├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── img_1.jpg
│ │ │ │ ├── img_2.jpg
│ │ │ │ ├── img_3.jpg
│ │ │ │ ├── img_4.jpg
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── drawable
│ │ │ │ └── title_bg.xml
│ │ │ ├── values-w820dp
│ │ │ │ └── dimens.xml
│ │ │ ├── menu
│ │ │ │ └── menu.xml
│ │ │ └── layout
│ │ │ │ ├── activity_list.xml
│ │ │ │ ├── item_view.xml
│ │ │ │ ├── item_view1.xml
│ │ │ │ ├── activity_main.xml
│ │ │ │ └── activity_both.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ckenergy
│ │ │ └── stackcard
│ │ │ └── sample
│ │ │ ├── HorizontalActivity.java
│ │ │ ├── VerticalActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── BothActivity.java
│ │ │ ├── RecyclerViewAdapter.java
│ │ │ ├── RecyclerViewAdapter1.java
│ │ │ └── BaseActivity.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── ckenergy
│ │ │ └── stackcard
│ │ │ └── sample
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ckenergy
│ │ └── stackcard
│ │ └── sample
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── stackcardlayoutmanager
├── consumer-rules.pro
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── ckenergy
│ │ │ └── stackcard
│ │ │ └── stackcardlayoutmanager
│ │ │ ├── ItemTransformation.java
│ │ │ ├── ChildViewOutlineProvider.java
│ │ │ ├── StackCardSmoothScroller.java
│ │ │ ├── DefaultChildSelectionListener.java
│ │ │ ├── StackCardChildSelectionListener.java
│ │ │ ├── CenterScrollListener.java
│ │ │ ├── ItemTouchHelperCallBack.java
│ │ │ ├── StackCardPostLayout.java
│ │ │ └── StackCardLayoutManager.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── ckenergy
│ │ │ └── stackcard
│ │ │ └── stackcardlayoutmanager
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── ckenergy
│ │ └── stackcard
│ │ └── stackcardlayoutmanager
│ │ └── ApplicationTest.java
├── proguard-rules.pro
└── build.gradle
├── img
├── exp.jpg
├── gif_v.gif
├── gif_h_f.gif
├── gif_h_l.gif
├── gif_swip.gif
├── h_in_n.jpg
├── h_in_p.jpg
├── h_out_n.jpg
├── h_out_p.jpg
├── v_c_exp.jpg
├── v_less.jpg
├── gif_h_in_n.gif
└── h_scroll2p.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README-CN.md
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/img/exp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/exp.jpg
--------------------------------------------------------------------------------
/img/gif_v.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/gif_v.gif
--------------------------------------------------------------------------------
/img/gif_h_f.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/gif_h_f.gif
--------------------------------------------------------------------------------
/img/gif_h_l.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/gif_h_l.gif
--------------------------------------------------------------------------------
/img/gif_swip.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/gif_swip.gif
--------------------------------------------------------------------------------
/img/h_in_n.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/h_in_n.jpg
--------------------------------------------------------------------------------
/img/h_in_p.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/h_in_p.jpg
--------------------------------------------------------------------------------
/img/h_out_n.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/h_out_n.jpg
--------------------------------------------------------------------------------
/img/h_out_p.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/h_out_p.jpg
--------------------------------------------------------------------------------
/img/v_c_exp.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/v_c_exp.jpg
--------------------------------------------------------------------------------
/img/v_less.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/v_less.jpg
--------------------------------------------------------------------------------
/img/gif_h_in_n.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/gif_h_in_n.gif
--------------------------------------------------------------------------------
/img/h_scroll2p.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/img/h_scroll2p.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/img_1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/img_1.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/img_2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/img_2.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/img_3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/img_3.jpg
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/img_4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/img_4.jpg
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StackCardLayoutManager
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ckenergy/StackCardRecyclerView/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Feb 08 12:05:34 CST 2022
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/title_bg.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
9 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ckenergy/stackcard/sample/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/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 = "StackCardRecycleView"
16 |
17 | include ':app', ':stackcardlayoutmanager'
18 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/ckenergy/stackcard/sample/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/test/java/com/ckenergy/stackcard/stackcardlayoutmanager/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/app/src/main/res/menu/menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/androidTest/java/com/ckenergy/stackcard/stackcardlayoutmanager/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StackCardRecyclerView
3 |
4 | vertical
5 | horizontal
6 | both
7 |
8 | change_stack_order
9 | change_number_order
10 |
11 | in_stack
12 | out_stack
13 | negative
14 | positive
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\android_sdk\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .DS_Store
3 |
4 | # Built application files
5 | *.apk
6 | *.ap_
7 |
8 | # Files for the Dalvik VM
9 | *.dex
10 |
11 | # Java class files
12 | *.class
13 |
14 | # Gradle files
15 | .gradle/
16 | build/
17 |
18 | # Local configuration file (sdk path, etc)
19 | local.properties
20 |
21 | # Proguard folder generated by Eclipse
22 | proguard/
23 |
24 | # Log Files
25 | *.log
26 |
27 | # Android Studio Navigation editor temp files
28 | .navigation/
29 |
30 | # Android Studio captures folder
31 | captures/
32 |
33 | # Generated files
34 | bin/
35 | gen/
36 | out/
37 |
38 | # Log Files
39 | *.log
40 |
41 | # Intellij
42 | *.iml
43 | .idea/
44 |
45 | # Keystore files
46 | *.jks
47 |
48 | stackcardlayoutmanager/upload
--------------------------------------------------------------------------------
/stackcardlayoutmanager/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in D:\android_sdk\sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/ItemTransformation.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | public class ItemTransformation {
4 |
5 | final float mScaleX;
6 | final float mScaleY;
7 | final int mTranslationX;
8 | final int mTranslationY;
9 | final float mAlpha;
10 | final int mClipLength;
11 |
12 |
13 | public ItemTransformation(final float scaleX, final float scaleY, final int translationX, final int translationY,final int clipLength, final float alpha) {
14 | mScaleX = scaleX;
15 | mScaleY = scaleY;
16 | mTranslationX = translationX;
17 | mTranslationY = translationY;
18 | mClipLength = clipLength;
19 | mAlpha = alpha;
20 | }
21 | }
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/ChildViewOutlineProvider.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.annotation.TargetApi;
4 | import android.graphics.Outline;
5 | import android.os.Build;
6 | import android.util.Log;
7 | import android.view.View;
8 | import android.view.ViewOutlineProvider;
9 |
10 | /**
11 | * Created by chengkai on 2016/12/9.
12 | */
13 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
14 | public class ChildViewOutlineProvider extends ViewOutlineProvider {
15 | @Override
16 | public void getOutline(View view, Outline outline) {
17 | Log.d(getClass().getSimpleName(),"width:"+view.getWidth()+",height:"+view.getHeight());
18 | outline.setRect(view.getLeft(),view.getTop(),view.getLeft()+view.getWidth()/2,view.getTop()+view.getHeight()/2);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
11 |
12 |
19 |
20 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
11 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_view1.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
15 |
16 |
21 |
22 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/HorizontalActivity.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.os.Bundle;
4 | import android.util.Log;
5 |
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardLayoutManager;
9 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardPostLayout;
10 |
11 | public class HorizontalActivity extends BaseActivity {
12 |
13 | @Override
14 | protected void onCreate(Bundle savedInstanceState) {
15 | super.onCreate(savedInstanceState);
16 | setContentView(R.layout.activity_list);
17 |
18 | final RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
19 |
20 | StackCardLayoutManager stackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.HORIZONTAL, false, new StackCardPostLayout());
21 | RecyclerViewAdapter1 adapter = new RecyclerViewAdapter1(20);
22 |
23 | initRecyclerView(recyclerView,stackCardLayoutManager, adapter);
24 |
25 | Log.d(getClass().getSimpleName(),stackCardLayoutManager.getStackOrder()+"");
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/VerticalActivity.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.recyclerview.widget.RecyclerView;
6 |
7 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardLayoutManager;
8 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardPostLayout;
9 |
10 | public class VerticalActivity extends BaseActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_list);
16 |
17 | RecyclerView recyclerView = (RecyclerView) findViewById(R.id.list);
18 |
19 | StackCardLayoutManager stackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.VERTICAL,false, new StackCardPostLayout());
20 | stackCardLayoutManager.setStackOrder(StackCardLayoutManager.OUT_STACK_ORDER);
21 | stackCardLayoutManager.setNumberOrder(StackCardLayoutManager.NEGATIVE_ORDER);
22 | RecyclerViewAdapter1 adapter = new RecyclerViewAdapter1(20);
23 |
24 | initRecyclerView(recyclerView, stackCardLayoutManager, adapter);
25 |
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | compileSdk 31
8 |
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 31
12 |
13 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
14 | consumerProguardFiles "consumer-rules.pro"
15 | }
16 |
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | compileOptions {
24 | sourceCompatibility JavaVersion.VERSION_11
25 | targetCompatibility JavaVersion.VERSION_11
26 | }
27 | kotlinOptions {
28 | jvmTarget = '1.8'
29 | }
30 | }
31 |
32 | dependencies {
33 |
34 | implementation 'androidx.core:core-ktx:1.7.0'
35 | implementation 'androidx.appcompat:appcompat:1.4.1'
36 | implementation 'com.google.android.material:material:1.5.0'
37 | testImplementation 'junit:junit:4.13.2'
38 | androidTestImplementation 'androidx.test.ext:junit:1.1.3'
39 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
40 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
22 |
23 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.view.View;
6 |
7 | import androidx.appcompat.app.AppCompatActivity;
8 |
9 | public class MainActivity extends AppCompatActivity implements View.OnClickListener{
10 |
11 | @Override
12 | protected void onCreate(Bundle savedInstanceState) {
13 | super.onCreate(savedInstanceState);
14 | setContentView(R.layout.activity_main);
15 | findViewById(R.id.vertical).setOnClickListener(this);
16 | findViewById(R.id.horizontal).setOnClickListener(this);
17 | findViewById(R.id.both).setOnClickListener(this);
18 |
19 |
20 | }
21 |
22 | @Override
23 | public void onClick(View v) {
24 | switch (v.getId()) {
25 | case R.id.vertical:
26 | startActivity(new Intent(this,VerticalActivity.class));
27 | break;
28 | case R.id.horizontal:
29 | startActivity(new Intent(this,HorizontalActivity.class));
30 | break;
31 | case R.id.both:
32 | startActivity(new Intent(this,BothActivity.class));
33 | break;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | android {
8 | compileSdk 31
9 |
10 | defaultConfig {
11 | applicationId "com.ckenergy.stackcard.sample"
12 | minSdk 21
13 | targetSdk 31
14 | versionCode 1
15 | versionName '1.0'
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_11
28 | targetCompatibility JavaVersion.VERSION_11
29 | }
30 | kotlinOptions {
31 | jvmTarget = '1.8'
32 | }
33 | }
34 |
35 | dependencies {
36 | implementation fileTree(dir: 'libs', include: ['*.jar'])
37 | implementation 'androidx.recyclerview:recyclerview:1.2.1'
38 | testImplementation 'junit:junit:4.12'
39 | implementation 'androidx.appcompat:appcompat:1.4.1'
40 | implementation project(':stackcardlayoutmanager')
41 | // compile 'com.ckenergy.stackcardlayoutmanager:stackcardlayoutmanager:1.0.1'
42 | }
43 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/BothActivity.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.os.Bundle;
4 |
5 | import androidx.recyclerview.widget.RecyclerView;
6 |
7 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardLayoutManager;
8 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardPostLayout;
9 |
10 | public class BothActivity extends BaseActivity {
11 |
12 | @Override
13 | protected void onCreate(Bundle savedInstanceState) {
14 | super.onCreate(savedInstanceState);
15 | setContentView(R.layout.activity_both);
16 |
17 | RecyclerView vRecyclerView = (RecyclerView) findViewById(R.id.list1);
18 |
19 | StackCardLayoutManager vStackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.VERTICAL,true, new StackCardPostLayout());
20 | initRecyclerView(vRecyclerView, vStackCardLayoutManager, new RecyclerViewAdapter(20));
21 |
22 | final RecyclerView hRecyclerView = (RecyclerView) findViewById(R.id.list2);
23 | StackCardLayoutManager hStackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.HORIZONTAL, false, new StackCardPostLayout());
24 |
25 | initRecyclerView(hRecyclerView, hStackCardLayoutManager, new RecyclerViewAdapter(20));
26 |
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/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 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/StackCardSmoothScroller.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.content.Context;
4 | import android.view.View;
5 |
6 | import androidx.recyclerview.widget.LinearSmoothScroller;
7 | import androidx.recyclerview.widget.RecyclerView;
8 |
9 | /**
10 | * Custom implementation of {@link RecyclerView.SmoothScroller} that can work only with {@link StackCardLayoutManager}.
11 | *
12 | * @see StackCardLayoutManager
13 | */
14 | public abstract class StackCardSmoothScroller extends LinearSmoothScroller {
15 |
16 | protected StackCardSmoothScroller(final Context context) {
17 | super(context);
18 | }
19 |
20 | @SuppressWarnings("RefusedBequest")
21 | @Override
22 | public int calculateDyToMakeVisible(final View view, final int snapPreference) {
23 | final StackCardLayoutManager layoutManager = (StackCardLayoutManager) getLayoutManager();
24 | if (null == layoutManager || !layoutManager.canScrollVertically()) {
25 | return 0;
26 | }
27 | int dy = layoutManager.getOffsetForCurrentView(view);
28 | return dy;
29 | }
30 |
31 | @SuppressWarnings("RefusedBequest")
32 | @Override
33 | public int calculateDxToMakeVisible(final View view, final int snapPreference) {
34 | final StackCardLayoutManager layoutManager = (StackCardLayoutManager) getLayoutManager();
35 | if (null == layoutManager || !layoutManager.canScrollHorizontally()) {
36 | return 0;
37 | }
38 | int dx = layoutManager.getOffsetForCurrentView(view);
39 | return dx;
40 | }
41 |
42 | }
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_both.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
17 |
18 |
23 |
24 |
29 |
30 |
31 |
32 |
36 |
37 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/DefaultChildSelectionListener.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.util.Log;
4 | import android.view.View;
5 |
6 | import androidx.annotation.NonNull;
7 | import androidx.recyclerview.widget.RecyclerView;
8 |
9 | public class DefaultChildSelectionListener extends StackCardChildSelectionListener {
10 |
11 | @NonNull
12 | private final OnCenterItemClickListener mOnCenterItemClickListener;
13 |
14 | protected DefaultChildSelectionListener(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager) {
15 | super(recyclerView, stackCardLayoutManager);
16 |
17 | mOnCenterItemClickListener = onCenterItemClickListener;
18 | }
19 |
20 | @Override
21 | protected void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v) {
22 | mOnCenterItemClickListener.onCenterItemClicked(recyclerView, stackCardLayoutManager, v);
23 | }
24 |
25 | @Override
26 | protected void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v) {
27 | int position = stackCardLayoutManager.getPosition(v);
28 | Log.d("onBackItemClicked","position:"+position);
29 | recyclerView.smoothScrollToPosition(position);
30 | }
31 |
32 | public static DefaultChildSelectionListener initCenterItemListener(@NonNull final OnCenterItemClickListener onCenterItemClickListener, @NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager) {
33 | return new DefaultChildSelectionListener(onCenterItemClickListener, recyclerView, stackCardLayoutManager);
34 | }
35 |
36 | public interface OnCenterItemClickListener {
37 |
38 | void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v);
39 | }
40 | }
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/StackCardChildSelectionListener.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.view.View;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | public abstract class StackCardChildSelectionListener {
9 |
10 | @NonNull
11 | private final RecyclerView mRecyclerView;
12 | @NonNull
13 | private final StackCardLayoutManager mStackCardLayoutManager;
14 |
15 | private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
16 | @Override
17 | public void onClick(final View v) {
18 | final RecyclerView.ViewHolder holder = mRecyclerView.getChildViewHolder(v);
19 | final int position = holder.getAdapterPosition();
20 |
21 | if (position == mStackCardLayoutManager.getCenterItemPosition()) {
22 | onCenterItemClicked(mRecyclerView, mStackCardLayoutManager, v);
23 | } else {
24 | onBackItemClicked(mRecyclerView, mStackCardLayoutManager, v);
25 | }
26 | }
27 | };
28 |
29 | protected StackCardChildSelectionListener(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager) {
30 | mRecyclerView = recyclerView;
31 | mStackCardLayoutManager = stackCardLayoutManager;
32 |
33 | mRecyclerView.addOnChildAttachStateChangeListener(new RecyclerView.OnChildAttachStateChangeListener() {
34 | @Override
35 | public void onChildViewAttachedToWindow(final View view) {
36 | view.setOnClickListener(mOnClickListener);
37 | }
38 |
39 | @Override
40 | public void onChildViewDetachedFromWindow(final View view) {
41 | view.setOnClickListener(null);
42 | }
43 | });
44 | }
45 |
46 | protected abstract void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v);
47 |
48 | protected abstract void onBackItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v);
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/RecyclerViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.graphics.Color;
4 | import android.util.Log;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.TextView;
9 |
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.ckenergy.stackcard.stackcardlayoutmanager.ItemTouchHelperCallBack;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 | import java.util.Random;
17 |
18 | /**
19 | * Created by chengkai on 2016/11/28.
20 | */
21 | public class RecyclerViewAdapter extends RecyclerView.Adapter implements ItemTouchHelperCallBack.onSwipListener {
22 |
23 | @Override
24 | public void onSwip(RecyclerView.ViewHolder viewHolder, int position) {
25 | remove(position);
26 | }
27 |
28 | class Bean {
29 | int mColor;
30 | int mPosition;
31 | }
32 |
33 | private final Random mRandom = new Random();
34 | List cards = new ArrayList<>();
35 |
36 | RecyclerViewAdapter(int count) {
37 | for (int i = 0; count > i; ++i) {
38 | Bean card = new Bean();
39 | card.mColor = Color.argb(255, mRandom.nextInt(256), mRandom.nextInt(256), mRandom.nextInt(256));
40 | card.mPosition = i;
41 | cards.add(card);
42 | }
43 | }
44 |
45 | @Override
46 | public TestViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
47 |
48 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view, parent, false);
49 | return new TestViewHolder(view);
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(final TestViewHolder holder, final int position) {
54 | holder.top.setText(String.valueOf(cards.get(position).mPosition));
55 | holder.bottom.setText(String.valueOf(cards.get(position).mPosition));
56 | holder.itemView.setBackgroundColor(cards.get(position).mColor);
57 | Log.d(this.getClass().getSimpleName(), "position:" + position);
58 | }
59 |
60 | @Override
61 | public int getItemCount() {
62 | return cards.size();
63 | }
64 |
65 | public void remove(int position) {
66 | cards.remove(position);
67 | notifyItemRemoved(position);
68 | }
69 |
70 |
71 | class TestViewHolder extends RecyclerView.ViewHolder {
72 |
73 | private TextView top;
74 | private TextView bottom;
75 |
76 | TestViewHolder(View view) {
77 | super(view);
78 | top = (TextView) view.findViewById(R.id.top);
79 | bottom = (TextView) view.findViewById(R.id.bottom);
80 | }
81 |
82 | }
83 |
84 |
85 |
86 | }
87 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/RecyclerViewAdapter1.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.util.Log;
4 | import android.view.LayoutInflater;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.ImageView;
8 | import android.widget.TextView;
9 |
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | import com.ckenergy.stackcard.stackcardlayoutmanager.ItemTouchHelperCallBack;
13 |
14 | import java.util.ArrayList;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by chengkai on 2016/11/28.
19 | */
20 | public class RecyclerViewAdapter1 extends RecyclerView.Adapter implements ItemTouchHelperCallBack.onSwipListener {
21 |
22 | int[] mImgs = {R.mipmap.img_1,R.mipmap.img_2,R.mipmap.img_3,R.mipmap.img_4};
23 |
24 | @Override
25 | public void onSwip(RecyclerView.ViewHolder viewHolder, int position) {
26 | remove(position);
27 | }
28 |
29 | class Bean {
30 | int mPosition;
31 | int mImgRes;
32 | }
33 |
34 | List cards = new ArrayList<>();
35 |
36 | RecyclerViewAdapter1(int count) {
37 | for (int i = 0; count > i; ++i) {
38 | Bean card = new Bean();
39 | card.mPosition = i;
40 | card.mImgRes = mImgs[i% mImgs.length];
41 | cards.add(card);
42 | }
43 | }
44 |
45 | @Override
46 | public TestViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
47 |
48 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view1, parent, false);
49 | return new TestViewHolder(view);
50 | }
51 |
52 | @Override
53 | public void onBindViewHolder(final TestViewHolder holder, final int position) {
54 | holder.top.setText(String.valueOf(cards.get(position).mPosition));
55 | holder.bottom.setText(String.valueOf(cards.get(position).mPosition));
56 | holder.img.setImageResource(cards.get(position).mImgRes);
57 | Log.d(this.getClass().getSimpleName(), "position:" + position);
58 | }
59 |
60 | @Override
61 | public int getItemCount() {
62 | return cards.size();
63 | }
64 |
65 | public void remove(int position) {
66 | cards.remove(position);
67 | notifyItemRemoved(position);
68 | }
69 |
70 |
71 | class TestViewHolder extends RecyclerView.ViewHolder {
72 |
73 | private TextView top;
74 | private TextView bottom;
75 | private ImageView img;
76 |
77 | TestViewHolder(View view) {
78 | super(view);
79 | top = (TextView) view.findViewById(R.id.top);
80 | bottom = (TextView) view.findViewById(R.id.bottom);
81 | img = (ImageView) view.findViewById(R.id.img);
82 | }
83 |
84 | }
85 |
86 |
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/CenterScrollListener.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 |
4 | import androidx.recyclerview.widget.RecyclerView;
5 |
6 | /**
7 | * Class for centering items after scroll event.
8 | * This class will listen to current scroll state and if item is not centered after scroll it will automatically scroll it to center.
9 | */
10 | public class CenterScrollListener extends RecyclerView.OnScrollListener {
11 |
12 | private boolean mAutoSet = true;
13 |
14 | private int arrow;
15 |
16 | @Override
17 | public void onScrollStateChanged(final RecyclerView recyclerView, final int newState) {
18 | super.onScrollStateChanged(recyclerView, newState);
19 | // Log.d(getClass().getSimpleName(),"newState:"+newState);
20 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
21 | if (!(layoutManager instanceof StackCardLayoutManager)) {
22 | mAutoSet = true;
23 | return;
24 | }
25 |
26 | final StackCardLayoutManager lm = (StackCardLayoutManager) layoutManager;
27 | if (!mAutoSet) {
28 | if (RecyclerView.SCROLL_STATE_IDLE == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
29 | final int scrollNeeded = lm.getOffsetCenterView();
30 | int itemSize= lm.getScrollItemSize();
31 | int centerPosition = lm.getCenterItemPosition();
32 | // Log.d(this.getClass().getSimpleName(),"scrollNeeded:"+scrollNeeded+",centerPosition:"+centerPosition);
33 | // Log.d(getClass().getSimpleName(),"newState:"+newState);
34 | int totalOffset = centerPosition*itemSize-scrollNeeded;
35 | // Log.d(getClass().getSimpleName(),"totalOffset:"+totalOffset);
36 |
37 | int distance = scrollNeeded;
38 | if (totalOffset > (lm.getItemCount()-1)*itemSize) {
39 | distance = (lm.getItemCount()-1)*itemSize-totalOffset;
40 | }else if (totalOffset < 0) {
41 | distance = -totalOffset;
42 | }else {
43 | if (Math.abs(scrollNeeded) > itemSize/10) {// move itemsize 1/20 than move to next
44 | if (lm.getNumberOrder()*arrow*scrollNeeded < 0) {
45 | distance = (int) (scrollNeeded-Math.signum(scrollNeeded)*itemSize);
46 | }
47 | }
48 | }
49 | // Log.d(this.getClass().getSimpleName(),"distance:"+distance);
50 | if (StackCardLayoutManager.HORIZONTAL == lm.getOrientation()) {
51 | recyclerView.smoothScrollBy(distance*lm.getNumberOrder(), 0);
52 | } else {
53 | recyclerView.smoothScrollBy(0, distance*lm.getNumberOrder());
54 | }
55 | mAutoSet = true;
56 | }
57 | }
58 | if (RecyclerView.SCROLL_STATE_DRAGGING == newState || RecyclerView.SCROLL_STATE_SETTLING == newState) {
59 | mAutoSet = false;
60 | }
61 | }
62 |
63 | @Override
64 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
65 | // Log.d(this.getClass().getSimpleName(),"dx:"+dx+",dy:"+dy);
66 | final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
67 | if (!(layoutManager instanceof StackCardLayoutManager)) {
68 | mAutoSet = true;
69 | return;
70 | }
71 |
72 | final StackCardLayoutManager lm = (StackCardLayoutManager) layoutManager;
73 | if (!mAutoSet) {
74 | if (StackCardLayoutManager.HORIZONTAL == lm.getOrientation()) {
75 | arrow = (int) Math.signum(dx);
76 | }else {
77 | arrow = (int) Math.signum(dy);
78 | }
79 | }
80 | }
81 | }
--------------------------------------------------------------------------------
/README-CN.md:
--------------------------------------------------------------------------------
1 | # StackCardRecyclerView
2 | 类似于Android 6.0最近任务
3 |
4 |
5 |
6 |
7 | ## View GIF ##
8 |
9 |
10 |
11 |
12 |
13 | 感谢 [CarouselLayoutManager](https://github.com/Azoft/CarouselLayoutManager) , 我参考了他的代码.
14 |
15 | ### dependencies ###
16 | ##### Maven Central #####
17 | ```xml
18 |
19 | com.ckenergy.stackcardlayoutmanager
20 | stackcardlayoutmanager
21 | 1.0.1
22 | pom
23 |
24 | ```
25 | ##### Gradle #####
26 | ```
27 | compile 'com.ckenergy.stackcardlayoutmanager:stackcardlayoutmanager:1.0.1'
28 | ```
29 | ##### Ivy #####
30 | ```xml
31 |
32 |
33 |
34 | ```
35 |
36 | ### 有竖直(Vertical)和水平(Horizontal) 两种类型###
37 |
38 | 
39 |
40 | ### 不管横向还是竖向都有卡片入栈和出栈堆叠模式(in stack and out stack) 和正反序排序模式(positive and negative) ###
41 |
42 | #### 水平 ####
43 |
44 | * 1.正序, 入栈
45 | * 2.正序, 出栈
46 |
47 | 
48 |
49 | * 3.反序, 入栈
50 | * 4.反序, 出栈
51 |
52 | 
53 |
54 |
55 | ### 高度或宽度不够的话会影响显示数量 ###
56 |
57 | 在竖直模式当宽度大于高度时,在水平模式当高度大于宽度时,都显示less模式
58 |
59 | * 1.less have 7 item
60 | * 2.more have 9 item
61 |
62 |
63 |
64 | ### How to use? ###
65 |
66 | - 默认创建的 new StackCardLayoutManager 正序和入栈模式,
67 | 你可以这样改变它们
68 |
69 | ``` java
70 | StackCardLayoutManager stackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.VERTICAL,true, new StackCardPostLayout());
71 | stackCardLayoutManager.setStackOrder(StackCardLayoutManager.OUT_STACK_ORDER);
72 | stackCardLayoutManager.setNumberOrder(StackCardLayoutManager.NEGATIVE_ORDER);
73 | recyclerView.setLayoutManager(stackCardLayoutManager);
74 | ```
75 |
76 | - 添加滑动事件swip listener
77 |
78 |
79 | ``` java
80 | ItemTouchHelperCallBack itemTouchHelperCallBack = new ItemTouchHelperCallBack();
81 | itemTouchHelperCallBack.setOnSwipListener(swipListener);
82 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallBack);
83 | itemTouchHelper.attachToRecyclerView(recyclerView);
84 | ```
85 |
86 | - 添加自动对齐和点击移动事件
87 |
88 |
89 |
90 | ``` java
91 | // enable center post touching on item and item click listener
92 | DefaultChildSelectionListener.initCenterItemListener(new DefaultChildSelectionListener.OnCenterItemClickListener() {
93 | @Override
94 | public void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v) {
95 | final int position = recyclerView.getChildLayoutPosition(v);
96 | final String msg = String.format(Locale.US, "Item %1$d was clicked", position);
97 | Log.d("onCenterItemClicked", msg);
98 | Toast.makeText(BaseActivity.this, msg, Toast.LENGTH_SHORT).show();
99 | }
100 | }, recyclerView, layoutManager);
101 | ```
102 |
103 | ### TODO ###
104 |
105 | * 1.在5.0以下的过度绘制(我使用的 view.setClipBounds(rect) 这个方法来避免过度绘制,但是在5.0以下没有效果 )
106 | * 2.需要在滑动起来时速度更快
107 |
108 | #### License ####
109 |
110 | Copyright 2016 ckenergy <2ckenergy@gmail.com>
111 | Licensed under the Apache License, Version 2.0 (the "License");
112 | you may not use this file except in compliance with the License.
113 | You may obtain a copy of the License at
114 |
115 | http://www.apache.org/licenses/LICENSE-2.0
116 |
117 | Unless required by applicable law or agreed to in writing, software
118 | distributed under the License is distributed on an "AS IS" BASIS,
119 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120 | See the License for the specific language governing permissions and
121 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StackCardRecyclerView
2 | [中文版REANDME](./README-CN.md)
3 |
4 |
5 | this is RecyclerView like 6.0 Recent task
6 |
7 |
8 |
9 |
10 | ## View GIF ##
11 |
12 |
13 |
14 |
15 |
16 | thanks for [CarouselLayoutManager](https://github.com/Azoft/CarouselLayoutManager) , I use his framework.
17 |
18 | ### dependencies ###
19 | ##### Maven Central #####
20 | ```xml
21 |
22 | com.ckenergy.stackcardlayoutmanager
23 | stackcardlayoutmanager
24 | 1.0.1
25 | pom
26 |
27 | ```
28 | ##### Gradle #####
29 | ```
30 | compile 'com.ckenergy.stackcardlayoutmanager:stackcardlayoutmanager:1.0.1'
31 | ```
32 | ##### Ivy #####
33 | ```xml
34 |
35 |
36 |
37 | ```
38 |
39 | ### There have two type Vertical and Horizontal ###
40 |
41 |
42 |
43 | ### Any Type have two stack order(in stack and out stack) and two number order(positive and negative) ###
44 |
45 | #### Example Horizontal not circle ####
46 |
47 | * 1.in stack order, positive
48 | * 2.out stack order, positive
49 |
50 |
51 |
52 | * 3.in stack order, negative
53 | * 4.out stack order, negative
54 |
55 |
56 |
57 |
58 | ### There also have two number count type(less and more) for Vertical and Horizontal ###
59 |
60 | It will be less count when width is bigger than height in Vertical layout and height is bigger than width is Horizontal layout.
61 |
62 | * 1.less have 7 item
63 | * 2.more have 9 item
64 |
65 |
66 |
67 | ### How to use? ###
68 |
69 | - new StackCardLayoutManager default is in stack and positive,
70 | you can change this order
71 |
72 | ``` java
73 | StackCardLayoutManager stackCardLayoutManager = new StackCardLayoutManager(StackCardLayoutManager.VERTICAL,true, new StackCardPostLayout());
74 | stackCardLayoutManager.setStackOrder(StackCardLayoutManager.OUT_STACK_ORDER);
75 | stackCardLayoutManager.setNumberOrder(StackCardLayoutManager.NEGATIVE_ORDER);
76 | recyclerView.setLayoutManager(stackCardLayoutManager);
77 | ```
78 |
79 | - add swip and swip listener
80 |
81 |
82 |
83 | ``` java
84 | ItemTouchHelperCallBack itemTouchHelperCallBack = new ItemTouchHelperCallBack();
85 | itemTouchHelperCallBack.setOnSwipListener(swipListener);
86 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallBack);
87 | itemTouchHelper.attachToRecyclerView(recyclerView);
88 | ```
89 |
90 | - add scroll to position and center listener
91 |
92 |
93 |
94 | ``` java
95 | // enable center post touching on item and item click listener
96 | DefaultChildSelectionListener.initCenterItemListener(new DefaultChildSelectionListener.OnCenterItemClickListener() {
97 | @Override
98 | public void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v) {
99 | final int position = recyclerView.getChildLayoutPosition(v);
100 | final String msg = String.format(Locale.US, "Item %1$d was clicked", position);
101 | Log.d("onCenterItemClicked", msg);
102 | Toast.makeText(BaseActivity.this, msg, Toast.LENGTH_SHORT).show();
103 | }
104 | }, recyclerView, layoutManager);
105 | ```
106 |
107 | ### TODO ###
108 |
109 | * 1.below LOLLIPOP overDraw need fix (i use view.setClipBounds(rect) but in below SDK LOLLIPOP is not work )
110 | * 2.need more faster (although it have fast scroll,when i scroll more faster the view aren't so fast)
111 | * 3.my english : )
112 |
113 | #### License ####
114 |
115 | Copyright 2016 ckenergy <2ckenergy@gmail.com>
116 | Licensed under the Apache License, Version 2.0 (the "License");
117 | you may not use this file except in compliance with the License.
118 | You may obtain a copy of the License at
119 |
120 | http://www.apache.org/licenses/LICENSE-2.0
121 |
122 | Unless required by applicable law or agreed to in writing, software
123 | distributed under the License is distributed on an "AS IS" BASIS,
124 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
125 | See the License for the specific language governing permissions and
126 | limitations under the License.
--------------------------------------------------------------------------------
/app/src/main/java/com/ckenergy/stackcard/sample/BaseActivity.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.sample;
2 |
3 | import android.util.Log;
4 | import android.view.Menu;
5 | import android.view.MenuItem;
6 | import android.view.View;
7 | import android.widget.Toast;
8 |
9 | import androidx.annotation.NonNull;
10 | import androidx.appcompat.app.AppCompatActivity;
11 | import androidx.recyclerview.widget.ItemTouchHelper;
12 | import androidx.recyclerview.widget.RecyclerView;
13 |
14 | import com.ckenergy.stackcard.stackcardlayoutmanager.CenterScrollListener;
15 | import com.ckenergy.stackcard.stackcardlayoutmanager.DefaultChildSelectionListener;
16 | import com.ckenergy.stackcard.stackcardlayoutmanager.ItemTouchHelperCallBack;
17 | import com.ckenergy.stackcard.stackcardlayoutmanager.StackCardLayoutManager;
18 |
19 | import java.util.ArrayList;
20 | import java.util.List;
21 | import java.util.Locale;
22 |
23 | public class BaseActivity extends AppCompatActivity {
24 |
25 | List managers = new ArrayList<>();
26 |
27 | public void initRecyclerView(final RecyclerView recyclerView, final StackCardLayoutManager layoutManager, final RecyclerView.Adapter adapter) {
28 |
29 | managers.add(layoutManager);
30 |
31 | recyclerView.setLayoutManager(layoutManager);
32 | // we expect only fixed sized item for now
33 | recyclerView.setHasFixedSize(true);
34 | // sample adapter with random data
35 | recyclerView.setAdapter(adapter);
36 | // enable center post scrolling
37 | recyclerView.addOnScrollListener(new CenterScrollListener());
38 |
39 | /**
40 | * remove item
41 | * http://blog.csdn.net/u012943767/article/details/51670757
42 | */
43 | if (adapter instanceof ItemTouchHelperCallBack.onSwipListener) {
44 | ItemTouchHelperCallBack.onSwipListener swipListener = (ItemTouchHelperCallBack.onSwipListener) adapter;
45 | ItemTouchHelperCallBack itemTouchHelperCallBack = new ItemTouchHelperCallBack();
46 | itemTouchHelperCallBack.setOnSwipListener(swipListener);
47 |
48 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(itemTouchHelperCallBack);
49 | itemTouchHelper.attachToRecyclerView(recyclerView);
50 | }
51 |
52 | // enable center post touching on item and item click listener
53 | DefaultChildSelectionListener.initCenterItemListener(new DefaultChildSelectionListener.OnCenterItemClickListener() {
54 | @Override
55 | public void onCenterItemClicked(@NonNull final RecyclerView recyclerView, @NonNull final StackCardLayoutManager stackCardLayoutManager, @NonNull final View v) {
56 | final int position = recyclerView.getChildLayoutPosition(v);
57 | final String msg = String.format(Locale.US, "Item %1$d was clicked", position);
58 | Log.d("onCenterItemClicked", msg);
59 | Toast.makeText(BaseActivity.this, msg, Toast.LENGTH_SHORT).show();
60 | }
61 | }, recyclerView, layoutManager);
62 |
63 | layoutManager.addOnItemSelectionListener(new StackCardLayoutManager.OnCenterItemSelectionListener() {
64 |
65 | @Override
66 | public void onCenterItemChanged(final int adapterPosition) {
67 | if (StackCardLayoutManager.INVALID_POSITION != adapterPosition) {
68 | // final int value = adapter.cards.get(adapterPosition).mPosition;
69 | /*
70 | adapter.mPosition[adapterPosition] = (value % 10) + (value / 10 + 1) * 10;
71 | adapter.notifyItemChanged(adapterPosition);
72 | */
73 | }
74 | }
75 | });
76 | }
77 |
78 | @Override
79 | public boolean onCreateOptionsMenu(Menu menu) {
80 | getMenuInflater().inflate(R.menu.menu,menu);
81 | return true;
82 | }
83 |
84 | @Override
85 | public boolean onOptionsItemSelected(MenuItem item) {
86 |
87 | int stackOrder = 0; // = StackCardLayoutManager.IN_STACK_ORDER;
88 | int numberOrder = 0;
89 | int stringID = 0;
90 | switch (item.getItemId()) {
91 | case R.id.stack:
92 | if(managers.get(0).getStackOrder() == StackCardLayoutManager.IN_STACK_ORDER) {
93 | stackOrder = StackCardLayoutManager.OUT_STACK_ORDER;
94 | stringID = R.string.out_stack;
95 | }else {
96 | stackOrder = StackCardLayoutManager.IN_STACK_ORDER;
97 | stringID = R.string.in_stack;
98 | }
99 | break;
100 | case R.id.number:
101 | if(managers.get(0).getNumberOrder() == StackCardLayoutManager.POSITIVE_ORDER) {
102 | numberOrder = StackCardLayoutManager.NEGATIVE_ORDER;
103 | stringID = R.string.negative;
104 | }else {
105 | numberOrder = StackCardLayoutManager.POSITIVE_ORDER;
106 | stringID = R.string.positive;
107 | }
108 | break;
109 | }
110 | Toast.makeText(this,getString(stringID),Toast.LENGTH_SHORT).show();
111 |
112 | for (StackCardLayoutManager manager : managers) {
113 | if (stackOrder != 0) {
114 | manager.setStackOrder(stackOrder);
115 | }
116 | if (numberOrder != 0) {
117 | manager.setNumberOrder(numberOrder);
118 | }
119 | }
120 | return true;
121 |
122 | }
123 |
124 | }
125 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/ItemTouchHelperCallBack.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.graphics.Rect;
4 | import android.os.Build;
5 | import android.util.Log;
6 | import android.view.View;
7 |
8 | import androidx.core.view.ViewCompat;
9 | import androidx.recyclerview.widget.ItemTouchHelper;
10 | import androidx.recyclerview.widget.RecyclerView;
11 |
12 | /**
13 | * Created by chengkai on 2016/11/28.
14 | */
15 | public class ItemTouchHelperCallBack extends ItemTouchHelper.Callback {
16 |
17 | private onSwipListener mOnSwipListener;
18 |
19 | private boolean isStartSwip = false;
20 |
21 | private Rect beforeCurrentRect;
22 | private Rect beforeBelowRect;
23 |
24 | private View belowView;
25 |
26 | @Override
27 | public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
28 | // Log.d(getClass().getSimpleName(),"getMovementFlags");
29 | if (!(recyclerView.getLayoutManager() instanceof StackCardLayoutManager)) {
30 | return makeMovementFlags(0, 0);
31 | }
32 | final int dragFlags;
33 | final int swipeFlags;
34 | StackCardLayoutManager layoutManager = (StackCardLayoutManager) recyclerView.getLayoutManager();
35 | dragFlags = 0;
36 | if (layoutManager.getOrientation() == StackCardLayoutManager.HORIZONTAL) {
37 | swipeFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
38 | }else {
39 | swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
40 | }
41 |
42 | /**
43 | * below the {@link Build.VERSION_CODES.LOLLIPOP} view.setClipBounds(rect) is not work
44 | * so we needn't Calculation the rect
45 | */
46 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
47 | if (!isStartSwip) {
48 | beforeCurrentRect = ViewCompat.getClipBounds(viewHolder.itemView);
49 | isStartSwip = true;
50 | int current = viewHolder.getAdapterPosition();
51 | // Log.d(getClass().getSimpleName(),"current:"+current);
52 | Rect rect = new Rect(0, 0, viewHolder.itemView.getWidth(), viewHolder.itemView.getHeight());
53 | ViewCompat.setClipBounds(viewHolder.itemView, rect);
54 | int belowPosition;
55 | if (layoutManager.getStackOrder() == StackCardLayoutManager.IN_STACK_ORDER) {
56 | belowPosition = current - 1;
57 | }else {
58 | belowPosition = current + 1;
59 | }
60 | belowView = layoutManager.findViewByPosition(belowPosition);
61 | if(belowView != null) {
62 | beforeBelowRect = ViewCompat.getClipBounds(belowView);
63 | Rect belowRect;
64 | if (layoutManager.getOrientation() == StackCardLayoutManager.HORIZONTAL) {
65 | float scale = viewHolder.itemView.getScaleY() / belowView.getScaleY();
66 | int width = Math.round(beforeCurrentRect.width()*scale)+beforeBelowRect.width();
67 | if (layoutManager.getStackOrder() * layoutManager.getNumberOrder() < 0) {
68 | belowRect = new Rect(belowView.getWidth()-width, 0, belowView.getWidth(), beforeBelowRect.height());
69 | }else {
70 | belowRect = new Rect(0, 0, width, beforeBelowRect.height());
71 | }
72 | }else {
73 | float scale = viewHolder.itemView.getScaleX()/belowView.getScaleX();
74 | int height = Math.round(beforeCurrentRect.height()*scale)+beforeBelowRect.height();
75 | if (layoutManager.getStackOrder() * layoutManager.getNumberOrder() < 0) {
76 | belowRect = new Rect(0, belowView.getHeight()-height, belowView.getWidth(), belowView.getHeight());
77 | }else {
78 | belowRect = new Rect(0, 0, beforeBelowRect.width(), height);
79 | }
80 | }
81 | ViewCompat.setClipBounds(belowView, belowRect);
82 | }
83 | }
84 | }
85 |
86 | return makeMovementFlags(dragFlags, swipeFlags);
87 | }
88 |
89 | @Override
90 | public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
91 | super.clearView(recyclerView, viewHolder);
92 | Log.d(getClass().getSimpleName(),"clearView:"+viewHolder.getAdapterPosition());
93 | if (isStartSwip) {
94 | isStartSwip = false;
95 | if (viewHolder.getAdapterPosition() >= 0 ) {
96 | if (belowView != null) {
97 | ViewCompat.setClipBounds(belowView, beforeBelowRect);
98 | }
99 | ViewCompat.setClipBounds(viewHolder.itemView, beforeCurrentRect);
100 | }
101 | }
102 | }
103 |
104 | @Override
105 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
106 | Log.d(getClass().getSimpleName(),"onMove");
107 | return false;
108 | }
109 |
110 | public onSwipListener getOnSwipListener() {
111 | return mOnSwipListener;
112 | }
113 |
114 | public void setOnSwipListener(onSwipListener onSwipListener) {
115 | this.mOnSwipListener = onSwipListener;
116 | }
117 |
118 | @Override
119 | public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
120 | // Log.d(getClass().getSimpleName(),"onSwiped,direction:"+direction);
121 | int position = viewHolder.getAdapterPosition();
122 | if (mOnSwipListener != null) {
123 | mOnSwipListener.onSwip(viewHolder,position);
124 | }
125 | }
126 |
127 | public interface onSwipListener {
128 | void onSwip(RecyclerView.ViewHolder viewHolder, int position);
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/StackCardPostLayout.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.os.Build;
4 |
5 | import androidx.annotation.NonNull;
6 | import androidx.recyclerview.widget.RecyclerView;
7 |
8 | /**
9 | * Implementation of {@link StackCardLayoutManager.IPostLayout} that makes interesting scaling of items.
10 | */
11 | public class StackCardPostLayout implements StackCardLayoutManager.IPostLayout {
12 |
13 | private static final int BASE = 40;
14 |
15 | private boolean isLessType = false;
16 |
17 | private int smallDistance;
18 | private int mediumDistance;
19 | private int bigDistance;
20 |
21 | public int getCenterViewOffset(@NonNull StackCardLayoutManager layoutManager, int length) {
22 | int mediumCount = isLessType ? 1 : 2;
23 |
24 | /**
25 | * need show 3.5 SmallDistance item and (isLessType ? 1 : 2) MediumDistance item
26 | */
27 | float centerStartRatio = mediumCount / getMediumDistanceRatio() + 3.5f / getSmallDistanceRatio();
28 | int centerViewStart = (int) (length*centerStartRatio);
29 | if (layoutManager.getStackOrder() * layoutManager.getNumberOrder() < 0) {
30 | centerViewStart = length-layoutManager.getScrollItemSize()-centerViewStart;
31 | }
32 |
33 | smallDistance = Math.round(length/getSmallDistanceRatio()/layoutManager.getBaseScale());
34 | mediumDistance = Math.round(length/getMediumDistanceRatio()/layoutManager.getBaseScale());
35 | bigDistance = Math.round(length/getBigDistanceRatio()/layoutManager.getBaseScale());
36 | return centerViewStart;
37 | }
38 |
39 |
40 | /**
41 | *
42 | * make sure the childview is smaller than parent
43 | *
44 | * @param layoutManager this
45 | * @param orientation layoutManager orientation {@link RecyclerView#getLayoutDirection()}
46 | * @return
47 | */
48 | @Override
49 | public float getBaseScale(@NonNull StackCardLayoutManager layoutManager, int orientation) {
50 | // Log.d(getClass().getSimpleName(),"getBaseScale");
51 | int parentHeight = layoutManager.getHeightNoPadding();
52 | int parentWidth = layoutManager.getWidthNoPadding();
53 | float baseScale = 1;
54 | int mDecoratedChildWidth = layoutManager.getDecoratedChildWidth();
55 | int mDecoratedChildHeight = layoutManager.getDecoratedChildHeight();
56 |
57 | /**
58 | * it is auto scale child view when it is bigger than parent
59 | */
60 | if(orientation == StackCardLayoutManager.VERTICAL && parentHeight < parentWidth) {
61 | if (parentHeight < mDecoratedChildWidth) {
62 | baseScale = 1.0f*parentHeight/mDecoratedChildWidth;
63 | }
64 | layoutManager.setMaxVisibleItems(2);
65 | isLessType = true;
66 | }else if(orientation == StackCardLayoutManager.HORIZONTAL) {
67 | if (parentHeight < mDecoratedChildHeight) {
68 | baseScale = 1.0f*parentHeight/mDecoratedChildHeight;
69 | }
70 | if (parentWidth < parentHeight) {
71 | layoutManager.setMaxVisibleItems(2);
72 | isLessType = true;
73 | }
74 | }
75 |
76 | return baseScale;
77 | }
78 |
79 | @Override
80 | public int getCenterViewStartOffset(@NonNull StackCardLayoutManager layoutManager, int orientation) {
81 | // Log.d(getClass().getSimpleName(),"getCenterViewStartOffset");
82 | int length ;
83 | if (orientation == StackCardLayoutManager.VERTICAL) {
84 | length = layoutManager.getHeightNoPadding();
85 | }else {
86 | length = layoutManager.getWidthNoPadding();
87 | }
88 | return getCenterViewOffset(layoutManager, length);
89 | }
90 |
91 | @Override
92 | public ItemTransformation transformChild(@NonNull final StackCardLayoutManager layoutManager, final float itemPositionToCenterDiff, final int orientation) {
93 | // Log.d(getClass().getSimpleName(),"transformChild");
94 | int base = BASE;
95 | float itemDiff = Math.abs(itemPositionToCenterDiff-2.5f);
96 | final float scale = Math.min((base-itemDiff)/base, 1);
97 | // Log.d("StackCardZoomPostLayoutListener","itemPositionToCenterDiff:"+itemPositionToCenterDiff);
98 | int translateY;
99 | int translateX;
100 | int height;
101 | int width;
102 | width = layoutManager.getWidthNoPadding();
103 | height = layoutManager.getHeightNoPadding();
104 | int changePosition = isLessType ? 1 : 2;
105 | float ratio;
106 |
107 | /**
108 | * below the {@link Build.VERSION_CODES.LOLLIPOP} view.setClipBounds(rect) is not work
109 | * so we needn't Calculation the rect
110 | */
111 | int clipLength;
112 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
113 | if(itemPositionToCenterDiff <= -changePosition-1) {
114 | clipLength = smallDistance;
115 | }else if(itemPositionToCenterDiff <= -1 && itemPositionToCenterDiff > -changePosition-1) {
116 | clipLength = mediumDistance;
117 | }else {
118 | clipLength = bigDistance;
119 | }
120 | clipLength = Math.round(clipLength/scale);
121 | if (isLessType && itemPositionToCenterDiff >= 1) {
122 | clipLength = -1;
123 | }
124 | }else {
125 | clipLength = -1;
126 | }
127 | /**
128 | * use the x instead of itemPositionToCenterDiff ,y instead of ratio
129 | * isLessType == false changePosition = 2 the x range(-6,2)
130 | * 1.when (x <= -2) i need it closeest each other so derivatives y' = 1/25 , y = (x+a)/25
131 | * when it close to -2 the y need close to -2/6 ,because when (x > -2 && x < 0) the function is y = x/6,
132 | * the x close to -2 the y is close to -2/6, so it must successive , finial in (x <= -2) the function is y = (x+2)/25-2/6
133 | *
134 | * 2.when (x > -2 && x < 0) i need it closer each other so derivatives y' = 1/6 , y = (x+a)/6
135 | * when it close to 0 the y need close to 0, the function is y = x/6
136 | *
137 | * 3.when ( x > 0) i need it closer each other so derivatives y' = 1/2 , y = (x+a)/2
138 | * the function is y = x/2
139 | *
140 | * isLessType == true changePosition = 1 the x range(-5,1)
141 | * there is different at moreType the MediumDistanceRatio only one item
142 | *
143 | */
144 | if (itemPositionToCenterDiff <= -changePosition) {
145 | ratio = ((itemPositionToCenterDiff+changePosition) / getSmallDistanceRatio()
146 | -changePosition/getMediumDistanceRatio());
147 | }else if (itemPositionToCenterDiff <= 0) {
148 | ratio = itemPositionToCenterDiff / getMediumDistanceRatio();
149 | }else {
150 | ratio = itemPositionToCenterDiff / getBigDistanceRatio();
151 | }
152 | if (StackCardLayoutManager.VERTICAL == orientation) {
153 | translateY = Math.round(height * ratio);
154 | translateX = 0;
155 | } else {
156 | translateX = Math.round(width * ratio);
157 | translateY = 0;
158 | }
159 | float alpha = 1;
160 | int transparentPosition = isLessType ? -5 : -6;
161 | if(itemPositionToCenterDiff > transparentPosition && itemPositionToCenterDiff < -changePosition) {
162 | alpha = itemPositionToCenterDiff/4f-transparentPosition/4f;
163 | }else if(itemPositionToCenterDiff <= transparentPosition) {
164 | alpha = 0;
165 | }
166 |
167 | // Log.d("StackCardPostLayout", "itemPositionToCenterDiff:"+itemPositionToCenterDiff+",alpha:"+alpha);
168 | return new ItemTransformation(scale, scale, translateX, translateY, clipLength, alpha);
169 | }
170 |
171 | public float getSmallDistanceRatio() {
172 | if (isLessType) {
173 | return 25f;
174 | }else {
175 | return 35f;
176 | }
177 | }
178 |
179 | public float getMediumDistanceRatio() {
180 | if (isLessType) {
181 | return 6f;
182 | }else {
183 | return 12f;
184 | }
185 | }
186 |
187 | public float getBigDistanceRatio() {
188 | if (isLessType) {
189 | return 2f;
190 | }else {
191 | return 2.5f;
192 | }
193 | }
194 | }
--------------------------------------------------------------------------------
/stackcardlayoutmanager/src/main/java/com/ckenergy/stackcard/stackcardlayoutmanager/StackCardLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.ckenergy.stackcard.stackcardlayoutmanager;
2 |
3 | import android.annotation.TargetApi;
4 | import android.graphics.PointF;
5 | import android.graphics.Rect;
6 | import android.os.Build;
7 | import android.os.Handler;
8 | import android.os.Looper;
9 | import android.os.Parcel;
10 | import android.os.Parcelable;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 |
14 | import androidx.annotation.CallSuper;
15 | import androidx.annotation.NonNull;
16 | import androidx.annotation.Nullable;
17 | import androidx.core.view.ViewCompat;
18 | import androidx.recyclerview.widget.OrientationHelper;
19 | import androidx.recyclerview.widget.RecyclerView;
20 |
21 | import java.lang.ref.WeakReference;
22 | import java.util.ArrayList;
23 | import java.util.Iterator;
24 | import java.util.List;
25 |
26 | /**
27 | * An implementation of {@link RecyclerView.LayoutManager} that layout items like Stack Card.
28 | * Generally there is one center item and bellow this item there are maximum {@link StackCardLayoutManager#getMaxVisibleItems()} items on each side of the center
29 | * item. By default {@link StackCardLayoutManager#getMaxVisibleItems()} is {@link StackCardLayoutManager#MAX_VISIBLE_ITEMS}.
30 | *
31 | * This LayoutManager supports only fixedSized adapter items.
32 | *
33 | * This LayoutManager supports {@link StackCardLayoutManager#HORIZONTAL} and {@link StackCardLayoutManager#VERTICAL} orientations.
34 | *
35 | * This LayoutManager supports circle layout. By default it if disabled. We don't recommend to use circle layout with adapter items count less then 3.
36 | *
37 | * Please be sure that layout_width of adapter item is a constant value and not {@link ViewGroup.LayoutParams#MATCH_PARENT}
38 | * for {@link #HORIZONTAL} orientation.
39 | * So like layout_height is not {@link ViewGroup.LayoutParams#MATCH_PARENT} for {@link StackCardLayoutManager#VERTICAL}
40 | *
41 | */
42 | @SuppressWarnings({"ClassWithTooManyMethods", "OverlyComplexClass"})
43 | public class StackCardLayoutManager extends RecyclerView.LayoutManager {
44 |
45 | private static final String TAG = "StackCardLayoutManager";
46 |
47 | public static final int HORIZONTAL = OrientationHelper.HORIZONTAL;
48 | public static final int VERTICAL = OrientationHelper.VERTICAL;
49 |
50 | // public static final int
51 |
52 | public static final int INVALID_POSITION = -1;
53 | public static final int MAX_VISIBLE_ITEMS = 3;
54 |
55 | /**
56 | * small number in the bottom,
57 | */
58 | public static final int IN_STACK_ORDER = 1;
59 | /**
60 | * big number in the bottom,
61 | */
62 | public static final int OUT_STACK_ORDER = -1;
63 |
64 | /**
65 | * left to right, top to bottom
66 | */
67 | public static final int NEGATIVE_ORDER = -1;
68 | /**
69 | * right to left, bottom to top
70 | */
71 | public static final int POSITIVE_ORDER = 1;
72 |
73 | private int mStackOrder;
74 |
75 | private int mNumberOrder;
76 |
77 | private boolean mIsRemoveLayout;
78 |
79 | private int mRemoveBelowPosition;
80 |
81 | private List mRemoveList;
82 |
83 | private RecyclerView mRecyclerView;
84 |
85 |
86 | // private int mRemoveBelowPosition;
87 |
88 | // public static final float SMALL_DISTANCE_RATIO = 35f;
89 | // public static final float MEDIUM_DISTANCE_RATIO = 12f;
90 | // public static final float BIG_DISTANCE_RATIO = 2.5f;
91 |
92 | // public static final float SMALL_DISTANCE_RATIO = 25f;
93 | // public static final float MEDIUM_DISTANCE_RATIO = 6f;
94 | // public static final float BIG_DISTANCE_RATIO = 2f;
95 |
96 | private static final boolean CIRCLE_LAYOUT = false;
97 |
98 | // private int layoutCountType;
99 |
100 | private Integer mDecoratedChildWidth;
101 | private Integer mDecoratedChildHeight;
102 |
103 | private final int mOrientation;
104 | private final boolean mCircleLayout;
105 |
106 | private int mPendingScrollPosition;
107 |
108 | private final LayoutHelper mLayoutHelper = new LayoutHelper(MAX_VISIBLE_ITEMS);
109 |
110 | private IPostLayout mViewPostLayout;
111 |
112 | private final List mOnCenterItemSelectionListeners = new ArrayList<>();
113 | private int mCenterItemPosition = INVALID_POSITION;
114 | private int mItemsCount;
115 |
116 | private StackCardSavedState mPendingStackCardSavedState;
117 |
118 | private float mBaseScale;
119 |
120 | private int mCenterViewStart;
121 |
122 | private int mOverScrollMode;
123 |
124 | /**
125 | * implements {@link IPostLayout} to how layout item
126 | * @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
127 | */
128 | public StackCardLayoutManager(final int orientation, @NonNull IPostLayout iPostLayout) {
129 | this(orientation, CIRCLE_LAYOUT, iPostLayout);
130 | }
131 |
132 | /**
133 | * If circleLayout is true then all items will be in cycle. Scroll will be infinite on both sides.
134 | *
135 | * @param orientation should be {@link #VERTICAL} or {@link #HORIZONTAL}
136 | * @param circleLayout true for enabling circleLayout
137 | */
138 | public StackCardLayoutManager(final int orientation, final boolean circleLayout, @NonNull IPostLayout iPostLayout) {
139 | this(orientation, circleLayout, IN_STACK_ORDER, iPostLayout);
140 | }
141 |
142 | public StackCardLayoutManager(final int orientation, final boolean circleLayout,int stackOrder, @NonNull IPostLayout iPostLayout) {
143 | this(orientation, circleLayout, stackOrder, POSITIVE_ORDER, iPostLayout);
144 | }
145 |
146 | public StackCardLayoutManager(final int orientation, final boolean circleLayout,int stackOrder, int numberOrder, @NonNull IPostLayout iPostLayout) {
147 | if (HORIZONTAL != orientation && VERTICAL != orientation) {
148 | throw new IllegalArgumentException("orientation should be HORIZONTAL or VERTICAL");
149 | }
150 | mOrientation = orientation;
151 | mCircleLayout = circleLayout;
152 | mStackOrder = (int) Math.signum(stackOrder);
153 | mNumberOrder = (int) Math.signum(numberOrder);
154 | mPendingScrollPosition = INVALID_POSITION;
155 | this.mViewPostLayout = iPostLayout;
156 | }
157 |
158 | public float getBaseScale() {
159 | return mBaseScale;
160 | }
161 |
162 | /**
163 | * set the layout stack Order
164 | *
165 | * @return type of {@link StackCardLayoutManager#IN_STACK_ORDER} or {@link StackCardLayoutManager#IN_STACK_ORDER}
166 | */
167 | public int getStackOrder() {
168 | return mStackOrder;
169 | }
170 |
171 | /**
172 | * set the layout stack Order
173 | *
174 | * @param stackOrder type of {@link StackCardLayoutManager#IN_STACK_ORDER} or {@link StackCardLayoutManager#IN_STACK_ORDER}
175 | */
176 | public void setStackOrder(int stackOrder) {
177 | if (this.mStackOrder == stackOrder) {
178 | return;
179 | }
180 | this.mStackOrder = (int) Math.signum(stackOrder);
181 | requestLayout();
182 | }
183 |
184 | /**
185 | * get the layout number order
186 | *
187 | * @return type of {@link StackCardLayoutManager#NEGATIVE_ORDER} or {@link StackCardLayoutManager#POSITIVE_ORDER}
188 | */
189 | public int getNumberOrder() {
190 | return mNumberOrder;
191 | }
192 |
193 | /**
194 | * set the layout number order
195 | *
196 | * @param numberOrder type of {@link StackCardLayoutManager#NEGATIVE_ORDER} or {@link StackCardLayoutManager#POSITIVE_ORDER}
197 | */
198 | public void setNumberOrder(int numberOrder) {
199 | if (this.mNumberOrder == numberOrder) {
200 | return;
201 | }
202 | this.mNumberOrder = (int) Math.signum(numberOrder);
203 | requestLayout();
204 | }
205 |
206 | /**
207 | * Setup {@link IPostLayout} for this LayoutManager.
208 | * Its methods will be called for each visible view item after general LayoutManager layout finishes.
209 | *
210 | * Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
211 | *
212 | * @param postLayout listener for item layout changes. Can be null.
213 | */
214 | public void setPostLayoutListener(@Nullable final IPostLayout postLayout) {
215 | if (postLayout == null) {
216 | return;
217 | }
218 | mViewPostLayout = postLayout;
219 | requestLayout();
220 | }
221 |
222 | /**
223 | * Setup maximum visible (layout) items on each side of the center item.
224 | * Basically during scrolling there can be more visible items (+1 item on each side), but in idle state this is the only reached maximum.
225 | *
226 | * @param maxVisibleItems should be great then 0, if bot an {@link IllegalAccessException} will be thrown
227 | */
228 | @CallSuper
229 | public void setMaxVisibleItems(final int maxVisibleItems) {
230 | if (0 >= maxVisibleItems) {
231 | throw new IllegalArgumentException("maxVisibleItems can't be less then 1");
232 | }
233 | if (mLayoutHelper.mMaxVisibleItems == maxVisibleItems) {
234 | return;
235 | }
236 | mLayoutHelper.mMaxVisibleItems = maxVisibleItems;
237 | requestLayout();
238 | }
239 |
240 | /**
241 | * @return current setup for maximum visible items.
242 | * @see #setMaxVisibleItems(int)
243 | */
244 | public int getMaxVisibleItems() {
245 | return mLayoutHelper.mMaxVisibleItems;
246 | }
247 |
248 | @Override
249 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
250 | return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
251 | }
252 |
253 | /**
254 | * @return current layout orientation
255 | * @see #VERTICAL
256 | * @see #HORIZONTAL
257 | */
258 | public int getOrientation() {
259 | return mOrientation;
260 | }
261 |
262 | @Override
263 | public boolean canScrollHorizontally() {
264 | return 0 != getChildCount() && HORIZONTAL == mOrientation;
265 | }
266 |
267 | @Override
268 | public boolean canScrollVertically() {
269 | return 0 != getChildCount() && VERTICAL == mOrientation;
270 | }
271 |
272 | @Override
273 | public void onAttachedToWindow(final RecyclerView recyclerView) {
274 | super.onAttachedToWindow(recyclerView);
275 | mRecyclerView = recyclerView;
276 | mOverScrollMode = recyclerView.getOverScrollMode();
277 | recyclerView.setOverScrollMode(View.OVER_SCROLL_NEVER);//remove the can't scroll effect
278 | // Log.d(TAG,"sdk:"+Build.VERSION.SDK_INT);
279 | // mRecyclerView.getItemAnimator().setMoveDuration(1000);
280 | }
281 |
282 | @Override
283 | public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
284 | super.onDetachedFromWindow(view, recycler);
285 | mRecyclerView.setOverScrollMode(mOverScrollMode);
286 | mRecyclerView = null;
287 | }
288 |
289 | @TargetApi(Build.VERSION_CODES.LOLLIPOP)
290 | private void setOutLine(RecyclerView recyclerView) {
291 | ChildViewOutlineProvider outlineProvider = new ChildViewOutlineProvider();
292 | int count = recyclerView.getChildCount();
293 | for(int i = 0; i position) {
326 | throw new IllegalArgumentException("position can't be less then 0. position is : " + position);
327 | }
328 | mPendingScrollPosition = position;
329 | // Log.d(getClass().getSimpleName(),"position:"+position);
330 | requestLayout();
331 | }
332 |
333 | @SuppressWarnings("RefusedBequest")
334 | @Override
335 | public void smoothScrollToPosition(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.State state, final int position) {
336 |
337 | final StackCardSmoothScroller mySmoothScroller =
338 | new StackCardSmoothScroller(recyclerView.getContext()) {
339 | @Override
340 | public PointF computeScrollVectorForPosition(final int targetPosition) {
341 | if (0 > targetPosition) {
342 | throw new IllegalArgumentException("position can't be less then 0. position is : " + position);
343 | }
344 | if (targetPosition >= state.getItemCount()) {
345 | throw new IllegalArgumentException("position can't be great then adapter items count. position is : " + position);
346 | }
347 | return StackCardLayoutManager.this.computeScrollVectorForPosition(targetPosition);
348 | }
349 | };
350 |
351 | mySmoothScroller.setTargetPosition(position);
352 | startSmoothScroll(mySmoothScroller);
353 | }
354 |
355 | protected PointF computeScrollVectorForPosition(final int targetPosition) {
356 | if (0 == getChildCount()) {
357 | return null;
358 | }
359 |
360 | // Log.d(TAG, "computeScrollVectorForPosition,centerPosition:"+targetPosition);
361 |
362 | final float currentScrollPosition = makeScrollPositionInRange0ToCount(getCurrentScrollPosition(), mItemsCount);
363 | int direction = targetPosition < currentScrollPosition ? -1 : 1;
364 | direction = direction*getNumberOrder();
365 | if (HORIZONTAL == mOrientation) {
366 | return new PointF(direction, 0);
367 | } else {
368 | return new PointF(0, direction);
369 | }
370 | }
371 |
372 | @Override
373 | public int scrollVerticallyBy(final int dy, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
374 | if (HORIZONTAL == mOrientation) {
375 | return 0;
376 | }
377 | return scrollBy(dy, recycler, state);
378 | }
379 |
380 | @Override
381 | public int scrollHorizontallyBy(final int dx, final RecyclerView.Recycler recycler, final RecyclerView.State state) {
382 | if (VERTICAL == mOrientation) {
383 | return 0;
384 | }
385 | return scrollBy(dx, recycler, state);
386 | }
387 |
388 | /**
389 | * This method is called from {@link #scrollHorizontallyBy(int, RecyclerView.Recycler, RecyclerView.State)} and
390 | * {@link #scrollVerticallyBy(int, RecyclerView.Recycler, RecyclerView.State)} to calculate needed scroll that is allowed.
391 | *
392 | * This method may do relayout work.
393 | *
394 | * @param diff distance that we want to scroll by
395 | * @param recycler Recycler to use for fetching potentially cached views for a position
396 | * @param state Transient state of RecyclerView
397 | * @return distance that we actually scrolled by
398 | */
399 | @CallSuper
400 | protected int scrollBy(final int diff, @NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
401 | if (0 == getChildCount() || 0 == diff) {
402 | return 0;
403 | }
404 | final int resultScroll;
405 | if (mCircleLayout) {
406 | resultScroll = diff;
407 |
408 | mLayoutHelper.mScrollOffset += resultScroll;
409 |
410 | final int maxOffset = getScrollItemSize() * mItemsCount;
411 | while (0 > mLayoutHelper.mScrollOffset) {
412 | mLayoutHelper.mScrollOffset += maxOffset;
413 | }
414 | while (mLayoutHelper.mScrollOffset > maxOffset) {
415 | mLayoutHelper.mScrollOffset -= maxOffset;
416 | }
417 |
418 | mLayoutHelper.mScrollOffset -= resultScroll;
419 | } else {
420 |
421 | float currentScrollPosition = getCurrentScrollPosition();
422 | float scale = 1;
423 | if (getNumberOrder() == POSITIVE_ORDER) {
424 | if (diff < 0 && currentScrollPosition <= 0 ) {
425 | if (getStackOrder() == OUT_STACK_ORDER) {
426 | scale = (float) (Math.pow(1.1f,currentScrollPosition))*2;
427 | }else {
428 | scale = (float) (Math.pow(1.1f,currentScrollPosition))/2;
429 | }
430 | }else if (diff > 0 && currentScrollPosition >= mItemsCount-1){
431 | if (getStackOrder() == OUT_STACK_ORDER) {
432 | scale = (float) (Math.pow(1.1f,mItemsCount-1-currentScrollPosition))/2;
433 | }else {
434 | scale = (float) (Math.pow(1.1f,(mItemsCount-1-currentScrollPosition)))*2;
435 | }
436 | }
437 | }else {
438 | if (diff > 0 && currentScrollPosition <= 0 ) {
439 | if (getStackOrder() == OUT_STACK_ORDER) {
440 | scale = (float) (Math.pow(1.1f, currentScrollPosition))*2;
441 | }else {
442 | scale = (float) (Math.pow(1.1f, currentScrollPosition))/2;
443 | }
444 | }else if (diff < 0 && currentScrollPosition >= mItemsCount-1){
445 | if (getStackOrder() == OUT_STACK_ORDER) {
446 | scale = (float) (Math.pow(1.1f,mItemsCount-1-currentScrollPosition))/2;
447 | }else {
448 | scale = (float) (Math.pow(1.1f,(mItemsCount-1-currentScrollPosition)))*2;
449 | }
450 | }
451 | }
452 |
453 | // Log.d(getClass().getSimpleName(),"scale:"+scale+",currentScrollPosition:"+currentScrollPosition);
454 | resultScroll = (int) (diff*scale);
455 | }
456 | // Log.d(getClass().getSimpleName(),"resultScroll:"+resultScroll+",diff:"+diff+",mScrollOffset:"+mLayoutHelper.mScrollOffset);
457 | mLayoutHelper.mScrollOffset += resultScroll*getNumberOrder();
458 | fillData(recycler, state, false);
459 | return resultScroll;
460 | }
461 |
462 | @Override
463 | public void onMeasure(final RecyclerView.Recycler recycler, final RecyclerView.State state, final int widthSpec, final int heightSpec) {
464 | mDecoratedChildHeight = null;
465 | mDecoratedChildWidth = null;
466 | super.onMeasure(recycler, state, widthSpec, heightSpec);
467 | }
468 |
469 | @SuppressWarnings("rawtypes")
470 | @Override
471 | public void onAdapterChanged(final RecyclerView.Adapter oldAdapter, final RecyclerView.Adapter newAdapter) {
472 | super.onAdapterChanged(oldAdapter, newAdapter);
473 |
474 | removeAllViews();
475 | }
476 |
477 | @SuppressWarnings("RefusedBequest")
478 | @Override
479 | @CallSuper
480 | public void onLayoutChildren(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state) {
481 | if (0 == state.getItemCount()) {
482 | removeAndRecycleAllViews(recycler);
483 | selectItemCenterPosition(INVALID_POSITION);
484 | return;
485 | }
486 |
487 | boolean childMeasuringNeeded = false;
488 | if (null == mDecoratedChildWidth) {
489 | final View view = recycler.getViewForPosition(0);
490 | addView(view);
491 | measureChildWithMargins(view, 0, 0);
492 |
493 | mDecoratedChildWidth = getDecoratedMeasuredWidth(view);
494 | mDecoratedChildHeight = getDecoratedMeasuredHeight(view);
495 |
496 | mBaseScale = 1;
497 | if (mViewPostLayout != null) {
498 | mBaseScale = mViewPostLayout.getBaseScale(this, getOrientation());
499 | mCenterViewStart = mViewPostLayout.getCenterViewStartOffset(this, getOrientation());
500 | }
501 |
502 | removeAndRecycleView(view, recycler);
503 |
504 | if (INVALID_POSITION == mPendingScrollPosition && null == mPendingStackCardSavedState) {
505 | mPendingScrollPosition = mCenterItemPosition;
506 | }
507 |
508 | childMeasuringNeeded = true;
509 | }
510 |
511 | if (INVALID_POSITION != mPendingScrollPosition) {
512 | final int itemsCount = state.getItemCount();
513 | mPendingScrollPosition = 0 == itemsCount ? INVALID_POSITION : Math.max(0, Math.min(itemsCount - 1, mPendingScrollPosition));
514 | }
515 | if (INVALID_POSITION != mPendingScrollPosition) {
516 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingScrollPosition, state);
517 | mPendingScrollPosition = INVALID_POSITION;
518 | mPendingStackCardSavedState = null;
519 | } else if (null != mPendingStackCardSavedState) {
520 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mPendingStackCardSavedState.mCenterItemPosition, state);
521 | mPendingStackCardSavedState = null;
522 | } else if (state.didStructureChange() && INVALID_POSITION != mCenterItemPosition) {
523 | mLayoutHelper.mScrollOffset = calculateScrollForSelectingPosition(mCenterItemPosition, state);
524 | }
525 |
526 | fillData(recycler, state, childMeasuringNeeded);
527 | }
528 |
529 | private int calculateScrollForSelectingPosition(final int itemPosition, final RecyclerView.State state) {
530 | final int fixedItemPosition = itemPosition < state.getItemCount() ? itemPosition : state.getItemCount() - 1;
531 | return VERTICAL == mOrientation ? fixedItemPosition * mDecoratedChildHeight : fixedItemPosition * mDecoratedChildWidth;
532 | }
533 |
534 | private void fillData(@NonNull final RecyclerView.Recycler recycler, @NonNull final RecyclerView.State state, final boolean childMeasuringNeeded) {
535 | // Log.d(TAG,"fillData,"+"currentScrollPosition:"+currentScrollPosition);
536 | mItemsCount = state.getItemCount();
537 | final float currentScrollPosition = getCurrentScrollPosition();
538 | // Log.d(TAG,"mItemsCount:"+mItemsCount+",currentScrollPosition:"+currentScrollPosition);
539 | generateLayoutOrder(currentScrollPosition, state);
540 | removeAndRecycleUnusedViews(mLayoutHelper, recycler);
541 |
542 | detectOnItemSelectionChanged(currentScrollPosition, state);
543 |
544 | final int width = getWidthNoPadding();
545 | final int height = getHeightNoPadding();
546 | if (VERTICAL == mOrientation) {
547 | fillDataVertical(recycler, width, height, childMeasuringNeeded);
548 | } else {
549 | fillDataHorizontal(recycler, width, height, childMeasuringNeeded);
550 | }
551 |
552 | recycler.clear();
553 |
554 | }
555 |
556 | private void detectOnItemSelectionChanged(final float currentScrollPosition, final RecyclerView.State state) {
557 | final float absCurrentScrollPosition;
558 | if (mCircleLayout) {
559 | absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, state.getItemCount());
560 | }else {
561 | absCurrentScrollPosition = currentScrollPosition;
562 | }
563 | final int centerItem = Math.round(absCurrentScrollPosition);
564 |
565 | if (mCenterItemPosition != centerItem) {
566 | mCenterItemPosition = centerItem;
567 | new Handler(Looper.getMainLooper()).post(new Runnable() {
568 | @Override
569 | public void run() {
570 | selectItemCenterPosition(centerItem);
571 | }
572 | });
573 | }
574 | }
575 |
576 | private void selectItemCenterPosition(final int centerItem) {
577 | for (final OnCenterItemSelectionListener onCenterItemSelectionListener : mOnCenterItemSelectionListeners) {
578 | onCenterItemSelectionListener.onCenterItemChanged(centerItem);
579 | }
580 | }
581 |
582 | private void fillDataVertical(final RecyclerView.Recycler recycler, final int width, final int height, final boolean childMeasuringNeeded) {
583 | final int start = (width - mDecoratedChildWidth) / 2;
584 | final int end = start + mDecoratedChildWidth;
585 |
586 | for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
587 | final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
588 | final int top = mCenterViewStart;
589 | final int bottom = top + mDecoratedChildHeight;
590 | fillChildItem(start, top, end, bottom, layoutOrder, recycler, i, childMeasuringNeeded);
591 | }
592 | }
593 |
594 | private void fillDataHorizontal(final RecyclerView.Recycler recycler, final int width, final int height, final boolean childMeasuringNeeded) {
595 | final int top = (height - mDecoratedChildHeight) / 2;
596 | final int bottom = top + mDecoratedChildHeight;
597 |
598 | for (int i = 0, count = mLayoutHelper.mLayoutOrder.length; i < count; ++i) {
599 | final LayoutOrder layoutOrder = mLayoutHelper.mLayoutOrder[i];
600 | final int start = mCenterViewStart;
601 | final int end = start + mDecoratedChildWidth;
602 | fillChildItem(start, top, end, bottom, layoutOrder, recycler, i, childMeasuringNeeded);
603 | }
604 | }
605 |
606 | private void removeAndRecycleUnusedViews(final LayoutHelper layoutHelper, final RecyclerView.Recycler recycler) {
607 | final List viewsToRemove = new ArrayList<>();
608 | for (int i = 0, size = getChildCount(); i < size; ++i) {
609 | final View child = getChildAt(i);
610 | final ViewGroup.LayoutParams lp = child.getLayoutParams();
611 | if (!(lp instanceof RecyclerView.LayoutParams)) {
612 | viewsToRemove.add(child);
613 | continue;
614 | }
615 | final RecyclerView.LayoutParams recyclerViewLp = (RecyclerView.LayoutParams) lp;
616 | final int adapterPosition = recyclerViewLp.getViewAdapterPosition();
617 | if (recyclerViewLp.isItemRemoved() || !layoutHelper.hasAdapterPosition(adapterPosition)) {
618 | viewsToRemove.add(child);
619 | }
620 | }
621 |
622 | for (final View view : viewsToRemove) {
623 | removeAndRecycleView(view, recycler);
624 | }
625 | }
626 |
627 | @Override
628 | public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
629 | super.onItemsRemoved(recyclerView, positionStart, itemCount);
630 | mIsRemoveLayout = true;
631 | if (mRemoveList == null) {
632 | mRemoveList = new ArrayList<>();
633 | }
634 | if (getStackOrder() == IN_STACK_ORDER) {
635 | mRemoveBelowPosition = positionStart - 1;
636 | }else {
637 | mRemoveBelowPosition = positionStart;
638 | }
639 | // Log.d(TAG,"onItemsRemoved:"+ mRemoveBelowPosition);
640 | }
641 |
642 | private void fillChildItem(final int start, final int top, final int end, final int bottom, @NonNull final LayoutOrder layoutOrder,
643 | @NonNull final RecyclerView.Recycler recycler, final int i, final boolean childMeasuringNeeded) {
644 | final View view = bindChild(layoutOrder.mItemAdapterPosition, recycler, childMeasuringNeeded);
645 | // Log.d(TAG,"mItemAdapterPosition:"+layoutOrder.mItemAdapterPosition+",itemCount:"+mItemsCount);
646 |
647 | ViewCompat.setElevation(view, i);
648 |
649 | ItemTransformation transformation = null;
650 | if (null != mViewPostLayout) {
651 | transformation = mViewPostLayout.transformChild(this, layoutOrder.mItemPositionDiff, mOrientation);
652 | }
653 | if (null == transformation) {
654 | view.layout(start, top, end, bottom);
655 | } else {
656 | float scaleX = transformation.mScaleX* mBaseScale;
657 | float scaleY = transformation.mScaleY* mBaseScale;
658 |
659 | int viewWidth = view.getMeasuredWidth();
660 | int viewHeight = view.getMeasuredHeight();
661 | if (viewWidth <= 0 || viewHeight <= 0) {
662 | viewWidth = getDecoratedChildWidth();
663 | viewHeight = getDecoratedChildHeight();
664 | }
665 | if (getOrientation() == VERTICAL) {
666 | if (getStackOrder()*getNumberOrder() < 0) {
667 | ViewCompat.setPivotX(view,view.getMeasuredWidth()/2);
668 | ViewCompat.setPivotY(view,view.getMeasuredHeight());
669 | }else {
670 | ViewCompat.setPivotX(view, view.getMeasuredWidth()/2);
671 | ViewCompat.setPivotY(view, 0);
672 | }
673 | }else {
674 | if (getStackOrder() * getNumberOrder() < 0) {
675 | ViewCompat.setPivotX(view,view.getMeasuredWidth());
676 | ViewCompat.setPivotY(view,view.getMeasuredHeight()/2);
677 | }else {
678 | ViewCompat.setPivotX(view,0);
679 | ViewCompat.setPivotY(view,view.getMeasuredHeight()/2);
680 | }
681 | }
682 | ViewCompat.setScaleX(view, scaleX);
683 | ViewCompat.setScaleY(view, scaleY);
684 |
685 | // Log.d(TAG, "center:"+getCenterItemPosition()+",itemcount:"+mItemsCount);
686 |
687 | /**
688 | * below the {@link Build.VERSION_CODES.LOLLIPOP} view.setClipBounds(rect) is not work
689 | * so we needn't Calculation the rect
690 | */
691 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
692 | Rect rect = getRect(viewWidth,viewHeight,layoutOrder.mItemAdapterPosition,view,transformation.mClipLength);
693 | if (mIsRemoveLayout) {
694 | fixRemoved(viewWidth,viewHeight,layoutOrder,view,transformation,rect);
695 | }else {
696 | ViewCompat.setClipBounds(view, rect);
697 | }
698 | }
699 |
700 | int lastPosition = getCenterItemPosition()-getStackOrder()*(getLayoutCount()-(getLayoutCount()/2-1));
701 | final float alpha = transformation.mAlpha;
702 | /**
703 | * when remove item, the last will add, if you set view alpha when the view is not init finish, the alpha is not work,
704 | * so need delay it.
705 | */
706 | boolean needFixAlpha = mIsRemoveLayout && alpha<1 && ((getStackOrder() == OUT_STACK_ORDER) || (getStackOrder() == IN_STACK_ORDER
707 | && getCenterItemPosition() == mItemsCount-1)) && layoutOrder.mItemAdapterPosition == lastPosition;
708 | if (needFixAlpha) {
709 | view.setVisibility(View.INVISIBLE);
710 | long delay = mRecyclerView.getItemAnimator().getMoveDuration()+200;
711 | view.postDelayed(new Runnable() {
712 | @Override
713 | public void run() {
714 | view.setVisibility(View.VISIBLE);
715 | ViewCompat.setAlpha(view, alpha);
716 | }
717 | }, delay);
718 | }else{
719 | ViewCompat.setAlpha(view, alpha);
720 | }
721 |
722 | int translationX = transformation.mTranslationX*getStackOrder()*getNumberOrder();
723 | int translationY = transformation.mTranslationY*getStackOrder()*getNumberOrder();
724 | view.layout((start + translationX), (top + translationY), (end + translationX), (bottom + translationY));
725 |
726 | }
727 | }
728 |
729 | private Rect getRect(int viewWidth, int viewHeight, int adapterPosition, View view, int clipLength) {
730 | Rect rect = ViewCompat.getClipBounds(view);
731 | if (rect == null) {
732 | rect = new Rect();
733 | }
734 | if (getOrientation() == VERTICAL) {
735 | int height = clipLength;
736 | if (getStackOrder()*getNumberOrder() < 0) {
737 | rect.set(0, viewHeight-height, viewWidth, viewHeight);
738 | }else {
739 | rect.set(0,0,viewWidth,height);
740 | }
741 | }else {
742 | int width = clipLength;
743 | if (getStackOrder() * getNumberOrder() < 0) {
744 | rect.set(viewWidth-width,0,viewWidth,viewHeight);
745 | }else {
746 | rect.set(0,0,width,viewHeight);
747 | }
748 | }
749 | if (clipLength < 0) {
750 | rect.set(0, 0, viewWidth, viewHeight);
751 | }
752 | if (!mCircleLayout) {
753 | /**
754 | * in the top don't need clip rect
755 | */
756 | boolean needFixClip = ((getStackOrder() == IN_STACK_ORDER && adapterPosition == getItemCount()-1) ||
757 | (getStackOrder() == OUT_STACK_ORDER && adapterPosition == 0));
758 | if (needFixClip) {
759 | rect.set(0, 0, viewWidth, viewHeight);
760 | }
761 | }
762 | return rect;
763 | }
764 |
765 | private void fixRemoved(int viewWidth, int viewHeight, @NonNull LayoutOrder layoutOrder,final View view,
766 | ItemTransformation transformation, Rect rect) {
767 | RemoveBean bean = null;
768 | boolean needfixRemove = (getStackOrder() == IN_STACK_ORDER && mRemoveBelowPosition <= layoutOrder.mItemAdapterPosition)
769 | || (getStackOrder() == OUT_STACK_ORDER && (mRemoveBelowPosition == layoutOrder.mItemAdapterPosition
770 | || mRemoveBelowPosition == mItemsCount));
771 | if (needfixRemove) {
772 | Rect beforeRect = ViewCompat.getClipBounds(view);
773 | // Log.d(TAG,"position:"+layoutOrder.mItemAdapterPosition+",beforeRect:"+beforeRect);
774 | if (getOrientation() == VERTICAL) {
775 | int height = Math.round(beforeRect.height()/transformation.mScaleY);
776 | if (getNumberOrder()*getStackOrder() > 0) {
777 | beforeRect.set(0 ,0 ,viewWidth, height);
778 | }else {
779 | beforeRect.set(0, viewHeight-height, viewWidth,viewHeight);
780 | }
781 | }else {
782 | int width = Math.round(beforeRect.width()/transformation.mScaleX);
783 | if (getNumberOrder()*getStackOrder() > 0) {
784 | beforeRect.set(0 ,0 ,width, viewHeight);
785 | }else {
786 | beforeRect.set(viewWidth-width, 0, viewWidth,viewHeight);
787 | }
788 | }
789 | bean = new RemoveBean(layoutOrder.mItemAdapterPosition, rect);
790 | ViewCompat.setClipBounds(view, beforeRect);
791 | }else {
792 | ViewCompat.setClipBounds(view, rect);
793 | // Log.d(TAG,"position:"+layoutOrder.mItemAdapterPosition+",rect:"+rect);
794 | }
795 | if (bean != null) {
796 | mRemoveList.add(bean);
797 | }
798 | }
799 |
800 | @Override
801 | public void onLayoutCompleted(RecyclerView.State state) {
802 | super.onLayoutCompleted(state);
803 |
804 | if (mIsRemoveLayout) {
805 | mIsRemoveLayout = false;
806 | long delay = mRecyclerView.getItemAnimator().getMoveDuration();
807 | // Log.d(TAG,"onLayoutCompleted,delay:"+delay);
808 | mRecyclerView.postDelayed(new Runnable() {
809 | @Override
810 | public void run() {
811 | if (mRemoveList != null && mRemoveList.size() > 0) {
812 | for (RemoveBean bean : mRemoveList) {
813 | // Log.d(TAG,"onLayoutCompleted,position:"+bean.position+",rect:"+bean.clipRect);
814 | View view = findViewByPosition(bean.position);
815 | ViewCompat.setClipBounds(view, bean.clipRect);
816 | }
817 | mRemoveList.clear();
818 | }
819 | }
820 | }, delay);
821 | }
822 | // Log.d(TAG,"onLayoutCompleted");
823 | }
824 |
825 | /**
826 | * get the item count while need layout
827 | * @return
828 | */
829 | public int getLayoutCount() {
830 | return Math.min(mLayoutHelper.mMaxVisibleItems * 2 + 3, mItemsCount);
831 | }
832 |
833 | /**
834 | * @return current scroll position of center item. this value can be in any range if it is cycle layout.
835 | * if this is not, that then it is in [0, {@link #mItemsCount - 1}]
836 | */
837 | private float getCurrentScrollPosition() {
838 | final int fullScrollSize = getMaxScrollOffset();
839 | if (0 == fullScrollSize) {
840 | return 0;
841 | }
842 | return 1.0f * mLayoutHelper.mScrollOffset / getScrollItemSize();
843 | }
844 |
845 | /**
846 | * @return maximum scroll value to fill up all items in layout. Generally this is only needed for non cycle layouts.
847 | */
848 | private int getMaxScrollOffset() {
849 | return getScrollItemSize() * (mItemsCount - 1);
850 | }
851 |
852 | /**
853 | * 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
854 | * (this item should layout last). So this method will calculate layout order and fill up {@link #mLayoutHelper} object.
855 | * This object will be filled by only needed to layout items. Non visible items will not be there.
856 | *
857 | * @param currentScrollPosition current scroll position this is a value that indicates position of center item
858 | * (if this value is int, then center item is really in the center of the layout, else it is near state).
859 | * Be aware that this value can be in any range is it is cycle layout
860 | * @param state Transient state of RecyclerView
861 | * @see #getCurrentScrollPosition()
862 | */
863 | protected void generateLayoutOrder(final float currentScrollPosition, @NonNull final RecyclerView.State state) {
864 | mItemsCount = state.getItemCount();
865 | float absCurrentScrollPosition;
866 | if (mCircleLayout) {
867 | absCurrentScrollPosition = makeScrollPositionInRange0ToCount(currentScrollPosition, mItemsCount);
868 | }else {
869 |
870 | /**when out of range(0,mItemsCount-1) make it slow*/
871 | /*if (currentScrollPosition < 0 ) {
872 | //the min is never small than -1.2 function y= 1.2*(1.1^x)-1.2 when x < 0
873 | absCurrentScrollPosition = (float) (1.2f*Math.pow(1.1f,currentScrollPosition)-1.2);
874 | }else if (currentScrollPosition > mItemsCount-1){
875 | //the max is never big than mItemsCount+1.7 function y= -2.7*((1.1^(mItemsCount-1-x)-1) when x > mItemsCount-1
876 | absCurrentScrollPosition = mItemsCount-1+(float) (-2.7f*Math.pow(1.1f,mItemsCount-1-currentScrollPosition)+2.7);
877 | }else {
878 | }*/
879 | absCurrentScrollPosition = currentScrollPosition;
880 | }
881 | // Log.d(TAG,"generateLayoutOrder,"+"absCurrentScrollPosition:"+absCurrentScrollPosition+",currentScrollPosition:"+currentScrollPosition);
882 | final int centerItem = Math.round(absCurrentScrollPosition);
883 |
884 | if (mCircleLayout && 1 < mItemsCount) {
885 | final int layoutCount = getLayoutCount();// + 3 = 1 (center item) + 2 (addition bellow maxVisibleItems)
886 |
887 | mLayoutHelper.initLayoutOrder(layoutCount);
888 |
889 | final int countLayoutCenter = layoutCount / 2 + getStackOrder()*2;
890 | if (getStackOrder() == OUT_STACK_ORDER) {
891 | for (int i = 1; i <= layoutCount; ++i) {
892 | final int position = Math.round( centerItem + i-1 - countLayoutCenter + mItemsCount) % mItemsCount;
893 | mLayoutHelper.setLayoutOrder(layoutCount-i, position, absCurrentScrollPosition-((centerItem+i - 1)-countLayoutCenter));
894 | }
895 | }else {
896 | for (int i = 1; i <= layoutCount; ++i) {
897 | final int position = Math.round( centerItem + i-1 - countLayoutCenter + mItemsCount) % mItemsCount;
898 | mLayoutHelper.setLayoutOrder(i-1, position, (centerItem + (i-1-absCurrentScrollPosition) -countLayoutCenter));
899 | }
900 | }
901 |
902 | } else {
903 |
904 | if (getStackOrder() == OUT_STACK_ORDER) {
905 | final int firstVisible= Math.max(centerItem -(mLayoutHelper.mMaxVisibleItems - 1), 0);
906 | final int lastVisible = Math.min(centerItem +(mLayoutHelper.mMaxVisibleItems + 3), mItemsCount - 1);
907 | final int layoutCount = Math.abs(lastVisible - firstVisible) + 1;
908 |
909 | mLayoutHelper.initLayoutOrder(layoutCount);
910 |
911 | for (int i = firstVisible; i <= lastVisible; ++i) {
912 | /**mMaxVisibleItems =3 the (itemPositionDiff,itemPositionDiff) is range (-2,6) */
913 | mLayoutHelper.setLayoutOrder(lastVisible - i, i, (absCurrentScrollPosition-i));
914 | }
915 | }else {
916 | final int firstVisible = Math.max(centerItem - (mLayoutHelper.mMaxVisibleItems + 3), 0);
917 | final int lastVisible = Math.min(centerItem + (mLayoutHelper.mMaxVisibleItems - 1), mItemsCount - 1);
918 | final int layoutCount = Math.abs(lastVisible - firstVisible) + 1;
919 |
920 | mLayoutHelper.initLayoutOrder(layoutCount);
921 |
922 | for (int i = firstVisible; i <= lastVisible; ++i) {
923 | /**mMaxVisibleItems =3 the (itemPositionDiff,itemPositionDiff) is range (-6,2) */
924 | mLayoutHelper.setLayoutOrder(i - firstVisible, i, (i - absCurrentScrollPosition));
925 | }
926 | }
927 |
928 | }
929 | }
930 |
931 | public int getWidthNoPadding() {
932 | return getWidth() - getPaddingStart() - getPaddingEnd();
933 | }
934 |
935 | public int getHeightNoPadding() {
936 | return getHeight() - getPaddingEnd() - getPaddingStart();
937 | }
938 |
939 | private View bindChild(final int position, @NonNull final RecyclerView.Recycler recycler, final boolean childMeasuringNeeded) {
940 | // Log.d(TAG,"bindChild");
941 | final View view = findViewForPosition(recycler, position);
942 |
943 | if (null == view.getParent()) {
944 | addView(view);
945 | measureChildWithMargins(view, 0, 0);
946 | } else {
947 | detachView(view);
948 | attachView(view);
949 | if (childMeasuringNeeded) {
950 | measureChildWithMargins(view, 0, 0);
951 | }
952 | }
953 | return view;
954 | }
955 |
956 | private View findViewForPosition(final RecyclerView.Recycler recycler, final int position) {
957 | for (int i = 0, size = getChildCount(); i < size; ++i) {
958 | final View child = getChildAt(i);
959 | final ViewGroup.LayoutParams lp = child.getLayoutParams();
960 | if (!(lp instanceof RecyclerView.LayoutParams)) {
961 | continue;
962 | }
963 | final RecyclerView.LayoutParams recyclerLp = (RecyclerView.LayoutParams) lp;
964 | final int adapterPosition = recyclerLp.getViewAdapterPosition();
965 | if (adapterPosition == position) {
966 | if (recyclerLp.isItemChanged()) {
967 | recycler.bindViewToPosition(child, position);
968 | measureChildWithMargins(child, 0, 0);
969 | }
970 | return child;
971 | }
972 | }
973 | return recycler.getViewForPosition(position);
974 | }
975 |
976 | /**
977 | * is the first child width
978 | * @return mDecoratedChildWidth
979 | */
980 | public Integer getDecoratedChildWidth() {
981 | return mDecoratedChildWidth;
982 | }
983 |
984 | /**
985 | * is the first child width
986 | * @param mDecoratedChildWidth
987 | */
988 | public void setDecoratedChildWidth(Integer mDecoratedChildWidth) {
989 | this.mDecoratedChildWidth = mDecoratedChildWidth;
990 | }
991 |
992 | /**
993 | * is the first child height
994 | * @return mDecoratedChildHeight
995 | */
996 | public Integer getDecoratedChildHeight() {
997 | return mDecoratedChildHeight;
998 | }
999 |
1000 | /**
1001 | * is the first child height
1002 | * @param mDecoratedChildHeight
1003 | */
1004 | public void setDecoratedChildHeight(Integer mDecoratedChildHeight) {
1005 | this.mDecoratedChildHeight = mDecoratedChildHeight;
1006 | }
1007 |
1008 | /**
1009 | * @return full item size
1010 | */
1011 | protected int getScrollItemSize() {
1012 | if (VERTICAL == mOrientation) {
1013 | return mDecoratedChildHeight;
1014 | } else {
1015 | return mDecoratedChildWidth;
1016 | }
1017 | }
1018 |
1019 | @Override
1020 | public Parcelable onSaveInstanceState() {
1021 | if (null != mPendingStackCardSavedState) {
1022 | return new StackCardSavedState(mPendingStackCardSavedState);
1023 | }
1024 | final StackCardSavedState savedState = new StackCardSavedState(super.onSaveInstanceState());
1025 | savedState.mCenterItemPosition = mCenterItemPosition;
1026 | return savedState;
1027 | }
1028 |
1029 | @Override
1030 | public void onRestoreInstanceState(final Parcelable state) {
1031 | if (state instanceof StackCardSavedState) {
1032 | mPendingStackCardSavedState = (StackCardSavedState) state;
1033 | super.onRestoreInstanceState(mPendingStackCardSavedState.mSuperState);
1034 | } else {
1035 | super.onRestoreInstanceState(state);
1036 | }
1037 | }
1038 |
1039 | /**
1040 | * @return Scroll offset from nearest item from center
1041 | */
1042 | int getOffsetCenterView() {
1043 | return Math.round(getCurrentScrollPosition()) * getScrollItemSize() - mLayoutHelper.mScrollOffset;
1044 | }
1045 |
1046 | /**
1047 | * the {@link RecyclerView#smoothScrollToPosition(int)} will call
1048 | * {@link StackCardLayoutManager#smoothScrollToPosition(RecyclerView, RecyclerView.State, int)} ,and in this method will startScroll
1049 | * {@link android.support.v7.widget.RecyclerView.LayoutManager#startSmoothScroll(RecyclerView.SmoothScroller)} ,then will calculate distance in
1050 | * {@link StackCardSmoothScroller#calculateDxToMakeVisible(View, int)} or {@link StackCardSmoothScroller#calculateDyToMakeVisible(View, int)}
1051 | * ,finally will call this
1052 | * @param view the position of child view
1053 | * @return the distance of current view for position view
1054 | */
1055 | int getOffsetForCurrentView(@NonNull final View view) {
1056 | final int position = getPosition(view);
1057 | // Log.d(TAG,"getOffsetForCurrentView,position:"+position);
1058 | final int fullCircles = mLayoutHelper.mScrollOffset / (mItemsCount * getScrollItemSize());
1059 | int fullOffset = fullCircles * mItemsCount * getScrollItemSize();
1060 | if (0 > mLayoutHelper.mScrollOffset) {
1061 | fullOffset -= 1;
1062 | }
1063 | // Log.d(TAG,"getOffsetForCurrentView,fullOffset:"+fullOffset);
1064 | int offset;
1065 | if (0 == fullOffset || 0 < Math.signum(fullOffset)) {
1066 | offset = mLayoutHelper.mScrollOffset - position * getScrollItemSize() - fullOffset;
1067 | } else {
1068 | offset = mLayoutHelper.mScrollOffset + position * getScrollItemSize() - fullOffset;
1069 | }
1070 | // Log.d(TAG,"getOffsetForCurrentView,offset:"+offset);
1071 | return offset*getNumberOrder();
1072 | }
1073 |
1074 | /**
1075 | * Helper method that make scroll in range of [0, count). Generally this method is needed only for cycle layout.
1076 | *
1077 | * @param currentScrollPosition any scroll position range.
1078 | * @param count adapter items count
1079 | * @return good scroll position in range of [0, count)
1080 | */
1081 | private static float makeScrollPositionInRange0ToCount(final float currentScrollPosition, final int count) {
1082 | float absCurrentScrollPosition = currentScrollPosition;
1083 | while (0 > absCurrentScrollPosition) {
1084 | absCurrentScrollPosition += count;
1085 | }
1086 | while (Math.round(absCurrentScrollPosition) >= count) {
1087 | absCurrentScrollPosition -= count;
1088 | }
1089 | return absCurrentScrollPosition;
1090 | }
1091 |
1092 | private static class RemoveBean {
1093 | int position;
1094 | Rect clipRect;
1095 |
1096 | public RemoveBean(int position, Rect clipRect) {
1097 | this.position = position;
1098 | this.clipRect = clipRect;
1099 | }
1100 | }
1101 |
1102 | /**
1103 | * This interface is layout view ,if you want layout view in your way you should implements the interface
1104 | *
1105 | * Generally this method should be used for scaling and translating view item for better (different) view presentation of layouting.
1106 | */
1107 | public interface IPostLayout {
1108 |
1109 | /**
1110 | * get the centerView offset parent top, it call on {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} method
1111 | * when {@link #mDecoratedChildWidth} == null , in the {@link #onMeasure(RecyclerView.Recycler, RecyclerView.State, int, int)}
1112 | * method the {@link #mDecoratedChildWidth} set null, so the method is difference from {@link #transformChild(StackCardLayoutManager, float, int)},
1113 | * when init finish this never call again, so didn't put some params need always change when scrooling, you should put it in
1114 | *{@link #transformChild(StackCardLayoutManager, float, int)}
1115 | *
1116 | * @param layoutManager this
1117 | * @param orientation layoutManager orientation {@link RecyclerView#getLayoutDirection()}
1118 | * @return the distance centerView from parent top
1119 | */
1120 | float getBaseScale(@NonNull StackCardLayoutManager layoutManager, int orientation);
1121 |
1122 | /**
1123 | * get the offset between centerView and parent top, it call on {@link #onLayoutChildren(RecyclerView.Recycler, RecyclerView.State)} method
1124 | * when {@link #mDecoratedChildWidth} == null , in the {@link #onMeasure(RecyclerView.Recycler, RecyclerView.State, int, int)}
1125 | * method the {@link #mDecoratedChildWidth} set null, so the method is difference from {@link #transformChild(StackCardLayoutManager, float, int)},
1126 | * when init finish this never call again, so didn't put some params need always change when scrooling, you should put it in
1127 | *{@link #transformChild(StackCardLayoutManager, float, int)}
1128 | *
1129 | * @param layoutManager this
1130 | * @param orientation layoutManager orientation {@link RecyclerView#getLayoutDirection()}
1131 | * @return the distance centerView from parent top
1132 | */
1133 | int getCenterViewStartOffset(@NonNull StackCardLayoutManager layoutManager, int orientation);
1134 |
1135 | /**
1136 | * Called after child layout finished. Generally you can do any translation and scaling work here.
1137 | * it call on {@link #fillChildItem(int, int, int, int, LayoutOrder, RecyclerView.Recycler, int, boolean)} method
1138 | * the method is difference from {@link #getBaseScale(StackCardLayoutManager, int)} and
1139 | * {@link #getCenterViewStartOffset(StackCardLayoutManager, int)}, it always call when you are scrolling
1140 | * init finish this never call again, so didn't put some params need always change when scrooling, you should put it in
1141 | * {@link #transformChild(StackCardLayoutManager, float, int)}
1142 | *
1143 | * @param layoutManager this
1144 | * @param itemPositionToCenterDiff view center line difference to layout center. if > 0 then this item is bellow layout center line, else if not
1145 | * @param orientation layoutManager orientation {@link RecyclerView#getLayoutDirection()}
1146 | */
1147 | ItemTransformation transformChild(@NonNull final StackCardLayoutManager layoutManager, final float itemPositionToCenterDiff, final int orientation);
1148 | }
1149 |
1150 | public interface OnCenterItemSelectionListener {
1151 |
1152 | /**
1153 | * Listener that will be called on every change of center item.
1154 | * This listener will be triggered on every layout operation if item was changed.
1155 | * Do not do any expensive operations in this method since this will effect scroll experience.
1156 | *
1157 | * @param adapterPosition current layout center item
1158 | */
1159 | void onCenterItemChanged(final int adapterPosition);
1160 | }
1161 |
1162 | /**
1163 | * Helper class that holds currently visible items.
1164 | * Generally this class fills this list.
1165 | *
1166 | * This class holds all scroll and maxVisible items state.
1167 | *
1168 | * @see #getMaxVisibleItems()
1169 | */
1170 | private static class LayoutHelper {
1171 |
1172 | private int mMaxVisibleItems;
1173 |
1174 | private int mScrollOffset;
1175 |
1176 | private LayoutOrder[] mLayoutOrder;
1177 |
1178 | private final List> mReusedItems = new ArrayList<>();
1179 |
1180 | LayoutHelper(final int maxVisibleItems) {
1181 | mMaxVisibleItems = maxVisibleItems;
1182 | }
1183 |
1184 | /**
1185 | * 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.
1186 | *
1187 | * @param layoutCount items count that will be layout
1188 | */
1189 | void initLayoutOrder(final int layoutCount) {
1190 | if (null == mLayoutOrder || mLayoutOrder.length != layoutCount) {
1191 | if (null != mLayoutOrder) {
1192 | recycleItems(mLayoutOrder);
1193 | }
1194 | mLayoutOrder = new LayoutOrder[layoutCount];
1195 | fillLayoutOrder();
1196 | }
1197 | }
1198 |
1199 | /**
1200 | * Called during layout generation process of filling this list. Should be called only after {@link #initLayoutOrder(int)} method call.
1201 | *
1202 | * @param arrayPosition position in layout order
1203 | * @param itemAdapterPosition adapter position of item for future data filling logic
1204 | * @param itemPositionDiff difference of current item scroll position and center item position.
1205 | * if this is a center item and it is in real center of layout, then this will be 0.
1206 | * if current layout is not in the center, then this value will never be int.
1207 | * if this item center is bellow layout center line then this value is greater then 0,
1208 | * else less then 0.
1209 | */
1210 | void setLayoutOrder(final int arrayPosition, final int itemAdapterPosition, final float itemPositionDiff) {
1211 | final LayoutOrder item = mLayoutOrder[arrayPosition];
1212 | item.mItemAdapterPosition = itemAdapterPosition;
1213 | item.mItemPositionDiff = itemPositionDiff;
1214 | }
1215 |
1216 | /**
1217 | * Checks is this screen Layout has this adapterPosition view in layout
1218 | *
1219 | * @param adapterPosition adapter position of item for future data filling logic
1220 | * @return true is adapterItem is in layout
1221 | */
1222 | boolean hasAdapterPosition(final int adapterPosition) {
1223 | if (null != mLayoutOrder) {
1224 | for (final LayoutOrder layoutOrder : mLayoutOrder) {
1225 | if (layoutOrder.mItemAdapterPosition == adapterPosition) {
1226 | return true;
1227 | }
1228 | }
1229 | }
1230 | return false;
1231 | }
1232 |
1233 | @SuppressWarnings("VariableArgumentMethod")
1234 | private void recycleItems(@NonNull final LayoutOrder... layoutOrders) {
1235 | for (final LayoutOrder layoutOrder : layoutOrders) {
1236 | //noinspection ObjectAllocationInLoop
1237 | mReusedItems.add(new WeakReference<>(layoutOrder));
1238 | }
1239 | }
1240 |
1241 | private void fillLayoutOrder() {
1242 | for (int i = 0; i < mLayoutOrder.length; ++i) {
1243 | if (null == mLayoutOrder[i]) {
1244 | mLayoutOrder[i] = createLayoutOrder();
1245 | }
1246 | }
1247 | }
1248 |
1249 | private LayoutOrder createLayoutOrder() {
1250 | final Iterator> iterator = mReusedItems.iterator();
1251 | while (iterator.hasNext()) {
1252 | final WeakReference layoutOrderWeakReference = iterator.next();
1253 | final LayoutOrder layoutOrder = layoutOrderWeakReference.get();
1254 | iterator.remove();
1255 | if (null != layoutOrder) {
1256 | return layoutOrder;
1257 | }
1258 | }
1259 | return new LayoutOrder();
1260 | }
1261 | }
1262 |
1263 | /**
1264 | * Class that holds item data.
1265 | * This class is filled during {@link #generateLayoutOrder(float, RecyclerView.State)} and used during {@link #fillData(RecyclerView.Recycler, RecyclerView.State, boolean)}
1266 | */
1267 | private static class LayoutOrder {
1268 |
1269 | /**
1270 | * Item adapter position
1271 | */
1272 | private int mItemAdapterPosition;
1273 | /**
1274 | * 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.
1275 | */
1276 | private float mItemPositionDiff;
1277 | }
1278 |
1279 | protected static class StackCardSavedState implements Parcelable {
1280 |
1281 | private final Parcelable mSuperState;
1282 | private int mCenterItemPosition;
1283 |
1284 | protected StackCardSavedState(@Nullable final Parcelable superState) {
1285 | mSuperState = superState;
1286 | }
1287 |
1288 | private StackCardSavedState(@NonNull final Parcel in) {
1289 | mSuperState = in.readParcelable(Parcelable.class.getClassLoader());
1290 | mCenterItemPosition = in.readInt();
1291 | }
1292 |
1293 | protected StackCardSavedState(@NonNull final StackCardSavedState other) {
1294 | mSuperState = other.mSuperState;
1295 | mCenterItemPosition = other.mCenterItemPosition;
1296 | }
1297 |
1298 | @Override
1299 | public int describeContents() {
1300 | return 0;
1301 | }
1302 |
1303 | @Override
1304 | public void writeToParcel(final Parcel dest, final int flags) {
1305 | dest.writeParcelable(mSuperState, flags);
1306 | dest.writeInt(mCenterItemPosition);
1307 | }
1308 |
1309 | public static final Creator CREATOR
1310 | = new Creator() {
1311 | @Override
1312 | public StackCardSavedState createFromParcel(final Parcel source) {
1313 | return new StackCardSavedState(source);
1314 | }
1315 |
1316 | @Override
1317 | public StackCardSavedState[] newArray(final int size) {
1318 | return new StackCardSavedState[size];
1319 | }
1320 | };
1321 | }
1322 | }
--------------------------------------------------------------------------------