├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── misc.xml ├── modules.xml └── runConfigurations.xml ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── example │ │ └── cyandev │ │ └── androidplayground │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── example │ │ │ └── cyandev │ │ │ └── androidplayground │ │ │ ├── ScrollingActivity.java │ │ │ └── widget │ │ │ ├── HeaderFloatBehavior.java │ │ │ └── HeaderScrollingBehavior.java │ └── res │ │ ├── drawable-nodpi │ │ └── bg_header.jpg │ │ ├── layout │ │ ├── activity_scrolling.xml │ │ └── item_simple.xml │ │ ├── menu │ │ └── menu_scrolling.xml │ │ ├── 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 │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── example │ └── cyandev │ └── androidplayground │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | AndroidPlayground -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FancyBehaviorDemo 2 | 3 | 效果及实现分析请移步该[简书文章](http://www.jianshu.com/p/7f50faa65622) 4 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.neenbedankt.android-apt' 3 | 4 | android { 5 | compileSdkVersion 24 6 | buildToolsVersion "24.0.2" 7 | 8 | dataBinding { 9 | enabled = true 10 | } 11 | 12 | defaultConfig { 13 | applicationId "com.example.cyandev.androidplayground" 14 | minSdkVersion 19 15 | targetSdkVersion 24 16 | versionCode 1 17 | versionName "1.0" 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | } 26 | 27 | dependencies { 28 | compile fileTree(dir: 'libs', include: ['*.jar']) 29 | testCompile 'junit:junit:4.12' 30 | compile 'com.android.support:appcompat-v7:24.2.0' 31 | compile 'com.android.support:design:24.2.0' 32 | compile 'com.squareup.okhttp3:okhttp:3.4.1' 33 | compile 'io.reactivex:rxandroid:1.2.1' 34 | apt "com.google.dagger:dagger-compiler:2.2" 35 | provided 'org.glassfish:javax.annotation:10.0-b28' 36 | compile "com.google.dagger:dagger:2.2" 37 | compile "com.facebook.react:react-native:+" 38 | } 39 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/cyandev/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/cyandev/androidplayground/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.cyandev.androidplayground; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/cyandev/androidplayground/ScrollingActivity.java: -------------------------------------------------------------------------------- 1 | package com.example.cyandev.androidplayground; 2 | 3 | import android.os.Bundle; 4 | import android.support.design.widget.FloatingActionButton; 5 | import android.support.design.widget.Snackbar; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.support.v7.widget.RecyclerView; 8 | import android.support.v7.widget.Toolbar; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.TextView; 12 | 13 | public class ScrollingActivity extends AppCompatActivity { 14 | 15 | private RecyclerView recyclerView; 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | setContentView(R.layout.activity_scrolling); 21 | 22 | recyclerView = (RecyclerView) findViewById(R.id.rv); 23 | recyclerView.setAdapter(new RecyclerView.Adapter() { 24 | @Override 25 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 26 | return new ViewHolder(getLayoutInflater().inflate(R.layout.item_simple, parent, false)); 27 | } 28 | 29 | @Override 30 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 31 | ViewHolder vh = (ViewHolder) holder; 32 | vh.text.setText("Fake Item " + (position + 1)); 33 | vh.text2.setText("Lorem ipsum dolor sit amet, consectetur adipiscing elit."); 34 | } 35 | 36 | @Override 37 | public int getItemCount() { 38 | return 20; 39 | } 40 | 41 | class ViewHolder extends RecyclerView.ViewHolder { 42 | 43 | TextView text; 44 | TextView text2; 45 | 46 | public ViewHolder(View itemView) { 47 | super(itemView); 48 | 49 | text = (TextView) itemView.findViewById(R.id.text); 50 | text2 = (TextView) itemView.findViewById(R.id.text2); 51 | } 52 | 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/cyandev/androidplayground/widget/HeaderFloatBehavior.java: -------------------------------------------------------------------------------- 1 | package com.example.cyandev.androidplayground.widget; 2 | 3 | import android.animation.ArgbEvaluator; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | 10 | import com.example.cyandev.androidplayground.R; 11 | 12 | import java.lang.ref.WeakReference; 13 | 14 | /** 15 | * Created by cyandev on 2016/12/14. 16 | */ 17 | public class HeaderFloatBehavior extends CoordinatorLayout.Behavior { 18 | 19 | private WeakReference dependentView; 20 | private ArgbEvaluator argbEvaluator; 21 | 22 | public HeaderFloatBehavior(Context context, AttributeSet attrs) { 23 | super(context, attrs); 24 | 25 | argbEvaluator = new ArgbEvaluator(); 26 | } 27 | 28 | @Override 29 | public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) { 30 | if (dependency != null && dependency.getId() == R.id.scrolling_header) { 31 | dependentView = new WeakReference<>(dependency); 32 | return true; 33 | } 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) { 39 | Resources resources = getDependentView().getResources(); 40 | final float progress = 1.f - 41 | Math.abs(dependency.getTranslationY() / (dependency.getHeight() - resources.getDimension(R.dimen.collapsed_header_height))); 42 | 43 | // Translation 44 | final float collapsedOffset = resources.getDimension(R.dimen.collapsed_float_offset_y); 45 | final float initOffset = resources.getDimension(R.dimen.init_float_offset_y); 46 | final float translateY = collapsedOffset + (initOffset - collapsedOffset) * progress; 47 | child.setTranslationY(translateY); 48 | 49 | // Background 50 | child.setBackgroundColor((int) argbEvaluator.evaluate( 51 | progress, 52 | resources.getColor(R.color.colorCollapsedFloatBackground), 53 | resources.getColor(R.color.colorInitFloatBackground))); 54 | 55 | // Margins 56 | final float collapsedMargin = resources.getDimension(R.dimen.collapsed_float_margin); 57 | final float initMargin = resources.getDimension(R.dimen.init_float_margin); 58 | final int margin = (int) (collapsedMargin + (initMargin - collapsedMargin) * progress); 59 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 60 | lp.setMargins(margin, 0, margin, 0); 61 | child.setLayoutParams(lp); 62 | 63 | return true; 64 | } 65 | 66 | private View getDependentView() { 67 | return dependentView.get(); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/cyandev/androidplayground/widget/HeaderScrollingBehavior.java: -------------------------------------------------------------------------------- 1 | package com.example.cyandev.androidplayground.widget; 2 | 3 | import android.content.Context; 4 | import android.content.res.Resources; 5 | import android.os.Handler; 6 | import android.support.design.widget.CoordinatorLayout; 7 | import android.support.v4.view.ViewCompat; 8 | import android.support.v7.widget.RecyclerView; 9 | import android.util.AttributeSet; 10 | import android.view.View; 11 | import android.widget.Scroller; 12 | 13 | import com.example.cyandev.androidplayground.R; 14 | 15 | import java.lang.ref.WeakReference; 16 | 17 | /** 18 | * Created by cyandev on 2016/11/3. 19 | */ 20 | public class HeaderScrollingBehavior extends CoordinatorLayout.Behavior { 21 | 22 | private boolean isExpanded = false; 23 | private boolean isScrolling = false; 24 | 25 | private WeakReference dependentView; 26 | private Scroller scroller; 27 | private Handler handler; 28 | 29 | public HeaderScrollingBehavior(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | scroller = new Scroller(context); 32 | handler = new Handler(); 33 | } 34 | 35 | public boolean isExpanded() { 36 | return isExpanded; 37 | } 38 | 39 | @Override 40 | public boolean layoutDependsOn(CoordinatorLayout parent, RecyclerView child, View dependency) { 41 | if (dependency != null && dependency.getId() == R.id.scrolling_header) { 42 | dependentView = new WeakReference<>(dependency); 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | @Override 49 | public boolean onLayoutChild(CoordinatorLayout parent, RecyclerView child, int layoutDirection) { 50 | CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams) child.getLayoutParams(); 51 | if (lp.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) { 52 | child.layout(0, 0, parent.getWidth(), (int) (parent.getHeight() - getDependentViewCollapsedHeight())); 53 | return true; 54 | } 55 | return super.onLayoutChild(parent, child, layoutDirection); 56 | } 57 | 58 | @Override 59 | public boolean onDependentViewChanged(CoordinatorLayout parent, RecyclerView child, View dependency) { 60 | Resources resources = getDependentView().getResources(); 61 | final float progress = 1.f - 62 | Math.abs(dependency.getTranslationY() / (dependency.getHeight() - resources.getDimension(R.dimen.collapsed_header_height))); 63 | 64 | child.setTranslationY(dependency.getHeight() + dependency.getTranslationY()); 65 | 66 | float scale = 1 + 0.4f * (1.f - progress); 67 | dependency.setScaleX(scale); 68 | dependency.setScaleY(scale); 69 | 70 | dependency.setAlpha(progress); 71 | 72 | return true; 73 | } 74 | 75 | @Override 76 | public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View directTargetChild, View target, int nestedScrollAxes) { 77 | return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 78 | } 79 | 80 | @Override 81 | public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, RecyclerView child, View directTargetChild, View target, int nestedScrollAxes) { 82 | scroller.abortAnimation(); 83 | isScrolling = false; 84 | 85 | super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); 86 | } 87 | 88 | @Override 89 | public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, int dx, int dy, int[] consumed) { 90 | if (dy < 0) { 91 | return; 92 | } 93 | 94 | View dependentView = getDependentView(); 95 | float newTranslateY = dependentView.getTranslationY() - dy; 96 | float minHeaderTranslate = -(dependentView.getHeight() - getDependentViewCollapsedHeight()); 97 | 98 | if (newTranslateY > minHeaderTranslate) { 99 | dependentView.setTranslationY(newTranslateY); 100 | consumed[1] = dy; 101 | } 102 | } 103 | 104 | @Override 105 | public void onNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) { 106 | if (dyUnconsumed > 0) { 107 | return; 108 | } 109 | 110 | View dependentView = getDependentView(); 111 | float newTranslateY = dependentView.getTranslationY() - dyUnconsumed; 112 | final float maxHeaderTranslate = 0; 113 | 114 | if (newTranslateY < maxHeaderTranslate) { 115 | dependentView.setTranslationY(newTranslateY); 116 | } 117 | } 118 | 119 | @Override 120 | public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, RecyclerView child, View target, float velocityX, float velocityY) { 121 | return onUserStopDragging(velocityY); 122 | } 123 | 124 | @Override 125 | public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, RecyclerView child, View target) { 126 | if (!isScrolling) { 127 | onUserStopDragging(800); 128 | } 129 | } 130 | 131 | private boolean onUserStopDragging(float velocity) { 132 | View dependentView = getDependentView(); 133 | float translateY = dependentView.getTranslationY(); 134 | float minHeaderTranslate = -(dependentView.getHeight() - getDependentViewCollapsedHeight()); 135 | 136 | if (translateY == 0 || translateY == minHeaderTranslate) { 137 | return false; 138 | } 139 | 140 | boolean targetState; // Flag indicates whether to expand the content. 141 | if (Math.abs(velocity) <= 800) { 142 | if (Math.abs(translateY) < Math.abs(translateY - minHeaderTranslate)) { 143 | targetState = false; 144 | } else { 145 | targetState = true; 146 | } 147 | velocity = 800; // Limit velocity's minimum value. 148 | } else { 149 | if (velocity > 0) { 150 | targetState = true; 151 | } else { 152 | targetState = false; 153 | } 154 | } 155 | 156 | float targetTranslateY = targetState ? minHeaderTranslate : 0; 157 | 158 | scroller.startScroll(0, (int) translateY, 0, (int) (targetTranslateY - translateY), (int) (1000000 / Math.abs(velocity))); 159 | handler.post(flingRunnable); 160 | isScrolling = true; 161 | return true; 162 | } 163 | 164 | private float getDependentViewCollapsedHeight() { 165 | return getDependentView().getResources().getDimension(R.dimen.collapsed_header_height); 166 | } 167 | 168 | private View getDependentView() { 169 | return dependentView.get(); 170 | } 171 | 172 | private Runnable flingRunnable = new Runnable() { 173 | @Override 174 | public void run() { 175 | if (scroller.computeScrollOffset()) { 176 | getDependentView().setTranslationY(scroller.getCurrY()); 177 | handler.post(this); 178 | } else { 179 | isExpanded = getDependentView().getTranslationY() != 0; 180 | isScrolling = false; 181 | } 182 | } 183 | }; 184 | 185 | } 186 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/bg_header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/drawable-nodpi/bg_header.jpg -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_scrolling.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 24 | 31 | 32 | 33 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_simple.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 20 | 21 | 28 | 29 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_scrolling.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unixzii/android-FancyBehaviorDemo/c0f32534c03408ead593bb6aaf8bbbcfe79fa258/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #b0ffffff 7 | #e4e4e4 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 16dp 6 | 180dp 7 | 16dp 8 | 50dp 9 | 130dp 10 | 5dp 11 | 20dp 12 | 5dp 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidPlayground 3 | ScrollingActivity 4 | 5 | com.example.cyandev.androidplayground.widget.HeaderScrollingBehavior 6 | com.example.cyandev.androidplayground.widget.HeaderFloatBehavior 7 | 8 | Settings 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 15 | 16 |