├── demo
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── drawable-xhdpi
│ │ │ │ ├── icon1.jpg
│ │ │ │ ├── icon10.jpg
│ │ │ │ ├── icon11.jpg
│ │ │ │ ├── icon12.jpg
│ │ │ │ ├── icon13.jpg
│ │ │ │ ├── icon14.png
│ │ │ │ ├── icon15.jpg
│ │ │ │ ├── icon16.jpg
│ │ │ │ ├── icon17.jpg
│ │ │ │ ├── icon18.jpg
│ │ │ │ ├── icon19.jpg
│ │ │ │ ├── icon2.jpg
│ │ │ │ ├── icon3.jpg
│ │ │ │ ├── icon4.jpg
│ │ │ │ ├── icon5.jpg
│ │ │ │ ├── icon6.jpg
│ │ │ │ ├── icon7.jpg
│ │ │ │ ├── icon8.jpg
│ │ │ │ └── icon9.jpg
│ │ │ ├── values
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ └── layout
│ │ │ │ ├── time_set_single_layout.xml
│ │ │ │ ├── time_set_item.xml
│ │ │ │ ├── activity_stack_layout.xml
│ │ │ │ ├── stack_header.xml
│ │ │ │ ├── activity_time_set_layout.xml
│ │ │ │ ├── activity_stack_list_item.xml
│ │ │ │ ├── activity_double_stack_layout.xml
│ │ │ │ ├── activity_stack_spring_layout.xml
│ │ │ │ ├── stack_single_layout.xml
│ │ │ │ ├── activity_router.xml
│ │ │ │ └── activity_stack_scroll_layout.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── example
│ │ │ │ └── baidu
│ │ │ │ └── app
│ │ │ │ ├── entity
│ │ │ │ └── TimeItemInfo.java
│ │ │ │ ├── activity
│ │ │ │ ├── RouterActivity.java
│ │ │ │ ├── SingleStackScrollActivity.java
│ │ │ │ ├── DoubleStackScrollActivity.java
│ │ │ │ └── TimeSetActivity.java
│ │ │ │ └── adapter
│ │ │ │ ├── StackViewAdapter.java
│ │ │ │ └── DoubleStackViewAdapter.java
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── baidu
│ │ │ └── app
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── baidu
│ │ └── app
│ │ └── ApplicationTest.java
├── build.gradle
├── proguard-rules.pro
└── demo.iml
├── StackscrollLayout
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-mdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ └── ic_launcher.png
│ │ │ ├── drawable-xhdpi
│ │ │ │ └── ic_dismiss_all.png
│ │ │ ├── values
│ │ │ │ ├── attrs.xml
│ │ │ │ ├── strings.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── ids.xml
│ │ │ │ └── dimens.xml
│ │ │ ├── drawable
│ │ │ │ ├── shape_stack.xml
│ │ │ │ ├── scroll_navbar_scrim.xml
│ │ │ │ └── shape_shadow.xml
│ │ │ └── layout
│ │ │ │ ├── stack_list_header.xml
│ │ │ │ ├── stack_list_item.xml
│ │ │ │ └── stack_scrolled_layout.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── baidu
│ │ │ └── scrollstack
│ │ │ ├── uitl
│ │ │ ├── Define.java
│ │ │ ├── BaseInterpolator.java
│ │ │ ├── StackViewBaseAdapter.java
│ │ │ ├── VelocityTracker.java
│ │ │ ├── Pools.java
│ │ │ ├── LocalPathInterpolator.java
│ │ │ └── FlingAnimationUtils.java
│ │ │ ├── view
│ │ │ ├── Gefingerpoken.java
│ │ │ ├── VelocityTrackerInterface.java
│ │ │ ├── ExpandableRowView.java
│ │ │ ├── ScrollAdapter.java
│ │ │ ├── AlphaOptimizedView.java
│ │ │ ├── BounceInterpolator.java
│ │ │ ├── ObservableScrollView.java
│ │ │ ├── StackHeaderView.java
│ │ │ └── ExpandableView.java
│ │ │ └── stack
│ │ │ ├── AmbientState.java
│ │ │ ├── StackIndentationFunctor.java
│ │ │ ├── AnimationFilter.java
│ │ │ ├── StackScrollLayoutParent.java
│ │ │ ├── PiecewiseLinearIndentationFunctor.java
│ │ │ ├── StackScrollerDecorView.java
│ │ │ └── StackScrollState.java
│ ├── test
│ │ └── java
│ │ │ └── com
│ │ │ └── example
│ │ │ └── baidu
│ │ │ └── statckscrolllayout
│ │ │ └── ExampleUnitTest.java
│ └── androidTest
│ │ └── java
│ │ └── com
│ │ └── example
│ │ └── baidu
│ │ └── statckscrolllayout
│ │ └── ApplicationTest.java
├── build.gradle
└── StackscrollLayout.iml
├── settings.gradle
├── gif
└── stack_scroll_layout.gif
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── local.properties
├── gradle.properties
├── StatckScrollLayout.iml
├── gradlew.bat
├── README.md
└── gradlew
/demo/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/StackscrollLayout/.gitignore:
--------------------------------------------------------------------------------
1 | build/
2 | .DS_Store
3 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':StackscrollLayout'
2 | include ':demo'
3 |
--------------------------------------------------------------------------------
/gif/stack_scroll_layout.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/gif/stack_scroll_layout.gif
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon1.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon10.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon10.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon11.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon11.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon12.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon12.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon13.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon13.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon14.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon14.png
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon15.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon15.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon16.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon16.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon17.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon17.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon18.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon18.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon19.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon19.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon2.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon3.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon4.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon5.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon6.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon7.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon8.jpg
--------------------------------------------------------------------------------
/demo/src/main/res/drawable-xhdpi/icon9.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/demo/src/main/res/drawable-xhdpi/icon9.jpg
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/drawable-xhdpi/ic_dismiss_all.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/stillwheel/StatckScrollLayout/HEAD/StackscrollLayout/src/main/res/drawable-xhdpi/ic_dismiss_all.png
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Apr 15 16:22:38 CST 2016
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-2.10-all.zip
7 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/Define.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | import android.os.Build;
4 |
5 | /**
6 | * Created by baidu on 16/4/1.
7 | */
8 | public class Define {
9 | public static final int SDK_INT = Build.VERSION.SDK_INT;
10 | }
11 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/Gefingerpoken.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.view.MotionEvent;
4 |
5 | public interface Gefingerpoken {
6 | boolean onInterceptTouchEvent(MotionEvent ev);
7 | boolean onTouchEvent(MotionEvent ev);
8 | }
9 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StackScrollView
3 |
5 | platform
6 |
7 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 | #FF6E6E6E
8 | #00888888
9 |
10 |
--------------------------------------------------------------------------------
/demo/src/test/java/com/example/baidu/app/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/StackscrollLayout/src/test/java/com/example/baidu/statckscrolllayout/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
9 | */
10 | public class ExampleUnitTest {
11 | @Test
12 | public void addition_isCorrect() throws Exception {
13 | assertEquals(4, 2 + 2);
14 | }
15 | }
--------------------------------------------------------------------------------
/demo/src/androidTest/java/com/example/baidu/app/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/local.properties:
--------------------------------------------------------------------------------
1 | ## This file is automatically generated by Android Studio.
2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
3 | #
4 | # This file should *NOT* be checked into Version Control Systems,
5 | # as it contains information specific to your local configuration.
6 | #
7 | # Location of the SDK. This is only used by Gradle.
8 | # For customization when using a Version Control System, please read the
9 | # header note.
10 | sdk.dir=/Users/baidu/Library/Android/sdk
--------------------------------------------------------------------------------
/StackscrollLayout/src/androidTest/java/com/example/baidu/statckscrolllayout/ApplicationTest.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack;
2 |
3 | import android.app.Application;
4 | import android.test.ApplicationTestCase;
5 |
6 | /**
7 | * Testing Fundamentals
8 | */
9 | public class ApplicationTest extends ApplicationTestCase {
10 | public ApplicationTest() {
11 | super(Application.class);
12 | }
13 | }
--------------------------------------------------------------------------------
/StackscrollLayout/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 |
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | }
24 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/drawable/shape_stack.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
10 |
11 |
16 |
17 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/VelocityTrackerInterface.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.view.MotionEvent;
4 |
5 | /**
6 | * An interface for a velocity tracker to delegate. To be implemented by different velocity tracking
7 | * algorithms.
8 | */
9 | public interface VelocityTrackerInterface {
10 | void addMovement(MotionEvent event);
11 |
12 | void computeCurrentVelocity(int units);
13 |
14 | float getXVelocity();
15 |
16 | float getYVelocity();
17 |
18 | void recycle();
19 | }
20 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
16 |
17 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/BaseInterpolator.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | /**
6 | * Created by baidu on 16/4/1.
7 | */
8 | abstract public class BaseInterpolator implements Interpolator {
9 | private int mChangingConfiguration;
10 | /**
11 | * @hide
12 | */
13 | public int getChangingConfiguration() {
14 | return mChangingConfiguration;
15 | }
16 |
17 | /**
18 | * @hide
19 | */
20 | void setChangingConfiguration(int changingConfiguration) {
21 | mChangingConfiguration = changingConfiguration;
22 | }
23 | }
--------------------------------------------------------------------------------
/demo/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 22
5 | buildToolsVersion "22.0.1"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 22
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 | }
20 |
21 | dependencies {
22 | compile fileTree(dir: 'libs', include: ['*.jar'])
23 | compile project(':StackscrollLayout')
24 | compile 'com.android.support:appcompat-v7:22.0.0'
25 | }
26 |
--------------------------------------------------------------------------------
/demo/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 /Users/baidu/Library/Android/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 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/entity/TimeItemInfo.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.entity;
2 |
3 | /**
4 | * Created by LingJian·HE on 16/8/10.
5 | */
6 | public class TimeItemInfo {
7 |
8 | String time;
9 | boolean isSelect;
10 |
11 | public TimeItemInfo(String time, boolean isSelect) {
12 | this.time = time;
13 | this.isSelect = isSelect;
14 | }
15 |
16 | public String getTime() {
17 | return time;
18 | }
19 |
20 | public void setTime(String time) {
21 | this.time = time;
22 | }
23 |
24 | public boolean isSelect() {
25 | return isSelect;
26 | }
27 |
28 | public void setSelect(boolean select) {
29 | isSelect = select;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/ExpandableRowView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import com.baidu.scrollstack.stack.StackScrollerDecorView;
4 | import com.baidu.scrollstack.R;
5 |
6 | import android.content.Context;
7 | import android.util.AttributeSet;
8 | import android.view.View;
9 |
10 | /**
11 | * Like {@link ExpandableView}, but setting an outline for the height and clipping.
12 | */
13 | public class ExpandableRowView extends StackScrollerDecorView {
14 |
15 | public ExpandableRowView(Context context, AttributeSet attrs) {
16 | super(context, attrs);
17 | }
18 |
19 | @Override
20 | protected View findContentView() {
21 | return findViewById(R.id.content);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/ScrollAdapter.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.view.View;
4 |
5 | /**
6 | * A scroll adapter which can be queried for meta information about the scroll state
7 | */
8 | public interface ScrollAdapter {
9 |
10 | /**
11 | * @return Whether the view returned by {@link #getHostView()} is scrolled to the top
12 | */
13 | public boolean isScrolledToTop();
14 |
15 | /**
16 | * @return Whether the view returned by {@link #getHostView()} is scrolled to the bottom
17 | */
18 | public boolean isScrolledToBottom();
19 |
20 | /**
21 | * @return The view in which the scrolling is performed
22 | */
23 | public View getHostView();
24 | }
25 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/time_set_single_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
13 |
14 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/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: -Xmx10248m -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 | #Tue Mar 29 13:34:15 CST 2016
16 | systemProp.http.proxyHost=agent.baidu.com
17 | systemProp.http.proxyPort=8118
18 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/AlphaOptimizedView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.View;
6 |
7 | /**
8 | * A View which does not have overlapping renderings commands and therefore does not need a
9 | * layer when alpha is changed.
10 | */
11 | public class AlphaOptimizedView extends View
12 | {
13 | public AlphaOptimizedView(Context context) {
14 | super(context);
15 | }
16 |
17 | public AlphaOptimizedView(Context context, AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 |
21 | public AlphaOptimizedView(Context context, AttributeSet attrs, int defStyleAttr) {
22 | super(context, attrs, defStyleAttr);
23 | }
24 |
25 | @Override
26 | public boolean hasOverlappingRendering() {
27 | return false;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/BounceInterpolator.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.view.animation.Interpolator;
4 |
5 | /**
6 | * An implementation of a bouncer interpolator optimized for unlock hinting.
7 | */
8 | public class BounceInterpolator implements Interpolator {
9 |
10 | private final static float SCALE_FACTOR = 7.5625f;
11 |
12 | @Override
13 | public float getInterpolation(float t) {
14 | t *= 11f / 10f;
15 | if (t < 4f / 11f) {
16 | return SCALE_FACTOR * t * t;
17 | } else if (t < 8f / 11f) {
18 | float t2 = t - 6f / 11f;
19 | return SCALE_FACTOR * t2 * t2 + 3f / 4f;
20 | } else if (t < 10f / 11f) {
21 | float t2 = t - 9f / 11f;
22 | return SCALE_FACTOR * t2 * t2 + 15f / 16f;
23 | } else {
24 | return 1;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/layout/stack_list_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/StatckScrollLayout.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/demo/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | StackScrollView
3 | 周大福
4 | 六福珠宝
5 | 达利园
6 | HARSON
7 | 九阳
8 | 苏泊尔
9 | 清风
10 | 爱国者
11 | 天堂伞
12 | 洛娃
13 | Yoobao
14 | DESLON
15 | 三利
16 | 纽曼
17 | 费列罗
18 | 三只松鼠
19 | 西域美农
20 | 周黑鸭
21 | 达利园
22 |
23 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/time_set_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
15 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/drawable/scroll_navbar_scrim.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
18 |
19 |
20 |
25 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/layout/stack_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
13 |
14 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_stack_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
14 |
18 |
19 |
26 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/stack_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
17 |
26 |
27 |
--------------------------------------------------------------------------------
/demo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
9 |
10 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
22 |
23 |
24 |
27 |
28 |
29 |
32 |
33 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_time_set_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_stack_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
15 |
22 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_double_stack_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
10 |
11 |
18 |
19 |
25 |
26 |
32 |
33 |
34 |
35 |
36 |
42 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/activity/RouterActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.activity;
2 |
3 | import com.example.baidu.app.R;
4 |
5 | import android.app.Activity;
6 | import android.content.Intent;
7 | import android.os.Bundle;
8 | import android.view.View;
9 |
10 | /**
11 | * Created by LingJian·HE on 16/8/9.
12 | */
13 | public class RouterActivity extends Activity {
14 |
15 | View.OnClickListener onDemo1ClickListener = new View.OnClickListener() {
16 | @Override
17 | public void onClick(View v) {
18 | showDemo(SingleStackScrollActivity.class);
19 | }
20 | };
21 |
22 | View.OnClickListener onDemo2ClickListener = new View.OnClickListener() {
23 | @Override
24 | public void onClick(View v) {
25 | showDemo(DoubleStackScrollActivity.class);
26 | }
27 | };
28 |
29 | View.OnClickListener onDemo3ClickListener = new View.OnClickListener() {
30 | @Override
31 | public void onClick(View v) {
32 | showDemo(TimeSetActivity.class);
33 | }
34 | };
35 |
36 | @Override
37 | protected void onCreate(Bundle savedInstanceState) {
38 | super.onCreate(savedInstanceState);
39 | setContentView(R.layout.activity_router);
40 | initView();
41 | }
42 |
43 | private void initView() {
44 | findViewById(R.id.demo_single_stack).setOnClickListener(onDemo1ClickListener);
45 | findViewById(R.id.demo_double_stack).setOnClickListener(onDemo2ClickListener);
46 | findViewById(R.id.demo_time_set).setOnClickListener(onDemo3ClickListener);
47 | }
48 |
49 | private void showDemo(Class clsName) {
50 | Intent intent = new Intent();
51 | intent.setClass(this, clsName);
52 | startActivity(intent);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/adapter/StackViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.adapter;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.baidu.scrollstack.uitl.StackViewBaseAdapter;
7 | import com.example.baidu.app.R;
8 | import com.example.baidu.app.entity.TimeItemInfo;
9 |
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.view.ViewGroup;
13 | import android.widget.TextView;
14 |
15 | /**
16 | * Created by helingjian on 16/3/29.
17 | */
18 | public class StackViewAdapter extends StackViewBaseAdapter{
19 |
20 | private List list = new ArrayList();
21 | private LayoutInflater layoutInflater;
22 |
23 | public StackViewAdapter(LayoutInflater inflater){
24 | layoutInflater = inflater;
25 | }
26 |
27 | public void setData(List list) {
28 | this.list = list;
29 | }
30 |
31 | @Override
32 | public int getCount() {
33 | return list.size();
34 | }
35 |
36 | @Override
37 | public Object getItem(int position) {
38 | return list.get(position);
39 | }
40 |
41 | @Override
42 | public long getItemId(int position) {
43 | return 0;
44 | }
45 |
46 | @Override
47 | public View getView(int position, View convertView, ViewGroup parent) {
48 | ViewHolder viewHolder;
49 | if (convertView == null) {
50 | convertView = layoutInflater.inflate(R.layout.time_set_item, null);
51 | viewHolder = new ViewHolder();
52 | viewHolder.textView = (TextView) convertView.findViewById(R.id.text);
53 | convertView.setTag(viewHolder);
54 | } else {
55 | viewHolder = (ViewHolder) convertView.getTag();
56 | }
57 | viewHolder.textView.setText(list.get(position).getTime());
58 | convertView.setTag(list.get(position));
59 | return convertView;
60 | }
61 |
62 | class ViewHolder {
63 | TextView textView;
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/values/ids.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_stack_spring_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
20 |
24 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
46 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/drawable/shape_shadow.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | -
5 |
6 |
7 |
8 |
9 |
10 | -
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | -
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | -
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 | -
51 |
52 |
53 |
54 |
55 |
56 |
57 | -
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/adapter/DoubleStackViewAdapter.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.adapter;
2 |
3 | import com.baidu.scrollstack.uitl.StackViewBaseAdapter;
4 | import com.example.baidu.app.R;
5 |
6 | import android.view.LayoutInflater;
7 | import android.view.View;
8 | import android.view.ViewGroup;
9 | import android.widget.ImageView;
10 | import android.widget.TextView;
11 |
12 | /**
13 | * Created by helingjian on 16/3/29.
14 | */
15 | public class DoubleStackViewAdapter extends StackViewBaseAdapter {
16 |
17 | private int[] drawableIds = new int[] {};
18 | private String[] titles = new String[] {};
19 | private LayoutInflater layoutInflater;
20 |
21 | public DoubleStackViewAdapter(LayoutInflater inflater) {
22 | layoutInflater = inflater;
23 | }
24 |
25 | public void setData(int[] drawables, String[] titleIds) {
26 | this.drawableIds = drawables;
27 | this.titles = titleIds;
28 | }
29 |
30 | @Override
31 | public int getCount() {
32 | return drawableIds.length;
33 | }
34 |
35 | @Override
36 | public Object getItem(int position) {
37 | return null;
38 | }
39 |
40 | @Override
41 | public long getItemId(int position) {
42 | return 0;
43 | }
44 |
45 | @Override
46 | public View getView(int position, View convertView, ViewGroup parent) {
47 | ViewHolder viewHolder;
48 | if (convertView == null) {
49 | convertView = layoutInflater.inflate(R.layout.activity_stack_list_item, null);
50 | viewHolder = new ViewHolder();
51 | viewHolder.textView = (TextView) convertView.findViewById(R.id.text);
52 | viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
53 | convertView.setTag(viewHolder);
54 | } else {
55 | viewHolder = (ViewHolder) convertView.getTag();
56 | }
57 | viewHolder.textView.setText(titles[position]);
58 | viewHolder.imageView.setImageResource(drawableIds[position]);
59 | return convertView;
60 | }
61 |
62 | class ViewHolder {
63 | TextView textView;
64 | ImageView imageView;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/stack_single_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
12 |
13 |
20 |
24 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
46 |
47 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/StackViewBaseAdapter.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | import android.database.DataSetObservable;
4 | import android.database.DataSetObserver;
5 | import android.view.View;
6 | import android.view.ViewGroup;
7 | import android.widget.Adapter;
8 | import android.widget.ListAdapter;
9 | import android.widget.SpinnerAdapter;
10 |
11 | /**
12 | * Created by helingjian on 16/3/25.
13 | */
14 | public abstract class StackViewBaseAdapter implements ListAdapter, SpinnerAdapter {
15 | private final DataSetObservable mDataSetObservable = new DataSetObservable();
16 |
17 | public boolean hasStableIds() {
18 | return false;
19 | }
20 |
21 | public void registerDataSetObserver(DataSetObserver observer) {
22 | mDataSetObservable.registerObserver(observer);
23 | }
24 |
25 | public void unregisterDataSetObserver(DataSetObserver observer) {
26 | mDataSetObservable.unregisterObserver(observer);
27 | }
28 |
29 | /**
30 | * Notifies the attached observers that the underlying data has been changed
31 | * and any View reflecting the data set should refresh itself.
32 | */
33 | public void notifyDataSetChanged() {
34 | mDataSetObservable.notifyChanged();
35 | }
36 |
37 | /**
38 | * Notifies the attached observers that the underlying data is no longer valid
39 | * or available. Once invoked this adapter is no longer valid and should
40 | * not report further data set changes.
41 | */
42 | public void notifyDataSetInvalidated() {
43 | mDataSetObservable.notifyInvalidated();
44 | }
45 |
46 | public boolean areAllItemsEnabled() {
47 | return true;
48 | }
49 |
50 | public boolean isEnabled(int position) {
51 | return true;
52 | }
53 |
54 | public View getDropDownView(int position, View convertView, ViewGroup parent) {
55 | return getView(position, convertView, parent);
56 | }
57 |
58 | public int getItemViewType(int position) {
59 | return 0;
60 | }
61 |
62 | public int getViewTypeCount() {
63 | return 1;
64 | }
65 |
66 | public boolean isEmpty() {
67 | return getCount() == 0;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/AmbientState.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import java.util.ArrayList;
4 |
5 | import android.view.View;
6 |
7 | /**
8 | * A global state to track all input states for the algorithm.
9 | */
10 | public class AmbientState {
11 | private ArrayList mDraggedViews = new ArrayList();
12 | private int mScrollY;
13 | private boolean mDimmed;
14 | private float mOverScrollTopAmount;
15 | private float mOverScrollBottomAmount;
16 | private boolean mDark;
17 | private boolean mHideSensitive;
18 |
19 | public int getScrollY() {
20 | return mScrollY;
21 | }
22 |
23 | public void setScrollY(int scrollY) {
24 | this.mScrollY = scrollY;
25 | }
26 |
27 | public void onBeginDrag(View view) {
28 | mDraggedViews.add(view);
29 | }
30 |
31 | public void onDragFinished(View view) {
32 | mDraggedViews.remove(view);
33 | }
34 |
35 | public ArrayList getDraggedViews() {
36 | return mDraggedViews;
37 | }
38 |
39 | /**
40 | * @param dimmed Whether we are in a dimmed state (on the lockscreen), where the backgrounds are
41 | * translucent and everything is scaled back a bit.
42 | */
43 | public void setDimmed(boolean dimmed) {
44 | mDimmed = dimmed;
45 | }
46 |
47 | /** In dark mode, we draw as little as possible, assuming a black background */
48 | public void setDark(boolean dark) {
49 | mDark = dark;
50 | }
51 |
52 | public void setHideSensitive(boolean hideSensitive) {
53 | mHideSensitive = hideSensitive;
54 | }
55 |
56 |
57 | public boolean isDimmed() {
58 | return mDimmed;
59 | }
60 |
61 | public boolean isDark() {
62 | return mDark;
63 | }
64 |
65 | public boolean isHideSensitive() {
66 | return mHideSensitive;
67 | }
68 |
69 | public void setOverScrollAmount(float amount, boolean onTop) {
70 | if (onTop) {
71 | mOverScrollTopAmount = amount;
72 | } else {
73 | mOverScrollBottomAmount = amount;
74 | }
75 | }
76 |
77 | public float getOverScrollAmount(boolean top) {
78 | return top ? mOverScrollTopAmount : mOverScrollBottomAmount;
79 | }
80 |
81 | }
82 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_router.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
16 |
17 |
23 |
24 |
34 |
35 |
45 |
46 |
56 |
57 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/VelocityTracker.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2014 The Android Open Source Project
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License
15 | */
16 |
17 | package com.baidu.scrollstack.uitl;
18 |
19 | import com.baidu.scrollstack.view.VelocityTrackerInterface;
20 |
21 | import android.view.MotionEvent;
22 |
23 | /**
24 | * An implementation of {@link VelocityTrackerInterface} using the platform-standard
25 | * {@link android.view.VelocityTracker}.
26 | */
27 | public class VelocityTracker implements VelocityTrackerInterface {
28 |
29 | private static final Pools.SynchronizedPool sPool =
30 | new Pools.SynchronizedPool<>(2);
31 |
32 | private android.view.VelocityTracker mTracker;
33 |
34 | public static VelocityTracker obtain() {
35 | VelocityTracker tracker = sPool.acquire();
36 | if (tracker == null) {
37 | tracker = new VelocityTracker();
38 | }
39 | tracker.setTracker(android.view.VelocityTracker.obtain());
40 | return tracker;
41 | }
42 |
43 | public void setTracker(android.view.VelocityTracker tracker) {
44 | mTracker = tracker;
45 | }
46 |
47 | @Override
48 | public void addMovement(MotionEvent event) {
49 | mTracker.addMovement(event);
50 | }
51 |
52 | @Override
53 | public void computeCurrentVelocity(int units) {
54 | mTracker.computeCurrentVelocity(units);
55 | }
56 |
57 | @Override
58 | public float getXVelocity() {
59 | return mTracker.getXVelocity();
60 | }
61 |
62 | @Override
63 | public float getYVelocity() {
64 | return mTracker.getYVelocity();
65 | }
66 |
67 | @Override
68 | public void recycle() {
69 | mTracker.recycle();
70 | sPool.release(this);
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/layout/stack_scrolled_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
19 |
20 |
27 |
31 |
38 |
39 |
40 |
41 |
45 |
46 |
47 |
48 |
49 |
50 |
57 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/demo/src/main/res/layout/activity_stack_scroll_layout.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
12 |
13 |
20 |
24 |
31 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
50 |
51 |
52 |
53 |
60 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 52dp
5 |
6 | 256dp
7 |
8 | 10dp
9 |
10 | 116dp
11 |
12 | 84dp
13 | 25dp
14 |
15 | 12dp
16 |
17 | 12dp
18 |
19 | 0dp
20 | 200dp
21 | !-- The padding between the individual notification cards. -->
22 | 10dp
23 |
24 | 36dp
25 | 10dp
26 |
27 | 8dp
28 |
29 | 70dp
30 |
31 | 12dp
32 |
33 | 2dp
34 |
35 | 12dp
36 |
37 | 12dp
38 |
39 | 1dp
40 |
41 | 0dp
42 | 40dp
43 | -1px
44 | 0x37
45 | 75dp
46 |
47 | 60dp
48 |
49 | 3dp
50 | 300dp
51 |
52 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/StackIndentationFunctor.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | /**
4 | * A functor which can be queried for offset given the number of items before it.
5 | */
6 | public abstract class StackIndentationFunctor {
7 |
8 | protected int mTotalTransitionDistance;
9 | protected int mDistanceToPeekStart;
10 | protected int mMaxItemsInStack;
11 | protected int mPeekSize;
12 | protected boolean mStackStartsAtPeek;
13 |
14 | /**
15 | * @param maxItemsInStack The maximum number of items which should be visible at the same time,
16 | * i.e the function returns totalTransitionDistance for the element with
17 | * index maxItemsInStack
18 | * @param peekSize The visual appearance of this is how far the cards in the stack peek
19 | * out below the top card and it is measured in real pixels.
20 | * Note that the visual appearance does not necessarily always correspond to
21 | * the actual visual distance below the top card but is a maximum,
22 | * achieved when the next card just starts transitioning into the stack and
23 | * the stack is full.
24 | * If distanceToPeekStart is 0, we directly start at the peek, otherwise the
25 | * first element transitions between 0 and distanceToPeekStart.
26 | * Visualization:
27 | * --------------------------------------------------- ---
28 | * | | |
29 | * | FIRST ITEM | | <- distanceToPeekStart
30 | * | | |
31 | * |---------------------------------------------------| --- ---
32 | * |__________________SECOND ITEM______________________| | <- peekSize
33 | * |===================================================| _|_
34 | *
35 | * @param distanceToPeekStart The distance to the start of the peak.
36 | */
37 | StackIndentationFunctor(int maxItemsInStack, int peekSize, int distanceToPeekStart) {
38 | mDistanceToPeekStart = distanceToPeekStart;
39 | mStackStartsAtPeek = mDistanceToPeekStart == 0;
40 | mMaxItemsInStack = maxItemsInStack;
41 | mPeekSize = peekSize;
42 | updateTotalTransitionDistance();
43 |
44 | }
45 |
46 | private void updateTotalTransitionDistance() {
47 | mTotalTransitionDistance = mDistanceToPeekStart + mPeekSize;
48 | }
49 |
50 | public void setPeekSize(int mPeekSize) {
51 | this.mPeekSize = mPeekSize;
52 | updateTotalTransitionDistance();
53 | }
54 |
55 | public void setDistanceToPeekStart(int distanceToPeekStart) {
56 | mDistanceToPeekStart = distanceToPeekStart;
57 | mStackStartsAtPeek = mDistanceToPeekStart == 0;
58 | updateTotalTransitionDistance();
59 | }
60 |
61 | /**
62 | * Gets the offset of this Functor given a the quantity of items before it
63 | *
64 | * @param itemsBefore how many items are already in the stack before this element
65 | * @return the offset
66 | */
67 | public abstract float getValue(float itemsBefore);
68 | }
69 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/AnimationFilter.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import java.util.ArrayList;
4 |
5 | /**
6 | * Filters the animations for only a certain type of properties.
7 | */
8 | public class AnimationFilter {
9 | boolean animateAlpha;
10 | boolean animateY;
11 | boolean animateZ;
12 | boolean animateScale;
13 | boolean animateHeight;
14 | boolean animateTopInset;
15 | boolean animateDimmed;
16 | boolean animateDark;
17 | boolean animateHideSensitive;
18 | boolean hasDelays;
19 | boolean hasGoToFullShadeEvent;
20 |
21 | public AnimationFilter animateAlpha() {
22 | animateAlpha = true;
23 | return this;
24 | }
25 |
26 | public AnimationFilter animateY() {
27 | animateY = true;
28 | return this;
29 | }
30 |
31 | public AnimationFilter hasDelays() {
32 | hasDelays = true;
33 | return this;
34 | }
35 |
36 | public AnimationFilter animateZ() {
37 | animateZ = true;
38 | return this;
39 | }
40 |
41 | public AnimationFilter animateScale() {
42 | animateScale = true;
43 | return this;
44 | }
45 |
46 | public AnimationFilter animateHeight() {
47 | animateHeight = true;
48 | return this;
49 | }
50 |
51 | public AnimationFilter animateTopInset() {
52 | animateTopInset = true;
53 | return this;
54 | }
55 |
56 | public AnimationFilter animateDimmed() {
57 | animateDimmed = true;
58 | return this;
59 | }
60 |
61 | public AnimationFilter animateDark() {
62 | animateDark = true;
63 | return this;
64 | }
65 |
66 | public AnimationFilter animateHideSensitive() {
67 | animateHideSensitive = true;
68 | return this;
69 | }
70 |
71 | /**
72 | * Combines multiple filters into {@code this} filter, using or as the operand .
73 | *
74 | * @param events The animation events from the filters to combine.
75 | */
76 | public void applyCombination(ArrayList events) {
77 | reset();
78 | int size = events.size();
79 | for (int i = 0; i < size; i++) {
80 | combineFilter(events.get(i).filter);
81 | if (events.get(i).animationType ==
82 | StackScrollLayout.AnimationEvent.ANIMATION_TYPE_GO_TO_FULL_SHADE) {
83 | hasGoToFullShadeEvent = true;
84 | }
85 | }
86 | }
87 |
88 | private void combineFilter(AnimationFilter filter) {
89 | animateAlpha |= filter.animateAlpha;
90 | animateY |= filter.animateY;
91 | animateZ |= filter.animateZ;
92 | animateScale |= filter.animateScale;
93 | animateHeight |= filter.animateHeight;
94 | animateTopInset |= filter.animateTopInset;
95 | animateDimmed |= filter.animateDimmed;
96 | animateDark |= filter.animateDark;
97 | animateHideSensitive |= filter.animateHideSensitive;
98 | hasDelays |= filter.hasDelays;
99 | }
100 |
101 | private void reset() {
102 | animateAlpha = false;
103 | animateY = false;
104 | animateZ = false;
105 | animateScale = false;
106 | animateHeight = false;
107 | animateTopInset = false;
108 | animateDimmed = false;
109 | animateDark = false;
110 | animateHideSensitive = false;
111 | hasDelays = false;
112 | hasGoToFullShadeEvent = false;
113 | }
114 | }
115 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/StackScrollLayoutParent.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import java.lang.reflect.Field;
4 |
5 | import com.baidu.scrollstack.R;;
6 |
7 | import android.content.Context;
8 | import android.util.AttributeSet;
9 | import android.util.TypedValue;
10 | import android.view.View;
11 | import android.view.ViewGroup;
12 |
13 | /**
14 | * Created by helingjian on 16/3/14.
15 | */
16 | public class StackScrollLayoutParent extends ViewGroup{
17 |
18 | protected int mPaddingLeft;
19 | protected int mPaddingTop;
20 | protected int mPaddingRight;
21 | protected int mPaddingBottom;
22 | protected int mLeft;
23 | protected int mTop;
24 | protected int mRight;
25 | protected int mBottom;
26 | protected Context mContext;
27 | protected Field mScrollXField;
28 | protected Field mScrollYField;
29 |
30 | OnHierarchyChangeListener mOnHierarchyChangeListener = new OnHierarchyChangeListener() {
31 | @Override
32 | public void onChildViewAdded(View parent, View child) {
33 | onViewAdded(child);
34 | }
35 |
36 | @Override
37 | public void onChildViewRemoved(View parent, View child) {
38 | onViewRemoved(child);
39 | }
40 | };
41 | public StackScrollLayoutParent(Context context) {
42 | super(context);
43 | init();
44 | }
45 |
46 | public StackScrollLayoutParent(Context context, AttributeSet attrs) {
47 | super(context, attrs);
48 | init();
49 | }
50 |
51 | public StackScrollLayoutParent(Context context, AttributeSet attrs, int defStyleAttr) {
52 | super(context, attrs, defStyleAttr);
53 | init();
54 | }
55 |
56 | @Override
57 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
58 | }
59 |
60 | private void init() {
61 | mPaddingLeft = getPaddingLeft();
62 | mPaddingTop = getPaddingTop();
63 | mPaddingRight = getPaddingRight();
64 | mPaddingBottom = getPaddingBottom();
65 | mLeft = getLeft();
66 | mTop = getTop();
67 | mRight = getRight();
68 | mBottom = getBottom();
69 | mContext = getContext();
70 | setOnHierarchyChangeListener(mOnHierarchyChangeListener);
71 | }
72 |
73 | public void invalidateParentIfNeeded() {
74 | if (isHardwareAccelerated() && getParent() instanceof View) {
75 | ((View) getParent()).invalidate();
76 | }
77 | }
78 |
79 | public void setScrollXObject(int value){
80 | try {
81 | if(mScrollXField != null) {
82 | mScrollXField.set(this, value);
83 | return;
84 | }
85 | Class workerClass = Class.forName(View.class.getName());
86 | Field mScrollXField = workerClass.getDeclaredField("mScrollX");
87 | mScrollXField.setAccessible(true);
88 | mScrollXField.set(this, value);
89 | } catch (Exception e) {
90 | e.printStackTrace();
91 | }
92 | }
93 |
94 | public void setScrollYObject(int value){
95 | try {
96 | if(mScrollYField != null) {
97 | mScrollYField.set(this, value);
98 | return;
99 | }
100 | Class workerClass = Class.forName(View.class.getName());
101 | Field mScrollYField = workerClass.getDeclaredField("mScrollY");
102 | mScrollYField.setAccessible(true);
103 | mScrollYField.set(this, value);
104 | } catch (Exception e) {
105 | e.printStackTrace();
106 | }
107 | }
108 |
109 | public void onViewAdded(View child) {
110 |
111 | }
112 |
113 | public void onViewRemoved(View child) {
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # StackScrollLayout
2 | ##可折叠的下拉菜单动效展示
3 | 
4 |
5 |
6 | ###配置说明
7 | ``````
8 | 1.直接将/StackscrollLayout/build/outputs/aar/StackscrollLayout-release.aar放入lib目录下,
9 | 并修改build.gradle文件
10 | repositories {
11 | flatDir {
12 | dirs 'libs'
13 | }
14 | }
15 |
16 | dependencies {
17 | compile fileTree(dir: 'libs', include: ['*.jar'])
18 | testCompile 'junit:junit:4.12'
19 | compile 'com.android.support:appcompat-v7:23.3.0'
20 | compile(name:'StackscrollLayout-release',ext:'aar');
21 | }
22 |
23 | 2.如需修改源代码, 请在项目中导入StackscrollLayout这个module, 并做如下配置
24 | build.gradle
25 | dependencies {
26 | ...
27 | compile project(':StackscrollLayout')
28 | ...
29 | }
30 | settings.gradle
31 | include ':StackscrollLayout'
32 | ``````
33 |
34 | ###使用说明
35 | 具体使用方法可参考上传的demo
36 | ``````
37 | 1.初始化
38 | stackViewAdapter = new StackViewAdapter(LayoutInflater.from(this)); // 创建一个adapter
39 | stackViewAdapter.setData(getData()); // 为Adapter绑定数据
40 | mStackScroller.setAdapter(stackViewAdapter); //为Scroller设置adapter
41 | mStackScroller.setOnItemClickListener(onStackItemClickListener); // 绑定点击事件
42 | stackViewAdapter继承StackViewBaseAdapter实现了ListAdapter, SpinnerAdapter接口,
43 | 所以这里的adapter实现逻辑与ListView的adapter基本一样, 刷新数据时只要调下stackViewAdapter的notifyDataSetChanged即可
44 | adapter的实现逻辑主要在StackScrollLayout.java与StackViewBaseAdapter.java中
45 |
46 | 2.一些方法
47 | 展开: stackScrollPanelView.expand(false);
48 | 关闭: stackScrollPanelView.collapse(false);
49 | 其中括号里的参数为delay, false表示直接执行操作, true表示延迟一段时间后执行
50 | 改delay的值可以在PanelView修改对应delay参数
51 |
52 | 3.核心代码
53 | 第一次点击屏幕时,由于此时view未展开,Touch事件首先被父类PanelView拦截处理:
54 | 代码:PanelView的ActionDown中return true拦截该事件;
55 |
56 |
57 | Action_Down ---> (mExpandedHeight == 0 || PanelView正在进行展开或收合动画) Touch事件首先被父类PanelView拦截处理:
58 | 然后onTouchEvent的ActionMove方法:此处主要处理下拉时更新View的状态
59 | final float newHeight = Math.max(0, h + mInitialOffsetOnTouch);
60 | setExpandedHeightInternal(newHeight);
61 | setExpandedHeightInternal->onHeightUpdated->mStackScroller.setStackHeight(expandedHeight),这里最终实现下拉展开效果
62 |
63 | Action_Down ---> (PanelView已经全部展开时) 最后会交友StackScrollLayout的onScrollTouch函数处理
64 | overScrollBy:随手指拖拽
65 | overScrollDown:向下拖拽到达顶部时,继续向下拖拽
66 | overScrollUp:向上拖拽到达底部时,继续向上拖拽
67 | setOverScrollAmount ---> onOverScrolled --> applyCurrentState ---> StackScrollState的apply()方法,
68 | apply方法是处理view的层叠效果的
69 |
70 | updateChildren() ->StackScrollAlgorithm的getStackScrollState ---> 调用findNumberOfItemsInTopStackAndUpdateState计算即将被完全遮挡的view 的index
71 | 然后再调用updatePositionsForState方法计算每个View的currentYPosition = childViewState.yTranslation + childHeight + mPaddingBetweenElements;
72 |
73 |
74 | 4.版本兼容问题
75 | API21一下不支持elevation,所以API21以下的View层进行了下处理:
76 | StackScrollLayout.java
77 | @Override
78 | protected void dispatchDraw(Canvas canvas) { // 重绘View的涂层
79 | if (Define.SDK_INT < 21) {
80 | for (int i = getChildCount() - 1; i >= 0; i--) {
81 | View child = getChildAt(i);
82 | drawChild(canvas, child, getDrawingTime());
83 | }
84 | } else {
85 | super.dispatchDraw(canvas);
86 | }
87 | }
88 | ExpandableView.java
89 | @Override
90 | protected void dispatchDraw(Canvas canvas) { // 重绘View边缘的阴影
91 | if (Define.SDK_INT < 21) {
92 | super.dispatchDraw(canvas);
93 |
94 | canvas.save();
95 | Paint paint = new Paint();
96 | paint.setColor(Color.TRANSPARENT);
97 | paint.setAlpha(currentAlpha);
98 | paint.setStrokeWidth(0);
99 |
100 | Shader shader = new LinearGradient(0, getHeight() - shadowRadius, 0, getHeight(),
101 | new int[] {shadowStartColor, shadowEndColor}, null, Shader.TileMode.MIRROR);
102 | paint.setShader(shader);
103 | canvas.drawRect(0, getHeight() - shadowRadius, getWidth(), getHeight(), paint);
104 |
105 | canvas.restore();
106 | } else {
107 | super.dispatchDraw(canvas);
108 | }
109 | }
110 | ``````````
111 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/PiecewiseLinearIndentationFunctor.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import java.util.ArrayList;
4 |
5 | /**
6 | * A Functor which interpolates the stack distance linearly based on base values.
7 | * The base values are based on an interpolation between a linear function and a
8 | * quadratic function
9 | */
10 | public class PiecewiseLinearIndentationFunctor extends StackIndentationFunctor {
11 |
12 | private final ArrayList mBaseValues;
13 | private final float mLinearPart;
14 |
15 | /**
16 | * @param maxItemsInStack The maximum number of items which should be visible at the same time,
17 | * i.e the function returns totalTransitionDistance for the element with
18 | * index maxItemsInStack
19 | * @param peekSize The visual appearance of this is how far the cards in the stack peek
20 | * out below the top card and it is measured in real pixels.
21 | * Note that the visual appearance does not necessarily always correspond to
22 | * the actual visual distance below the top card but is a maximum,
23 | * achieved when the next card just starts transitioning into the stack and
24 | * the stack is full.
25 | * If distanceToPeekStart is 0, we directly start at the peek, otherwise the
26 | * first element transitions between 0 and distanceToPeekStart.
27 | * Visualization:
28 | * --------------------------------------------------- ---
29 | * | | |
30 | * | FIRST ITEM | | <- distanceToPeekStart
31 | * | | |
32 | * |---------------------------------------------------| --- ---
33 | * |__________________SECOND ITEM______________________| | <- peekSize
34 | * |===================================================| _|_
35 | *
36 | * @param distanceToPeekStart The distance to the start of the peak.
37 | * @param linearPart The interpolation factor between the linear and the quadratic amount taken.
38 | * This factor must be somewhere in [0 , 1]
39 | */
40 | PiecewiseLinearIndentationFunctor(int maxItemsInStack,
41 | int peekSize,
42 | int distanceToPeekStart,
43 | float linearPart) {
44 | super(maxItemsInStack, peekSize, distanceToPeekStart);
45 | mBaseValues = new ArrayList(maxItemsInStack+1);
46 | initBaseValues();
47 | mLinearPart = linearPart;
48 | }
49 |
50 | private void initBaseValues() {
51 | int sumOfSquares = getSumOfSquares(mMaxItemsInStack-1);
52 | int totalWeight = 0;
53 | mBaseValues.add(0.0f);
54 | for (int i = 0; i < mMaxItemsInStack - 1; i++) {
55 | totalWeight += (mMaxItemsInStack - i - 1) * (mMaxItemsInStack - i - 1);
56 | mBaseValues.add((float) totalWeight / sumOfSquares);
57 | }
58 | }
59 |
60 | /**
61 | * Get the sum of squares up to and including n, i.e sum(i * i, 1, n)
62 | *
63 | * @param n the maximum square to include
64 | * @return
65 | */
66 | private int getSumOfSquares(int n) {
67 | return n * (n + 1) * (2 * n + 1) / 6;
68 | }
69 |
70 | @Override
71 | public float getValue(float itemsBefore) {
72 | if (mStackStartsAtPeek) {
73 | // We directly start at the stack, so no initial interpolation.
74 | itemsBefore++;
75 | }
76 | if (itemsBefore < 0) {
77 | return 0;
78 | } else if (itemsBefore >= mMaxItemsInStack) {
79 | return mTotalTransitionDistance;
80 | }
81 | int below = (int) itemsBefore;
82 | float partialIn = itemsBefore - below;
83 |
84 | if (below == 0) {
85 | return mDistanceToPeekStart * partialIn;
86 | } else {
87 | float result = mDistanceToPeekStart;
88 | float progress = mBaseValues.get(below - 1) * (1 - partialIn)
89 | + mBaseValues.get(below) * partialIn;
90 | result += (progress * (1 - mLinearPart)
91 | + (itemsBefore - 1) / (mMaxItemsInStack - 1) * mLinearPart) * mPeekSize;
92 | return result;
93 | }
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/Pools.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | /**
4 | * Helper class for crating pools of objects. An example use looks like this:
5 | *
6 | * public class MyPooledClass {
7 | *
8 | * private static final SynchronizedPool sPool =
9 | * new SynchronizedPool(10);
10 | *
11 | * public static MyPooledClass obtain() {
12 | * MyPooledClass instance = sPool.acquire();
13 | * return (instance != null) ? instance : new MyPooledClass();
14 | * }
15 | *
16 | * public void recycle() {
17 | * // Clear state if needed.
18 | * sPool.release(this);
19 | * }
20 | *
21 | * . . .
22 | * }
23 | *
24 | *
25 | * @hide
26 | */
27 | public final class Pools {
28 |
29 | /**
30 | * Interface for managing a pool of objects.
31 | *
32 | * @param The pooled type.
33 | */
34 | public static interface Pool {
35 |
36 | /**
37 | * @return An instance from the pool if such, null otherwise.
38 | */
39 | public T acquire();
40 |
41 | /**
42 | * Release an instance to the pool.
43 | *
44 | * @param instance The instance to release.
45 | * @return Whether the instance was put in the pool.
46 | *
47 | * @throws IllegalStateException If the instance is already in the pool.
48 | */
49 | public boolean release(T instance);
50 | }
51 |
52 | private Pools() {
53 | /* do nothing - hiding constructor */
54 | }
55 |
56 | /**
57 | * Simple (non-synchronized) pool of objects.
58 | *
59 | * @param The pooled type.
60 | */
61 | public static class SimplePool implements Pool {
62 | private final Object[] mPool;
63 |
64 | private int mPoolSize;
65 |
66 | /**
67 | * Creates a new instance.
68 | *
69 | * @param maxPoolSize The max pool size.
70 | *
71 | * @throws IllegalArgumentException If the max pool size is less than zero.
72 | */
73 | public SimplePool(int maxPoolSize) {
74 | if (maxPoolSize <= 0) {
75 | throw new IllegalArgumentException("The max pool size must be > 0");
76 | }
77 | mPool = new Object[maxPoolSize];
78 | }
79 |
80 | @Override
81 | @SuppressWarnings("unchecked")
82 | public T acquire() {
83 | if (mPoolSize > 0) {
84 | final int lastPooledIndex = mPoolSize - 1;
85 | T instance = (T) mPool[lastPooledIndex];
86 | mPool[lastPooledIndex] = null;
87 | mPoolSize--;
88 | return instance;
89 | }
90 | return null;
91 | }
92 |
93 | @Override
94 | public boolean release(T instance) {
95 | if (isInPool(instance)) {
96 | throw new IllegalStateException("Already in the pool!");
97 | }
98 | if (mPoolSize < mPool.length) {
99 | mPool[mPoolSize] = instance;
100 | mPoolSize++;
101 | return true;
102 | }
103 | return false;
104 | }
105 |
106 | private boolean isInPool(T instance) {
107 | for (int i = 0; i < mPoolSize; i++) {
108 | if (mPool[i] == instance) {
109 | return true;
110 | }
111 | }
112 | return false;
113 | }
114 | }
115 |
116 | /**
117 | * Synchronized) pool of objects.
118 | *
119 | * @param The pooled type.
120 | */
121 | public static class SynchronizedPool extends SimplePool {
122 | private final Object mLock = new Object();
123 |
124 | /**
125 | * Creates a new instance.
126 | *
127 | * @param maxPoolSize The max pool size.
128 | *
129 | * @throws IllegalArgumentException If the max pool size is less than zero.
130 | */
131 | public SynchronizedPool(int maxPoolSize) {
132 | super(maxPoolSize);
133 | }
134 |
135 | @Override
136 | public T acquire() {
137 | synchronized (mLock) {
138 | return super.acquire();
139 | }
140 | }
141 |
142 | @Override
143 | public boolean release(T element) {
144 | synchronized (mLock) {
145 | return super.release(element);
146 | }
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/activity/SingleStackScrollActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.activity;
2 |
3 | import com.baidu.scrollstack.stack.StackScrollLayout;
4 | import com.baidu.scrollstack.view.StackScrollPanelView;
5 | import com.example.baidu.app.R;
6 | import com.example.baidu.app.adapter.DoubleStackViewAdapter;
7 |
8 | import android.app.Activity;
9 | import android.os.Bundle;
10 | import android.view.LayoutInflater;
11 | import android.view.View;
12 | import android.widget.TextView;
13 |
14 | /**
15 | * Created by helingjian on 16/3/14.
16 | */
17 | public class SingleStackScrollActivity extends Activity {
18 |
19 | private static final int TYPE_STACK_ONLY = 0;
20 | private static final int TYPE_STACK_AND_SCROLL = 1;
21 | private static final int TYPE_STACK_WITH_SPRING = 2;
22 | private int demoType = 2;
23 | private DoubleStackViewAdapter stackViewAdapter;
24 | private StackScrollPanelView stackScrollPanelView;
25 | private StackScrollLayout mStackScroller;
26 | private TextView stackHeader;
27 | private int[] drawableIds;
28 | private String[] titles;
29 |
30 | StackScrollLayout.OnItemClickListener onStackItemClickListener = new StackScrollLayout.OnItemClickListener() {
31 | @Override
32 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
33 | stackHeader.setText(titles[position]);
34 | }
35 | };
36 |
37 | @Override
38 | protected void onCreate(Bundle savedInstanceState) {
39 | super.onCreate(savedInstanceState);
40 | switch (demoType) {
41 | case TYPE_STACK_ONLY:
42 | setContentView(R.layout.activity_stack_layout);
43 | break;
44 | case TYPE_STACK_AND_SCROLL:
45 | setContentView(R.layout.activity_stack_scroll_layout);
46 | break;
47 | case TYPE_STACK_WITH_SPRING:
48 | setContentView(R.layout.activity_stack_spring_layout);
49 | break;
50 | default:
51 | break;
52 | }
53 | initValues();
54 | initScroller();
55 | }
56 |
57 | private void initScroller() {
58 | mStackScroller = (StackScrollLayout) findViewById(R.id.stack_scroller);
59 | stackHeader = (TextView) findViewById(R.id.stack_scroll_header);
60 | stackScrollPanelView = (StackScrollPanelView) findViewById(R.id.stack_scroll_panel);
61 | stackViewAdapter = new DoubleStackViewAdapter(LayoutInflater.from(this));
62 | stackViewAdapter.setData(drawableIds, titles);
63 | mStackScroller.setAdapter(stackViewAdapter);
64 | mStackScroller.setOnItemClickListener(onStackItemClickListener);
65 | if (demoType == TYPE_STACK_AND_SCROLL) {
66 | stackScrollPanelView.setEnableOverScroll(false);
67 | }
68 | }
69 |
70 | // @Override
71 | // public void onBackPressed() {
72 | // if (stackScrollPanelView.isFullyExpanded()) {
73 | // stackScrollPanelView.collapse(false);
74 | // } else if (stackScrollPanelView.isFullyCollapsed()) {
75 | // stackScrollPanelView.expand(false);
76 | // }
77 | // stackViewAdapter.notifyDataSetChanged();
78 | // }
79 |
80 | private void initValues() {
81 | titles = new String[] {
82 | getString(R.string.item_name_1), getString(R.string.item_name_2),
83 | getString(R.string.item_name_3), getString(R.string.item_name_4),
84 | getString(R.string.item_name_5), getString(R.string.item_name_6),
85 | getString(R.string.item_name_7), getString(R.string.item_name_8),
86 | getString(R.string.item_name_9), getString(R.string.item_name_10),
87 | getString(R.string.item_name_11), getString(R.string.item_name_12),
88 | getString(R.string.item_name_13), getString(R.string.item_name_4),
89 | getString(R.string.item_name_15), getString(R.string.item_name_16),
90 | getString(R.string.item_name_17), getString(R.string.item_name_8),
91 | getString(R.string.item_name_19)
92 | };
93 | drawableIds = new int[] {
94 | R.drawable.icon1, R.drawable.icon2, R.drawable.icon3, R.drawable.icon4,
95 | R.drawable.icon5, R.drawable.icon6, R.drawable.icon7, R.drawable.icon8,
96 | R.drawable.icon9, R.drawable.icon10, R.drawable.icon11, R.drawable.icon12,
97 | R.drawable.icon13, R.drawable.icon4, R.drawable.icon15, R.drawable.icon16,
98 | R.drawable.icon17, R.drawable.icon8, R.drawable.icon19
99 | };
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/ObservableScrollView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import android.view.View;
7 | import android.widget.ScrollView;
8 |
9 | /**
10 | * A scroll view which can be observed for scroll change events.
11 | */
12 | public class ObservableScrollView extends ScrollView {
13 |
14 | private Listener mListener;
15 | private int mLastOverscrollAmount;
16 | private boolean mTouchEnabled = true;
17 | private boolean mHandlingTouchEvent;
18 | private float mLastX;
19 | private float mLastY;
20 | private boolean mBlockFlinging;
21 | private boolean mTouchCancelled;
22 |
23 | public ObservableScrollView(Context context) {
24 | this(context, null);
25 | }
26 |
27 | public ObservableScrollView(Context context, AttributeSet attrs) {
28 | super(context, attrs);
29 | }
30 |
31 | public void setListener(Listener listener) {
32 | mListener = listener;
33 | }
34 |
35 | public void setTouchEnabled(boolean touchEnabled) {
36 | mTouchEnabled = touchEnabled;
37 | }
38 |
39 | public boolean isScrolledToBottom() {
40 | return getScrollY() == getMaxScrollY();
41 | }
42 |
43 | public boolean isHandlingTouchEvent() {
44 | return mHandlingTouchEvent;
45 | }
46 |
47 | public int getMaxScrollY() {
48 | int scrollRange = 0;
49 | if (getChildCount() > 0) {
50 | View child = getChildAt(0);
51 | scrollRange = Math.max(0,
52 | child.getHeight() - (getHeight() - getPaddingBottom() - getPaddingTop()));
53 | }
54 | return scrollRange;
55 | }
56 |
57 | @Override
58 | public boolean onTouchEvent(MotionEvent ev) {
59 | mHandlingTouchEvent = true;
60 | mLastX = ev.getX();
61 | mLastY = ev.getY();
62 | boolean result = super.onTouchEvent(ev);
63 | mHandlingTouchEvent = false;
64 | return result;
65 | }
66 |
67 | @Override
68 | public boolean onInterceptTouchEvent(MotionEvent ev) {
69 | mHandlingTouchEvent = true;
70 | mLastX = ev.getX();
71 | mLastY = ev.getY();
72 | boolean result = super.onInterceptTouchEvent(ev);
73 | mHandlingTouchEvent = false;
74 | return result;
75 | }
76 |
77 | @Override
78 | public boolean dispatchTouchEvent(MotionEvent ev) {
79 | if (ev.getAction() == MotionEvent.ACTION_DOWN) {
80 | if (!mTouchEnabled) {
81 | mTouchCancelled = true;
82 | return false;
83 | }
84 | mTouchCancelled = false;
85 | } else if (mTouchCancelled) {
86 | return false;
87 | } else if (!mTouchEnabled) {
88 | MotionEvent cancel = MotionEvent.obtain(ev);
89 | cancel.setAction(MotionEvent.ACTION_CANCEL);
90 | super.dispatchTouchEvent(ev);
91 | cancel.recycle();
92 | mTouchCancelled = true;
93 | return false;
94 | }
95 | return super.dispatchTouchEvent(ev);
96 | }
97 |
98 | @Override
99 | protected void onScrollChanged(int l, int t, int oldl, int oldt) {
100 | super.onScrollChanged(l, t, oldl, oldt);
101 | if (mListener != null) {
102 | mListener.onScrollChanged();
103 | }
104 | }
105 |
106 | @Override
107 | protected boolean overScrollBy(int deltaX, int deltaY, int scrollX, int scrollY,
108 | int scrollRangeX, int scrollRangeY, int maxOverScrollX, int maxOverScrollY,
109 | boolean isTouchEvent) {
110 | mLastOverscrollAmount = Math.max(0, scrollY + deltaY - getMaxScrollY());
111 | return super.overScrollBy(deltaX, deltaY, scrollX, scrollY, scrollRangeX, scrollRangeY,
112 | maxOverScrollX, maxOverScrollY, isTouchEvent);
113 | }
114 |
115 | public void setBlockFlinging(boolean blockFlinging) {
116 | mBlockFlinging = blockFlinging;
117 | }
118 |
119 | @Override
120 | public void fling(int velocityY) {
121 | if (!mBlockFlinging) {
122 | super.fling(velocityY);
123 | }
124 | }
125 |
126 | @Override
127 | protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) {
128 | super.onOverScrolled(scrollX, scrollY, clampedX, clampedY);
129 | if (mListener != null && mLastOverscrollAmount > 0) {
130 | mListener.onOverscrolled(mLastX, mLastY, mLastOverscrollAmount);
131 | }
132 | }
133 |
134 | public interface Listener {
135 | void onScrollChanged();
136 |
137 | void onOverscrolled(float lastX, float lastY, int amount);
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/StackScrollerDecorView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import com.baidu.scrollstack.BuildConfig;
4 | import com.baidu.scrollstack.uitl.Define;
5 | import com.baidu.scrollstack.uitl.LocalPathInterpolator;
6 | import com.baidu.scrollstack.view.ExpandableView;
7 |
8 | import android.annotation.TargetApi;
9 | import android.content.Context;
10 | import android.graphics.Outline;
11 | import android.graphics.Rect;
12 | import android.os.Build;
13 | import android.util.AttributeSet;
14 | import android.view.View;
15 | import android.view.ViewOutlineProvider;
16 | import android.view.animation.Interpolator;
17 | import android.view.animation.PathInterpolator;
18 |
19 | /**
20 | * A common base class for all views in the notification stack scroller which don't have a
21 | * background.
22 | */
23 | public abstract class StackScrollerDecorView extends ExpandableView {
24 | public final Interpolator ALPHA_IN = getPathInterpolator(0.4f, 0f, 1f, 1f);
25 | public final Interpolator ALPHA_OUT = getPathInterpolator(0f, 0f, 0.8f, 1f);
26 | protected View mContent;
27 | private boolean mIsVisible;
28 | private boolean mAnimating;
29 | private boolean mWillBeGone;
30 |
31 | public StackScrollerDecorView(Context context, AttributeSet attrs) {
32 | super(context, attrs);
33 | }
34 |
35 | @TargetApi(21)
36 | @Override
37 | protected void onFinishInflate() {
38 | super.onFinishInflate();
39 | mContent = findContentView();
40 | setInvisible();
41 | if (Define.SDK_INT >= 21 && getBackground() == null) {
42 | setOutlineProvider(new ViewOutlineProvider() {
43 | @Override
44 | public void getOutline(View view, Outline outline) {
45 | Rect clipBounds = new Rect();
46 | clipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), getHeight());
47 | outline.setRect(clipBounds);
48 | }
49 | });
50 | }
51 | }
52 |
53 | @Override
54 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
55 | super.onLayout(changed, left, top, right, bottom);
56 |
57 | }
58 |
59 | @Override
60 | public boolean isTransparent() {
61 | return true;
62 | }
63 |
64 | public void performVisibilityAnimation(boolean nowVisible) {
65 | animateText(nowVisible, null /* onFinishedRunnable */);
66 | }
67 |
68 | public void performVisibilityAnimation(boolean nowVisible, Runnable onFinishedRunnable) {
69 | animateText(nowVisible, onFinishedRunnable);
70 | }
71 |
72 | public boolean isVisible() {
73 | return mIsVisible || mAnimating;
74 | }
75 |
76 | /**
77 | * Animate the text to a new visibility.
78 | *
79 | * @param nowVisible should it now be visible
80 | * @param onFinishedRunnable A runnable which should be run when the animation is
81 | * finished.
82 | */
83 | private void animateText(boolean nowVisible, final Runnable onFinishedRunnable) {
84 | if (nowVisible != mIsVisible) {
85 | // Animate text
86 | float endValue = nowVisible ? 1.0f : 0.0f;
87 | Interpolator interpolator;
88 | if (nowVisible) {
89 | interpolator = ALPHA_IN;
90 | } else {
91 | interpolator = ALPHA_OUT;
92 | }
93 | mAnimating = true;
94 | mContent.animate()
95 | .alpha(endValue)
96 | .setInterpolator(interpolator)
97 | .setDuration(260)
98 | .withEndAction(new Runnable() {
99 | @Override
100 | public void run() {
101 | mAnimating = false;
102 | if (onFinishedRunnable != null) {
103 | onFinishedRunnable.run();
104 | }
105 | }
106 | });
107 |
108 | mIsVisible = nowVisible;
109 | } else {
110 | if (onFinishedRunnable != null) {
111 | onFinishedRunnable.run();
112 | }
113 | }
114 | }
115 |
116 | public void setInvisible() {
117 | mContent.setAlpha(0.0f);
118 | mIsVisible = false;
119 | }
120 |
121 | @Override
122 | public void performRemoveAnimation(long duration, float translationDirection,
123 | Runnable onFinishedRunnable) {
124 | // TODO: Use duration
125 | performVisibilityAnimation(false);
126 | }
127 |
128 | @Override
129 | public void performAddAnimation(long delay, long duration) {
130 | // TODO: use delay and duration
131 | performVisibilityAnimation(true);
132 | }
133 |
134 | @Override
135 | public boolean hasOverlappingRendering() {
136 | return false;
137 | }
138 |
139 | public void cancelAnimation() {
140 | mContent.animate().cancel();
141 | }
142 |
143 | public boolean willBeGone() {
144 | return mWillBeGone;
145 | }
146 |
147 | public void setWillBeGone(boolean willBeGone) {
148 | mWillBeGone = willBeGone;
149 | }
150 |
151 | protected abstract View findContentView();
152 |
153 | @TargetApi(21)
154 | private Interpolator getPathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2){
155 | return Define.SDK_INT >= 21 ? new PathInterpolator(controlX1, controlY1, controlX2, controlY2)
156 | : new LocalPathInterpolator(controlX1, controlY1, controlX2, controlY2);
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/StackscrollLayout/StackscrollLayout.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/StackHeaderView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import com.baidu.scrollstack.R;
4 | import com.baidu.scrollstack.uitl.Define;
5 |
6 | import android.annotation.TargetApi;
7 | import android.content.Context;
8 | import android.content.res.Configuration;
9 | import android.content.res.TypedArray;
10 | import android.graphics.Outline;
11 | import android.graphics.Rect;
12 | import android.util.AttributeSet;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 | import android.view.ViewOutlineProvider;
16 | import android.widget.RelativeLayout;
17 |
18 | /**
19 | * The view to manage the header area in the expanded status bar.
20 | */
21 | public class StackHeaderView extends RelativeLayout {
22 |
23 | private final Rect mClipBounds = new Rect();
24 | private final LayoutValues mCollapsedValues = new LayoutValues();
25 | private final LayoutValues mExpandedValues = new LayoutValues();
26 | private final LayoutValues mCurrentValues = new LayoutValues();
27 | private boolean mExpanded;
28 | private boolean mListening;
29 | private int mCollapsedHeight;
30 | private int mExpandedHeight;
31 | private boolean mCaptureValues;
32 | private float mCurrentT;
33 |
34 | public StackHeaderView(Context context) {
35 | this(context, null);
36 | }
37 |
38 | public StackHeaderView(Context context, AttributeSet attrs) {
39 | this(context, attrs, 0);
40 | }
41 |
42 | public StackHeaderView(Context context, AttributeSet attrs, int defStyleAttr) {
43 | super(context, attrs, defStyleAttr);
44 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StackHeaderView, defStyleAttr, 0);
45 | mCollapsedHeight = a.getLayoutDimension(R.styleable.StackHeaderView_collapsed_height, 0);
46 | mExpandedHeight = a.getLayoutDimension(R.styleable.StackHeaderView_expanded_height, 0);
47 | }
48 |
49 | @TargetApi(21)
50 | @Override
51 | protected void onFinishInflate() {
52 | super.onFinishInflate();
53 | addOnLayoutChangeListener(new OnLayoutChangeListener() {
54 | @Override
55 | public void onLayoutChange(View v, int left, int top, int right,
56 | int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
57 | if ((right - left) != (oldRight - oldLeft)) {
58 | // width changed, update clipping
59 | setClipping(getHeight());
60 | }
61 | }
62 | });
63 | if (Define.SDK_INT >= 21) {
64 | setOutlineProvider(new ViewOutlineProvider() {
65 | @Override
66 | public void getOutline(View view, Outline outline) {
67 | outline.setRect(mClipBounds);
68 | }
69 | });
70 | }
71 | requestCaptureValues();
72 | }
73 |
74 | @Override
75 | protected void onLayout(boolean changed, int l, int t, int r, int b) {
76 | super.onLayout(changed, l + getPaddingLeft(), t + getPaddingTop(), r - getPaddingRight(),
77 | b - getPaddingBottom());
78 | if (mCaptureValues) {
79 | if (mExpanded) {
80 | captureLayoutValues(mExpandedValues);
81 | } else {
82 | captureLayoutValues(mCollapsedValues);
83 | }
84 | mCaptureValues = false;
85 | updateLayoutValues(mCurrentT);
86 | }
87 | }
88 |
89 | @Override
90 | protected void onConfigurationChanged(Configuration newConfig) {
91 | super.onConfigurationChanged(newConfig);
92 | }
93 |
94 | private void requestCaptureValues() {
95 | mCaptureValues = true;
96 | requestLayout();
97 | }
98 |
99 | public int getCollapsedHeight() {
100 | return mCollapsedHeight;
101 | }
102 |
103 | public int getExpandedHeight() {
104 | return mExpandedHeight;
105 | }
106 |
107 | public void setListening(boolean listening) {
108 | if (listening == mListening) {
109 | return;
110 | }
111 | mListening = listening;
112 | }
113 |
114 | public void setExpanded(boolean expanded) {
115 | boolean changed = expanded != mExpanded;
116 | mExpanded = expanded;
117 | if (changed) {
118 | updateEverything();
119 | }
120 | }
121 |
122 | public void updateEverything() {
123 | updateHeights();
124 | requestCaptureValues();
125 | }
126 |
127 | private void updateHeights() {
128 | int height = mExpanded ? mExpandedHeight : mCollapsedHeight;
129 | ViewGroup.LayoutParams lp = getLayoutParams();
130 | if (lp != null && lp.height != height) {
131 | lp.height = height;
132 | setLayoutParams(lp);
133 | }
134 | }
135 |
136 | public void setExpansion(float t) {
137 | if (!mExpanded) {
138 | t = 0f;
139 | }
140 | mCurrentT = t;
141 | float height = mCollapsedHeight + t * (mExpandedHeight - mCollapsedHeight);
142 | if (height < mCollapsedHeight) {
143 | height = mCollapsedHeight;
144 | }
145 | if (height > mExpandedHeight) {
146 | height = mExpandedHeight;
147 | }
148 | setClipping(height);
149 | updateLayoutValues(t);
150 | }
151 |
152 | private void updateLayoutValues(float t) {
153 | if (mCaptureValues) {
154 | return;
155 | }
156 | mCurrentValues.interpoloate(mCollapsedValues, mExpandedValues, t);
157 | }
158 |
159 |
160 | @TargetApi(21)
161 | private void setClipping(float height) {
162 | if (Define.SDK_INT < 18) {
163 | ViewGroup.LayoutParams lp = getLayoutParams();
164 | if (lp.height != height) {
165 | lp.height = (int) height;
166 | setLayoutParams(lp);
167 | }
168 | } else {
169 | mClipBounds.set(getPaddingLeft(), 0, getWidth() - getPaddingRight(), (int) height);
170 | if (Define.SDK_INT >= 18) {
171 | setClipBounds(mClipBounds);
172 | }
173 | if (height != getHeight()) {
174 | if (Define.SDK_INT >= 21) {
175 | invalidateOutline();
176 | } else {
177 | requestLayout();
178 | }
179 | }
180 | }
181 | }
182 |
183 | @Override
184 | public boolean shouldDelayChildPressedState() {
185 | return true;
186 | }
187 |
188 | private void captureLayoutValues(LayoutValues target) {
189 | target.signalClusterAlpha = 1f;
190 | target.settingsRotation = !mExpanded ? 90f : 0f;
191 | }
192 |
193 | /**
194 | * Captures all layout values (position, visibility) for a certain state. This is used for
195 | * animations.
196 | */
197 | private static final class LayoutValues {
198 |
199 | float dateExpandedAlpha;
200 | float dateCollapsedAlpha;
201 | float emergencyCallsOnlyAlpha;
202 | float alarmStatusAlpha;
203 | float timeScale = 1f;
204 | float dateY;
205 | float avatarScale;
206 | float avatarX;
207 | float avatarY;
208 | float batteryX;
209 | float batteryY;
210 | float batteryLevelAlpha;
211 | float settingsAlpha;
212 | float settingsTranslation;
213 | float signalClusterAlpha;
214 | float settingsRotation;
215 |
216 | public void interpoloate(LayoutValues v1, LayoutValues v2, float t) {
217 | timeScale = v1.timeScale * (1 - t) + v2.timeScale * t;
218 | dateY = v1.dateY * (1 - t) + v2.dateY * t;
219 | avatarScale = v1.avatarScale * (1 - t) + v2.avatarScale * t;
220 | avatarX = v1.avatarX * (1 - t) + v2.avatarX * t;
221 | avatarY = v1.avatarY * (1 - t) + v2.avatarY * t;
222 | batteryX = v1.batteryX * (1 - t) + v2.batteryX * t;
223 | batteryY = v1.batteryY * (1 - t) + v2.batteryY * t;
224 | settingsTranslation = v1.settingsTranslation * (1 - t) + v2.settingsTranslation * t;
225 |
226 | float t1 = Math.max(0, t - 0.5f) * 2;
227 | settingsRotation = v1.settingsRotation * (1 - t1) + v2.settingsRotation * t1;
228 | emergencyCallsOnlyAlpha =
229 | v1.emergencyCallsOnlyAlpha * (1 - t1) + v2.emergencyCallsOnlyAlpha * t1;
230 |
231 | float t2 = Math.min(1, 2 * t);
232 | signalClusterAlpha = v1.signalClusterAlpha * (1 - t2) + v2.signalClusterAlpha * t2;
233 |
234 | float t3 = Math.max(0, t - 0.7f) / 0.3f;
235 | batteryLevelAlpha = v1.batteryLevelAlpha * (1 - t3) + v2.batteryLevelAlpha * t3;
236 | settingsAlpha = v1.settingsAlpha * (1 - t3) + v2.settingsAlpha * t3;
237 | dateExpandedAlpha = v1.dateExpandedAlpha * (1 - t3) + v2.dateExpandedAlpha * t3;
238 | dateCollapsedAlpha = v1.dateCollapsedAlpha * (1 - t3) + v2.dateCollapsedAlpha * t3;
239 | alarmStatusAlpha = v1.alarmStatusAlpha * (1 - t3) + v2.alarmStatusAlpha * t3;
240 | }
241 | }
242 |
243 | }
244 |
--------------------------------------------------------------------------------
/demo/demo.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/stack/StackScrollState.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.stack;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import com.baidu.scrollstack.uitl.Define;
7 | import com.baidu.scrollstack.view.ExpandableRowView;
8 | import com.baidu.scrollstack.view.ExpandableView;
9 | import com.baidu.scrollstack.R;;
10 |
11 | import android.graphics.Rect;
12 | import android.util.Log;
13 | import android.view.View;
14 | import android.view.ViewGroup;
15 |
16 | public class StackScrollState {
17 |
18 | private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
19 |
20 | private final ViewGroup mHostView;
21 | private Map mStateMap;
22 | private final Rect mClipRect = new Rect();
23 | private final int mClearAllTopPadding;
24 |
25 | public StackScrollState(ViewGroup hostView) {
26 | mHostView = hostView;
27 | mStateMap = new HashMap();
28 | mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
29 | R.dimen.clear_all_padding_top);
30 | }
31 |
32 | public ViewGroup getHostView() {
33 | return mHostView;
34 | }
35 |
36 | public void resetViewStates() {
37 | int numChildren = mHostView.getChildCount();
38 | for (int i = 0; i < numChildren; i++) {
39 | ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
40 | ViewState viewState = mStateMap.get(child);
41 | if (viewState == null) {
42 | viewState = new ViewState();
43 | mStateMap.put(child, viewState);
44 | }
45 | // initialize with the default values of the view
46 | viewState.height = child.getIntrinsicHeight();
47 | viewState.gone = child.getVisibility() == View.GONE;
48 | viewState.alpha = 1;
49 | viewState.notGoneIndex = -1;
50 | }
51 | }
52 |
53 | public ViewState getViewStateForView(View requestedView) {
54 | return mStateMap.get(requestedView);
55 | }
56 |
57 | public void removeViewStateForView(View child) {
58 | mStateMap.remove(child);
59 | }
60 |
61 | /**
62 | * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
63 | * The properties are only applied if they effectively changed.
64 | */
65 | public void apply() {
66 | int numChildren = mHostView.getChildCount();
67 | for (int i = 0; i < numChildren; i++) {
68 | ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
69 | ViewState state = mStateMap.get(child);
70 | if (state == null) {
71 | Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
72 | "to the hostView");
73 | continue;
74 | }
75 | if (!state.gone) {
76 | float alpha = child.getAlpha();
77 | float yTranslation = child.getTranslationY();
78 | float xTranslation = child.getTranslationX();
79 | float zTranslation = child.getTranslationZ();
80 | float scale = child.getScaleX();
81 | int height = child.getActualHeight();
82 | float newAlpha = state.alpha;
83 | float newYTranslation = state.yTranslation;
84 | float newZTranslation = state.zTranslation;
85 | float newScale = state.scale;
86 | int newHeight = state.height;
87 | boolean becomesInvisible = newAlpha == 0.0f;
88 | if (alpha != newAlpha && xTranslation == 0) {
89 | // apply layer type
90 | boolean becomesFullyVisible = newAlpha == 1.0f;
91 | boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
92 | int layerType = child.getLayerType();
93 | int newLayerType = newLayerTypeIsHardware
94 | ? View.LAYER_TYPE_HARDWARE
95 | : View.LAYER_TYPE_NONE;
96 | if (layerType != newLayerType) {
97 | child.setLayerType(newLayerType, null);
98 | }
99 |
100 | // apply alpha
101 | if (!becomesInvisible) {
102 | child.setAlpha(newAlpha);
103 | }
104 | }
105 |
106 | // apply visibility
107 | int oldVisibility = child.getVisibility();
108 | int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
109 | if (newVisibility != oldVisibility) {
110 | child.setVisibility(newVisibility);
111 | }
112 |
113 | // apply yTranslation
114 | if (yTranslation != newYTranslation) {
115 | child.setTranslationY(newYTranslation);
116 | }
117 |
118 | // apply zTranslation
119 | if (zTranslation != newZTranslation) {
120 | child.setTranslationZ(newZTranslation);
121 | }
122 |
123 | // apply scale
124 | if (scale != newScale) {
125 | child.setScaleX(newScale);
126 | child.setScaleY(newScale);
127 | }
128 |
129 | // apply height
130 | if (height != newHeight) {
131 | child.setActualHeight(newHeight, false /* notifyListeners */);
132 | }
133 |
134 | // apply dimming
135 | child.setDimmed(state.dimmed, false /* animate */);
136 |
137 | // apply dark
138 | child.setDark(state.dark, false /* animate */);
139 |
140 | // apply hiding sensitive
141 | child.setHideSensitive(
142 | state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
143 |
144 | // apply clipping
145 | float oldClipTopAmount = child.getClipTopAmount();
146 | if (oldClipTopAmount != state.clipTopAmount) {
147 | child.setClipTopAmount(state.clipTopAmount);
148 | }
149 | updateChildClip(child, newHeight, state.topOverLap);
150 |
151 | if (child instanceof ExpandableRowView) {
152 | ExpandableRowView expandableRowView = (ExpandableRowView) child;
153 | boolean visible = state.topOverLap < mClearAllTopPadding;
154 | expandableRowView.performVisibilityAnimation(visible && !expandableRowView.willBeGone());
155 | }
156 |
157 | }
158 | }
159 | }
160 |
161 | /**
162 | * Updates the clipping of a view
163 | *
164 | * @param child the view to update
165 | * @param height the currently applied height of the view
166 | * @param clipInset how much should this view be clipped from the top
167 | */
168 | private void updateChildClip(View child, int height, int clipInset) {
169 | mClipRect.set(0,
170 | clipInset,
171 | child.getWidth(),
172 | height);
173 | if (Define.SDK_INT >= 18) {
174 | child.setClipBounds(mClipRect);
175 | }
176 | }
177 |
178 | private View getNextChildNotGone(int childIndex) {
179 | int childCount = mHostView.getChildCount();
180 | for (int i = childIndex + 1; i < childCount; i++) {
181 | View child = mHostView.getChildAt(i);
182 | if (child.getVisibility() != View.GONE) {
183 | return child;
184 | }
185 | }
186 | return null;
187 | }
188 |
189 | public static class ViewState {
190 |
191 | // These are flags such that we can create masks for filtering.
192 |
193 | public static final int LOCATION_UNKNOWN = 0x00;
194 | public static final int LOCATION_FIRST_CARD = 0x01;
195 | public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
196 | public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
197 | public static final int LOCATION_MAIN_AREA = 0x08;
198 | public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
199 | public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
200 |
201 | float alpha;
202 | float yTranslation;
203 | float zTranslation;
204 | int height;
205 | boolean gone;
206 | float scale;
207 | boolean dimmed;
208 | boolean dark;
209 | boolean hideSensitive;
210 |
211 | /**
212 | * The amount which the view should be clipped from the top. This is calculated to
213 | * perceive consistent shadows.
214 | */
215 | int clipTopAmount;
216 |
217 | /**
218 | * How much does the child overlap with the previous view on the top? Can be used for
219 | * a clipping optimization
220 | */
221 | int topOverLap;
222 |
223 | /**
224 | * The index of the view, only accounting for views not equal to GONE
225 | */
226 | int notGoneIndex;
227 |
228 | /**
229 | * The location this view is currently rendered at.
230 | *
231 | * See LOCATION_ flags.
232 | */
233 | int location;
234 |
235 | @Override
236 | public String toString() {
237 | return "ViewState{" +
238 | "alpha=" + alpha +
239 | ", yTranslation=" + yTranslation +
240 | ", zTranslation=" + zTranslation +
241 | ", height=" + height +
242 | ", gone=" + gone +
243 | ", scale=" + scale +
244 | ", dimmed=" + dimmed +
245 | ", dark=" + dark +
246 | ", hideSensitive=" + hideSensitive +
247 | ", clipTopAmount=" + clipTopAmount +
248 | ", topOverLap=" + topOverLap +
249 | ", notGoneIndex=" + notGoneIndex +
250 | ", location=" + location +
251 | '}';
252 | }
253 | }
254 | }
255 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/activity/DoubleStackScrollActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.activity;
2 |
3 | import com.baidu.scrollstack.stack.StackScrollLayout;
4 | import com.baidu.scrollstack.view.StackScrollPanelView;
5 | import com.example.baidu.app.R;
6 | import com.example.baidu.app.adapter.DoubleStackViewAdapter;
7 |
8 | import android.app.Activity;
9 | import android.graphics.Color;
10 | import android.os.Bundle;
11 | import android.view.LayoutInflater;
12 | import android.view.View;
13 | import android.widget.TextView;
14 |
15 | /**
16 | * Created by helingjian on 16/3/14.
17 | */
18 | public class DoubleStackScrollActivity extends Activity {
19 |
20 | String[] brands =
21 | new String[] {"雅戈尔", "法利鳄鱼", "皮特丹顿", "蓝豹", "九牧王", "路易诗兰", "马克华菲", "法国鳄鱼", "蔓哈顿", "汤尼威尔", "雷迪波尔", "柏郎亚高",
22 | "萨尔雷斯", "庄子", "豹狮杰", "富哥", "开蒙", "卡敦斐尔", "袋鼠", "企鹅岛/杰翰", "卡丹路", "老人城", "法国老人头", "唐鹰", "帝高", "纳巴罗",
23 | "杰凡尼", "皮尔卡丹", "堡尼", "杉杉", "罗蒙", "金利来", "迪迪公子", "报喜鸟", "法派", "汤尼俊仕", "花雨伞", "花花公子", "U",
24 | "卡努迪路", "BOSS", "迪莱", "梦特娇", "宝姿", "海澜之家", "ESPRIT", "保罗世家", "NAUTICA", "BABEI(巴贝)",
25 | "CROCODILE(鳄鱼恤)", "比华利保罗", "波司登", "七匹狼", "USPOLO", "塔吉", "奥德臣", "斯诺沙克", "卡利斯特", "威可多", "JOEONE",
26 | "马思图", "BAILY(巴利)", "尼诺卡丹", "雷诺天蒙", "虎都", "圣大保罗", "卡迪尔", "鳄鱼", "法国圣龙", "沙弛", "GORNIA",
27 | "卡斯德利", "杰克琼斯", "巴宝利", "迪赛", "汤米", "路易世尊", "鄂尔多斯", "路易德士", "与狼共舞", "老爷车", "轩帝尼", "凯撒", "V-ONE",
28 | "富铤", "旗牌王", "娃娃鱼", "胜龙", "昂斯", "布莱.希尔顿", "马基堡", "北京威可多", "日弛尼", "波顿", "克美.米罗", "卓凡里诺", "歌德克依",
29 | "萨巴帝尼", "登喜路", "吉普", "洛滋", "PIEEDENTON", "铁狮丹顿", "萨托尼", "西海岸", "培罗城", "帕给维龙", "法函诗", "意劳迪斯", "应大",
30 | "依文", "NOVELI", "DEZUN", "卡丹迪诺", "维尼熊", "威玛", "艾登堡", "吉田.格雷曼", "萨里奥托", "SWUBTRO", "宾度", "DIDIBOY",
31 | "法曼斯", "雅格狮丹", "宏士达", "路卡迪龙", "红豆", "澳林斯顿", "卡拉里尼", "EZIO", "卡帝乐鳄鱼", "米盖尔", "吉尼亚", "欧麦格", "贝尼",
32 | "ERDOS", "亚涛", "菲罗伦思", "智圣", "V.E.DELURE", "美国苹果", "浪比时", "卡尔丹顿", "CK", "海螺", "圣吉.卡丹", "骆驼",
33 | "西斯顿", "圣宝龙", "ZzHISHEENG", "庄吉", "法梦迪尔", "博斯里拉", "卡丹雷杰", "KENT﹠CURWEN", "龙达飞", "纪凡希", "杭州苹果", "洲艳",
34 | "新巴贝", "名马", "约翰", "派尔森", "华伦天奴", "雄", "都彭", "堡尼.丹尼", "佛伦杰尼", "赛思特", "才子", "邦纳斯", "利郎", "豪年度",
35 | "思豪CEO", "步森", "丽都法诗", "天地人", "瑞尔纳", "欧兰诺", "红杉树", "金狮鹏", "卡莎米亚", "太子龙", "曼克顿", "虎豹", "沃尔夫", "金盾",
36 | "威斯康尼", "名盾", "玛克世家", "EVERGENERATING", "奇尔斯丹.老人城", "苹果", "华斯度", "BALENCIAGA", "诺帝卡", "艾帝", "博格西尼",
37 | "集杰", "郝捷伦", "卡斯保罗", "巴克兰盾", "新奥斯卡丹", "POLO", "斯得亚", "幸运鹿", "万祺凯易", "华伦", "斯卡图", "耶纳诺", "CERRUT",
38 | "FROGNIEZILA", "伊托马斯", "郎维高", "苹果树", "奥里托那", "天恒", "雷弛", "班尼波士", "乔顿", "奥伦多兰", "左天奴", "古士旗",
39 | "威鹿", "秋艳", "佛伦斯", "绅浪", "伊斯丹奴", "FONOCC", "阿雷克斯.鲁尼", "稻草人", "亨威", "八千代", "迪仕豹", "凯洋", "顺美", "路嘉纳",
40 | "仕东利", "浪登", "CANLI", "GALAXY(佳乐喜)", "BYNIC", "TESTANTIN", "埃得蒙", "培罗蒙", "利丹", "肯迪文", "欧彭", "卡奴丹路",
41 | "罗马世家", "恒源祥", "莱斯马", "TSTANIN", "格尼亚", "皇家圣保罗", "古杰狮", "哈雷纳.金狐", "岳豹", "CTPARIS/娃娃鱼", "花狐/阿尔皮纳",
42 | "爵士丹尼", "贝利龙", "威可多", "蓝豹", "LEONARDO/老人头", "大维", "诗丹贝克", "利丹王", "胜龙", "UCLA", "法国百灵鸟", "GNTLE",
43 | "乔夫", "圣罗兰", "圣凡尼", "GNTLEGOODLUCKCLAD", "卡莱蒂尼", "凯迪东倪", "雷诺马汀", "宗洋", "新竹针织", "万事好", "鹿王", "新罗蒙",
44 | "东方圣罗", "佛尼亚", "金狐狸", "喜尔得", "亚韵", "圣甸奴", "ELLEHOMME", "汤尼俊仕", "利奥纳多/老人头", "法兰诗顿", "德雷萨斯来客", "地牌",
45 | "金丝狐", "卡博尔", "凯莱露喜", "里奇波士", "四海龙", "浪肯", "马狮龙", "劲霸", "斯蒂文", "玛珂爱萨尼", "阔伯", "真正", "REMOGLANNI",
46 | "萨尔雷斯", "庄子", "豹狮杰", "富哥", "开蒙", "卡敦斐尔", "袋鼠", "企鹅岛/杰翰", "卡丹路", "老人城", "法国老人头", "唐鹰", "帝高", "纳巴罗",
47 | "绅贵", "迈亚", "乔治亨", "TIESHIDANDUN", "特派", "比音勒芬", "仕帝曼", "积家", "幸运岛", "虎都", "喜来登", "大红鹰", "乔顿",
48 | "雷诺天蒙", "虎都", "圣大保罗", "卡迪尔", "鳄鱼", "法国圣龙", "尼诺卡丹", "雷诺天蒙", "虎都", "圣大保罗", "FALIEYUZJIEYU", "法国老人头"};
49 | private int[] drawableLeftIds;
50 | private String[] leftTitles;
51 | private DoubleStackViewAdapter stackViewAdapterLeft;
52 | private StackScrollLayout mStackScrollerLeft;
53 | private TextView stackHeaderLeft;
54 | private StackScrollLayout mStackScrollerRight;
55 | private TextView stackHeaderRight;
56 | private DoubleStackViewAdapter stackViewAdapterRight;
57 | private StackScrollPanelView stackScrollPanelViewRight;
58 | private StackScrollPanelView stackScrollPanelViewLeft;
59 | private TextView mainText;
60 | StackScrollLayout.OnItemClickListener onRightStackItemClickListener = new StackScrollLayout.OnItemClickListener() {
61 | @Override
62 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
63 | stackScrollPanelViewLeft.collapse(false);
64 | stackScrollPanelViewRight.collapse(false);
65 | mainText.setText(((TextView) itemView.findViewById(R.id.text)).getText());
66 | }
67 | };
68 | private View lastItemView;
69 | StackScrollLayout.OnItemClickListener onLeftStackItemClickListener = new StackScrollLayout.OnItemClickListener() {
70 | @Override
71 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
72 | stackScrollPanelViewRight.setExpandedHeightInternal(0);
73 | mStackScrollerRight.setStackHeight(0);
74 | stackScrollPanelViewRight.expand(true);
75 | stackViewAdapterRight.setData(getBrandDrawables(position), getBrandNames(position));
76 | stackViewAdapterRight.notifyDataSetChanged();
77 | changBackground(itemView, true);
78 | changBackground(lastItemView, false);
79 | lastItemView = itemView;
80 | }
81 | };
82 |
83 | @Override
84 | protected void onCreate(Bundle savedInstanceState) {
85 | super.onCreate(savedInstanceState);
86 | setContentView(R.layout.activity_double_stack_layout);
87 | mainText = (TextView) findViewById(R.id.main_text);
88 | initValues();
89 | initScroller();
90 | }
91 |
92 | private void initScroller() {
93 |
94 | View leftStackView = findViewById(R.id.stack_view_left);
95 | stackScrollPanelViewLeft = (StackScrollPanelView) leftStackView.findViewById(R.id.stack_scroll_panel);
96 | mStackScrollerLeft = (StackScrollLayout) leftStackView.findViewById(R.id.stack_scroller);
97 | stackHeaderLeft = (TextView) leftStackView.findViewById(R.id.stack_scroll_header);
98 | stackViewAdapterLeft = new DoubleStackViewAdapter(LayoutInflater.from(this));
99 | stackViewAdapterLeft.setData(drawableLeftIds, leftTitles);
100 | mStackScrollerLeft.setAdapter(stackViewAdapterLeft);
101 | mStackScrollerLeft.setOnItemClickListener(onLeftStackItemClickListener);
102 |
103 | View rightStackView = findViewById(R.id.stack_view_right);
104 | stackScrollPanelViewRight = (StackScrollPanelView) rightStackView.findViewById(R.id.stack_scroll_panel);
105 | mStackScrollerRight = (StackScrollLayout) rightStackView.findViewById(R.id.stack_scroller);
106 | stackHeaderRight = (TextView) rightStackView.findViewById(R.id.stack_scroll_header);
107 | stackViewAdapterRight = new DoubleStackViewAdapter(LayoutInflater.from(this));
108 | stackViewAdapterRight.setData(getBrandDrawables(0), getBrandNames(0));
109 | mStackScrollerRight.setAdapter(stackViewAdapterRight);
110 | mStackScrollerRight.setOnItemClickListener(onRightStackItemClickListener);
111 |
112 | stackHeaderLeft.setText("类别");
113 | stackHeaderRight.setText("店铺");
114 | }
115 |
116 | private void initValues() {
117 | leftTitles = new String[] {
118 | getString(R.string.item_name_19), getString(R.string.item_name_18),
119 | getString(R.string.item_name_17), getString(R.string.item_name_16),
120 | getString(R.string.item_name_15), getString(R.string.item_name_14),
121 | getString(R.string.item_name_13), getString(R.string.item_name_12),
122 | getString(R.string.item_name_11), getString(R.string.item_name_10),
123 | getString(R.string.item_name_9), getString(R.string.item_name_8),
124 | getString(R.string.item_name_7), getString(R.string.item_name_6),
125 | getString(R.string.item_name_5), getString(R.string.item_name_4),
126 | getString(R.string.item_name_3), getString(R.string.item_name_2),
127 | getString(R.string.item_name_1)
128 | };
129 | drawableLeftIds = new int[] {
130 | R.drawable.icon1, R.drawable.icon2, R.drawable.icon3, R.drawable.icon4,
131 | R.drawable.icon5, R.drawable.icon6, R.drawable.icon7, R.drawable.icon8,
132 | R.drawable.icon9, R.drawable.icon10, R.drawable.icon11, R.drawable.icon12,
133 | R.drawable.icon13, R.drawable.icon4, R.drawable.icon15, R.drawable.icon16,
134 | R.drawable.icon17, R.drawable.icon8, R.drawable.icon19
135 | };
136 | }
137 |
138 | private String[] getBrandNames(int position) {
139 | int length = (position) % 3 == 0 ? 6 - (position % 3) : drawableLeftIds.length;
140 | String[] brandNames = new String[length];
141 | for (int i = 0; i < brandNames.length; i++) {
142 | brandNames[i] = brands[position * drawableLeftIds.length + i];
143 | }
144 | return brandNames;
145 | }
146 |
147 | private int[] getBrandDrawables(int position) {
148 | int[] brandDrawablesIds;
149 | if (position % 3 == 0) {
150 | brandDrawablesIds = new int[6];
151 | for (int i = 0; i < brandDrawablesIds.length; i++) {
152 | brandDrawablesIds[i] = drawableLeftIds[position % 6 + i];
153 | }
154 | return brandDrawablesIds;
155 | }
156 |
157 | brandDrawablesIds = new int[drawableLeftIds.length];
158 | for (int i = position; i < drawableLeftIds.length - position; i++) {
159 | brandDrawablesIds[i] = drawableLeftIds[i + position];
160 | }
161 | for (int i = 0; i < position; i++) {
162 | brandDrawablesIds[i] = drawableLeftIds[position - i];
163 | }
164 | return brandDrawablesIds;
165 | }
166 |
167 | private void changBackground(View itemView, boolean focus) {
168 | if (itemView == null) {
169 | return;
170 | }
171 | if (focus) {
172 | ((TextView) itemView.findViewById(R.id.text)).setTextColor(0xffff5579);
173 | } else {
174 | itemView.findViewById(R.id.content).setBackgroundColor(Color.WHITE);
175 | ((TextView) itemView.findViewById(R.id.text)).setTextColor(Color.BLACK);
176 | }
177 | }
178 |
179 | }
180 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/LocalPathInterpolator.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | /**
4 | * Created by baidu on 16/4/1.
5 | */
6 |
7 | import java.lang.reflect.Method;
8 |
9 | import com.baidu.scrollstack.R;
10 |
11 | import android.content.Context;
12 | import android.content.res.Resources;
13 | import android.content.res.TypedArray;
14 | import android.graphics.Path;
15 | import android.util.AttributeSet;
16 |
17 | import android.util.Log;
18 | import android.view.InflateException;
19 | import android.view.animation.Interpolator;
20 |
21 | /**
22 | * An interpolator that can traverse a Path that extends from Point
23 | * (0, 0) to (1, 1). The x coordinate along the Path
24 | * is the input value and the output is the y coordinate of the line at that point.
25 | * This means that the Path must conform to a function y = f(x).
26 | *
27 | * The Path must not have gaps in the x direction and must not
28 | * loop back on itself such that there can be two points sharing the same x coordinate.
29 | * It is alright to have a disjoint line in the vertical direction:
30 | *
31 | * Path path = new Path();
32 | * path.lineTo(0.25f, 0.25f);
33 | * path.moveTo(0.25f, 0.5f);
34 | * path.lineTo(1f, 1f);
35 | *
36 | */
37 | public class LocalPathInterpolator extends BaseInterpolator {
38 |
39 | // This governs how accurate the approximation of the Path is.
40 | private static final float PRECISION = 0.002f;
41 | //x1 0.0, y1 0.0, x2 0.2, y2 1.0
42 | private final float[] key_0P4_0P0_0P2_1P0 = new float[] {0.0f, 0.0f, 0.0f, 0.023427581f, 0.035791017f, 0.0028686523f, 0.045366786f, 0.068359375f, 0.011230469f, 0.06661378f, 0.09799805f, 0.024719238f, 0.08787831f, 0.125f, 0.04296875f, 0.109721854f, 0.1496582f, 0.06561279f, 0.13253522f, 0.17226563f, 0.092285156f, 0.18184042f, 0.2125f, 0.15625f, 0.23639746f, 0.24804688f, 0.2319336f, 0.29561847f, 0.28125f, 0.31640625f, 0.42353198f, 0.35000002f, 0.5f, 0.489804f, 0.39023438f, 0.5932617f, 0.55632406f, 0.4375f, 0.68359375f, 0.62268347f, 0.49414063f, 0.7680664f, 0.6558978f, 0.52670896f, 0.80718994f, 0.6892802f, 0.5625f, 0.84375f, 0.7230328f, 0.60180664f, 0.8773804f, 0.75742936f, 0.6449219f, 0.90771484f, 0.79281265f, 0.6921387f, 0.9343872f, 0.8295862f, 0.74375f, 0.95703125f, 0.86820143f, 0.8000488f, 0.97528076f, 0.9091417f, 0.8613281f, 0.98876953f, 0.952907f, 0.9278809f, 0.99713135f, 1.0f, 1.0f, 1.0f};
43 | private final float[] key_0P4_0P0_1P0_1P0 = new float[] {0.0f, 0.0f, 0.0f, 0.026292618f, 0.038061526f, 0.0028686523f, 0.053826418f, 0.07714844f, 0.011230469f, 0.08288214f, 0.11711426f, 0.024719238f, 0.11360615f, 0.1578125f, 0.04296875f, 0.14604114f, 0.19909668f, 0.06561279f, 0.18015276f, 0.24082032f, 0.092285156f, 0.25298005f, 0.325f, 0.15625f, 0.33095658f, 0.4091797f, 0.2319336f, 0.41253653f, 0.4921875f, 0.31640625f, 0.49595878f, 0.57285154f, 0.40673828f, 0.5793328f, 0.65f, 0.5f, 0.66068685f, 0.7224609f, 0.5932617f, 0.73799545f, 0.7890625f, 0.68359375f, 0.80919707f, 0.8486328f, 0.7680664f, 0.87220454f, 0.9f, 0.84375f, 0.8999783f, 0.9222412f, 0.8773804f, 0.92491275f, 0.94199216f, 0.90771484f, 0.9467427f, 0.95910645f, 0.9343872f, 0.9652022f, 0.9734375f, 0.95703125f, 0.9800248f, 0.98483884f, 0.97528076f, 0.9909436f, 0.99316406f, 0.98876953f, 0.9976912f, 0.9982666f, 0.99713135f, 1.0f, 1.0f, 1.0f};
44 | private final float[] key_0P0_0P0_0P2_1P0 = new float[] {0.0f, 0.0f, 0.0f, 0.0019146586f, 5.9814454E-4f, 0.0028686523f, 0.00750935f, 0.0024414063f, 0.011230469f, 0.016561627f, 0.0056030275f, 0.024719238f, 0.028851194f, 0.010156251f, 0.04296875f, 0.044160172f, 0.016174316f, 0.06561279f, 0.062273446f, 0.02373047f, 0.092285156f, 0.10606654f, 0.043750003f, 0.15625f, 0.15858112f, 0.07080078f, 0.2319336f, 0.21824203f, 0.10546875f, 0.31640625f, 0.2835738f, 0.14833984f, 0.40673828f, 0.35323417f, 0.2f, 0.5f, 0.42606008f, 0.26103514f, 0.5932617f, 0.50112975f, 0.33203125f, 0.68359375f, 0.57784355f, 0.41357422f, 0.7680664f, 0.6560235f, 0.50625f, 0.84375f, 0.6957733f, 0.5569458f, 0.8773804f, 0.73607063f, 0.6106445f, 0.90771484f, 0.77705646f, 0.66741943f, 0.9343872f, 0.81891257f, 0.72734374f, 0.95703125f, 0.86186063f, 0.79049075f, 0.97528076f, 0.9061593f, 0.8569336f, 0.98876953f, 0.9520997f, 0.9267456f, 0.99713135f, 1.0f, 1.0f, 1.0f};
45 | private final float[] key_0P0_0P0_0P8_1P0 = new float[] {0.0f, 0.0f, 0.0f, 0.0025773577f, 0.0023010254f, 0.0028686523f, 0.010101028f, 0.009033203f, 0.011230469f, 0.022258457f, 0.019940186f, 0.024719238f, 0.038737163f, 0.034765627f, 0.04296875f, 0.059224747f, 0.053253174f, 0.06561279f, 0.08340891f, 0.07514649f, 0.092285156f, 0.11097745f, 0.10018921f, 0.12261963f, 0.14161831f, 0.12812501f, 0.15625f, 0.21086934f, 0.19165039f, 0.2319336f, 0.28866893f, 0.26367188f, 0.31640625f, 0.37252772f, 0.34213868f, 0.40673828f, 0.4599619f, 0.425f, 0.5f, 0.63565856f, 0.5957031f, 0.68359375f, 0.71902144f, 0.67944336f, 0.7680664f, 0.7961691f, 0.759375f, 0.84375f, 0.86476f, 0.8334473f, 0.90771484f, 0.89515543f, 0.86764526f, 0.9343872f, 0.92260915f, 0.8996094f, 0.95703125f, 0.94690496f, 0.9290832f, 0.97528076f, 0.96788716f, 0.95581055f, 0.98876953f, 0.98551685f, 0.9795349f, 0.99713135f, 1.0f, 1.0f, 1.0f};
46 | private final float[] key_0P0_0P0_0P35_1P0 = new float[] {0.0f, 0.0f, 0.0f, 0.002035768f, 0.0010238647f, 0.0028686523f, 0.007988239f, 0.0040893555f, 0.011230469f, 0.017626053f, 0.0091873165f, 0.024719238f, 0.030719133f, 0.016308594f, 0.04296875f, 0.04703886f, 0.02544403f, 0.06561279f, 0.06635824f, 0.036584474f, 0.092285156f, 0.11309643f, 0.064843744f, 0.15625f, 0.1691604f, 0.10101318f, 0.2319336f, 0.23282084f, 0.14501953f, 0.31640625f, 0.3024079f, 0.19678955f, 0.40673828f, 0.37633184f, 0.25625f, 0.5f, 0.45311284f, 0.32332763f, 0.5932617f, 0.53142357f, 0.39794922f, 0.68359375f, 0.6101511f, 0.4800415f, 0.7680664f, 0.68848515f, 0.56953126f, 0.84375f, 0.7660398f, 0.66634524f, 0.90771484f, 0.80458397f, 0.7174759f, 0.9343872f, 0.8430646f, 0.7704102f, 0.95703125f, 0.8816234f, 0.82513887f, 0.97528076f, 0.92045635f, 0.88165283f, 0.98876953f, 0.95981425f, 0.93994296f, 0.99713135f, 1.0f, 1.0f, 1.0f};
47 |
48 | private float[] mX; // x coordinates in the line
49 |
50 | private float[] mY; // y coordinates in the line
51 |
52 | /**
53 | * Create an interpolator for a cubic Bezier curve. The end points
54 | * (0, 0) and (1, 1) are assumed.
55 | *
56 | * @param controlX1 The x coordinate of the first control point of the cubic Bezier.
57 | * @param controlY1 The y coordinate of the first control point of the cubic Bezier.
58 | * @param controlX2 The x coordinate of the second control point of the cubic Bezier.
59 | * @param controlY2 The y coordinate of the second control point of the cubic Bezier.
60 | */
61 | public LocalPathInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
62 | initCubic(controlX1, controlY1, controlX2, controlY2);
63 | }
64 |
65 | private void initCubic(float x1, float y1, float x2, float y2) {
66 | Path path = new Path();
67 | path.moveTo(0, 0);
68 | path.cubicTo(x1, y1, x2, y2, 1f, 1f);
69 | String key = ("key" + "_" + x1 + "_" + y1 + "_" + x2 + "_" + y2).replace(".", "P");
70 | initPath(path, key);
71 | }
72 |
73 | private void initPath(Path path, String key) {
74 | float[] pointComponents = new float[] {0, 0, 0, 1, 1, 1};
75 | switch (key) {
76 | case "key_0P4_0P0_0P2_1P0":
77 | pointComponents = key_0P4_0P0_0P2_1P0;
78 | break;
79 | case "key_0P4_0P0_1P0_1P0":
80 | pointComponents = key_0P4_0P0_1P0_1P0;
81 | break;
82 | case "key_0P0_0P0_0P2_1P0":
83 | pointComponents = key_0P0_0P0_0P2_1P0;
84 | break;
85 | case "key_0P0_0P0_0P8_1P0":
86 | pointComponents = key_0P0_0P0_0P8_1P0;
87 | break;
88 | case "key_0P0_0P0_0P35_1P0":
89 | pointComponents = key_0P0_0P0_0P35_1P0;
90 | break;
91 | default:
92 | break;
93 | }
94 | // if (key.equals("key_0P0_0P0_0P35_1P0")) {
95 | // try {
96 | // Class pathClass = path.getClass();
97 | // Method method = pathClass.getDeclaredMethod("approximate", new Class[] {float.class});
98 | // pointComponents = (float[]) method.invoke(path, new Object[] {PRECISION});
99 | // } catch (Exception e) {
100 | // e.printStackTrace();
101 | // }
102 | // Log.i("AndroidRuntime", "initPath key " + key);
103 | // StringBuffer stringBuffer = new StringBuffer();
104 | // for (int i = 0; i < pointComponents.length; i++) {
105 | // stringBuffer.append(pointComponents[i] + "f, ");
106 | // }
107 | // Log.i("AndroidRuntime", "pointComponents " + stringBuffer.toString());
108 | // Log.i("AndroidRuntime", "----------------------------------------------------------");
109 | // }
110 | int numPoints = pointComponents.length / 3;
111 | if (pointComponents[1] != 0 || pointComponents[2] != 0
112 | || pointComponents[pointComponents.length - 2] != 1
113 | || pointComponents[pointComponents.length - 1] != 1) {
114 | throw new IllegalArgumentException("!!!!!!!!!!!!!!The Path must start at (0,0) and end at (1,1)");
115 | }
116 |
117 | mX = new float[numPoints];
118 | mY = new float[numPoints];
119 | float prevX = 0;
120 | float prevFraction = 0;
121 | int componentIndex = 0;
122 | for (int i = 0; i < numPoints; i++) {
123 | float fraction = pointComponents[componentIndex++];
124 | float x = pointComponents[componentIndex++];
125 | float y = pointComponents[componentIndex++];
126 | if (fraction == prevFraction && x != prevX) {
127 | throw new IllegalArgumentException(
128 | "The Path cannot have discontinuity in the X axis.");
129 | }
130 | if (x < prevX) {
131 | throw new IllegalArgumentException("The Path cannot loop back on itself.");
132 | }
133 | mX[i] = x;
134 | mY[i] = y;
135 | prevX = x;
136 | prevFraction = fraction;
137 | }
138 | }
139 |
140 | /**
141 | * Using the line in the Path in this interpolator that can be described as
142 | * y = f(x), finds the y coordinate of the line given t
143 | * as the x coordinate. Values less than 0 will always return 0 and values greater
144 | * than 1 will always return 1.
145 | *
146 | * @param t Treated as the x coordinate along the line.
147 | *
148 | * @return The y coordinate of the Path along the line where x = t.
149 | *
150 | * @see Interpolator#getInterpolation(float)
151 | */
152 | @Override
153 | public float getInterpolation(float t) {
154 | if (t <= 0) {
155 | return 0;
156 | } else if (t >= 1) {
157 | return 1;
158 | }
159 | // Do a binary search for the correct x to interpolate between.
160 | int startIndex = 0;
161 | int endIndex = mX.length - 1;
162 |
163 | while (endIndex - startIndex > 1) {
164 | int midIndex = (startIndex + endIndex) / 2;
165 | if (t < mX[midIndex]) {
166 | endIndex = midIndex;
167 | } else {
168 | startIndex = midIndex;
169 | }
170 | }
171 |
172 | float xRange = mX[endIndex] - mX[startIndex];
173 | if (xRange == 0) {
174 | return mY[startIndex];
175 | }
176 |
177 | float tInRange = t - mX[startIndex];
178 | float fraction = tInRange / xRange;
179 |
180 | float startY = mY[startIndex];
181 | float endY = mY[endIndex];
182 | return startY + (fraction * (endY - startY));
183 | }
184 | }
185 |
--------------------------------------------------------------------------------
/demo/src/main/java/com/example/baidu/app/activity/TimeSetActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.baidu.app.activity;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import com.baidu.scrollstack.stack.StackScrollLayout;
7 | import com.baidu.scrollstack.view.StackScrollPanelView;
8 | import com.example.baidu.app.R;
9 | import com.example.baidu.app.adapter.StackViewAdapter;
10 | import com.example.baidu.app.entity.TimeItemInfo;
11 |
12 | import android.app.Activity;
13 | import android.graphics.Color;
14 | import android.os.Bundle;
15 | import android.os.Handler;
16 | import android.os.Message;
17 | import android.view.LayoutInflater;
18 | import android.view.View;
19 | import android.widget.TextView;
20 |
21 | /**
22 | * Created by helingjian on 16/3/14.
23 | */
24 | public class TimeSetActivity extends Activity {
25 |
26 | private static final int TAG_YEAR = 0;
27 | private static final int TAG_MOINTH = 1;
28 | private static final int TAG_DAY = 2;
29 | private List yearInfos = new ArrayList();
30 | private List monthInfos = new ArrayList();
31 | private List dayInfos = new ArrayList();
32 | private View lastYearItemView;
33 | private StackScrollLayout mStackScrollerDays;
34 | private StackScrollLayout mStackScrollerMonth;
35 | private StackScrollLayout mStackScrollerYear;
36 | private Handler handler = new Handler() {
37 | @Override
38 | public void handleMessage(Message msg) {
39 | super.handleMessage(msg);
40 | switch (msg.what) {
41 | case TAG_YEAR: {
42 | View itemView = (View) msg.obj;
43 | int ownScrollY = mStackScrollerYear.getOwnScrollY();
44 | int toY;
45 | int translationY = (int) itemView.getTranslationY();
46 | int speed = 10;//msg.arg2;//Math.max(Math.abs(msg.arg1 - translationY) / 10, 5);
47 | if (translationY > msg.arg1) {
48 | toY = ownScrollY + speed;
49 | toY = Math.min(toY, ownScrollY + (translationY - msg.arg1));
50 | } else if (translationY < msg.arg1) {
51 | toY = ownScrollY - speed;
52 | toY = Math.max(ownScrollY - (msg.arg1 - translationY), toY);
53 | } else {
54 | break;
55 | }
56 | mStackScrollerYear.setOwnScrollY(toY);
57 | mStackScrollerYear.updateChildren();
58 | if (itemView.getTranslationY() != msg.arg1) {
59 | smoothToCenter(itemView, TAG_YEAR, msg.arg2);
60 | }
61 | }
62 | break;
63 |
64 | case TAG_MOINTH: {
65 | View itemView = (View) msg.obj;
66 | int ownScrollY = mStackScrollerMonth.getOwnScrollY();
67 | int toY;
68 | int translationY = (int) itemView.getTranslationY();
69 | int speed = 10;//msg.arg2;//Math.max(Math.abs(msg.arg1 - translationY) / 10, 5);
70 | if (translationY > msg.arg1) {
71 | toY = ownScrollY + speed;
72 | toY = Math.min(toY, ownScrollY + (translationY - msg.arg1));
73 | } else if (translationY < msg.arg1) {
74 | toY = ownScrollY - speed;
75 | toY = Math.max(ownScrollY - (msg.arg1 - translationY), toY);
76 | } else {
77 | break;
78 | }
79 | mStackScrollerMonth.setOwnScrollY(toY);
80 | mStackScrollerMonth.updateChildren();
81 | if (itemView.getTranslationY() != msg.arg1) {
82 | smoothToCenter(itemView, TAG_MOINTH, msg.arg2);
83 | }
84 | }
85 | break;
86 |
87 | case TAG_DAY: {
88 | View itemView = (View) msg.obj;
89 | int ownScrollY = mStackScrollerDays.getOwnScrollY();
90 | int toY;
91 | int translationY = (int) itemView.getTranslationY();
92 | int speed = 10;//msg.arg2;//Math.max(Math.abs(msg.arg1 - translationY) / 10, 5);
93 | if (translationY > msg.arg1) {
94 | toY = ownScrollY + speed;
95 | toY = Math.min(toY, ownScrollY + (translationY - msg.arg1));
96 | } else if (translationY < msg.arg1) {
97 | toY = ownScrollY - speed;
98 | toY = Math.max(ownScrollY - (msg.arg1 - translationY), toY);
99 | } else {
100 | break;
101 | }
102 | mStackScrollerDays.setOwnScrollY(toY);
103 | mStackScrollerDays.updateChildren();
104 | if (itemView.getTranslationY() != msg.arg1) {
105 | smoothToCenter(itemView, TAG_DAY, msg.arg2);
106 | }
107 | }
108 | default:
109 | break;
110 | }
111 | }
112 | };
113 | StackScrollLayout.OnItemClickListener onYearItemClickListener = new StackScrollLayout.OnItemClickListener() {
114 | @Override
115 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
116 | if (position == 0) {
117 | return;
118 | }
119 | TimeItemInfo info = (TimeItemInfo) itemView.getTag();
120 | info.setSelect(!info.isSelect());
121 | changBackground(itemView, info.isSelect());
122 |
123 | if (lastYearItemView != null && lastYearItemView != itemView) {
124 | TimeItemInfo lastInfo = (TimeItemInfo) lastYearItemView.getTag();
125 | lastInfo.setSelect(false);
126 | changBackground(lastYearItemView, lastInfo.isSelect());
127 | }
128 | lastYearItemView = itemView;
129 | smoothToCenter(itemView, TAG_YEAR, 0);
130 | }
131 | };
132 | private View lastMonthItemView;
133 | StackScrollLayout.OnItemClickListener onMonthItemClickListener = new StackScrollLayout.OnItemClickListener() {
134 | @Override
135 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
136 | if (position == 0) {
137 | return;
138 | }
139 | TimeItemInfo info = (TimeItemInfo) itemView.getTag();
140 | info.setSelect(!info.isSelect());
141 | changBackground(itemView, info.isSelect());
142 |
143 | if (lastMonthItemView != null && lastMonthItemView != itemView) {
144 | TimeItemInfo lastInfo = (TimeItemInfo) lastMonthItemView.getTag();
145 | lastInfo.setSelect(false);
146 | changBackground(lastMonthItemView, lastInfo.isSelect());
147 | }
148 | lastMonthItemView = itemView;
149 | smoothToCenter(itemView, TAG_MOINTH, 0);
150 | }
151 | };
152 | private View lastDayItemView;
153 | StackScrollLayout.OnItemClickListener onDayItemClickListener = new StackScrollLayout.OnItemClickListener() {
154 | @Override
155 | public void onItemClick(StackScrollLayout parent, View itemView, int position, long id) {
156 | if (position == 0) {
157 | return;
158 | }
159 |
160 | TimeItemInfo info = (TimeItemInfo) itemView.getTag();
161 | info.setSelect(!info.isSelect());
162 | changBackground(itemView, info.isSelect());
163 |
164 | if (lastDayItemView != null && lastDayItemView != itemView) {
165 | TimeItemInfo lastInfo = (TimeItemInfo) lastDayItemView.getTag();
166 | lastInfo.setSelect(false);
167 | changBackground(lastDayItemView, lastInfo.isSelect());
168 | }
169 | lastDayItemView = itemView;
170 | smoothToCenter(itemView, TAG_DAY, 0);
171 | }
172 | };
173 |
174 | @Override
175 | protected void onCreate(Bundle savedInstanceState) {
176 | super.onCreate(savedInstanceState);
177 | setContentView(R.layout.activity_time_set_layout);
178 | initValues();
179 | insertTitles();
180 | initScroller();
181 | }
182 |
183 | private void insertTitles() {
184 | yearInfos.add(0, new TimeItemInfo("年份", false));
185 | monthInfos.add(0, new TimeItemInfo("月份", false));
186 | dayInfos.add(0, new TimeItemInfo("日期", false));
187 | }
188 |
189 | private void initValues() {
190 | for (int i = 1970; i <= 2016; i++) {
191 | yearInfos.add(new TimeItemInfo(String.valueOf(i), false));
192 | }
193 | for (int i = 0; i <= 12; i++) {
194 | monthInfos.add(new TimeItemInfo(String.valueOf(i), false));
195 | }
196 | for (int i = 0; i <= 30; i++) {
197 | dayInfos.add(new TimeItemInfo(String.valueOf(i), false));
198 | }
199 | }
200 |
201 | private void initScroller() {
202 | initYearStackView();
203 | initMonthStackView();
204 | initDayStackView();
205 | }
206 |
207 | private void initYearStackView() {
208 | View YearStackView = findViewById(R.id.stack_view_year);
209 | mStackScrollerYear = (StackScrollLayout) YearStackView.findViewById(R.id.stack_scroller);
210 | StackViewAdapter stackViewAdapterYear = new StackViewAdapter(LayoutInflater.from(this));
211 | stackViewAdapterYear.setData(yearInfos);
212 | mStackScrollerYear.setAdapter(stackViewAdapterYear);
213 | mStackScrollerYear.setOnItemClickListener(onYearItemClickListener);
214 | StackScrollPanelView stackScrollYearPanelView =
215 | (StackScrollPanelView) YearStackView.findViewById(R.id.stack_scroll_panel);
216 | stackScrollYearPanelView.setTouchDisabled(true);
217 | stackScrollYearPanelView.expand(true);
218 | }
219 |
220 | private void initMonthStackView() {
221 | View MonthStackView = findViewById(R.id.stack_view_month);
222 | mStackScrollerMonth = (StackScrollLayout) MonthStackView.findViewById(R.id.stack_scroller);
223 | StackViewAdapter stackViewAdapterMonth = new StackViewAdapter(LayoutInflater.from(this));
224 | stackViewAdapterMonth.setData(monthInfos);
225 | mStackScrollerMonth.setAdapter(stackViewAdapterMonth);
226 | mStackScrollerMonth.setOnItemClickListener(onMonthItemClickListener);
227 | StackScrollPanelView stackScrollMonthPanelView =
228 | (StackScrollPanelView) MonthStackView.findViewById(R.id.stack_scroll_panel);
229 | stackScrollMonthPanelView.setTouchDisabled(true);
230 | stackScrollMonthPanelView.expand(true);
231 | }
232 |
233 | private void initDayStackView() {
234 | View daysStackView = findViewById(R.id.stack_view_days);
235 | mStackScrollerDays = (StackScrollLayout) daysStackView.findViewById(R.id.stack_scroller);
236 | StackViewAdapter stackViewAdapterDays = new StackViewAdapter(LayoutInflater.from(this));
237 | stackViewAdapterDays.setData(dayInfos);
238 | mStackScrollerDays.setAdapter(stackViewAdapterDays);
239 | mStackScrollerDays.setOnItemClickListener(onDayItemClickListener);
240 | StackScrollPanelView stackScrollDaysPanelView =
241 | (StackScrollPanelView) daysStackView.findViewById(R.id.stack_scroll_panel);
242 | stackScrollDaysPanelView.setTouchDisabled(true);
243 | stackScrollDaysPanelView.expand(true);
244 | }
245 |
246 | private void changBackground(View itemView, boolean focus) {
247 | if (itemView == null) {
248 | return;
249 | }
250 | if (focus) {
251 | itemView.findViewById(R.id.content).setBackgroundColor(0xff116cd5);
252 | ((TextView) itemView.findViewById(R.id.text)).setTextColor(Color.WHITE);
253 | } else {
254 | itemView.findViewById(R.id.content).setBackgroundColor(0xff489bf8);
255 | ((TextView) itemView.findViewById(R.id.text)).setTextColor(Color.WHITE);
256 | }
257 | }
258 |
259 | private void smoothToCenter(View itemView, int type, int speed) {
260 | final int center = 104 + 20 + 104 + 20 + 20;
261 | Message message = Message.obtain();
262 | message.what = type;
263 | message.obj = itemView;
264 | message.arg1 = center;
265 | if (speed == 0) {
266 | message.arg2 = (int) (Math.abs(itemView.getTranslationY() - center) / 10);
267 | }
268 | handler.removeMessages(type);
269 | handler.sendMessageDelayed(message, 0);
270 | }
271 | }
272 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/uitl/FlingAnimationUtils.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.uitl;
2 |
3 | import android.animation.Animator;
4 | import android.annotation.TargetApi;
5 | import android.content.Context;
6 | import android.os.Build;
7 | import android.view.ViewPropertyAnimator;
8 | import android.view.animation.AnimationUtils;
9 | import android.view.animation.DecelerateInterpolator;
10 | import android.view.animation.Interpolator;
11 | import android.view.animation.PathInterpolator;
12 |
13 | /**
14 | * Utility class to calculate general fling animation when the finger is released.
15 | */
16 | public class FlingAnimationUtils {
17 |
18 | private static final float LINEAR_OUT_SLOW_IN_X2 = 0.35f;
19 | private static final float LINEAR_OUT_FASTER_IN_X2 = 0.5f;
20 | private static final float LINEAR_OUT_FASTER_IN_Y2_MIN = 0.4f;
21 | private static final float LINEAR_OUT_FASTER_IN_Y2_MAX = 0.5f;
22 | private static final float MIN_VELOCITY_DP_PER_SECOND = 250;
23 | private static final float HIGH_VELOCITY_DP_PER_SECOND = 3000;
24 |
25 | /**
26 | * Crazy math. http://en.wikipedia.org/wiki/B%C3%A9zier_curve
27 | */
28 | private static final float LINEAR_OUT_SLOW_IN_START_GRADIENT = 1.0f / LINEAR_OUT_SLOW_IN_X2;
29 |
30 | private Interpolator mLinearOutSlowIn;
31 | private Interpolator mFastOutSlowIn;
32 | private Interpolator mFastOutLinearIn;
33 |
34 | private float mMinVelocityPxPerSecond;
35 | private float mMaxLengthSeconds;
36 | private float mHighVelocityPxPerSecond;
37 |
38 | private AnimatorProperties mAnimatorProperties = new AnimatorProperties();
39 |
40 | public FlingAnimationUtils(Context ctx, float maxLengthSeconds) {
41 | mMaxLengthSeconds = maxLengthSeconds;
42 | mLinearOutSlowIn = generateInterpolator(0, 0, LINEAR_OUT_SLOW_IN_X2, 1);
43 | if (Define.SDK_INT >= 21) {
44 | mFastOutSlowIn = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_slow_in);
45 | mFastOutLinearIn = AnimationUtils.loadInterpolator(ctx, android.R.interpolator.fast_out_linear_in);
46 | } else {
47 | mFastOutSlowIn = new LocalPathInterpolator(0.4f, 0, 0.2f, 1);
48 | mFastOutLinearIn = new LocalPathInterpolator(0.4f, 0, 1, 1);
49 | }
50 |
51 | mMinVelocityPxPerSecond
52 | = MIN_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
53 | mHighVelocityPxPerSecond
54 | = HIGH_VELOCITY_DP_PER_SECOND * ctx.getResources().getDisplayMetrics().density;
55 | }
56 |
57 | /**
58 | * Applies the interpolator and length to the animator, such that the fling animation is
59 | * consistent with the finger motion.
60 | *
61 | * @param animator the animator to apply
62 | * @param currValue the current value
63 | * @param endValue the end value of the animator
64 | * @param velocity the current velocity of the motion
65 | */
66 | public void apply(Animator animator, float currValue, float endValue, float velocity) {
67 | apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
68 | }
69 |
70 | /**
71 | * Applies the interpolator and length to the animator, such that the fling animation is
72 | * consistent with the finger motion.
73 | *
74 | * @param animator the animator to apply
75 | * @param currValue the current value
76 | * @param endValue the end value of the animator
77 | * @param velocity the current velocity of the motion
78 | */
79 | public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
80 | float velocity) {
81 | apply(animator, currValue, endValue, velocity, Math.abs(endValue - currValue));
82 | }
83 |
84 | /**
85 | * Applies the interpolator and length to the animator, such that the fling animation is
86 | * consistent with the finger motion.
87 | *
88 | * @param animator the animator to apply
89 | * @param currValue the current value
90 | * @param endValue the end value of the animator
91 | * @param velocity the current velocity of the motion
92 | * @param maxDistance the maximum distance for this interaction; the maximum animation length
93 | * gets multiplied by the ratio between the actual distance and this value
94 | */
95 | public void apply(Animator animator, float currValue, float endValue, float velocity,
96 | float maxDistance) {
97 | AnimatorProperties properties = getProperties(currValue, endValue, velocity,
98 | maxDistance);
99 | animator.setDuration(properties.duration);
100 | animator.setInterpolator(properties.interpolator);
101 | }
102 |
103 | /**
104 | * Applies the interpolator and length to the animator, such that the fling animation is
105 | * consistent with the finger motion.
106 | *
107 | * @param animator the animator to apply
108 | * @param currValue the current value
109 | * @param endValue the end value of the animator
110 | * @param velocity the current velocity of the motion
111 | * @param maxDistance the maximum distance for this interaction; the maximum animation length
112 | * gets multiplied by the ratio between the actual distance and this value
113 | */
114 | public void apply(ViewPropertyAnimator animator, float currValue, float endValue,
115 | float velocity, float maxDistance) {
116 | AnimatorProperties properties = getProperties(currValue, endValue, velocity,
117 | maxDistance);
118 | animator.setDuration(properties.duration);
119 | animator.setInterpolator(properties.interpolator);
120 | }
121 |
122 | private AnimatorProperties getProperties(float currValue,
123 | float endValue, float velocity, float maxDistance) {
124 | float maxLengthSeconds = (float) (mMaxLengthSeconds
125 | * Math.sqrt(Math.abs(endValue - currValue) / maxDistance));
126 | float diff = Math.abs(endValue - currValue);
127 | float velAbs = Math.abs(velocity);
128 | float durationSeconds = LINEAR_OUT_SLOW_IN_START_GRADIENT * diff / velAbs;
129 | if (durationSeconds <= maxLengthSeconds) {
130 | mAnimatorProperties.interpolator = mLinearOutSlowIn;
131 | } else if (velAbs >= mMinVelocityPxPerSecond) {
132 |
133 | // Cross fade between fast-out-slow-in and linear interpolator with current velocity.
134 | durationSeconds = maxLengthSeconds;
135 | VelocityInterpolator velocityInterpolator
136 | = new VelocityInterpolator(durationSeconds, velAbs, diff);
137 | InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
138 | velocityInterpolator, mLinearOutSlowIn, mLinearOutSlowIn);
139 | mAnimatorProperties.interpolator = superInterpolator;
140 | } else {
141 |
142 | // Just use a normal interpolator which doesn't take the velocity into account.
143 | durationSeconds = maxLengthSeconds;
144 | mAnimatorProperties.interpolator = mFastOutSlowIn;
145 | }
146 | mAnimatorProperties.duration = (long) (durationSeconds * 1000);
147 | return mAnimatorProperties;
148 | }
149 |
150 | /**
151 | * Applies the interpolator and length to the animator, such that the fling animation is
152 | * consistent with the finger motion for the case when the animation is making something
153 | * disappear.
154 | *
155 | * @param animator the animator to apply
156 | * @param currValue the current value
157 | * @param endValue the end value of the animator
158 | * @param velocity the current velocity of the motion
159 | * @param maxDistance the maximum distance for this interaction; the maximum animation length
160 | * gets multiplied by the ratio between the actual distance and this value
161 | */
162 | public void applyDismissing(Animator animator, float currValue, float endValue,
163 | float velocity, float maxDistance) {
164 | AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
165 | maxDistance);
166 | animator.setDuration(properties.duration);
167 | animator.setInterpolator(properties.interpolator);
168 | }
169 |
170 | /**
171 | * Applies the interpolator and length to the animator, such that the fling animation is
172 | * consistent with the finger motion for the case when the animation is making something
173 | * disappear.
174 | *
175 | * @param animator the animator to apply
176 | * @param currValue the current value
177 | * @param endValue the end value of the animator
178 | * @param velocity the current velocity of the motion
179 | * @param maxDistance the maximum distance for this interaction; the maximum animation length
180 | * gets multiplied by the ratio between the actual distance and this value
181 | */
182 | public void applyDismissing(ViewPropertyAnimator animator, float currValue, float endValue,
183 | float velocity, float maxDistance) {
184 | AnimatorProperties properties = getDismissingProperties(currValue, endValue, velocity,
185 | maxDistance);
186 | animator.setDuration(properties.duration);
187 | animator.setInterpolator(properties.interpolator);
188 | }
189 |
190 | private AnimatorProperties getDismissingProperties(float currValue, float endValue,
191 | float velocity, float maxDistance) {
192 | float maxLengthSeconds = (float) (mMaxLengthSeconds
193 | * Math.pow(Math.abs(endValue - currValue) / maxDistance, 0.5f));
194 | float diff = Math.abs(endValue - currValue);
195 | float velAbs = Math.abs(velocity);
196 | float y2 = calculateLinearOutFasterInY2(velAbs);
197 |
198 | float startGradient = y2 / LINEAR_OUT_FASTER_IN_X2;
199 | Interpolator mLinearOutFasterIn = generateInterpolator(0, 0, LINEAR_OUT_FASTER_IN_X2, y2);
200 | float durationSeconds = startGradient * diff / velAbs;
201 | if (durationSeconds <= maxLengthSeconds) {
202 | mAnimatorProperties.interpolator = mLinearOutFasterIn;
203 | } else if (velAbs >= mMinVelocityPxPerSecond) {
204 |
205 | // Cross fade between linear-out-faster-in and linear interpolator with current
206 | // velocity.
207 | durationSeconds = maxLengthSeconds;
208 | VelocityInterpolator velocityInterpolator
209 | = new VelocityInterpolator(durationSeconds, velAbs, diff);
210 | InterpolatorInterpolator superInterpolator = new InterpolatorInterpolator(
211 | velocityInterpolator, mLinearOutFasterIn, mLinearOutSlowIn);
212 | mAnimatorProperties.interpolator = superInterpolator;
213 | } else {
214 |
215 | // Just use a normal interpolator which doesn't take the velocity into account.
216 | durationSeconds = maxLengthSeconds;
217 | mAnimatorProperties.interpolator = mFastOutLinearIn;
218 | }
219 | mAnimatorProperties.duration = (long) (durationSeconds * 1000);
220 | return mAnimatorProperties;
221 | }
222 |
223 | /**
224 | * Calculates the y2 control point for a linear-out-faster-in path interpolator depending on the
225 | * velocity. The faster the velocity, the more "linear" the interpolator gets.
226 | *
227 | * @param velocity the velocity of the gesture.
228 | *
229 | * @return the y2 control point for a cubic bezier path interpolator
230 | */
231 | private float calculateLinearOutFasterInY2(float velocity) {
232 | float t = (velocity - mMinVelocityPxPerSecond)
233 | / (mHighVelocityPxPerSecond - mMinVelocityPxPerSecond);
234 | t = Math.max(0, Math.min(1, t));
235 | return (1 - t) * LINEAR_OUT_FASTER_IN_Y2_MIN + t * LINEAR_OUT_FASTER_IN_Y2_MAX;
236 | }
237 |
238 | /**
239 | * @return the minimum velocity a gesture needs to have to be considered a fling
240 | */
241 | public float getMinVelocityPxPerSecond() {
242 | return mMinVelocityPxPerSecond;
243 | }
244 |
245 | /**
246 | * An interpolator which interpolates two interpolators with an interpolator.
247 | */
248 | private static final class InterpolatorInterpolator implements Interpolator {
249 |
250 | private Interpolator mInterpolator1;
251 | private Interpolator mInterpolator2;
252 | private Interpolator mCrossfader;
253 |
254 | InterpolatorInterpolator(Interpolator interpolator1, Interpolator interpolator2,
255 | Interpolator crossfader) {
256 | mInterpolator1 = interpolator1;
257 | mInterpolator2 = interpolator2;
258 | mCrossfader = crossfader;
259 | }
260 |
261 | @Override
262 | public float getInterpolation(float input) {
263 | float t = mCrossfader.getInterpolation(input);
264 | return (1 - t) * mInterpolator1.getInterpolation(input)
265 | + t * mInterpolator2.getInterpolation(input);
266 | }
267 | }
268 |
269 | /**
270 | * An interpolator which interpolates with a fixed velocity.
271 | */
272 | private static final class VelocityInterpolator implements Interpolator {
273 |
274 | private float mDurationSeconds;
275 | private float mVelocity;
276 | private float mDiff;
277 |
278 | private VelocityInterpolator(float durationSeconds, float velocity, float diff) {
279 | mDurationSeconds = durationSeconds;
280 | mVelocity = velocity;
281 | mDiff = diff;
282 | }
283 |
284 | @Override
285 | public float getInterpolation(float input) {
286 | float time = input * mDurationSeconds;
287 | return time * mVelocity / mDiff;
288 | }
289 | }
290 |
291 | private static class AnimatorProperties {
292 | Interpolator interpolator;
293 | long duration;
294 | }
295 |
296 | @TargetApi(21)
297 | private Interpolator generateInterpolator(float controlX1, float controlY1, float controlX2, float controlY2) {
298 | if (Define.SDK_INT >= 21) {
299 | return new PathInterpolator(controlX1, controlY1, controlX2, controlY2);
300 | } else {
301 | return new LocalPathInterpolator(controlX1, controlY1, controlX2, controlY2);
302 | }
303 | }
304 |
305 | }
306 |
--------------------------------------------------------------------------------
/StackscrollLayout/src/main/java/com/baidu/scrollstack/view/ExpandableView.java:
--------------------------------------------------------------------------------
1 | package com.baidu.scrollstack.view;
2 |
3 | import java.util.ArrayList;
4 |
5 | import com.baidu.scrollstack.R;
6 | import com.baidu.scrollstack.stack.StackScrollLayout;
7 | import com.baidu.scrollstack.uitl.Define;
8 |
9 | import android.content.Context;
10 | import android.graphics.Canvas;
11 | import android.graphics.Color;
12 | import android.graphics.LinearGradient;
13 | import android.graphics.Paint;
14 | import android.graphics.Rect;
15 | import android.graphics.Shader;
16 | import android.util.AttributeSet;
17 | import android.util.Log;
18 | import android.view.MotionEvent;
19 | import android.view.View;
20 | import android.view.ViewGroup;
21 | import android.widget.FrameLayout;
22 |
23 | /**
24 | * An abstract view for expandable views.
25 | */
26 | public abstract class ExpandableView extends FrameLayout implements View.OnClickListener {
27 |
28 | private final int mMaxNotificationHeight;
29 | protected int mActualHeight;
30 | protected int mClipTopAmount;
31 | protected int currentAlpha;
32 | protected int shadowRadius;
33 | protected int shadowStartColor;
34 | protected int shadowEndColor;
35 | private OnHeightChangedListener mOnHeightChangedListener;
36 | private boolean mActualHeightInitialized;
37 | private ArrayList mMatchParentViews = new ArrayList();
38 | private PerformClick performClick;
39 | private OnClickListener onClickListener;
40 |
41 | public ExpandableView(Context context, AttributeSet attrs) {
42 | super(context, attrs);
43 | mMaxNotificationHeight = getResources().getDimensionPixelSize(
44 | R.dimen.stackitem_max_height);
45 | shadowRadius = getResources().getDimensionPixelSize(
46 | R.dimen.shadow_radius_size);
47 | shadowStartColor = getResources().getColor(R.color.shadow_start_color);
48 | shadowEndColor = getResources().getColor(R.color.shadow_end_color);
49 | performClick = new PerformClick();
50 | super.setOnClickListener(this);
51 | }
52 |
53 | @Override
54 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
55 | int ownMaxHeight = mMaxNotificationHeight;
56 | int heightMode = MeasureSpec.getMode(heightMeasureSpec);
57 | boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
58 | boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
59 | if (hasFixedHeight || isHeightLimited) {
60 | int size = MeasureSpec.getSize(heightMeasureSpec);
61 | ownMaxHeight = Math.min(ownMaxHeight, size);
62 | }
63 | int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
64 | int maxChildHeight = 0;
65 | int childCount = getChildCount();
66 | for (int i = 0; i < childCount; i++) {
67 | View child = getChildAt(i);
68 | int childHeightSpec = newHeightSpec;
69 | ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
70 | if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
71 | if (layoutParams.height >= 0) {
72 | // An actual height is set
73 | childHeightSpec = layoutParams.height > ownMaxHeight
74 | ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
75 | : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
76 | }
77 | child.measure(
78 | getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
79 | childHeightSpec);
80 | int childHeight = child.getMeasuredHeight();
81 | if (Define.SDK_INT < 21) {
82 | childHeight += shadowRadius;
83 | maxChildHeight += shadowRadius;
84 | }
85 | maxChildHeight = Math.max(maxChildHeight, childHeight);
86 | } else {
87 | mMatchParentViews.add(child);
88 | }
89 | }
90 | int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
91 | newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
92 | for (View child : mMatchParentViews) {
93 | child.measure(getChildMeasureSpec(
94 | widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
95 | newHeightSpec);
96 | }
97 | mMatchParentViews.clear();
98 | int width = MeasureSpec.getSize(widthMeasureSpec);
99 | setMeasuredDimension(width, ownHeight);
100 | }
101 |
102 | @Override
103 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
104 | super.onLayout(changed, left, top, right, bottom);
105 | if (!mActualHeightInitialized && mActualHeight == 0) {
106 | int initialHeight = getInitialHeight();
107 | if (initialHeight != 0) {
108 | setActualHeight(initialHeight);
109 | }
110 | }
111 | }
112 |
113 | protected int getInitialHeight() {
114 | return getHeight();
115 | }
116 |
117 | @Override
118 | public boolean dispatchTouchEvent(MotionEvent ev) {
119 | if (filterMotionEvent(ev)) {
120 | return super.dispatchTouchEvent(ev);
121 | }
122 | return false;
123 | }
124 |
125 | private boolean filterMotionEvent(MotionEvent event) {
126 | return event.getActionMasked() != MotionEvent.ACTION_DOWN
127 | || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
128 | }
129 |
130 | /**
131 | * Sets the actual height of this notification. This is different than the laid out
132 | * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
133 | *
134 | * @param actualHeight The height of this notification.
135 | * @param notifyListeners Whether the listener should be informed about the change.
136 | */
137 | public void setActualHeight(int actualHeight, boolean notifyListeners) {
138 | mActualHeightInitialized = true;
139 | mActualHeight = actualHeight;
140 | if (notifyListeners) {
141 | notifyHeightChanged();
142 | }
143 | }
144 |
145 | /**
146 | * See {@link #setActualHeight}.
147 | *
148 | * @return The current actual height of this notification.
149 | */
150 | public int getActualHeight() {
151 | return mActualHeight;
152 | }
153 |
154 | public void setActualHeight(int actualHeight) {
155 | setActualHeight(actualHeight, true);
156 | }
157 |
158 | /**
159 | * @return The maximum height of this notification.
160 | */
161 | public int getMaxHeight() {
162 | return getHeight();
163 | }
164 |
165 | /**
166 | * @return The minimum height of this notification.
167 | */
168 | public int getMinHeight() {
169 | return getHeight();
170 | }
171 |
172 | /**
173 | * Sets the notification as dimmed. The default implementation does nothing.
174 | *
175 | * @param dimmed Whether the notification should be dimmed.
176 | * @param fade Whether an animation should be played to change the state.
177 | */
178 | public void setDimmed(boolean dimmed, boolean fade) {
179 | }
180 |
181 | /**
182 | * Sets the notification as dark. The default implementation does nothing.
183 | *
184 | * @param dark Whether the notification should be dark.
185 | * @param fade Whether an animation should be played to change the state.
186 | */
187 | public void setDark(boolean dark, boolean fade) {
188 | }
189 |
190 | /**
191 | * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
192 | * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
193 | * of a stack scroller update such that the updated intrinsic height (which is dependent on
194 | * whether private or public layout is showing) gets taken into account into all layout
195 | * calculations.
196 | */
197 | public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
198 | }
199 |
200 | /**
201 | * Sets whether the notification should hide its private contents if it is sensitive.
202 | */
203 | public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
204 | long duration) {
205 | }
206 |
207 | /**
208 | * @return The desired notification height.
209 | */
210 | public int getIntrinsicHeight() {
211 | return getHeight();
212 | }
213 |
214 | public int getClipTopAmount() {
215 | return mClipTopAmount;
216 | }
217 |
218 | /**
219 | * Sets the amount this view should be clipped from the top. This is used when an expanded
220 | * notification is scrolling in the top or bottom stack.
221 | *
222 | * @param clipTopAmount The amount of pixels this view should be clipped from top.
223 | */
224 | public void setClipTopAmount(int clipTopAmount) {
225 | mClipTopAmount = clipTopAmount;
226 | }
227 |
228 | public void setOnHeightChangedListener(OnHeightChangedListener listener) {
229 | mOnHeightChangedListener = listener;
230 | }
231 |
232 | /**
233 | * @return Whether we can expand this views content.
234 | */
235 | public boolean isContentExpandable() {
236 | return false;
237 | }
238 |
239 | public void notifyHeightChanged() {
240 | if (mOnHeightChangedListener != null) {
241 | mOnHeightChangedListener.onHeightChanged(this);
242 | }
243 | }
244 |
245 | public boolean isTransparent() {
246 | return false;
247 | }
248 |
249 | /**
250 | * Perform a remove animation on this view.
251 | *
252 | * @param duration The duration of the remove animation.
253 | * @param translationDirection The direction value from [-1 ... 1] indicating in which the
254 | * animation should be performed. A value of -1 means that The
255 | * remove animation should be performed upwards,
256 | * such that the child appears to be going away to the top. 1
257 | * Should mean the opposite.
258 | * @param onFinishedRunnable A runnable which should be run when the animation is finished.
259 | */
260 | public abstract void performRemoveAnimation(long duration, float translationDirection,
261 | Runnable onFinishedRunnable);
262 |
263 | public abstract void performAddAnimation(long delay, long duration);
264 |
265 | public void onHeightReset() {
266 | if (mOnHeightChangedListener != null) {
267 | mOnHeightChangedListener.onReset(this);
268 | }
269 | }
270 |
271 | /**
272 | * This method returns the drawing rect for the view which is different from the regular
273 | * drawing rect, since we layout all children in the {@link StackScrollLayout} at
274 | * position 0 and usually the translation is neglected. Since we are manually clipping this
275 | * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
276 | * ensure that accessibility and focusing work correctly.
277 | *
278 | * @param outRect The (scrolled) drawing bounds of the view.
279 | */
280 | @Override
281 | public void getDrawingRect(Rect outRect) {
282 | super.getDrawingRect(outRect);
283 | outRect.left += getTranslationX();
284 | outRect.right += getTranslationX();
285 | outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
286 | outRect.top += getTranslationY() + getClipTopAmount();
287 | }
288 |
289 | @Override
290 | public String toString() {
291 | return "ExpandableView{" +
292 | "this:" + getId() +
293 | ", mMaxNotificationHeight=" + mMaxNotificationHeight +
294 | ", mOnHeightChangedListener=" + mOnHeightChangedListener +
295 | ", mActualHeight=" + mActualHeight +
296 | ", mClipTopAmount=" + mClipTopAmount +
297 | ", mActualHeightInitialized=" + mActualHeightInitialized +
298 | ", mMatchParentViews=" + mMatchParentViews +
299 | '}';
300 | }
301 |
302 | @Override
303 | public float getTranslationZ() {
304 | if (Define.SDK_INT >= 21) {
305 | return super.getTranslationZ();
306 | } else {
307 | return 0;
308 | }
309 | }
310 |
311 | @Override
312 | public void setTranslationZ(float translationZ) {
313 | if (Define.SDK_INT >= 21) {
314 | super.setTranslationZ(translationZ);
315 | }
316 | }
317 |
318 | @Override
319 | public void setTranslationY(float translationY) {
320 | super.setTranslationY(translationY);
321 | }
322 |
323 | @Override
324 | public void setClipBounds(Rect clipBounds) {
325 | if (Define.SDK_INT >= 18) {
326 | super.setClipBounds(clipBounds);
327 | }
328 | }
329 |
330 | @Override
331 | public void setAlpha(float alpha) {
332 | if (Define.SDK_INT >= 21) {
333 | super.setAlpha(alpha);
334 | }
335 | }
336 |
337 | public void setShadowAlpha(float distance, int height) {
338 | if (Define.SDK_INT >= 21) {
339 | return;
340 | }
341 | int maxAlpha = 200;
342 | int newAlpha = (int) ((height - distance + shadowRadius) / shadowRadius * maxAlpha);
343 | newAlpha = Math.max(newAlpha, 0);
344 | newAlpha = Math.min(newAlpha, maxAlpha);
345 | if (currentAlpha != newAlpha) {
346 | currentAlpha = newAlpha;
347 | invalidate();
348 | }
349 | }
350 |
351 | @Override
352 | protected void dispatchDraw(Canvas canvas) {
353 | if (Define.SDK_INT < 21) {
354 | super.dispatchDraw(canvas);
355 |
356 | canvas.save();
357 | Paint paint = new Paint();
358 | paint.setColor(Color.TRANSPARENT);
359 | paint.setAlpha(currentAlpha);
360 | paint.setStrokeWidth(0);
361 |
362 | Shader shader = new LinearGradient(0, getHeight() - shadowRadius, 0, getHeight(),
363 | new int[] {shadowStartColor, shadowEndColor}, null, Shader.TileMode.MIRROR);
364 | paint.setShader(shader);
365 | canvas.drawRect(0, getHeight() - shadowRadius, getWidth(), getHeight(), paint);
366 |
367 | canvas.restore();
368 | } else {
369 | super.dispatchDraw(canvas);
370 | }
371 | }
372 |
373 | protected void log(String tag, String log) {
374 | ViewGroup parent = (ViewGroup) getParent();
375 | int index = parent.indexOfChild(this);
376 | Log.i(tag + " : " + index, log);
377 | }
378 |
379 | @Override
380 | public boolean onTouchEvent(MotionEvent event) {
381 | int action = event.getAction();
382 | switch (action) {
383 | case MotionEvent.ACTION_UP:
384 | if (onClickListener == null && performClick != null) {
385 | performClick.performClick(this);
386 | }
387 | break;
388 | }
389 | return super.onTouchEvent(event);
390 | }
391 |
392 | @Override
393 | public void setOnClickListener(OnClickListener l) {
394 | onClickListener = l;
395 | }
396 |
397 | @Override
398 | public void onClick(View v) {
399 | if (onClickListener != null) {
400 | onClickListener.onClick(this);
401 | }
402 | }
403 |
404 | /**
405 | * A listener notifying when {@link #getActualHeight} changes.
406 | */
407 | public interface OnHeightChangedListener {
408 |
409 | /**
410 | * @param view the view for which the height changed, or {@code null} if just the top
411 | * padding or the padding between the elements changed
412 | */
413 | void onHeightChanged(ExpandableView view);
414 |
415 | /**
416 | * Called when the view is reset and therefore the height will change abruptly
417 | *
418 | * @param view The view which was reset.
419 | */
420 | void onReset(ExpandableView view);
421 | }
422 |
423 | private class PerformClick {
424 |
425 | public void performClick(View v) {
426 | ViewGroup parent = (ViewGroup) getParent();
427 | if (parent != null && parent instanceof StackScrollLayout) {
428 | StackScrollLayout StackScrollLayout = (StackScrollLayout) parent;
429 | StackScrollLayout.performClick(v);
430 | }
431 | }
432 | }
433 | }
434 |
--------------------------------------------------------------------------------