├── .gitignore ├── Android-stick-navigation-layout ├── .gitignore ├── android-nestedscroll │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ ├── project.properties │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── heaven7 │ │ │ └── android │ │ │ └── scroll │ │ │ ├── INestedScrollHelper.java │ │ │ ├── IScrollHelper.java │ │ │ ├── NestedScrollFactory.java │ │ │ ├── NestedScrollHelper.java │ │ │ ├── ScrollHelper.java │ │ │ └── Util.java │ │ └── res │ │ └── values │ │ └── strings.xml ├── androidx_compat.gradle ├── app │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ └── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ └── com │ │ │ └── heaven7 │ │ │ └── android │ │ │ └── sticky_navigation_layout │ │ │ └── demo │ │ │ ├── AbsMainActivity.java │ │ │ ├── EnterActivity.java │ │ │ ├── OnScrollChangeSupplier.java │ │ │ ├── StickyDelegateSupplier.java │ │ │ ├── demos │ │ │ ├── BaseActivity.java │ │ │ ├── MultiplexStickyNavigationDemo.java │ │ │ ├── SimpleStickyNavigationDemo.java │ │ │ └── TestNestedScrollFrameLayout.java │ │ │ ├── extra │ │ │ └── OnScrollListenerImpl.java │ │ │ ├── fragment │ │ │ ├── BaseFragment.java │ │ │ ├── StickyFragment.java │ │ │ └── TabFragment.java │ │ │ └── view │ │ │ ├── RadioButtonPoint.java │ │ │ └── SimpleViewPagerIndicator.java │ │ └── res │ │ ├── layout │ │ ├── ac_test_nested_scroll_framelayout.xml │ │ ├── activity_main.xml │ │ ├── activity_main2.xml │ │ ├── frag_sticky_layout.xml │ │ ├── fragment_tab.xml │ │ ├── item.xml │ │ └── item_hor_tab.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ ├── bg_shadow.png │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ ├── values-w820dp │ │ └── dimens.xml │ │ └── values │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml ├── backup │ ├── StickyNavigationLayout_backup.java │ ├── StickyNavigationLayout_medlinker.java │ ├── bintrayUpload.gradle │ ├── demo.txt │ ├── frag_home_handpick.xml │ └── project.properties ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── sticky-navigation-layout │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── heaven7 │ │ └── android │ │ └── StickyLayout │ │ ├── NestedScrollFrameLayout.java │ │ └── StickyNavigationLayout.java │ └── res │ └── values │ ├── attrs.xml │ └── strings.xml ├── LICENSE ├── README.md └── art └── sticky_navigation_layout.gif /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/workspace.xml 38 | 39 | # Keystore files 40 | *.jks 41 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | .idea 8 | /build 9 | /captures 10 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply from: '../androidx_compat.gradle' 4 | 5 | group = 'com.github.LightSun.Android-sticky-navigation-layout' 6 | version = library_version 7 | 8 | android { 9 | compileSdkVersion 28 10 | buildToolsVersion '28.0.3' 11 | 12 | defaultConfig { 13 | minSdkVersion 19 14 | targetSdkVersion 28 15 | versionCode 103 16 | versionName "1.0.3" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | lintOptions{ 26 | abortOnError false 27 | checkReleaseBuilds false 28 | } 29 | } 30 | //------------ multi lib >>> ------ 31 | task sourcesJar(type: Jar) { 32 | //classifier = 'sources' 33 | getArchiveClassifier().set('sources') 34 | from android.sourceSets.main.javaDirectories 35 | } 36 | artifacts { 37 | archives sourcesJar 38 | } 39 | if (android.productFlavors.size() > 0) { 40 | android.libraryVariants.all { variant -> 41 | if (variant.name.toLowerCase().contains("debug")) { 42 | return 43 | } 44 | 45 | def bundleTask = tasks["bundle${variant.name.capitalize()}"] 46 | 47 | artifacts { 48 | archives(bundleTask.archivePath) { 49 | classifier variant.flavorName 50 | builtBy bundleTask 51 | name = project.name 52 | } 53 | } 54 | 55 | } 56 | } 57 | //------------ multi lib <<< ------ 58 | 59 | dependencies { 60 | implementation fileTree(dir: 'libs', include: ['*.jar']) 61 | testImplementation 'junit:junit:4.12' 62 | //implementation 'com.android.support:appcompat-v7:26.1.0' 63 | } 64 | 65 | task javadoc(type: Javadoc) { 66 | options.encoding = "utf-8" 67 | } -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/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 D:\study\android\sdk2/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 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/project.properties: -------------------------------------------------------------------------------- 1 | #project 2 | project.name = android-nestedscroll 3 | project.groupId = com.heaven7.android.scroll 4 | project.artifactId = android-nestedscroll 5 | project.packaging = aar 6 | project.siteUrl = https://github.com/LightSun/Android-sticky-navigation-layout 7 | project.githubRepo = LightSun/Android-sticky-navigation-layout 8 | #new 9 | project.desc = a lib of process android nested scrolling. 10 | project.liscenseName = 'The Apache Software License, Version 2.0' 11 | project.licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 12 | 13 | #javadoc 14 | javadoc.name=android-nestedscroll 15 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/INestedScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.view.MotionEvent; 4 | 5 | import androidx.core.view.NestedScrollingChild; 6 | 7 | /** 8 | * an expand interface of {@link IScrollHelper} 9 | * Created by heaven7 on 2016/11/15. 10 | */ 11 | public interface INestedScrollHelper extends IScrollHelper{ 12 | 13 | 14 | /** 15 | * set to enable or disable the nested scroll. 16 | * @param enable true to enable 17 | */ 18 | void setNestedScrollingEnabled(boolean enable); 19 | 20 | /** 21 | * is enabled the nested scroll. 22 | * @return true to enable. 23 | */ 24 | boolean isNestedScrollingEnabled(); 25 | 26 | /** 27 | * similar to {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)} , but is a nested scroll implement of it. 28 | * @param ev the touch event 29 | * @return true if should intercept. 30 | */ 31 | boolean onInterceptTouchEvent(MotionEvent ev); 32 | 33 | /** 34 | * similar to {@link android.view.ViewGroup#onTouchEvent(MotionEvent)} , but is a nested scroll implement of it. 35 | * @param event the touch event 36 | * @return true if handle the touch event. 37 | */ 38 | boolean onTouchEvent(MotionEvent event); 39 | 40 | /** 41 | * Does not perform bounds checking. Used by internal methods that have already validated input. 42 | *

43 | * It is unlike the {@link #scrollBy(int, int)}, and it can communicate with {@link NestedScrollingChild}. 44 | * It also reports any unused scroll request to the related EdgeEffect (current not implements). 45 | * 46 | * @param dx The amount of horizontal scroll request 47 | * @param dy The amount of vertical scroll request 48 | * @param ev The originating MotionEvent, or null if not from a touch event. 49 | * @return Whether any scroll was consumed in either direction. 50 | */ 51 | boolean nestedScrollBy(int dx, int dy, MotionEvent ev); 52 | 53 | /** 54 | * do scroll internal. this method is often called in {@link #nestedScrollBy(int, int, MotionEvent)}. 55 | * 56 | * @param dx the delta x, may be negative 57 | * @param dy the delta y , may be negative 58 | * @param consumed optional , if not null it will contains the consumed x and y by this scroll. 59 | * @param dispatchScroll if scrolled , whether dispatch the scroll distance changed or not. 60 | * @return the consumed x and y as array by this scroll. 61 | */ 62 | int[] nestedScroll(int dx, int dy, int[] consumed, boolean dispatchScroll); 63 | } 64 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/IScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * the scroll helper. this define a specification of scroll. help to fast handle the scroll. 7 | * Created by heaven7 on 2016/11/15. 8 | */ 9 | public interface IScrollHelper { 10 | 11 | /** 12 | * The view is not currently scrolling. 13 | */ 14 | int SCROLL_STATE_IDLE = 0; 15 | 16 | /** 17 | * The view is currently being dragged by outside input such as user touch input. 18 | */ 19 | int SCROLL_STATE_DRAGGING = 1; 20 | 21 | /** 22 | * The view is currently animating to a final position while not under 23 | * outside control. 24 | */ 25 | int SCROLL_STATE_SETTLING = 2; 26 | 27 | /** 28 | * set the scroll state. 29 | * @param state the target scroll state. must be one of {@link #SCROLL_STATE_IDLE} or {@link #SCROLL_STATE_DRAGGING} or {@link #SCROLL_STATE_SETTLING}. 30 | */ 31 | void setScrollState(int state); 32 | 33 | /** 34 | * dispatch the scroll distance changed. 35 | * @param dx the amount of pixels to scroll by horizontally 36 | * @param dy the amount of pixels to scroll by vertically 37 | */ 38 | void dispatchOnScrolled(int dx, int dy); 39 | 40 | /** 41 | * get the scroll state, which is set by call {@link #setScrollState(int)}. 42 | * @return {@link IScrollHelper#SCROLL_STATE_IDLE}, {@link IScrollHelper#SCROLL_STATE_DRAGGING} or 43 | * {@link IScrollHelper#SCROLL_STATE_SETTLING} 44 | */ 45 | int getScrollState(); 46 | 47 | /** 48 | * Move the scrolled position of your view. This will cause a call to 49 | * {@link View#onScrollChanged(int, int, int, int)} and the view will be 50 | * invalidated. 51 | * @param dx the amount of pixels to scroll by horizontally 52 | * @param dy the amount of pixels to scroll by vertically 53 | */ 54 | void scrollBy(int dx, int dy); 55 | 56 | /** 57 | * Set the scrolled position of your view. This will cause a call to 58 | * {@link View#onScrollChanged(int, int, int, int)} and the view will be 59 | * invalidated. 60 | * @param x the x position to scroll to 61 | * @param y the y position to scroll to 62 | */ 63 | void scrollTo(int x, int y); 64 | 65 | /** 66 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 67 | * 68 | * @param dx the number of pixels to scroll by on the X axis 69 | * @param dy the number of pixels to scroll by on the Y axis 70 | */ 71 | void smoothScrollBy(int dx, int dy); 72 | 73 | /** 74 | * Like {@link View#scrollTo}, but scroll smoothly instead of immediately. 75 | * 76 | * @param x the position where to scroll on the X axis 77 | * @param y the position where to scroll on the Y axis 78 | */ 79 | void smoothScrollTo(int x, int y); 80 | 81 | 82 | /** 83 | * Stop any current scroll in progress, such as one started by 84 | * {@link #smoothScrollBy(int, int)}, {@link #fling(float, float)} or a touch-initiated fling. 85 | */ 86 | void stopScroll(); 87 | 88 | /** 89 | * override method of {@link View#computeScroll()}. you should call this method in it. 90 | */ 91 | void computeScroll(); 92 | 93 | /** 94 | * is the scroll finish or not. 95 | * @return the scroll finish or not. 96 | * @since 1.0.3 97 | */ 98 | boolean isScrollFinish(); 99 | 100 | /** 101 | * Begin a standard fling with an initial velocity along each axis in pixels per second. 102 | * If the velocity given is below the system-defined minimum this method will return false 103 | * and no fling will occur. 104 | * 105 | * @param velocityX Initial horizontal velocity in pixels per second 106 | * @param velocityY Initial vertical velocity in pixels per second 107 | * @return true if the fling was started, false if the velocity was too low to fling or 108 | * does not support scrolling in the axis fling is issued. 109 | */ 110 | boolean fling(float velocityX, float velocityY); 111 | 112 | 113 | /** 114 | * add a scroll change listener. 115 | * @param l the OnScrollChangeListener 116 | */ 117 | void addOnScrollChangeListener(OnScrollChangeListener l); 118 | 119 | /** 120 | * remove a scroll change listener. 121 | * @param l the OnScrollChangeListener 122 | */ 123 | void removeOnScrollChangeListener(OnScrollChangeListener l); 124 | 125 | /** 126 | * judge if has the target OnScrollChangeListener. 127 | * @param l the OnScrollChangeListener 128 | * @return true if has the target OnScrollChangeListener 129 | */ 130 | boolean hasOnScrollChangeListener(OnScrollChangeListener l); 131 | 132 | /** 133 | * on scroll change listener. 134 | */ 135 | interface OnScrollChangeListener { 136 | 137 | /** 138 | * called when the scroll state change 139 | * 140 | * @param target the target view 141 | * @param state the scroll state . see {@link #SCROLL_STATE_IDLE} and etc. 142 | */ 143 | void onScrollStateChanged(View target, int state); 144 | 145 | /** 146 | * Callback method to be invoked when the view has been scrolled. This will be 147 | * called after the scroll has completed. 148 | *

149 | * This callback will also be called if visible item range changes after a layout 150 | * calculation. In that case, dx and dy will be 0. 151 | * 152 | * @param target the target view 153 | * @param dx The amount of horizontal scroll. dx > 0 means finger left. 154 | * @param dy The amount of vertical scroll. dy > 0 means finger up. 155 | */ 156 | void onScrolled(View target, int dx, int dy); 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/NestedScrollFactory.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.view.View; 4 | import android.widget.OverScroller; 5 | 6 | import androidx.core.view.NestedScrollingChild; 7 | 8 | /** 9 | * the scroll factory of {@link ScrollHelper} and {@link NestedScrollHelper}. 10 | * Created by heaven7 on 2016/11/15. 11 | */ 12 | public class NestedScrollFactory { 13 | 14 | /** 15 | * create a ScrollHelper. 16 | * @param target the target view 17 | * @param sensitivity Multiplier for how sensitive the helper should be about detecting 18 | * the start of a drag. Larger values are more sensitive. 1.0f is normal. 19 | * @param scroller the over Scroller 20 | * @param callback the callback 21 | */ 22 | public static ScrollHelper create(View target, float sensitivity, OverScroller scroller, ScrollHelper.ScrollCallback callback){ 23 | return new ScrollHelper(target , sensitivity, scroller, callback); 24 | } 25 | /** 26 | * create a ScrollHelper. 27 | * @param target the target view 28 | * @param scroller the over Scroller 29 | * @param callback the callback 30 | */ 31 | public static ScrollHelper create(View target, OverScroller scroller, ScrollHelper.ScrollCallback callback){ 32 | return new ScrollHelper(target , 1, scroller, callback); 33 | } 34 | /** 35 | * create a ScrollHelper and use the default {@link OverScroller]} with default the interpolator. 36 | * @param target the target view 37 | * @param callback the callback 38 | */ 39 | public static ScrollHelper create(View target, ScrollHelper.ScrollCallback callback){ 40 | return new ScrollHelper(target , 1, new OverScroller(target.getContext()), callback); 41 | } 42 | 43 | /** 44 | * create the nested scroll helper. 45 | * @param target the target view 46 | * @param sensitivity Multiplier for how sensitive the helper should be about detecting 47 | * the start of a drag. Larger values are more sensitive. 1.0f is normal. 48 | * @param scroller the scroller 49 | * @param child the NestedScrollingChild. 50 | * @param callback the callback 51 | */ 52 | public static NestedScrollHelper create(View target, float sensitivity, OverScroller scroller, NestedScrollingChild child, 53 | NestedScrollHelper.NestedScrollCallback callback) { 54 | return new NestedScrollHelper(target , sensitivity, scroller, child , callback); 55 | } 56 | /** 57 | * create the nested scroll helper, but the target view must implements {@link NestedScrollingChild}. 58 | * @param target the target view 59 | * @param scroller the scroller 60 | * @param callback the callback 61 | */ 62 | public static NestedScrollHelper create(View target, OverScroller scroller, NestedScrollHelper.NestedScrollCallback callback) { 63 | return new NestedScrollHelper(target , 1, scroller, (NestedScrollingChild) target, callback); 64 | } 65 | /** 66 | * create the nested scroll helper, use default {@link OverScroller} , but the target view must implements {@link NestedScrollingChild}. 67 | * @param target the target view 68 | * @param callback the callback 69 | */ 70 | public static NestedScrollHelper create(View target, NestedScrollHelper.NestedScrollCallback callback) { 71 | return new NestedScrollHelper(target , 1, new OverScroller(target.getContext()), (NestedScrollingChild) target, callback); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/NestedScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.util.Log; 4 | import android.view.MotionEvent; 5 | import android.view.VelocityTracker; 6 | import android.view.View; 7 | import android.widget.OverScroller; 8 | 9 | import androidx.core.view.NestedScrollingChild; 10 | import androidx.core.view.VelocityTrackerCompat; 11 | import androidx.core.view.ViewCompat; 12 | 13 | /** 14 | * nested scroll helper . it can communicate with {@link NestedScrollingChild}. 15 | * Created by heaven7 on 2016/11/15. 16 | */ 17 | public class NestedScrollHelper extends ScrollHelper implements INestedScrollHelper { 18 | 19 | private final NestedScrollingChild mNestedChild; 20 | 21 | private final int[] mScrollConsumed = new int[2]; 22 | private final int[] mNestedOffsets = new int[2]; 23 | private final int[] mScrollOffset = new int[2]; 24 | private int mScrollPointerId; 25 | 26 | protected final int[] mTempXY = new int[2]; 27 | 28 | private VelocityTracker mVelocityTracker; 29 | private int mInitialTouchX; 30 | private int mLastTouchX; 31 | private int mInitialTouchY; 32 | private int mLastTouchY; 33 | 34 | /** 35 | * create the nested scroll helper. But,the target view must implements interface {@link NestedScrollingChild}. 36 | * @param target the target view 37 | * @param scroller the scroller 38 | * @param callback the callback 39 | */ 40 | public NestedScrollHelper(View target, OverScroller scroller, NestedScrollCallback callback) { 41 | this(target, 1, scroller, (NestedScrollingChild) target, callback); 42 | } 43 | 44 | /** 45 | * create the nested scroll helper. 46 | * @param target the target view 47 | * @param sensitivity Multiplier for how sensitive the helper should be about detecting 48 | * the start of a drag. Larger values are more sensitive. 1.0f is normal. 49 | * @param scroller the scroller 50 | * @param child the NestedScrollingChild. 51 | * @param callback the callback 52 | */ 53 | public NestedScrollHelper(View target, float sensitivity, OverScroller scroller, NestedScrollingChild child, NestedScrollCallback callback) { 54 | super(target, sensitivity, scroller, callback); 55 | Util.check(child); 56 | this.mNestedChild = child; 57 | } 58 | 59 | @Override 60 | public void setNestedScrollingEnabled(boolean enable) { 61 | if (mNestedChild.isNestedScrollingEnabled() != enable) { 62 | mNestedChild.setNestedScrollingEnabled(enable); 63 | onNestedScrollStateChanged(enable); 64 | } 65 | } 66 | 67 | @Override 68 | public boolean isNestedScrollingEnabled() { 69 | return mNestedChild.isNestedScrollingEnabled(); 70 | } 71 | 72 | @Override 73 | public boolean onInterceptTouchEvent(MotionEvent ev) { 74 | if (mVelocityTracker == null) { 75 | mVelocityTracker = VelocityTracker.obtain(); 76 | } 77 | mVelocityTracker.addMovement(ev); 78 | boolean canScrollHorizontally = mCallback.canScrollHorizontally(getTarget()); 79 | boolean canScrollVertically = mCallback.canScrollVertically(getTarget()); 80 | 81 | final int action = ev.getActionMasked(); 82 | final int actionIndex = ev.getActionIndex(); 83 | final int mTouchSlop = getTouchSlop(); 84 | 85 | switch (action) { 86 | case MotionEvent.ACTION_DOWN: 87 | mScrollPointerId = ev.getPointerId(0); 88 | mInitialTouchX = mLastTouchX = (int) (ev.getX() + 0.5f); 89 | mInitialTouchY = mLastTouchY = (int) (ev.getY() + 0.5f); 90 | 91 | if (getScrollState() == SCROLL_STATE_SETTLING) { 92 | getTarget().getParent().requestDisallowInterceptTouchEvent(true); 93 | setScrollState(SCROLL_STATE_DRAGGING); 94 | } 95 | 96 | // Clear the nested offsets 97 | mNestedOffsets[0] = mNestedOffsets[1] = 0; 98 | 99 | int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 100 | if (canScrollHorizontally) { 101 | nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 102 | } 103 | if (canScrollVertically) { 104 | nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 105 | } 106 | mNestedChild.startNestedScroll(nestedScrollAxis); 107 | break; 108 | 109 | case MotionEvent.ACTION_POINTER_DOWN: 110 | mScrollPointerId = ev.getPointerId(actionIndex); 111 | mInitialTouchX = mLastTouchX = (int) (ev.getX(actionIndex) + 0.5f); 112 | mInitialTouchY = mLastTouchY = (int) (ev.getY(actionIndex) + 0.5f); 113 | break; 114 | 115 | case MotionEvent.ACTION_MOVE: 116 | final int index = ev.findPointerIndex(mScrollPointerId); 117 | if (index < 0) { 118 | Log.e(mTag, "Error processing scroll; pointer index for id " + 119 | mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 120 | return false; 121 | } 122 | 123 | final int x = (int) (ev.getX(index) + 0.5f); 124 | final int y = (int) (ev.getY(index) + 0.5f); 125 | 126 | if (getScrollState() != SCROLL_STATE_DRAGGING) { 127 | final int dx = x - mInitialTouchX; 128 | final int dy = y - mInitialTouchY; 129 | 130 | boolean startScroll = false; 131 | if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 132 | mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); 133 | startScroll = true; 134 | } 135 | if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 136 | mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); 137 | startScroll = true; 138 | } 139 | if (startScroll) { 140 | setScrollState(SCROLL_STATE_DRAGGING); 141 | } 142 | } 143 | break; 144 | 145 | case MotionEvent.ACTION_POINTER_UP: { 146 | onPointerUp(ev); 147 | } 148 | break; 149 | 150 | case MotionEvent.ACTION_UP: { 151 | mVelocityTracker.clear(); 152 | mNestedChild.stopNestedScroll(); 153 | } 154 | break; 155 | 156 | case MotionEvent.ACTION_CANCEL: { 157 | cancelTouch(); 158 | } 159 | break; 160 | } 161 | return getScrollState() == SCROLL_STATE_DRAGGING || ((NestedScrollCallback) mCallback).forceInterceptTouchEvent(this, ev); 162 | } 163 | 164 | @Override 165 | public boolean onTouchEvent(MotionEvent event) { 166 | final boolean canScrollHorizontally = mCallback.canScrollHorizontally(getTarget()); 167 | final boolean canScrollVertically = mCallback.canScrollVertically(getTarget()); 168 | 169 | if (mVelocityTracker == null) { 170 | mVelocityTracker = VelocityTracker.obtain(); 171 | } 172 | boolean eventAddedToVelocityTracker = false; 173 | 174 | final MotionEvent vtev = MotionEvent.obtain(event); 175 | final int action = event.getActionMasked(); 176 | final int actionIndex = event.getActionIndex(); 177 | 178 | final NestedScrollCallback mCallback = (NestedScrollCallback) this.mCallback; 179 | final int mTouchSlop = getTouchSlop(); 180 | 181 | if (action == MotionEvent.ACTION_DOWN) { 182 | mNestedOffsets[0] = mNestedOffsets[1] = 0; 183 | } 184 | vtev.offsetLocation(mNestedOffsets[0], mNestedOffsets[1]); 185 | 186 | switch (action) { 187 | 188 | case MotionEvent.ACTION_DOWN: { 189 | if (mCallback.shouldStopScrollOnActionDown()) { 190 | stopScrollerInternal(); 191 | } 192 | mScrollPointerId = event.getPointerId(0); 193 | mInitialTouchX = mLastTouchX = (int) (event.getX() + 0.5f); 194 | mInitialTouchY = mLastTouchY = (int) (event.getY() + 0.5f); 195 | 196 | int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; 197 | if (canScrollHorizontally) { 198 | nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; 199 | } 200 | if (canScrollVertically) { 201 | nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; 202 | } 203 | mNestedChild.startNestedScroll(nestedScrollAxis); 204 | } 205 | break; 206 | 207 | case MotionEvent.ACTION_POINTER_DOWN: { 208 | mScrollPointerId = event.getPointerId(actionIndex); 209 | mInitialTouchX = mLastTouchX = (int) (event.getX(actionIndex) + 0.5f); 210 | mInitialTouchY = mLastTouchY = (int) (event.getY(actionIndex) + 0.5f); 211 | } 212 | break; 213 | 214 | case MotionEvent.ACTION_MOVE: { 215 | //we should follow the nested standard of Google. 216 | final int index = event.findPointerIndex(mScrollPointerId); 217 | if (index < 0) { 218 | Log.e(mTag, "Error processing scroll; pointer index for id " + 219 | mScrollPointerId + " not found. Did any MotionEvents get skipped?"); 220 | return false; 221 | } 222 | final int x = (int) (event.getX(index) + 0.5f); 223 | final int y = (int) (event.getY(index) + 0.5f); 224 | int dx = mLastTouchX - x; 225 | int dy = mLastTouchY - y; 226 | 227 | /* if(DEBUG) { 228 | Log.i(mTag, "onTouchEvent: " + String.format("before --- dispatchNestedPreScroll ---- (dx = %d ,dy = %d )", dx, dy)); 229 | }*/ 230 | 231 | if (mNestedChild.dispatchNestedPreScroll(dx, dy, mScrollConsumed, mScrollOffset)) { 232 | /*if(DEBUG) { 233 | Log.i(mTag, "onTouchEvent_dispatchNestedPreScroll: parent consumed: " + Arrays.toString(mScrollConsumed)); 234 | }*/ 235 | dx -= mScrollConsumed[0]; 236 | dy -= mScrollConsumed[1]; 237 | vtev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 238 | // Updated the nested offsets 239 | mNestedOffsets[0] += mScrollOffset[0]; 240 | mNestedOffsets[1] += mScrollOffset[1]; 241 | } 242 | 243 | if (getScrollState() != SCROLL_STATE_DRAGGING) { 244 | boolean startScroll = false; 245 | if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { 246 | if (dx > 0) { 247 | dx -= mTouchSlop; 248 | } else { 249 | dx += mTouchSlop; 250 | } 251 | startScroll = true; 252 | } 253 | if (canScrollVertically && Math.abs(dy) > mTouchSlop) { 254 | if (dy > 0) { 255 | dy -= mTouchSlop; 256 | } else { 257 | dy += mTouchSlop; 258 | } 259 | startScroll = true; 260 | } 261 | if (startScroll) { 262 | setScrollState(SCROLL_STATE_DRAGGING); 263 | } 264 | } 265 | 266 | if (getScrollState() == SCROLL_STATE_DRAGGING) { 267 | mLastTouchX = x - mScrollOffset[0]; 268 | mLastTouchY = y - mScrollOffset[1]; 269 | if (nestedScrollBy(dx, dy, vtev)) { 270 | getTarget().getParent().requestDisallowInterceptTouchEvent(true); 271 | } 272 | } 273 | } 274 | break; 275 | 276 | case MotionEvent.ACTION_POINTER_UP: { 277 | onPointerUp(event); 278 | } 279 | break; 280 | 281 | case MotionEvent.ACTION_CANCEL: 282 | cancelTouch(); 283 | break; 284 | 285 | case MotionEvent.ACTION_UP: 286 | mVelocityTracker.addMovement(vtev); 287 | eventAddedToVelocityTracker = true; 288 | mVelocityTracker.computeCurrentVelocity(1000, getMaxFlingVelocity()); 289 | final float xvel = canScrollHorizontally ? 290 | -mVelocityTracker.getXVelocity(mScrollPointerId) : 0; 291 | final float yvel = canScrollVertically ? 292 | -mVelocityTracker.getYVelocity(mScrollPointerId) : 0; 293 | /* final float xvel = VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId); 294 | final float yvel =VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId);*/ 295 | // like recycler view 296 | if (!((xvel != 0 || yvel != 0) && fling(xvel, yvel))) { 297 | setScrollState(SCROLL_STATE_IDLE); 298 | } 299 | resetTouch(); 300 | break; 301 | } 302 | if (!eventAddedToVelocityTracker) { 303 | mVelocityTracker.addMovement(vtev); 304 | } 305 | vtev.recycle(); 306 | 307 | return true; 308 | } 309 | 310 | @Override 311 | public boolean nestedScrollBy(int dx, int dy, MotionEvent ev) { 312 | mScrollConsumed[0] = 0; 313 | mScrollConsumed[1] = 0; 314 | 315 | //here we dispatch scroll later. 316 | nestedScroll(dx, dy, mScrollConsumed, false); 317 | final int consumedX = mScrollConsumed[0]; 318 | final int consumedY = mScrollConsumed[1]; 319 | 320 | final int unconsumedX = dx - consumedX; 321 | final int unconsumedY = dy - consumedY; 322 | 323 | if (DEBUG) { 324 | Log.i(mTag, "nestedScrollBy: before dispatchNestedScroll--> consumedX = " + consumedX + " ,consumedY = " + consumedY + 325 | " ,unconsumedX = " + unconsumedX + " ,unconsumedY = " + unconsumedY); 326 | } 327 | if (mNestedChild.dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) { 328 | // Update the last touch co-ords, taking any scroll offset into account 329 | mLastTouchX -= mScrollOffset[0]; 330 | mLastTouchY -= mScrollOffset[1]; 331 | if (ev != null) { 332 | ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); 333 | } 334 | mNestedOffsets[0] += mScrollOffset[0]; 335 | mNestedOffsets[1] += mScrollOffset[1]; 336 | } 337 | if (consumedX != 0 || consumedY != 0) { 338 | dispatchOnScrolled(consumedX, consumedY); 339 | } 340 | /* if (!awakenScrollBars()) { 341 | getTarget().invalidate(); 342 | }*/ 343 | return consumedX != 0 || consumedY != 0; 344 | } 345 | 346 | @Override 347 | public int[] nestedScroll(int dx, int dy, int[] consumed, boolean dispatchScroll) { 348 | 349 | //gesture up dy >0 , gesture down dy < 0 350 | if (consumed == null) { 351 | consumed = new int[2]; 352 | } 353 | // Logger.i(TAG, "scrollInternal", "dx = " + dx + ",dy = " + dy + " ,consumed = " + Arrays.toString(consumed)); 354 | 355 | getScrollXY(mTempXY); 356 | final View target = getTarget(); 357 | final int maxX = mCallback.getMaximumXScrollDistance(target); 358 | final int maxY = mCallback.getMaximumYScrollDistance(target); 359 | 360 | final int scrollX = mTempXY[0]; 361 | final int scrollY = mTempXY[1]; 362 | 363 | // isNestedScrollingEnabled() 364 | /** parent scroll done(but child's scroll x and y is zero.) , child can continue scroll ? #getScrollXY(mTempXY); can resolve it. 365 | * see this error log: 366 | I/StickyNavigationLayout: called [ nestedScroll() ]: (scrollX = 0 ,scrollY = 525, maxX = 1080 ,maxY = 525) 367 | I/StickyNavigationLayout: called [ nestedScroll() ]: (scrollX = 0 ,scrollY = 525, maxX = 1080 ,maxY = 525) 368 | I/NestedScrollFrameLayout: called [ nestedScroll() ]: (scrollX = 0 ,scrollY = 0, maxX = 1080 ,maxY = 262) 369 | I/NestedScrollFrameLayout: called [ nestedScrollBy() ]: before dispatchNestedScroll--> consumedX = 0 ,consumedY = 1 ,unconsumedX = 15 ,unconsumedY = 0 370 | */ 371 | if (DEBUG) { 372 | Log.i(mTag, "nestedScroll: " + String.format("(scrollX = %d ,scrollY = %d, maxX = %d ,maxY = %d)", 373 | scrollX, scrollY, maxX, maxY)); 374 | } 375 | 376 | int by = 0; 377 | if (mCallback.canScrollVertically(target)) { 378 | if (dy > 0) { 379 | //gesture up 380 | if (scrollY < maxY) { 381 | int maxH = maxY - scrollY; 382 | by = consumed[1] = Math.min(dy, maxH); 383 | } else { 384 | //ignore 385 | } 386 | } else { 387 | //gesture down 388 | if (scrollY > 0) { 389 | by = consumed[1] = -Math.min(Math.abs(dy), scrollY); 390 | } 391 | } 392 | } 393 | 394 | int bx = 0; 395 | if (mCallback.canScrollHorizontally(target)) { 396 | if (dx > 0) { 397 | //gesture left 398 | //only cal scroll in maxX 399 | if (scrollX < maxX) { 400 | bx = consumed[0] = Math.min(dx, maxX - scrollX); 401 | } 402 | } else { 403 | //gesture right 404 | if (scrollX > 0) { 405 | by = consumed[0] = -Math.min(Math.abs(dx), scrollX); 406 | } 407 | } 408 | } 409 | scrollBy(bx, by); 410 | if (dispatchScroll) { 411 | if (bx != 0 || by != 0) { 412 | dispatchOnScrolled(bx, by); 413 | } 414 | } 415 | return consumed; 416 | } 417 | 418 | @Override 419 | protected boolean onFling(boolean canScrollHorizontal, boolean canScrollVertical, float velocityX, float velocityY) { 420 | if (DEBUG) { 421 | Log.i(mTag, "onFling: "+"velocityX = " + velocityX + " ,velocityY = " + velocityY ); 422 | } 423 | if (!mNestedChild.dispatchNestedPreFling(velocityX, velocityY)) { 424 | final boolean canScroll = canScrollHorizontal || canScrollVertical; 425 | mNestedChild.dispatchNestedFling(velocityX, velocityY, canScroll); 426 | 427 | if (canScroll) { 428 | setScrollState(SCROLL_STATE_SETTLING); 429 | final float mMaxFlingVelocity = getMaxFlingVelocity(); 430 | final View mTarget = getTarget(); 431 | 432 | velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 433 | velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 434 | 435 | //mScroller.fling(0, getScrollY(), velocityX, velocityY, 0, 0, 0, mTopViewHeight); 436 | // getScrollXY(mTempXY); 437 | final int maxX; 438 | final int maxY; 439 | if(isNestedScrollingEnabled()){ 440 | final int parentScrollX = mTarget.getParent() != null ? ((View) mTarget.getParent()).getScrollX() : 0; 441 | final int parentScrollY = mTarget.getParent() != null ? ((View) mTarget.getParent()).getScrollY() : 0; 442 | maxX = parentScrollX == 0 ? mCallback.getMaximumXScrollDistance(mTarget): 0; 443 | maxY = parentScrollY == 0 ? mCallback.getMaximumYScrollDistance(mTarget): 0; 444 | if(DEBUG){ 445 | Log.i(mTag, "onFling: parentScrollX = " + parentScrollX + " ,parentScrollY = " + parentScrollY); 446 | } 447 | }else{ 448 | maxX = mCallback.getMaximumXScrollDistance(mTarget); 449 | maxY = mCallback.getMaximumYScrollDistance(mTarget); 450 | } 451 | if (DEBUG) { 452 | Log.i(mTag, "onFling: after adjust , velocityX = " + velocityX + " ,velocityY = " + velocityY); 453 | Log.i(mTag, "onFling: maxX = " + maxX + " ,maxY = " + maxY ); 454 | } 455 | getScroller().fling(mTarget.getScrollX(), mTarget.getScrollY(), (int) velocityX, (int) velocityY, 456 | 0, canScrollHorizontal ? maxX : 0, 457 | 0, canScrollVertical ? maxY: 0 458 | ); 459 | ViewCompat.postInvalidateOnAnimation(mTarget); 460 | return true; 461 | } 462 | } 463 | return false; 464 | } 465 | 466 | /** 467 | * get the scroll x and y of the current view. 468 | * 469 | * @param scrollXY the array of scroll x and y. can be null. 470 | * @return the array of scroll x and y. 471 | */ 472 | protected int[] getScrollXY(int[] scrollXY) { 473 | if (scrollXY == null) { 474 | scrollXY = new int[2]; 475 | } 476 | final View target = getTarget(); 477 | final NestedScrollCallback mCallback = (NestedScrollCallback) this.mCallback; 478 | final int parentScrollX = target.getParent() != null ? ((View) target.getParent()).getScrollX() : 0; 479 | final int parentScrollY = target.getParent() != null ? ((View) target.getParent()).getScrollY() : 0; 480 | 481 | if (isNestedScrollingEnabled()) { 482 | scrollXY[0] = mCallback.adjustScrollX(target, parentScrollX, mCallback.getMaximumXScrollDistance(target)); 483 | scrollXY[1] = mCallback.adjustScrollY(target, parentScrollY, mCallback.getMaximumYScrollDistance(target)); 484 | } else { 485 | scrollXY[0] = target.getScrollX(); 486 | scrollXY[1] = target.getScrollY(); 487 | } 488 | return scrollXY; 489 | } 490 | 491 | /** 492 | * called in {@link #setNestedScrollingEnabled(boolean)} (boolean)}, when the nested scroll enable state changed. 493 | * 494 | * @param enable true to enable 495 | */ 496 | protected void onNestedScrollStateChanged(boolean enable) { 497 | 498 | } 499 | 500 | protected void cancelTouch() { 501 | /* if (!mScroller.isFinished()) { 502 | mScroller.abortAnimation(); 503 | }*/ 504 | resetTouch(); 505 | setScrollState(SCROLL_STATE_IDLE); 506 | } 507 | 508 | protected void resetTouch() { 509 | if (mVelocityTracker != null) { 510 | mVelocityTracker.recycle(); 511 | mVelocityTracker = null; 512 | } 513 | mNestedChild.stopNestedScroll(); 514 | //TODO releaseGlows(); 515 | } 516 | 517 | protected void onPointerUp(MotionEvent e) { 518 | final int actionIndex = e.getActionIndex(); 519 | if (e.getPointerId(actionIndex) == mScrollPointerId) { 520 | // Pick a new pointer to pick up the slack. 521 | final int newIndex = actionIndex == 0 ? 1 : 0; 522 | mScrollPointerId = e.getPointerId(newIndex); 523 | mInitialTouchX = mLastTouchX = (int) (e.getX(newIndex) + 0.5f); 524 | mInitialTouchY = mLastTouchY = (int) (e.getY(newIndex) + 0.5f); 525 | } 526 | } 527 | 528 | /** 529 | * the callback of {@link NestedScrollHelper}. this class help we handle the nested scrolling. 530 | */ 531 | public static abstract class NestedScrollCallback extends ScrollCallback { 532 | /** 533 | * should stop scroll on the down event 534 | * 535 | * @return true if should stop scroll on the down event. 536 | */ 537 | public boolean shouldStopScrollOnActionDown() { 538 | return false; 539 | } 540 | 541 | /** 542 | * force intercept the touch event. 543 | * 544 | * @param helper the nested scroll helper. 545 | * @param ev the touch event 546 | * @return true to force intercept touch event. 547 | */ 548 | public boolean forceInterceptTouchEvent(NestedScrollHelper helper, MotionEvent ev) { 549 | 550 | return false; 551 | } 552 | 553 | /** 554 | * adjust the current scroll y of the target view. This give a chance to adjust it. 555 | * this is only called when {@link NestedScrollHelper#isNestedScrollingEnabled()} is true. 556 | * this if often used in {@link IScrollHelper#scrollBy(int, int)} ]}. 557 | * But, you should care about it when you want to change. 558 | * 559 | * @param target the target view 560 | * @param parentScrollY the scroll y of the parent view 561 | * @param maxY the max y . which comes from {@link #getMaximumYScrollDistance(View)}. 562 | * @return the current scroll y. 563 | */ 564 | public int adjustScrollY(View target, int parentScrollY, int maxY) { 565 | return target.getScrollY() + parentScrollY; 566 | } 567 | 568 | /** 569 | * adjust the current scroll x of the target view. This give a chance to adjust it. 570 | * this is only called when {@link NestedScrollHelper#isNestedScrollingEnabled()} is true. 571 | * But, you should care about it when you want to change. 572 | * 573 | * @param target the target view 574 | * @param parentScrollX the scroll x of the parent view 575 | * @param maxX the max x . which comes from {@link #getMaximumXScrollDistance(View)}. 576 | * @return the current scroll x. 577 | */ 578 | public int adjustScrollX(View target, int parentScrollX, int maxX) { 579 | return target.getScrollX() + parentScrollX; 580 | } 581 | } 582 | } 583 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/ScrollHelper.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.util.Log; 4 | import android.view.View; 5 | import android.view.ViewConfiguration; 6 | import android.view.ViewGroup; 7 | import android.view.animation.AnimationUtils; 8 | import android.widget.OverScroller; 9 | 10 | import androidx.core.view.ViewCompat; 11 | 12 | import java.util.concurrent.CopyOnWriteArrayList; 13 | 14 | /** 15 | *

16 | * this class is a simple implement of {@link IScrollHelper}. it can do most work of scroller. 17 | * such as {@link IScrollHelper#smoothScrollTo(int, int)}, {@link IScrollHelper#smoothScrollBy(int, int)} and etc. 18 | *

19 | * Created by heaven7 on 2016/11/14. 20 | */ 21 | public class ScrollHelper implements IScrollHelper { 22 | 23 | /*protected*/ static final boolean DEBUG = Util.sDEBUG; 24 | 25 | private static final long ANIMATED_SCROLL_GAP = 250; 26 | 27 | private CopyOnWriteArrayList mScrollListeners; 28 | private final OverScroller mScroller; 29 | protected final ScrollCallback mCallback; 30 | protected final String mTag; 31 | private final View mTarget; 32 | 33 | private final int mTouchSlop; 34 | private final float mMinFlingVelocity; 35 | private final float mMaxFlingVelocity; 36 | 37 | private long mLastScroll; 38 | private int mScrollState = SCROLL_STATE_IDLE; 39 | 40 | /** 41 | * create a ScrollHelper. 42 | * 43 | * @param target the target view 44 | * @param scroller the over Scroller 45 | * @param callback the callback 46 | */ 47 | public ScrollHelper(View target, OverScroller scroller, ScrollCallback callback) { 48 | this(target, 1, scroller, callback); 49 | } 50 | 51 | /** 52 | * create a ScrollHelper. 53 | * 54 | * @param target the target view 55 | * @param sensitivity Multiplier for how sensitive the helper should be about detecting 56 | * the start of a drag. Larger values are more sensitive. 1.0f is normal. 57 | * @param scroller the over Scroller 58 | * @param callback the callback 59 | */ 60 | public ScrollHelper(View target, float sensitivity, OverScroller scroller, ScrollCallback callback) { 61 | Util.check(target, "target view can't be null."); 62 | Util.check(scroller, null); 63 | Util.check(callback, "ScrollCallback can't be null"); 64 | final ViewConfiguration vc = ViewConfiguration.get(target.getContext()); 65 | this.mTag = target.getClass().getSimpleName(); 66 | this.mTarget = target; 67 | this.mCallback = callback; 68 | this.mScroller = scroller; 69 | this.mTouchSlop = (int) (vc.getScaledTouchSlop() * (1 / sensitivity)); 70 | this.mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity(); 71 | this.mMinFlingVelocity = vc.getScaledMinimumFlingVelocity(); 72 | } 73 | 74 | public OverScroller getScroller() { 75 | return mScroller; 76 | } 77 | 78 | public int getTouchSlop() { 79 | return mTouchSlop; 80 | } 81 | 82 | public float getMinFlingVelocity() { 83 | return mMinFlingVelocity; 84 | } 85 | 86 | public float getMaxFlingVelocity() { 87 | return mMaxFlingVelocity; 88 | } 89 | 90 | public View getTarget() { 91 | return mTarget; 92 | } 93 | 94 | @Override 95 | public void dispatchOnScrolled(int dx, int dy) { 96 | // Pass the current scrollX/scrollY values; no actual change in these properties occurred 97 | // but some general-purpose code may choose to respond to changes this way. 98 | /* final int scrollX = mTarget.getScrollX(); 99 | final int scrollY = mTarget.getScrollY(); 100 | mTarget.onScrollChanged(scrollX, scrollY, scrollX, scrollY);*/ 101 | 102 | // Invoke listeners last. Subclassed view methods always handle the event first. 103 | // All internal state is consistent by the time listeners are invoked. 104 | if (mScrollListeners != null && mScrollListeners.size() > 0) { 105 | for (OnScrollChangeListener l : mScrollListeners) { 106 | if (l != null) { 107 | l.onScrolled(mTarget, dx, dy); 108 | } 109 | } 110 | } 111 | // Pass the real deltas to onScrolled, the RecyclerView-specific method. 112 | onScrolled(dx, dy); 113 | } 114 | 115 | /** 116 | * Called when the scroll position of this view changes. Subclasses should use 117 | * this method to respond to scrolling within the adapter's data set instead of an explicit 118 | * listener. this is called in {@link #dispatchOnScrolled(int, int)}. 119 | *

120 | *

This method will always be invoked before listeners. If a subclass needs to perform 121 | * any additional upkeep or bookkeeping after scrolling but before listeners run, 122 | * this is a good place to do so.

123 | * 124 | * @param dx horizontal distance scrolled in pixels 125 | * @param dy vertical distance scrolled in pixels 126 | */ 127 | protected void onScrolled(int dx, int dy) { 128 | mCallback.onScrolled(dx, dy); 129 | } 130 | 131 | @Override 132 | public int getScrollState() { 133 | return mScrollState; 134 | } 135 | 136 | @Override 137 | public void setScrollState(int state) { 138 | if (state == mScrollState) { 139 | return; 140 | } 141 | if (DEBUG) { 142 | Log.d(mTag, "setting scroll state to " + state + " from " + mScrollState, 143 | new Exception()); 144 | } 145 | mScrollState = state; 146 | if (state != SCROLL_STATE_SETTLING) { 147 | stopScrollerInternal(); 148 | } 149 | dispatchOnScrollStateChanged(state); 150 | } 151 | 152 | protected void stopScrollerInternal() { 153 | if (!mScroller.isFinished()) { 154 | mScroller.abortAnimation(); 155 | } 156 | } 157 | 158 | /** 159 | * dispatch the scroll state change, this is called in {@link #setScrollState(int)}. 160 | * 161 | * @param state the target scroll state. 162 | */ 163 | protected void dispatchOnScrollStateChanged(int state) { 164 | if (mScrollListeners != null && mScrollListeners.size() > 0) { 165 | for (OnScrollChangeListener l : mScrollListeners) { 166 | if (l != null) { 167 | l.onScrollStateChanged(mTarget, state); 168 | } 169 | } 170 | } 171 | } 172 | 173 | @Override 174 | public void scrollBy(int dx, int dy) { 175 | scrollTo(mTarget.getScrollX() + dx, mTarget.getScrollY() + dy); 176 | } 177 | 178 | /** 179 | * {@inheritDoc}. Note: this is similar to {@link View#scrollTo(int, int)}, but limit the range of scroll, 180 | * which is indicate by {@link ScrollCallback#getMaximumXScrollDistance(View)} with {@link ScrollCallback#getMaximumYScrollDistance(View)}. 181 | * 182 | * @param x the x position to scroll to 183 | * @param y the y position to scroll to 184 | */ 185 | @Override 186 | public void scrollTo(int x, int y) { 187 | mTarget.scrollTo(Math.min(x, mCallback.getMaximumXScrollDistance(mTarget)), 188 | Math.min(y, mCallback.getMaximumYScrollDistance(mTarget))); 189 | } 190 | 191 | @Override 192 | public void smoothScrollBy(int dx, int dy) { 193 | if (mTarget instanceof ViewGroup && ((ViewGroup) mTarget).getChildCount() == 0) { 194 | // Nothing to do. 195 | return; 196 | } 197 | long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; 198 | if (duration > ANIMATED_SCROLL_GAP) { 199 | // design from scrollView 200 | final int scrollX = mTarget.getScrollX(); 201 | final int scrollY = mTarget.getScrollY(); 202 | final int maxX = mCallback.getMaximumXScrollDistance(mTarget); 203 | final int maxY = mCallback.getMaximumYScrollDistance(mTarget); 204 | if ((scrollX + dx) > maxX) { 205 | dx -= scrollX + dx - maxX; 206 | } 207 | if ((scrollY + dy) > maxY) { 208 | dy -= scrollY + dy - maxY; 209 | } 210 | setScrollState(SCROLL_STATE_SETTLING); 211 | mScroller.startScroll(scrollX, scrollY, dx, dy); 212 | ViewCompat.postInvalidateOnAnimation(mTarget); 213 | } else { 214 | if (!mScroller.isFinished()) { 215 | mScroller.abortAnimation(); 216 | } 217 | mTarget.scrollBy(dx, dy); 218 | } 219 | mLastScroll = AnimationUtils.currentAnimationTimeMillis(); 220 | } 221 | 222 | @Override 223 | public final void smoothScrollTo(int x, int y) { 224 | smoothScrollBy(x - mTarget.getScrollX(), y - mTarget.getScrollY()); 225 | } 226 | 227 | @Override 228 | public void stopScroll() { 229 | setScrollState(SCROLL_STATE_IDLE); 230 | stopScrollerInternal(); 231 | } 232 | 233 | @Override 234 | public void computeScroll() { 235 | if (mScroller.computeScrollOffset()) {//true if not finish 236 | if(DEBUG){ 237 | Log.i(mTag, "computeScroll: scroll not finished: currX = " + mScroller.getCurrX() 238 | + " ,currY = " + mScroller.getCurrY()); 239 | } 240 | mTarget.scrollTo(mScroller.getCurrX(), mScroller.getCurrY()); 241 | ViewCompat.postInvalidateOnAnimation(mTarget); 242 | } 243 | } 244 | 245 | @Override 246 | public boolean isScrollFinish(){ 247 | return mScroller.isFinished(); 248 | } 249 | 250 | @Override 251 | public boolean fling(float velocityX, float velocityY) { 252 | final boolean canScrollHorizontal = mCallback.canScrollHorizontally(mTarget); 253 | final boolean canScrollVertical = mCallback.canScrollVertically(mTarget); 254 | 255 | if (!canScrollHorizontal || Math.abs(velocityX) < mMinFlingVelocity) { 256 | velocityX = 0; 257 | } 258 | if (!canScrollVertical || Math.abs(velocityY) < mMinFlingVelocity) { 259 | velocityY = 0; 260 | } 261 | if (velocityX == 0 && velocityY == 0) { 262 | // If we don't have any velocity, return false 263 | return false; 264 | } 265 | return onFling(canScrollHorizontal, canScrollVertical, velocityX, velocityY); 266 | } 267 | 268 | @Override 269 | public void addOnScrollChangeListener(OnScrollChangeListener l) { 270 | if (mScrollListeners == null) { 271 | mScrollListeners = new CopyOnWriteArrayList<>(); 272 | } 273 | mScrollListeners.add(l); 274 | } 275 | 276 | @Override 277 | public void removeOnScrollChangeListener(OnScrollChangeListener l) { 278 | if (mScrollListeners != null) { 279 | mScrollListeners.remove(l); 280 | } 281 | } 282 | 283 | @Override 284 | public boolean hasOnScrollChangeListener(OnScrollChangeListener l) { 285 | return mScrollListeners != null && mScrollListeners.contains(l); 286 | } 287 | 288 | /** 289 | * do fling , this method is called in {@link #fling(float, float)} 290 | * 291 | * @param canScrollHorizontal if can scroll in Horizontal 292 | * @param canScrollVertical if can scroll in Vertical 293 | * @param velocityX the velocity of X 294 | * @param velocityY the velocity of y 295 | * @return true if the fling was started. 296 | */ 297 | protected boolean onFling(boolean canScrollHorizontal, boolean canScrollVertical, float velocityX, float velocityY) { 298 | if (canScrollHorizontal || canScrollVertical) { 299 | setScrollState(SCROLL_STATE_SETTLING); 300 | velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); 301 | velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); 302 | 303 | mScroller.fling(mTarget.getScrollX(), mTarget.getScrollY(), (int) velocityX, (int) velocityY, 304 | 0, canScrollHorizontal ? mCallback.getMaximumXScrollDistance(mTarget) : 0, 305 | 0, canScrollVertical ? mCallback.getMaximumYScrollDistance(mTarget) : 0 306 | ); 307 | //TODO why recyclerView use mScroller.fling(0, 0, (int)velocityX, (int)velocityY,Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); 308 | ViewCompat.postInvalidateOnAnimation(mTarget); 309 | return true; 310 | } 311 | return false; 312 | } 313 | 314 | /** 315 | * get the scroll state as string log. 316 | * @param state the scroll state. 317 | * @return the state as string 318 | */ 319 | public static String getScrollStateString(int state) { 320 | switch (state) { 321 | case SCROLL_STATE_DRAGGING: 322 | return "SCROLL_STATE_DRAGGING"; 323 | 324 | case SCROLL_STATE_SETTLING: 325 | return "SCROLL_STATE_SETTLING"; 326 | 327 | case SCROLL_STATE_IDLE: 328 | return "SCROLL_STATE_IDLE"; 329 | 330 | default: 331 | return "unknown state"; 332 | } 333 | } 334 | 335 | 336 | /** 337 | * the scroll callback of {@link ScrollHelper}. 338 | */ 339 | public static abstract class ScrollCallback { 340 | 341 | /** 342 | * if can scroll in Horizontal 343 | * 344 | * @param target the target view. 345 | * @return true if can scroll in Horizontal 346 | */ 347 | public abstract boolean canScrollHorizontally(View target); 348 | 349 | /** 350 | * if can scroll in Vertical 351 | * 352 | * @param target the target view. 353 | * @return true if can scroll in Vertical 354 | */ 355 | public abstract boolean canScrollVertically(View target); 356 | 357 | /** 358 | * get the maximum x scroll distance of the target view. 359 | * 360 | * @param target the target view. 361 | * @return the maximum x scroll distance 362 | */ 363 | public int getMaximumXScrollDistance(View target) { 364 | return target.getWidth(); 365 | } 366 | 367 | /** 368 | * get the maximum y scroll distance of the target view. 369 | * 370 | * @param target the target view. 371 | * @return the maximum y scroll distance 372 | */ 373 | public int getMaximumYScrollDistance(View target) { 374 | return target.getHeight(); 375 | } 376 | 377 | /** 378 | * called in {@link ScrollHelper#dispatchOnScrolled(int, int)}. 379 | * 380 | * @param dx the delta x 381 | * @param dy the delta y 382 | */ 383 | public void onScrolled(int dx, int dy) { 384 | 385 | } 386 | 387 | } 388 | } 389 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/java/com/heaven7/android/scroll/Util.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.scroll; 2 | 3 | import android.text.TextUtils; 4 | 5 | /** 6 | * internal util class 7 | * Created by heaven7 on 2016/11/16. 8 | */ 9 | /*public*/ final class Util { 10 | 11 | static boolean sDEBUG = false; 12 | 13 | /*public*/ static void check(Object obj ,String msg){ 14 | if(obj == null){ 15 | throw TextUtils.isEmpty(msg) ? new NullPointerException(): new NullPointerException(msg); 16 | } 17 | } 18 | /*public*/ static void check(Object obj ){ 19 | if(obj == null){ 20 | throw new NullPointerException(); 21 | } 22 | } 23 | 24 | public static void setDebug(boolean debug){ 25 | sDEBUG = debug; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/android-nestedscroll/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android-nestedScroll 3 | 4 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/androidx_compat.gradle: -------------------------------------------------------------------------------- 1 | 2 | def useAndroidX = true 3 | 4 | dependencies { 5 | def androidX_ver = "1.1.0" 6 | def androidSupport_ver = '28.0.0' 7 | if(useAndroidX){ 8 | implementation "androidx.appcompat:appcompat:$androidX_ver" 9 | implementation "androidx.recyclerview:recyclerview:$androidX_ver" 10 | // implementation "androidx.exifinterface:exifinterface:$androidX_ver" 11 | implementation "com.google.android.material:material:$androidX_ver" 12 | }else { 13 | implementation "com.android.support:recyclerview-v7:$androidSupport_ver" 14 | implementation "com.android.support:appcompat-v7:$androidSupport_ver" 15 | // implementation "com.android.support:exifinterface:$androidSupport_ver" 16 | implementation "com.android.support:design:${androidSupport_ver}" 17 | } 18 | } -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../androidx_compat.gradle' 3 | 4 | android { 5 | compileSdkVersion 28 6 | buildToolsVersion '28.0.3' 7 | 8 | defaultConfig { 9 | applicationId "com.heaven7.android.sticky_navigation_layout.demo" 10 | minSdkVersion 19 11 | targetSdkVersion 28 12 | versionCode 1 13 | versionName "1.0" 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | lintOptions { 22 | abortOnError false 23 | checkReleaseBuilds false 24 | } 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | //compile fileTree(dir: 'libs', include: ['*.jar']) 33 | testImplementation 'junit:junit:4.12' 34 | //implementation 'com.android.support:appcompat-v7:26.1.0' 35 | //implementation 'com.android.support:design:26.1.0' 36 | 37 | implementation 'com.github.LightSun:SuperAdapter:2.1.7-x' 38 | implementation('com.github.LightSun:util-v1:1.1.7-beta-x') { 39 | exclude group: 'com.android.support' 40 | exclude group: 'com.heaven7.android.component' 41 | } 42 | 43 | implementation 'com.jakewharton:butterknife:10.2.1' 44 | annotationProcessor 'com.jakewharton:butterknife-compiler:10.2.1' 45 | 46 | implementation project(':android-nestedscroll') 47 | implementation project(':sticky-navigation-layout') 48 | 49 | implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.0.0' 50 | //implementation 'com.android.support:swiperefreshlayout:28.0.0' 51 | /* implementation('com.heaven7.android.StickyLayout:sticky-navigation-layout:1.0.2'){ 52 | exclude group:'com.heaven7.android.scroll' 53 | }*/ 54 | 55 | //implementation 'com.heaven7.android.scroll:android-nestedscroll:1.0' 56 | //implementation project(':sticky-navigation-layout') 57 | 58 | } 59 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/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 D:\study\android\sdk2/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 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/AbsMainActivity.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo; 2 | 3 | import android.app.Activity; 4 | import android.app.ListActivity; 5 | import android.content.Context; 6 | import android.content.Intent; 7 | import android.os.Bundle; 8 | import android.view.View; 9 | import android.widget.ListView; 10 | 11 | import com.heaven7.adapter.ISelectable; 12 | import com.heaven7.adapter.QuickAdapter; 13 | import com.heaven7.core.util.ViewHelper; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | public abstract class AbsMainActivity extends ListActivity { 19 | 20 | protected final List mInfos = new ArrayList<>(); 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | // setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, options)); 26 | addDemos(mInfos); 27 | setListAdapter(new QuickAdapter(android.R.layout.simple_list_item_1, mInfos) { 28 | @Override 29 | protected void onBindData(Context context, int position, final ActivityInfo item, 30 | int itemLayoutId, ViewHelper helper) { 31 | helper.setText(android.R.id.text1, item.desc) 32 | .setRootOnClickListener(new View.OnClickListener() { 33 | @Override 34 | public void onClick(View v) { 35 | startActivity(new Intent(AbsMainActivity.this ,item.clazz)); 36 | } 37 | }); 38 | } 39 | }); 40 | } 41 | 42 | protected abstract void addDemos(List list); 43 | 44 | @Override 45 | protected void onListItemClick(ListView l, View v, int position, long id) { 46 | 47 | } 48 | 49 | public static class ActivityInfo implements ISelectable { 50 | final String desc; 51 | final Class clazz; 52 | 53 | public ActivityInfo(Class clazz, String desc) { 54 | this.clazz = clazz; 55 | this.desc = desc; 56 | } 57 | public ActivityInfo(Context context, Class clazz, int stringResId) { 58 | this.clazz = clazz; 59 | this.desc = context.getResources().getString(stringResId); 60 | } 61 | 62 | @Override 63 | public void setSelected(boolean selected) { 64 | } 65 | @Override 66 | public boolean isSelected() { 67 | return false; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/EnterActivity.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo; 2 | 3 | import com.heaven7.android.sticky_navigation_layout.demo.demos.MultiplexStickyNavigationDemo; 4 | import com.heaven7.android.sticky_navigation_layout.demo.demos.SimpleStickyNavigationDemo; 5 | import com.heaven7.android.sticky_navigation_layout.demo.demos.TestNestedScrollFrameLayout; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * Created by heaven7 on 2016/11/14. 11 | */ 12 | public class EnterActivity extends AbsMainActivity { 13 | 14 | @Override 15 | protected void addDemos(List list) { 16 | list.add(new ActivityInfo(TestNestedScrollFrameLayout.class, "TestNestedScrollFrameLayout")) ; 17 | list.add(new ActivityInfo(SimpleStickyNavigationDemo.class, "SimpleStickyNavigationDemo")) ; 18 | list.add(new ActivityInfo(MultiplexStickyNavigationDemo.class, "MultiplexStickyNavigationDemo")) ; 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/OnScrollChangeSupplier.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo; 2 | 3 | import com.heaven7.android.scroll.IScrollHelper; 4 | 5 | /** 6 | * Created by heaven7 on 2016/11/16. 7 | */ 8 | public interface OnScrollChangeSupplier { 9 | 10 | IScrollHelper.OnScrollChangeListener getOnScrollChangeListener(); 11 | } 12 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/StickyDelegateSupplier.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo; 2 | 3 | import com.heaven7.android.StickyLayout.StickyNavigationLayout; 4 | 5 | /** 6 | * Created by heaven7 on 2016/11/1. 7 | */ 8 | public interface StickyDelegateSupplier { 9 | 10 | StickyNavigationLayout.IStickyCallback getStickyDelegate(); 11 | } 12 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/demos/BaseActivity.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.demos; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.appcompat.app.AppCompatActivity; 6 | 7 | import butterknife.ButterKnife; 8 | 9 | /** 10 | * Created by heaven7 on 2016/11/15. 11 | */ 12 | public abstract class BaseActivity extends AppCompatActivity { 13 | 14 | @Override 15 | public void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(getLayoutId()); 18 | ButterKnife.bind(this); 19 | init(savedInstanceState); 20 | } 21 | 22 | protected abstract int getLayoutId(); 23 | 24 | protected abstract void init(Bundle savedInstanceState); 25 | } 26 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/demos/MultiplexStickyNavigationDemo.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.demos; 2 | 3 | import android.os.Bundle; 4 | import android.util.SparseArray; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.RadioGroup; 8 | 9 | import androidx.appcompat.app.AppCompatActivity; 10 | import androidx.drawerlayout.widget.DrawerLayout; 11 | 12 | import com.heaven7.android.scroll.IScrollHelper; 13 | import com.heaven7.android.sticky_navigation_layout.demo.OnScrollChangeSupplier; 14 | import com.heaven7.android.sticky_navigation_layout.demo.R; 15 | import com.heaven7.android.sticky_navigation_layout.demo.fragment.BaseFragment; 16 | import com.heaven7.android.sticky_navigation_layout.demo.fragment.StickyFragment; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | 21 | /** 22 | * this is a multiples demo of sticky . 23 | * Created by heaven7 on 2016/11/3. 24 | */ 25 | public class MultiplexStickyNavigationDemo extends AppCompatActivity implements OnScrollChangeSupplier{ 26 | 27 | 28 | @BindView(R.id.rg) 29 | RadioGroup mRg; 30 | 31 | @BindView(R.id.drawer_layout) 32 | DrawerLayout mDrawerLayout; 33 | 34 | @BindView(R.id.fl_content) 35 | ViewGroup mContent; 36 | 37 | private final SparseArray sCache = new SparseArray<>(6); 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | // setContentView(R.layout.activity_main2); 43 | setContentView(getLayoutInflater().inflate(R.layout.activity_main2, null)); 44 | ButterKnife.bind(this); 45 | 46 | sCache.put(R.id.rb_chuzhen, new StickyFragment()); 47 | sCache.put(R.id.rb_circle, new StickyFragment()); 48 | sCache.put(R.id.rb_first_page, new StickyFragment()); 49 | sCache.put(R.id.rb_msg, new StickyFragment()); 50 | 51 | mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); 52 | 53 | mRg.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() { 54 | @Override 55 | public void onCheckedChanged(RadioGroup group, int checkedId) { 56 | getSupportFragmentManager().beginTransaction() 57 | .replace(R.id.fl_content, sCache.get(checkedId),"StickyFragment") 58 | .commit(); 59 | } 60 | }); 61 | mRg.check(R.id.rb_chuzhen); 62 | } 63 | 64 | @Override 65 | public IScrollHelper.OnScrollChangeListener getOnScrollChangeListener() { 66 | return mScrollChangeListener; 67 | } 68 | private final IScrollHelper.OnScrollChangeListener mScrollChangeListener = new IScrollHelper.OnScrollChangeListener() { 69 | @Override 70 | public void onScrollStateChanged(View target, int state) { 71 | //TODO 72 | } 73 | @Override 74 | public void onScrolled(View target, int dx, int dy) { 75 | //TODO 76 | } 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/demos/SimpleStickyNavigationDemo.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.demos; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.google.android.material.snackbar.Snackbar; 11 | import com.heaven7.adapter.QuickRecycleViewAdapter; 12 | import com.heaven7.android.StickyLayout.StickyNavigationLayout; 13 | import com.heaven7.android.sticky_navigation_layout.demo.R; 14 | import com.heaven7.android.sticky_navigation_layout.demo.fragment.TabFragment; 15 | import com.heaven7.android.sticky_navigation_layout.demo.view.SimpleViewPagerIndicator; 16 | import com.heaven7.core.util.ViewHelper; 17 | 18 | import java.util.ArrayList; 19 | import java.util.List; 20 | 21 | import butterknife.BindView; 22 | 23 | /** 24 | * this is a simple demo of sticky navigation layout.. 25 | * Created by heaven7 on 2016/11/15. 26 | */ 27 | public class SimpleStickyNavigationDemo extends BaseActivity { 28 | 29 | @BindView(R.id.stickyLayout) 30 | StickyNavigationLayout mStickyNavLayout; 31 | 32 | @BindView(R.id.vp_indicator) 33 | SimpleViewPagerIndicator mIndicator; 34 | @BindView(R.id.rv) 35 | RecyclerView mRv_subscribe; 36 | 37 | @Override 38 | protected int getLayoutId() { 39 | return R.layout.activity_main; 40 | } 41 | 42 | @Override 43 | protected void init(Bundle savedInstanceState) { 44 | initEvents(); 45 | initDatas(); 46 | } 47 | 48 | private void initEvents() { 49 | } 50 | private void initDatas() { 51 | List mDatas = new ArrayList<>(); 52 | for (int i = 0; i < 50; i++) { 53 | mDatas.add(new TabFragment.Data(" title -> " + i)); 54 | } 55 | mRv_subscribe.setLayoutManager(new LinearLayoutManager(this)); 56 | mRv_subscribe.setAdapter(new QuickRecycleViewAdapter(R.layout.item, mDatas) { 57 | @Override 58 | protected void onBindData(Context context, int position, TabFragment.Data item, int itemLayoutId, ViewHelper helper) { 59 | helper.setText(R.id.id_info, item.title) 60 | .setRootOnClickListener(new View.OnClickListener() { 61 | @Override 62 | public void onClick(View v) { 63 | Snackbar.make(v, R.string.action_settings, Snackbar.LENGTH_LONG).show(); 64 | } 65 | }); 66 | } 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/demos/TestNestedScrollFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.demos; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.heaven7.android.sticky_navigation_layout.demo.R; 6 | 7 | /** 8 | * Created by heaven7 on 2016/11/16. 9 | */ 10 | public class TestNestedScrollFrameLayout extends BaseActivity { 11 | 12 | @Override 13 | protected int getLayoutId() { 14 | return R.layout.ac_test_nested_scroll_framelayout; 15 | } 16 | 17 | @Override 18 | protected void init(Bundle savedInstanceState) { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/extra/OnScrollListenerImpl.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.extra; 2 | 3 | import androidx.recyclerview.widget.GridLayoutManager; 4 | import androidx.recyclerview.widget.LinearLayoutManager; 5 | import androidx.recyclerview.widget.RecyclerView; 6 | import androidx.recyclerview.widget.StaggeredGridLayoutManager; 7 | 8 | import com.heaven7.adapter.RecyclerViewUtils; 9 | import com.heaven7.core.util.Logger; 10 | 11 | public class OnScrollListenerImpl extends RecyclerView.OnScrollListener { 12 | 13 | private static final String TAG = "OnScrollListenerImpl"; 14 | 15 | public void onScrollStateChanged(RecyclerView rv, int newState){ 16 | //dragging-> setting->idle and dragging->idle 17 | switch (newState){ 18 | case RecyclerView.SCROLL_STATE_DRAGGING: 19 | Logger.i(TAG, "SCROLL_STATE_DRAGGING"); 20 | break; 21 | 22 | case RecyclerView.SCROLL_STATE_SETTLING: 23 | Logger.i(TAG, "SCROLL_STATE_SETTLING"); 24 | // final int firstPos = findFirstVisibleItemPosition(rv); 25 | break; 26 | 27 | case RecyclerView.SCROLL_STATE_IDLE : 28 | Logger.i(TAG, "SCROLL_STATE_IDLE"); 29 | final int lastPos = RecyclerViewUtils.findLastVisibleItemPosition(rv); 30 | if(lastPos == RecyclerView.NO_POSITION){ 31 | Logger.i(TAG, "onScrollStateChanged", "can't find last position of RecyclerView."); 32 | return; 33 | } 34 | break; 35 | } 36 | } 37 | 38 | public void onScrolled(RecyclerView recyclerView, int dx, int dy){ 39 | 40 | } 41 | 42 | public static int findFirstVisibleItemPosition(RecyclerView rv) { 43 | RecyclerView.LayoutManager lm = rv.getLayoutManager(); 44 | int firstPos = RecyclerView.NO_POSITION; 45 | if (lm instanceof GridLayoutManager) { 46 | firstPos = ((GridLayoutManager) lm).findFirstVisibleItemPosition(); 47 | 48 | } else if (lm instanceof LinearLayoutManager) { 49 | firstPos = ((LinearLayoutManager) lm).findFirstVisibleItemPosition(); 50 | 51 | } else if (lm instanceof StaggeredGridLayoutManager) { 52 | int positions[] = ((StaggeredGridLayoutManager) lm).findFirstVisibleItemPositions(null); 53 | for (int pos : positions) { 54 | if (pos < firstPos) { 55 | firstPos = pos; 56 | } 57 | } 58 | } 59 | return firstPos; 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | 12 | import com.heaven7.core.util.Toaster; 13 | 14 | import butterknife.ButterKnife; 15 | 16 | public abstract class BaseFragment extends Fragment { 17 | 18 | private Toaster mToaster; 19 | 20 | @Nullable 21 | @Override 22 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 23 | return inflater.inflate(getLayoutId(), container, false); 24 | } 25 | 26 | @Override 27 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 28 | super.onViewCreated(view, savedInstanceState); 29 | ButterKnife.bind(this, view); 30 | mToaster = new Toaster(view.getContext()); 31 | init(view.getContext(), savedInstanceState); 32 | } 33 | 34 | public Toaster getToaster() { 35 | return mToaster; 36 | } 37 | 38 | @Override 39 | public void onDestroyView() { 40 | super.onDestroyView(); 41 | } 42 | 43 | 44 | protected abstract int getLayoutId(); 45 | 46 | protected abstract void init(Context context, Bundle savedInstanceState); 47 | } 48 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/fragment/StickyFragment.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.LinearLayout; 8 | 9 | import androidx.fragment.app.Fragment; 10 | import androidx.fragment.app.FragmentPagerAdapter; 11 | import androidx.recyclerview.widget.LinearLayoutManager; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | import androidx.viewpager.widget.ViewPager; 14 | 15 | import com.google.android.material.snackbar.Snackbar; 16 | import com.heaven7.adapter.QuickRecycleViewAdapter; 17 | import com.heaven7.android.StickyLayout.StickyNavigationLayout; 18 | import com.heaven7.android.sticky_navigation_layout.demo.OnScrollChangeSupplier; 19 | import com.heaven7.android.sticky_navigation_layout.demo.R; 20 | import com.heaven7.android.sticky_navigation_layout.demo.StickyDelegateSupplier; 21 | import com.heaven7.android.sticky_navigation_layout.demo.view.SimpleViewPagerIndicator; 22 | import com.heaven7.core.util.Logger; 23 | import com.heaven7.core.util.ViewHelper; 24 | 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import butterknife.BindView; 29 | 30 | /** 31 | * Created by heaven7 on 2016/11/3. 32 | */ 33 | public class StickyFragment extends BaseFragment { 34 | 35 | private static final String TAG = "StickyFragment"; 36 | 37 | private static final int MODE_FEED = 1; 38 | private static final int MODE_SUBSCRIBE = 2; 39 | private int mMode = MODE_FEED; 40 | 41 | @BindView(R.id.stickyLayout) 42 | StickyNavigationLayout mStickyNavLayout; 43 | 44 | @BindView(R.id.vp_indicator) 45 | SimpleViewPagerIndicator mIndicator; 46 | @BindView(R.id.vp) 47 | ViewPager mViewPager; 48 | @BindView(R.id.top_view) 49 | View mTopView; 50 | 51 | @BindView(R.id.fl_subscribe) 52 | ViewGroup mVg_subscribe; 53 | @BindView(R.id.ll_indicator) 54 | LinearLayout mLl_indicator; 55 | 56 | @BindView(R.id.rv_subscribe) 57 | RecyclerView mRv_subscribe; 58 | @BindView(R.id.rv_tabs) 59 | RecyclerView mRv_tabs; 60 | 61 | private String[] mTitles = new String[] { "简介", "评价", "相关" }; 62 | private TabFragment[] mFragments = new TabFragment[mTitles.length]; 63 | private FragmentPagerAdapter mAdapter; 64 | private QuickRecycleViewAdapter mSubscribeAdapter; 65 | 66 | 67 | @Override 68 | protected int getLayoutId() { 69 | return R.layout.frag_sticky_layout; 70 | } 71 | 72 | @Override 73 | protected void init(Context context, Bundle savedInstanceState) { 74 | initEvents(); 75 | initDatas(); 76 | } 77 | 78 | private void initEvents() { 79 | mTopView.setOnClickListener(new View.OnClickListener() { 80 | @Override 81 | public void onClick(View v) { 82 | switchMode(); 83 | // Snackbar.make(v, R.string.action_settings, Snackbar.LENGTH_LONG).show(); 84 | } 85 | }); 86 | mViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 87 | /** 88 | * position : 中的position 89 | * positionOffset: 当前页面偏移的百分比 90 | * positionOffsetPixels: 当前页面偏移的像素位置 91 | */ 92 | @Override 93 | public void onPageScrolled(int position, float positionOffset, 94 | int positionOffsetPixels) { // 会多次调用 95 | mIndicator.scroll(position, positionOffset); 96 | } 97 | }); 98 | if( getContext() instanceof OnScrollChangeSupplier){ 99 | mStickyNavLayout.addOnScrollChangeListener(((OnScrollChangeSupplier) getContext()).getOnScrollChangeListener()); 100 | } 101 | } 102 | 103 | private void switchMode() { 104 | if(mMode == MODE_FEED){ 105 | mMode = MODE_SUBSCRIBE; 106 | mLl_indicator.setVisibility(View.GONE); 107 | mViewPager.setVisibility(View.GONE); 108 | mVg_subscribe.setVisibility(View.VISIBLE); 109 | // mStickyNavLayout.setEnableStickyTouch(false); 110 | Logger.i(TAG, "switchMode" , "to mode: MODE_SUBSCRIBE"); 111 | }else{ 112 | mMode = MODE_FEED; 113 | mVg_subscribe.setVisibility(View.GONE); 114 | mLl_indicator.setVisibility(View.VISIBLE); 115 | mViewPager.setVisibility(View.VISIBLE); 116 | // mStickyNavLayout.setEnableStickyTouch(true); 117 | Logger.i(TAG, "switchMode" , "to mode: MODE_FEED"); 118 | } 119 | } 120 | 121 | private void initDatas() { 122 | List mDatas = new ArrayList<>(); 123 | for (int i = 0; i < 50; i++) { 124 | mDatas.add(new TabFragment.Data(" title -> " + i)); 125 | } 126 | mRv_subscribe.setLayoutManager(new LinearLayoutManager(getContext())); 127 | mRv_subscribe.setAdapter(mSubscribeAdapter = new QuickRecycleViewAdapter(R.layout.item, mDatas) { 128 | @Override 129 | protected void onBindData(Context context, int position, TabFragment.Data item, int itemLayoutId, ViewHelper helper) { 130 | helper.setText(R.id.id_info, item.title) 131 | .setRootOnClickListener(new View.OnClickListener() { 132 | @Override 133 | public void onClick(View v) { 134 | Snackbar.make(v, R.string.action_settings, Snackbar.LENGTH_LONG).show(); 135 | } 136 | }); 137 | } 138 | }); 139 | 140 | mIndicator.setTitles(mTitles); 141 | for (int i = 0; i < mTitles.length; i++) { 142 | mFragments[i] = TabFragment.newInstance(mTitles[i]); 143 | } 144 | mAdapter = new FragmentPagerAdapter(getChildFragmentManager()) { 145 | @Override 146 | public int getCount() { 147 | return mTitles.length; 148 | } 149 | @Override 150 | public Fragment getItem(int position) { 151 | return mFragments[position]; 152 | } 153 | }; 154 | mViewPager.setAdapter(mAdapter); 155 | mViewPager.setCurrentItem(0); 156 | mStickyNavLayout.addStickyDelegate(mStickyDelegate); 157 | 158 | mRv_tabs.setLayoutManager(new LinearLayoutManager(getContext(), LinearLayoutManager.HORIZONTAL, false)); 159 | mRv_tabs.setAdapter(new QuickRecycleViewAdapter(R.layout.item_hor_tab, mDatas) { 160 | @Override 161 | protected void onBindData(Context context, int position, TabFragment.Data item, int itemLayoutId, ViewHelper helper) { 162 | helper.setText(R.id.tv_tab, item.title) 163 | .setRootOnClickListener(new View.OnClickListener() { 164 | @Override 165 | public void onClick(View v) { 166 | Snackbar.make(v, R.string.action_settings, Snackbar.LENGTH_LONG).show(); 167 | } 168 | }); 169 | } 170 | }); 171 | } 172 | 173 | private StickyNavigationLayout.IStickyCallback getChildStickyDelegate() { 174 | final Fragment item = mAdapter.getItem(mViewPager.getCurrentItem()); 175 | if(item instanceof StickyDelegateSupplier) { 176 | return ((StickyDelegateSupplier) item).getStickyDelegate(); 177 | } 178 | Logger.i(TAG, "getChildStickyDelegate", "can't find ChildStickyDelegate."); 179 | return null; 180 | } 181 | 182 | private final StickyNavigationLayout.IStickyCallback mStickyDelegate = new StickyNavigationLayout.IStickyCallback() { 183 | @Override 184 | public void afterOnMeasure(StickyNavigationLayout snv, View top, View indicator, View contentView) { 185 | final ViewGroup.LayoutParams lp = mVg_subscribe.getLayoutParams(); 186 | lp.height = snv.getMeasuredHeight() - snv.getScrollY(); 187 | mVg_subscribe.setLayoutParams(lp); 188 | // Logger.i(TAG, "afterOnMeasure" , "mVg_subscribe: height = " + lp.height +" ,snv.scrollY = " + snv.getScrollY()); 189 | } 190 | }; 191 | } 192 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/fragment/TabFragment.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.fragment; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | import androidx.recyclerview.widget.LinearLayoutManager; 12 | import androidx.recyclerview.widget.RecyclerView; 13 | import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; 14 | 15 | import com.google.android.material.snackbar.Snackbar; 16 | import com.heaven7.adapter.BaseSelector; 17 | import com.heaven7.adapter.QuickRecycleViewAdapter; 18 | import com.heaven7.android.sticky_navigation_layout.demo.R; 19 | import com.heaven7.core.util.ViewHelper; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | 24 | public class TabFragment extends Fragment { 25 | 26 | public static final String TITLE = "title"; 27 | 28 | private String mTitle = "Defaut Value"; 29 | private RecyclerView mRecyclerView; 30 | private List mDatas = new ArrayList(); 31 | 32 | private SwipeRefreshLayout swipeView ; 33 | private QuickRecycleViewAdapter mAdapter; 34 | 35 | @Override 36 | public void onCreate(Bundle savedInstanceState) { 37 | super.onCreate(savedInstanceState); 38 | if (getArguments() != null) { 39 | mTitle = getArguments().getString(TITLE); 40 | } 41 | } 42 | 43 | @Override 44 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 45 | Bundle savedInstanceState) { 46 | View view = inflater.inflate(R.layout.fragment_tab, container, false); 47 | mRecyclerView = (RecyclerView) view.findViewById(R.id.rv); 48 | mRecyclerView.setLayoutManager(new LinearLayoutManager(container.getContext())); 49 | // mDelegate = new StickyNavigationLayout.RecyclerViewStickyDelegate(mRecyclerView); 50 | 51 | swipeView = (SwipeRefreshLayout) view.findViewById(R.id.swipe); 52 | swipeView.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { 53 | @Override 54 | public void onRefresh() { 55 | swipeView.postDelayed(new Runnable() { 56 | @Override 57 | public void run() { 58 | swipeView.setRefreshing(false); 59 | } 60 | }, 2000); 61 | } 62 | }); 63 | for (int i = 0; i < 50; i++) { 64 | mDatas.add(new Data(mTitle + " -> " + i)); 65 | } 66 | mRecyclerView.setAdapter(mAdapter = new QuickRecycleViewAdapter(R.layout.item, mDatas) { 67 | @Override 68 | protected void onBindData(Context context, int position, Data item, int itemLayoutId, ViewHelper helper) { 69 | helper.setText(R.id.id_info, item.title) 70 | .setRootOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | Snackbar.make(v, R.string.action_settings, Snackbar.LENGTH_LONG).show(); 74 | } 75 | }); 76 | } 77 | }); 78 | return view; 79 | } 80 | 81 | @Override 82 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 83 | super.onViewCreated(view, savedInstanceState); 84 | } 85 | 86 | public static TabFragment newInstance(String title) { 87 | TabFragment tabFragment = new TabFragment(); 88 | Bundle bundle = new Bundle(); 89 | bundle.putString(TITLE, title); 90 | tabFragment.setArguments(bundle); 91 | return tabFragment; 92 | } 93 | 94 | public static class Data extends BaseSelector { 95 | public String title; 96 | 97 | public Data(String title) { 98 | this.title = title; 99 | } 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/view/RadioButtonPoint.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Paint; 6 | import android.graphics.drawable.Drawable; 7 | import android.util.AttributeSet; 8 | import android.view.View; 9 | import android.widget.RadioButton; 10 | import android.widget.TextView; 11 | 12 | public class RadioButtonPoint extends RadioButton { 13 | public static final int STATUS_UNREAD_NUMBER = 1; 14 | public static final int STATUS_RED_POINT = 2; 15 | public static final int STATUS_CLEAR = 3; 16 | private int mStatus = STATUS_CLEAR; 17 | private int mUnreadNumber; 18 | 19 | private Paint mCirclePaint; 20 | private int mCircleLeft; 21 | private int mRadius; 22 | private int mColor; 23 | 24 | private TextView mTextView; 25 | 26 | public RadioButtonPoint(Context context) { 27 | super(context); 28 | init(null, 0); 29 | } 30 | 31 | public RadioButtonPoint(Context context, AttributeSet attrs) { 32 | super(context, attrs); 33 | init(attrs, 0); 34 | } 35 | 36 | public RadioButtonPoint(Context context, AttributeSet attrs, int defStyleAttr) { 37 | super(context, attrs, defStyleAttr); 38 | init(attrs, defStyleAttr); 39 | } 40 | 41 | 42 | private void init(AttributeSet attrs, int defStyle) { 43 | /* final TypedArray a = getContext().obtainStyledAttributes( 44 | attrs, R.styleable.RadioButtonPoint, defStyle, 0); 45 | mRadius = a.getDimensionPixelSize(R.styleable.RadioButtonPoint_circle_radius, DimenUtil.dip2px(4)); 46 | mColor = a.getColor(R.styleable.RadioButtonPoint_circle_color, 0xfffe412d); 47 | a.recycle();*/ 48 | 49 | mCirclePaint = new Paint(); 50 | mCirclePaint.setColor(mColor); 51 | mCirclePaint.setAntiAlias(true); 52 | } 53 | 54 | @Override 55 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 56 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 57 | final Drawable[] compoundDrawables = getCompoundDrawables(); 58 | int drawableWidth = 0; 59 | if (null != compoundDrawables) { 60 | drawableWidth = compoundDrawables[1].getIntrinsicWidth(); 61 | } 62 | mCircleLeft = (getMeasuredWidth() + drawableWidth) / 2; 63 | } 64 | 65 | @Override 66 | protected void onDraw(Canvas canvas) { 67 | super.onDraw(canvas); 68 | 69 | switch (mStatus) { 70 | case STATUS_UNREAD_NUMBER: 71 | if (mUnreadNumber > 0 && mTextView != null) { 72 | mTextView.setVisibility(View.VISIBLE); 73 | mTextView.setText(mUnreadNumber + ""); 74 | } 75 | break; 76 | 77 | case STATUS_RED_POINT: 78 | canvas.drawCircle(mCircleLeft, mRadius, mRadius, mCirclePaint); 79 | 80 | if (mTextView != null) { 81 | mTextView.setVisibility(View.GONE); 82 | } 83 | break; 84 | 85 | case STATUS_CLEAR: 86 | if (mTextView != null) { 87 | mTextView.setVisibility(View.GONE); 88 | } 89 | break; 90 | 91 | default: 92 | if (mTextView != null) { 93 | mTextView.setVisibility(View.GONE); 94 | } 95 | break; 96 | 97 | } 98 | } 99 | 100 | public void updateStatus(int status, int unreadNumber) { 101 | if (unreadNumber > 99) { 102 | unreadNumber = 99; 103 | } 104 | mStatus = status; 105 | mUnreadNumber = unreadNumber; 106 | invalidate(); 107 | } 108 | 109 | public void updateStatus(int status) { 110 | updateStatus(status, 0); 111 | } 112 | 113 | public void setUnreadNumberTextView(TextView textView) { 114 | mTextView = textView; 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/java/com/heaven7/android/sticky_navigation_layout/demo/view/SimpleViewPagerIndicator.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.sticky_navigation_layout.demo.view; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.util.AttributeSet; 8 | import android.util.TypedValue; 9 | import android.view.Gravity; 10 | import android.widget.LinearLayout; 11 | import android.widget.TextView; 12 | 13 | public class SimpleViewPagerIndicator extends LinearLayout { 14 | 15 | private static final int COLOR_TEXT_NORMAL = 0xFF000000; 16 | private static final int COLOR_INDICATOR_COLOR = Color.GREEN; 17 | 18 | private String[] mTitles; 19 | private int mTabCount; 20 | private int mIndicatorColor = COLOR_INDICATOR_COLOR; 21 | private float mTranslationX; 22 | private Paint mPaint = new Paint(); 23 | private int mTabWidth; 24 | 25 | public SimpleViewPagerIndicator(Context context) { 26 | this(context, null); 27 | } 28 | 29 | public SimpleViewPagerIndicator(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | mPaint.setColor(mIndicatorColor); 32 | mPaint.setStrokeWidth(9.0F); 33 | } 34 | 35 | @Override 36 | protected void onSizeChanged(int w, int h, int oldw, int oldh) { 37 | super.onSizeChanged(w, h, oldw, oldh); 38 | if (mTabCount != 0) { 39 | mTabWidth = w / mTabCount; 40 | } 41 | } 42 | 43 | public void setTitles(String[] titles) { 44 | mTitles = titles; 45 | mTabCount = titles.length; 46 | generateTitleView(); 47 | } 48 | 49 | public void setIndicatorColor(int indicatorColor) { 50 | this.mIndicatorColor = indicatorColor; 51 | } 52 | 53 | @Override 54 | protected void dispatchDraw(Canvas canvas) { 55 | super.dispatchDraw(canvas); 56 | canvas.save(); 57 | canvas.translate(mTranslationX, getHeight() - 2); 58 | canvas.drawLine(0, 0, mTabWidth, 0, mPaint); 59 | canvas.restore(); 60 | } 61 | 62 | public void scroll(int position, float offset) { 63 | /** 64 | *
65 | 		 *  0-1:position=0 ;1-0:postion=0;
66 | 		 * 
67 | */ 68 | // System.out.println("offset = "+offset); //使得指示线可以平滑的 滑动过去 69 | mTranslationX = getWidth() / mTabCount * (position + offset); 70 | invalidate(); 71 | } 72 | 73 | private void generateTitleView() { 74 | if (getChildCount() > 0) 75 | this.removeAllViews(); 76 | int count = mTitles.length; 77 | 78 | setWeightSum(count); 79 | for (int i = 0; i < count; i++) { 80 | final TextView tv = new TextView(getContext()); 81 | final String title = mTitles[i]; 82 | LayoutParams lp = new LayoutParams(0, LayoutParams.MATCH_PARENT); 83 | lp.weight = 1; 84 | tv.setGravity(Gravity.CENTER); 85 | tv.setTextColor(COLOR_TEXT_NORMAL); 86 | tv.setText(title); 87 | tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, 16); 88 | tv.setLayoutParams(lp); 89 | addView(tv); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/ac_test_nested_scroll_framelayout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 16 | 17 | 24 | 31 | 38 | 45 | 46 | 47 | 57 | 58 | 64 | 65 | 74 | 75 | 76 | 81 | 82 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/activity_main2.xml: -------------------------------------------------------------------------------- 1 | 10 | 11 | 16 | 17 | 22 | 23 | 33 | 34 | 41 | 48 | 55 | 62 | 63 | 64 | 65 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/frag_sticky_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 17 | 18 | 23 | 24 | 33 | 34 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 55 | 56 | 64 | 65 | 69 | 70 | 71 | 72 | 73 | 79 | 80 | 85 | 86 | 90 | 91 | 92 | 96 | 97 | 98 | 99 | 100 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/fragment_tab.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/item.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/layout/item_hor_tab.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-xhdpi/bg_shadow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-xhdpi/bg_shadow.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Android-sticky-navigation-layout 3 | 4 | tab_首页 5 | tab_出诊 6 | tab_圈子 7 | tab_消息 8 | tab_我的 9 | 10 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/backup/bintrayUpload.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.jfrog.bintray' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | 4 | //version = libraryVersion 5 | 6 | // load properties 7 | Properties properties = new Properties() 8 | File localPropertiesFile = project.file("local.properties"); 9 | if(localPropertiesFile.exists()){ 10 | properties.load(localPropertiesFile.newDataInputStream()) 11 | } 12 | File projectPropertiesFile = project.file("project.properties"); 13 | if(projectPropertiesFile.exists()){ 14 | properties.load(projectPropertiesFile.newDataInputStream()) 15 | } 16 | 17 | // read properties 18 | def projectName = properties.getProperty("project.name") 19 | def projectGroupId = properties.getProperty("project.groupId") 20 | def projectArtifactId = properties.getProperty("project.artifactId") 21 | def projectVersionName = android.defaultConfig.versionName 22 | def projectPackaging = properties.getProperty("project.packaging") 23 | def projectSiteUrl = properties.getProperty("project.siteUrl") 24 | def projectGitUrl = projectSiteUrl + ".git" 25 | def projectDesc = properties.getProperty("project.desc") 26 | 27 | def licenseName = properties.getProperty("project.liscenseName") 28 | def licenseUrl = properties.getProperty("project.licenseUrl") 29 | 30 | 31 | def githubRepository = properties.getProperty("project.githubRepo") 32 | 33 | def developerId = properties.getProperty("developer.id") 34 | def developerName = properties.getProperty("developer.name") 35 | def developerEmail = properties.getProperty("developer.email") 36 | 37 | def bintrayUser = properties.getProperty("bintray.user") 38 | def bintrayApikey = properties.getProperty("bintray.apikey") 39 | 40 | def javadocName = properties.getProperty("javadoc.name") 41 | def bintrayRepo = "maven" 42 | def projectLicenses = ["Apache-2.0"] 43 | 44 | group = projectGroupId // Maven Group ID for the artifact 45 | //------------------------------ install ------------------------------------// 46 | 47 | install { 48 | repositories.mavenInstaller { 49 | // This generates POM.xml with proper parameters 50 | pom { 51 | project { 52 | packaging projectPackaging 53 | groupId projectGroupId 54 | artifactId projectArtifactId 55 | 56 | // Add your description here 57 | name projectName 58 | description projectDesc 59 | version projectVersionName 60 | url projectSiteUrl 61 | 62 | // Set your license 63 | licenses { 64 | license { 65 | name licenseName 66 | url licenseUrl 67 | } 68 | } 69 | developers { 70 | developer { 71 | id developerId 72 | name developerName 73 | email developerEmail 74 | } 75 | } 76 | scm { 77 | connection projectGitUrl 78 | developerConnection projectGitUrl 79 | url projectSiteUrl 80 | 81 | } 82 | } 83 | } 84 | } 85 | } 86 | 87 | //-------------------------------------------------------------- 88 | 89 | if (project.hasProperty("android")) { // Android libraries 90 | task sourcesJar(type: Jar) { 91 | classifier = 'sources' 92 | from android.sourceSets.main.java.srcDirs 93 | } 94 | 95 | task javadoc(type: Javadoc) { 96 | source = android.sourceSets.main.java.srcDirs 97 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) 98 | } 99 | } else { // Java libraries 100 | task sourcesJar(type: Jar, dependsOn: classes) { 101 | classifier = 'sources' 102 | from sourceSets.main.allSource 103 | } 104 | } 105 | 106 | task javadocJar(type: Jar, dependsOn: javadoc) { 107 | classifier = 'javadoc' 108 | from javadoc.destinationDir 109 | } 110 | 111 | artifacts { 112 | archives javadocJar 113 | archives sourcesJar 114 | } 115 | // javadoc configuration 116 | javadoc { 117 | options{ 118 | encoding "UTF-8" 119 | charSet 'UTF-8' 120 | author true 121 | version projectVersionName 122 | links "http://docs.oracle.com/javase/7/docs/api" 123 | title javadocName 124 | } 125 | } 126 | 127 | // ---------------- Bintray ------------------------------------- 128 | 129 | //Properties properties = new Properties() 130 | //properties.load(project.file('local.properties').newDataInputStream()) 131 | 132 | bintray { 133 | user = bintrayUser 134 | key = bintrayApikey 135 | 136 | configurations = ['archives'] 137 | pkg { 138 | repo = bintrayRepo 139 | name = projectName 140 | desc = projectDesc 141 | websiteUrl = projectSiteUrl 142 | issueTrackerUrl = projectSiteUrl + '/issues' 143 | vcsUrl = projectGitUrl 144 | licenses = projectLicenses 145 | githubRepo = githubRepository //Optional Github repository 146 | githubReleaseNotesFile = 'README.md' //Optional Github readme file 147 | publish = false // can't direct publish now. bintray for secure? 148 | publicDownloadNumbers = true 149 | version { 150 | desc = projectDesc 151 | gpg { 152 | sign = true //Determines whether to GPG sign the files. The default is false 153 | passphrase = properties.getProperty("bintray.gpg.password") 154 | //Optional. The passphrase for GPG signing' 155 | } 156 | } 157 | } 158 | } 159 | 160 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/backup/demo.txt: -------------------------------------------------------------------------------- 1 | private final StickyNavigationLayout.IStickyDelegate mStickyDelegate = new StickyNavigationLayout.IStickyDelegate() { 2 | @Override 3 | public void afterOnMeasure(StickyNavigationLayout snv, View top, View indicator, View contentView) { 4 | 5 | final ViewGroup.LayoutParams lp = mfL_subscribe_parent.getLayoutParams(); 6 | if (mMode == MODE_HOME_SUBCRIBE && snv.getScrollY() != 0) { 7 | lp.height = snv.getMeasuredHeight() ; 8 | }else{ 9 | int bottomHeight = snv.getResources().getDimensionPixelSize(R.dimen.content_bottom_height); 10 | lp.height = contentView.getMeasuredHeight() + bottomHeight; 11 | } 12 | Logger.w(TAG, "afterOnMeasure", "mVg_subscribe: height = " + lp.height + " ,snv.scrollY = " + snv.getScrollY()); 13 | Logger.w(TAG, "afterOnMeasure", "rv: " + mRv_departments.getMeasuredHeight()); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/backup/frag_home_handpick.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 24 | 25 | 31 | 32 | 38 | 39 | 51 | 52 | 53 | 54 | 62 | 63 | 72 | 73 | 81 | 82 | 93 | 94 | 95 | 96 | 101 | 102 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 118 | 119 | 125 | 126 | 136 | 137 | 138 | 145 | 146 | 147 | 148 | 152 | 153 | 154 | 155 | 169 | 170 | 171 | 172 | 181 | 182 | 191 | 192 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/backup/project.properties: -------------------------------------------------------------------------------- 1 | #project 2 | project.name = sticky-navigation-layout 3 | project.groupId = com.heaven7.android.StickyLayout 4 | project.artifactId = sticky-navigation-layout 5 | project.packaging = aar 6 | project.siteUrl = https://github.com/LightSun/Android-sticky-navigation-layout 7 | project.githubRepo = LightSun/Android-sticky-navigation-layout 8 | #new 9 | project.desc = a lib of android sticky navigation layout. 10 | project.liscenseName = 'The Apache Software License, Version 2.0' 11 | project.licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 12 | 13 | 14 | #javadoc 15 | javadoc.name=sticky-navigation-layout 16 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | library_version = "1.0.4" 6 | } 7 | repositories { 8 | google() 9 | jcenter() 10 | } 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.4.2' //2.2 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:2.1' 14 | 15 | // in the individual module build.gradle files 16 | // classpath 'com.novoda:bintray-release:0.8.0' 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | jcenter() 24 | maven { url "https://jitpack.io" } 25 | } 26 | // buildDir = "D:/tmp/${rootProject.name}/${project.name}" // conflict with butterknife 27 | //for bintray so Disable javadoc check for Bintray upload 28 | tasks.withType(Javadoc) { 29 | // options.addStringOption('Xdoclint:none', '-quiet') 30 | options.addStringOption('X', '-quiet') 31 | options.addStringOption('encoding', 'UTF-8') 32 | options.addStringOption('charSet', 'UTF-8') 33 | } 34 | } 35 | 36 | task clean(type: Delete) { 37 | delete rootProject.buildDir 38 | } 39 | 40 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableAapt2=false 20 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/Android-stick-navigation-layout/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Android-stick-navigation-layout/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Dec 22 15:41:03 CST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip 7 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/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 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/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 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', 2 | ':sticky-navigation-layout', 3 | ':android-nestedscroll' 4 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.github.dcendents.android-maven' 3 | apply from: '../androidx_compat.gradle' 4 | 5 | group = 'com.github.LightSun.Android-sticky-navigation-layout' 6 | version = library_version 7 | 8 | android { 9 | compileSdkVersion 28 10 | buildToolsVersion '28.0.3' 11 | 12 | defaultConfig { 13 | minSdkVersion 19 14 | targetSdkVersion 28 15 | versionCode 102 16 | versionName "1.0.2" 17 | } 18 | buildTypes { 19 | release { 20 | minifyEnabled false 21 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | lintOptions{ 25 | abortOnError false 26 | checkReleaseBuilds false 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | androidTestImplementation 'junit:junit:4.12' 33 | //implementation 'com.android.support:appcompat-v7:26.1.0' 34 | 35 | //compile 'com.android.support:design:24.2.1' 36 | //compile 'com.heaven7.core.util:util-v1:1.1.2' 37 | 38 | //implementation 'com.heaven7.android.scroll:android-nestedscroll:1.0' 39 | implementation project(":android-nestedscroll") 40 | } 41 | 42 | //------------ multi lib >>> ------ 43 | task sourcesJar(type: Jar) { 44 | //classifier = 'sources' 45 | getArchiveClassifier().set('sources') 46 | from android.sourceSets.main.javaDirectories 47 | } 48 | artifacts { 49 | archives sourcesJar 50 | } 51 | if (android.productFlavors.size() > 0) { 52 | android.libraryVariants.all { variant -> 53 | if (variant.name.toLowerCase().contains("debug")) { 54 | return 55 | } 56 | 57 | def bundleTask = tasks["bundle${variant.name.capitalize()}"] 58 | 59 | artifacts { 60 | archives(bundleTask.archivePath) { 61 | classifier variant.flavorName 62 | builtBy bundleTask 63 | name = project.name 64 | } 65 | } 66 | 67 | } 68 | } 69 | //------------ multi lib <<< ------ -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/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 D:\study\android\sdk2/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 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/src/main/java/com/heaven7/android/StickyLayout/NestedScrollFrameLayout.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.StickyLayout; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.view.MotionEvent; 7 | import android.view.View; 8 | import android.widget.FrameLayout; 9 | 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.Nullable; 12 | import androidx.core.view.NestedScrollingChild; 13 | import androidx.core.view.NestedScrollingChild2; 14 | import androidx.core.view.NestedScrollingChild3; 15 | import androidx.core.view.NestedScrollingChildHelper; 16 | import androidx.core.view.NestedScrollingParent; 17 | import androidx.core.view.NestedScrollingParentHelper; 18 | import androidx.core.view.ViewCompat; 19 | 20 | import com.heaven7.android.scroll.IScrollHelper; 21 | import com.heaven7.android.scroll.NestedScrollFactory; 22 | import com.heaven7.android.scroll.NestedScrollHelper; 23 | 24 | /** 25 | * this is a a child of FrameLayout, but can nested as {@link NestedScrollingChild} or {@link NestedScrollingParent}. 26 | * it can scroll in vertical. 27 | * Created by heaven7 on 2016/11/14. 28 | * @attr ref R.styleable#NestedScrollFrameLayout_nsfl_max_percent 29 | * @attr ref R.styleable#NestedScrollFrameLayout_nsfl_orientation 30 | */ 31 | public class NestedScrollFrameLayout extends FrameLayout implements NestedScrollingChild, 32 | /* NestedScrollingChild2, NestedScrollingChild3,*/ 33 | NestedScrollingParent { 34 | 35 | private static final String TAG = NestedScrollFrameLayout.class.getSimpleName(); 36 | 37 | public static final int HORIZONTAL = 0; 38 | public static final int VERTICAL = 1; 39 | 40 | private NestedScrollingParentHelper mNestedScrollingParentHelper; 41 | private NestedScrollingChildHelper mNestedScrollingChildHelper; 42 | private NestedScrollHelper mNestedHelper; 43 | 44 | private int[] mParentScrollConsumed = new int[2]; 45 | private final int[] mParentOffsetInWindow = new int[2]; 46 | private float mMaxPercent = 1f; 47 | 48 | public NestedScrollFrameLayout(Context context) { 49 | this(context, null); 50 | } 51 | 52 | public NestedScrollFrameLayout(Context context, AttributeSet attrs) { 53 | this(context, attrs, 0); 54 | } 55 | 56 | public NestedScrollFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 57 | super(context, attrs, defStyleAttr); 58 | init(context, attrs); 59 | } 60 | 61 | private void init(Context context, AttributeSet attrs) { 62 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NestedScrollFrameLayout); 63 | final int orientation; 64 | try { 65 | //just compat old 66 | mMaxPercent = a.getFloat(R.styleable.NestedScrollFrameLayout_nsfl_max_y_percent, 1f); 67 | if(mMaxPercent == 1f){ 68 | mMaxPercent = a.getFloat(R.styleable.NestedScrollFrameLayout_nsfl_max_percent, 1f); 69 | } 70 | orientation = a.getInt(R.styleable.NestedScrollFrameLayout_nsfl_orientation, VERTICAL); 71 | }finally { 72 | a.recycle(); 73 | } 74 | 75 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 76 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); 77 | mNestedHelper = NestedScrollFactory.create(this, new NestedScrollHelper.NestedScrollCallback() { 78 | @Override 79 | public boolean canScrollHorizontally(View target) { 80 | return orientation == HORIZONTAL; 81 | } 82 | @Override 83 | public boolean canScrollVertically(View target) { 84 | return orientation == VERTICAL; 85 | } 86 | @Override 87 | public int getMaximumYScrollDistance(View target) { 88 | return (int) (target.getHeight() * mMaxPercent); 89 | } 90 | @Override 91 | public int getMaximumXScrollDistance(View target) { 92 | return (int) (target.getWidth() * mMaxPercent); 93 | } 94 | }); 95 | setNestedScrollingEnabled(true); 96 | } 97 | /** 98 | * set the max y percent, default is 1. it is also can assign in xml config. 99 | * @param maxYPercent the max y percent 100 | */ 101 | public void setMaximumPercent(float maxYPercent){ 102 | this.mMaxPercent = maxYPercent; 103 | } 104 | 105 | /** 106 | * get the max y percent , it is used in scroll . 107 | * @return the max y percent 108 | */ 109 | public float getMaximumPercent(){ 110 | return this.mMaxPercent ; 111 | } 112 | /** 113 | *

Use {@linkplain #setMaximumPercent(float)} instead

114 | * set the max y percent, default is 1. it is also can assign in xml config. 115 | * @param maxYPercent the max y percent 116 | */ 117 | @Deprecated 118 | public void setMaximumYPercent(float maxYPercent){ 119 | this.mMaxPercent = maxYPercent; 120 | } 121 | 122 | /** 123 | *

Use {@linkplain #getMaximumPercent()} instead

124 | * get the max y percent , it is used in scroll . 125 | * @return the max y percent 126 | */ 127 | @Deprecated 128 | public float getMaximumYPercent(){ 129 | return this.mMaxPercent ; 130 | } 131 | 132 | /** 133 | * Return the current scrolling state of the RecyclerView. 134 | * 135 | * @return {@link com.heaven7.android.scroll.IScrollHelper#SCROLL_STATE_IDLE}, {@link com.heaven7.android.scroll.IScrollHelper#SCROLL_STATE_DRAGGING} or 136 | * {@link com.heaven7.android.scroll.IScrollHelper#SCROLL_STATE_SETTLING} 137 | */ 138 | public int getScrollState() { 139 | return mNestedHelper.getScrollState(); 140 | } 141 | 142 | /** 143 | * add a scroll change listener. 144 | * @param l the OnScrollChangeListener 145 | */ 146 | public void addOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 147 | mNestedHelper.addOnScrollChangeListener(l); 148 | } 149 | 150 | /** 151 | * remove a scroll change listener. 152 | * @param l the OnScrollChangeListener 153 | */ 154 | public void removeOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 155 | mNestedHelper.removeOnScrollChangeListener(l); 156 | } 157 | 158 | /** 159 | * judge if has the target OnScrollChangeListener. 160 | * @param l the OnScrollChangeListener 161 | * @return true if has the target OnScrollChangeListener 162 | */ 163 | public boolean hasOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 164 | return mNestedHelper.hasOnScrollChangeListener(l); 165 | } 166 | 167 | 168 | @Override 169 | public boolean onInterceptTouchEvent(MotionEvent ev) { 170 | if(!isNestedScrollingEnabled()){ 171 | return super.onInterceptTouchEvent(ev); 172 | } 173 | return mNestedHelper.onInterceptTouchEvent(ev); 174 | } 175 | 176 | @Override 177 | public boolean onTouchEvent(MotionEvent event) { 178 | if(!isNestedScrollingEnabled()){ 179 | return super.onTouchEvent(event); 180 | } 181 | return mNestedHelper.onTouchEvent(event); 182 | } 183 | @Override 184 | public void computeScroll() { 185 | mNestedHelper.computeScroll(); 186 | } 187 | 188 | // =========================== nested parent ======================================= 189 | @Override 190 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 191 | return isEnabled() && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 192 | } 193 | 194 | @Override 195 | public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { 196 | // Reset the counter of how much leftover scroll needs to be consumed. 197 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); 198 | // Dispatch up to the nested parent 199 | startNestedScroll(nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL); 200 | } 201 | 202 | @Override 203 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 204 | mNestedHelper.nestedScroll(dx, dy, consumed, true); 205 | 206 | // Now let our nested parent consume the leftovers 207 | final int[] parentConsumed = mParentScrollConsumed; 208 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { 209 | consumed[0] += parentConsumed[0]; 210 | consumed[1] += parentConsumed[1]; 211 | } 212 | } 213 | 214 | @Override 215 | public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 216 | int dxUnconsumed, int dyUnconsumed) { 217 | 218 | // Dispatch up to the nested parent first 219 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 220 | mParentOffsetInWindow); 221 | } 222 | 223 | @Override 224 | public void onStopNestedScroll(View target) { 225 | mNestedScrollingParentHelper.onStopNestedScroll(target); 226 | 227 | // Dispatch up our nested parent 228 | stopNestedScroll(); 229 | } 230 | 231 | @Override 232 | public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 233 | return dispatchNestedFling(velocityX, velocityY, consumed); 234 | } 235 | 236 | @Override 237 | public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 238 | return dispatchNestedPreFling(velocityX, velocityY); 239 | } 240 | 241 | @Override 242 | public int getNestedScrollAxes() { 243 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 244 | } 245 | //======================== NestedScrollingParent end ======================== 246 | 247 | //======================== NestedScrollingChild begin ======================== 248 | public void setNestedScrollingEnabled(boolean enabled) { 249 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); 250 | } 251 | 252 | public boolean isNestedScrollingEnabled() { 253 | return mNestedScrollingChildHelper.isNestedScrollingEnabled(); 254 | } 255 | 256 | public boolean startNestedScroll(int axes) { 257 | return mNestedScrollingChildHelper.startNestedScroll(axes); 258 | } 259 | 260 | public void stopNestedScroll() { 261 | mNestedScrollingChildHelper.stopNestedScroll(); 262 | } 263 | 264 | public boolean hasNestedScrollingParent() { 265 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 266 | } 267 | 268 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, 269 | int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { 270 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, 271 | dxUnconsumed, dyUnconsumed, offsetInWindow); 272 | } 273 | 274 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 275 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 276 | } 277 | 278 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 279 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 280 | } 281 | 282 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 283 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 284 | } 285 | //======================== end NestedScrollingChild ===================== 286 | 287 | //====================== NestedScrollingChild2 ====================== 288 | public boolean startNestedScroll(int axes, int type) { 289 | return mNestedScrollingChildHelper.startNestedScroll(axes, type); 290 | } 291 | 292 | public void stopNestedScroll(int type) { 293 | mNestedScrollingChildHelper.stopNestedScroll(type); 294 | } 295 | 296 | public boolean hasNestedScrollingParent(int type) { 297 | return mNestedScrollingChildHelper.hasNestedScrollingParent(type); 298 | } 299 | 300 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) { 301 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type); 302 | } 303 | public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) { 304 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx , dy, consumed, offsetInWindow, type); 305 | } 306 | 307 | //=========================== NestedScrollingChild3 ====================== 308 | public void dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, 309 | @Nullable int[] offsetInWindow, int type, @NonNull int[] consumed) { 310 | mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed , dyConsumed, dxUnconsumed, dyUnconsumed, 311 | offsetInWindow, type, consumed); 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/src/main/java/com/heaven7/android/StickyLayout/StickyNavigationLayout.java: -------------------------------------------------------------------------------- 1 | package com.heaven7.android.StickyLayout; 2 | 3 | 4 | import android.content.Context; 5 | import android.content.res.TypedArray; 6 | import android.os.Parcel; 7 | import android.os.Parcelable; 8 | import android.util.AttributeSet; 9 | import android.util.Log; 10 | import android.view.MotionEvent; 11 | import android.view.View; 12 | import android.view.ViewGroup; 13 | import android.widget.LinearLayout; 14 | 15 | import androidx.core.view.NestedScrollingChild; 16 | import androidx.core.view.NestedScrollingChildHelper; 17 | import androidx.core.view.NestedScrollingParent; 18 | import androidx.core.view.NestedScrollingParentHelper; 19 | import androidx.core.view.ViewCompat; 20 | 21 | import com.heaven7.android.scroll.IScrollHelper; 22 | import com.heaven7.android.scroll.NestedScrollFactory; 23 | import com.heaven7.android.scroll.NestedScrollHelper; 24 | 25 | import java.util.ArrayList; 26 | 27 | /** 28 | * sticky navigation layout:similar to google+ app. 29 | *

30 | * Note: @attr ref android.R.styleable#stickyLayout_content_id the content view must be the direct child of StickyNavigationLayout. 31 | * or else may cause bug. 32 | *

33 | * 34 | * @author heaven7 35 | * @attr ref com.heaven7.android.sticky_navigation_layout.demo.R.styleable#stickyLayout_content_id 36 | * @attr ref com.heaven7.android.sticky_navigation_layout.demo.R.styleable#stickyLayout_top_id 37 | * @attr ref com.heaven7.android.sticky_navigation_layout.demo.R.styleable#stickyLayout_indicator_id 38 | * @attr ref com.heaven7.android.sticky_navigation_layout.demo.R.styleable#stickyLayout_auto_fit_scroll 39 | * @attr ref com.heaven7.android.sticky_navigation_layout.demo.R.styleable#stickyLayout_threshold_percent 40 | */ 41 | public class StickyNavigationLayout extends LinearLayout implements NestedScrollingParent, NestedScrollingChild { 42 | 43 | private static final String TAG = "StickyNavLayout"; 44 | 45 | private static final boolean DEBUG = false; 46 | 47 | private final NestedScrollHelper mNestedHelper; 48 | 49 | /** 50 | * the top view 51 | */ 52 | private View mTop; 53 | /** 54 | * the navigation view 55 | */ 56 | private View mIndicator; 57 | /** 58 | * the child view which will be intercept 59 | */ 60 | private View mContentView; 61 | private int mTopViewId; 62 | private int mIndicatorId; 63 | private int mContentId; 64 | 65 | private int mTopViewHeight; 66 | 67 | private GroupCallbacks mGroupCallback; 68 | 69 | /** 70 | * auto fit the sticky scroll 71 | */ 72 | private final boolean mAutoFitScroll; 73 | /** 74 | * the percent of auto fix. 75 | */ 76 | private float mAutoFitPercent = 0.5f; 77 | 78 | private final NestedScrollingParentHelper mNestedScrollingParentHelper; 79 | private final NestedScrollingChildHelper mNestedScrollingChildHelper; 80 | 81 | private final int[] mParentScrollConsumed = new int[2]; 82 | private final int[] mParentOffsetInWindow = new int[2]; 83 | 84 | private boolean mEnableStickyTouch = true; 85 | 86 | public StickyNavigationLayout(Context context, AttributeSet attrs) { 87 | super(context, attrs); 88 | //setOrientation(LinearLayout.VERTICAL); 89 | mGroupCallback = new GroupCallbacks(); 90 | mNestedScrollingParentHelper = new NestedScrollingParentHelper(this); 91 | mNestedScrollingChildHelper = new NestedScrollingChildHelper(this); 92 | mNestedHelper = NestedScrollFactory.create(this, new NestedScrollHelper.NestedScrollCallback() { 93 | @Override 94 | public boolean canScrollHorizontally(View target) { 95 | return false; 96 | } 97 | @Override 98 | public boolean canScrollVertically(View target) { 99 | return true; 100 | } 101 | @Override 102 | public int getMaximumYScrollDistance(View target) { 103 | return mTopViewHeight; 104 | } 105 | }); 106 | 107 | final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StickyNavigationLayout); 108 | // mCodeSet will set stick view from onFinishInflate. 109 | 110 | try { 111 | mTopViewId = a.getResourceId(R.styleable.StickyNavigationLayout_stickyLayout_top_id, 0); 112 | mIndicatorId = a.getResourceId(R.styleable.StickyNavigationLayout_stickyLayout_indicator_id, 0); 113 | mContentId = a.getResourceId(R.styleable.StickyNavigationLayout_stickyLayout_content_id, 0); 114 | 115 | mAutoFitScroll = a.getBoolean(R.styleable.StickyNavigationLayout_stickyLayout_auto_fit_scroll, false); 116 | mAutoFitPercent = a.getFloat(R.styleable.StickyNavigationLayout_stickyLayout_threshold_percent, 0.5f); 117 | }finally { 118 | a.recycle(); 119 | } 120 | 121 | //getWindowVisibleDisplayFrame(mExpectTopRect); 122 | setNestedScrollingEnabled(true); 123 | } 124 | 125 | @Override 126 | protected void onFinishInflate() { 127 | super.onFinishInflate(); 128 | 129 | mTop = findViewById(mTopViewId); 130 | mIndicator = findViewById(mIndicatorId); 131 | mContentView = findViewById(mContentId); 132 | } 133 | 134 | @Override 135 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 136 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 137 | 138 | if (mEnableStickyTouch && mContentView != null && mIndicator != null) { 139 | // set the height of content view 140 | ViewGroup.LayoutParams params = mContentView.getLayoutParams(); 141 | int expect = getMeasuredHeight() - mIndicator.getMeasuredHeight(); 142 | //avoid onMeasure all the time 143 | if(params.height != expect) { 144 | params.height = expect; 145 | } 146 | if (DEBUG) { 147 | Log.i(TAG, "onMeasure: height = " + params.height + ", snv height = " + getMeasuredHeight()); 148 | Log.i(TAG, "onMeasure: ---> snv bottom= " + getBottom()); 149 | } 150 | } 151 | mGroupCallback.afterOnMeasure(this, mTop, mIndicator, mContentView); 152 | } 153 | /* @Override 154 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 155 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 156 | 157 | mGroupCallback.afterOnMeasure(this, mTop, mIndicator, mContentView); 158 | if (mEnableStickyTouch && mContentView != null && mIndicator != null) { 159 | ViewGroup.LayoutParams params = mContentView.getLayoutParams(); 160 | params.height = getMeasuredHeight() - mIndicator.getMeasuredHeight(); 161 | if (DEBUG) { 162 | Log.i(TAG, "onMeasure: height = " + params.height + ", snv height = " + getMeasuredHeight()); 163 | Log.i(TAG, "onMeasure: ---> snv bottom= " + getBottom()); 164 | } 165 | } 166 | }*/ 167 | 168 | @Override 169 | protected void onLayout(boolean changed, int l, int t, int r, int b) { 170 | super.onLayout(changed, l, t, r, b); 171 | if (DEBUG) { 172 | Log.i(TAG, "onLayout"); 173 | } 174 | if (mEnableStickyTouch && mTop != null) { 175 | mTopViewHeight = mTop.getMeasuredHeight(); 176 | final ViewGroup.LayoutParams lp = mTop.getLayoutParams(); 177 | if (lp instanceof MarginLayoutParams) { 178 | mTopViewHeight += ((MarginLayoutParams) lp).topMargin + ((MarginLayoutParams) lp).bottomMargin; 179 | } 180 | } 181 | } 182 | /** 183 | * add a scroll change listener. 184 | * @param l the OnScrollChangeListener 185 | */ 186 | public void addOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 187 | mNestedHelper.addOnScrollChangeListener(l); 188 | } 189 | 190 | /** 191 | * remove a scroll change listener. 192 | * @param l the OnScrollChangeListener 193 | */ 194 | public void removeOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 195 | mNestedHelper.removeOnScrollChangeListener(l); 196 | } 197 | 198 | /** 199 | * judge if has the target OnScrollChangeListener. 200 | * @param l the OnScrollChangeListener 201 | * @return true if has the target OnScrollChangeListener 202 | */ 203 | public boolean hasOnScrollChangeListener(IScrollHelper.OnScrollChangeListener l){ 204 | return mNestedHelper.hasOnScrollChangeListener(l); 205 | } 206 | 207 | 208 | /** 209 | * Return the current scrolling state of the RecyclerView. 210 | */ 211 | public int getScrollState() { 212 | return mNestedHelper.getScrollState(); 213 | } 214 | 215 | /** 216 | * set if enable the sticky touch 217 | * 218 | * @param enable true to enable, false to disable. 219 | */ 220 | public void setEnableStickyTouch(boolean enable) { 221 | if (mEnableStickyTouch != enable) { 222 | this.mEnableStickyTouch = enable; 223 | requestLayout(); 224 | } 225 | } 226 | 227 | /** 228 | * is the sticky touch enabled. 229 | * 230 | * @return true to enable. 231 | */ 232 | public boolean isStickyTouchEnabled() { 233 | return mEnableStickyTouch; 234 | } 235 | 236 | /** 237 | * add a sticky delegate 238 | * 239 | * @param delegate sticky delegate. 240 | */ 241 | public void addStickyDelegate(IStickyCallback delegate) { 242 | mGroupCallback.addStickyDelegate(delegate); 243 | } 244 | 245 | /** 246 | * remove a sticky delegate 247 | * 248 | * @param delegate sticky delegate. 249 | */ 250 | public void removeStickyDelegate(IStickyCallback delegate) { 251 | mGroupCallback.removeStickyDelegate(delegate); 252 | } 253 | 254 | @Override 255 | public boolean onTouchEvent(MotionEvent event) { 256 | if(!mEnableStickyTouch){ 257 | return super.onTouchEvent(event); 258 | } 259 | return mNestedHelper.onTouchEvent(event); 260 | } 261 | 262 | 263 | /** 264 | * Begin a standard fling with an initial velocity along each axis in pixels per second. 265 | * If the velocity given is below the system-defined minimum this method will return false 266 | * and no fling will occur. 267 | * 268 | * @param velocityX Initial horizontal velocity in pixels per second 269 | * @param velocityY Initial vertical velocity in pixels per second 270 | * @return true if the fling was started, false if the velocity was too low to fling or 271 | * does not support scrolling in the axis fling is issued. 272 | */ 273 | public boolean fling(int velocityX, int velocityY) { 274 | return mNestedHelper.fling(velocityX , velocityY); 275 | } 276 | 277 | /** 278 | * Like {@link #scrollTo}, but scroll smoothly instead of immediately. 279 | * 280 | * @param x the position where to scroll on the X axis 281 | * @param y the position where to scroll on the Y axis 282 | */ 283 | public final void smoothScrollTo(int x, int y) { 284 | mNestedHelper.smoothScrollBy(x - getScrollX(), y - getScrollY()); 285 | } 286 | 287 | /** 288 | * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. 289 | * 290 | * @param dx the number of pixels to scroll by on the X axis 291 | * @param dy the number of pixels to scroll by on the Y axis 292 | */ 293 | public final void smoothScrollBy(int dx, int dy) { 294 | mNestedHelper.smoothScrollBy(dx , dy); 295 | } 296 | 297 | @Override 298 | public void computeScroll() { 299 | mNestedHelper.computeScroll(); 300 | } 301 | 302 | @Override 303 | protected Parcelable onSaveInstanceState() { 304 | final SaveState saveState = new SaveState(super.onSaveInstanceState()); 305 | saveState.mEnableStickyTouch = this.mEnableStickyTouch; 306 | return saveState; 307 | } 308 | 309 | @Override 310 | protected void onRestoreInstanceState(Parcelable state) { 311 | super.onRestoreInstanceState(state); 312 | if (state != null) { 313 | SaveState ss = (SaveState) state; 314 | this.mEnableStickyTouch = ss.mEnableStickyTouch; 315 | } 316 | } 317 | 318 | protected static class SaveState extends BaseSavedState { 319 | 320 | boolean mEnableStickyTouch; 321 | 322 | public SaveState(Parcel source) { 323 | super(source); 324 | mEnableStickyTouch = source.readByte() == 1; 325 | } 326 | 327 | public SaveState(Parcelable superState) { 328 | super(superState); 329 | } 330 | 331 | @Override 332 | public void writeToParcel(Parcel out, int flags) { 333 | super.writeToParcel(out, flags); 334 | out.writeByte((byte) (mEnableStickyTouch ? 1 : 0)); 335 | } 336 | 337 | public static final Parcelable.Creator CREATOR 338 | = new Parcelable.Creator() { 339 | @Override 340 | public SaveState createFromParcel(Parcel in) { 341 | return new SaveState(in); 342 | } 343 | @Override 344 | public SaveState[] newArray(int size) { 345 | return new SaveState[size]; 346 | } 347 | }; 348 | } 349 | 350 | /** 351 | * the internal group Sticky Delegate. 352 | */ 353 | private static class GroupCallbacks implements IStickyCallback { 354 | 355 | private final ArrayList mDelegates = new ArrayList<>(5); 356 | 357 | public void addStickyDelegate(IStickyCallback delegate) { 358 | if(!mDelegates.contains(delegate)) { 359 | mDelegates.add(delegate); 360 | } 361 | } 362 | 363 | public void removeStickyDelegate(IStickyCallback delegate) { 364 | mDelegates.remove(delegate); 365 | } 366 | 367 | public void clear() { 368 | mDelegates.clear(); 369 | } 370 | 371 | @Override 372 | public void afterOnMeasure(StickyNavigationLayout snv, View top, View indicator, View content) { 373 | for (IStickyCallback delegate : mDelegates) { 374 | delegate.afterOnMeasure(snv, top, indicator, content); 375 | } 376 | } 377 | 378 | } 379 | 380 | 381 | /** 382 | * the sticky callback 383 | */ 384 | public interface IStickyCallback { 385 | 386 | /** 387 | * called after the {@link StickyNavigationLayout#onMeasure(int, int)}. this is useful used when we want to 388 | * toggle two views visibility in {@link StickyNavigationLayout}(or else may cause bug). see it in demo. 389 | * @param snv the {@link StickyNavigationLayout} 390 | * @param top the top view 391 | * @param indicator the indicator view 392 | * @param contentView the content view 393 | * 394 | */ 395 | void afterOnMeasure(StickyNavigationLayout snv, View top, View indicator, View contentView); 396 | } 397 | 398 | //======================== NestedScrollingParent begin ======================== 399 | @Override 400 | public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { 401 | return isEnabled() && mEnableStickyTouch && (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0; 402 | } 403 | 404 | @Override 405 | public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { 406 | // Reset the counter of how much leftover scroll needs to be consumed. 407 | mNestedScrollingParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); 408 | // Dispatch up to the nested parent 409 | startNestedScroll(nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL); 410 | } 411 | 412 | @Override 413 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 414 | mNestedHelper.nestedScroll(dx, dy, consumed , true); 415 | // Logger.w(TAG, "onNestedPreScroll", "consumed = " + Arrays.toString(consumed)); 416 | 417 | // Now let our nested parent consume the leftovers 418 | final int[] parentConsumed = mParentScrollConsumed; 419 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { 420 | consumed[0] += parentConsumed[0]; 421 | consumed[1] += parentConsumed[1]; 422 | } 423 | } 424 | 425 | @Override 426 | public void onNestedScroll(View target, int dxConsumed, int dyConsumed, 427 | int dxUnconsumed, int dyUnconsumed) { 428 | // Dispatch up to the nested parent first 429 | dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, 430 | mParentOffsetInWindow); 431 | } 432 | 433 | @Override 434 | public void onStopNestedScroll(View target) { 435 | // Logger.i(TAG, "onStopNestedScroll"); 436 | checkAutoFitScroll(); 437 | mNestedScrollingParentHelper.onStopNestedScroll(target); 438 | 439 | // Dispatch up our nested parent 440 | stopNestedScroll(); 441 | } 442 | 443 | private void checkAutoFitScroll() { 444 | //check auto fit scroll 445 | if (mAutoFitScroll) { 446 | //check whole gesture. 447 | final float scrollY = getScrollY(); 448 | if(scrollY >= mTopViewHeight * mAutoFitPercent){ 449 | smoothScrollTo(0, mTopViewHeight); 450 | }else{ 451 | smoothScrollTo(0, 0); 452 | } 453 | } 454 | } 455 | 456 | @Override 457 | public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { 458 | return dispatchNestedFling(velocityX, velocityY, consumed); 459 | } 460 | 461 | @Override 462 | public boolean onNestedPreFling(View target, float velocityX, float velocityY) { 463 | return dispatchNestedPreFling(velocityX, velocityY); 464 | } 465 | 466 | @Override 467 | public int getNestedScrollAxes() { 468 | return mNestedScrollingParentHelper.getNestedScrollAxes(); 469 | } 470 | //======================== NestedScrollingParent end ======================== 471 | 472 | //======================== NestedScrollingChild begin ======================== 473 | public void setNestedScrollingEnabled(boolean enabled) { 474 | mNestedScrollingChildHelper.setNestedScrollingEnabled(enabled); 475 | } 476 | 477 | public boolean isNestedScrollingEnabled() { 478 | return mNestedScrollingChildHelper.isNestedScrollingEnabled(); 479 | } 480 | 481 | public boolean startNestedScroll(int axes) { 482 | return mNestedScrollingChildHelper.startNestedScroll(axes); 483 | } 484 | 485 | public void stopNestedScroll() { 486 | mNestedScrollingChildHelper.stopNestedScroll(); 487 | } 488 | 489 | public boolean hasNestedScrollingParent() { 490 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 491 | } 492 | 493 | public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, 494 | int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { 495 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, 496 | dxUnconsumed, dyUnconsumed, offsetInWindow); 497 | } 498 | 499 | public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { 500 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); 501 | } 502 | 503 | public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { 504 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 505 | } 506 | 507 | public boolean dispatchNestedPreFling(float velocityX, float velocityY) { 508 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 509 | } 510 | //======================== end NestedScrollingChild ===================== 511 | } 512 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Android-stick-navigation-layout/sticky-navigation-layout/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Sticky-navigation-layout 3 | Settings 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Android-sticky-navigation-layout 2 | a lib of android sticky navigation layout , not only contains it, but also contains a lib of 'android-nested-scrolling'. 3 | 4 |   android 停靠控件, 包含android最新嵌套滑动处理库。 不依赖listView or recyclerView.只要是实现了NestedScrollingChild的控件都可以。' 5 |   通过android-nestedScroll 嵌套滑动处理库, 可完美实现google+效果。 6 | 7 | Demo Screen Capture 8 | 9 | ## Gradle config 10 | 11 | ```java 12 | dependencies { 13 | //android 嵌套滑动处理库 14 | compile 'com.heaven7.android.scroll:android-nestedscroll:1.0' 15 | //sticky-navigation-layout . 16 | compile 'com.heaven7.android.StickyLayout:sticky-navigation-layout:1.0.2' 17 | } 18 | ``` 19 | 20 | 21 | ## sticky-navigation-layout 使用步骤: 22 | 23 | ---原理。 24 | ```java 25 |   停靠控件分成3个部分: 头,head view 26 | 停靠: navigation view 27 |                         随手势滚动控件: recyclerView或者其他。 28 | ``` 29 | 30 | 31 | 使用指南:  (demo中已包含复杂的业务或者说布局, 如果仍有问题,可提issue.) 32 | 33 | - 1,在xml中添加 停靠控件。如下图. 34 | ```java 35 | 46 | 47 | 53 | 54 | 63 | 64 | 65 | 70 | 71 | 78 | 79 | 80 | 81 | 82 | ``` 83 | 具体见demo, 其中: 84 | ```java 85 | /** 86 | stickyLayout_top_id 表示 head view 的id. 87 | stickyLayout_indicator_id 表示 navigation view 的id. 88 | stickyLayout_content_id 表示 下面内容的 view 的 id. 89 | 其余还有2个属性: 90 |             stickyLayout_auto_fit_scroll       滑动完手离开时,是否自动平滑到指定位置, 默认false. 91 | 具体的距离根据 stickyLayout_threshold_percent 来确定。 92 |             stickyLayout_threshold_percent     自动平滑到位置的 百分比.float类型。不写默认是0.5 93 | 94 | */ 95 | ``` 96 | - NestedScrollFrameLayout 97 | 是自定义的另外一个支持嵌套滑动的控件。如果你想你的Header view支持嵌套滑动。 98 | 用此控件即可。 99 | 100 | 101 | 102 | ## Android-nestedScroll 库, 说明文档 103 | 104 | - 简述 105 | 106 |   随着google写了一套嵌套滑动的处理规范, NestedScrollingChild 和 NestedScrollingParent. 但是我们要怎么去实现呢? 107 | 108 |   比如我们要写一个支持停靠的自定义控件。而且可内嵌SwipeRefreshLayout和 RecyclerView.那么用嵌套滑动规范就是 109 | 110 | 最佳选择。 因为最新的SwipeRefreshLayout和 RecyclerView都针对嵌套滑动做了支持。 我这套库就可以极大简化代码。 111 | 112 | 具体可以见 StickyNavigationLayout 源码。 所以处理嵌套滑动用Android-nestedScroll库就对啦. 113 | 114 |   使用 Android-nestedScroll 后的处理 滑动 以及 嵌套滑动 的核心代码: 115 | 116 | ```java 117 | @Override 118 | public boolean onInterceptTouchEvent(MotionEvent ev) { 119 | if(!isNestedScrollingEnabled()){ 120 | return super.onInterceptTouchEvent(ev); 121 | } 122 | return mNestedHelper.onInterceptTouchEvent(ev); 123 | } 124 | 125 | @Override 126 | public boolean onTouchEvent(MotionEvent event) { 127 | if(!isNestedScrollingEnabled()){ 128 | return super.onTouchEvent(event); 129 | } 130 | return mNestedHelper.onTouchEvent(event); 131 | } 132 | @Override 133 | public void computeScroll() { 134 | mNestedHelper.computeScroll(); 135 | } 136 | @Override 137 | public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { 138 | mNestedHelper.nestedScroll(dx, dy, consumed, true); 139 | 140 | // Now let our nested parent consume the leftovers 141 | final int[] parentConsumed = mParentScrollConsumed; 142 | if (dispatchNestedPreScroll(dx - consumed[0], dy - consumed[1], parentConsumed, null)) { 143 | consumed[0] += parentConsumed[0]; 144 | consumed[1] += parentConsumed[1]; 145 | } 146 | } 147 |    ... 148 | ``` 149 | 150 | 下面介绍一下 Android-nestedScroll 库. 151 | 152 | - IScrollHelper 滑动处理服务的超级接口, 实现类ScrollHelper. 153 | 154 | ```java 155 | 滑动状态的3个常量: 156 |        空闲: SCROLL_STATE_IDLE 157 |        拖拽中: SCROLL_STATE_DRAGGING 158 |        setting中: SCROLL_STATE_SETTLING (一般用于fling 或者 平滑 smoothScroll) 159 | 160 |     //设置滑动状态,内部会自动通知状态改变 161 | void setScrollState(int state); 162 | //获取当前的滑动状态 163 | int getScrollState(); 164 | 165 | //通知滑动距离改变 166 | void dispatchOnScrolled(int dx, int dy); 167 | 168 | //按照给定的增量x和y滑动 169 | void scrollBy(int dx, int dy); 170 | 171 | //滑动到指定的x和y 172 | void scrollTo(int x, int y); 173 | 174 |    //按照指定的增量平滑 175 | void smoothScrollBy(int dx, int dy); 176 |    //平滑到指定的x 和 y 177 | void smoothScrollTo(int x, int y); 178 | 179 |    // 停止滑动 180 | void stopScroll(); 181 | 182 |    //同view的 computeScroll, 主要是在自定义控件时,通过重写View的computeScroll方法, 调用此方法即可. 183 | void computeScroll(); 184 | 185 | //按照指定的x轴速度和 y轴速度 fling. 186 | boolean fling(float velocityX, float velocityY); 187 | 188 | //添加滑动监听器 189 | void addOnScrollChangeListener(OnScrollChangeListener l); 190 | 191 | //移除滑动监听器 192 | void removeOnScrollChangeListener(OnScrollChangeListener l); 193 | 194 | //是否包含该滑动监听器 195 | boolean hasOnScrollChangeListener(OnScrollChangeListener l); 196 | 197 | ``` 198 | - 内部类: OnScrollChangeListener 滑动监听器 (包含状态监听和距离监听)。 199 | ```java 200 | 来源: View类也有。不过从api 23开始才有通用的, 以前listView,recyclerView都是单独的监听器。 201 | 202 |    方法: void onScrollStateChanged(View target, int state); 状态改变回调 203 | 204 |           void onScrolled(View target, int dx, int dy);   滑动距离回调 205 | // ps: dx,dy 带方向的),比如dy > 0 表示 手势向上。 206 | ``` 207 | 208 | - INestedScrollHelper   嵌套滑动处理辅助接口。实现类 NestedScrollHelper. 209 | ```java 210 | 211 | //设置是否启用嵌套滑动, 同NestedScrollingChildHeloer.setNestedScrollingEnabled. 只不过增加状态改变回调 212 | void setNestedScrollingEnabled(boolean enable); 213 | 214 | //是否启用了嵌套滑动 215 | boolean isNestedScrollingEnabled(); 216 | 217 | 218 |    //复写 {@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)} 调用此方法, 支持嵌套滑动. 具体见 demo实现。               219 | boolean onInterceptTouchEvent(MotionEvent ev); 220 | 221 | 222 | //复写 {@link android.view.ViewGroup#onTouchEvent(MotionEvent)} 调用此方法 ,支持嵌套滑动 223 | boolean onTouchEvent(MotionEvent event); 224 | 225 |     //通过指定的增量 执行嵌套滑动 ,返回true如果执行了 226 | boolean nestedScrollBy(int dx, int dy, MotionEvent ev); 227 | 228 | 229 |     /*真正执行嵌套滑动, 230 | // @param dx x增量 231 | //@param dy y增量 232 |     //@param consumed x 和 y轴 消耗的滑动的距离。可选参数 233 | // @param dispatchScroll 是否通知滑动距离改变 234 | //@return 返回x ,y 消耗的滑动距离 235 | */ 236 | int[] nestedScroll(int dx, int dy, int[] consumed, boolean dispatchScroll); 237 | 238 | ``` 239 | 240 | - 滑动处理相关的回调 ScrollCallback 和 NestedScrollCallback 。(非OnScrollChangeListener监听器) 241 | 242 | ```java 243 |       //水平方向是否可滑动。如果支持水平滑动返回true 244 | public abstract boolean canScrollHorizontally(View target); 245 | 246 | //竖直是否支持滑动 247 | public abstract boolean canScrollVertically(View target); 248 | 249 | //获取x轴最大的滑动距离 250 | public int getMaximumXScrollDistance(View target) ; 251 | 252 | //获取y轴最大的滑动距离 253 | public int getMaximumYScrollDistance(View target); 254 | 255 |        //滑动距离回调。默认空实现 256 | public void onScrolled(int dx, int dy); 257 | ``` 258 | 259 | - NestedScrollFactory 创建ScrollHelper和 NestedScrollHelper对象的工厂 260 | 261 | ## About me 262 | * heaven7 263 | * email: donshine723@gmail.com or 978136772@qq.com 264 | 265 | ## hope 266 | i like technology. especially the open-source technology.And previous i didn't contribute to it caused by i am a little lazy, but now i really want to do some for the open-source. So i hope to share and communicate with the all of you. 267 | 268 | 269 | ## License 270 | 271 | Copyright 2016 272 | heaven7(donshine723@gmail.com) 273 | 274 | Licensed under the Apache License, Version 2.0 (the "License"); 275 | you may not use this file except in compliance with the License. 276 | You may obtain a copy of the License at 277 | 278 | http://www.apache.org/licenses/LICENSE-2.0 279 | 280 | Unless required by applicable law or agreed to in writing, software 281 | distributed under the License is distributed on an "AS IS" BASIS, 282 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 283 | See the License for the specific language governing permissions and 284 | limitations under the License. 285 | -------------------------------------------------------------------------------- /art/sticky_navigation_layout.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/LightSun/Android-sticky-navigation-layout/575300a0067dd004a0880d3865bd6a35cdc18044/art/sticky_navigation_layout.gif --------------------------------------------------------------------------------