diff --git a/README.md b/README.md index ce3504c..7b9dff4 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # 主要解决AppBarLayout的置顶效果存在的以下问题 - 1.头部超过一屏回拉会出现明显卡顿 2.惯性滑动无法用手指停下 @@ -91,11 +90,13 @@ allprojects { } ``` 然后: -`implementation(或api) 'com.github.weimingjue:sticky:0.9.8'` +`implementation(或api) 'com.github.weimingjue:sticky:0.9.9'` ## 说明 如果没有tag="sticky"则它就是一个可嵌套滑动的view 支持androidX所有view及第三方的View嵌套,不支持ListView +Android5.0及以后可以使用RelativeLayout和ConstantLayout + 无需混淆配置 \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index 7d7aaba..c53ab96 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' android { compileSdkVersion COMPILE_SDK_VERSION @@ -25,10 +26,12 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation project(':sticky') - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation 'androidx.recyclerview:recyclerview:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0' //自己的adapter - api 'com.github.weimingjue:BaseAdapter:4.1.3' + api 'com.github.weimingjue:BaseAdapter:4.3.0' + implementation "androidx.core:core-ktx:1.3.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1516b8b..7c82235 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,7 +3,7 @@ package="com.wang.example"> adapter = BaseAdapterRvList.createAdapter(R.layout.adapter_main); - ArrayList list = new ArrayList<>(); - for (int i = 0; i < 80; i++) { - list.add("第" + i); - } - adapter.setListAndNotifyDataSetChanged(list); - mRv.setAdapter(adapter); - - //代码设置 -// StickyNestedScrollLayout snsl = findViewById(R.id.snsl_main); -// TextView tv = findViewById(R.id.tv_main); -// snsl.setChildTag(tv, "sticky"); -// snsl.setChildTag(mRv, "match"); - } -} diff --git a/app/src/main/java/com/wang/example/MainActivity.kt b/app/src/main/java/com/wang/example/MainActivity.kt new file mode 100644 index 0000000..817b668 --- /dev/null +++ b/app/src/main/java/com/wang/example/MainActivity.kt @@ -0,0 +1,29 @@ +package com.wang.example + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.RecyclerView +import com.wang.adapters.adapter.BaseAdapterRvList +import com.wang.sticky.StickyNestedScrollLayout +import java.util.* + +class MainActivity : AppCompatActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + val rv = findViewById(R.id.rv_main) + val adapter = BaseAdapterRvList.createAdapter(R.layout.adapter_main) + val list = ArrayList() + for (i in 0..79) { + list.add("第$i") + } + adapter.setListAndNotifyDataSetChanged(list) + rv.adapter = adapter + + val snsl = findViewById(R.id.snsl_main) + //代码设置 +// val tv = findViewById(R.id.tv_main) +// snsl.setChildTag(tv, StickyNestedScrollLayout.TAG_STICKY) +// snsl.setChildTag(rv, StickyNestedScrollLayout.TAG_MATCH) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/wang/example/MyApplication.java b/app/src/main/java/com/wang/example/MyApplication.java deleted file mode 100644 index f00b8b7..0000000 --- a/app/src/main/java/com/wang/example/MyApplication.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.wang.example; - -import android.app.Application; - -public class MyApplication extends Application { - private static MyApplication mApp; - - @Override - public void onCreate() { - super.onCreate(); - mApp = this; - } - - public static MyApplication getContext() { - return mApp; - } -} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 330ce9e..9f22bd5 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -11,46 +11,48 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - + android:textSize="80sp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + android:textSize="30sp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv1" /> - - + android:textSize="80sp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv2" /> + android:layout_height="wrap_content" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv3"> + android:textSize="80sp" + app:layout_constraintLeft_toLeftOf="parent" + app:layout_constraintTop_toBottomOf="@+id/tv5" /> - + \ No newline at end of file diff --git a/build.gradle b/build.gradle index af4f0b1..c70905a 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.4.32' repositories { maven { url 'https://jitpack.io' } google() @@ -8,7 +9,8 @@ buildscript { maven { url 'http://maven.aliyun.com/nexus/content/groups/public/' } } dependencies { - classpath 'com.android.tools.build:gradle:3.6.4' + classpath 'com.android.tools.build:gradle:4.1.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -27,11 +29,11 @@ task clean(type: Delete) { } ext { //用于编译的SDK版本 - COMPILE_SDK_VERSION = 28 + COMPILE_SDK_VERSION = 29 //最低支持Android版本 MIN_SDK_VERSION = 16 //目标版本 - TARGET_SDK_VERSION = 28 + TARGET_SDK_VERSION = 29 } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d2af134..451fdc0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sat Dec 26 13:10:11 CST 2020 +#Tue Apr 27 15:19:57 CST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/sticky/build.gradle b/sticky/build.gradle index 2804ac3..1ec7fb8 100644 --- a/sticky/build.gradle +++ b/sticky/build.gradle @@ -1,4 +1,5 @@ apply plugin: 'com.android.library' +apply plugin: 'kotlin-android' android { compileSdkVersion COMPILE_SDK_VERSION @@ -24,6 +25,6 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'androidx.recyclerview:recyclerview:1.1.0' - implementation 'androidx.legacy:legacy-support-v4:1.0.0' + implementation "androidx.core:core-ktx:1.3.2" + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" } diff --git a/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.java b/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.java deleted file mode 100644 index 60a72fc..0000000 --- a/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.java +++ /dev/null @@ -1,431 +0,0 @@ -package com.wang.sticky; - -import android.content.Context; -import android.os.Build; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.LinearLayout; - -import androidx.annotation.IntRange; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.RequiresApi; -import androidx.core.view.ViewCompat; -import androidx.core.widget.NestedScrollView; - -import java.util.ArrayList; - -/** - * 悬浮view,基于NestedScrollView,所以只能有一个child(不使用悬浮可当做NestedScrollView的优化版) - *

- * 使用: - * 设置tag:sticky即可悬浮 - *

- * 附加:{@link #setOnStickyScrollChangedListener}{@link #getStickyTop}{@link #setChildTag} - * tag:match撑满布局 - */ -public final class StickyNestedScrollLayout extends FrameLayout { - public static final String TAG_STICKY = "sticky", TAG_MATCH = "match"; - private MyScrollView mScrollView; - - public StickyNestedScrollLayout(Context context) { - this(context, null); - } - - public StickyNestedScrollLayout(Context context, @Nullable AttributeSet attrs) { - this(context, attrs, 0); - } - - public StickyNestedScrollLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) - public StickyNestedScrollLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { - super(context, attrs, defStyleAttr, defStyleRes); - } - - /** - * 套一层,可以少很多逻辑 - */ - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - if (getChildCount() > 0) { - throw new RuntimeException("只允许添加一个子类"); - } - LinearLayout ll = new LinearLayout(getContext()); - ll.setOrientation(LinearLayout.VERTICAL); - mScrollView = new MyScrollView(getContext(), ll); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - mScrollView.setClipChildren(getClipChildren()); - } - mScrollView.addView(child, index, params); - super.addView(mScrollView, -1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); - super.addView(ll, -1, new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); - } - - /** - * 只支持固定宽高 - */ - @Override - public void setLayoutParams(ViewGroup.LayoutParams params) { - if (params.width == ViewGroup.LayoutParams.WRAP_CONTENT) { - params.width = ViewGroup.LayoutParams.MATCH_PARENT; - } - if (params.height == ViewGroup.LayoutParams.WRAP_CONTENT) { - params.height = ViewGroup.LayoutParams.MATCH_PARENT; - } - super.setLayoutParams(params); - } - - /** - * 高必须确定 - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int mode = MeasureSpec.getMode(heightMeasureSpec); - int size = MeasureSpec.getSize(heightMeasureSpec); - if (mode != MeasureSpec.EXACTLY) { - heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.min(getResources().getDisplayMetrics().heightPixels, size), MeasureSpec.EXACTLY); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // 私有类 - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - private static final class MyScrollView extends NestedScrollView { - - /** - * 请使用{@link #getStickyViews()} - */ - @Nullable - private ArrayList mStickyViews; - private final LinearLayout mLlAdd; - - private boolean mIsDown = false; - - private OnStickyScrollChangedListener mListener; - - public MyScrollView(Context context, LinearLayout llAdd) { - super(context); - mLlAdd = llAdd; - } - - /** - * 惯性滑动时手指按下则立即停止 - */ - @Override - public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type, @NonNull int[] consumed) { - if (mIsDown && type != ViewCompat.TYPE_TOUCH) { - return; - } - super.onNestedScroll(target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed); - } - - /** - * 修复滑动类似RecyclerView时不跟随的问题 - */ - @Override - public void onNestedPreScroll(@NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) { - if (dy > 0) {//nsv往上滑不跟随,这里处理一下 - if (canScrollVertically(1)) { - int stickyTop = getStickyTop(getStickyViews().size() - 1); - int dy2; - if (stickyTop > 0) { - dy2 = Math.min(dy, stickyTop); - } else { - dy2 = dy; - } - consumed[1] = consumed[1] + dy2; - scrollBy(0, dy2); - return;//暂时先全部消耗掉(几像素) - } - } - super.onNestedPreScroll(target, dx, dy, consumed, type); - } - - /** - * 如果子有match就设置为当前高 - */ - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int height = MeasureSpec.getSize(heightMeasureSpec); - if (getChildCount() > 0) { - changeMatch(getChildAt(0), height); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - private void changeMatch(View view, int height) { - if (TAG_MATCH.equals(view.getTag()) || TAG_MATCH.equals(view.getTag(R.id.tag_sticky))) { - ViewGroup.LayoutParams params = view.getLayoutParams(); - if (params != null) { - params.height = height; - } - } - if (view instanceof ViewGroup) { - ViewGroup vg0 = (ViewGroup) view; - for (int i = 0; i < vg0.getChildCount(); i++) { - changeMatch(vg0.getChildAt(i), height); - } - } - } - - /** - * 当按下时停掉惯性滑动 - */ - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - switch (ev.getAction()) { - case MotionEvent.ACTION_DOWN: - mIsDown = true; - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - mIsDown = false; - break; - default: - break; - } - return super.dispatchTouchEvent(ev); - } - - @NonNull - public ArrayList getStickyViews() { - if (mStickyViews != null) { - return mStickyViews; - } - mStickyViews = new ArrayList<>(2); - - if (getChildCount() > 0) { - initStickyViews(getChildAt(0), 0); - } - return mStickyViews; - } - - /** - * 遍历得到所有悬浮view - */ - private void initStickyViews(View checkView, int viewIndex) { - if (TAG_STICKY.equals(checkView.getTag()) || TAG_STICKY.equals(checkView.getTag(R.id.tag_sticky))) { - StickyInfo info = new StickyInfo(checkView, checkView.getLayoutParams(), (ViewGroup) checkView.getParent(), viewIndex); - //noinspection ConstantConditions 这里不可能是null - if (!mStickyViews.contains(info)) { - mStickyViews.add(info); - } - return; - } - if (checkView instanceof ViewGroup) { - ViewGroup vg = (ViewGroup) checkView; - for (int i = 0; i < vg.getChildCount(); i++) { - initStickyViews(vg.getChildAt(i), i); - } - } - } - - /** - * 悬浮切换 - */ - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - for (int i = 0; i < getStickyViews().size(); i++) { - StickyInfo info = getStickyViews().get(i); - int top = getStickyTop(i); - int index = llIndexOfSticky(info.mStickyView); - if (top > 0) { - if (index >= 0) { - info.mStickyParent.removeView(info.mEmptyView); - mLlAdd.removeView(info.mStickyView); - - info.mStickyParams.height = info.mStickyParamsHeight; - info.mStickyParent.addView(info.mStickyView, info.mStickyIndex, info.mStickyParams); - } - } else { - if (index < 0) { - info.mStickyParent.removeView(info.mStickyView); - mLlAdd.removeView(info.mEmptyView); - - info.mStickyParamsHeight = info.mStickyParams.height; - info.mStickyParams.height = info.mStickyView.getHeight(); - info.mStickyParent.addView(info.mEmptyView, info.mStickyIndex, info.mStickyParams); - - LinearLayout.LayoutParams params = newLlParams(info.mStickyParams); - params.leftMargin += getStickyParentLeftToThis(info.mStickyParent); - mLlAdd.addView(info.mStickyView, params); - } - } - - if (mListener != null) { - mListener.onScroll(i, top); - } - } - } - - private int llIndexOfSticky(View sv) { - for (int i = 0; i < mLlAdd.getChildCount(); i++) { - if (mLlAdd.getChildAt(i) == sv) { - return i; - } - } - return -1; - } - - /** - * 悬浮view的父类距自己左边 - */ - private int getStickyParentLeftToThis(View v) { - if (v == null || v == this) { - return 0; - } - return v.getLeft() + getStickyParentLeftToThis((View) v.getParent()); - } - - /** - * 悬浮view距自己顶端 - */ - private int getStickyTopToThis(View v) { - if (v == null || v == this) { - return 0; - } - return v.getTop() + getStickyTopToThis((View) v.getParent()); - } - - /** - * @return 悬浮view距顶部距离 - */ - @IntRange(from = 0) - public int getStickyTop(int stickyIndex) { - if (stickyIndex < 0 || stickyIndex >= getStickyViews().size()) { - return 0; - } - StickyInfo info = getStickyViews().get(stickyIndex); - View v = info.mStickyParent.getChildAt(info.mStickyIndex); - int top = getStickyTopToThis(v); - if (v.getLayoutParams() instanceof MarginLayoutParams) { - top -= ((MarginLayoutParams) v.getLayoutParams()).topMargin; - } - top = top - getScrollY() + getPaddingTop() - mLlAdd.getHeight();//去掉scroll和悬浮遮挡的 - if (llIndexOfSticky(info.mStickyView) >= 0) {//去掉自己悬浮高 - LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) info.mStickyView.getLayoutParams(); - top += info.mStickyView.getHeight() + params.topMargin + params.bottomMargin; - } - return Math.max(top, 0); - } - - public void setOnStickyScrollChangedListener(OnStickyScrollChangedListener listener) { - mListener = listener; - } - - /** - * new 出来的,不是原对象 - */ - private LinearLayout.LayoutParams newLlParams(ViewGroup.LayoutParams lp) { - if (lp instanceof LinearLayout.LayoutParams) { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams((MarginLayoutParams) lp); - params.gravity = ((LinearLayout.LayoutParams) lp).gravity; - return params; - } else if (lp instanceof FrameLayout.LayoutParams) { - LinearLayout.LayoutParams params = new LinearLayout.LayoutParams((MarginLayoutParams) lp); - params.gravity = ((FrameLayout.LayoutParams) lp).gravity; - return params; - } else if (lp instanceof MarginLayoutParams) { - return new LinearLayout.LayoutParams((MarginLayoutParams) lp); - } - return new LinearLayout.LayoutParams(lp); - } - - private class StickyInfo { - @NonNull - private View mStickyView; - @NonNull - private ViewGroup.LayoutParams mStickyParams; - private int mStickyParamsHeight; - @NonNull - private ViewGroup mStickyParent; - private int mStickyIndex; - private final View mEmptyView = new View(getContext()); - - StickyInfo(@NonNull View stickyView, @NonNull ViewGroup.LayoutParams stickyParams, - @NonNull ViewGroup stickyParent, int stickyIndex) { - mStickyView = stickyView; - mStickyParams = stickyParams; - mStickyParent = stickyParent; - mStickyIndex = stickyIndex; - } - - @Override - public boolean equals(@Nullable Object obj) { - if (obj instanceof StickyInfo) { - return mStickyView == ((StickyInfo) obj).mStickyView; - } - return super.equals(obj); - } - } - } - - public interface OnStickyScrollChangedListener { - /** - * @param stickyPosition 第几个悬浮,单悬浮可忽略 - * @param stickyTop 悬浮view距顶部距离,从max-0 - */ - void onScroll(int stickyPosition, int stickyTop); - } - - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - // 公共方法 - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * 获取第一个悬浮view距顶部的距离 - * 0为悬浮中 - */ - @IntRange(from = 0) - public int getStickyTop() { - return mScrollView.getStickyTop(0); - } - - /** - * 获取第Index悬浮view距顶部的距离 - * 0为悬浮中 - */ - @IntRange(from = 0) - public int getStickyTop(int stickyIndex) { - return mScrollView.getStickyTop(stickyIndex); - } - - public void setOnStickyScrollChangedListener(OnStickyScrollChangedListener listener) { - mScrollView.setOnStickyScrollChangedListener(listener); - } - - /** - * 获得悬浮时的父布局 - * 你可以设置background、padding等 - */ - public LinearLayout getStickyParent() { - return mScrollView.mLlAdd; - } - - /** - * 获得内部滑动的view,可设置Scroll相关操作 - */ - public NestedScrollView getScrollView() { - return mScrollView; - } - - /** - * 备用方法:当tag被占用时 - * - * @param child 直接或间接子类 - * @param tag {@link #TAG_STICKY}{@link #TAG_MATCH} - */ - public void setChildTag(View child, String tag) { - child.setTag(R.id.tag_sticky, tag); - mScrollView.mStickyViews = null;//重新获取 - requestLayout(); - } -} \ No newline at end of file diff --git a/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.kt b/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.kt new file mode 100644 index 0000000..10aa809 --- /dev/null +++ b/sticky/src/main/java/com/wang/sticky/StickyNestedScrollLayout.kt @@ -0,0 +1,397 @@ +package com.wang.sticky + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.annotation.IdRes +import androidx.annotation.IntRange +import androidx.core.view.ViewCompat +import androidx.core.widget.NestedScrollView +import java.util.* +import kotlin.math.max +import kotlin.math.min + +/** + * 悬浮view,基于NestedScrollView,所以只能有一个child(不使用悬浮可当做NestedScrollView的优化版) + * + * + * 使用: + * 设置tag:sticky即可悬浮 + * + * + * 附加:[.setOnStickyScrollChangedListener][.getStickyTop][.setChildTag] + * tag:match撑满布局 + */ +class StickyNestedScrollLayout @JvmOverloads +constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0 +) : FrameLayout(context, attrs, defStyleAttr) { + private val scrollView: MyScrollView = + MyScrollView(LinearLayout(context).apply { orientation = LinearLayout.VERTICAL }) + + /** + * 套一层,可以少很多逻辑 + */ + override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) { + if (childCount > 0) { + throw RuntimeException("只允许添加一个子类") + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + scrollView.clipChildren = clipChildren + } + scrollView.addView(child, index, params) + + //添加sv + super.addView(scrollView, -1, LayoutParams(MATCH_PARENT, MATCH_PARENT)) + + //添加ll + super.addView(scrollView.llAdd, -1, LayoutParams(MATCH_PARENT, WRAP_CONTENT)) + } + + /** + * 只支持固定宽高 + */ + override fun setLayoutParams(params: ViewGroup.LayoutParams) { + if (params.width == WRAP_CONTENT) { + params.width = MATCH_PARENT + } + if (params.height == WRAP_CONTENT) { + params.height = MATCH_PARENT + } + super.setLayoutParams(params) + } + + /** + * 高必须确定 + */ + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var hms = heightMeasureSpec + if (MeasureSpec.getMode(hms) != MeasureSpec.EXACTLY) { + hms = MeasureSpec.makeMeasureSpec( + min(resources.displayMetrics.heightPixels, MeasureSpec.getSize(hms)), + MeasureSpec.EXACTLY + ) + } + super.onMeasure(widthMeasureSpec, hms) + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 私有类 + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + private class MyScrollView(val llAdd: LinearLayout) : NestedScrollView(llAdd.context) { + /** + * 请使用[getStickyViews] + */ + @Suppress("PropertyName") + var _stickyViews: ArrayList? = null + private var isDown = false + var listener: ((stickyPosition: Int, stickyTop: Int) -> Unit)? = null + + /** + * 惯性滑动时手指按下则立即停止 + */ + override fun onNestedScroll( + target: View, + dxConsumed: Int, + dyConsumed: Int, + dxUnconsumed: Int, + dyUnconsumed: Int, + type: Int, consumed: IntArray + ) { + if (isDown && type != ViewCompat.TYPE_TOUCH) { + return + } + super.onNestedScroll( + target, + dxConsumed, + dyConsumed, + dxUnconsumed, + dyUnconsumed, + type, + consumed + ) + } + + /** + * 修复滑动类似RecyclerView时不跟随的问题 + */ + override fun onNestedPreScroll( + target: View, + dx: Int, + dy: Int, + consumed: IntArray, + type: Int + ) { + if (dy > 0) { //nsv往上滑不跟随,这里处理一下 + if (canScrollVertically(1)) { + val stickyTop = getStickyTop(getStickyViews().size - 1) + val dy2 = if (stickyTop > 0) min(dy, stickyTop) else dy + consumed[1] = consumed[1] + dy2 + scrollBy(0, dy2) + return //暂时先全部消耗掉(几像素) + } + } + super.onNestedPreScroll(target, dx, dy, consumed, type) + } + + /** + * 如果子有match就设置为当前高 + */ + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val height: Int = MeasureSpec.getSize(heightMeasureSpec) + if (childCount > 0) { + changeMatch(getChildAt(0), height) + } + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + } + + private fun changeMatch(view: View, height: Int) { + if (TAG_MATCH == view.tag || TAG_MATCH == view.getTag(R.id.tag_sticky)) { + view.layoutParams?.height = height + } + if (view is ViewGroup) { + for (i in 0 until view.childCount) { + changeMatch(view.getChildAt(i), height) + } + } + } + + /** + * 当按下时停掉惯性滑动 + */ + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + when (ev.action) { + MotionEvent.ACTION_DOWN -> { + isDown = true + } + MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + isDown = false + } + } + return super.dispatchTouchEvent(ev) + } + + fun getStickyViews(): ArrayList { + if (_stickyViews != null) { + return _stickyViews!! + } + _stickyViews = ArrayList(2) + if (childCount > 0) { + initStickyViews(getChildAt(0), 0) + } + return _stickyViews!! + } + + /** + * 遍历得到所有悬浮view + */ + private fun initStickyViews(checkView: View, viewIndex: Int) { + if (TAG_STICKY == checkView.tag || TAG_STICKY == checkView.getTag(R.id.tag_sticky)) { + val info = StickyInfo(checkView, viewIndex) + if (!getStickyViews().contains(info)) { + getStickyViews().add(info) + } + return + } + if (checkView is ViewGroup) { + for (i in 0 until checkView.childCount) { + initStickyViews(checkView.getChildAt(i), i) + } + } + } + + /** + * 悬浮切换 + */ + override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { + super.onScrollChanged(l, t, oldl, oldt) + for ((i, info) in getStickyViews().withIndex()) { + val top = getStickyTop(i) + val index = llIndexOfSticky(info.stickyView) + if (top > 0 && index >= 0) {//去掉悬浮 + info.stickyParent.removeView(info.emptyView) + llAdd.removeView(info.stickyView) + info.stickyParams.height = info.stickyParamsHeight + info.stickyParent.addView(info.stickyView, info.stickyIndex, info.stickyParams) + } else if (top <= 0 && index < 0) {//添加悬浮 + info.stickyParent.removeView(info.stickyView) + llAdd.removeView(info.emptyView) + info.stickyParamsHeight = info.stickyParams.height + info.stickyParams.height = info.stickyView.height + info.stickyParent.addView(info.emptyView, info.stickyIndex, info.stickyParams) + val params: LinearLayout.LayoutParams = newLlParams(info.stickyParams) + params.leftMargin += getStickyParentLeftToThis(info.stickyParent) + llAdd.addView(info.stickyView, params) + } + listener?.let { it(i, top) } + } + } + + private fun llIndexOfSticky(sv: View): Int { + for (i in 0 until llAdd.childCount) { + if (llAdd.getChildAt(i) === sv) { + return i + } + } + return -1 + } + + /** + * 悬浮view的父类距自己左边 + */ + private fun getStickyParentLeftToThis(v: View?): Int { + return if (v == null || v == this) 0 else v.left + getStickyParentLeftToThis(v.parent as View) + } + + /** + * 悬浮view距自己顶端 + */ + private fun getStickyTopToThis(v: View?): Int { + return if (v == null || v == this) 0 else v.top + getStickyTopToThis(v.parent as View) + } + + /** + * @return 悬浮view距顶部距离 + */ + @IntRange(from = 0) + fun getStickyTop(stickyIndex: Int): Int { + if (stickyIndex < 0 || stickyIndex >= getStickyViews().size) { + return 0 + } + val info = getStickyViews()[stickyIndex] + val v: View = info.stickyParent.getChildAt(info.stickyIndex) + var top = getStickyTopToThis(v) + if (v.layoutParams is MarginLayoutParams) { + top -= (v.layoutParams as MarginLayoutParams).topMargin + } + top = top - scrollY + paddingTop - llAdd.height //去掉scroll和悬浮遮挡的 + if (llIndexOfSticky(info.stickyView) >= 0) { //去掉自己悬浮高 + val params: LinearLayout.LayoutParams = + info.stickyView.layoutParams as LinearLayout.LayoutParams + top += info.stickyView.height + params.topMargin + params.bottomMargin + } + return max(top, 0) + } + + /** + * new 出来的,不是原对象 + */ + private fun newLlParams(lp: ViewGroup.LayoutParams): LinearLayout.LayoutParams { + when (lp) { + is LinearLayout.LayoutParams -> { + val params: LinearLayout.LayoutParams = + LinearLayout.LayoutParams(lp as MarginLayoutParams) + params.gravity = lp.gravity + return params + } + is LayoutParams -> { + val params: LinearLayout.LayoutParams = + LinearLayout.LayoutParams(lp as MarginLayoutParams) + params.gravity = lp.gravity + return params + } + is MarginLayoutParams -> { + return LinearLayout.LayoutParams(lp) + } + else -> return LinearLayout.LayoutParams(lp) + } + } + + /** + * @param stickyIndex 可能会因parent addView变化导致不准,暂时先忽略这种情况 + */ + private class StickyInfo(val stickyView: View, val stickyIndex: Int) { + val stickyParams: ViewGroup.LayoutParams = stickyView.layoutParams + val stickyParent = stickyView.parent as ViewGroup + var stickyParamsHeight = 0 + + val emptyView = ViewProxy(stickyView) + + override fun equals(other: Any?) = + if (other is StickyInfo) stickyView === other.stickyView else super.equals(other) + + override fun hashCode(): Int { + var result = stickyView.hashCode() + result = 31 * result + stickyParams.hashCode() + result = 31 * result + stickyParent.hashCode() + result = 31 * result + stickyIndex + result = 31 * result + stickyParamsHeight + result = 31 * result + emptyView.hashCode() + return result + } + } + } + + /** + * 代理view(目前只代理findViewById) + */ + private class ViewProxy(private val targetView: View) : View(targetView.context) { + + init { + id = targetView.id + } + + /** + * 此处重写findViewById来代理 + */ + protected fun findViewTraversal(@IdRes id: Int) = targetView.findViewById(id) + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + // 公共方法 + //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * 获取第Index悬浮view距顶部的距离(默认第一个) + * @return 0为悬浮中 + */ + @JvmOverloads + @IntRange(from = 0) + fun getStickyTop(stickyIndex: Int = 0): Int { + return scrollView.getStickyTop(stickyIndex) + } + + /** + * @param listener stickyPosition:第几个悬浮,单悬浮可忽略 + * stickyTop 悬浮view距顶部距离,从max-0 + */ + fun setOnStickyScrollChangedListener(listener: ((stickyPosition: Int, stickyTop: Int) -> Unit)?) { + scrollView.listener = listener + } + + /** + * 获得悬浮时的父布局 + * 你可以设置background、padding等 + */ + fun getStickyParent() = scrollView.llAdd + + /** + * 获得内部滑动的view,可设置Scroll相关操作 + */ + fun getScrollView(): NestedScrollView = scrollView + + /** + * 备用方法:当tag被占用时 + * + * @param child 直接或间接子类 + * @param tag [TAG_STICKY]、[TAG_MATCH] + */ + fun setChildTag(child: View, tag: String) { + child.setTag(R.id.tag_sticky, tag) + scrollView._stickyViews = null //重新获取 + requestLayout() + } + + companion object { + const val TAG_STICKY = "sticky" + const val TAG_MATCH = "match" + private const val MATCH_PARENT = ViewGroup.LayoutParams.MATCH_PARENT + private const val WRAP_CONTENT = ViewGroup.LayoutParams.WRAP_CONTENT + } +} \ No newline at end of file