2015-08-07 14:41:00 u013894711 阅读数 438
  • 快速入门Android开发 视频 教程 android studio

    这是一门快速入门Android开发课程,顾名思义是让大家能快速入门Android开发。 学完能让你学会如下知识点: Android的发展历程 搭建Java开发环境 搭建Android开发环境 Android Studio基础使用方法 Android Studio创建项目 项目运行到模拟器 项目运行到真实手机 Android中常用控件 排查开发中的错误 Android中请求网络 常用Android开发命令 快速入门Gradle构建系统 项目实战:看美图 常用Android Studio使用技巧 项目签名打包 如何上架市场

    25494 人正在学习 去看看 任苹蜻

说是保存activity的状态好像有点不太对,不过是在是想不出更好的名词所以大家就将就一下吧。


以网易新闻为例 我们在这儿定义三个activity(新闻列表, 新闻详情, 新闻评论页)。大家会发现从新闻详情页面进入到新闻评论页时评论的数据并不会重新加载,而且位置也停留在之前退出的位置,给人的感觉就是这个activity的状态得到了保存只是在需要他出现的时候一下子跳了出来,这种效果对于用户体验是极好的。之前卤煮想过保存activity的数据和滑动停止的位置来实现,但是效果非常不好,在查阅资料后发现可以利用java中的栈来实现,我们可以监听当前activity的返回事件,在返回的时候只需要将当前task移到后台。这样给用户的感觉就是当前activity好像已经被销毁了。不多说了,上代码。


@Override
public boolean onKeyDown(int keyCode, android.view.KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {

//核心代码
moveTaskToBack(true);
return true;
}
return super.onKeyDown(keyCode, event);
}

****评论页的launchMode应该为singleInstance

当然网易还有一个小功能,那就是从新闻详情页面退出到新闻列表页时,新闻评论页的task就真的需要被kill掉了。这儿我的做法是拿到新闻评论页面的实例,然后在新闻情页的ondestroy方法中结束新闻评论页面的task。(这种方法感觉只是权益之计,希望大神能多多指导)


下载链接 http://download.csdn.net/detail/u013894711/8973605

2017-02-06 08:47:23 Anson11223 阅读数 7365
  • 快速入门Android开发 视频 教程 android studio

    这是一门快速入门Android开发课程,顾名思义是让大家能快速入门Android开发。 学完能让你学会如下知识点: Android的发展历程 搭建Java开发环境 搭建Android开发环境 Android Studio基础使用方法 Android Studio创建项目 项目运行到模拟器 项目运行到真实手机 Android中常用控件 排查开发中的错误 Android中请求网络 常用Android开发命令 快速入门Gradle构建系统 项目实战:看美图 常用Android Studio使用技巧 项目签名打包 如何上架市场

    25494 人正在学习 去看看 任苹蜻

Android仿今日头条详情页实现

源码地址:

Android仿今日头条详情页实现 github源码地址

动态图

最近项目有个需求,需要实现一个和今日头条新闻详情页一样的体验。上部分是webview来展示新闻内容,下半部分是listview来展示评论区,可无限加载更多。
起初的实现思路是 将webview放置在listview头部,看似没有什么问题,实现之后发现,webview各种奇怪的问题:黑屏,图片闪烁白屏,渲染速度慢等等问题;
将webview和listview独立放置遍没有问题;于是反编译了一下头条的实现:

Screen Shot

从上图可以知道,实现的原理是,ViewGroup包着listview和webview实现的;于是顺着这条思路往下走。

今日头条的代码是混淆的无法直接使用,我采用的方案是ScrollView里边嵌套了webview+listview;

这套方案有以下几个问题需要解决:

 1. 解决webview在scrollview全部展开的问题。不展开的方法太过复杂,手势处理太麻烦,这里采用展开的形式
 2. 我们知道scrollview包含的childview是无法复用了,那么首先要解决listview的复用问题;
 3. 滑动到listview和webview边界的时候,对于手势事件的交换和状态的保存。

1、webview全部展开的问题

        mWebView.loadUrl(url);
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                int w = View.MeasureSpec.makeMeasureSpec(0,
                        View.MeasureSpec.UNSPECIFIED);
                int h = View.MeasureSpec.makeMeasureSpec(0,
                        View.MeasureSpec.UNSPECIFIED);
                //重新测量
                view.measure(w, h);
                mWebViewHeight = view.getHeight();
                Log.i(TAG, "WEBVIEW高度:" + view.getHeight());
            }
        });

2和3、listview复用问题和手势切换问题

1、复用意味着不能全部展开,现将高度定为(屏幕高度-状态栏高度-标题栏高度);

2、当scrollview滚动到底部的时候,让listview根据手势惯性fling一会,以加强体验,然后将手势交给listview;

3、当listview滑动到底部的时候,加载更多;

4、当listview滑动到顶部的时候,向上滚5个像素,并将手势交给scrollview,便可向上流畅滑动;

5、当listview即将到达顶部的时候,手动往下拖动,当到达边界的时候,需要让scrollview跟着以前scrollby,让用户感知是一起滑动的,当手指松开的时候,要让scrollview惯性滚动一会,以增强体验;

遗留问题

1、scrollview和listview的内部滚动速度是不一致的,scrollview是比较大的,在scrollview滚动到底部的时候,listview采用scrollview滚动速度的三分之一进行fling,体验下来感觉还是比较流畅的。但总感觉还是不够稳妥,优化思路是:接管scrollview和listview的滚动速度,手动控制两个控件的过渡过程;

2、当listview即将到达顶部的时候,手动往下拖动,当到达边界的时候,需要让scrollview跟着以前scrollby,让用户感知是一起滑动的,当手指松开的时候,要让scrollview惯性滚动一会,这里的惯性也由于速度不一致的问题,造成一点点的那么不自然;优化思路和1一致;

Done

QQ:452825089

mail:452825089@qq.com

wechat:ice3897315

blog:http://iceAnson.github.io

2019-04-04 10:53:41 qq_42433597 阅读数 420
  • 快速入门Android开发 视频 教程 android studio

    这是一门快速入门Android开发课程,顾名思义是让大家能快速入门Android开发。 学完能让你学会如下知识点: Android的发展历程 搭建Java开发环境 搭建Android开发环境 Android Studio基础使用方法 Android Studio创建项目 项目运行到模拟器 项目运行到真实手机 Android中常用控件 排查开发中的错误 Android中请求网络 常用Android开发命令 快速入门Gradle构建系统 项目实战:看美图 常用Android Studio使用技巧 项目签名打包 如何上架市场

    25494 人正在学习 去看看 任苹蜻

转载自@ice_Anson


Android仿今日头条详情页实现

源码地址:

Android仿今日头条详情页实现 github源码地址

动态图

最近项目有个需求,需要实现一个和今日头条新闻详情页一样的体验。上部分是webview来展示新闻内容,下半部分是listview来展示评论区,可无限加载更多。
起初的实现思路是 将webview放置在listview头部,看似没有什么问题,实现之后发现,webview各种奇怪的问题:黑屏,图片闪烁白屏,渲染速度慢等等问题;
将webview和listview独立放置遍没有问题;于是反编译了一下头条的实现:

Screen Shot

从上图可以知道,实现的原理是,ViewGroup包着listview和webview实现的;于是顺着这条思路往下走。

今日头条的代码是混淆的无法直接使用,我采用的方案是ScrollView里边嵌套了webview+listview;

这套方案有以下几个问题需要解决:

 1. 解决webview在scrollview全部展开的问题。不展开的方法太过复杂,手势处理太麻烦,这里采用展开的形式
 2. 我们知道scrollview包含的childview是无法复用了,那么首先要解决listview的复用问题;
 3. 滑动到listview和webview边界的时候,对于手势事件的交换和状态的保存。
  • 1
  • 2
  • 3
  • 4

1、webview全部展开的问题

        mWebView.loadUrl(url);
        mWebView.setWebViewClient(new WebViewClient() {
            @Override
            public void onPageFinished(WebView view, String url) {
                super.onPageFinished(view, url);
                int w = View.MeasureSpec.makeMeasureSpec(0,
                        View.MeasureSpec.UNSPECIFIED);
                int h = View.MeasureSpec.makeMeasureSpec(0,
                        View.MeasureSpec.UNSPECIFIED);
                //重新测量
                view.measure(w, h);
                mWebViewHeight = view.getHeight();
                Log.i(TAG, "WEBVIEW高度:" + view.getHeight());
            }
        });
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

2和3、listview复用问题和手势切换问题

1、复用意味着不能全部展开,现将高度定为(屏幕高度-状态栏高度-标题栏高度);

2、当scrollview滚动到底部的时候,让listview根据手势惯性fling一会,以加强体验,然后将手势交给listview;

3、当listview滑动到底部的时候,加载更多;

4、当listview滑动到顶部的时候,向上滚5个像素,并将手势交给scrollview,便可向上流畅滑动;

5、当listview即将到达顶部的时候,手动往下拖动,当到达边界的时候,需要让scrollview跟着以前scrollby,让用户感知是一起滑动的,当手指松开的时候,要让scrollview惯性滚动一会,以增强体验;

遗留问题

1、scrollview和listview的内部滚动速度是不一致的,scrollview是比较大的,在scrollview滚动到底部的时候,listview采用scrollview滚动速度的三分之一进行fling,体验下来感觉还是比较流畅的。但总感觉还是不够稳妥,优化思路是:接管scrollview和listview的滚动速度,手动控制两个控件的过渡过程;

2、当listview即将到达顶部的时候,手动往下拖动,当到达边界的时候,需要让scrollview跟着以前scrollby,让用户感知是一起滑动的,当手指松开的时候,要让scrollview惯性滚动一会,这里的惯性也由于速度不一致的问题,造成一点点的那么不自然;优化思路和1一致;

Done

QQ:452825089

mail:452825089@qq.com

wechat:ice3897315

blog:http://iceAnson.github.io

@[TOC](这里写自定义目录标题)

欢迎使用Markdown编辑器

你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Markdown编辑器, 可以仔细阅读这篇文章,了解一下Markdown的基本语法知识。

新的改变

我们对Markdown编辑器进行了一些功能拓展与语法支持,除了标准的Markdown编辑器功能,我们增加了如下几点新功能,帮助你用它写博客:

  1. 全新的界面设计 ,将会带来全新的写作体验;
  2. 在创作中心设置你喜爱的代码高亮样式,Markdown 将代码片显示选择的高亮样式 进行展示;
  3. 增加了 图片拖拽 功能,你可以将本地的图片直接拖拽到编辑区域直接展示;
  4. 全新的 KaTeX数学公式 语法;
  5. 增加了支持甘特图的mermaid语法1 功能;
  6. 增加了 多屏幕编辑 Markdown文章功能;
  7. 增加了 焦点写作模式、预览模式、简洁写作模式、左右区域同步滚轮设置 等功能,功能按钮位于编辑区域与预览区域中间;
  8. 增加了 检查列表 功能。

功能快捷键

撤销:Ctrl/Command + Z
重做:Ctrl/Command + Y
加粗:Ctrl/Command + B
斜体:Ctrl/Command + I
标题:Ctrl/Command + Shift + H
无序列表:Ctrl/Command + Shift + U
有序列表:Ctrl/Command + Shift + O
检查列表:Ctrl/Command + Shift + C
插入代码:Ctrl/Command + Shift + K
插入链接:Ctrl/Command + Shift + L
插入图片:Ctrl/Command + Shift + G

合理的创建标题,有助于目录的生成

直接输入1次#,并按下space后,将生成1级标题。
输入2次#,并按下space后,将生成2级标题。
以此类推,我们支持6级标题。有助于使用TOC语法后生成一个完美的目录。

如何改变文本的样式

强调文本 强调文本

加粗文本 加粗文本

标记文本

删除文本

引用文本

H2O is是液体。

210 运算结果是 1024.

插入链接与图片

链接: link.

图片: Alt

带尺寸的图片: Alt

居中的图片: Alt

居中并且带尺寸的图片: Alt

当然,我们为了让用户更加便捷,我们增加了图片拖拽功能。

如何插入一段漂亮的代码片

博客设置页面,选择一款你喜欢的代码片高亮样式,下面展示同样高亮的 代码片.

// An highlighted block
var foo = 'bar';

生成一个适合你的列表

  • 项目
    • 项目
      • 项目
  1. 项目1
  2. 项目2
  3. 项目3
  • 计划任务
  • 完成任务

创建一个表格

一个简单的表格是这么创建的:

项目 Value
电脑 $1600
手机 $12
导管 $1

设定内容居中、居左、居右

使用:---------:居中
使用:----------居左
使用----------:居右

第一列 第二列 第三列
第一列文本居中 第二列文本居右 第三列文本居左

SmartyPants

SmartyPants将ASCII标点字符转换为“智能”印刷标点HTML实体。例如:

TYPE ASCII HTML
Single backticks 'Isn't this fun?' ‘Isn’t this fun?’
Quotes "Isn't this fun?" “Isn’t this fun?”
Dashes -- is en-dash, --- is em-dash – is en-dash, — is em-dash

创建一个自定义列表

Markdown
Text-to-HTML conversion tool
Authors
John
Luke

如何创建一个注脚

一个具有注脚的文本。2

注释也是必不可少的

Markdown将文本转换为 HTML

KaTeX数学公式

您可以使用渲染LaTeX数学表达式 KaTeX:

Gamma公式展示 Γ(n)=(n1)!nN\Gamma(n) = (n-1)!\quad\forall n\in\mathbb N 是通过欧拉积分

Γ(z)=0tz1etdt . \Gamma(z) = \int_0^\infty t^{z-1}e^{-t}dt\,.

你可以找到更多关于的信息 LaTeX 数学表达式here.

新的甘特图功能,丰富你的文章

Mon 06Mon 13Mon 20已完成 进行中 计划一 计划二 现有任务Adding GANTT diagram functionality to mermaid
  • 关于 甘特图 语法,参考 这儿,

UML 图表

可以使用UML图表进行渲染。 Mermaid. 例如下面产生的一个序列图::

张三李四王五你好!李四, 最近怎么样?你最近怎么样,王五?我很好,谢谢!我很好,谢谢!李四想了很长时间,文字太长了不适合放在一行.打量着王五...很好... 王五, 你怎么样?张三李四王五

这将产生一个流程图。:

链接
长方形
圆角长方形
菱形
  • 关于 Mermaid 语法,参考 这儿,

FLowchart流程图

我们依旧会支持flowchart的流程图:

Created with Raphaël 2.2.0开始我的操作确认?结束yesno
  • 关于 Flowchart流程图 语法,参考 这儿.

导出与导入

导出

如果你想尝试使用此编辑器, 你可以在此篇文章任意编辑。当你完成了一篇文章的写作, 在上方工具栏找到 文章导出 ,生成一个.md文件或者.html文件进行本地保存。

导入

如果你想加载一篇你写过的.md文件或者.html文件,在上方工具栏可以选择导入功能进行对应扩展名的文件导入,
继续你的创作。


  1. mermaid语法说明 ↩︎

  2. 注脚的解释 ↩︎

2018-08-30 20:13:23 jiyidehao 阅读数 296
  • 快速入门Android开发 视频 教程 android studio

    这是一门快速入门Android开发课程,顾名思义是让大家能快速入门Android开发。 学完能让你学会如下知识点: Android的发展历程 搭建Java开发环境 搭建Android开发环境 Android Studio基础使用方法 Android Studio创建项目 项目运行到模拟器 项目运行到真实手机 Android中常用控件 排查开发中的错误 Android中请求网络 常用Android开发命令 快速入门Gradle构建系统 项目实战:看美图 常用Android Studio使用技巧 项目签名打包 如何上架市场

    25494 人正在学习 去看看 任苹蜻

如题在开发中遇到 展示类似今日头条WebView +评论列表的需求。首先想到的是RecyclerView + HeaderView(Webview) 的思路。当然一开始在自己手机上并没有遇到什么问题,然而同事在不同的手机上发现有崩溃现象,于是乎上网度娘,google。发现到问题可能出现在WebView 和所在的Activity 的layerType的设置上,也就是对分别对MainFest文件中Activity 的硬件加速,xml中的硬件加速声明,java 代码中的硬件加速也做了控制。最终问题终于得到解决,在不同的手机上,WebView 页面都不会崩溃了(因为关闭掉了硬件加速),却带来了另一个比较严重的问题,就是在我们滑动WebView的时候页面非常的卡顿(可以清晰的看到,尤其是webview 中的文字会闪的我们眼睛晕)。这个问题一直留存了好多个版本。。。。终于在近期版本没有太多任务的时候,将这个问题列为项目需求来 解决。我也花费了好长的时间查找资料,排除问题。首先我尝试了第一种方法:就是自定义ViewGroup  内部包含自定义WebView 和ListView(RecyclerView),然而网上好多可参考的项目都会有或多或少的缺点,最终这种方式没走通。然后第二种方法: 是在对比别人的项目的过程中,发现一个很奇怪的问题,为什么我参考的这个项目不卡(这个项目和我们一样也是RecyclerView + header,况且把h5 页面和我们出问题的那个手机都用到这个参考的项目),找了好久没找出来,最终让同事看看有么有什么地方漏掉的。同事果真发现了,参考项目的Webview 基本是是很原生的没有做什么处理,而我们项目的WebView 封装了好多,设置了好多参数。就这样最终发现问题真的是WebView 的设置有问题。问题在代码中设置了layerType(); 将代码中设置layerType 的方法注释掉后就解决了问题。

最终总结(如果没有太高的要求,还是可以做到不卡顿的,不会崩):

1.使用RecyclerView(ListView) + headerView(包含WebView ) 的方式。

2.将清单文件,xml,代码中的所所有关于硬件加速的设置都使用默认的(不需要额外添加的)。

 

2019-06-05 09:32:24 wengsheng147 阅读数 336
  • 快速入门Android开发 视频 教程 android studio

    这是一门快速入门Android开发课程,顾名思义是让大家能快速入门Android开发。 学完能让你学会如下知识点: Android的发展历程 搭建Java开发环境 搭建Android开发环境 Android Studio基础使用方法 Android Studio创建项目 项目运行到模拟器 项目运行到真实手机 Android中常用控件 排查开发中的错误 Android中请求网络 常用Android开发命令 快速入门Gradle构建系统 项目实战:看美图 常用Android Studio使用技巧 项目签名打包 如何上架市场

    25494 人正在学习 去看看 任苹蜻

作者:王正一

链接:

https://segmentfault.com/a/1190000019272870

 

很多大厂 App 新闻类客户端文章详情页都内容区域是 Webview,下面评论区域是 RecylcerView 但是可以连贯在一起滚动,是如何做到的呢? 相信这篇文章会给你一定的启发。

 

1

从一个简单的DEMO看什么是嵌套滚动

 

我们先来看一下DEMO的效果,直观的感受一下什么是嵌套滚动:

 

 

在解释上图涉及到哪些嵌套滑动操作之前,我先贴一下嵌套布局的xml结构:

 

 

<com.wzy.nesteddetail.view.NestedWebViewRecyclerViewGroup>
    <com.wzy.nesteddetail.view.NestedScrollWebView/>  
    <TextView /> 
    <android.support.v7.widget.RecyclerView />
</com.wzy.nesteddetail.view.NestedWebViewRecyclerViewGroup>

 

其中:

 

1. NestedWebViewRecyclerViewGroup为最外层滑动容器;

2. com.wzy.nesteddetail.view.NestedScrollWebView为布局顶部可嵌套滑动的View;

3. TextView为布局中部不可滑动的View;

4. android.support.v7.widget.RecyclerView为布局底部可滑动的View;

 

现在我们来说明一下,简单的DEMO效果中包含了哪些嵌套滑动操作:

 

1. 向上滑动顶部WebView时,首先滑动WebView的内容,WebView的内容滑动到底后再滑动外层容器。外层容器滑动到RecyclerView完全露出后,再将滑动距离或者剩余速度传递给RecyclerView继续滑动.

 

2. 滑动底部RecyclerView时,首先滑动RecyclerView的内容,RecyclerView的内容滑动到顶后再滑动外层容器。外层容器也滑动到顶后,再将滑动距离或者剩余速度传递给WebView继续滑动.

 

3. 触摸本身不可滑动的TextView时,滑动事件被外层容器拦截。外层容器根据滑动方向和是否滑动到相应阈值,再将相应的滑动距离或者速度传递给WebView或者RecyclerView.

 

再不知道NestedScrolling机制之前,我相信大部分人想实现上面的滑动效果都是比较头大的,特别是滑动距离和速度要从WebView->外层容器->RecyclerView并且还要支持反向传递。


有了Google提供的牛逼嵌套滑动机制,再加上这篇文章粗浅的科普,我相信大部分人都能够实现这种滑动效果。这种效果最常见的应用场景就是各种新闻客户端的详情页。

 

2

NestedScrolling接口简介

 

Android在support.v4包中提供了用于View支持嵌套滑动的两个接口:

 

  • NestedScrollingParent

  • NestedScrollingChild

 

我先用比较白话的语言介绍一下NestedScrolling的工作原理:

 

1. Google从逻辑上区分了滑动的两个角色:NestedScrollingParent简称ns parent,NestedScrollingChild简称ns child。对应了滑动布局中的外层滑动容器和内部滑动容器。

 

2. ns child在收到DOWN事件时,找到离自己最近的ns parent,与它进行绑定并关闭它的事件拦截机制。

 

3. ns child会在接下来的MOVE事件中判定出用户触发了滑动手势,并把事件拦截下来给自己消费。

 

4. 消费MOVE事件流时,对于每一个MOVE事件增加的滑动距离:

 

  • 4.1. ns child并不是直接自己消费,而是先将它交给ns parent,让ns parent可以在ns child滑动前进行消费。

  • 4.2. 如果ns parent没有消费或者滑动没消费完,ns child再消费剩下的滑动。

  • 4.3. 如果ns child消费后滑动还是有剩余,会把剩下的滑动距离再交给ns parent消费。

  • 4.4. 最后如果ns parent消费滑动后还有剩余,ns child可以做最终处理。

 

5. ns child在收到UP事件时,可以计算出需要滚动的速度,ns child对于速度的消费流程是:

 

  • 5.1 ns child在进行flying操作前,先询问ns parent是否需要消费该速度。如果ns parent消费该速度,后续就由ns parent带飞,自己就不消费该速度了。如果ns parent不消费,则ns child进行自己的flying操作。

  • 5.2 ns child在flying过程中,如果已经滚动到阈值速度仍没有消费完,会再次将速度分发给ns parent,将ns parent进行消费。

 

NestedScrollingParent和NestedScrollingChild的源码定义也是为了配合滑动实现定义出来的:

 

NestedScrollingChild

 

// 设置是否开启嵌套滑动
void setNestedScrollingEnabled(boolean enabled);
// 获得设置开启了嵌套滑动    
boolean isNestedScrollingEnabled(); 
// 沿给定的轴线开始嵌套滚动                
boolean startNestedScroll(@ScrollAxis int axes);
// 停止当前嵌套滚动    
void stopNestedScroll();  
// 如果有ns parent,返回true                          
boolean hasNestedScrollingParent(); 
// 消费滑动时间前,先让ns parent消费                
boolean dispatchNestedPreScroll(int dx , int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow);
// ns parent消费ns child剩余滚动后是否还有剩余。return true代表还有剩余              
boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow);   
// 消费fly速度前,先让ns parent消费        
boolean dispatchNestedPreFling(float velocityX, float velocityY); 
// ns parent消费ns child消费后的速度之后是否还有剩余。return true代表还有剩余                                            
boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed);                           

 

NestedScrollingParent

 

// 决定是否接收子View的滚动事件
boolean onStartNestedScroll();   
// 响应子View的滚动                      
void onNestedScrollAccepted(); 
// 滚动结束的回调                                
void onStopNestedScroll();
// ns child滚动前回调     
void onNestedPreScroll();  
// ns child滚动后回调                   
void onNestedScroll();
// ns child flying前回调                                
boolean onNestedPreFling();  
// ns child flying后回调                           
boolean onNestedFling();
// 返回当前布局嵌套滚动的坐标轴                           
int getNestedScrollAxes();                     

 

Google为了让开发者更加方便的实现这两个接口,提供了NestedScrollingParentHelper和NestedScrollingChildHelper这两个辅助。所以实现NestedScrolling这两个接口的常用写法是:

 

ns child:

 

public class NestedScrollingWebView extends WebView implements NestedScrollingChild {
    private NestedScrollingChildHelper mChildHelper;

    private NestedScrollingChildHelper getNestedScrollingHelper() {
        if (mChildHelper == null) {
            mChildHelper = new NestedScrollingChildHelper(this);
        }
        return mChildHelper;
    }

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        getNestedScrollingHelper().setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return getNestedScrollingHelper().isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes) {
        return getNestedScrollingHelper().startNestedScroll(axes);
    }

    @Override
    public void stopNestedScroll() {
        getNestedScrollingHelper().stopNestedScroll();
    }

    @Override
    public boolean hasNestedScrollingParent() {
        return getNestedScrollingHelper().hasNestedScrollingParent();
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow) {
        return getNestedScrollingHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow) {
        return getNestedScrollingHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return getNestedScrollingHelper().dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return getNestedScrollingHelper().dispatchNestedPreFling(velocityX, velocityY);
    }

    @Override
    public boolean startNestedScroll(int axes, int type) {
        return getNestedScrollingHelper().startNestedScroll(axes, type);
    }

    @Override
    public void stopNestedScroll(int type) {
        getNestedScrollingHelper().stopNestedScroll(type);
    }

    @Override
    public boolean hasNestedScrollingParent(int type) {
        return getNestedScrollingHelper().hasNestedScrollingParent(type);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, @Nullable int[] offsetInWindow, int type) {
        return getNestedScrollingHelper().dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow, type);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, @Nullable int[] consumed, @Nullable int[] offsetInWindow, int type) {
        return getNestedScrollingHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
    }
}                         

 

ns parent:

 

public class NestedScrollingDetailContainer extends ViewGroup implements NestedScrollingParent {
    private NestedScrollingParentHelper mParentHelper;    

    private NestedScrollingParentHelper getNestedScrollingHelper() {
        if (mParentHelper == null) {
            mParentHelper = new NestedScrollingParentHelper(this);
        }
        return mParentHelper;
    }    

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

    @Override
    public int getNestedScrollAxes() {
        return getNestedScrollingHelper().getNestedScrollAxes();
    }

    @Override
    public void onNestedScrollAccepted(View child, View target, int axes) {
        getNestedScrollingHelper().onNestedScrollAccepted(child, target, axes);
    }

    @Override
    public void onStopNestedScroll(View child) {
        getNestedScrollingHelper().onStopNestedScroll(child);
    }

    @Override
    public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
        // 处理预先flying事件
        return false;
    }

    @Override
    public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) {
        // 处理后续flying事件
        return false;
    }

    @Override
    public void onNestedScroll(@NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
        // 处理后续scroll事件
    }

    @Override
    public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed) {
        // 处理预先滑动scroll事件
    }
}                 

 

3

效果实现

 

只知道原理大家肯定是不满足的,结合原理进行实操才是关键。这里以DEMO的效果为例,想要实现DEMO的效果,需要自定义两个嵌套滑动容器:

 

  1. 自定义一个支持嵌套WebView和RecyclerView滑动的外部容器。

  2. 自定义一个实现NestedScrollingChild接口的WebView。

 

外部滑动容器

 

在实现外部滑动的容器的时候,我们首先需要考虑这个外部滑动容器的滑动阈值是什么?


答: 外部滑动的滑动阈值=外部容器中所有子View的高度-外部容器的高度。

 

同理类似WebView的滑动阈值=WebView的内容高度-WebView的容器高度。

 

对应代码实现:

 

private int mInnerScrollHeight;    // 可滑动的最大距离
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    int width;
    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int measureWidth = MeasureSpec.getSize(widthMeasureSpec);

    int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
    if (widthMode == MeasureSpec.EXACTLY) {
        width = measureWidth;
    } else {
        width = mScreenWidth;
    }

    int left = getPaddingLeft();
    int right = getPaddingRight();
    int top = getPaddingTop();
    int bottom = getPaddingBottom();
    int count = getChildCount();
    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        LayoutParams params = child.getLayoutParams();
        int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, left + right, params.width);
        int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, top + bottom, params.height);
        measureChild(child, childWidthMeasureSpec, childHeightMeasureSpec);
    }
    setMeasuredDimension(width, measureHeight);
    findWebView(this);
    findRecyclerView(this);
}

@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    int childTotalHeight = 0;
    mInnerScrollHeight = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View child = getChildAt(i);
        int childWidth = child.getMeasuredWidth();
        int childHeight = child.getMeasuredHeight();
        child.layout(0, childTotalHeight, childWidth, childHeight + childTotalHeight);
        childTotalHeight += childHeight;
        mInnerScrollHeight += childHeight;
    }
    mInnerScrollHeight -= getMeasuredHeight();
}              

 

其次,需要考虑当WebView传递上滑事件和RecyclerView传递下滑事件时如何处理:

 

1. 向上滑动时,如果WebView内容还没有到底,该事件交给WebView处理;如果WebView内容已经滑动到底,但是滑动距离没有超过外部容器的最大滑动距离,该事件由滑动容器自身处理;如果WebView内容已经滑动到底,并且滑动距离超过了外部容器的最大滑动距离,这时将滑动事件传递给底部的 RecyclerView,让RecyclerView处理;

 

2. 向下滑动时,如果RecyclerView没有到顶部,该事件交给RecyclerView处理;如果RecyclerView已经到顶部,并且外部容器的滑动距离不为0,该事件由外部容器处理;如果RecyclerView已经到顶部,并且外部容器的滑动距离已经为0,则该事件交给WebView处理;

 

对应的WebView传递上滑速度和RecyclerView传递下滑速度,处理和Scroll传递类似。

 

对应代码实现:

 

@Override
public void onNestedPreScroll(@NonNull View target, int dx, int dy, @Nullable int[] consumed, int type) {
    boolean isWebViewBottom = !canWebViewScrollDown();
    boolean isCenter = isParentCenter();
    if (dy > 0 && isWebViewBottom && getScrollY() < getInnerScrollHeight()) {
        //为了WebView滑动到底部,继续向下滑动父控件
        scrollBy(0, dy);
        if (consumed != null) {
            consumed[1] = dy;
        }
    } else if (dy < 0 && isCenter) {
        //为了RecyclerView滑动到顶部时,继续向上滑动父控件
        scrollBy(0, dy);
        if (consumed != null) {
            consumed[1] = dy;
        }
    }
    if (isCenter && !isWebViewBottom) {
        //异常情况的处理
        scrollToWebViewBottom();
    }
}

@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
    if (target instanceof NestedScrollingWebView) {
        //WebView滑到底部时,继续向下滑动父控件和RV
        mCurFlyingType = FLYING_FROM_WEBVIEW_TO_PARENT;
        parentFling(velocityY);
    } else if (target instanceof RecyclerView && velocityY < 0 && getScrollY() >= getInnerScrollHeight()) {
        //RV滑动到顶部时,继续向上滑动父控件和WebView,这里用于计算到达父控件的顶部时RV的速度
        mCurFlyingType = FLYING_FROM_RVLIST_TO_PARENT;
        parentFling((int) velocityY);
    } else if (target instanceof RecyclerView && velocityY > 0) {
        mIsRvFlyingDown = true;
    }

    return false;
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        int currY = mScroller.getCurrY();
        switch (mCurFlyingType) {
            case FLYING_FROM_WEBVIEW_TO_PARENT://WebView向父控件滑动
                if (mIsRvFlyingDown) {
                    //RecyclerView的区域的fling由自己完成
                    break;
                }
                scrollTo(0, currY);
                invalidate();
                checkRvTop();
                if (getScrollY() == getInnerScrollHeight() && !mIsSetFlying) {
                    //滚动到父控件底部,滚动事件交给RecyclerView
                    mIsSetFlying = true;
                    recyclerViewFling((int) mScroller.getCurrVelocity());
                }
                break;
            case FLYING_FROM_PARENT_TO_WEBVIEW://父控件向WebView滑动
                scrollTo(0, currY);
                invalidate();
                if (currY <= 0 && !mIsSetFlying) {
                    //滚动父控件顶部,滚动事件交给WebView
                    mIsSetFlying = true;
                    webViewFling((int) -mScroller.getCurrVelocity());
                }
                break;
            case FLYING_FROM_RVLIST_TO_PARENT://RecyclerView向父控件滑动,fling事件,单纯用于计算速度。RecyclerView的flying事件传递最终会转换成Scroll事件处理.
                if (getScrollY() != 0) {
                    invalidate();
                } else if (!mIsSetFlying) {
                    mIsSetFlying = true;
                    //滑动到顶部时,滚动事件交给WebView
                    webViewFling((int) -mScroller.getCurrVelocity());
                }
                break;
        }
    }
}            

 

最后,我们需要考虑,如果用户触摸的是内部的一个不可滑动View时,这时事件是没法通过NestedScrolling机制传递给自身的。所以需要主动拦截这种事件,拦截标准:

 

1. MOVE超过的TOUCH SLOP距离。

2. 当前触摸的不是支持NestedScrolling机制的View。

 

相应代码如下:

 

private int mLastY;

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mLastY = (int) event.getY();
            break;
        case MotionEvent.ACTION_MOVE:
            if (mLastY == 0) {
                mLastY = (int) event.getY();
                return true;
            }
            int y = (int) event.getY();
            int dy = y - mLastY;
            mLastY = y;
            scrollBy(0, -dy);
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            mLastY = 0;
            break;
    }
    return true;
}

private int mLastMotionY;

@Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
    final int action = ev.getAction();
    switch (action & MotionEvent.ACTION_MASK) {
        case MotionEvent.ACTION_MOVE:
            // 拦截落在不可滑动子View的MOVE事件
            final int y = (int) ev.getY();
            final int yDiff = Math.abs(y - mLastMotionY);
            boolean isInNestedChildViewArea = isTouchNestedInnerView((int)ev.getRawX(), (int)ev.getRawY());
            if (yDiff > TOUCH_SLOP && !isInNestedChildViewArea) {
                mIsBeingDragged = true;
                mLastMotionY = y;
                final ViewParent parent = getParent();
                if (parent != null) {
                    parent.requestDisallowInterceptTouchEvent(true);
                }
            }
            break;
        case MotionEvent.ACTION_DOWN:
            mLastMotionY = (int) ev.getY();
            mIsBeingDragged = false;
            break;
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            mIsBeingDragged = false;
            break;
    }

    return mIsBeingDragged;
}

private boolean isTouchNestedInnerView(int x, int y) {
    List<View> innerView = new ArrayList<>();
    if (mChildWebView != null) {
        innerView.add(mChildWebView);
    }
    if (mChildRecyclerView != null) {
        innerView.add(mChildRecyclerView);
    }

    for (View nestedView : innerView) {
        if (nestedView.getVisibility() != View.VISIBLE) {
            continue;
        }
        int[] location = new int[2];
        nestedView.getLocationOnScreen(location);
        int left = location[0];
        int top = location[1];
        int right = left + nestedView.getMeasuredWidth();
        int bottom = top + nestedView.getMeasuredHeight();
        if (y >= top && y <= bottom && x >= left && x <= right) {
            return true;
        }
    }
    return false;
}           

 

实现一个支持嵌套滑动的WebView

 

本身WebView是不支持嵌套滑动的,想要支持嵌套滑动,我们需要让WebView实现NestedScrollingChild接口,并且处理好TouchEvent方法中的事件传递。


实现NestedScrollingChild接口比较简单,上面也介绍过了,可以使用Google提供的NestedScrollingChildHelper辅助类。


处理TouchEvent的思路,需要遵循以下步骤:

 

1. DOWN事件时通知父布局,自己要开始嵌套滑动了。

2. 对于MOVE事件,先交给父布局消费。父布局判断WebView不能向下滑动了,就父布局消费;还能向下滑动,就给WebView消费。

3. 对于Flying事件,同样先咨询父布局是否消费。父布局判断WebView不能向下滑动了,就父布局消费;还能向下滑动,就给WebView消费。

 

WebView最大滑动距离=WebView自身内容的高度-WebView容器的高度

 

思路比较简单,我们看一下对应的核心代码:

 

@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
            mWebViewContentHeight = 0;
            mLastY = (int) event.getRawY();
            mFirstY = mLastY;
            if (!mScroller.isFinished()) {
                mScroller.abortAnimation();
            }
            initOrResetVelocityTracker();
            mIsSelfFling = false;
            mHasFling = false;
            mMaxScrollY = getWebViewContentHeight() - getHeight();
            startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
            if (getParent() != null) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            break;
        case MotionEvent.ACTION_MOVE:
            initVelocityTrackerIfNotExists();
            mVelocityTracker.addMovement(event);
            int y = (int) event.getRawY();
            int dy = y - mLastY;
            mLastY = y;
            if (getParent() != null) {
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            if (!dispatchNestedPreScroll(0, -dy, mScrollConsumed, null)) {
                scrollBy(0, -dy);
            }
            if (Math.abs(mFirstY - y) > TOUCHSLOP) {
                //屏蔽WebView本身的滑动,滑动事件自己处理
                event.setAction(MotionEvent.ACTION_CANCEL);
            }
            break;
        case MotionEvent.ACTION_CANCEL:
        case MotionEvent.ACTION_UP:
            if (isParentResetScroll() && mVelocityTracker != null) {
                mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
                int yVelocity = (int) -mVelocityTracker.getYVelocity();
                recycleVelocityTracker();
                mIsSelfFling = true;
                flingScroll(0, yVelocity);
            }
            break;
    }
    super.onTouchEvent(event);
    return true;
}

@Override
public void computeScroll() {
    if (mScroller.computeScrollOffset()) {
        final int currY = mScroller.getCurrY();
        if (!mIsSelfFling) {
            // parent flying
            scrollTo(0, currY);
            invalidate();
            return;
        }

        if (isWebViewCanScroll()) {
            scrollTo(0, currY);
            invalidate();
        }
        if (!mHasFling
                && mScroller.getStartY() < currY
                && !canScrollDown()
                && startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL)
                && !dispatchNestedPreFling(0, mScroller.getCurrVelocity())) {
            //滑动到底部时,将fling传递给父控件和RecyclerView
            mHasFling = true;
            dispatchNestedFling(0, mScroller.getCurrVelocity(), false);
        }
    }
}          

 

总结

 

NestedScrolling机制看似复杂,但其实就是实现两个接口的事情,而且Google提供了强大的辅助类Helper来帮助我们实现接口。

 

这种机制将滑动事件的传递封装起来,通过Helper辅助类实现ns parent和ns child之间的连接和交互。通过接口回调,也实现了ns parent和ns child的解耦。

 

DEMO项目链接:

 

https://github.com/wangzhengyi/Android-NestedDetail

没有更多推荐了,返回首页