精华内容
下载资源
问答
  • 现在数据分析领域,大家用得比较多的还是 Jupyter Notebook,本书使用的也是它。 本文内容来自书籍《对比Excel,轻松学习Python数据分析》,此书已经加入VIP会员权益中,只要是VIP会员即可免费阅读上千门电子书,点...

    现在数据分析领域,大家用得比较多的还是 Jupyter Notebook,本书使用的也是它。

    本文内容来自书籍《对比Excel,轻松学习Python数据分析》,此书已经加入VIP会员权益中,只要是VIP会员即可免费阅读上千门电子书,点此购买会员
    在这里插入图片描述

    新建Jupyter Notebook文件

    在电脑搜索框中输入Jupyter Notebook(不区分大小写),然后单击打开,如下图所示。
    在这里插入图片描述
    打开Jupyter Notebook后单击右上角的New按钮,在下拉列表中选择Python 3选项来创建一个Python文件,也可以选择Text File选项来创建一个.txt格式的文件,如下图所示。
    在这里插入图片描述
    当你看到下面这个界面时就表示你新建了一个Jupyer Notebook文件。
    在这里插入图片描述
    运行你的第一段代码

    如下图所示,在代码框中输入一段代码print(“hello world”),然后单击Run按钮,或者按Ctrl+Enter组合键,就会输出hello world,这就表示你的第一段代码运行成功了。当你想换一个代码框输入代码时,你可以通过单击左上角的“+”按钮来新增代码框。
    在这里插入图片描述
    重命名Jupyter Notebook文件

    当新建一个Jupyter Notebook文件时,该文件名默认为Untitled(类似于Excel中的工作簿),你可以单击File>Rename对该文件进行重命名,如下图所示。
    在这里插入图片描述
    保存Jupyter Notebook文件

    代码写好了,文件名也确定了,这个时候就可以对该代码文件进行保存了。保存的方法有两种。

    方法一,单击File>Save and Checkpoint保存文件,但是这种方法会将文件保存到默认路径下,且文件格式默认为ipynb,ipynb是Jupyter Notebook的专属文件格式。
    方法二,选择Download as选项对文件进行保存,它相当于Excel中的“另存为”,你可以自己选择保存路径及保存格式,如下图所示。

    在这里插入图片描述
    导入本地Jupyter Notebook文件

    当收到ipynb文件时,如何在电脑上打开该文件呢?你可以按Upload按钮,找到文件所在位置,从而将文件加载到电脑的Jupyter Notebook文件中,如下图所示。
    在这里插入图片描述
    这个功能和Excel中的“打开”是类似的,如下图所示。
    在这里插入图片描述
    Jupyter Notebook与Markdown

    Jupyter Notebook的代码框默认是code模式的,即用于编程的,如下图所示。
    在这里插入图片描述
    你也可以把Jupyter Notebook代码框的模式切换为Markdown模式,这个时候的代码框就会变成一个文本框,这个文本框的内容支持Markdown语法。当你做数据分析的时候,可以利用Markdown写下分析结果,如下图所示。
    在这里插入图片描述
    这也是Jupyter Notebook受广大数据从业者欢迎的一个原因。

    为Jupyter Notebook添加目录

    目录的作用是使对应的内容便于查找,一般篇幅比较长的内容都会有目录,比如书籍、毕业论文等。当一个程序中代码过多时,为了便于阅读,也可以为代码增加一个目录,下图左边框中的内容就是目录,你可以通过单击目录跳转到相应的代码部分。

    在这里插入图片描述
    目录不是Jupyter Notebook自带的,需要手动安装,具体安装过程如下。
    Step1:在Windows搜索框中输入Anaconda Prompt并单击打开,如下图所示。
    在这里插入图片描述
    Step2:输入 pip install jupyter_contrib_nbextensions 然后按 Enter 键运行,安装jupyter_contrib_nbextensions模块,如下图所示。
    在这里插入图片描述
    Step3:程序运行中途会出现 y/n 的选项,输入 y 并按 Enter 键运行,直到出现Successfully installed的提示,如下图所示。

    在这里插入图片描述
    在这里插入图片描述
    Step4:在Step3的基础上继续输入jupyter contrib nbextension install --user然后按Enter键进行用户配置,如下图所示。
    在这里插入图片描述
    Step5:等Step4完成以后,打开Jupyter Notebook会看到界面上多了Nbextensions选项卡,如下图所示。
    在这里插入图片描述
    单击Nbextensions选项卡打开,勾选Table of Contents(2)复选框,如下图所示。
    在这里插入图片描述

    Step6:这个时候打开一个已经带有目录的ipynb文件,就会看到主界面多了一个方框内的按钮(如下图所示),但是仍然没有目录。
    在这里插入图片描述
    按下图右上角方框内的按钮,目录就会显示出来了,如下图所示。
    在这里插入图片描述
    Step1~Step6为Jupyter Notebook创建了目录环境,下面介绍如何新建带有目录的文件。

    Step1:将代码框格式选择为Heading,如下图所示。
    在这里插入图片描述
    Step2:直接在代码框输入不同级别的标题,1个#表示一级标题,2个#表示二级标题,3个#表示三级标题(注意,#与标题文字之间是有空格的),标题级别随着#数量的增加依次递减。
    在这里插入图片描述
    Step3:运行Step2的代码块,就可以得到如下图所示的结果。
    在这里插入图片描述

    此书已加入到VIP会员卡,只要购买VIP会员卡即可免费阅读上百本电子书

    在这里插入图片描述

    阅读电子书的方法如下:

    打开CSDN APP(软件商城搜索“CSDN”即可找到哦)—>登录CSDN账号—>学习—>电子书

    在这里插入图片描述

    展开全文
  • 怎么在php中使用header函数下载文件发布时间:2020-12-21 16:38:23来源:亿速云阅读:74作者:Leah这期内容当中小编将会给大家带来有关怎么在php中使用header函数下载文件,文章内容丰富且以专业的角度为大家分析和...

    怎么在php中使用header函数下载文件

    发布时间:2020-12-21 16:38:23

    来源:亿速云

    阅读:74

    作者:Leah

    这期内容当中小编将会给大家带来有关怎么在php中使用header函数下载文件,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

    具体内容如下<?php

    /**

    * 下载文件

    * header函数

    *

    */

    dl_file($_GET ['filename']);

    function dl_file($file)

    {

    $file = ".//images//" . $file;

    //First, see if the file exists

    if (! is_file ( $file ))

    {

    die ( "404 File not found!" );

    }

    // Gather relevent info about file

    $len = filesize ( $file );

    $filename = basename ( $file );

    $file_extension = strtolower ( substr ( strrchr ( $filename, "." ), 1 ) );

    // This will set the Content-Type to the appropriate setting for the file

    switch ($file_extension)

    {

    case "pdf" :

    $ctype = "application/pdf";

    break;

    case "exe" :

    $ctype = "application/octet-stream";

    break;

    case "zip" :

    $ctype = "application/zip";

    break;

    case "doc" :

    $ctype = "application/msword";

    break;

    case "xls" :

    $ctype = "application/vnd.ms-excel";

    break;

    case "ppt" :

    $ctype = "application/vnd.ms-powerpoint";

    break;

    case "gif" :

    $ctype = "image/gif";

    break;

    case "png" :

    $ctype = "image/png";

    break;

    case "jpeg" :

    case "jpg" :

    $ctype = "image/jpg";

    break;

    case "mp3" :

    $ctype = "audio/mpeg";

    break;

    case "wav" :

    $ctype = "audio/x-wav";

    break;

    case "mpeg" :

    case "mpg" :

    case "mpe" :

    $ctype = "video/mpeg";

    break;

    case "mov" :

    $ctype = "video/quicktime";

    break;

    case "avi" :

    $ctype = "video/x-msvideo";

    break;

    // The following are for extensions that shouldn't be downloaded

    // (sensitive stuff, like php files)

    case "php" :

    case "htm" :

    case "html" :

    case "txt" :

    die ( "Cannot be used for " . $file_extension . " files!" );

    break;

    default :

    $ctype = "application/force-download";

    }

    $file_temp = fopen ( $file, "r" );

    // Begin writing headers

    header ( "Pragma: public" );

    header ( "Expires: 0" );

    header ( "Cache-Control: must-revalidate, post-check=0, pre-check=0" );

    header ( "Cache-Control: public" );

    header ( "Content-Description: File Transfer" );

    // Use the switch-generated Content-Type

    header ( "Content-Type: $ctype" );

    // Force the download

    $header = "Content-Disposition: attachment; filename=" . $filename . ";";

    header ( $header );

    header ( "Content-Transfer-Encoding: binary" );

    header ( "Content-Length: " . $len );

    //@readfile ( $file );

    echo fread ( $file_temp, filesize ( $file ) );

    fclose ( $file_temp );

    exit ();

    }

    ?>

    上述就是小编为大家分享的怎么在php中使用header函数下载文件了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

    展开全文
  • 2021-02-05 18:08:35来源:亿速云阅读:76作者:Leah这期内容当中小编将会给大家带来有关使用PHP怎么获取ttf格式文件的字体名,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。...

    使用PHP怎么获取ttf格式文件的字体名

    发布时间:2021-02-05 18:08:35

    来源:亿速云

    阅读:76

    作者:Leah

    这期内容当中小编将会给大家带来有关使用PHP怎么获取ttf格式文件的字体名,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

    具体如下:<?php

    $names = GetFontName('c:/windows/fonts/FZHPJW.TTF');

    foreach ($names as $name) {

    if ($name['language'] == 1033)

    $code = 'utf-16le';

    elseif ($name['language'] == 2052) $code = 'utf-16be';

    var_dump(mb_convert_encoding($name['name'],'utf-8',$code));

    }

    function GetFontName($FilePath) {

    $fp = fopen($FilePath, 'r');

    if ($fp) {

    //TT_OFFSET_TABLE

    $meta = unpack('n6', fread($fp, 12));

    //检查是否是一个true type字体文件以及版本号是否为1.0

    if ($meta[1] != 1 || $meta[2] != 0)

    return FALSE;

    $Found = FALSE;

    for ($i = 0; $i 

    //TT_TABLE_DIRECTORY

    $tablemeta = unpack('N4', $data = fread($fp, 16));

    if (substr($data, 0, 4) == 'name') {

    $Found = TRUE;

    break;

    }

    }

    if ($Found) {

    fseek($fp, $tablemeta[3]);

    //TT_NAME_TABLE_HEADER

    $tablecount = unpack('n3', fread($fp, 6));

    $Found = FALSE;

    for ($i = 0; $i 

    //TT_NAME_RECORD

    $table = unpack('n6', fread($fp, 12));

    if ($table[4] == 1) {

    $npos = ftell($fp);

    fseek($fp, $n = $tablemeta[3] + $tablecount[3] + $table[6], SEEK_SET);

    $fontname = trim($x = fread($fp, $table[5]));

    if (strlen($fontname) > 0) {

    $names[] = array (

    'platform' => $table[1], //平台(操作系统)

    'language' => $table[3], //字体名称的语言

    'encoding' => $table[2], //字体名称的编码

    'name' => $fontname //字体名称

    );

    //break;

    }

    fseek($fp, $npos, SEEK_SET);

    }

    }

    }

    fclose($fp);

    }

    return $names;

    }

    ?>

    运行结果:string(6) "SimHei"

    string(5) "SimHe" //貌似有UTF-16LE编码漏字的BUG

    string(6) "黑体"

    注:如果这里仅需要获取字体名称,可将上述代码进行改进如下:<?php

    $names = GetFontName('c:/windows/fonts/FZHPJW.TTF');

    $newnames = array();

    foreach ($names as $name) {

    if ($name['language'] == 1033)

    $code = 'utf-16le';

    elseif ($name['language'] == 2052) $code = 'utf-16be';

    array_push($newnames,@mb_convert_encoding($name['name'], 'utf-8', $code));

    }

    $font_name=array_pop($newnames);

    echo $font_name;

    function GetFontName($FilePath) {

    $fp = fopen($FilePath, 'r');

    if ($fp) {

    //TT_OFFSET_TABLE

    $meta = unpack('n6', fread($fp, 12));

    //检查是否是一个true type字体文件以及版本号是否为1.0

    if ($meta[1] != 1 || $meta[2] != 0)

    return FALSE;

    $Found = FALSE;

    for ($i = 0; $i 

    //TT_TABLE_DIRECTORY

    $tablemeta = unpack('N4', $data = fread($fp, 16));

    if (substr($data, 0, 4) == 'name') {

    $Found = TRUE;

    break;

    }

    }

    if ($Found) {

    fseek($fp, $tablemeta[3]);

    //TT_NAME_TABLE_HEADER

    $tablecount = unpack('n3', fread($fp, 6));

    $Found = FALSE;

    for ($i = 0; $i 

    //TT_NAME_RECORD

    $table = unpack('n6', fread($fp, 12));

    if ($table[4] == 1) {

    $npos = ftell($fp);

    fseek($fp, $n = $tablemeta[3] + $tablecount[3] + $table[6], SEEK_SET);

    $fontname = trim($x = fread($fp, $table[5]));

    if (strlen($fontname) > 0) {

    $names[] = array (

    'platform' => $table[1], //平台(操作系统)

    'language' => $table[3], //字体名称的语言

    'encoding' => $table[2], //字体名称的编码

    'name' => $fontname //字体名称

    );

    //break;

    }

    fseek($fp, $npos, SEEK_SET);

    }

    }

    }

    fclose($fp);

    }

    return $names;

    }

    ?>

    则此时可直接输出:黑体

    上述就是小编为大家分享的使用PHP怎么获取ttf格式文件的字体名了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注亿速云行业资讯频道。

    展开全文
  • 但是具体怎么让一个不是ScrollingView类型的View(如本篇要讲的AppBarLayout继承制LinearLayout的)怎么产生滑动效果的呢?这就是本篇所要讲解的内容了。 首先,我们先用一张图说明下整个交互逻辑,这里N...

    一、整体交互逻辑

    上一篇文章,我们从CoordinatorLayout源码出发,分析了一下Behavior几个重点方法的调用逻辑和流程。知道了整个交互的分发流程。但是具体是怎么让一个不是ScrollingView类型的View(如本篇要讲的AppBarLayout继承制LinearLayout的)怎么产生滑动效果的呢?这就是本篇所要讲解的内容了。

    首先,我们先用一张图说明下整个交互逻辑,这里NestedScrollingChild实现类用NestedScrollView(这张图是网上找的)

    • CoordinatorLayout作为父布局,用于管理和分发交互事件给对应Behavior处理。

    • 系统默认会将AppBarLayout.BehaviorAppBarLayout进行绑定,负责具体处理由CoordinatorLayout分发过来的事件。

    • NestedScrollView充当滑动子View的角色(实现了NestedScrollingChild接口),在自身滑动过程中将对应事件传递个CoordinatorLayout(实现了NestedScrollingParent2接口),在分发给AppBarLayout.Behavior

    • ScrollingViewBehaviorNestedScrollView进行绑定(通过),它会依赖于AppBarLayout,从而在AppBarLayout滑动的时候,可以收到回调,做处理。

    二、源码分析

    1、类简介

    上面介绍了整体的交互逻辑,下面就开始进入源码分析吧。

    这里NestedScrollView的不是重点分析对象,不会做过多介绍,如果感兴趣的可以看这篇文章。这里我们重点看下AppBarLayout和它的两个内部类:AppBarLayout.BehaviorScrollingViewBehavior

    先说AppBarLayout

    还是看看Google的官方介绍吧,我觉得学习过程中真的要多看看Google官方文档,它里面的描述是最准确的。很多时候,我们使用过程中需要注意的事项在里面也会有提及。

    AppBarLayout is a vertical LinearLayout which implements many of the features of material designs app bar concept, namely scrolling gestures.

    Children should provide their desired scrolling behavior through setScrollFlags(int) and the associated layout xml attribute: app:layout_scrollFlags.

    This view depends heavily on being used as a direct child within a CoordinatorLayout. If you use AppBarLayout within a different ViewGroup, most of it's functionality will not work.

    AppBarLayout also requires a separate scrolling sibling in order to know when to scroll. The binding is done through the AppBarLayout.ScrollingViewBehavior behavior class, meaning that you should set your scrolling view's behavior to be an instance of AppBarLayout.ScrollingViewBehavior. A string resource containing the full class name is available.

    大致翻译下就是:AppBarLayout是垂直方向的LinearLayout,同时遵循了material designs,可以支持很多滑动手势的交互。

    通过在xml文件中设置app:layout_scrollFlags属性,或者通过setScrollFlags(int) 可以给子View设置不同的交互逻辑。

    同时需要注意,该类只能作为CoordinatorLayout的直接子View,否则它的那些特性将会失效。你看这里Google就清除说明了使用时候的注意事项。所以多看官方文档总是没错的。

    为了和滑动控件进行交互绑定,我们需要将AppBarLayout.ScrollingViewBehavior类的全路劲设置给需要进行交互的滑动View(如NestedScrollView)。

    介绍完AppBarLayout,我们再看看AppBarLayout.Behavior

    The default AppBarLayout.Behavior for AppBarLayout. Implements the necessary nested scroll handling with offsetting.

    这个类,介绍很简单,就说是AppBarLayout的默认Behavior,实现了嵌套滑动的交互处理。

    最后,我们再看下ScrollingViewBehavior

    Behavior which should be used by Views which can scroll vertically and support nested scrolling to automatically scroll any AppBarLayout siblings.

    这个类的介绍也很简单,继承自Behavior,需要设置给要自动响应AppBarLayout嵌套滑动事件的View。如NestedScrollView

    2、交互流程分析

    上面大致说了每个类在交互过程中担任的角色,也大致看了一下简介,下面就进入具体的分析。AppBarLayout和其他可嵌套滑动的View(为了叙述方面,我们都直接用NestedScrollView代替了)之间的交互,无非就两种情况。

    • AppBarLayout引起的滑动,然后NestedScrollView需要进行相关处理

    • 由NestedScrollView引起的滑动,AppBarLayout需要进行相关处理

    所以,下面我们也从这两种方式进行分析,首先我们有AppBarLayout产生滑动的情况,这种情况下,我们的触摸范围是在AppBarLayout所在的范围, 通过上一篇CoordinatorLayout源码分析的学习,我们知道,最终会由CoordinatorLayout分发给它的子View的Behavior处理,这里会给到AppBarLayout.Behavior。所以这里我们就先看下AppBarLayout.Behavior中的onInterceptTouchEvent。但是我们发现,这个类里面没有这个方法。其实我们仔细看,会发现它是继承自HeaderBehaviorHeaderBehavior继承自ViewOffsetBehavior。所以,既然它自己没有这个方法,就找它父类了。在HeaerBehavior中有实现。

    @Override
          public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
              if (mTouchSlop < 0) {
                  mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
              }
      ​
              final int action = ev.getAction();
      ​
              // Shortcut since we're being dragged
              if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) {
                  return true;
              }
      ​
              switch (ev.getActionMasked()) {
                  case MotionEvent.ACTION_DOWN: {
                      mIsBeingDragged = false;
                      final int x = (int) ev.getX();
                      final int y = (int) ev.getY();
                      // 判断是否可以滑动 并且点击位置要在child(AppBarLayout)的范围内
                      if (canDragView(child) && parent.isPointInChildBounds(child, x, y)) {
                          // 记录下点击位置
                          mLastMotionY = y;
                          mActivePointerId = ev.getPointerId(0);
                          ensureVelocityTracker();
                      }
                      break;
                  }
      ​
                  case MotionEvent.ACTION_MOVE: {
                      final int activePointerId = mActivePointerId;
                      if (activePointerId == INVALID_POINTER) {
                          // If we don't have a valid id, the touch down wasn't on content.
                          break;
                      }
                      final int pointerIndex = ev.findPointerIndex(activePointerId);
                      if (pointerIndex == -1) {
                          break;
                      }
      ​
                      final int y = (int) ev.getY(pointerIndex);
                      final int yDiff = Math.abs(y - mLastMotionY);
                      // 判断是不是滑动操作
                      if (yDiff > mTouchSlop) {
                          // 设置拦截标志为true
                          mIsBeingDragged = true;
                          // 几下当前手指的位置
                          mLastMotionY = y;
                      }
                      break;
                  }
      ​
                  case MotionEvent.ACTION_CANCEL:
                  case MotionEvent.ACTION_UP: {
                      mIsBeingDragged = false;
                      mActivePointerId = INVALID_POINTER;
                      // Fling相关处理(Fling相关知识,不是重点,就自己去补充了)
                      if (mVelocityTracker != null) {
                          mVelocityTracker.recycle();
                          mVelocityTracker = null;
                      }
                      break;
                  }
              }
              // Fling相关处理
              if (mVelocityTracker != null) {
                  mVelocityTracker.addMovement(ev);
              }
      ​
              return mIsBeingDragged;
          }

    可以看到,里面代码还是比较简单的,主要就是判断是否是有效滑动,如果是,我们就拦截事件,然后交给自己的onTouchEvent()处理。具体过程请看方法里面的注释吧。

    然后我们就进入onTouchEvent()里面看下吧,我们发现,这个方法也是在HeaderBehavior中处理的。

    @Override
          public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent ev) {
              if (mTouchSlop < 0) {
                  mTouchSlop = ViewConfiguration.get(parent.getContext()).getScaledTouchSlop();
              }
      ​
              switch (ev.getActionMasked()) {
                  case MotionEvent.ACTION_DOWN: {
                      final int x = (int) ev.getX();
                      final int y = (int) ev.getY();
                      // 也是判断是否可以滑动 并且在范围内
                      if (parent.isPointInChildBounds(child, x, y) && canDragView(child)) {
                          // 是:记下触摸位置
                          mLastMotionY = y;
                          mActivePointerId = ev.getPointerId(0);
                          ensureVelocityTracker();
                      } else {
                          // 不是 直接放行
                          return false;
                      }
                      break;
                  }
      ​
                  case MotionEvent.ACTION_MOVE: {
                      final int activePointerIndex = ev.findPointerIndex(mActivePointerId);
                      if (activePointerIndex == -1) {
                          return false;
                      }
      ​
                      final int y = (int) ev.getY(activePointerIndex);
                      // 计算滑动距离
                      int dy = mLastMotionY - y;
      ​
                      // 如果是有效滑动并且之前没有进行过滑动
                      if (!mIsBeingDragged && Math.abs(dy) > mTouchSlop) {
                          // 设置滑动标志
                          mIsBeingDragged = true;
                          // 微调滑动距离
                          if (dy > 0) {
                              dy -= mTouchSlop;
                          } else {
                              dy += mTouchSlop;
                          }
                      }
      ​
                      // 如果手指是在滑动
                      if (mIsBeingDragged) {
                          mLastMotionY = y;
                          // We're being dragged so scroll the ABL
                          // 根据手指滑动的距离,移动AppBarLayout
                          scroll(parent, child, dy, getMaxDragOffset(child), 0);
                      }
                      break;
                  }
      ​
                  case MotionEvent.ACTION_UP:
                      if (mVelocityTracker != null) {
                          mVelocityTracker.addMovement(ev);
                          mVelocityTracker.computeCurrentVelocity(1000);
                          float yvel = mVelocityTracker.getYVelocity(mActivePointerId);
                          // 抬起的时候处理的Fling相关逻辑
                          fling(parent, child, -getScrollRangeForDragFling(child), 0, yvel);
                      }
                      // $FALLTHROUGH
                  case MotionEvent.ACTION_CANCEL: {
                      // 取消 重置滑动标志
                      mIsBeingDragged = false;
                      mActivePointerId = INVALID_POINTER;
                      if (mVelocityTracker != null) {
                          mVelocityTracker.recycle();
                          mVelocityTracker = null;
                      }
                      break;
                  }
              }
      ​
              if (mVelocityTracker != null) {
                  mVelocityTracker.addMovement(ev);
              }
      ​
              return true;
          }

    方法注释里里面已经说的比较清楚了,可以看到。有效滑动的时候,我们交给scroll()方法进行处理的,然后Fling操作是交给fling()方法处理的。我们先看scroll()方法

    final int scroll(CoordinatorLayout coordinatorLayout, V header,
                  int dy, int minOffset, int maxOffset) {
              return setHeaderTopBottomOffset(coordinatorLayout, header,
                      getTopBottomOffsetForScrollingSibling() - dy, minOffset, maxOffset);
          }

    又给到了setHeaderTopBottomOffset()处理,那我们继续跟踪,这里要注意下。AppBarLayout.Behavior重写了该方法,所以这里需要进入AppBarLayout.Behavior里面看实现逻辑:

    @Override
              //newOffeset:AppBarLayout已经移动的距离-手指滑动的距离(手指向上滑为正,向下滑为负)
              //minOffset:负的AppBarLayout的height,
              //maxOffset:0。
              int setHeaderTopBottomOffset(CoordinatorLayout coordinatorLayout,
                      AppBarLayout appBarLayout, int newOffset, int minOffset, int maxOffset) {
                  // 获取当前AppBarLayout的偏移量(已经移动过的距离)
                  final int curOffset = getTopBottomOffsetForScrollingSibling();
                  int consumed = 0;
      ​
                  // 合法性检测:AppBarLayout滑动的距离必须在minOffset和maxOffset之间
                  if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
                      // 计算出最终可以移动的距离
                      newOffset = MathUtils.clamp(newOffset, minOffset, maxOffset);
                      // 判断是否需要移动(newOffset就是本次滑动最终需要产生的偏移量)
                      // 如果当前的偏移量和最终需要的不相等,才进行移动
                      if (curOffset != newOffset) {
                          final int interpolatedOffset = appBarLayout.hasChildWithInterpolator()
                                  // 如果我们自己设置了插值器 会进行条用
                                  ? interpolateOffset(appBarLayout, newOffset)
                                  : newOffset;
      ​
                          // 最终通过ViewCompat.offsetTopAndBottom()移动AppBarLayout
                          final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
      ​
                          // 更新消费的距离
                          consumed = curOffset - newOffset;
                          // 如果没有设置Interpolator 为0
                          mOffsetDelta = newOffset - interpolatedOffset;
      ​
                          if (!offsetChanged && appBarLayout.hasChildWithInterpolator()) {
                          // 虽然这里没有移动操作 但是在我们自己设置的插值器中可能产生了移动 需要给依赖的View发送通知
                              coordinatorLayout.dispatchDependentViewsChanged(appBarLayout);
                          }
      ​
                          // 回调OnOffsetChangedListener监听
                          appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
      ​
                          // 根据我们设置的ScrollFlags调整状态
                          updateAppBarLayoutDrawableState(coordinatorLayout, appBarLayout, newOffset,
                                  newOffset < curOffset ? -1 : 1, false);
                      }
                  } else {
                      // Reset the offset delta
                      mOffsetDelta = 0;
                  }
      ​
                  return consumed;
              }
    
     

    在这里面,我们看到,正常情况下就会setTopAndBottomOffset()方法来移动我们的AppBarLayout了,同时做回调通知,最后根据设置的不同的scrolls_flag更新子View的状态(该隐藏的隐藏,该出来的出来)。

    到这里,我们知道AppBarLayout是怎么通过手指滑动产生移动的了,但是我们实际使用中,AppBarLayout发生移动的同时,NestedScrollView也会跟着上下滚动,NestedScrollView又是怎么知道需要滚动的呢?还记得在介绍类的时候说了,必须给NestedScrollView设置ScrollingViewBehavior么。玄机就在这个类里面,我们看下该类:

      public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
      ​
              public ScrollingViewBehavior() {}
      ​
              public ScrollingViewBehavior(Context context, AttributeSet attrs) {
                  super(context, attrs);
      ​
                  final TypedArray a = context.obtainStyledAttributes(attrs,
                          R.styleable.ScrollingViewBehavior_Layout);
                  setOverlayTop(a.getDimensionPixelSize(
                          R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
                  a.recycle();
              }
      ​
              @Override
              public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
                  // 同AppBarLayout产生关联,那AppBarLayout发生移动的时候,就会收到回调了
                  return dependency instanceof AppBarLayout;
              }
      ​
              @Override
              public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
                      View dependency) {
                  // 这里收到AppBarLayout移动的通知,然后就跟着移动了
                  offsetChildAsNeeded(parent, child, dependency);
                  return false;
              }
      ​
              ……
      ​
              private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
                  // 获取到AppBarLayout的Behavior
                  final CoordinatorLayout.Behavior behavior =
                          ((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
                  if (behavior instanceof Behavior) {
                      // Offset the child, pinning it to the bottom the header-dependency, maintaining
                      // any vertical gap and overlap
                      final Behavior ablBehavior = (Behavior) behavior;
                      // 计算出需要移动的距离,并跟随移动
                      ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
                              + ablBehavior.mOffsetDelta
                              + getVerticalLayoutGap()
                              - getOverlapPixelsForOffset(dependency));
                  }
              }
              ……
          }

    这里就不多解释了,看过上篇文章应该很容易就理解了,NestedScrollView和AppBarLayout通过这个类产生了依赖关系。AppBarLayout移动的时候会通知进入这里。就可以做同步移动操作了。

    到这里,AppBarLayout的移动,并通知NestedScrollView同时移动的过程就分析完了。其实在onTouchEvent()中ACTION_UP的时候,还有一个fling()方法进行惯性滑动的操作。这里就不具体分析,机制也是一样,感兴趣的同学自己看下源码就明白了。只是这里说明一点,因为Fling的主体是AppBarLayout,所以不管我们在其上面滑动多块,最多也就只能滑动AppBarLayout的最大可滑动距离,而不会去滑动NestedScrollView。

    说完AppBarLayout的滑动后,我们接着看我们之前说的第二种情况,由NestedScrollView滑动,引起AppBarLayout相应移动的逻辑了。我们知道,NestedScrollView滑动的时候,会通过CoordinatorLayout将相关的方法代理给Behavior来处理(这里也就是AppBarLayout.Behavior)。根据嵌套滑动机制,会首先调用onStartNestedScroll()方法。那我们进看下这个实现逻辑吧:

    @Override
          //directTargetChild=target=NestedScrollView
              public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child,
                      View directTargetChild, View target, int nestedScrollAxes, int type) {
                  //如果滑动方向为VERTICAL,有足够空间进行滑动,并且有可滑动Child,started=true;
                  final boolean started = (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0
                          && child.hasScrollableChildren()
                          && parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
      ​
                  if (started && mOffsetAnimator != null) {
                      // Cancel any offset animation
                      mOffsetAnimator.cancel();
                  }
      ​
                  // A new nested scroll has started so clear out the previous ref
                  mLastNestedScrollingChildRef = null;
      ​
                  return started;
              }
    这里主要方向和有足够滑动空间比较好理解,说下要有可以滑动的Child这个判断,看下具体实现:
    
      boolean hasScrollableChildren() {
              return getTotalScrollRange() != 0;
          }
          
      public final int getTotalScrollRange() {
              if (mTotalScrollRange != INVALID_SCROLL_RANGE) {
                  return mTotalScrollRange;
              }
      ​
              int range = 0;
              for (int i = 0, z = getChildCount(); i < z; i++) {
                  final View child = getChildAt(i);
                  final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                  final int childHeight = child.getMeasuredHeight();
                  final int flags = lp.mScrollFlags;
      ​
                  if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
                      // We're set to scroll so add the child's height
                      range += childHeight + lp.topMargin + lp.bottomMargin;
      ​
                      if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                          // For a collapsing scroll, we to take the collapsed height into account.
                          // We also break straight away since later views can't scroll beneath
                          // us
                          range -= ViewCompat.getMinimumHeight(child);
                          break;
                      }
                  } else {
                      // As soon as a view doesn't have the scroll flag, we end the range calculation.
                      // This is because views below can not scroll under a fixed view.
                      break;
                  }
              }
              return mTotalScrollRange = Math.max(0, range - getTopInset());
          }

    就是通过判断ChildView的scroll_flags属性,计算设置了SCROLL_FLAG_SCROLL属性的Child高度和,设置了该属性就代表是可以滚动的View,然后减去设置SCROLL_FLAG_EXIT_UNTIL_COLLAPSED的折叠高度,为什么要减去呢,因为这个标志,代表该View会变小知道达到最小高度,所以该控件的最小高度是不能计算在可滚动范围内的。

    回到主流程上,所以正常情况下,onStartNestedScroll()是会返回true,也就是告诉系统我要消费滑动,后面就会继续进入onNestedPreScroll()方法。

    @Override
              public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                      View target, int dx, int dy, int[] consumed, int type) {
                  if (dy != 0) {
                      int min, max;
                      if (dy < 0) {
                          // 向下滚动 
                          min = -child.getTotalScrollRange(); // 该方法上面已经讲过了,获取到可滚动距离,然后取负数
                          max = min + child.getDownNestedPreScrollRange(); // 判断有没有向下滑动需要立即滑出的距离
                      } else {
                          // 向上滚动
                          min = -child.getUpNestedPreScrollRange(); // 内部就是getTotalScrollRange()
                          max = 0;
                      }
                      if (min != max) {
                          // 通过scroll方法进行滑动处理
                          consumed[1] = scroll(coordinatorLayout, child, dy, min, max);
                      }
                  }
              }

    上面注释已经说的比较清楚了。就是根据滑动方向,然后分别做好边界值的处理。最后通过scroll()方法进行滑动处理。

    getUpNestedPreScrollRange() 就是调用的getTotalScrollRange(),上面已经讲了,不再赘述。getDownNestedPreScrollRange()这个方法会获取到下滑的时候,需要立即移动的距离,然后加上min的值给到max。这样在后面通过scroll()方法处理移动的时候,才能够展示,如果这里返回时0,代表没有需要里展示的情况,这样min和max两个值相等,最终sroll()内部就不会产生移动,consumed[1]=0,最终就会给NestedScrollView自己去消费滑动距离。

    int getDownNestedPreScrollRange() {
              if (mDownPreScrollRange != INVALID_SCROLL_RANGE) {
                  // If we already have a valid value, return it
                  return mDownPreScrollRange;
              }
      ​
              int range = 0;
              for (int i = getChildCount() - 1; i >= 0; i--) {
                  final View child = getChildAt(i);
                  final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                  final int childHeight = child.getMeasuredHeight();
                  final int flags = lp.mScrollFlags;
      ​
                  if ((flags & LayoutParams.FLAG_QUICK_RETURN) == LayoutParams.FLAG_QUICK_RETURN) {
                      // First take the margin into account
                      range += lp.topMargin + lp.bottomMargin;
                      // The view has the quick return flag combination...
                      if ((flags & LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED) != 0) {
                          // If they're set to enter collapsed, use the minimum height
                          range += ViewCompat.getMinimumHeight(child);
                      } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
                          // Only enter by the amount of the collapsed height
                          range += childHeight - ViewCompat.getMinimumHeight(child);
                      } else {
                          // Else use the full height (minus the top inset)
                          range += childHeight - getTopInset();
                      }
                  } else if (range > 0) {
                      // If we've hit an non-quick return scrollable view, and we've already hit a
                      // quick return view, return now
                      break;
                  }
              }
              return mDownPreScrollRange = Math.max(0, range);
          }

    就是判断是否有设置enterAlways或者enterAlwaysCollapsed的flag。如果只有scroll|enterAlways,下滑的时候,需要先将控件显示出来,需要加上整个View的高度。如果是scroll|enterAlways|enterAlwaysCollapsed,需要先滑出控件的最小高度,需要加上控件的最小高度。

    接着继续嵌套滑动机制的下一步骤onNestedScroll()onStopNestedScroll()方法。这两个方法比较简单,就一起介绍了

    @Override
              public void onNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout child,
                      View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed,
                      int type) {
                  if (dyUnconsumed < 0) {
                      // If the scrolling view is scrolling down but not consuming, it's probably be at
                      // the top of it's content
                      scroll(coordinatorLayout, child, dyUnconsumed,
                              -child.getDownNestedScrollRange(), 0);
                  }
              }
      ​
              @Override
              public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, AppBarLayout abl,
                      View target, int type) {
                  if (type == ViewCompat.TYPE_TOUCH) {
                      // If we haven't been flung then let's see if the current view has been set to snap
                      snapToChildIfNeeded(coordinatorLayout, abl);
                  }
      ​
                  // Keep a reference to the previous nested scrolling child
                  mLastNestedScrollingChildRef = new WeakReference<>(target);
              }

    onNestedScroll()就是判断是否还有未消费的滑动距离,如果有,就直接交给scroll()方法进行处理。一般就是NestedScrollView已经滑到边界的时候,才会有剩余未消费的距离,其他情况一般dyUnconsumed=0

    onStopNestedScroll()就是对吸附效果进行处理,也就是snap标志位,判断最终是要全部显示设置了snap的View还是全部隐藏。

    展开全文
  • 使用php怎么将数组转换成json格式发布时间:2021-01-05 16:43:16来源:亿速云阅读:71作者:Leah这期内容当中小编将会给大家带来有关使用php怎么将数组转换成json格式,文章内容丰富且以专业的角度为大家分析和叙述...
  • 分享给大家供大家参考,具体如下:在http简析中,我们提到了浏览器请求资源的一个流程,那么这个流程能不能用php来模拟呢?...写入http协议使用fwrite向资源写入内容3.接收数据请求成功后返回的数据会被存放...
  • 2021-02-19 17:29:06来源:亿速云阅读:82作者:Leah这期内容当中小编将会给大家带来有关使用php怎么将数组转换为csv格式的文件并输出,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以...
  • 使用php怎么将网页中的图片存入数组发布时间:2021-01-27 16:21:16来源:亿速云阅读:93作者:Leah这期内容当中小编将会给大家带来有关使用php怎么将网页中的图片存入数组,文章内容丰富且以专业的角度为大家分析和...
  • 2020-12-16 15:46:56来源:亿速云阅读:82作者:Leah这期内容当中小编将会给大家带来有关使用PHP怎么实现一个生成唯一订单号功能,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获...
  • 编者按:在本文中,来自腾讯平台与内容事业部(PCG)数据服务中心的田红瀚将介绍他们的工程师是如何基于...具体地,通过使用Alluxio作为一个分布式缓存层,Impala SQL查询中的HDFS扫描的性能在不经进一步优化的情况...
  • 今天为大家带来的内容是:干货分享:python爬虫模拟浏览器的两种方法实例分析(赶紧收藏)文章主要介绍了python爬虫模拟浏览器的两种方法,结合实例形式分析了Python爬虫模拟浏览器的两种常见操作技巧与使用注意事项,...
  • 具体如下:相关内容:什么是模块模块的导入模块的导入自模块的导入同级目录导入不同级目录导入目录内导入目录外目录外导入目录内__name__什么是模块:在Python中,模块就是一个个方法和类的仓库,如果我们想要使用...
  • 这是需要学习的很具体内容怎么可能用工具就一下子做完。 一个网站的SEO优化时长期的! 外贸SEO的外贸工具有哪些? 外贸SEO的外贸工具: Yoast的高级服务不是免费的,但该公司确实提供了一些免费的增值服务,...
  • 可调电阻具体怎样接线常见的可调电阻具体怎么接,这是我们在工作过程中应该注意的细节,要知道其接法就必须了解可调电阻的原理是什么,这样在连接的过程中才能够更加准确。在对于可调电阻的连接过程中还要对影响可调...
  • Android 上百实例源码分析以及开源分析 集合打包4

    千次下载 热门讨论 2012-07-10 21:54:03
    在Jamendo中,主要是通过再定义一个SeparatedListAdapter来进行这个工作,我们来看看它是怎么实现的:我理解的Adapter过程,首先通过调用getCount()来获得总Row数目,然后对一行调用getView进行绘制,因此要实现在...
  • 连连看算法分析分享

    2020-09-11 10:17:57
    章节内容: 本章主要是介绍连连看常用的算法,包括连连看的地图生成算法,连连看的匹配和消除算法。不涉及具体的代码实现。 相信很多同学在自己的游戏生涯里面都玩过连连看游戏,那么连连看游戏是怎么做的呢?用...
  • :这里我们使用的是 id 选择器,如果不清楚的话可以点击下方 HTML 的豪华外套之 ”CSS“ 进入关于 css 的页面中进行介绍中进行查看, id选择器的大致意思是 可以具体到位,单独装饰 HTML 的豪华外套之 ”CSS“ @...
  • 四川企信云讯呱呱怎么收费的方法策略根据收料分析,确定网络推广方法及策略,详细列出将使用哪些网络推广方法,如搜索引擎推广,博客推广,邮件群发营销,内容营销,QQ群通讯,社区发帖,攒写软文宣传,活动推广,...
  • 11月10日14:30——16:20 一、需求捕获 问题1:说说需求捕获的常用方法 问题2:说说你用到的需求捕获的方法、...1.说说5W+1H工作法的内容,以及在工作中怎么使用。 2.举一个具体的例子说明需求分析的主要过程。
  • 本文部分内容为了上传便捷使用截图。 数据分析流程 特征工程框架图: 具体步骤及内容: 1获取数据 2 数据预处理 2.1 特征工程 2.1.1 特征理解 拿到数据的第一件事情当然是看数据怎么样了,也就是看里面...
  • 详细内容Python中的下划线具有特殊的意义,...(推荐学习:Python视频教程)在Python中下划线还具有 private 和 protected 类似的访问权限作用,下面我们具体分析。Python主要存在四种命名:(1)object #公用方法(2...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 390
精华内容 156
关键字:

具体怎么使用内容分析