├── .gitignore
├── .idea
├── codeStyles
│ └── Project.xml
├── gradle.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── app
├── .gitignore
├── build.gradle
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── hirayclay
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── hirayclay
│ │ │ ├── Align.java
│ │ │ ├── Config.java
│ │ │ ├── ItemChangeListener.java
│ │ │ ├── MainActivity.java
│ │ │ ├── StackAdapter.java
│ │ │ ├── StackLayoutManager.java
│ │ │ └── VerticalActivity.java
│ ├── res
│ │ ├── animator
│ │ │ └── item_animator.xml
│ │ ├── drawable
│ │ │ ├── circle_shape.xml
│ │ │ ├── xm1.jpg
│ │ │ ├── xm2.jpg
│ │ │ ├── xm3.jpg
│ │ │ ├── xm4.jpg
│ │ │ ├── xm5.jpg
│ │ │ ├── xm6.jpg
│ │ │ ├── xm7.jpg
│ │ │ ├── xm8.jpg
│ │ │ └── xm9.jpg
│ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ ├── activity_vertical.xml
│ │ │ ├── item_card.xml
│ │ │ └── vertical_item_card.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ └── values
│ │ │ ├── colors.xml
│ │ │ ├── dimens.xml
│ │ │ ├── strings.xml
│ │ │ └── styles.xml
│ └── res_sub
│ │ ├── menu
│ │ └── rest.xml
│ │ └── values
│ │ ├── artical.xml
│ │ └── color.xml
│ └── test
│ └── java
│ └── com
│ └── hirayclay
│ └── ExampleUnitTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── static
├── README-cn.md
├── VerticallSLM.gif
├── app-hr.apk
├── app-vertical.apk
├── app.apk
├── app_hr.apk
├── art.gif
├── art_new.gif
├── hrreverse.gif
├── stackManager2.gif
└── stackmanager3.gif
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | /.idea/vcs.xml
7 | /.idea/misc.xml
8 | /.idea/markdown-navigator
9 | .DS_Store
10 | /build
11 | /captures
12 | .externalNativeBuild
13 | libs/
14 | .idea/markdown-navigator.xml
15 |
16 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [中文](static/README-cn.md)
2 | (因为目前代码组织得非常烂,仅仅是个玩具,如果有Item 增删操作的话最好不要用进项目,因为predictive动画并不支持。当初只是模仿着写,对RV的一整套东西并没有理解很透彻)
3 |
4 | # Why
5 | A long long time ago ,i was inspired by this project [android-pile-layout](https://github.com/xmuSistone/android-pile-layout) ,the author cannot find the appropriate math model with [LayoutManager](https://github.com/HirayClay/StackLayoutManager/blob/master/app/src/main/java/com/hirayclay/StackLayoutManager.java) .Now i have some spare time and try to do the UI with layoutManager,barely ok with the result.
6 |
7 | # Blog
8 | this is the relevant [blog](http://blog.csdn.net/u014296305/article/details/73496017) ,i hope it helps to understanding it
9 |
10 | # Display
11 | ### horizontal
12 |
13 |
14 | ### vertical(only top)
15 |
16 |
17 | ### Demo Apk
18 | [download](static/app-vertical.apk)(vertical support)
19 |
20 | ### Attention
21 | If you don't care about item ADD REMOVE operation, this is what you want,
22 | because predictive animation is not supported yet,otherwise take care!
23 |
24 | # Usage
25 | ```java
26 |
27 | Config config = new Config();
28 | config.secondaryScale = 0.8f;
29 | config.scaleRatio = 0.5f;
30 | config.maxStackCount = 3;
31 | config.initialStackCount = 2;
32 | config.space = 70;
33 | config.parallex = 1.5f;//parallex factor
34 | config.align= Align.RIGHT
35 | recyclerview.setLayoutManager(new StackLayoutManager(config));
36 | recyclerview.setAdapter(new StackAdapter(datas));
37 |
38 | ```
39 |
40 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 27
5 | buildToolsVersion "27.0.3"
6 | defaultConfig {
7 | applicationId "com.hirayclay"
8 | minSdkVersion 19
9 | targetSdkVersion 27
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 |
15 | sourceSets {
16 | main {
17 | res.srcDirs('src/main/res', 'src/main/res_sub')
18 | }
19 | }
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | }
27 |
28 | dependencies {
29 | implementation fileTree(dir: 'libs', include: ['*.jar'])
30 | androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
31 | exclude group: 'com.android.support', module: 'support-annotations'
32 | })
33 | implementation 'com.android.support:appcompat-v7:27.1.1'
34 | implementation 'com.android.support:design:27.1.1'
35 | implementation 'com.jakewharton:butterknife:8.8.1'
36 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.8.1'
37 | implementation 'com.github.bumptech.glide:glide:3.8.0'
38 | implementation 'com.makeramen:roundedimageview:2.3.0'
39 | implementation 'com.android.support:recyclerview-v7:27.1.1'
40 | implementation 'com.android.support:cardview-v7:27.1.1'
41 | testImplementation 'junit:junit:4.12'
42 | }
43 |
--------------------------------------------------------------------------------
/app/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri May 04 14:44:19 CST 2018
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
7 |
--------------------------------------------------------------------------------
/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 E:\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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/hirayclay/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.hirayclay", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/Align.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | /**
4 | * Created by CJJ on 2017/10/16.
5 | *
6 | * @author CJJ
7 | */
8 |
9 | enum Align {
10 |
11 |
12 | LEFT(1),
13 | RIGHT(-1),
14 | TOP(1),
15 | BOTTOM(-1);
16 |
17 | int layoutDirection;
18 |
19 | Align(int sign) {
20 | this.layoutDirection = sign;
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/Config.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.support.annotation.FloatRange;
4 | import android.support.annotation.IntRange;
5 |
6 | /**
7 | * Created by CJJ on 2017/6/20.
8 | *
9 | * @author CJJ
10 | */
11 |
12 | public class Config {
13 |
14 | @IntRange(from = 2)
15 | public int space = 60;
16 | public int maxStackCount = 3;
17 | public int initialStackCount = 0;
18 | @FloatRange(from = 0f, to = 1f)
19 | public float secondaryScale;
20 | @FloatRange(from = 0f, to = 1f)
21 | public float scaleRatio;
22 | /**
23 | * the real scroll distance might meet requirement,
24 | * so we multiply a factor fro parallex
25 | */
26 | @FloatRange(from = 1f,to = 2f)
27 | public float parallex = 1f;
28 | Align align;
29 |
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/ItemChangeListener.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * Created by hiray on 2017/12/27.
7 | *
8 | * @author hiray
9 | * notify the observer the item in the base position has changed
10 | */
11 |
12 | public interface ItemChangeListener {
13 |
14 | /**
15 | *
16 | * @param itemView the new item in the base position
17 | * @param position the item's position in list
18 | */
19 | void onItemChange(View itemView, int position);
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.content.Intent;
4 | import android.os.Bundle;
5 | import android.support.v7.app.AppCompatActivity;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.widget.Button;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 | import butterknife.OnClick;
15 | import butterknife.Unbinder;
16 |
17 | public class MainActivity extends AppCompatActivity {
18 | private static final String TAG = "MainActivity";
19 | @BindView(R.id.recyclerview)
20 | RecyclerView recyclerview;
21 |
22 | //horizontal reverse recyclerview
23 | @BindView(R.id.recyclerview1)
24 | RecyclerView hrRecyclerView;
25 | @BindView(R.id.button)
26 | Button button;
27 | private StackLayoutManager layoutManager;
28 | private Unbinder unbinder;
29 |
30 | @Override
31 | protected void onCreate(Bundle savedInstanceState) {
32 | super.onCreate(savedInstanceState);
33 | setContentView(R.layout.activity_main);
34 | unbinder = ButterKnife.bind(this);
35 | resetDefault();
36 | resetRight();
37 | }
38 |
39 |
40 | @OnClick(R.id.button)
41 | public void resetDefault() {
42 | List datas = new ArrayList<>();
43 | for (int i = 0; i < 15; i++) {
44 | datas.add(String.valueOf(i));
45 | }
46 |
47 | Config config = new Config();
48 | config.secondaryScale = 0.8f;
49 | config.scaleRatio = 0.4f;
50 | config.maxStackCount = 4;
51 | config.initialStackCount = 2;
52 | config.space = 15;
53 | config.align = Align.LEFT;
54 | recyclerview.setLayoutManager(layoutManager = new StackLayoutManager(config));
55 | recyclerview.setAdapter(new StackAdapter(datas));
56 |
57 | }
58 |
59 | @OnClick(R.id.button1)
60 | public void resetRight() {
61 | List datas = new ArrayList<>();
62 | for (int i = 0; i < 15; i++) {
63 | datas.add(String.valueOf(i));
64 | }
65 |
66 | Config config = new Config();
67 | config.secondaryScale = 0.8f;
68 | config.scaleRatio = 0.4f;
69 | config.maxStackCount = 4;
70 | config.initialStackCount = 2;
71 | config.space = getResources().getDimensionPixelOffset(R.dimen.item_space);
72 |
73 | config.align = Align.RIGHT;
74 | hrRecyclerView.setLayoutManager(new StackLayoutManager(config));
75 | hrRecyclerView.setAdapter(new StackAdapter(datas));
76 | }
77 |
78 | @OnClick(R.id.button2)
79 | public void viewVertical() {
80 | startActivity(new Intent(this, VerticalActivity.class));
81 | }
82 |
83 | @OnClick(R.id.scroll_to_specific_item)
84 | public void onScrollToItem() {
85 | layoutManager.scrollToPosition(10);
86 | }
87 |
88 | @Override
89 | protected void onDestroy() {
90 | super.onDestroy();
91 | unbinder.unbind();
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/StackAdapter.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.content.Context;
4 | import android.support.v7.widget.RecyclerView;
5 | import android.view.LayoutInflater;
6 | import android.view.View;
7 | import android.view.ViewGroup;
8 | import android.widget.ImageView;
9 | import android.widget.TextView;
10 | import android.widget.Toast;
11 |
12 | import com.bumptech.glide.Glide;
13 |
14 | import java.util.Arrays;
15 | import java.util.List;
16 |
17 | /**
18 | * Created by CJJ on 2017/3/7.
19 | */
20 |
21 | public class StackAdapter extends RecyclerView.Adapter {
22 |
23 | private LayoutInflater inflater;
24 | private List datas;
25 | private Context context;
26 | private List imageUrls = Arrays.asList(
27 | R.drawable.xm2,
28 | R.drawable.xm3,
29 | R.drawable.xm4,
30 | R.drawable.xm5,
31 | R.drawable.xm6,
32 | R.drawable.xm7,
33 | R.drawable.xm1,
34 | R.drawable.xm8,
35 | R.drawable.xm9,
36 | R.drawable.xm1,
37 | R.drawable.xm2,
38 | R.drawable.xm3,
39 | R.drawable.xm4,
40 | R.drawable.xm5,
41 | R.drawable.xm6
42 | );
43 | private boolean vertical;
44 |
45 | public StackAdapter(List datas) {
46 | this.datas = datas;
47 | }
48 |
49 | @Override
50 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
51 | if (inflater == null) {
52 | context = parent.getContext();
53 | inflater = LayoutInflater.from(parent.getContext());
54 | }
55 | if (vertical)
56 | return new ViewHolder(inflater.inflate(R.layout.vertical_item_card, parent, false));
57 | return new ViewHolder(inflater.inflate(R.layout.item_card, parent, false));
58 | }
59 |
60 | public StackAdapter vertical() {
61 | this.vertical = true;
62 | return this;
63 | }
64 |
65 | @Override
66 | public void onBindViewHolder(ViewHolder holder, int position) {
67 | Glide.with(context).load(imageUrls.get(position)).into(holder.cover);
68 | holder.index.setText(datas.get(holder.getAdapterPosition()));
69 | }
70 |
71 | @Override
72 | public int getItemCount() {
73 | return datas == null ? 0 : datas.size();
74 | }
75 |
76 | class ViewHolder extends RecyclerView.ViewHolder {
77 | ImageView cover;
78 | TextView index;
79 |
80 | public ViewHolder(View itemView) {
81 | super(itemView);
82 | cover = (ImageView) itemView.findViewById(R.id.cover);
83 | index = (TextView) itemView.findViewById(R.id.index);
84 | itemView.setOnClickListener(new View.OnClickListener() {
85 | @Override
86 | public void onClick(View v) {
87 | Toast.makeText(context.getApplicationContext(), String.valueOf(getAdapterPosition()), Toast.LENGTH_SHORT)
88 | .show();
89 | }
90 | });
91 | }
92 | }
93 | }
94 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/StackLayoutManager.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.animation.Animator;
4 | import android.animation.AnimatorListenerAdapter;
5 | import android.animation.ObjectAnimator;
6 | import android.support.v7.widget.RecyclerView;
7 | import android.util.Log;
8 | import android.view.MotionEvent;
9 | import android.view.VelocityTracker;
10 | import android.view.View;
11 | import android.view.ViewConfiguration;
12 |
13 | import java.lang.reflect.InvocationTargetException;
14 | import java.lang.reflect.Method;
15 |
16 | import static android.support.v7.widget.RecyclerView.NO_POSITION;
17 | import static com.hirayclay.Align.BOTTOM;
18 | import static com.hirayclay.Align.LEFT;
19 | import static com.hirayclay.Align.RIGHT;
20 | import static com.hirayclay.Align.TOP;
21 |
22 | /**
23 | * Created by CJJ on 2017/5/17.
24 | * my thought is simple:we assume the first item in the initial state is the base position ,
25 | * we only need to calculate the appropriate position{@link #left(int index)}for the given item
26 | * index with the given offset{@link #mTotalOffset}.After solve this thinking confusion ,this
27 | * layoutManager is easy to implement
28 | *
29 | * @author CJJ
30 | */
31 |
32 | class StackLayoutManager extends RecyclerView.LayoutManager {
33 |
34 | private static final String TAG = "StackLayoutManager";
35 |
36 | //the space unit for the stacked item
37 | private int mSpace = 60;
38 | /**
39 | * the offset unit,deciding current position(the sum of {@link #mItemWidth} and {@link #mSpace})
40 | */
41 | private int mUnit;
42 | //item width
43 | private int mItemWidth;
44 | private int mItemHeight;
45 | //the counting variable ,record the total offset including parallex
46 | private int mTotalOffset;
47 | //record the total offset without parallex
48 | private int mRealOffset;
49 | private ObjectAnimator animator;
50 | private int animateValue;
51 | private int duration = 300;
52 | private RecyclerView.Recycler recycler;
53 | private int lastAnimateValue;
54 | //the max stacked item count;
55 | private int maxStackCount = 4;
56 | //initial stacked item
57 | private int initialStackCount = 4;
58 | private float secondaryScale = 0.8f;
59 | private float scaleRatio = 0.4f;
60 | private float parallex = 1f;
61 | private int initialOffset;
62 | private boolean initial;
63 | private int mMinVelocityX;
64 | private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
65 | private int pointerId;
66 | private Align direction = LEFT;
67 | private RecyclerView mRV;
68 | private Method sSetScrollState;
69 | private int mPendingScrollPosition = NO_POSITION;
70 |
71 | StackLayoutManager(Config config) {
72 | this();
73 | this.maxStackCount = config.maxStackCount;
74 | this.mSpace = config.space;
75 | this.initialStackCount = config.initialStackCount;
76 | this.secondaryScale = config.secondaryScale;
77 | this.scaleRatio = config.scaleRatio;
78 | this.direction = config.align;
79 | this.parallex = config.parallex;
80 | }
81 |
82 |
83 | @SuppressWarnings("unused")
84 | public StackLayoutManager() {
85 | setAutoMeasureEnabled(true);
86 | }
87 |
88 | @Override
89 | public boolean isAutoMeasureEnabled() {
90 | return true;
91 | }
92 |
93 | @Override
94 | public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
95 | if (getItemCount() <= 0)
96 | return;
97 | this.recycler = recycler;
98 | detachAndScrapAttachedViews(recycler);
99 | //got the mUnit basing on the first child,of course we assume that all the item has the same size
100 | View anchorView = recycler.getViewForPosition(0);
101 | measureChildWithMargins(anchorView, 0, 0);
102 | mItemWidth = anchorView.getMeasuredWidth();
103 | mItemHeight = anchorView.getMeasuredHeight();
104 | if (canScrollHorizontally())
105 | mUnit = mItemWidth + mSpace;
106 | else mUnit = mItemHeight + mSpace;
107 | //because this method will be called twice
108 | initialOffset = resolveInitialOffset();
109 | mMinVelocityX = ViewConfiguration.get(anchorView.getContext()).getScaledMinimumFlingVelocity();
110 | fill(recycler, 0);
111 |
112 | }
113 |
114 | //we need take direction into account when calc initialOffset
115 | private int resolveInitialOffset() {
116 | int offset = initialStackCount * mUnit;
117 | if (mPendingScrollPosition != NO_POSITION) {
118 | offset = mPendingScrollPosition * mUnit;
119 | mPendingScrollPosition = NO_POSITION;
120 | }
121 |
122 | if (direction == LEFT)
123 | return offset;
124 | if (direction == RIGHT)
125 | return -offset;
126 | if (direction == TOP)
127 | return offset;
128 | else return offset;
129 | }
130 |
131 | @Override
132 | public void onLayoutCompleted(RecyclerView.State state) {
133 | super.onLayoutCompleted(state);
134 | if (getItemCount()<=0)
135 | return;
136 | if (!initial) {
137 | fill(recycler, initialOffset, false);
138 | initial = true;
139 | }
140 | }
141 |
142 | @Override
143 | public void onAdapterChanged(RecyclerView.Adapter oldAdapter, RecyclerView.Adapter newAdapter) {
144 | initial = false;
145 | mTotalOffset = mRealOffset = 0;
146 | }
147 |
148 | /**
149 | * the magic function :).all the work including computing ,recycling,and layout is done here
150 | *
151 | * @param recycler ...
152 | */
153 | private int fill(RecyclerView.Recycler recycler, int dy, boolean apply) {
154 | int delta = direction.layoutDirection * dy;
155 | // multiply the parallex factor
156 | if (apply)
157 | delta = (int) (delta * parallex);
158 | if (direction == LEFT)
159 | return fillFromLeft(recycler, delta);
160 | if (direction == RIGHT)
161 | return fillFromRight(recycler, delta);
162 | if (direction == TOP)
163 | return fillFromTop(recycler, delta);
164 | else return dy;//bottom alignment is not necessary,we don't support that
165 | }
166 |
167 | public int fill(RecyclerView.Recycler recycler, int dy) {
168 | return fill(recycler, dy, true);
169 | }
170 |
171 | private int fillFromTop(RecyclerView.Recycler recycler, int dy) {
172 | if (mTotalOffset + dy < 0 || (mTotalOffset + dy + 0f) / mUnit > getItemCount() - 1)
173 | return 0;
174 | detachAndScrapAttachedViews(recycler);
175 | mTotalOffset += direction.layoutDirection * dy;
176 | int count = getChildCount();
177 | //removeAndRecycle views
178 | for (int i = 0; i < count; i++) {
179 | View child = getChildAt(i);
180 | if (recycleVertically(child, dy))
181 | removeAndRecycleView(child, recycler);
182 | }
183 | int currPos = mTotalOffset / mUnit;
184 | int leavingSpace = getHeight() - (left(currPos) + mUnit);
185 | int itemCountAfterBaseItem = leavingSpace / mUnit + 2;
186 | int e = currPos + itemCountAfterBaseItem;
187 |
188 | int start = currPos - maxStackCount >= 0 ? currPos - maxStackCount : 0;
189 | int end = e >= getItemCount() ? getItemCount() - 1 : e;
190 |
191 | int left = getWidth() / 2 - mItemWidth / 2;
192 | //layout views
193 | for (int i = start; i <= end; i++) {
194 | View view = recycler.getViewForPosition(i);
195 |
196 | float scale = scale(i);
197 | float alpha = alpha(i);
198 |
199 | addView(view);
200 | measureChildWithMargins(view, 0, 0);
201 | int top = (int) (left(i) - (1 - scale) * view.getMeasuredHeight() / 2);
202 | int right = view.getMeasuredWidth() + left;
203 | int bottom = view.getMeasuredHeight() + top;
204 | layoutDecoratedWithMargins(view, left, top, right, bottom);
205 | view.setAlpha(alpha);
206 | view.setScaleY(scale);
207 | view.setScaleX(scale);
208 | }
209 |
210 | return dy;
211 | }
212 |
213 | private int fillFromRight(RecyclerView.Recycler recycler, int dy) {
214 |
215 | if (mTotalOffset + dy < 0 || (mTotalOffset + dy + 0f) / mUnit > getItemCount() - 1)
216 | return 0;
217 | detachAndScrapAttachedViews(recycler);
218 | mTotalOffset += dy;
219 | int count = getChildCount();
220 | //removeAndRecycle views
221 | for (int i = 0; i < count; i++) {
222 | View child = getChildAt(i);
223 | if (recycleHorizontally(child, dy))
224 | removeAndRecycleView(child, recycler);
225 | }
226 |
227 |
228 | int currPos = mTotalOffset / mUnit;
229 | int leavingSpace = left(currPos);
230 | int itemCountAfterBaseItem = leavingSpace / mUnit + 2;
231 | int e = currPos + itemCountAfterBaseItem;
232 |
233 | int start = currPos - maxStackCount <= 0 ? 0 : currPos - maxStackCount;
234 | int end = e >= getItemCount() ? getItemCount() - 1 : e;
235 |
236 | //layout view
237 | for (int i = start; i <= end; i++) {
238 | View view = recycler.getViewForPosition(i);
239 |
240 | float scale = scale(i);
241 | float alpha = alpha(i);
242 |
243 | addView(view);
244 | measureChildWithMargins(view, 0, 0);
245 | int left = (int) (left(i) - (1 - scale) * view.getMeasuredWidth() / 2);
246 | int top = 0;
247 | int right = left + view.getMeasuredWidth();
248 | int bottom = view.getMeasuredHeight();
249 |
250 | layoutDecoratedWithMargins(view, left, top, right, bottom);
251 | view.setAlpha(alpha);
252 | view.setScaleY(scale);
253 | view.setScaleX(scale);
254 | }
255 |
256 | return dy;
257 | }
258 |
259 | private int fillFromLeft(RecyclerView.Recycler recycler, int dy) {
260 | if (mTotalOffset + dy < 0 || (mTotalOffset + dy + 0f) / mUnit > getItemCount() - 1)
261 | return 0;
262 | detachAndScrapAttachedViews(recycler);
263 | mTotalOffset += direction.layoutDirection * dy;
264 | int count = getChildCount();
265 | //removeAndRecycle views
266 | for (int i = 0; i < count; i++) {
267 | View child = getChildAt(i);
268 | if (recycleHorizontally(child, dy))
269 | removeAndRecycleView(child, recycler);
270 | }
271 |
272 |
273 | int currPos = mTotalOffset / mUnit;
274 | int leavingSpace = getWidth() - (left(currPos) + mUnit);
275 | int itemCountAfterBaseItem = leavingSpace / mUnit + 2;
276 | int e = currPos + itemCountAfterBaseItem;
277 |
278 | int start = currPos - maxStackCount >= 0 ? currPos - maxStackCount : 0;
279 | int end = e >= getItemCount() ? getItemCount() - 1 : e;
280 |
281 | //layout view
282 | for (int i = start; i <= end; i++) {
283 | View view = recycler.getViewForPosition(i);
284 |
285 | float scale = scale(i);
286 | float alpha = alpha(i);
287 |
288 | addView(view);
289 | measureChildWithMargins(view, 0, 0);
290 | int left = (int) (left(i) - (1 - scale) * view.getMeasuredWidth() / 2);
291 | int top = 0;
292 | int right = left + view.getMeasuredWidth();
293 | int bottom = top + view.getMeasuredHeight();
294 | layoutDecoratedWithMargins(view, left, top, right, bottom);
295 | view.setAlpha(alpha);
296 | view.setScaleY(scale);
297 | view.setScaleX(scale);
298 | }
299 |
300 | return dy;
301 | }
302 |
303 | private View.OnTouchListener mTouchListener = new View.OnTouchListener() {
304 | @Override
305 | public boolean onTouch(View v, MotionEvent event) {
306 | mVelocityTracker.addMovement(event);
307 | if (event.getAction() == MotionEvent.ACTION_DOWN) {
308 | if (animator != null && animator.isRunning())
309 | animator.cancel();
310 | pointerId = event.getPointerId(0);
311 |
312 | }
313 | if (event.getAction() == MotionEvent.ACTION_UP) {
314 | if (v.isPressed()) v.performClick();
315 | mVelocityTracker.computeCurrentVelocity(1000, 14000);
316 | float xVelocity = mVelocityTracker.getXVelocity(pointerId);
317 | int o = mTotalOffset % mUnit;
318 | int scrollX;
319 | if (Math.abs(xVelocity) < mMinVelocityX && o != 0) {
320 | if (o >= mUnit / 2)
321 | scrollX = mUnit - o;
322 | else scrollX = -o;
323 | int dur = (int) (Math.abs((scrollX + 0f) / mUnit) * duration);
324 | Log.i(TAG, "onTouch: ======BREW===");
325 | brewAndStartAnimator(dur, scrollX);
326 | }
327 | }
328 | return false;
329 | }
330 |
331 | };
332 |
333 | private RecyclerView.OnFlingListener mOnFlingListener = new RecyclerView.OnFlingListener() {
334 | @Override
335 | public boolean onFling(int velocityX, int velocityY) {
336 | int o = mTotalOffset % mUnit;
337 | int s = mUnit - o;
338 | int scrollX;
339 | int vel = absMax(velocityX, velocityY);
340 | if (vel * direction.layoutDirection > 0) {
341 | scrollX = s;
342 | } else
343 | scrollX = -o;
344 | int dur = computeSettleDuration(Math.abs(scrollX), Math.abs(vel));
345 | brewAndStartAnimator(dur, scrollX);
346 | setScrollStateIdle();
347 | return true;
348 | }
349 | };
350 |
351 | private int absMax(int a, int b) {
352 | if (Math.abs(a) > Math.abs(b))
353 | return a;
354 | else return b;
355 | }
356 |
357 | @Override
358 | public void onAttachedToWindow(RecyclerView view) {
359 | super.onAttachedToWindow(view);
360 | mRV = view;
361 | //check when raise finger and settle to the appropriate item
362 | view.setOnTouchListener(mTouchListener);
363 |
364 | view.setOnFlingListener(mOnFlingListener);
365 | }
366 |
367 | private int computeSettleDuration(int distance, float xvel) {
368 | float sWeight = 0.5f * distance / mUnit;
369 | float velWeight = xvel > 0 ? 0.5f * mMinVelocityX / xvel : 0;
370 |
371 | return (int) ((sWeight + velWeight) * duration);
372 | }
373 |
374 | private void brewAndStartAnimator(int dur, int finalXorY) {
375 | animator = ObjectAnimator.ofInt(StackLayoutManager.this, "animateValue", 0, finalXorY);
376 | animator.setDuration(dur);
377 | animator.start();
378 | animator.addListener(new AnimatorListenerAdapter() {
379 | @Override
380 | public void onAnimationEnd(Animator animation) {
381 | lastAnimateValue = 0;
382 | }
383 |
384 | @Override
385 | public void onAnimationCancel(Animator animation) {
386 | lastAnimateValue = 0;
387 | }
388 | });
389 | }
390 |
391 | /******************************precise math method*******************************/
392 | private float alpha(int position) {
393 | float alpha;
394 | int currPos = mTotalOffset / mUnit;
395 | float n = (mTotalOffset + .0f) / mUnit;
396 | if (position > currPos)
397 | alpha = 1.0f;
398 | else {
399 | //temporary linear map,barely ok
400 | alpha = 1 - (n - position) / maxStackCount;
401 | }
402 | //for precise checking,oh may be kind of dummy
403 | return alpha <= 0.001f ? 0 : alpha;
404 | }
405 |
406 | private float scale(int position) {
407 | switch (direction) {
408 | default:
409 | case LEFT:
410 | case RIGHT:
411 | return scaleDefault(position);
412 | }
413 | }
414 |
415 | private float scaleDefault(int position) {
416 |
417 | float scale;
418 | int currPos = this.mTotalOffset / mUnit;
419 | float n = (mTotalOffset + .0f) / mUnit;
420 | float x = n - currPos;
421 | // position >= currPos+1;
422 | if (position >= currPos) {
423 | if (position == currPos)
424 | scale = 1 - scaleRatio * (n - currPos) / maxStackCount;
425 | else if (position == currPos + 1)
426 | //let the item's (index:position+1) scale be 1 when the item slide 1/2 mUnit,
427 | // this have better visual effect
428 | {
429 | // scale = 0.8f + (0.4f * x >= 0.2f ? 0.2f : 0.4f * x);
430 | scale = secondaryScale + (x > 0.5f ? 1 - secondaryScale : 2 * (1 - secondaryScale) * x);
431 | } else scale = secondaryScale;
432 | } else {//position <= currPos
433 | if (position < currPos - maxStackCount)
434 | scale = 0f;
435 | else {
436 | scale = 1f - scaleRatio * (n - currPos + currPos - position) / maxStackCount;
437 | }
438 | }
439 | return scale;
440 | }
441 |
442 | /**
443 | * @param position the index of the item in the adapter
444 | * @return the accurate left position for the given item
445 | */
446 | private int left(int position) {
447 |
448 |
449 | int currPos = mTotalOffset / mUnit;
450 | int tail = mTotalOffset % mUnit;
451 | float n = (mTotalOffset + .0f) / mUnit;
452 | float x = n - currPos;
453 |
454 | switch (direction) {
455 | default:
456 | case LEFT:
457 | case TOP:
458 | //from left to right or top to bottom
459 | //these two scenario are actually same
460 | return ltr(position, currPos, tail, x);
461 | case RIGHT:
462 | return rtl(position, currPos, tail, x);
463 | }
464 | }
465 |
466 | /**
467 | * @param position ..
468 | * @param currPos ..
469 | * @param tail .. change
470 | * @param x ..
471 | * @return the left position for given item
472 | */
473 | private int rtl(int position, int currPos, int tail, float x) {
474 | //虽然是做对称变换,但是必须考虑到scale给 对称变换带来的影响
475 | float scale = scale(position);
476 | int ltr = ltr(position, currPos, tail, x);
477 | return (int) (getWidth() - ltr - (mItemWidth) * scale);
478 | }
479 |
480 | private int ltr(int position, int currPos, int tail, float x) {
481 | int left;
482 |
483 | if (position <= currPos) {
484 |
485 | if (position == currPos) {
486 | left = (int) (mSpace * (maxStackCount - x));
487 | } else {
488 | left = (int) (mSpace * (maxStackCount - x - (currPos - position)));
489 |
490 | }
491 | } else {
492 | if (position == currPos + 1)
493 | left = mSpace * maxStackCount + mUnit - tail;
494 | else {
495 | float closestBaseItemScale = scale(currPos + 1);
496 |
497 | //调整因为scale导致的left误差
498 | // left = (int) (mSpace * maxStackCount + (position - currPos) * mUnit - tail
499 | // -(position - currPos)*(mItemWidth) * (1 - closestBaseItemScale));
500 |
501 | int baseStart = (int) (mSpace * maxStackCount + mUnit - tail + closestBaseItemScale * (mUnit - mSpace) + mSpace);
502 | left = (int) (baseStart + (position - currPos - 2) * mUnit - (position - currPos - 2) * (1 - secondaryScale) * (mUnit - mSpace));
503 | if (BuildConfig.DEBUG)
504 | Log.i(TAG, "ltr: currPos " + currPos
505 | + " pos:" + position
506 | + " left:" + left
507 | + " baseStart" + baseStart
508 | + " currPos+1:" + left(currPos + 1));
509 | }
510 | left = left <= 0 ? 0 : left;
511 | }
512 | return left;
513 | }
514 |
515 |
516 | @SuppressWarnings("unused")
517 | public void setAnimateValue(int animateValue) {
518 | this.animateValue = animateValue;
519 | int dy = this.animateValue - lastAnimateValue;
520 | fill(recycler, direction.layoutDirection * dy, false);
521 | lastAnimateValue = animateValue;
522 | }
523 |
524 | @SuppressWarnings("unused")
525 | public int getAnimateValue() {
526 | return animateValue;
527 | }
528 |
529 | /**
530 | * should recycle view with the given dy or say check if the
531 | * view is out of the bound after the dy is applied
532 | *
533 | * @param view ..
534 | * @param dy ..
535 | * @return ..
536 | */
537 | private boolean recycleHorizontally(View view/*int position*/, int dy) {
538 | return view != null && (view.getLeft() - dy < 0 || view.getRight() - dy > getWidth());
539 | }
540 |
541 | private boolean recycleVertically(View view, int dy) {
542 | return view != null && (view.getTop() - dy < 0 || view.getBottom() - dy > getHeight());
543 | }
544 |
545 |
546 | @Override
547 | public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler, RecyclerView.State state) {
548 | return fill(recycler, dx);
549 | }
550 |
551 | @Override
552 | public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
553 | return fill(recycler, dy);
554 | }
555 |
556 | @Override
557 | public boolean canScrollHorizontally() {
558 | return direction == LEFT || direction == RIGHT;
559 | }
560 |
561 | @Override
562 | public boolean canScrollVertically() {
563 | return direction == TOP || direction == BOTTOM;
564 | }
565 |
566 | @Override
567 | public RecyclerView.LayoutParams generateDefaultLayoutParams() {
568 | return new RecyclerView.LayoutParams(RecyclerView.LayoutParams.WRAP_CONTENT, RecyclerView.LayoutParams.WRAP_CONTENT);
569 | }
570 |
571 | /**
572 | * we need to set scrollstate to {@link RecyclerView#SCROLL_STATE_IDLE} idle
573 | * stop RV from intercepting the touch event which block the item click
574 | */
575 | private void setScrollStateIdle() {
576 | try {
577 | if (sSetScrollState == null)
578 | sSetScrollState = RecyclerView.class.getDeclaredMethod("setScrollState", int.class);
579 | sSetScrollState.setAccessible(true);
580 | sSetScrollState.invoke(mRV, RecyclerView.SCROLL_STATE_IDLE);
581 | } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
582 | e.printStackTrace();
583 | }
584 | }
585 |
586 | @Override
587 | public void scrollToPosition(int position) {
588 | if (position > getItemCount() - 1) {
589 | Log.i(TAG, "position is " + position + " but itemCount is " + getItemCount());
590 | return;
591 | }
592 | int currPosition = mTotalOffset / mUnit;
593 | int distance = (position - currPosition) * mUnit;
594 | int dur = computeSettleDuration(Math.abs(distance), 0);
595 | brewAndStartAnimator(dur, distance);
596 | }
597 |
598 | @Override
599 | public void requestLayout() {
600 | super.requestLayout();
601 | initial = false;
602 | }
603 |
604 | @SuppressWarnings("unused")
605 | public interface CallBack {
606 |
607 | float scale(int totalOffset, int position);
608 |
609 | float alpha(int totalOffset, int position);
610 |
611 | float left(int totalOffset, int position);
612 | }
613 | }
614 |
--------------------------------------------------------------------------------
/app/src/main/java/com/hirayclay/VerticalActivity.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.RecyclerView;
6 | import android.view.Menu;
7 | import android.view.MenuItem;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import butterknife.BindView;
13 | import butterknife.ButterKnife;
14 |
15 | public class VerticalActivity extends AppCompatActivity {
16 |
17 | @BindView(R.id.recyclerview_vertical)
18 | RecyclerView verticalRecyclerview;
19 |
20 | @Override
21 | protected void onCreate(Bundle savedInstanceState) {
22 | super.onCreate(savedInstanceState);
23 | setContentView(R.layout.activity_vertical);
24 | ButterKnife.bind(this);
25 | vr();
26 | }
27 |
28 | private void vr() {
29 | List datas = new ArrayList<>();
30 | for (int i = 0; i < 15; i++) {
31 | datas.add(String.valueOf(i));
32 | }
33 |
34 | Config config = new Config();
35 | config.secondaryScale = 0.95f;
36 | config.scaleRatio = 0.4f;
37 | config.maxStackCount = 4;
38 | config.initialStackCount = 4;
39 | config.space = 45;
40 | config.parallex = 1.5f;
41 | config.align = Align.TOP;
42 | verticalRecyclerview.setLayoutManager(new StackLayoutManager(config));
43 | verticalRecyclerview.setAdapter(new StackAdapter(datas).vertical());
44 | }
45 |
46 | @Override
47 | public boolean onOptionsItemSelected(MenuItem item) {
48 | switch (item.getItemId()) {
49 | case R.id.reset:
50 | vr();
51 | break;
52 | }
53 | return super.onOptionsItemSelected(item);
54 | }
55 |
56 | @Override
57 | public boolean onCreateOptionsMenu(Menu menu) {
58 | getMenuInflater().inflate(R.menu.rest, menu);
59 | return super.onCreateOptionsMenu(menu);
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/src/main/res/animator/item_animator.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
6 |
7 |
12 |
13 |
14 | -
15 |
16 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/circle_shape.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm1.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm2.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm3.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm4.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm5.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm6.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm7.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm8.jpg
--------------------------------------------------------------------------------
/app/src/main/res/drawable/xm9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/drawable/xm9.jpg
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
29 |
30 |
36 |
37 |
44 |
45 |
46 |
47 |
48 |
55 |
56 |
57 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_vertical.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
26 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/vertical_item_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
21 |
28 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.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 | 15dp
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StackLayoutManager
3 | com.hirayclay.StackLayoutManager
4 | reset
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res_sub/menu/rest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res_sub/values/artical.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | BSD licenses are a family of permissive free software licenses, imposing minimal restrictions on the use and redistribution of covered software. This is in contrast to copyleft licenses, which have reciprocity share-alike requirements. The original BSD license was used for its namesake, the Berkeley Software Distribution (BSD), a Unix-like operating system. The original version has since been revised and its descendants are more properly termed modified BSD licenses. BSD is both a license and a class of license (generally referred to as BSD-like). The modified BSD license (in wide use today) is very similar to the license originally used for the BSD version of Unix. The BSD license is a simple license that merely requires that all code licensed under the BSD license be licensed under the BSD license if redistributed in source code format. BSD (unlike some other licenses) does not require that source code be distributed at all.
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res_sub/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #ccc
4 |
--------------------------------------------------------------------------------
/app/src/test/java/com/hirayclay/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.hirayclay;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | maven {
7 | url 'https://maven.google.com/'
8 | name 'Google'
9 | }
10 | }
11 | dependencies {
12 | classpath 'com.android.tools.build:gradle:3.1.3'
13 |
14 | // NOTE: Do not place your application dependencies here; they belong
15 | // in the individual module build.gradle files
16 | }
17 | }
18 |
19 | allprojects {
20 | repositories {
21 | jcenter()
22 | maven {
23 | url 'https://maven.google.com/'
24 | name 'Google'
25 | }
26 | }
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | ## Project-wide Gradle settings.
2 | #
3 | # For more details on how to configure your build environment visit
4 | # http://www.gradle.org/docs/current/userguide/build_environment.html
5 | #
6 | # Specifies the JVM arguments used for the daemon process.
7 | # The setting is particularly useful for tweaking memory settings.
8 | # Default value: -Xmx1024m -XX:MaxPermSize=256m
9 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
10 | #
11 | # When configured, Gradle will run in incubating parallel mode.
12 | # This option should only be used with decoupled projects. More details, visit
13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
14 | # org.gradle.parallel=true
15 | #Wed Aug 02 23:04:57 CST 2017
16 | systemProp.http.proxyHost=mirrors.neusoft.edu.cn
17 | org.gradle.jvmargs=-Xmx1536m
18 | systemProp.http.proxyPort=80
19 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jun 16 14:33:51 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/static/README-cn.md:
--------------------------------------------------------------------------------
1 | # 起源
2 | 很久之前受[android-pile-layout](https://github.com/xmuSistone/android-pile-layout)这个项目启发,觉得挺有意思的,
3 | 不过作者是用ViewGroup实现的,说在尝试用LayoutManager实现的过程没有找到适合的数学模型就放弃了,所以最近有时间自己动手
4 | 用LayoutManger实现一下,最后的效果还OK.
5 |
6 | # 博客
7 | 这是对应的 [博客](http://blog.csdn.net/u014296305/article/details/73496017) ,希望能帮助理解实现思路,不过
8 | 博客是实现那天写的,只写了从右至左滑动叠加这一种思路,不过最原始的想法已经在里面了,至于从左到右滑动叠加,可以参考
9 | 这个[分支](https://github.com/HirayClay/StackLayoutManager/tree/orientation)的代码,其实也就是对前一种情况
10 | 做一个对称变换,不过有些小细节注意一下.
11 | 目前master分支上的代码只支持左右两种方向。orientation分支除了左右方向外还支持垂直方向(只有top方向)
12 |
13 |
14 | # 效果
15 |
16 |
17 | ### 安装包
18 | [download](https://github.com/HirayClay/StackLayoutManager/blob/orientation/static/app-vertical.apk)
19 |
20 | ## 使用示例
21 | ```java
22 |
23 | Config config = new Config();
24 | config.secondaryScale = 0.8f; //没有叠加的item的缩放比例
25 | config.scaleRatio = 0.5f;
26 | config.maxStackCount = 3; //最大叠加数量
27 | config.initialStackCount = 2;//初始状态下叠加的item数量
28 | config.space = 70; //item之间的间隔
29 | recyclerview.setLayoutManager(new StackLayoutManager(config));
30 | recyclerview.setAdapter(new StackAdapter(datas));
31 |
32 | ```
33 | 要注意的一点是,item根节点带margin可能会导致item显示不全,最好是item和内容一样大小
34 |
--------------------------------------------------------------------------------
/static/VerticallSLM.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/VerticallSLM.gif
--------------------------------------------------------------------------------
/static/app-hr.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/app-hr.apk
--------------------------------------------------------------------------------
/static/app-vertical.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/app-vertical.apk
--------------------------------------------------------------------------------
/static/app.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/app.apk
--------------------------------------------------------------------------------
/static/app_hr.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/app_hr.apk
--------------------------------------------------------------------------------
/static/art.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/art.gif
--------------------------------------------------------------------------------
/static/art_new.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/art_new.gif
--------------------------------------------------------------------------------
/static/hrreverse.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/hrreverse.gif
--------------------------------------------------------------------------------
/static/stackManager2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/stackManager2.gif
--------------------------------------------------------------------------------
/static/stackmanager3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/HirayClay/StackLayoutManager/89902db36b316c89f68547a75feb57a7a25edb34/static/stackmanager3.gif
--------------------------------------------------------------------------------