【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和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使用介绍: