) : RecyclerView.LayoutManager() {
+ private enum class FlingOrientation{NONE, LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}
+
+ enum class ScrollOrientation{LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP}
+
+ private var mVisibleItemCount = visibleCount
+
+ private var mScrollOrientation = scrollOrientation
+
+ private var mScrollOffset: Int
+
+ private lateinit var mOnScrollListener: RecyclerView.OnScrollListener
+ private lateinit var mOnFlingListener: RecyclerView.OnFlingListener
+
+ //做动画的组件,支持自定义
+ private var mAnimation: StackAnimation? = null
+ //做布局的组件,支持自定义
+ private var mLayout: StackLayout? = null
+
+ //是否是翻页效果
+ private var mPagerMode = true
+
+ //触发翻页效果的最低 Fling速度
+ private var mPagerFlingVelocity = 0
+
+ //标志当前滚动是否是调用{@link #scrollToCenter(Int, RecyclerView, Boolean)}之后触发的滚动
+ private var mFixScrolling = false
+
+ //fling的方向,用来判断是前翻还是后翻
+ private var mFlingOrientation = FlingOrientation.NONE
+
+ init {
+ mScrollOffset = when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.BOTTOM_TO_TOP -> 0
+ else -> Int.MAX_VALUE
+ }
+
+ if (StackAnimation::class.java.isAssignableFrom(animation)) {
+ try {
+ val cla = animation.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType)
+ mAnimation = cla.newInstance(scrollOrientation, visibleCount) as StackAnimation
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ if (StackLayout::class.java.isAssignableFrom(layout)) {
+ try {
+ val cla = layout.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType)
+ mLayout = cla.newInstance(scrollOrientation, visibleCount, 30) as StackLayout
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+ }
+
+ /**
+ * 设置是否为ViewPager 式翻页模式.
+ *
+ * 当设置为 true 的时候,可以配合{@link #setPagerFlingVelocity(Int)} 设置触发翻页的最小速度.
+ * @param isPagerMode 这个值默认是 false,当设置为 true 的时候,会有 viewPager 翻页效果.
+ */
+ fun setPagerMode(isPagerMode: Boolean) {
+ mPagerMode = isPagerMode
+ }
+
+ /**
+ * @return 当前是否为ViewPager翻页模式.
+ */
+ fun getPagerMode(): Boolean {
+ return mPagerMode
+ }
+
+ /**
+ * 设置触发ViewPager翻页效果的最小速度.
+ *
+ * 该值仅在 {@link #getPagerMode() == true}的时候有效.
+ * @param velocity 默认值是2000.
+ */
+ fun setPagerFlingVelocity(@IntRange(from = 0, to = Int.MAX_VALUE.toLong()) velocity: Int) {
+ mPagerFlingVelocity = Math.min(Int.MAX_VALUE, Math.max(0, velocity))
+ }
+
+ /**
+ * @return 当前触发翻页的最小 fling 速度.
+ */
+ fun getPagerFlingVelocity(): Int {
+ return mPagerFlingVelocity
+ }
+
+ /**
+ * 设置recyclerView 静止时候可见的itemView 个数.
+ * @param count 可见 itemView,默认为3
+ */
+ fun setVisibleItemCount(@IntRange(from = 1, to = Long.MAX_VALUE)count: Int) {
+ mVisibleItemCount = Math.min(itemCount - 1, Math.max(1, count))
+ mAnimation?.setVisibleCount(mVisibleItemCount)
+ }
+
+ /**
+ * 获取recyclerView 静止时候可见的itemView 个数.
+ * @return 静止时候可见的itemView 个数,默认为3.
+ */
+ fun getVisibleItemCount(): Int {
+ return mVisibleItemCount
+ }
+
+ /**
+ * 设置 item 偏移值,即第 i 个 item 相对于 第 i-1个 item 在水平方向的偏移值,默认是40px.
+ * @param offset 每个 item 相对于前一个的偏移值.
+ */
+ fun setItemOffset(offset: Int) {
+ mLayout?.setItemOffset(offset)
+ }
+
+ /**
+ * 获取每个 item 相对于前一个的水平偏移值.
+ * @return 每个 item 相对于前一个的水平偏移值.
+ */
+ fun getItemOffset(): Int {
+ return if (mLayout == null) {
+ 0
+ } else {
+ mLayout!!.getItemOffset()
+ }
+ }
+
+ /**
+ * 设置item 移动动画.
+ * @param animation item 移动动画.
+ */
+ fun setAnimation(animation: StackAnimation) {
+ mAnimation = animation
+ }
+
+ /**
+ * 获取 item 移动动画.
+ * @return item 移动动画.
+ */
+ fun getAnimation(): StackAnimation? {
+ return mAnimation
+ }
+
+ /**
+ * 获取StackLayoutManager 的滚动方向.
+ * @return StackLayoutManager 的滚动方向.
+ */
+ fun getScrollOrientation(): ScrollOrientation {
+ return mScrollOrientation
+ }
+
+ constructor(scrollOrientation: ScrollOrientation) : this(scrollOrientation, 3, DefaultAnimation::class.java, DefaultLayout::class.java)
+
+ constructor(scrollOrientation: ScrollOrientation, visibleCount: Int) : this(scrollOrientation, visibleCount, DefaultAnimation::class.java, DefaultLayout::class.java)
+
+ constructor() : this(ScrollOrientation.RIGHT_TO_LEFT)
+
+
+ override fun generateDefaultLayoutParams(): RecyclerView.LayoutParams {
+ return RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT)
+ }
+
+ override fun onAttachedToWindow(view: RecyclerView) {
+ super.onAttachedToWindow(view)
+ mOnFlingListener = object : RecyclerView.OnFlingListener() {
+ override fun onFling(velocityX: Int, velocityY: Int): Boolean {
+ if (mPagerMode) {
+ when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> {
+ mFlingOrientation = when {
+ velocityX > mPagerFlingVelocity -> FlingOrientation.RIGHT_TO_LEFT
+ velocityX < -mPagerFlingVelocity -> FlingOrientation.LEFT_TO_RIGHT
+ else -> FlingOrientation.NONE
+ }
+ if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动
+ mFixScrolling = true
+ }
+ }
+ else -> {
+ mFlingOrientation = when {
+ velocityY > mPagerFlingVelocity -> FlingOrientation.BOTTOM_TO_TOP
+ velocityY < -mPagerFlingVelocity -> FlingOrientation.TOP_TO_BOTTOM
+ else -> FlingOrientation.NONE
+ }
+ if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动
+ mFixScrolling = true
+ }
+ }
+ }
+
+ calculateAndScrollToTarget(view)
+ }
+ return mPagerMode
+ }
+ }
+ view.onFlingListener = mOnFlingListener
+
+ mOnScrollListener = object : RecyclerView.OnScrollListener() {
+ override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
+ if (newState == SCROLL_STATE_IDLE) {
+ if (!mFixScrolling) {
+ mFixScrolling = true
+ calculateAndScrollToTarget(view)
+ } else {
+ //表示此次 IDLE 是由修正位置结束触发的
+ mFixScrolling = false
+ }
+ } else if (newState == SCROLL_STATE_DRAGGING) {
+ mFixScrolling = false
+ }
+ }
+ }
+ view.addOnScrollListener(mOnScrollListener)
+ }
+
+ override fun onDetachedFromWindow(view: RecyclerView?, recycler: RecyclerView.Recycler?) {
+ super.onDetachedFromWindow(view, recycler)
+ if (view?.onFlingListener == mOnFlingListener) {
+ view.onFlingListener = null
+ }
+ view?.removeOnScrollListener(mOnScrollListener)
+ }
+
+ override fun canScrollHorizontally(): Boolean {
+ return when(mScrollOrientation) {
+ ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> true
+ else -> false
+ }
+ }
+
+ override fun canScrollVertically(): Boolean {
+ return when(mScrollOrientation) {
+ ScrollOrientation.TOP_TO_BOTTOM, ScrollOrientation.BOTTOM_TO_TOP -> true
+ else -> false
+ }
+ }
+
+ override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: RecyclerView.State) {
+ mLayout?.requestLayout()
+
+ removeAndRecycleAllViews(recycler)
+
+ mScrollOffset = getValidOffset(mScrollOffset)
+
+ loadItemView(recycler)
+ }
+
+ override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State): Int {
+ return handleScrollBy(dx, recycler)
+ }
+
+ override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: RecyclerView.State?): Int {
+ return handleScrollBy(dy, recycler)
+ }
+
+ override fun scrollToPosition(position: Int) {
+ if (position < 0 || position >= itemCount) {
+ throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
+ }
+ mScrollOffset = getPositionOffset(position)
+ requestLayout()
+ }
+
+ override fun smoothScrollToPosition(recyclerView: RecyclerView, state: RecyclerView.State?, position: Int) {
+ if (position < 0 || position >= itemCount) {
+ throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]")
+ }
+ mFixScrolling = true
+ scrollToCenter(position, recyclerView, true)
+ }
+
+ private fun handleScrollBy(offset: Int, recycler: RecyclerView.Recycler): Int {
+ //期望值,不得超过最大最小值,所以期望值不一定等于实际值
+ val expectOffset = mScrollOffset + offset
+
+ //实际值
+ mScrollOffset = getValidOffset(expectOffset)
+
+ //实际偏移,超过最大最小值之后的偏移都应该是0,该值作为返回值,否则在极限位置进行滚动的时候不会出现弹性阴影
+ val exactMove = mScrollOffset - expectOffset + offset
+
+ if (exactMove == 0) {
+ //itemViews 位置都不会改变,直接 return
+ return 0
+ }
+
+ detachAndScrapAttachedViews(recycler)
+
+ loadItemView(recycler)
+ return exactMove
+ }
+
+ private fun loadItemView(recycler: RecyclerView.Recycler) {
+ val firstVisiblePosition = getFirstVisibleItemPosition()
+ val lastVisiblePosition = getLastVisibleItemPosition()
+
+ //位移百分比
+ val movePercent = getFirstVisibleItemMovePercent()
+
+ for (i in lastVisiblePosition downTo firstVisiblePosition) {
+ val view = recycler.getViewForPosition(i)
+ //添加到recycleView 中
+ addView(view)
+ //测量
+ measureChild(view, 0, 0)
+ //布局
+ mLayout?.doLayout(this, mScrollOffset, movePercent, view, i - firstVisiblePosition)
+ //做动画
+ mAnimation?.doAnimation(movePercent, view, i - firstVisiblePosition)
+ }
+
+ //重用
+ if (firstVisiblePosition - 1 >= 0) {
+ val view = recycler.getViewForPosition(firstVisiblePosition - 1)
+ resetViewAnimateProperty(view)
+ removeAndRecycleView(view, recycler)
+ }
+ if (lastVisiblePosition + 1 < itemCount) {
+ val view = recycler.getViewForPosition(lastVisiblePosition + 1)
+ resetViewAnimateProperty(view)
+ removeAndRecycleView(view, recycler)
+ }
+ }
+
+ private fun resetViewAnimateProperty(view: View) {
+ view.rotationY = 0f
+ view.rotationX = 0f
+ view.scaleX = 1f
+ view.scaleY = 1f
+ view.alpha = 1f
+ }
+
+ private fun calculateAndScrollToTarget(view: RecyclerView) {
+ val targetPosition = calculateCenterPosition(getFirstVisibleItemPosition())
+ scrollToCenter(targetPosition, view, true)
+ }
+
+ private fun scrollToCenter(targetPosition: Int, recyclerView: RecyclerView, animation: Boolean) {
+ val targetOffset = getPositionOffset(targetPosition)
+ when(mScrollOrientation) {
+ ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> {
+ if (animation) {
+ recyclerView.smoothScrollBy(targetOffset - mScrollOffset, 0)
+ } else {
+ recyclerView.scrollBy(targetOffset - mScrollOffset, 0)
+ }
+ }
+ else -> {
+ if (animation) {
+ recyclerView.smoothScrollBy(0, targetOffset - mScrollOffset)
+ } else {
+ recyclerView.scrollBy(0, targetOffset - mScrollOffset)
+ }
+ }
+ }
+ }
+
+ private fun getValidOffset(expectOffset: Int): Int {
+ return when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> Math.max(Math.min(width * (itemCount - 1), expectOffset), 0)
+ else -> Math.max(Math.min(height * (itemCount - 1), expectOffset), 0)
+ }
+ }
+
+ private fun getPositionOffset(position: Int): Int {
+ return when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT -> position * width
+ ScrollOrientation.LEFT_TO_RIGHT -> (itemCount - 1 - position) * width
+ ScrollOrientation.BOTTOM_TO_TOP -> position * height
+ ScrollOrientation.TOP_TO_BOTTOM -> (itemCount - 1 - position) * height
+ }
+ }
+
+ private fun getFirstVisibleItemPosition(): Int {
+ return when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT -> Math.floor((mScrollOffset * 1.0 / width)).toInt()
+ ScrollOrientation.LEFT_TO_RIGHT -> itemCount - 1 - Math.ceil((mScrollOffset * 1.0 / width)).toInt()
+ ScrollOrientation.BOTTOM_TO_TOP -> Math.floor((mScrollOffset * 1.0 / height)).toInt()
+ ScrollOrientation.TOP_TO_BOTTOM -> itemCount - 1 - Math.ceil((mScrollOffset * 1.0 / height)).toInt()
+ }
+ }
+
+ private fun getLastVisibleItemPosition(): Int {
+ val firstVisiblePosition = getFirstVisibleItemPosition()
+ return if (firstVisiblePosition + mVisibleItemCount > itemCount - 1) {
+ itemCount - 1
+ } else {
+ firstVisiblePosition + mVisibleItemCount
+ }
+ }
+
+ private fun getFirstVisibleItemMovePercent(): Float {
+ return when (mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT -> {
+ return if (width == 0) {
+ 0f
+ } else {
+ (mScrollOffset % width) * 1.0f / width
+ }
+ }
+ ScrollOrientation.LEFT_TO_RIGHT -> {
+ if (width == 0) {
+ return 0f
+ }
+ val targetPercent = 1 - (mScrollOffset % width) * 1.0f / width
+ return if (targetPercent == 1f) {
+ 0f
+ } else {
+ targetPercent
+ }
+ }
+ ScrollOrientation.BOTTOM_TO_TOP -> (mScrollOffset % height) * 1.0f / height
+ ScrollOrientation.TOP_TO_BOTTOM -> {
+ val targetPercent = 1 - (mScrollOffset % height) * 1.0f / height
+ return if (targetPercent == 1f) {
+ 0f
+ } else {
+ targetPercent
+ }
+ }
+ }
+ }
+
+ private fun calculateCenterPosition(position: Int): Int {
+ //当是 Fling 触发的时候
+ val triggerOrientation = mFlingOrientation
+ mFlingOrientation = FlingOrientation.NONE
+ when(mScrollOrientation) {
+ ScrollOrientation.RIGHT_TO_LEFT -> {
+ if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) {
+ return position + 1
+ } else if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) {
+ return position
+ }
+ }
+ ScrollOrientation.LEFT_TO_RIGHT -> {
+ if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) {
+ return position + 1
+ } else if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) {
+ return position
+ }
+ }
+ ScrollOrientation.BOTTOM_TO_TOP -> {
+ if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) {
+ return position + 1
+ } else if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) {
+ return position
+ }
+ }
+ ScrollOrientation.TOP_TO_BOTTOM -> {
+ if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) {
+ return position + 1
+ } else if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) {
+ return position
+ }
+ }
+ }
+
+ //当不是 fling 触发的时候
+ val percent = getFirstVisibleItemMovePercent()
+ //向左移动超过50% position(firstVisibleItemPosition)++
+ //否 position不变
+ return if (percent < 0.5) {
+ position
+ } else {
+ position + 1
+ }
+ }
+}
\ No newline at end of file
diff --git a/StackLayoutManager/src/main/res/values/strings.xml b/StackLayoutManager/src/main/res/values/strings.xml
new file mode 100644
index 0000000..f91a725
--- /dev/null
+++ b/StackLayoutManager/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ StackLayoutManager
+
diff --git a/StackLayoutManager/src/test/java/com/littlemango/stacklayoutmanager/ExampleUnitTest.java b/StackLayoutManager/src/test/java/com/littlemango/stacklayoutmanager/ExampleUnitTest.java
new file mode 100644
index 0000000..f5264e5
--- /dev/null
+++ b/StackLayoutManager/src/test/java/com/littlemango/stacklayoutmanager/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package com.littlemango.stacklayoutmanager;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see Testing documentation
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 0000000..5bd00d6
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,27 @@
+// Top-level build file where you can add configuration options common to all sub-projects/modules.
+
+buildscript {
+ ext.kotlin_version = '1.2.50'
+ repositories {
+ google()
+ jcenter()
+ }
+ dependencies {
+ classpath 'com.android.tools.build:gradle:3.1.3'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+
+ // NOTE: Do not place your application dependencies here; they belong
+ // in the individual module build.gradle files
+ }
+}
+
+allprojects {
+ repositories {
+ google()
+ jcenter()
+ }
+}
+
+task clean(type: Delete) {
+ delete rootProject.buildDir
+}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..743d692
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,13 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx1536m
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 0000000..7a3265e
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..40dfc80
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jun 28 17:28:50 CST 2018
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip
diff --git a/gradlew b/gradlew
new file mode 100755
index 0000000..cccdd3d
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,172 @@
+#!/usr/bin/env sh
+
+##############################################################################
+##
+## Gradle start up script for UN*X
+##
+##############################################################################
+
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
+
+APP_NAME="Gradle"
+APP_BASE_NAME=`basename "$0"`
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS=""
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD="maximum"
+
+warn () {
+ echo "$*"
+}
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+}
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "`uname`" in
+ CYGWIN* )
+ cygwin=true
+ ;;
+ Darwin* )
+ darwin=true
+ ;;
+ MINGW* )
+ msys=true
+ ;;
+ NONSTOP* )
+ nonstop=true
+ ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD="$JAVA_HOME/jre/sh/java"
+ else
+ JAVACMD="$JAVA_HOME/bin/java"
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD="java"
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
+ MAX_FD_LIMIT=`ulimit -H -n`
+ if [ $? -eq 0 ] ; then
+ if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
+ MAX_FD="$MAX_FD_LIMIT"
+ fi
+ ulimit -n $MAX_FD
+ if [ $? -ne 0 ] ; then
+ warn "Could not set maximum file descriptor limit: $MAX_FD"
+ fi
+ else
+ warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
+ fi
+fi
+
+# For Darwin, add options to specify how the application appears in the dock
+if $darwin; then
+ GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
+fi
+
+# For Cygwin, switch paths to Windows format before running java
+if $cygwin ; then
+ APP_HOME=`cygpath --path --mixed "$APP_HOME"`
+ CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+ JAVACMD=`cygpath --unix "$JAVACMD"`
+
+ # We build the pattern for arguments to be converted via cygpath
+ ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
+ SEP=""
+ for dir in $ROOTDIRSRAW ; do
+ ROOTDIRS="$ROOTDIRS$SEP$dir"
+ SEP="|"
+ done
+ OURCYGPATTERN="(^($ROOTDIRS))"
+ # Add a user-defined pattern to the cygpath arguments
+ if [ "$GRADLE_CYGPATTERN" != "" ] ; then
+ OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
+ fi
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ i=0
+ for arg in "$@" ; do
+ CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
+ CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
+
+ if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
+ eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
+ else
+ eval `echo args$i`="\"$arg\""
+ fi
+ i=$((i+1))
+ done
+ case $i in
+ (0) set -- ;;
+ (1) set -- "$args0" ;;
+ (2) set -- "$args0" "$args1" ;;
+ (3) set -- "$args0" "$args1" "$args2" ;;
+ (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ esac
+fi
+
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
+}
+APP_ARGS=$(save "$@")
+
+# Collect all arguments for the java command, following the shell quoting and substitution rules
+eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
+
+# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
+if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
+ cd "$(dirname "$0")"
+fi
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 0000000..f955316
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,84 @@
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS=
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto init
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto init
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:init
+@rem Get command-line arguments, handling Windows variants
+
+if not "%OS%" == "Windows_NT" goto win9xME_args
+
+:win9xME_args
+@rem Slurp the command line arguments.
+set CMD_LINE_ARGS=
+set _SKIP=2
+
+:win9xME_args_slurp
+if "x%~1" == "x" goto execute
+
+set CMD_LINE_ARGS=%*
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/sample/.DS_Store b/sample/.DS_Store
new file mode 100644
index 0000000..4885661
Binary files /dev/null and b/sample/.DS_Store differ
diff --git a/sample/.gitignore b/sample/.gitignore
new file mode 100644
index 0000000..796b96d
--- /dev/null
+++ b/sample/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/sample/build.gradle b/sample/build.gradle
new file mode 100644
index 0000000..256e1d9
--- /dev/null
+++ b/sample/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.application'
+
+apply plugin: 'kotlin-android'
+
+apply plugin: 'kotlin-android-extensions'
+
+android {
+ compileSdkVersion 27
+ defaultConfig {
+ applicationId "com.littlemango.stacklayoutmanagermaster"
+ minSdkVersion 19
+ targetSdkVersion 27
+ versionCode 1
+ versionName "1.0"
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
+ }
+ }
+}
+
+ext.kotlin_version = '1.2.50'
+
+dependencies {
+ implementation fileTree(include: ['*.jar'], dir: 'libs')
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
+ implementation 'com.android.support:appcompat-v7:27.1.1'
+ implementation 'com.android.support.constraint:constraint-layout:1.1.2'
+ testImplementation 'junit:junit:4.12'
+ androidTestImplementation 'com.android.support.test:runner:1.0.2'
+ androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
+ implementation 'com.android.support:recyclerview-v7:27.1.1'
+ implementation 'com.android.support:cardview-v7:27.1.1'
+ implementation project(':StackLayoutManager')
+ implementation 'com.afollestad.material-dialogs:core:0.9.6.0'
+ implementation 'com.android.support:design:27.1.1'
+}
diff --git a/sample/proguard-rules.pro b/sample/proguard-rules.pro
new file mode 100644
index 0000000..f1b4245
--- /dev/null
+++ b/sample/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/sample/src/.DS_Store b/sample/src/.DS_Store
new file mode 100644
index 0000000..a0eee9e
Binary files /dev/null and b/sample/src/.DS_Store differ
diff --git a/sample/src/androidTest/java/com/littlemango/stacklayoutmanagermaster/ExampleInstrumentedTest.kt b/sample/src/androidTest/java/com/littlemango/stacklayoutmanagermaster/ExampleInstrumentedTest.kt
new file mode 100644
index 0000000..b927e69
--- /dev/null
+++ b/sample/src/androidTest/java/com/littlemango/stacklayoutmanagermaster/ExampleInstrumentedTest.kt
@@ -0,0 +1,24 @@
+package com.littlemango.stacklayoutmanagermaster
+
+import android.support.test.InstrumentationRegistry
+import android.support.test.runner.AndroidJUnit4
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+import org.junit.Assert.*
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+@RunWith(AndroidJUnit4::class)
+class ExampleInstrumentedTest {
+ @Test
+ fun useAppContext() {
+ // Context of the app under test.
+ val appContext = InstrumentationRegistry.getTargetContext()
+ assertEquals("com.littlemango.stacklayoutmanagermaster", appContext.packageName)
+ }
+}
diff --git a/sample/src/main/.DS_Store b/sample/src/main/.DS_Store
new file mode 100644
index 0000000..7cf6474
Binary files /dev/null and b/sample/src/main/.DS_Store differ
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3f30da7
--- /dev/null
+++ b/sample/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/FadeInFadeOutAnimation.java b/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/FadeInFadeOutAnimation.java
new file mode 100644
index 0000000..c866d2c
--- /dev/null
+++ b/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/FadeInFadeOutAnimation.java
@@ -0,0 +1,30 @@
+package com.littlemango.stacklayoutmanagermaster;
+
+import android.support.v7.widget.LinearLayoutManager;
+import android.view.View;
+
+import com.littlemango.stacklayoutmanager.StackAnimation;
+import com.littlemango.stacklayoutmanager.StackLayoutManager.ScrollOrientation;
+
+import org.jetbrains.annotations.NotNull;
+
+public class FadeInFadeOutAnimation extends StackAnimation {
+
+ private int mVisibleCount;
+
+ FadeInFadeOutAnimation(@NotNull ScrollOrientation scrollOrientation, int visibleCount) {
+ super(scrollOrientation, visibleCount);
+ mVisibleCount = visibleCount;
+ }
+
+ @Override
+ public void doAnimation(float firstMovePercent, @NotNull View itemView, int position) {
+ if (position == 0) {
+ itemView.setAlpha(1 - firstMovePercent + 0.5f);
+ } else if (position == mVisibleCount) {
+ itemView.setAlpha(firstMovePercent);
+ }
+ itemView.setScaleX(1);
+ itemView.setScaleY(1);
+ }
+}
diff --git a/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/MainActivity.java b/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/MainActivity.java
new file mode 100644
index 0000000..5ec978c
--- /dev/null
+++ b/sample/src/main/java/com/littlemango/stacklayoutmanagermaster/MainActivity.java
@@ -0,0 +1,174 @@
+package com.littlemango.stacklayoutmanagermaster;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.RecyclerView;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.afollestad.materialdialogs.MaterialDialog;
+import com.littlemango.stacklayoutmanager.StackLayoutManager;
+import com.littlemango.stacklayoutmanager.StackLayoutManager.ScrollOrientation;
+import java.util.Random;
+
+public class MainActivity extends AppCompatActivity {
+
+ private static final String TAG = "MainActivity";
+ private RecyclerView mRecyclerView;
+
+ private static final int mStackCount = 30;
+
+ private int mRandomPosition;
+
+ private StackLayoutManager mStackLayoutManager;
+
+ private String[] selectItems;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ mRecyclerView = findViewById(R.id.recycleView);
+ mStackLayoutManager = new StackLayoutManager();
+ mRecyclerView.setLayoutManager(mStackLayoutManager);
+ mRecyclerView.setAdapter(new StackLayoutAdapter());
+
+ selectItems = getResources().getStringArray(R.array.items);
+ resetRandom();
+
+ findViewById(R.id.floatButton).setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+
+ new MaterialDialog.Builder(MainActivity.this)
+ .items(selectItems)
+ .itemsCallback(new MaterialDialog.ListCallback() {
+ @Override
+ public void onSelection(MaterialDialog dialog, View view, int which, CharSequence text) {
+ switch (which) {
+ case 0:
+ mRecyclerView.smoothScrollToPosition(mRandomPosition);
+ resetRandom();
+ break;
+ case 1:
+ mRecyclerView.scrollToPosition(mRandomPosition);
+ resetRandom();
+ break;
+ case 2:
+ mStackLayoutManager = new StackLayoutManager(ScrollOrientation.LEFT_TO_RIGHT);
+ mRecyclerView.setLayoutManager(mStackLayoutManager);
+ break;
+ case 3:
+ mStackLayoutManager = new StackLayoutManager(ScrollOrientation.RIGHT_TO_LEFT);
+ mRecyclerView.setLayoutManager(mStackLayoutManager);
+ break;
+ case 4:
+ mStackLayoutManager = new StackLayoutManager(ScrollOrientation.TOP_TO_BOTTOM);
+ mRecyclerView.setLayoutManager(mStackLayoutManager);
+ break;
+ case 5:
+ mStackLayoutManager = new StackLayoutManager(ScrollOrientation.BOTTOM_TO_TOP);
+ mRecyclerView.setLayoutManager(mStackLayoutManager);
+ break;
+ case 6:
+ mStackLayoutManager.setPagerMode(!mStackLayoutManager.getPagerMode());
+ break;
+ case 7:
+ mStackLayoutManager.setItemOffset(mStackLayoutManager.getItemOffset() + 10);
+ mStackLayoutManager.requestLayout();
+ break;
+ case 8:
+ mStackLayoutManager.setItemOffset(mStackLayoutManager.getItemOffset() - 10);
+ mStackLayoutManager.requestLayout();
+ break;
+ case 9:
+ mStackLayoutManager.setVisibleItemCount(mStackLayoutManager.getVisibleItemCount() + 1);
+ mStackLayoutManager.requestLayout();
+ break;
+ case 10:
+ mStackLayoutManager.setVisibleItemCount(mStackLayoutManager.getVisibleItemCount() - 1);
+ mStackLayoutManager.requestLayout();
+ break;
+ case 11:
+ mStackLayoutManager.setPagerFlingVelocity(mStackLayoutManager.getPagerFlingVelocity() + 5000);
+ break;
+ case 12:
+ mStackLayoutManager.setPagerFlingVelocity(mStackLayoutManager.getPagerFlingVelocity() - 5000);
+ break;
+ case 13:
+ mStackLayoutManager.setAnimation(new FadeInFadeOutAnimation(mStackLayoutManager.getScrollOrientation(),
+ mStackLayoutManager.getVisibleItemCount()));
+ mStackLayoutManager.requestLayout();
+ break;
+ }
+ }
+ })
+ .show();
+ }
+ });
+ }
+
+ class StackLayoutAdapter extends RecyclerView.Adapter {
+
+ @NonNull
+ @Override
+ public StackHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
+
+ View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.image_card, viewGroup, false);
+ return new StackHolder(view);
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull StackHolder stackHolder, int position) {
+ int res;
+ switch (position % 6) {
+ case 0:
+ res = R.drawable.image1;
+ break;
+ case 1:
+ res = R.drawable.image2;
+ break;
+ case 2:
+ res = R.drawable.image3;
+ break;
+ case 3:
+ res = R.drawable.image4;
+ break;
+ case 4:
+ res = R.drawable.image5;
+ break;
+ default:
+ res = R.drawable.image6;
+ break;
+ }
+ stackHolder.imageView.setImageResource(res);
+ stackHolder.textView.setText("" + position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mStackCount;
+ }
+
+ class StackHolder extends RecyclerView.ViewHolder {
+ ImageView imageView;
+ TextView textView;
+
+ StackHolder(@NonNull View itemView) {
+ super(itemView);
+ imageView = itemView.findViewById(R.id.imageView);
+ textView = itemView.findViewById(R.id.textView);
+ }
+ }
+ }
+
+ private void resetRandom() {
+ mRandomPosition = Math.abs(new Random().nextInt() % mStackCount);
+ selectItems[0] = getResources().getString(R.string.smooth_scroll) + mRandomPosition;
+ selectItems[1] = getResources().getString(R.string.scroll) + mRandomPosition;
+ }
+}
diff --git a/sample/src/main/res/.DS_Store b/sample/src/main/res/.DS_Store
new file mode 100644
index 0000000..eeff520
Binary files /dev/null and b/sample/src/main/res/.DS_Store differ
diff --git a/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
new file mode 100644
index 0000000..c7bd21d
--- /dev/null
+++ b/sample/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,34 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_launcher_background.xml b/sample/src/main/res/drawable/ic_launcher_background.xml
new file mode 100644
index 0000000..d5fccc5
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/main/res/drawable/ic_star_black_24dp.xml b/sample/src/main/res/drawable/ic_star_black_24dp.xml
new file mode 100644
index 0000000..85c045c
--- /dev/null
+++ b/sample/src/main/res/drawable/ic_star_black_24dp.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/sample/src/main/res/drawable/image1.jpg b/sample/src/main/res/drawable/image1.jpg
new file mode 100644
index 0000000..5421ce2
Binary files /dev/null and b/sample/src/main/res/drawable/image1.jpg differ
diff --git a/sample/src/main/res/drawable/image2.jpg b/sample/src/main/res/drawable/image2.jpg
new file mode 100644
index 0000000..8d42da4
Binary files /dev/null and b/sample/src/main/res/drawable/image2.jpg differ
diff --git a/sample/src/main/res/drawable/image3.jpg b/sample/src/main/res/drawable/image3.jpg
new file mode 100644
index 0000000..0bc6112
Binary files /dev/null and b/sample/src/main/res/drawable/image3.jpg differ
diff --git a/sample/src/main/res/drawable/image4.jpg b/sample/src/main/res/drawable/image4.jpg
new file mode 100644
index 0000000..f08ede0
Binary files /dev/null and b/sample/src/main/res/drawable/image4.jpg differ
diff --git a/sample/src/main/res/drawable/image5.jpg b/sample/src/main/res/drawable/image5.jpg
new file mode 100644
index 0000000..ec80cb4
Binary files /dev/null and b/sample/src/main/res/drawable/image5.jpg differ
diff --git a/sample/src/main/res/drawable/image6.jpg b/sample/src/main/res/drawable/image6.jpg
new file mode 100644
index 0000000..f2696a2
Binary files /dev/null and b/sample/src/main/res/drawable/image6.jpg differ
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
new file mode 100644
index 0000000..76870e7
--- /dev/null
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -0,0 +1,25 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/layout/image_card.xml b/sample/src/main/res/layout/image_card.xml
new file mode 100644
index 0000000..d31d327
--- /dev/null
+++ b/sample/src/main/res/layout/image_card.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..eca70cf
--- /dev/null
+++ b/sample/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..a2f5908
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1b52399
Binary files /dev/null and b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..ff10afd
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..115a4c7
Binary files /dev/null and b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..dcd3cd8
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..459ca60
Binary files /dev/null and b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..8ca12fe
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..8e19b41
Binary files /dev/null and b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..b824ebd
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..4c19a13
Binary files /dev/null and b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/sample/src/main/res/values-zh-rCN/arrays.xml b/sample/src/main/res/values-zh-rCN/arrays.xml
new file mode 100644
index 0000000..c9afae9
--- /dev/null
+++ b/sample/src/main/res/values-zh-rCN/arrays.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+ -
+ - 卡片从左到右移出
+ - 卡片从右到左移出
+ - 卡片从上到下移出
+ - 卡片从下到上移出
+ - 改变PageMode
+ - 增加每个item之间的偏移
+ - 减少每个item之间的偏移
+ - 增加可见item的数量
+ - 减少可见item的数量
+ - 增加PageMode=true时的滑动阻尼
+ - 减少PageMode=true时的滑动阻尼
+ - 自定义动画
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/values-zh-rCN/strings.xml b/sample/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..08dc386
--- /dev/null
+++ b/sample/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,5 @@
+
+ StackLayoutManagerMaster
+ 带动画滚动到指定位置
+ 不带动画滚动到指定位置
+
diff --git a/sample/src/main/res/values/arrays.xml b/sample/src/main/res/values/arrays.xml
new file mode 100644
index 0000000..35cfefd
--- /dev/null
+++ b/sample/src/main/res/values/arrays.xml
@@ -0,0 +1,19 @@
+
+
+
+ -
+ -
+ - From Right To Left
+ - From Left To Right
+ - From Top To Bottom
+ - From Bottom To Top
+ - Change PageMode
+ - Increase item offset
+ - Decrease item offset
+ - Increase visible itemCount
+ - Decrease visible itemCount
+ - Increase fling damp
+ - Decrease fling damp
+ - Use custom animation
+
+
\ No newline at end of file
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/main/res/values/colors.xml
new file mode 100644
index 0000000..3ab3e9c
--- /dev/null
+++ b/sample/src/main/res/values/colors.xml
@@ -0,0 +1,6 @@
+
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/main/res/values/strings.xml
new file mode 100644
index 0000000..3835c26
--- /dev/null
+++ b/sample/src/main/res/values/strings.xml
@@ -0,0 +1,5 @@
+
+ StackLayoutManagerMaster
+ smoothScroll to
+ scroll to
+
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
new file mode 100644
index 0000000..5885930
--- /dev/null
+++ b/sample/src/main/res/values/styles.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
+
diff --git a/sample/src/test/java/com/littlemango/stacklayoutmanagermaster/ExampleUnitTest.kt b/sample/src/test/java/com/littlemango/stacklayoutmanagermaster/ExampleUnitTest.kt
new file mode 100644
index 0000000..379a710
--- /dev/null
+++ b/sample/src/test/java/com/littlemango/stacklayoutmanagermaster/ExampleUnitTest.kt
@@ -0,0 +1,17 @@
+package com.littlemango.stacklayoutmanagermaster
+
+import org.junit.Test
+
+import org.junit.Assert.*
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * See [testing documentation](http://d.android.com/tools/testing).
+ */
+class ExampleUnitTest {
+ @Test
+ fun addition_isCorrect() {
+ assertEquals(4, 2 + 2)
+ }
+}
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 0000000..3892bda
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1 @@
+include ':sample', ':StackLayoutManager'