文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Android嵌套滚动与协调滚动如何实现

2023-07-02 10:24

关注

本文小编为大家详细介绍“Android嵌套滚动与协调滚动如何实现”,内容详细,步骤清晰,细节处理妥当,希望这篇“Android嵌套滚动与协调滚动如何实现”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。

一、CoordinatorLayout + Behavior

CoordinatorLayout 顾名思义是协调布局,其原理很简单,在onMeasure()的时候保存childView,通过 PreDrawListener监听childView的变化,最终通过双层for循环找到对应的Behavior,分发任务即可。CoordinatorLayout实现了NestedScrollingParent2,那么在childView实现了NestedScrollingChild方法时候也能解决滑动冲突问题。

而Behavior就是一个应用于View的观察者模式,一个View跟随者另一个View的变化而变化,或者说一个View监听另一个View。

在Behavior中,被观察View 也就是事件源被称为denpendcy,而观察View,则被称为child。

一般自定义Behavior来说分两种情况:

这里我们以之前的效果为主来实现自定义的Behavior,先设置NestedScrollView在ImageView下面:

public class MyScrollBehavior extends ViewOffsetBehavior<NestedScrollView> {    private int topImgHeight;    private int topTextHeight;    public MyScrollBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,                                   @NonNull View dependency) {        return dependency instanceof ImageView ;    }    @Override    protected void layoutChild(CoordinatorLayout parent, NestedScrollView child, int layoutDirection) {        super.layoutChild(parent, child, layoutDirection);        if (topImgHeight == 0) {            final List<View> dependencies = parent.getDependencies(child);            for (int i = 0, z = dependencies.size(); i < z; i++) {                View view = dependencies.get(i);                if (view instanceof ImageView) {                    topImgHeight = view.getMeasuredHeight();                }             }        }        child.setTop(topImgHeight);        child.setBottom(child.getBottom() + topImgHeight);    }}

然后设置监听CoordinatorLayout里的滑动状态,ImageView做同样的滚动

public class MyImageBehavior extends CoordinatorLayout.Behavior<View> {    private int topBarHeight = 0;  //负图片高度    private int downEndY = 0;   //默认为0    public MyImageBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,                                       @NonNull View child, @NonNull View directTargetChild,                                       @NonNull View target, int axes, int type) {        //监听垂直滚动        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;    }    @Override    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,                                  @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {        if (topBarHeight == 0) {            topBarHeight = -child.getMeasuredHeight();        }        float transY = child.getTranslationY() - dy;        //处理上滑        if (dy > 0) {            if (transY >= topBarHeight) {                translationByConsume(child, transY, consumed, dy);                translationByConsume(target, transY, consumed, dy);            } else {                translationByConsume(child, topBarHeight, consumed, (child.getTranslationY() - topBarHeight));                translationByConsume(target, topBarHeight, consumed, (child.getTranslationY() - topBarHeight));            }        }        if (dy < 0 && !target.canScrollVertically(-1)) {            //处理下滑            if (transY >= topBarHeight && transY <= downEndY) {                translationByConsume(child, transY, consumed, dy);                translationByConsume(target, transY, consumed, dy);            } else {                translationByConsume(child, downEndY, consumed, (downEndY - child.getTranslationY()));                translationByConsume(target, downEndY, consumed, (downEndY - child.getTranslationY()));            }        }    }    @Override    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {        return super.onNestedFling(coordinatorLayout, child, target, velocityX,                velocityY, consumed);    }    private void translationByConsume(View view, float translationY, int[] consumed, float consumedDy) {        consumed[1] = (int) consumedDy;        view.setTranslationY(translationY);    }}

分别为ImageView和NestedScrollView设置对应的 Behavior。

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical">    <com.guadou.lib_baselib.view.titlebar.EasyTitleBar        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:Easy_title="CoordinatorLayout+Behavior" />    <androidx.coordinatorlayout.widget.CoordinatorLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <ImageView            android:layout_width="150dp"            android:layout_height="150dp"            app:layout_behavior="com.google.android.material.appbar.MyImageBehavior"            android:layout_gravity="center_horizontal"            android:contentDescription="我是测试的图片"            android:src="@mipmap/ic_launcher" />        <TextView            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#ccc"            android:gravity="center"            android:text="我是测试的分割线"            android:visibility="gone" />        <androidx.core.widget.NestedScrollView            android:id="@+id/nestedScroll"            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:layout_behavior="com.google.android.material.appbar.MyScrollBehavior">            <TextView                android:id="@+id/nestedScrollLayout"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:text="@string/scroll_content" />        </androidx.core.widget.NestedScrollView>    </androidx.coordinatorlayout.widget.CoordinatorLayout></LinearLayout>

我们先把TextView隐藏先不处理TextView。

这样我们就实现了自定义 Behavior 监听滚动的实现。那么我们加上TextView 的 Behavior 监听ImageView的滚动,做对应的滚动。

先修改 MyScrollBehavior 让他在ImageView和TextView下面

public class MyScrollBehavior extends ViewOffsetBehavior<NestedScrollView> {    private int topImgHeight;    private int topTextHeight;    public MyScrollBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull NestedScrollView child,                                   @NonNull View dependency) {        return dependency instanceof ImageView || dependency instanceof TextView ;    }    @Override    protected void layoutChild(CoordinatorLayout parent, NestedScrollView child, int layoutDirection) {        super.layoutChild(parent, child, layoutDirection);        if (topImgHeight == 0) {            final List<View> dependencies = parent.getDependencies(child);            for (int i = 0, z = dependencies.size(); i < z; i++) {                View view = dependencies.get(i);                if (view instanceof ImageView) {                    topImgHeight = view.getMeasuredHeight();                } else if (view instanceof TextView) {                    topTextHeight = view.getMeasuredHeight();                    view.setTop(topImgHeight);                    view.setBottom(view.getBottom() + topImgHeight);                }            }        }        child.setTop(topImgHeight + topTextHeight);        child.setBottom(child.getBottom() + topImgHeight + topTextHeight);    }}

然后设置监听ImageView的滚动:

public class MyTextBehavior extends CoordinatorLayout.Behavior<View> {    private int imgHeight;    public MyTextBehavior(Context context, AttributeSet attrs) {        super(context, attrs);    }    @Override    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {        return dependency instanceof ImageView;    }    @Override    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {        //跟随ImageView滚动,ImageView滚动多少我滚动多少        float translationY = dependency.getTranslationY();        if (imgHeight == 0) {            imgHeight = dependency.getHeight();        }        float offsetTranslationY = imgHeight + translationY;        child.setTranslationY(offsetTranslationY);        return true;    }}

xml修改如下:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical">    <com.guadou.lib_baselib.view.titlebar.EasyTitleBar        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:Easy_title="CoordinatorLayout+Behavior" />    <androidx.coordinatorlayout.widget.CoordinatorLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <ImageView            android:layout_width="150dp"            android:layout_height="150dp"            app:layout_behavior="com.google.android.material.appbar.MyImageBehavior"            android:layout_gravity="center_horizontal"            android:contentDescription="我是测试的图片"            android:src="@mipmap/ic_launcher" />        <TextView            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#ccc"            app:layout_behavior="com.google.android.material.appbar.MyTextBehavior"            android:gravity="center"            android:text="我是测试的分割线"            android:visibility="visible" />        <androidx.core.widget.NestedScrollView            android:id="@+id/nestedScroll"            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:layout_behavior="com.google.android.material.appbar.MyScrollBehavior">            <TextView                android:id="@+id/nestedScrollLayout"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:text="@string/scroll_content" />        </androidx.core.widget.NestedScrollView>    </androidx.coordinatorlayout.widget.CoordinatorLayout></LinearLayout>

看到上面的示例,我们把常用的几种 Behavior 都使用了一遍,系统的ViewOffsetBehavior 和监听滚动的 Behavior 监听View的 Behavior。

为了实现这么一个简单的效果就用了这么多类,这么复杂。我分分钟就能实现!

行行,我知道你厉害,这不是为了演示同样的效果,使用不同的方式实现嘛。通过 Behavior 可以实现一些嵌套滚动不能完成的效果,比如鼎鼎大名的支付宝首页效果,美团详情效果等。Behavior 更加的灵活,控制的粒度也更加的细。

但是如果只是简单实现上面的效果,我们可以用 AppBarLayout + 内部自带的 Behavior 也能实现类似的效果,AppBarLayout内部已经封装并使用了 Behavior 。我们看看如何实现。

二、CoordinatorLayout + AppBarLayout

其实内部也是基于 Behavior 实现的,内部实现为 HeaderBehavior 和 HeaderScrollingViewBehavior 。

对一些场景使用进行了封装,滚动效果,吸顶效果,折叠效果等。我们看看同样的效果,使用 AppBarLayout 如何实现吧:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:background="@color/white"    android:orientation="vertical">    <com.guadou.lib_baselib.view.titlebar.EasyTitleBar        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:Easy_title="CoordinatorLayout+AppBarLayout" />    <androidx.coordinatorlayout.widget.CoordinatorLayout        android:layout_width="match_parent"        android:layout_height="match_parent">        <com.google.android.material.appbar.AppBarLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:elevation="0dp"            android:background="@color/white"            android:orientation="vertical">            <ImageView                android:layout_width="match_parent"                android:layout_height="150dp"                android:contentDescription="我是测试的图片"                android:src="@mipmap/ic_launcher"                app:layout_scrollFlags="scroll" />            <TextView                android:layout_width="match_parent"                android:layout_height="50dp"                android:background="#ccc"                android:gravity="center"                android:text="我是测试的分割线"                app:layout_scrollFlags="noScroll" />        </com.google.android.material.appbar.AppBarLayout>        <androidx.core.widget.NestedScrollView            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:layout_behavior="@string/appbar_scrolling_view_behavior">            <TextView                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:text="@string/scroll_content" />        </androidx.core.widget.NestedScrollView>    </androidx.coordinatorlayout.widget.CoordinatorLayout></LinearLayout>

So Easy ! 真的是太方便了,类似的效果我们都能使用 AppbarLayout 来实现,比如一些详情页面顶部图片,下面列表或ViewPager的都可以使用这种方式,更加的便捷。

三、MotionLayout

不管怎么说,AppbarLayout 只能实现一些简单的效果,如果想要一些粒度比较细的效果,我们还得使用自定义 Behavior 来实现,但是它的实现确实是有点复杂,2019年谷歌推出了 MotionLayout 。

淘宝的出现可以说让世上没有难做的生意,那么 MotionLayout 的出现可以说让 Android 没有难实现的动画了。不管是动画效果,滚动效果,MotionLayout 绝杀!能用 Behavior 实现的 MotionLayout 几乎是都能做。

使用 MotionLayout 我们只需要定义起始点和结束点就行了,我们这里不需要根据百分比Fram进行别的操作,所以只定义最简单的使用。

我们看看如何用 MotionLayout 实现同样的效果:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    xmlns:app="http://schemas.android.com/apk/res-auto"    android:background="@color/white"    android:orientation="vertical">    <com.guadou.lib_baselib.view.titlebar.EasyTitleBar        android:layout_width="match_parent"        android:layout_height="wrap_content"        app:Easy_title="MotionLayout的动作" />    <androidx.constraintlayout.motion.widget.MotionLayout        android:layout_width="match_parent"        android:layout_weight="1"        app:layoutDescription="@xml/scene_scroll_13"        android:layout_height="0dp">        <ImageView            android:id="@+id/iv_img"            android:layout_width="150dp"            android:layout_height="150dp"            android:scaleType="centerCrop"            android:contentDescription="我是测试的图片"            android:src="@mipmap/ic_launcher" />        <TextView            android:id="@+id/tv_message"            android:layout_width="match_parent"            android:layout_height="50dp"            android:background="#ccc"            android:gravity="center"            android:text="我是测试的分割线"            tools:layout_editor_absoluteY="150dp" />        <androidx.core.widget.NestedScrollView            android:id="@+id/nestedScroll"            android:layout_width="match_parent"            android:layout_height="wrap_content">            <TextView                android:id="@+id/nestedScrollLayout"                android:layout_width="match_parent"                android:layout_height="wrap_content"                android:text="@string/scroll_content" />        </androidx.core.widget.NestedScrollView>    </androidx.constraintlayout.motion.widget.MotionLayout></LinearLayout>

定义的scene_scroll_13.xml

<?xml version="1.0" encoding="utf-8"?><MotionScene xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:motion="http://schemas.android.com/apk/res-auto">    <Transition        motion:constraintSetEnd="@+id/end"        motion:constraintSetStart="@+id/start">        <OnSwipe            motion:dragDirection="dragUp"            motion:touchAnchorId="@id/nestedScroll" />    </Transition>    <ConstraintSet android:id="@+id/start">        <Constraint            android:id="@id/iv_img"            android:layout_width="150dp"            android:layout_height="150dp"            android:translationY="0dp"            motion:layout_constraintLeft_toLeftOf="parent"            motion:layout_constraintRight_toRightOf="parent"            motion:layout_constraintTop_toTopOf="parent" />        <Constraint            android:id="@id/tv_message"            android:layout_width="match_parent"            android:layout_height="50dp"            motion:layout_constraintTop_toBottomOf="@id/iv_img" />        <Constraint            android:id="@id/nestedScroll"            android:layout_width="match_parent"            android:layout_height="0dp"            motion:layout_constraintBottom_toBottomOf="parent"            motion:layout_constraintTop_toBottomOf="@id/tv_message" />    </ConstraintSet>    <ConstraintSet android:id="@+id/end">        <Constraint            android:id="@id/iv_img"            android:layout_width="150dp"            android:layout_height="150dp"            android:translationY="-150dp"            motion:layout_constraintLeft_toLeftOf="parent"            motion:layout_constraintRight_toRightOf="parent"            motion:layout_constraintTop_toTopOf="parent" />        <Constraint            android:id="@id/tv_message"            android:layout_width="match_parent"            android:layout_height="50dp"            motion:layout_constraintLeft_toLeftOf="parent"            motion:layout_constraintRight_toRightOf="parent"            motion:layout_constraintTop_toTopOf="parent" />        <Constraint            android:id="@id/nestedScroll"            android:layout_width="match_parent"            android:layout_height="0dp"            motion:layout_constraintBottom_toBottomOf="parent"            motion:layout_constraintTop_toBottomOf="@id/tv_message" />    </ConstraintSet></MotionScene>

读到这里,这篇“Android嵌套滚动与协调滚动如何实现”文章已经介绍完毕,想要掌握这篇文章的知识点还需要大家自己动手实践使用过才能领会,如果想了解更多相关内容的文章,欢迎关注编程网行业资讯频道。

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     807人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     351人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     314人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     433人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     221人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯