【Android】折叠效果CoordinatorLayout+AppBarLayout首页效果&& CoordinatorLayout抖动问题解决方案--100个经典UI设计模板(95/100)

亲测效果如下:

在这里插入图片描述

布局结构

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:tools="http://schemas.android.com/tools">
    <android.support.design.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#F7F7F7"
        android:orientation="vertical"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        >

        <android.support.design.widget.AppBarLayout
            android:id="@+id/layout_app_bar"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:background="#F5F5F7"
            app:elevation="0dp">


            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                app:expandedTitleMarginEnd="60dp"
                app:contentScrim="@color/red"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">

                <!--头部背景图-->
                <ImageView
                    android:id="@+id/iv_header_bg"
                    android:layout_width="match_parent"
                    android:layout_height="220dp"
                    android:background="@color/purple_500"
                    app:layout_collapseMode="parallax"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />
                <!--                正在热播-->
                <android.support.constraint.ConstraintLayout
                    android:id="@+id/layout_living"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="55dp"
                    android:layout_marginBottom="19dp"
                    app:layout_collapseParallaxMultiplier="0.5">

                    <TextView
                        android:id="@+id/tv_hothint"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"

                        android:layout_marginTop="25dp"
                        android:text="正在热播"
                        android:textColor="#ffffffff"
                        android:textSize="16sp"
                        app:layout_constraintStart_toStartOf="@id/gl_start_hot_live"
                        app:layout_constraintTop_toTopOf="parent" />

                    <android.support.constraint.Guideline
                        android:id="@+id/gl_end_hot_live"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        app:layout_constraintGuide_end="20dp" />
                    <!-- 正在热播-->
                    <android.support.constraint.Guideline
                        android:id="@+id/gl_start_hot_live"
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:orientation="vertical"
                        app:layout_constraintGuide_begin="20dp" />

                    <android.support.v7.widget.RecyclerView
                        android:id="@+id/recycleView_live"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="10dp"
                        android:nestedScrollingEnabled="false"
                        app:layout_constraintEnd_toEndOf="parent"
                        app:layout_constraintStart_toStartOf="parent"
                        app:layout_constraintTop_toBottomOf="@id/tv_hothint" />
                </android.support.constraint.ConstraintLayout>

                <!--Toolbar放在下面不然会被挡住-->
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:popupTheme="@style/ThemeOverlay.AppCompat.Light"
                    app:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />

            </android.support.design.widget.CollapsingToolbarLayout>
            <!--            精彩视频-->
            <android.support.constraint.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:background="@color/white">

                <android.support.design.widget.TabLayout
                    android:id="@+id/layout_tab"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:fillViewport="false"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent"
                    app:tabIndicatorColor="#FF5252"
                    app:tabIndicatorFullWidth="false"
                    app:tabIndicatorHeight="1.0dp"
                    app:tabMaxWidth="100dp"
                    app:tabMinWidth="12dp"
                    app:tabMode="scrollable"
                    app:tabPaddingEnd="9dp"
                    app:tabPaddingStart="9dp"
                    app:tabSelectedTextColor="#FF666666"
                    app:tabTextColor="#66666666">
                    <!--指示器颜色-->
                    <!-- app:tabIndicatorColor="#0835f8"-->
                    <!--tab条目中字体颜色-->
                    <!--app:tabSelectedTextColor="#0835f8"-->
                    <android.support.design.widget.TabItem
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="最新" />

                    <android.support.design.widget.TabItem
                        android:layout_width="wrap_content"
                        android:layout_height="wrap_content"
                        android:text="最热" />

                </android.support.design.widget.TabLayout>
            </android.support.constraint.ConstraintLayout>
        </android.support.design.widget.AppBarLayout>

        <android.support.v4.widget.NestedScrollView
            android:id="@+id/nestedScrollView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:background="#FFFFFFFF"
            android:fillViewport="true"
            app:layout_behavior="@string/appbar_scrolling_view_behavior">

        </android.support.v4.widget.NestedScrollView>

    </android.support.design.widget.CoordinatorLayout>
    <!--返回、标题 更多-->
    <android.support.constraint.ConstraintLayout
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/layout_header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="30dp"
        app:layout_collapseMode="pin"
        app:layout_collapseParallaxMultiplier="0.5">

        <ImageView
            android:id="@+id/iv_return"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="TODO"
            app:layout_constraintStart_toStartOf="@id/gl_start"
            app:layout_constraintTop_toTopOf="parent" />

        <ImageView
            android:id="@+id/iv_header_icbc"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:contentDescription="TODO"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />
        <!--返回-->
        <ImageView
            android:id="@+id/ivmore"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"

            android:layout_marginRight="20dp"
            app:layout_constraintEnd_toEndOf="@id/gl_end"
            app:layout_constraintTop_toTopOf="parent" />

        <!--    标题图片-->
        <android.support.constraint.Guideline
            android:id="@+id/gl_end"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_end="20dp" />
        <!--更多-->
        <android.support.constraint.Guideline
            android:id="@+id/gl_start"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_begin="20dp" />
    </android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>

布局结构解析:
消除系统自带Toolbar的UI设计冲突问题。这样便于UI布局还原UI设计效果
折叠效果得到保证。头部布局放最后在上拉过程中不会被遮挡
这样的布局后续可以很方便的进化调整。比如进行滑动和tablayout联动等等
如果是不太复杂的首页这个可以直接拿来就用哦!

源码地址:
gitee:https://gitee.com/lc951/my-android
github:https://github.com/lichong951/MyAndroid/

参考

ConstraintLayout:约束布局ConstraintLayout看这一篇就够了
CollapsingToolbarLayout:android新特性:使用CollapsingToolbarLayout实现折叠效果及问题解决

CoordinatorLayout+AppBarLayout抖动问题

本想自己复现一下的,结果发现抖动效果没那么明显。借用一下这张图
在这里插入图片描述
解决方案如下:

/**
 * <pre>
 *     @author yangchong
 *     blog  : https://github.com/yangchong211
 *     time  : 2019/03/13
 *     desc  : 自定义Behavior
 *     revise: 解决appbarLayout若干问题
 *              1)快速滑动appbarLayout会出现回弹
 *              2)快速滑动appbarLayout到折叠状态下,立马下滑,会出现抖动的问题
 *              3)滑动appbarLayout,无法通过手指按下让其停止滑动
 * </pre>
 */
public class AppBarLayoutBehavior extends AppBarLayout.Behavior {

    private static final String TAG = "AppbarLayoutBehavior";
    private static final int TYPE_FLING = 1;
    private boolean isFlinging;
    private boolean shouldBlockNestedScroll;

    public AppBarLayoutBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @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) {
                headerBehaviorType = superclass.getSuperclass();
            }
            if (headerBehaviorType != null) {
                return headerBehaviorType.getDeclaredField("mFlingRunnable");
            }else {
                return null;
            }
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
            // 可能是28及以上版本
            Class<?> headerBehaviorType = superclass.getSuperclass().getSuperclass();
            if (headerBehaviorType != null) {
                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 = (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();
        }
    }

    @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);
    }

    @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);
        }
    }

    @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);
        }
    }

    @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);
        }
    }

}

应用:

<android.support.design.widget.AppBarLayout
    android:id="@+id/appbar"
    app:layout_behavior="org.yczbj.ycrefreshview.sticky.AppBarLayoutBehavior"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

关于这个抖动的问题已经有大佬博客写的很细致了。参考下面即可:
【亲测】CoordinatorLayout滑动抖动问题

解决CoordinatorLayout+AppBarLayout滑动抖动(回弹)问题

COORDINATORLAYOUT+APPBARLAYOUT+RECYCLERVIEW 滑动冲突引发屏幕抖动的解决方案

CoordinatorLayout滑动抖动问题

解决CoordinatorLayout的动画抖动以及回弹问题

CoordinatorLayout和AppBarLayout 嵌套滑动时抖动的一个原生bug

自研产品推荐

历时一年半多开发终于smartApi-v1.0.0版本在2023-09-15晚十点正式上线
smartApi是一款对标国外的postman的api调试开发工具,由于开发人力就作者一个所以人力有限,因此v1.0.0版本功能进行精简,大功能项有:

  • api参数填写
  • api请求响应数据展示
  • PDF形式的分享文档
  • Mock本地化解决方案
  • api列表数据本地化处理
  • 再加上UI方面的打磨

为了更好服务大家把之前的公众号和软件激活结合,如有疑问请大家反馈到公众号即可,下个版本30%以上的更新会来自公众号的反馈。
嗯!先解释不上服务端原因,API调试工具的绝大多数时候就是一个数据模型、数据处理、数据模型理解共识的问题解决工具,所以作者结合自己十多年开发使用的一些痛点来打造的,再加上服务端开发一般是面向企业的,作者目前没有精力和时间去打造企业服务。再加上没有资金投入所以服务端开发会滞后,至于什么时候会进行开发,这个要看募资情况和用户反馈综合考虑。虽然目前国内有些比较知名的api工具了,但作者使用后还是觉得和实际使用场景不符。如果有相关吐槽也可以在作者的公众号里反馈蛤!
下面是一段smartApi使用介绍:
在这里插入图片描述

下载地址:

https://pan.baidu.com/s/1iultkXqeLNG4_eNiefKTjQ?pwd=cnbl

posted @ 2022-01-25 10:41  lichong951  阅读(16)  评论(0编辑  收藏  举报  来源