热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

自定义Behavior之Floating控件进阶版

本篇文章介绍使用CoordinatorLayout的自定义Behavior来实现如下的效果本例效果和上篇文章的效果类似,因此建议先阅读上篇文章自定义Behavior之ToolBar上

本篇文章介绍使用CoordinatorLayout的自定义Behavior来实现如下的效果

这里写图片描述

本例效果和上篇文章的效果类似, 因此建议先阅读上篇文章

自定义Behavior之ToolBar上滑TabLayout颜色渐变

分析本例效果

首先我们来分析下整个例子需要实现哪些效果:

NestedScrollView上滑和下滑时覆盖背景
ImageView跟随NestedScrollView放大缩小与位移
滑动时会有黏性效果
滑动距离超过中间值后放开会自动滑向想要的方向
滑动距离未超过中间值放开则会自动回弹

本例需要的几个重要方法介绍

我们的例子中重写了Behavior的几个重要方法:

  • layoutDependsOn
  • onDependentViewChanged
  • onLayoutChild
  • onStartNestedScroll
  • onNestedPreScroll
  • onNestedScroll
  • onStopNestedScroll
  • onNestedScrollAccepted
  • onNestedPreFling
  • onStartNestedScroll

这些方法具体的说明可以参考:CoordinatorLayout自定义Behavior的简单总结

自定义 Behavior 实现思路

本例使用了两个自定义behavior, NestedScrollView不设依赖, 仅改变自己的位置, imageView将NestedScrollView作为依赖视图,通过获取NestedScrollView位置的改变, 计算出一个百分比值, 利用这个百分比值来调整imageView的位置以及大小

实现过程具体分析

首先继承Behavior, 在构造函数内做初始化工作

public class ContentScrollBehavior extends CoordinatorLayout.Behavior<View> {
private static final String TAG = ContentScrollBehavior.class.getSimpleName();
private WeakReference mChildView;
private OverScroller mOverScroller;
private Handler mHandler;
// 依赖视图起始y坐标
private float mDependencyOriginalY;
// 依赖视图最终y坐标
private float mDependencyFinalY;
private boolean isScrolling = false;

public ContentScrollBehavior(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
mOverScroller = new OverScroller(context);
mHandler = new Handler();
mDependencyOriginalY = context.getResources().getDimensionPixelOffset(R.dimen.content_height);
mDependencyFinalY = context.getResources().getDimensionPixelOffset(R.dimen.content_offset);
}
.....
}

mDependencyOriginalY和mDependencyFinalY是NestedScrollView的初始Y位置和最终Y位置

@Override
public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
if (params != null && params.height == CoordinatorLayout.LayoutParams.MATCH_PARENT) {
child.layout(0, 0, parent.getWidth(), parent.getHeight());
child.setTranslationY(mDependencyOriginalY);
mChildView = new WeakReference<>(child);
return true;
}

return super.onLayoutChild(parent, child, layoutDirection);
}

在onLayoutChild里设置NestedScrollView的初始位置

@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}

onStartNestedScroll在用户按下手指的时候回调,该方法在返回true的时候才会引发其他一系列的回调,这里我们只需要考虑垂直滑动,因此在垂直滑动条件成立的时候返回true

@Override
public void onNestedScrollAccepted(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {
isScrolling = false;
mOverScroller.abortAnimation();
super.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes);
}

onNestedScrollAccepted方法里我们可以做一些准备工作,比如让之前的滑动动画结束

@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View get, int dx, int dy, int[] consumed) {
//上滑操作dy值大于0,因此小于0直接分发给onNestedScroll
if (dy <0) {
return;
}

//上滑后的位置不小于mDependencyFinalY(100dp)
float transY = child.getTranslationY() - dy;
if (transY > 0 && transY >= mDependencyFinalY) {
child.setTranslationY(transY);
consumed[1] = dy;
}
}

@Override
public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
//上滑到底部并越界后dyUnconsumed值大于0直接忽略
if (dyUnconsumed > 0) {
return;
}

//下滑后的位置不大于mDependencyOriginalY(250dp)
float transY = child.getTranslationY() - dyUnconsumed;
if (transY > 0 && transY <= mDependencyOriginalY) {
child.setTranslationY(transY);
}
}

由于onNestedPreScroll方法会优先于onNestedScroll之前调用,因此我们可以将上滑动作分配到onNestedPreScroll,下滑动作分配到onNestedScroll,我们来分析下这样实现的原理:

  • 上滑
    当用户上滑时onNestedPreScroll优先调用,我们判断滑动方向,向上滑动才继续执行,通过调整子视图自己的translateY值来进行上移操作,并且消耗相应的consumed值,之后会回调onNestedScroll方法,如果dyUnconsumed还有值的话说明没有上滑操作没有完成,直接中断,然后继续回调onNestedPreScroll方法,重复一遍上面的操作,直到onNestedScroll方法里的dyUnconsumed消耗到0时就表示上滑到头了,整个上滑操作完成
  • 下滑
    我们在onNestedPreScroll方法中只有上滑时dy>0的情况才继续执行,因此下滑时dy<0的值不会在onNestedPreScroll中消耗掉,会直接传递到onNestedScroll方法中的dyUnconsumed,然后我们可以通过调整子视图自己的translateY值来进行下移操作,并消耗相应的dyUnconsumed值,然后不断重复上面步骤直到依赖视图完全实现完毕,整个下滑操作完成

最后解释下为什么要分别分配到两个方法中,因为子视图是NestedScrollView, 内部还包含了一个子视图(textview), 如果NestedScrollView还没消费完移动事件,而NestedScrollView内的子视图又可以向下滚动,这时我们就不能决定是让NestedScrollView位移还是NestedScrollView内的子视图滚动了,只有让NestedScrollView先消费完移动事件才能保证唯一性

@Override
public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, View child, View target, float velocityX, float velocityY) {
return onUserStopDragging(velocityY, child);
}

用户松开手指并且会发生惯性滚动之前调用,在这个方法内我们可以实现快速上滑或者快速下滑的操作

@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target) {
if (!isScrolling) {
onUserStopDragging(800, child);
}
}

用户松开手指如果不发生惯性滚动,就会执行该方法,这里我们可以用来实现黏性滑动的效果

private boolean onUserStopDragging(float velocity, View child) {
float translateY = child.getTranslationY();
float minHeaderTranslate = mDependencyFinalY;
float maxHeaderTranslate = mDependencyOriginalY;
float midHeaderTranslate = maxHeaderTranslate - minHeaderTranslate;
//中间态对比参数
float y = translateY - maxHeaderTranslate;
float ym = y + midHeaderTranslate;

if (translateY == mDependencyFinalY || translateY == mDependencyOriginalY) {
return false;
}

//在这里计算有没有超过中间态
boolean targetState; // Flag indicates whether to expand the content.
if (Math.abs(velocity) <= 800) {
//y范围(-450~0) ym范围(0~450),取绝对值比大小来判断滑动距离有没有超过1/2
targetState = Math.abs(y) >= Math.abs(ym);
velocity = 800; // Limit velocity's minimum value.
} else {
targetState = velocity > 0;
}

//根据targetState判断,超过中间态自动滑动剩余距离,没有则回到原处
float targetTranslateY = targetState ? minHeaderTranslate : maxHeaderTranslate;

//根据targetTranslateY的值来减去translateY来计算dy
mOverScroller.startScroll(0, (int) translateY, 0, (int) (targetTranslateY - translateY), (int) (1000000 / Math.abs(velocity)));
mHandler.post(flingRunnable);
isScrolling = true;

return true;
}

private Runnable flingRunnable = new Runnable() {
@Override
public void run() {
if (mOverScroller.computeScrollOffset()) {
getChildView().setTranslationY(mOverScroller.getCurrY());
mHandler.post(this);
} else {
isScrolling = false;
}
}
};

实现黏性滑动的代码, 如果提供了速度的话使用速度来滑动, 否则使用默认速度来滑动, 如果手指滑动如果超过中间值的话滑动到目标位置, 否则滑回原来的位置, 在计算出需要滑动的剩余距离后, 通过Scroller 配合 Handler 来实现该效果

最后贴一下xml布局


<android.support.design.widget.CoordinatorLayout
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"
android:orientation="vertical">


<ImageView
android:id="@+id/scrolling_header"
android:layout_width="match_parent"
android:layout_height="250dp"
android:background="@mipmap/banner3"/>


<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimary"
android:fitsSystemWindows="true"
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>


<TextView
android:id="@+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="350dp"
android:paddingBottom="60dp"
android:text="想告别起床气,快去定个闹铃吧!"
android:textColor="@android:color/white"
app:layout_anchor="@id/scrollView"
app:layout_anchorGravity="top|center_horizontal"
/>


<android.support.v4.widget.NestedScrollView
android:id="@+id/scrollView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/white"
app:layout_behavior="@string/content_behavior">


<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/large_text"/>


android.support.v4.widget.NestedScrollView>

<ImageView
android:id="@+id/add_alarm_clock"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@mipmap/add_alarm_clock_button_pressed"
app:layout_anchor="@id/scrolling_header"
app:layout_anchorGravity="center"
app:layout_behavior="@string/floating_image_behavoir"
/>


android.support.design.widget.CoordinatorLayout>

代码示例:
MaterialDesignFeatures

参考:
http://www.jianshu.com/p/7f50faa65622
http://www.jianshu.com/p/82d18b0d18f4

1


推荐阅读
  • 使用 ListView 浏览安卓系统中的回收站文件 ... [详细]
  • 本文介绍了一种自定义的Android圆形进度条视图,支持在进度条上显示数字,并在圆心位置展示文字内容。通过自定义绘图和组件组合的方式实现,详细展示了自定义View的开发流程和关键技术点。示例代码和效果展示将在文章末尾提供。 ... [详细]
  • 在处理 XML 数据时,如果需要解析 `` 标签的内容,可以采用 Pull 解析方法。Pull 解析是一种高效的 XML 解析方式,适用于流式数据处理。具体实现中,可以通过 Java 的 `XmlPullParser` 或其他类似的库来逐步读取和解析 XML 文档中的 `` 元素。这样不仅能够提高解析效率,还能减少内存占用。本文将详细介绍如何使用 Pull 解析方法来提取 `` 标签的内容,并提供一个示例代码,帮助开发者快速解决问题。 ... [详细]
  • 深入解析 Android 中 EditText 的 getLayoutParams 方法及其代码应用实例 ... [详细]
  • ButterKnife 是一款用于 Android 开发的注解库,主要用于简化视图和事件绑定。本文详细介绍了 ButterKnife 的基础用法,包括如何通过注解实现字段和方法的绑定,以及在实际项目中的应用示例。此外,文章还提到了截至 2016 年 4 月 29 日,ButterKnife 的最新版本为 8.0.1,为开发者提供了最新的功能和性能优化。 ... [详细]
  • 在探讨如何在Android的TextView中实现多彩文字与多样化字体效果时,本文提供了一种不依赖HTML技术的解决方案。通过使用SpannableString和相关的Span类,开发者可以轻松地为文本添加丰富的样式和颜色,从而提升用户体验。文章详细介绍了实现过程中的关键步骤和技术细节,帮助开发者快速掌握这一技巧。 ... [详细]
  • 【问题】在Android开发中,当为EditText添加TextWatcher并实现onTextChanged方法时,会遇到一个问题:即使只对EditText进行一次修改(例如使用删除键删除一个字符),该方法也会被频繁触发。这不仅影响性能,还可能导致逻辑错误。本文将探讨这一问题的原因,并提供有效的解决方案,包括使用Handler或计时器来限制方法的调用频率,以及通过自定义TextWatcher来优化事件处理,从而提高应用的稳定性和用户体验。 ... [详细]
  • 在Android开发中,当TextView的高度固定且内容超出时,可以通过设置其内置的滚动条属性来实现垂直滚动功能。具体来说,可以通过配置`android:scrollbars="vertical"`来启用垂直滚动,确保用户能够查看完整的内容。此外,为了优化用户体验,建议结合`setMovementMethod(ScrollerMovementMethod.getInstance())`方法,使滚动操作更加流畅和自然。 ... [详细]
  • 本文深入探讨了Ajax的工作机制及其在现代Web开发中的应用。Ajax作为一种异步通信技术,改变了传统的客户端与服务器直接交互的模式。通过引入Ajax,客户端与服务器之间的通信变得更加高效和灵活。文章详细分析了Ajax的核心原理,包括XMLHttpRequest对象的使用、数据传输格式(如JSON和XML)以及事件处理机制。此外,还介绍了Ajax在提升用户体验、实现动态页面更新等方面的具体应用,并讨论了其在当前Web开发中的重要性和未来发展趋势。 ... [详细]
  • 在Android开发中,实现多点触控功能需要使用`OnTouchListener`监听器来捕获触摸事件,并在`onTouch`方法中进行详细的事件处理。为了优化多点触控的交互体验,开发者可以通过识别不同的触摸手势(如缩放、旋转等)并进行相应的逻辑处理。此外,还可以结合`MotionEvent`类提供的方法,如`getPointerCount()`和`getPointerId()`,来精确控制每个触点的行为,从而提升用户操作的流畅性和响应性。 ... [详细]
  • 技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统
    技术分享:使用 Flask、AngularJS 和 Jinja2 构建高效前后端交互系统 ... [详细]
  • Kotlin协程中async和await的常见异常陷阱及正确的异常处理方法
    在Kotlin协程中,`async`和`await`是常用的异步编程工具,尤其是在与Jetpack组件结合时,能够显著简化Android开发中的异步任务处理。然而,不当使用这些工具可能会导致常见的异常陷阱,如未捕获的异常或异常传播问题。本文将深入探讨这些陷阱,并提供有效的异常处理方法,帮助开发者避免潜在的问题,确保应用的稳定性和可靠性。 ... [详细]
  • 在《ChartData类详解》一文中,我们将深入探讨 MPAndroidChart 中的 ChartData 类。本文将详细介绍如何设置图表颜色(Setting Colors)以及如何格式化数据值(Formatting Data Values),通过 ValueFormatter 的使用来提升图表的可读性和美观度。此外,我们还将介绍一些高级配置选项,帮助开发者更好地定制和优化图表展示效果。 ... [详细]
  • 在Android平台中,播放音频的采样率通常固定为44.1kHz,而录音的采样率则固定为8kHz。为了确保音频设备的正常工作,底层驱动必须预先设定这些固定的采样率。当上层应用提供的采样率与这些预设值不匹配时,需要通过重采样(resample)技术来调整采样率,以保证音频数据的正确处理和传输。本文将详细探讨FFMpeg在音频处理中的基础理论及重采样技术的应用。 ... [详细]
  • Web开发框架概览:Java与JavaScript技术及框架综述
    Web开发涉及服务器端和客户端的协同工作。在服务器端,Java是一种优秀的编程语言,适用于构建各种功能模块,如通过Servlet实现特定服务。客户端则主要依赖HTML进行内容展示,同时借助JavaScript增强交互性和动态效果。此外,现代Web开发还广泛使用各种框架和库,如Spring Boot、React和Vue.js,以提高开发效率和应用性能。 ... [详细]
author-avatar
食品质量的安全
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有