├── .gitignore ├── README.md ├── ScreenCapture └── screen.gif ├── apk └── StickyScrollViewSample.apk ├── libarary ├── .classpath ├── .project ├── AndroidManifest.xml ├── proguard-project.txt ├── project.properties └── src │ └── com │ └── likebamboo │ └── stickyscrollview │ ├── StickyScrollView.java │ ├── StickyScrollViewCallbacks.java │ ├── StickyScrollViewGlobalLayoutListener.java │ └── animation │ ├── AnimatorProxy.java │ └── ViewHelper.java └── sample ├── .classpath ├── .project ├── AndroidManifest.xml ├── proguard-project.txt ├── project.properties ├── res ├── drawable-hdpi │ └── ic_launcher.png ├── drawable-mdpi │ └── ic_launcher.png ├── drawable-xhdpi │ └── ic_launcher.png ├── layout │ └── activity_main.xml └── values │ ├── colors.xml │ ├── dimens.xml │ ├── id.xml │ ├── strings.xml │ └── styles.xml └── src └── com └── likebamboo └── stickyscrollview └── sample └── MainActivity.java /.gitignore: -------------------------------------------------------------------------------- 1 | .settings/ 2 | # Built application files 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | StickyScrollView 2 | ======================== 3 | 相信大家都知道有很多让ListView分组Header浮动悬停的开源控件,比如:**[StickyListHeaders](https://github.com/emilsjolander/StickyListHeaders)**、**[pinned-section-listview](https://github.com/beworker/pinned-section-listview)**,而本项目就是要在ScrollView中实现类似的功能。 4 | StickyScrollView 是一个让ScrollView同样支持浮动悬停的控件(该控件支持android2.2(api level 8)及以上的版本)。 5 | 效果图如下(gif屏幕录制,有点卡顿,实际使用不会的): 6 | ![sticyScrollView](https://raw.github.com/likebamboo/StickyScrollView/master/ScreenCapture/screen.gif) 7 | 8 | 构建 9 | ---- 10 | 该项目基于Eclipse构建。如果你使用的IDE是Eclipse,请: 11 | * 在Eclipse中菜单栏中选择 **File, Import, Existing projects into workspace...** 12 | * 选中你clone下来的repository的根目录,然后点击"import"导入项目(包括StickyScorllView 库项目和 StickyScrollViewSample示例项目)。 13 | * 导入之后如果有错误,请使用菜单栏中的 **Project - Clean** 清理下项目。 14 | * 在apk目录下有示例程序的[安装包](https://github.com/likebamboo/StickyScrollView/tree/master/apk),你也可以先下载安装到手机看下效果。 15 | 16 | 使用方法 17 | --------- 18 | 19 | #### 布局文件: 20 | 21 | ``` xml 22 | 25 | 26 | 30 | 31 | 35 | 36 | 40 | 41 | 44 | 45 | 48 | 49 | 50 | 51 | 52 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 75 | 76 | 77 | 85 | 86 | ``` 87 | 88 | 89 | #### Activity 90 | 91 | ``` java 92 | // 初始化控件 93 | mStickyScrollView = (StickyScrollView)findViewById(R.id.scroll_view); 94 | mStickyView = (TextView)findViewById(R.id.sticky); 95 | mPlaceholderView = findViewById(R.id.placeholder); 96 | 97 | 98 | mCallbacks = new StickyScrollViewCallbacks(mStickyView, mPlaceholderView, 99 | mPlaceholderView2, mStickyScrollView); 100 | 101 | mStickyScrollView.addCallbacks(mCallbacks); 102 | 103 | mStickyScrollView.getViewTreeObserver().addOnGlobalLayoutListener( 104 | new StickyScrollViewGlobalLayoutListener(mCallbacks)); 105 | 106 | ``` 107 | 108 | 其他 109 | ------ 110 | 在 [这里](https://github.com/emilsjolander/StickyScrollViewItems) 你可以找到与本项目具有相似功能的开源项目[StickyScrollViewItems](https://github.com/emilsjolander/StickyScrollViewItems)。 111 | 两个项目实现的功能是一样的,相比而言: 112 | * 本项目在使用的时候相对比较复杂,要写的代码会比较多,[StickyScrollViewItems](https://github.com/emilsjolander/StickyScrollViewItems) 项目使用起来比较简单。 113 | * 本项目支持子控件可悬停状态的动态切换,你可以在代码中通过设置``mCallbacks.setEnableSticky(!enableSticky);``来动态控制某个View是否悬停并且不影响其他子控件的悬停状态。 114 | * 本项目中可以方便的控制悬停控件的悬停范围,可控制悬停控件从哪个View到哪个View之前悬停,具体可参照 StickyScrollViewCallbacks 的第二个构造函数: 115 | ``` java 116 | // 不设置endView将一直悬停,直到ScrollView滚动到最底部 117 | public StickyScrollViewCallbacks(View stickyView, View placeholderView, View endView,StickyScrollView observableScrollView) 118 | ``` 119 | * 其他 120 | 121 | 总之,两个项目各有优缺点,大家可以根据自己的喜好选择啦。 122 | -------------------------------------------------------------------------------- /ScreenCapture/screen.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/likebamboo/StickyScrollView/9819ca60e2bc2f6e386f46e7eab1165d98eec31c/ScreenCapture/screen.gif -------------------------------------------------------------------------------- /apk/StickyScrollViewSample.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/likebamboo/StickyScrollView/9819ca60e2bc2f6e386f46e7eab1165d98eec31c/apk/StickyScrollViewSample.apk -------------------------------------------------------------------------------- /libarary/.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /libarary/.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | StickyScrollView 4 | 5 | 6 | 7 | 8 | 9 | com.android.ide.eclipse.adt.ResourceManagerBuilder 10 | 11 | 12 | 13 | 14 | com.android.ide.eclipse.adt.PreCompilerBuilder 15 | 16 | 17 | 18 | 19 | org.eclipse.jdt.core.javabuilder 20 | 21 | 22 | 23 | 24 | com.android.ide.eclipse.adt.ApkBuilder 25 | 26 | 27 | 28 | 29 | 30 | com.android.ide.eclipse.adt.AndroidNature 31 | org.eclipse.jdt.core.javanature 32 | 33 | 34 | -------------------------------------------------------------------------------- /libarary/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /libarary/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | -------------------------------------------------------------------------------- /libarary/project.properties: -------------------------------------------------------------------------------- 1 | # This file is automatically generated by Android Tools. 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED! 3 | # 4 | # This file must be checked in Version Control Systems. 5 | # 6 | # To customize properties used by the Ant build system edit 7 | # "ant.properties", and override values to adapt the script to your 8 | # project structure. 9 | # 10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): 11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt 12 | 13 | # Project target. 14 | target=android-17 15 | android.library=true 16 | -------------------------------------------------------------------------------- /libarary/src/com/likebamboo/stickyscrollview/StickyScrollView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * StickyScrollView.java 3 | * StickyScrollView 4 | * 5 | * Created by likebamboo on 2014-4-21 6 | * Copyright (c) 1998-2014 https://github.com/likebamboo All rights reserved. 7 | */ 8 | 9 | package com.likebamboo.stickyscrollview; 10 | 11 | import android.content.Context; 12 | import android.util.AttributeSet; 13 | import android.view.MotionEvent; 14 | import android.widget.ScrollView; 15 | 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | 19 | /** 20 | *

21 | * 自定义的ScrollView 22 | *

23 | * 通过监听ScrollView的滚动事件执行特定的回调方法【动态改变可浮动View的位置】 24 | * 25 | * @author likebamboo 26 | */ 27 | public class StickyScrollView extends ScrollView { 28 | private List mCallbacks; 29 | 30 | // 滑动距离及坐标 31 | private float xDistance, yDistance, xLast, yLast; 32 | 33 | /** 34 | *

35 | * 复写onInterceptTouchEvent是用来解决ScrollView与ViewPager之前滚动事件冲突的, 36 | *

37 | * 实际项目可根据需要选择是否需要这段代码 38 | */ 39 | @Override 40 | public boolean onInterceptTouchEvent(MotionEvent ev) { 41 | switch (ev.getAction()) { 42 | case MotionEvent.ACTION_DOWN: 43 | xDistance = yDistance = 0f; 44 | xLast = ev.getX(); 45 | yLast = ev.getY(); 46 | break; 47 | case MotionEvent.ACTION_MOVE: 48 | final float curX = ev.getX(); 49 | final float curY = ev.getY(); 50 | 51 | xDistance += Math.abs(curX - xLast); 52 | yDistance += Math.abs(curY - yLast); 53 | xLast = curX; 54 | yLast = curY; 55 | 56 | if (xDistance > yDistance) { 57 | return false; 58 | } 59 | } 60 | return super.onInterceptTouchEvent(ev); 61 | } 62 | 63 | public StickyScrollView(Context context, AttributeSet attrs) { 64 | super(context, attrs); 65 | } 66 | 67 | @Override 68 | protected void onScrollChanged(int l, int t, int oldl, int oldt) { 69 | super.onScrollChanged(l, t, oldl, oldt); 70 | // 滚动时回调 71 | if (mCallbacks != null && mCallbacks.size() != 0) { 72 | for (Callbacks item : mCallbacks) { 73 | item.onScrollChanged(); 74 | } 75 | } 76 | } 77 | 78 | @Override 79 | public int computeVerticalScrollRange() { 80 | return super.computeVerticalScrollRange(); 81 | } 82 | 83 | public void addCallbacks(Callbacks listener) { 84 | if (mCallbacks == null) { 85 | mCallbacks = new ArrayList(); 86 | } 87 | mCallbacks.add(listener); 88 | } 89 | 90 | public static interface Callbacks { 91 | public void onScrollChanged(); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /libarary/src/com/likebamboo/stickyscrollview/StickyScrollViewCallbacks.java: -------------------------------------------------------------------------------- 1 | /** 2 | * StickyScrollViewCallbacks.java 3 | * StickyScrollView 4 | * 5 | * Created by likebamboo on 2014-4-21 6 | * Copyright (c) 1998-2014 https://github.com/likebamboo All rights reserved. 7 | */ 8 | 9 | package com.likebamboo.stickyscrollview; 10 | 11 | import android.view.MotionEvent; 12 | import android.view.View; 13 | 14 | import com.likebamboo.stickyscrollview.animation.AnimatorProxy; 15 | import com.likebamboo.stickyscrollview.animation.ViewHelper; 16 | 17 | /** 18 | *

19 | * ScrollView滚动时的回调接口, 20 | *

21 | * 通过计算ScrollView滚动时悬停控件应该绘制的位置来绘制相应的View 22 | * 23 | * @author likebamboo 24 | */ 25 | public class StickyScrollViewCallbacks implements StickyScrollView.Callbacks { 26 | private static final float CLICK_DISTANCE = 3; 27 | 28 | /** 29 | * 悬停的View 30 | */ 31 | private View mStickyView = null; 32 | 33 | /** 34 | * 当mStickyView不处于悬浮状态时的停靠的View 35 | */ 36 | private View mPlaceholderView = null; 37 | 38 | /** 39 | * ScrollView控件 40 | */ 41 | private StickyScrollView mObservableScrollView = null; 42 | 43 | /** 44 | * 悬停控件的临界控件【非必须】,如果设置了该控件,当ScrollView滚动到该控件时,悬停控件会被这个控件顶出界面。 45 | */ 46 | private View mEndSticyView; 47 | 48 | /** 49 | * 50 | */ 51 | private boolean mEnableSticky = true; 52 | 53 | /** 54 | * touchDown时ScrollView滚动的坐标 55 | */ 56 | private float mTouchDownY = Float.MIN_VALUE; 57 | 58 | /** 59 | * 从touchDown到touchUp,ScrollView滚动的距离 60 | */ 61 | private float mScrollDistanceY = 0F; 62 | 63 | public StickyScrollViewCallbacks(View stickyView, View placeholderView, 64 | StickyScrollView observableScrollView) { 65 | this(stickyView, placeholderView, null, observableScrollView); 66 | } 67 | 68 | public StickyScrollViewCallbacks(View stickyView, View placeholderView, View endView, 69 | StickyScrollView observableScrollView) { 70 | this.mStickyView = stickyView; 71 | this.mPlaceholderView = placeholderView; 72 | this.mObservableScrollView = observableScrollView; 73 | this.mEndSticyView = endView; 74 | // 监听onTouch事件有两方面的考虑,scrollView的滚动与点击事件 75 | mStickyView.setOnTouchListener(new View.OnTouchListener() { 76 | @Override 77 | public boolean onTouch(View v, MotionEvent event) { 78 | switch (event.getAction()) { 79 | case MotionEvent.ACTION_DOWN:// 记录下当前的滚动位置 80 | mTouchDownY = mObservableScrollView.getScrollY(); 81 | break; 82 | case MotionEvent.ACTION_MOVE:// ScrollView滚动的过程中,记录滚动的差值 83 | float disY = Math.abs(mObservableScrollView.getScrollY() - mTouchDownY); 84 | // 如果当前差值大于之前记录差值,将差值替换 85 | if (disY > mScrollDistanceY) { 86 | mScrollDistanceY = disY; 87 | } 88 | break; 89 | } 90 | 91 | float translateY = ViewHelper.getTranslationY(mStickyView); 92 | if (AnimatorProxy.NEEDS_PROXY) { 93 | translateY = getTop(mStickyView); 94 | } 95 | 96 | // touch的坐标都是相对于本控件的,所以需要做一次转换 97 | // 构建一个新的MotionEvent事件 98 | MotionEvent newEvent = MotionEvent.obtain(event); 99 | newEvent.setLocation(newEvent.getX(), newEvent.getY() + translateY); 100 | mObservableScrollView.dispatchTouchEvent(newEvent); 101 | if (newEvent != null) { 102 | newEvent.recycle(); 103 | } 104 | 105 | // 如果是滚动,那么不让其触发onClick 事件 106 | if (mScrollDistanceY > CLICK_DISTANCE && event.getAction() == MotionEvent.ACTION_UP) { 107 | mScrollDistanceY = 0F; 108 | return true; 109 | } 110 | return false; 111 | } 112 | }); 113 | } 114 | 115 | @Override 116 | public void onScrollChanged() { 117 | // 首先计算移动的距离 118 | int translationY = calTranslationY(mEnableSticky); 119 | // 移动 120 | translateY(translationY); 121 | } 122 | 123 | /** 124 | * 计算需要移动的位置 125 | * 126 | * @return 127 | */ 128 | private int calTranslationY(boolean enableSticky) { 129 | // 如果不能浮动,那么固定在placeHolderView所在的位置 130 | if (!enableSticky) { 131 | return getTop(mPlaceholderView) - mObservableScrollView.getScrollY(); 132 | } 133 | int translationY = Math.max(0, 134 | getTop(mPlaceholderView) - mObservableScrollView.getScrollY()); 135 | if (mEndSticyView != null) { 136 | /** 137 | * 如果有滚动的临界区域 ,当滚动到指定的临界控件{mEndStricyView}之后,临界的控件将浮动顶上去(仿IOS效果) 138 | */ 139 | if (mObservableScrollView.getScrollY() + mStickyView.getHeight() > getTop(mEndSticyView)) { 140 | // 如果临界控件已经将浮动控件完全顶出ScrollView,那么就让View固定在屏幕的上面 141 | if (mObservableScrollView.getScrollY() > getTop(mEndSticyView)) { 142 | translationY = -mStickyView.getHeight(); 143 | } else { 144 | // 否则临界控件将其顶上去 145 | translationY = getTop(mEndSticyView) - mObservableScrollView.getScrollY() 146 | - mStickyView.getHeight(); 147 | } 148 | } 149 | } 150 | return translationY; 151 | } 152 | 153 | /** 154 | * 获取控件顶部的坐标 155 | * 156 | * @return 157 | */ 158 | private int getTop(View v) { 159 | return v.getTop(); 160 | } 161 | 162 | /** 163 | * 设置是否允许悬停 164 | */ 165 | public void setEnableSticky(boolean enable) { 166 | mEnableSticky = enable; 167 | onScrollChanged(); 168 | } 169 | 170 | /** 171 | * 获取是否允许悬停 172 | */ 173 | public boolean getEnableSticky() { 174 | return mEnableSticky; 175 | } 176 | 177 | /** 178 | * 将View移动到 position位置 179 | * 180 | * @param position 181 | */ 182 | private void translateY(int position) { 183 | /** 184 | *

185 |          * 针对2.3及以下版本,直接用layout方法改变其位置
186 |          * 也许有人说,可以用[NineOldAndroids](https://github.com/JakeWharton/NineOldAndroids)兼容动画啊。
187 |          * 可惜用NineOldAndroids动画执行后,控件的可点击区域还是在原来的地方啊【2.3及以下版本】。
188 |          * github上好多人都报告了这个问题,但是没办法解决。这是android2.3及以下版本的系统问题,不是NineOldAndroids开源控件的问题
189 |          * (之前我就是这么做的,发现不行后,才使用了layout方法,感兴趣的可以尝试下。)
190 |          */
191 |         if (AnimatorProxy.NEEDS_PROXY) {
192 |             int l = mStickyView.getLeft();
193 |             int r = mStickyView.getRight();
194 |             mStickyView.layout(l, position, r, position + mStickyView.getHeight());
195 |         } else {
196 |             ViewHelper.setTranslationY(mStickyView, position);
197 |         }
198 |     }
199 | }
200 | 


--------------------------------------------------------------------------------
/libarary/src/com/likebamboo/stickyscrollview/StickyScrollViewGlobalLayoutListener.java:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * StickyScrollViewGlobalLayoutListener.java
 3 |  * StickyScrollView
 4 |  * 
 5 |  * Created by likebamboo on 2014-4-21
 6 |  * Copyright (c) 1998-2014 https://github.com/likebamboo All rights reserved.
 7 |  */
 8 | 
 9 | package com.likebamboo.stickyscrollview;
10 | 
11 | import android.view.ViewTreeObserver.OnGlobalLayoutListener;
12 | 
13 | /**
14 |  * 该接口主要用来接口当界面首次绘制时,让悬停控件处在正确的位置上。
15 |  * 
16 |  * @author likebamboo
17 |  */
18 | public class StickyScrollViewGlobalLayoutListener implements OnGlobalLayoutListener {
19 | 
20 |     private StickyScrollViewCallbacks mCallbacks;
21 | 
22 |     public StickyScrollViewGlobalLayoutListener(StickyScrollViewCallbacks mCallbacks) {
23 |         this.mCallbacks = mCallbacks;
24 |     }
25 | 
26 |     @Override
27 |     public void onGlobalLayout() {
28 |         mCallbacks.onScrollChanged();
29 |     }
30 | 
31 | }
32 | 


--------------------------------------------------------------------------------
/libarary/src/com/likebamboo/stickyscrollview/animation/AnimatorProxy.java:
--------------------------------------------------------------------------------
  1 | package com.likebamboo.stickyscrollview.animation;
  2 | 
  3 | import android.graphics.Camera;
  4 | import android.graphics.Matrix;
  5 | import android.graphics.RectF;
  6 | import android.os.Build;
  7 | import android.view.View;
  8 | import android.view.animation.Animation;
  9 | import android.view.animation.Transformation;
 10 | 
 11 | import java.lang.ref.WeakReference;
 12 | import java.util.WeakHashMap;
 13 | 
 14 | /**
 15 |  * A proxy class to allow for modifying post-3.0 view properties on all pre-3.0
 16 |  * platforms. DO NOT wrap your views with this class if you
 17 |  * are using {@code ObjectAnimator} as it will handle that itself.
 18 |  */
 19 | public final class AnimatorProxy extends Animation {
 20 |     /** Whether or not the current running platform needs to be proxied. */
 21 |     public static final boolean NEEDS_PROXY = Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB;
 22 | 
 23 |     private static final WeakHashMap PROXIES = new WeakHashMap();
 24 | 
 25 |     /**
 26 |      * Create a proxy to allow for modifying post-3.0 view properties on all
 27 |      * pre-3.0 platforms. DO NOT wrap your views if you are
 28 |      * using {@code ObjectAnimator} as it will handle that itself.
 29 |      *
 30 |      * @param view View to wrap.
 31 |      * @return Proxy to post-3.0 properties.
 32 |      */
 33 |     public static AnimatorProxy wrap(View view) {
 34 |         AnimatorProxy proxy = PROXIES.get(view);
 35 |         // This checks if the proxy already exists and whether it still is the animation of the given view
 36 |         if (proxy == null || proxy != view.getAnimation()) {
 37 |             proxy = new AnimatorProxy(view);
 38 |             PROXIES.put(view, proxy);
 39 |         }
 40 |         return proxy;
 41 |     }
 42 | 
 43 |     private final WeakReference mView;
 44 |     private final Camera mCamera = new Camera();
 45 |     private boolean mHasPivot;
 46 | 
 47 |     private float mAlpha = 1;
 48 |     private float mPivotX;
 49 |     private float mPivotY;
 50 |     private float mRotationX;
 51 |     private float mRotationY;
 52 |     private float mRotationZ;
 53 |     private float mScaleX = 1;
 54 |     private float mScaleY = 1;
 55 |     private float mTranslationX;
 56 |     private float mTranslationY;
 57 | 
 58 |     private final RectF mBefore = new RectF();
 59 |     private final RectF mAfter = new RectF();
 60 |     private final Matrix mTempMatrix = new Matrix();
 61 | 
 62 |     private AnimatorProxy(View view) {
 63 |         setDuration(0); //perform transformation immediately
 64 |         setFillAfter(true); //persist transformation beyond duration
 65 |         view.setAnimation(this);
 66 |         mView = new WeakReference(view);
 67 |     }
 68 | 
 69 |     public float getTranslationY() {
 70 |         return mTranslationY;
 71 |     }
 72 |     public void setTranslationY(float translationY) {
 73 |         if (mTranslationY != translationY) {
 74 |             prepareForUpdate();
 75 |             mTranslationY = translationY;
 76 |             invalidateAfterUpdate();
 77 |         }
 78 |     }
 79 | 
 80 |     private void prepareForUpdate() {
 81 |         View view = mView.get();
 82 |         if (view != null) {
 83 |             computeRect(mBefore, view);
 84 |         }
 85 |     }
 86 |     private void invalidateAfterUpdate() {
 87 |         View view = mView.get();
 88 |         if (view == null || view.getParent() == null) {
 89 |             return;
 90 |         }
 91 | 
 92 |         final RectF after = mAfter;
 93 |         computeRect(after, view);
 94 |         after.union(mBefore);
 95 | 
 96 |         ((View)view.getParent()).invalidate(
 97 |                 (int) Math.floor(after.left),
 98 |                 (int) Math.floor(after.top),
 99 |                 (int) Math.ceil(after.right),
100 |                 (int) Math.ceil(after.bottom));
101 |     }
102 | 
103 |     private void computeRect(final RectF r, View view) {
104 |         // compute current rectangle according to matrix transformation
105 |         final float w = view.getWidth();
106 |         final float h = view.getHeight();
107 | 
108 |         // use a rectangle at 0,0 to make sure we don't run into issues with scaling
109 |         r.set(0, 0, w, h);
110 | 
111 |         final Matrix m = mTempMatrix;
112 |         m.reset();
113 |         transformMatrix(m, view);
114 |         mTempMatrix.mapRect(r);
115 | 
116 |         r.offset(view.getLeft(), view.getTop());
117 | 
118 |         // Straighten coords if rotations flipped them
119 |         if (r.right < r.left) {
120 |             final float f = r.right;
121 |             r.right = r.left;
122 |             r.left = f;
123 |         }
124 |         if (r.bottom < r.top) {
125 |             final float f = r.top;
126 |             r.top = r.bottom;
127 |             r.bottom = f;
128 |         }
129 |     }
130 | 
131 |     private void transformMatrix(Matrix m, View view) {
132 |         final float w = view.getWidth();
133 |         final float h = view.getHeight();
134 |         final boolean hasPivot = mHasPivot;
135 |         final float pX = hasPivot ? mPivotX : w / 2f;
136 |         final float pY = hasPivot ? mPivotY : h / 2f;
137 | 
138 |         final float rX = mRotationX;
139 |         final float rY = mRotationY;
140 |         final float rZ = mRotationZ;
141 |         if ((rX != 0) || (rY != 0) || (rZ != 0)) {
142 |             final Camera camera = mCamera;
143 |             camera.save();
144 |             camera.rotateX(rX);
145 |             camera.rotateY(rY);
146 |             camera.rotateZ(-rZ);
147 |             camera.getMatrix(m);
148 |             camera.restore();
149 |             m.preTranslate(-pX, -pY);
150 |             m.postTranslate(pX, pY);
151 |         }
152 | 
153 |         final float sX = mScaleX;
154 |         final float sY = mScaleY;
155 |         if ((sX != 1.0f) || (sY != 1.0f)) {
156 |             m.postScale(sX, sY);
157 |             final float sPX = -(pX / w) * ((sX * w) - w);
158 |             final float sPY = -(pY / h) * ((sY * h) - h);
159 |             m.postTranslate(sPX, sPY);
160 |         }
161 | 
162 |         m.postTranslate(mTranslationX, mTranslationY);
163 |     }
164 | 
165 |     @Override
166 |     protected void applyTransformation(float interpolatedTime, Transformation t) {
167 |         View view = mView.get();
168 |         if (view != null) {
169 |             t.setAlpha(mAlpha);
170 |             transformMatrix(t.getMatrix(), view);
171 |         }
172 |     }
173 | }
174 | 


--------------------------------------------------------------------------------
/libarary/src/com/likebamboo/stickyscrollview/animation/ViewHelper.java:
--------------------------------------------------------------------------------
 1 | /**
 2 |  * ViewHelper.java
 3 |  * StickyScrollView
 4 |  * 
 5 |  * Created by likebamboo on 2014-4-21
 6 |  * Copyright (c) 1998-2014 https://github.com/likebamboo All rights reserved.
 7 |  */
 8 | 
 9 | package com.likebamboo.stickyscrollview.animation;
10 | 
11 | import android.annotation.SuppressLint;
12 | import android.view.View;
13 | 
14 | import static com.likebamboo.stickyscrollview.animation.AnimatorProxy.NEEDS_PROXY;
15 | import static com.likebamboo.stickyscrollview.animation.AnimatorProxy.wrap;
16 | 
17 | /**
18 |  * 动画代理,来源于NineOldAndroids
19 |  * 
20 |  * @author likebamboo
21 |  */
22 | public final class ViewHelper {
23 |     private ViewHelper() {
24 |     }
25 | 
26 |     public static float getTranslationY(View view) {
27 |         return NEEDS_PROXY ? wrap(view).getTranslationY() : Honeycomb.getTranslationY(view);
28 |     }
29 | 
30 |     public static void setTranslationY(View view, float translationY) {
31 |         if (NEEDS_PROXY) {
32 |             wrap(view).setTranslationY(translationY);
33 |         } else {
34 |             Honeycomb.setTranslationY(view, translationY);
35 |         }
36 |     }
37 | 
38 |     @SuppressLint("NewApi")
39 |     private static final class Honeycomb {
40 |         static float getTranslationY(View view) {
41 |             return view.getTranslationY();
42 |         }
43 | 
44 |         static void setTranslationY(View view, float translationY) {
45 |             view.setTranslationY(translationY);
46 |         }
47 |     }
48 | }
49 | 


--------------------------------------------------------------------------------
/sample/.classpath:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 	
 4 | 	
 5 | 	
 6 | 	
 7 | 	
 8 | 	
 9 | 
10 | 


--------------------------------------------------------------------------------
/sample/.project:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 	StickyScrollViewSample
 4 | 	
 5 | 	
 6 | 	
 7 | 	
 8 | 		
 9 | 			com.android.ide.eclipse.adt.ResourceManagerBuilder
10 | 			
11 | 			
12 | 		
13 | 		
14 | 			com.android.ide.eclipse.adt.PreCompilerBuilder
15 | 			
16 | 			
17 | 		
18 | 		
19 | 			org.eclipse.jdt.core.javabuilder
20 | 			
21 | 			
22 | 		
23 | 		
24 | 			com.android.ide.eclipse.adt.ApkBuilder
25 | 			
26 | 			
27 | 		
28 | 	
29 | 	
30 | 		com.android.ide.eclipse.adt.AndroidNature
31 | 		org.eclipse.jdt.core.javanature
32 | 	
33 | 
34 | 


--------------------------------------------------------------------------------
/sample/AndroidManifest.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 6 | 
 7 |     
10 | 
11 |     
15 |         
18 |             
19 |                 
20 | 
21 |                 
22 |             
23 |         
24 |     
25 | 
26 | 


--------------------------------------------------------------------------------
/sample/proguard-project.txt:
--------------------------------------------------------------------------------
 1 | # To enable ProGuard in your project, edit project.properties
 2 | # to define the proguard.config property as described in that file.
 3 | #
 4 | # Add project specific ProGuard rules here.
 5 | # By default, the flags in this file are appended to flags specified
 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
 7 | # You can edit the include path and order by changing the ProGuard
 8 | # include property in project.properties.
 9 | #
10 | # For more details, see
11 | #   http://developer.android.com/guide/developing/tools/proguard.html
12 | 
13 | # Add any project specific keep options here:
14 | 
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | #   public *;
20 | #}
21 | 


--------------------------------------------------------------------------------
/sample/project.properties:
--------------------------------------------------------------------------------
 1 | # This file is automatically generated by Android Tools.
 2 | # Do not modify this file -- YOUR CHANGES WILL BE ERASED!
 3 | #
 4 | # This file must be checked in Version Control Systems.
 5 | #
 6 | # To customize properties used by the Ant build system edit
 7 | # "ant.properties", and override values to adapt the script to your
 8 | # project structure.
 9 | #
10 | # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
11 | #proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
12 | 
13 | # Project target.
14 | target=android-17
15 | android.library.reference.1=../libarary
16 | 


--------------------------------------------------------------------------------
/sample/res/drawable-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/likebamboo/StickyScrollView/9819ca60e2bc2f6e386f46e7eab1165d98eec31c/sample/res/drawable-hdpi/ic_launcher.png


--------------------------------------------------------------------------------
/sample/res/drawable-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/likebamboo/StickyScrollView/9819ca60e2bc2f6e386f46e7eab1165d98eec31c/sample/res/drawable-mdpi/ic_launcher.png


--------------------------------------------------------------------------------
/sample/res/drawable-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/likebamboo/StickyScrollView/9819ca60e2bc2f6e386f46e7eab1165d98eec31c/sample/res/drawable-xhdpi/ic_launcher.png


--------------------------------------------------------------------------------
/sample/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
 1 | 
 5 | 
 6 |     
10 | 
11 |     
14 | 
15 |         
19 | 
20 |             
24 | 
25 |                 
29 | 
30 |                 
33 | 
34 |                 
37 | 
38 |                 
39 | 
40 |                 
44 | 
45 |                 
46 | 
47 |                 
48 | 
49 |                 
50 | 
51 |                 
52 |             
53 |         
54 | 
55 |         
63 | 
64 |         
72 |     
73 | 
74 | 


--------------------------------------------------------------------------------
/sample/res/values/colors.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     #a6c
4 |     #fff
5 |     #09c
6 |     #f44
7 |     #c00
8 | 
9 | 


--------------------------------------------------------------------------------
/sample/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 |     120dp
4 |     16dp
5 | 
6 | 


--------------------------------------------------------------------------------
/sample/res/values/id.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |     
5 | 
6 | 


--------------------------------------------------------------------------------
/sample/res/values/strings.xml:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | 
4 |     StickyScrollView
5 |     Sticky
6 |     点击我开启悬停此View
7 |     点击我停止悬停此View
8 | 
9 | 


--------------------------------------------------------------------------------
/sample/res/values/styles.xml:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 | 
 4 |     
 8 | 
 9 |     
12 | 
13 |     
20 | 
21 |     
24 | 
25 |     
28 | 
29 | 


--------------------------------------------------------------------------------
/sample/src/com/likebamboo/stickyscrollview/sample/MainActivity.java:
--------------------------------------------------------------------------------
 1 | 
 2 | package com.likebamboo.stickyscrollview.sample;
 3 | 
 4 | import android.app.Activity;
 5 | import android.os.Bundle;
 6 | import android.view.View;
 7 | import android.widget.TextView;
 8 | import android.widget.Toast;
 9 | 
10 | import com.likebamboo.stickyscrollview.StickyScrollView;
11 | import com.likebamboo.stickyscrollview.StickyScrollViewCallbacks;
12 | import com.likebamboo.stickyscrollview.StickyScrollViewGlobalLayoutListener;
13 | 
14 | /**
15 |  * @author likebamboo
16 |  */
17 | public class MainActivity extends Activity {
18 |     private TextView mStickyView;
19 | 
20 |     private View mPlaceholderView;
21 | 
22 |     private StickyScrollView mStickyScrollView;
23 | 
24 |     private StickyScrollViewCallbacks mCallbacks;
25 | 
26 |     private View mStickyView2;
27 | 
28 |     private View mPlaceholderView2;
29 | 
30 |     private StickyScrollViewCallbacks mCallbacks2;
31 | 
32 |     @Override
33 |     public void onCreate(Bundle savedInstanceState) {
34 |         super.onCreate(savedInstanceState);
35 |         setContentView(R.layout.activity_main);
36 |         mStickyScrollView = (StickyScrollView)findViewById(R.id.scroll_view);
37 |         mStickyView = (TextView)findViewById(R.id.sticky);
38 |         mPlaceholderView = findViewById(R.id.placeholder);
39 | 
40 |         mStickyView2 = findViewById(R.id.sticky2);
41 |         mPlaceholderView2 = findViewById(R.id.placeholder2);
42 | 
43 |         mCallbacks = new StickyScrollViewCallbacks(mStickyView, mPlaceholderView,
44 |                 mPlaceholderView2, mStickyScrollView);
45 | 
46 |         mStickyView.setOnClickListener(new View.OnClickListener() {
47 |             @Override
48 |             public void onClick(View v) {
49 |                 boolean enableSticky = mCallbacks.getEnableSticky();
50 |                 mCallbacks.setEnableSticky(!enableSticky);
51 |                 if (enableSticky) {
52 |                     mStickyView.setText(R.string.start_sticky);
53 |                 } else {
54 |                     mStickyView.setText(R.string.stop_sticky);
55 |                 }
56 |             }
57 |         });
58 | 
59 |         mStickyScrollView.addCallbacks(mCallbacks);
60 | 
61 |         mStickyScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
62 |                 new StickyScrollViewGlobalLayoutListener(mCallbacks));
63 | 
64 |         mCallbacks2 = new StickyScrollViewCallbacks(mStickyView2, mPlaceholderView2,
65 |                 mStickyScrollView);
66 | 
67 |         mStickyView2.setOnClickListener(new View.OnClickListener() {
68 |             @Override
69 |             public void onClick(View v) {
70 |                 Toast.makeText(MainActivity.this, R.string.app_name, Toast.LENGTH_SHORT).show();
71 |             }
72 |         });
73 | 
74 |         mStickyScrollView.addCallbacks(mCallbacks2);
75 |         mStickyScrollView.getViewTreeObserver().addOnGlobalLayoutListener(
76 |                 new StickyScrollViewGlobalLayoutListener(mCallbacks2));
77 |     }
78 | }
79 | 


--------------------------------------------------------------------------------