IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> 【高级UI】【028】CoordinatorLayout源码解析和手动实现 -> 正文阅读

[游戏开发]【高级UI】【028】CoordinatorLayout源码解析和手动实现

CoordinatorLayout效果展示

在这里插入图片描述

CoordinatorLayout工作原理

CoordinatorLayout,可译为协调者布局,用于协调多个子View进行滑动交互

CoordinatorLayout实现滑动交互逻辑的核心是Behavior类,Behavior类规定子View间如何进行交互

CoordinatorLayout实现了一个自己的LayoutParam

CoordinatorLayout会为每个子View的LayoutParam设置一个Behavior

子View具体使用哪个Behavior,可以在xml布局中,通过自定义属性layout_behavior来指定

CoordinatorLayout在处理onTouchEvent时,会将事件转交给Behavior去处理,具体如何处理事件,是由Behavior中的逻辑来决定的

CoordinatorLayout交互动画

具体如何交互取决于Behavior代码,这里主要是介绍我们的Demo的动画逻辑

CoordinatorLayout包含三个子View,Text + Image + Toolbar

其中Text被NestedScrollView包裹,可以滑动

Toolbar在Image上层叠加显示,二者通过大小和透明度变化,形成滑动动画效果

默认状态:

Image完全展示,Toolbar透明度为0,Text滑动到顶端

Text向上滑动:

Toolbar透明度逐渐增加到100,Image逐渐变小,当Image和Toolbar一样大小时,不再变小,Toolbar透明度仍然继续增加

Text向下滑动:

Toolbar透明度逐渐减小到0,当Toolbar透明度为0时,Image逐渐变大,直到最大为止

NestedScrollView和CoordinatorLayout的关联

NestedScrollView是一个可以滚动的容器,它继承自FrameLayout,在基类的基础上增加了滑动功能

CoordinatorLayout继承自ViewGroup,它不处理任何布局功能,只是把布局工作转交给Behavior去处理

NestedScrollView和CoordinatorLayout联系起来的纽带在于

NestedScrollView实现了NestedScrollingChild接口,CoordinatorLayout实现了NestedScrollingParent接口

当Child进行滑动时,它会通知Parent,仅此而已

通过以上分析,我们基本可以确定这样一个事实

NestedScrollView仅负责滑动,CoordinatorLayout负责将布局事件和滑动事件等全部工作转发给子View

Behavior才是决定,子View如何进行布局,如何进行滑动交互的关键类

CoordinatorLayout的简单替代方案

其实,上面的动画,我们就算不用CoordinatorLayout,也可以很轻松地手动实现

无非就是监听ScrollView的滑动距离变化,然后控制Image大小和Toolbar透明度,这么简单的事情而已

其实CoordinatorLayout最终的工作,也就是这么简单而已

CoordinatorLayout出现的真正意义在于,它抽象了一套关于滑动交互逻辑的接口

使用CoordinatorLayout的话,全部的工作最后是交给Behavior去处理的

当我们需要使用新的子View,新的交互动画时,只要替换Behavior的实现类即可

可以看出,CoordinatorLayout的真正意义在于解耦,将交互动画的逻辑和控件解耦了,无需修改控件

如果我们用最简单的方法去实现,那么只要子View类型换一下,布局换一下,交互动画换一下,就得重新写一套控件

核心代码


	<?xml version="1.0" encoding="utf-8"?>
	<CoordinatorLayout 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:id="@+id/activity_main"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent"
	    tools:context=".MainActivity">
	
	    <ImageView
	        android:id="@+id/image"
	        android:layout_width="match_parent"
	        android:layout_height="200dp"
	        android:scaleType="centerCrop"
	        android:src="@mipmap/main"
	        app:behavior="com.dongnao.lsn_16_classdemo.design.ImageBehavior"
	        app:max_height="200dp"
	        app:min_height="60dp" />
	
	    <android.support.v7.widget.Toolbar
	        android:id="@+id/toolbar"
	        android:layout_width="match_parent"
	        android:layout_height="60dp"
	        android:alpha="0"
	        android:background="#3F51B5"
	        app:behavior="com.dongnao.lsn_16_classdemo.design.ToolbarBehavior"
	        app:threshold="150dp" />
	
	    <android.support.v4.widget.NestedScrollView
	        android:layout_width="match_parent"
	        android:layout_height="match_parent"
	        android:layout_below="@id/image">
	
	        <TextView
	            android:layout_width="wrap_content"
	            android:layout_height="wrap_content"
	            android:text="@string/large_text" />
	    </android.support.v4.widget.NestedScrollView>
	</CoordinatorLayout>


	@SuppressWarnings("all")
	public class CoordinatorLayout extends RelativeLayout implements NestedScrollingParent, ViewTreeObserver.OnGlobalLayoutListener {
	
	    float lastX;
	    float lastY;
	
	    public CoordinatorLayout(Context context) {
	        super(context);
	    }
	
	    public CoordinatorLayout(Context context, AttributeSet attrs) {
	        super(context, attrs);
	    }
	
	    public LayoutParams generateLayoutParams(AttributeSet attrs) {
	        return new LayoutParams(getContext(), attrs);
	    }
	
	    @Override
	    protected void onFinishInflate() {
	        super.onFinishInflate();
	        getViewTreeObserver().addOnGlobalLayoutListener(this);
	    }
	
	    @Override
	    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
	        super.onSizeChanged(w, h, oldw, oldh);
	        for (int i = 0; i < getChildCount(); i++) {
	            View v = getChildAt(i);
	            LayoutParams lp = (LayoutParams) v.getLayoutParams();
	            if (lp.behavior != null)
	                lp.behavior.onSizeChanged(this, v, w, h, oldw, oldh);
	        }
	    }
	
	    @Override
	    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
	        return true;
	    }
	
	    //当NestedScrollingChild发生滑动时,会调用此方法来通知NestedScrollingParent滑动了多少距离
	    //最常见的情景是,NestedScrollView通知CoordinatorLayout
	    //target:一般为NestedScrollView
	    //dyConsumed:NestedScrollView单次已消费滑动距离
	    //dyUnconsumed:NestedScrollView单次未消费滑动距离
	    //未消费的滑动距离是指,ScrollView已经滑动到顶端,手继续往下拉,但是控件内容缺没有继续滑动,手额外拉的这部分距离就是未消费的滑动距离
	    @Override
	    public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
	        for (int i = 0; i < getChildCount(); i++) {
	            View v = getChildAt(i);
	            LayoutParams lp = (LayoutParams) v.getLayoutParams();
	            //关键代码,转发滑动事件
	            if (lp.behavior != null)
	                lp.behavior.onNestedScroll(target, v, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
	        }
	    }
	
	    @Override
	    public void onGlobalLayout() {
	        for (int i = 0; i < getChildCount(); i++) {
	            View v = getChildAt(i);
	            LayoutParams lp = (LayoutParams) v.getLayoutParams();
	            if (lp.behavior != null)
	                lp.behavior.onLayoutFinish(this, v);
	        }
	    }
	
	    public class LayoutParams extends RelativeLayout.LayoutParams {
	
	        protected Behavior behavior;
	
	        public LayoutParams(Context context, AttributeSet attributeSet) {
	            super(context, attributeSet);
	            TypedArray a = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
	            String clazzName = a.getString(R.styleable.MyCoordinatorLayout_behavior);
	            behavior = parseBehaivor(context, attributeSet, clazzName);
	            a.recycle();
	        }
	
	        public LayoutParams(int w, int h) {
	            super(w, h);
	        }
	
	        public LayoutParams(ViewGroup.LayoutParams source) {
	            super(source);
	        }
	
	        public LayoutParams(MarginLayoutParams source) {
	            super(source);
	        }
	
	        public LayoutParams(RelativeLayout.LayoutParams source) {
	            super(source);
	        }
	
	        protected Behavior parseBehaivor(Context context, AttributeSet attrs, String name) {
	            try {
	                Class<?> aClass = Class.forName(name, true, context.getClassLoader());
	                Constructor c = aClass.getConstructor(new Class[]{Context.class, AttributeSet.class});
	                c.setAccessible(true);
	                return (Behavior) c.newInstance(context, attrs);
	            } catch (Throwable e) {
	                e.printStackTrace();
	            }
	            return null;
	        }
	    }
	}




	@SuppressWarnings("all")
	public class Behavior {
	
	    public Behavior(Context context, AttributeSet attrs) {
	
	    }
	
	    public void onLayoutFinish(View parent, View child) {
	
	    }
	
	    public void onSizeChanged(View parent, View child, int w, int h, int oldw, int oldh) {
	
	    }
	
	    public boolean onTouchEvent(CoordinatorLayout parent, View child, MotionEvent ev) {
	        return false;
	    }
	
	    public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
	        return false;
	    }
	
	    public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
	        return true;
	    }
	
	    public void onStopNestedScroll(View child) {
	
	    }
	
	    public void onNestedScrollAccepted(View child, View target, int axes) {
	
	    }
	
	    public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
	
	    }
	
	    public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
	
	    }
	
	    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
	        return false;
	    }
	
	    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
	        return false;
	    }
	}


	@SuppressWarnings("all")
	public class ToolbarBehavior extends Behavior {
	
	    Float threshold;
	
	    public ToolbarBehavior(Context context, AttributeSet attributeSet) {
	        super(context, attributeSet);
	        TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
	        threshold = attributes.getDimension(R.styleable.MyCoordinatorLayout_threshold, 0);
	    }
	
	    @Override
	    public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
	        //往下拉逐渐增加不透明度,往上拉逐渐减小不透明度
	        float alpha = scrollView.getScrollY() / threshold;
	        if (alpha > 1)
	            alpha = 1;
	        target.setAlpha(alpha);
	        //设置标题
	        Toolbar toolbar = (Toolbar) target;
	        toolbar.setTitleTextColor(Color.WHITE);
	        toolbar.setSubtitleTextColor(Color.WHITE);
	        toolbar.setTitle("CoordinatorLayout");
	        toolbar.setSubtitle("a demo for coordinator layout");
	    }
	}


	@SuppressWarnings("all")
	public class ImageBehavior extends Behavior {
	
	    Float minHeight;
	    Float maxHeight;
	
	    public ImageBehavior(Context context, AttributeSet attributeSet) {
	        super(context, attributeSet);
	        TypedArray attributes = context.obtainStyledAttributes(attributeSet, R.styleable.MyCoordinatorLayout);
	        minHeight = attributes.getDimension(R.styleable.MyCoordinatorLayout_min_height, 0);
	        maxHeight = attributes.getDimension(R.styleable.MyCoordinatorLayout_max_height, 0);
	    }
	
	    @Override
	    public void onNestedScroll(View scrollView, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
	        int scroll = scrollView.getScrollY();
	        //最顶端往上拉,滑动值变大,图片逐渐变小
	        if (scroll > 0) {
	            ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
	            layoutParams.height = layoutParams.height - Math.abs(dyConsumed);
	            if (layoutParams.height < minHeight)
	                layoutParams.height = minHeight.intValue();
	            target.setLayoutParams(layoutParams);
	        }
	        //最顶端往下拉,滑动值不变,图片逐渐增大
	        if (scroll == 0) {
	            ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
	            layoutParams.height = layoutParams.height + Math.abs(dyUnconsumed);
	            if (layoutParams.height >= maxHeight)
	                layoutParams.height = maxHeight.intValue();
	            target.setLayoutParams(layoutParams);
	        }
	    }
	}

源码下载

CoordinatorLayout源码解析和手动实现.zip

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-04-01 23:44:40  更:2022-04-01 23:45:28 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/16 19:05:20-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码