只需在xml里即可实现吸顶效果

用到的控件:

父布局
CoordinatorLayout

子控件
AppBarLayout

XML实现:
<androidx.coordinatorlayout.widget.CoordinatorLayout android:layout_width="
match_parent" android:layout_height="wrap_content"> <
com.google.android.material.appbar.AppBarLayout android:layout_width="
match_parent" android:layout_height="wrap_content" android:background="
@color/colorBackground" app:layout_behavior=".utils.CustomBehavior"> <!--
app:layout_behavior 这段代码是针对滑动卡顿而加的,自身不卡顿的可以删除。 CustomBehavior 是自定义类,我把代码放在文章的下面
--> <!-- 滑动时优先隐藏此段布局 --> <LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_marginTop="@dimen/dp_10"
android:layout_marginBottom="@dimen/dp_10" android:orientation="vertical" app:
layout_scrollFlags="scroll"> <!-- app:layout_scrollFlags="scroll" 关键代码 --> <!--
这里存放滑动时需要隐藏的控件 --> </LinearLayout> <!-- 这里存放吸顶的控件 --> <LinearLayout android:id="
@+id/layout_hot" android:layout_width="match_parent" android:layout_height="
wrap_content" android:orientation="vertical"> </LinearLayout> </
com.google.android.material.appbar.AppBarLayout> <!-- 也可以嵌套NestedScrollView
或其他父布局。 同时加上这个属性:app:layout_behavior --> <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_height="match_parent"
android:background="@color/colorBackground" android:overScrollMode="never" app:
layout_behavior="@string/appbar_scrolling_view_behavior"> </
androidx.core.widget.NestedScrollView> </
androidx.coordinatorlayout.widget.CoordinatorLayout>
自定义CustomBehavior
import android.content.Context; import android.util.AttributeSet; import
android.util.Log; import android.view.MotionEvent; import android.view.View;
import android.widget.OverScroller; import
androidx.coordinatorlayout.widget.CoordinatorLayout; import
com.google.android.material.appbar.AppBarLayout; import
java.lang.reflect.Field; /** * 防止 AppBarLayout 滑动卡顿 */ public class
CustomBehavior extends AppBarLayout.Behavior { private static final String TAG
= "AppbarLayoutBehavior"; private static final int TYPE_FLING = 1; /** *
记录是否有fling */ private boolean isFlinging; /** * 记录是否 */ private boolean
shouldBlockNestedScroll; public CustomBehavior(Context context, AttributeSet
attrs) { super(context, attrs); } /** * 是否拦截触摸事件 * @param parent
CoordinatorLayout * @param child AppBarLayout * @param ev ev * @return */
@Override public boolean onInterceptTouchEvent(CoordinatorLayout parent,
AppBarLayout child, MotionEvent ev) { LogUtil.d(TAG, "onInterceptTouchEvent:" +
child.getTotalScrollRange()); shouldBlockNestedScroll = isFlinging; switch
(ev.getActionMasked()) { case MotionEvent.ACTION_DOWN: //手指触摸屏幕的时候停止fling事件
stopAppbarLayoutFling(child); break; default: break; } return
super.onInterceptTouchEvent(parent, child, ev); } /** * 反射获取私有的flingRunnable
属性,考虑support 28以后变量名修改的问题 * @return Field * @throws NoSuchFieldException */
private Field getFlingRunnableField() throws NoSuchFieldException { Class<?>
superclass = this.getClass().getSuperclass(); try { // support design 27及一下版本
Class<?> headerBehaviorType = null; if (superclass != null) { String name =
superclass.getName(); LogUtil.d("AppBarLayout.Behavior父类",name);
headerBehaviorType = superclass.getSuperclass(); } if (headerBehaviorType !=
null) { String name = headerBehaviorType.getName();
LogUtil.d("AppBarLayout.Behavior父类的父类1",name); return
headerBehaviorType.getDeclaredField("mFlingRunnable"); }else { return null; } }
catch (NoSuchFieldException e) { e.printStackTrace(); // 可能是28及以上版本 Class<?>
headerBehaviorType = superclass.getSuperclass().getSuperclass(); if
(headerBehaviorType != null) { String name = headerBehaviorType.getName();
LogUtil.d("AppBarLayout.Behavior父类的父类2",name); return
headerBehaviorType.getDeclaredField("flingRunnable"); } else { return null; } }
} /** * 反射获取私有的scroller 属性,考虑support 28以后变量名修改的问题 * @return Field * @throws
NoSuchFieldException */ private Field getScrollerField() throws
NoSuchFieldException { Class<?> superclass = this.getClass().getSuperclass();
try { // support design 27及一下版本 Class<?> headerBehaviorType = null; if
(superclass != null) { headerBehaviorType = superclass.getSuperclass(); } if
(headerBehaviorType != null) { return
headerBehaviorType.getDeclaredField("mScroller"); }else { return null; } }
catch (NoSuchFieldException e) { e.printStackTrace(); // 可能是28及以上版本 Class<?>
headerBehaviorType = superclass.getSuperclass().getSuperclass(); if
(headerBehaviorType != null) { return
headerBehaviorType.getDeclaredField("scroller"); }else { return null; } } } /**
* 停止appbarLayout的fling事件 * @param appBarLayout */ private void
stopAppbarLayoutFling(AppBarLayout appBarLayout) {
//通过反射拿到HeaderBehavior中的flingRunnable变量 try { Field flingRunnableField =
getFlingRunnableField(); Field scrollerField = getScrollerField(); if
(flingRunnableField != null) { flingRunnableField.setAccessible(true); } if
(scrollerField != null) { scrollerField.setAccessible(true); } Runnable
flingRunnable = null; if (flingRunnableField != null) { flingRunnable =
(Runnable) flingRunnableField.get(this); } OverScroller overScroller = null; if
(scrollerField != null) { overScroller = (OverScroller)
scrollerField.get(this); } //下面是关键点 if (flingRunnable != null) { LogUtil.d(TAG,
"存在flingRunnable"); appBarLayout.removeCallbacks(flingRunnable);
flingRunnableField.set(this, null); } if (overScroller != null &&
!overScroller.isFinished()) { overScroller.abortAnimation(); } } catch
(NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException
e) { e.printStackTrace(); } } /** * 嵌套滑动开始(ACTION_DOWN),确定Behavior是否要监听此次事件 */
@Override public boolean onStartNestedScroll(CoordinatorLayout parent,
AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes,
int type) { LogUtil.d(TAG, "onStartNestedScroll");
stopAppbarLayoutFling(child); return super.onStartNestedScroll(parent, child,
directTargetChild, target, nestedScrollAxes, type); } /** * 嵌套滑动进行中,要监听的子
View将要滑动,滑动事件即将被消费(但最终被谁消费,可以通过代码控制) */ @Override public void
onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View
target, int dx, int dy, int[] consumed, int type) { LogUtil.d(TAG,
"onNestedPreScroll:" + child.getTotalScrollRange() + " ,dx:" + dx + " ,dy:" +
dy + " ,type:" + type); //type返回1时,表示当前target处于非touch的滑动,
//该bug的引起是因为appbar在滑动时,CoordinatorLayout内的实现NestedScrollingChild2接口的滑动
//子类还未结束其自身的fling //所以这里监听子类的非touch时的滑动,然后block掉滑动事件传递给AppBarLayout if (type ==
TYPE_FLING) { isFlinging = true; } if (!shouldBlockNestedScroll) {
super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed,
type); } } /** * 嵌套滑动进行中,要监听的子 View的滑动事件已经被消费 */ @Override public void
onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child, View
target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int
type) { LogUtil.d(TAG, "onNestedScroll: target:" + target.getClass() + " ," +
child.getTotalScrollRange() + " ,dxConsumed:" + dxConsumed + " ,dyConsumed:" +
dyConsumed + " " + ",type:" + type); if (!shouldBlockNestedScroll) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, type); } } /** * 嵌套滑动结束(ACTION_UP或ACTION_CANCEL) */
@Override public void onStopNestedScroll(CoordinatorLayout coordinatorLayout,
AppBarLayout abl, View target, int type) { LogUtil.d(TAG,
"onStopNestedScroll"); super.onStopNestedScroll(coordinatorLayout, abl, target,
type); isFlinging = false; shouldBlockNestedScroll = false; } private static
class LogUtil{ static void d(String tag, String string){ Log.d(tag,string); } }
}

技术
下载桌面版
GitHub
Gitee
SourceForge
百度网盘(提取码:draw)
云服务器优惠
华为云优惠券
腾讯云优惠券
阿里云优惠券
Vultr优惠券
站点信息
问题反馈
邮箱:[email protected]
吐槽一下
QQ群:766591547
关注微信