Alex_Max

Alex_Max 查看完整档案

填写现居城市  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

Alex_Max 发布了文章 · 1月14日

前端下载文件时怎么重命名

一般来说,我们有一个下载链接url,window.open(url, "_blank")就可以实现下载,但是这个有个问题就是文件默认的名称也许不是需求希望的,但是我们又希望可以指定一个名称,就可以采用下面的办法

onDownload(url,fileName) {
      let self = this;
      let x = new XMLHttpRequest();
      x.open("GET", url, true);
      x.responseType = "blob";
      x.onload = function() {
        //self.exportLoading = false;
        let url = window.URL.createObjectURL(x.response);
        let a = document.createElement("a");
        a.href = url;
        a.download = fileName;
        a.click();
      };
      x.send();
    },
查看原文

赞 0 收藏 0 评论 0

Alex_Max 发布了文章 · 1月8日

vue项目中web worker的使用

主要分为2个文件,一个worker文件,一个主文件(调用worker的).

worker文件,需要以worker.js为后缀名,你可以直接使用worker.js或者xx.worker.js都是可以的。

self.onmessage = (e) => {
  console.log("worker 接受到的message e--", e.data);
  for (let index = 0; index < 9999999999; index++) {
    if (index === 9999999999 - 1) {
      self.postMessage("计算结束了");
    }
  }
};

主文件,调用的地方可以是vue文件或者js文件都是可以的。这个看每个人的需求。我们这里以vue文件为例

import XX from "./xx.worker";//这个路径根据自己真实情况配置
...
created() {
    let xx = new XX();
    xx.postMessage({ start: "开始计算" });
    xx.onmessage = (msg) => {
      console.log("msg--", msg);
    };
}

相互之间通信主要就是依赖于postMessage和onmessage。核心代码就是这样,很简单。

再说下vue下vue.config.js的配置,前提需要npm install worker-loader -D

chainWebpack: (config) => {
    config.module
      .rule("worker")
      .test(/\.worker\.js$/)
      .use("worker-loader")
      .loader("worker-loader")
      .options({
        inline: "fallback"
      });
    config.module.rule("js").exclude.add(/\.worker\.js$/);
  },
查看原文

赞 0 收藏 0 评论 0

Alex_Max 赞了文章 · 2020-11-22

MobX 在 hook 中的使用

关于 mobX 在 react 16.8.0 以上的用法
以下例子均取自官网文档

一般用法:

import { observer, useLocalStore } from 'mobx-react';  
const Hooks = observer(() => {  
 const todo = useLocalStore(() =>  ({  
 title: 'Click to toggle', done: false,  toggle() {  
  todo.done = !todo.done  
  },  
 get emoji() {  
 return todo.done ? '😜' : '🏃'  
  },  
  }));  
  
 return <div onClick={todo.toggle}>  
    <h3>{todo.title} {todo.emoji}</h3>  
 </div>  
})  

可以看到原来的修饰符@observer,
现在是使用 HOC 来进行扩展的;

还有另外的 2 种修饰方法:

1:

import { useLocalStore, useObserver } from 'mobx-react';  
function Person() {  
 const person = useLocalStore(() => ({ name: 'John' }))  
 return useObserver(() => (  
 <div>  
    {person.name}  
    <button onClick={() => (person.name = 'Mike')}>No! I am Mike</button>  
 </div>  
 ))}  

使用 useObserver(()=>JSX.Element) 方法取代 observer(()=>JSX.Element)

2:

import { Observer, useLocalStore } from 'mobx-react';  
function ObservePerson() {  
 const person = useLocalStore(() => ({ name: 'John' }))  
 return (  
 <div>  
    {person.name} <i>I will never change my name</i>  
    <div>  
        <Observer>{() => <div>{person.name}</div>}</Observer>  
        <button onClick={() => (person.name = 'Mike')}>  
 I want to be Mike  </button>  
    </div>  
 </div>  
 )}  

使用部分渲染, 只有被 <Observer></Observer> 包裹的才能监听到对应值的改变

优化,分离,传值

import React, { FC } from 'react';import { observer, useLocalStore } from 'mobx-react';  
function initialFn(source) {  
 return ({  
 count: 2, get multiplied() {  
 return source.multiplier * this.count  
  },  
  inc() {  
 this.count += 1  },  
  });  
}  
  
const Counter: FC<{ multiplier: number }> = observer(props => {  
  
 const store = useLocalStore(  
  initialFn,  
 Object.assign({ a: 1 }, props),// 可以传任意值  
  );  
  
 return (  
 <div>  
    <button id="inc" onClick={store.inc}>  
    {`Count: ${store.count}`}  
    </button>  
    <span>{store.multiplied}</span>  
 </div>  
 )})  

这种分离的方法,基本已经耦合性已经很低了

查看原文

赞 3 收藏 0 评论 0

Alex_Max 回答了问题 · 2020-09-25

bee new创建项目跑到了GOPATH的bin目录下, 教程都是在src下创建的项目, 怎么回事?

我也遇到了 beego感觉对go mod没有做适配,还用的老的模式

关注 5 回答 4

Alex_Max 关注了标签 · 2020-09-25

golang

Go语言是谷歌2009发布的第二款开源编程语言。Go语言专门针对多处理器系统应用程序的编程进行了优化,使用Go编译的程序可以媲美C或C++代码的速度,而且更加安全、支持并行进程。
Go语言是谷歌推出的一种全新的编程语言,可以在不损失应用程序性能的情况下降低代码的复杂性。谷歌首席软件工程师罗布派克(Rob Pike)说:我们之所以开发Go,是因为过去10多年间软件开发的难度令人沮丧。Go是谷歌2009发布的第二款编程语言。

七牛云存储CEO许式伟出版《Go语言编程
go语言翻译项目 http://code.google.com/p/gola...
《go编程导读》 http://code.google.com/p/ac-m...
golang的官方文档 http://golang.org/doc/docs.html
golang windows上安装 http://code.google.com/p/gomi...

关注 26112

Alex_Max 收藏了问题 · 2020-09-21

用ant design中的Button的时候 属性ant-click-animating-withou如何取消

用ant design中的Button的时候 每次点击都会在视图button上添加一个属性ant-click-animating-withou 这个属性如何才能取消 或者说 每次点击button的时候 都会触发一层光晕式的对象 不管是重新设置outline或许box-shadow都无法取消

Alex_Max 收藏了文章 · 2020-08-29

浅谈js防抖和节流

防抖和节流严格算起来应该属于性能优化的知识,但实际上遇到的频率相当高,处理不当或者放任不管就容易引起浏览器卡死。所以还是很有必要早点掌握的。(信我,你看完肯定就懂了)

从滚动条监听的例子说起

先说一个常见的功能,很多网站会提供这么一个按钮:用于返回顶部。
返回顶部按钮

这个按钮只会在滚动到距离顶部一定位置之后才出现,那么我们现在抽象出这个功能需求-- 监听浏览器滚动事件,返回当前滚条与顶部的距离
这个需求很简单,直接写:

function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll  = showTop

但是!

图片描述

在运行的时候会发现存在一个问题:这个函数的默认执行频率,太!高!了!。 高到什么程度呢?以chrome为例,我们可以点击选中一个页面的滚动条,然后点击一次键盘的【向下方向键】,会发现函数执行了8-9次
图片描述

然而实际上我们并不需要如此高频的反馈,毕竟浏览器的性能是有限的,不应该浪费在这里,所以接着讨论如何优化这种场景。

防抖(debounce)

基于上述场景,首先提出第一种思路:在第一次触发事件时,不立即执行函数,而是给出一个期限值比如200ms,然后:

  • 如果在200ms内没有再次触发滚动事件,那么就执行函数
  • 如果在200ms内再次触发滚动事件,那么当前的计时取消,重新开始计时

效果:如果短时间内大量触发同一事件,只会执行一次函数。

实现:既然前面都提到了计时,那实现的关键就在于setTimeout这个函数,由于还需要一个变量来保存计时,考虑维护全局纯净,可以借助闭包来实现:

/*
* fn [function] 需要防抖的函数
* delay [number] 毫秒,防抖期限值
*/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) //进入该分支语句,说明当前正在一个计时过程中,并且又触发了相同事件。所以要取消当前的计时,重新开始计时
            timer = setTimeout(fn,delay) 
        }else{
            timer = setTimeout(fn,delay) // 进入该分支说明当前并没有在计时,那么就开始一个计时
        }
    }
}

当然 上述代码是为了贴合思路,方便理解(这么贴心不给个赞咩?),写完会发现其实 time = setTimeout(fn,delay)是一定会执行的,所以可以稍微简化下:


/*****************************简化后的分割线 ******************************/
function debounce(fn,delay){
    let timer = null //借助闭包
    return function() {
        if(timer){
            clearTimeout(timer) 
        }
        timer = setTimeout(fn,delay) // 简化写法
    }
}
// 然后是旧代码
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = debounce(showTop,1000) // 为了方便观察效果我们取个大点的间断值,实际使用根据需要来配置

此时会发现,必须在停止滚动1秒以后,才会打印出滚动条位置。

到这里,已经把防抖实现了,现在给出定义:

  • 对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。

节流(throttle)

继续思考,使用上面的防抖方案来处理问题的结果是:

  • 如果在限定时间段内,不断触发滚动事件(比如某个用户闲着无聊,按住滚动不断的拖来拖去),只要不停止触发,理论上就永远不会输出当前距离顶部的距离。

但是如果产品同学的期望处理方案是:即使用户不断拖动滚动条,也能在某个时间间隔之后给出反馈呢?(此处暂且不论哪种方案更合适,既然产品爸爸说话了我们就先考虑怎么实现)
图片描述

其实很简单:我们可以设计一种类似控制阀门一样定期开放的函数,也就是让函数执行一次后,在某个时间段内暂时失效,过了这段时间后再重新激活(类似于技能冷却时间)。

效果:如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

实现 这里借助setTimeout来做一个简单的实现,加上一个状态位valid来表示当前函数是否处于工作状态:

function throttle(fn,delay){
    let valid = true
    return function() {
       if(!valid){
           //休息时间 暂不接客
           return false 
       }
       // 工作时间,执行函数并且在间隔期内把状态位设为无效
        valid = false
        setTimeout(() => {
            fn()
            valid = true;
        }, delay)
    }
}
/* 请注意,节流函数并不止上面这种实现方案,
   例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
   也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
    */

// 以下照旧
function showTop  () {
    var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
  console.log('滚动条位置:' + scrollTop);
}
window.onscroll = throttle(showTop,1000) 

运行以上代码的结果是:

  • 如果一直拖着滚动条进行滚动,那么会以1s的时间间隔,持续输出当前位置和顶部的距离

其他应用场景举例

讲完了这两个技巧,下面介绍一下平时开发中常遇到的场景:

  1. 搜索框input事件,例如要支持输入实时搜索可以使用节流方案(间隔一段时间就必须查询相关内容),或者实现输入间隔大于某个值(如500ms),就当做用户输入完成,然后开始搜索,具体使用哪种方案要看业务需求。
  2. 页面resize事件,常见于需要做页面适配的时候。需要根据最终呈现的页面情况进行dom渲染(这种情形一般是使用防抖,因为只需要判断最后一次的变化情况)

思考总结

上述内容基于防抖和节流的核心思路设计了简单的实现算法,但是不代表实际的库(例如undercore js)的源码就直接是这样的,最起码的可以看出,在上述代码实现中,因为showTop本身的很简单,无需考虑作用域和参数传递,所以连apply都没有用到,实际上肯定还要考虑传递argument以及上下文环境(毕竟apply需要用到this对象)。这里的相关知识在本专栏《柯里化》和《this对象》的文章里也有提到。本文依然坚持突出核心代码,尽可能剥离无关功能点的思路行文因此不做赘述。


惯例:如果内容有错误的地方欢迎指出(觉得看着不理解不舒服想吐槽也完全没问题);如果有帮助,欢迎点赞和收藏,转载请征得同意后著明出处,如果有问题也欢迎私信交流,主页有邮箱地址

查看原文

Alex_Max 收藏了文章 · 2020-08-26

2020年 16 个最有用的 Vue UI库

点赞再看,养成习惯

本文 GitHubhttps://github.com/qq44924588... 上已经收录,更多往期高赞文章的分类,也整理了很多我的文档,和教程资料。欢迎Star和完善,大家面试可以参照考点复习,希望我们一起有点东西。

本文主要跟大家分享一些 Vue 的 UI 库,这些库都是 github 高星的库,废话不多说,我们一直来看看。

1. Vuetify (⭐️ 24k)

网站:https://vuetifyjs.com/zh-Hans/
github:https://github.com/vuetifyjs/...

Vuetify 是一个 Vue UI 库,包含手工制作的精美材料组件。不需要设计技能 - 创建令人惊叹的应用程序所需的一切都触手可及。Vuetify有超过100个组件元素,带有响应式网格系统,完全支持事件处理。通过每周的补丁和持续的更新,Vuetify 很可能在未来几年内仍然是最受欢迎的Vue库之一。

clipboard.png

2. Vue Material (⭐️ 8587)

网站:https://vuematerial.io/
github:https://github.com/vuemateria...

Vue Material 是一个轻量级的框架, 建立在谷歌的 Material Design 基础上。 设计强大的和美观的web应用并适用于不同的屏幕。我们可以动态地生成和使用主题,根据自己的需求只用组件,UI元素与组件的优势在于可以更简单的使用API和其他的。Vue Material 的目的是提供一组可重用的组件和一系列的UI元素,使用 Vue 2.0 建立支持 主流的浏览器 的应用。

clipboard.png

3. Element UI (⭐️ 44k)

网站:https://element.eleme.io/#/zh...

这个就不多说,国内前端开发基本都知道的,强大好用的 Vue UI 库。

clipboard.png

4. Buefy (⭐️ 7.1k)

网站:https://buefy.org/
github:https://github.com/buefy/buefy

如果我们希望为项目提供一个更简单且轻量的UI库,那么这个基于bulma的轻量级工具是一个很好的选择。虽然它的组件比列表中的其他库要少,但这也是它的优点之一。保持Buefy轻量级,并且只保留最重要的组件,如下拉菜单、表单等,对于只想为几个关键组件使用库的开发人员来说,这是最好的选择之一。

clipboard.png

5. Quasar Framework (⭐️ 13.8k)

网站:http://www.quasarchs.com/
github:https://github.com/buefy/buefy

Quasar允许开发人员编写一次代码,并且使用相同的代码库同时部署为网站、PWA、Mobile App和Electron App。使用最先进的CLI设计应用程序,并提供精心编写,速度非常快的Quasar Web组件。

当使用Quasar时,你不需要像HammerjsMomentjsBootstrap这样的额外重型库。它拥有这些功能,而且体积很小!

clipboard.png

6. VueStrap (⭐️ 4.8k)

网站:http://yuche.github.io/vue-st...
github:https://github.com/yuche/vue-...

接下来的两个库都是使用VueJS实现类似Bootstrap组件的两种方式。 VueStrap接受Bootstrap中发现的所有元素,并具有可以轻松导入和呈现的等效Vue组件。

clipboard.png

7. Bootstrap Vue (⭐️ 10.9k)

网站:https://bootstrap-vue.js.org/
github:https://github.com/bootstrap-...

最近,Vue.js 生态系统发布了一个新的软件包。它是流行的 Bootstrap 框架与 Vue.js 的集成。这个包称为 BootstrapVue。它允许我们使用与 Bootstrap(v4)集成的自定义组件。
它还支持自定义 Bootstrap 组件、网格系统,还支持 Vue.js 指令。

clipboard.png

8. UIV (⭐️ 813)

网站:https://uiv.wxsm.space/
github:https://github.com/wxsms/uiv

BootstrapVue之间的又一个集成。 尽管我对UIV的了解还不足以直接将其与前两个库进行比较,但需要注意一些关键事项。 UIV使用Bootstrap CSS作为依赖项,从而使库本身轻量级化,并且根据其文档,它支持SSR(服务器端渲染)。

clipboard.png

9. Mint UI (⭐️ 15.5k)

网站:http://mint-ui.github.io/#!/z...
github:https://github.com/ElemeFE/mi...

Mint UI 包含丰富的 CSS 和 JS 组件,能够满足日常的移动端开发需要。通过它,可以快速构建出风格统一的页面,提升开发效率。真正意义上的按需加载组件。可以只加载声明过的组件及其样式文件,无需再纠结文件体积过大。ue.js 高效的组件化方案,Mint UI 做到了轻量化。即使全部引入,压缩后的文件体积也仅有 ~30kb (JS + CSS) gzip。

clipboard.png

10. Vuecidity

网站:https://vuecidity.wemakesites...

Vuecidity是基于Material Design的VueJS组件库。 通过表单元素,指令,布局选项和UI组件,Vuecidity几乎涵盖了所有基础。 通过支持 Material Designs 主题,Vuecidity 作为 Vue版本,非常适合那些对Material Design感到满意的开发人员。

clipboard.png

11. iView (⭐️ 23.1)

网站:https://www.iviewui.com/
github:https://github.com/iview/iview

iView 是一套基于 Vue.js 的开源 UI 组件库,主要服务于 PC 界面的中后台产品,其特性包括:高质量、功能丰富友好的 API ,自由灵活地使用空间,细致、漂亮的 UI,事无巨细的文档和可自定义主题。

clipboard.png

12. DeepReader (⭐️ 87)

github:https://github.com/deep-philo...

这个模块化框架可能没有其他库那么广泛的用例,但是它的功能太酷了,所以还是要介绍五。 DeepReader构建了在线阅读环境,并带有可以添加注释,小部件和不同工具的不同组件,以创建完整的学习/阅读环境。

clipboard.png

13. KeenUI (⭐️ 3.9k )

网站:https://josephuspaye.github.i...
github:https://github.com/JosephusPa...

KeenUI 使用 Vue 编写的基本轻量级 UI 组件库,并受 Material Design 的启发,虽然受 Material UI 规范的启发,但 Keen-UI 并不是真正的 Material UI 库。它不是一个CSS框架,不包括网格系统或排版风格,但有需要Javascript 的组件。

clipboard.png

14. AT UI (⭐️ 1795)

网站:https://aotu.io/notes/2017/08...
github:https://github.com/aliqin/atui

AT UI专为前端Web应用程序而构建。 具备使用CSS预处理程序的能力,它几乎适用于几乎所有开发团队。 就我个人而言,我真的很喜欢AT UI随附的最小样式和字体选择,而且我认为添加到任何项目中都非常直观且容易。 与其他库相比,它的内置图标库(Feather)也是一个巨大的好处。

clipboard.png

15. Muse-UI (⭐️ 7084)

网站:https://muse-ui.org/#/en-US
github:https://github.com/museui/mus...

Muse UI 基于 Vue2.0 开发,Vue2.0是当下最快的前端框架之一,小巧,api友好,可用于开发的复杂单页应用。Muse UI是一个受Material Design启发的库,不仅包含我们所期望的所有核心Web组件,而且还包括一些移动组件,例如对话框,滑块和响应式刷新按钮。

clipboard.png

16 Vue Blu (⭐️ 1.5k)

网站:https://chenz24.github.io/vue...
github:https://github.com/chenz24/vu...

像Buefy一样,Vue Blu是Vue和Bulma之间的集成。 它非常有用且轻巧,并且与NPM,Webpack和Babel堆栈具有很好的集成。 它具有强大的布尔玛集成度,并充分利用了Flexbox功能。 我最喜欢的组件之一是时间线,可以轻松创建漂亮的时间线,非常适合进行项目更新。

clipboard.png

好了,今天就分享到这里,你最喜欢的Vue.js库是什么?欢迎留言讨论。


代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug

原文:
https://levelup.gitconnected....

作者:Matt Maribojoc 译者:前端小智 来源:medium

交流

文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub https://github.com/qq449245884/xiaozhi 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。

查看原文

Alex_Max 收藏了文章 · 2020-06-22

js 事件循环中的job queue和message queue

本文讨论的事件循环均是基于浏览器环境上的,类似nodejs环境下的事件循环与此并不相同。

读者首先要对js单线程事件循环机制以及Promise有基本理解;如果这两个概念不是很清楚,建议先阅读下面两篇文章:

THE JAVASCRIPT EVENT LOOP ; Promise 对象

本文是基于THE JAVASCRIPT EVENT LOOP ,并对其内容的延伸,所以下面提到的概念都按这篇文章的来。首先我会总结一下 THE JAVASCRIPT EVENT LOOP 。OK,让我们开始吧。

1,消息队列(message queue)

      我们知道js单线程的实现方式会把异步任务(setTimeout回调函数,事件监听回调函数等)放在一个消息队列中;当主任务队列任务为空时会去message queue查询是否有等待执行的任务,如果有则执行。

 例1:

var task_in_message_queue = () => {console.log('task in message queue')}
setTimeout(task_in_message_queue,0);
console.log('main task');

//result:
//main task
//task in message queue

setTimeout函数将task_in_message_queue函数添加到message queue队列中。等到主任务队列执行完成时(此时已打印main task),执行存在message queue队列中的task_in_message_queue函数

2,任务队列(job queue)

        ES6中引入了任务队列来执行Promise的回调函数。同message queue一样,job queue中的任务也是在主任务队列为空时才开始执行。

例2:

var promise = new Promise((resolve,reject) => {
    resolve('task in job queue');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise.then(resolve_callback);
console.log('main task');

//result:
//main task
//task in job queue

/**
这里有一个有趣的现象

在chrome中打印出的结果是
main task
task in job queue
undefined  //主任务的函数返回值

在firefox中的结果是
main task 
undefined  //主任务的函数返回值
task in job queue

感觉v8的实现是把job queue整合到了主任务队列尾部
**/

promise.then 将promise fulfilled状态下的回调函数resolve_callback添加到job queue中。等到主任务队列执行完成时(此时已打印main task),执行存在job queue队列中的resolve_callback函数

这里有一点需要注意的是promise构造函数会在主任务中立即执行,例子如下:

var promise = new Promise((resolve,reject) => {
    resolve('task in job queue');
    console.log('the promise construction executed');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise.then(resolve_callback);
console.log('main task');


//result:
//the promise construction executed
//main task
//task in job queue

3,任务队列(job queue)VS 消息队列(message queue)

       通过上面的例子我们知道主任务队列优先级是最高的,那么job queue和message queue哪个优先级更高呢?答案是job queue,js会将job queue中的任务完全执行完之后再执行message queue中的任务。例子如下:

var message_task = () => {console.log('message task');}
setTimeout(message_task,0);
var promise1 = new Promise((resolve,reject) => {
    resolve('promise 1 resolved');
});
var promise2 = new Promise((resolve,reject) => {
    resolve('promise 2 resolved');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
promise1.then(resolve_callback);
promise2.then(resolve_callback);
console.log('main task');


//result:
//main task
//promise 1 resolved
//promise 2 resolved
//message task

/**
这里chrome和firefox返回undefined的位置同上面的例子一样,也是不同的。有兴趣的话可以试试看一下。
**/

4,每次执行message queue中的任务前都会检查job queue吗?

        现在我们知道job queue的优先级高于message queue。那么每次执行message queue中任务前会检查job queue吗?我的意思是如果当前job queue为空,message queue中有多个任务(假设有m_task1和m_task2)。js开始执行message queue中的任务,在执行完m_task1时插入了一个j_task1在job queue中。那么接下来是先执行m_task2呢还是j_task1呢?如果先执行了m_task2的话,就说明js一旦开始执行message queue中的任务就会将所有message queue中任务执行完再检查其它任务队列。如果先执行j_task1的话,那么说明再执行每个message queue中的任务前都会先检查其它任务队列,先执行优先级高的任务队列中的任务。为此我们用如下代码来检验:

var promise_task = new Promise((resolve,reject) => {
    resolve('j_task1');
});
var resolve_callback = (resolve_message) => {console.log(resolve_message)}
var message_task1 = () => {
    promise_task.then(resolve_callback);
    console.log('m_task1');
}
var message_task2 = () => {console.log('m_task2');}
setTimeout(message_task1,0);
setTimeout(message_task2,0);


//result:
//m_task1
//j_task1
//m_task2

事实证明js在每次执行message queue中的任务前都会检查其它任务队列(至少会检查job queue),根据队列优先级决定先执行哪个队列中的任务。

5,主任务队列呢?

        上面我们了解了job queue和message queue中任务的执行顺序,简而言之:在每次一个任务结束时,js都会根据任务队列的优先级判断下一个执行任务是哪个。如果job queue中有任务则执行job queue中的第一个任务,否则执行message queue中的第一个任务。那么主任务队列是不是也一样呢?(逻辑上应该是一样的,否则job queue或者message queue中的任务可以递归创建新任务,这样就永远无法回到主任务队列了)。

        即每次选择执行任务前(或者每次任务结束后),js会根据主任务队列,job queue,message queue的优先级来挑选将要执行下一个任务是哪个。

        为此我们声明一个promise和一个message_task函数。在这个promise的回调函数中使用setTimeout创建一个message_task的message queue任务,同时在message_task中调用promise.then 函数创建一个job queue 任务。这样两个任务会循环创建并循环执行。运行后我们会在console中看到两个任务循环打印,这是我们在console中键入alert('stop')命令。如果页面显示了alert,console停止了打印就说明主任务队列的行为方式和job queue,message queue是一样的。否则的话,在这种情况下我们将永远无法回到主任务队列。验证代码如下:

var promise_task = new Promise((resolve,reject) => {
    resolve('j_task');
});
var resolve_callback = (resolve_message) => {
    setTimeout(message_task,0);
    console.log(resolve_message);
}
var message_task = () => {
    promise_task.then(resolve_callback);
    console.log('m_task');
}

promise_task.then(resolve_callback);


//result:
//console会循环打印 j_task 和 m_task
//这是在console中键入alert('stop')命令,观察是否弹出alert框,console中打印是否终止

希望大家自行求证一下,当然验证完毕后记得刷新页面,不然可能就崩了。另:最好在chrome下验证,firefox有些卡顿。

总结

        js事件循环规律可大致总结为如下:

        1,js中有三个任务队列:主任务队列,job queue,message queue;

        2,它们的优先级是:主任务队列 > job queue > message queue;

        3,每当要执行下一个任务前(或者一个任务完成后),js会根据优先级询问各个任务队列是否为空,一旦遇到非空任务队列时则取其第一个任务执行。

查看原文

Alex_Max 赞了文章 · 2020-06-01

Flutter样式和布局控件简析(一)

开始

搞前端的同学可能都习惯了CSS局部的思维,过去也出现过一些跟布局或者样式相关的标签,例如:big, center, font, s, strike, tt, u;但是目前也被CSS所代替,已经不推荐使用。但是在Flutter里面,是没有CSS这样一个概念的,布局和样式都可能会是一个组件或者是组件里面的属性所定义和实现的,对于习惯写样式的前端同学可能需要适应一下。

个人思考

现在可能要想一下,Flutter为啥没有像浏览器一样抽离出CSS?
我们知道在浏览器里面JS,CSS,HTML各司其职:行为,表现和结构,已经深入人心,也被很多人所推崇。但是Flutter好像反其道而行之,样式糅合在结构里面,这样究竟有啥意思尼?
首先应该是一个性能的考虑,浏览器解析CSS其实也是一个性能消耗点,没有CSS解析自然也可以加快页面的显示。
其次再讨论一下CSS,CSS确实非常适合描述样式和布局,但是也有很明显的缺点:作用域全局性,代码冗余,代码难以重用,难以模块化等;我们修修补补,又创造了less,sass等工具帮助我们去解决问题,但是自身的缺陷依然会存在,甚至有点钻牛角尖,因为存在了CSS,所以只能改进CSS。
而在Flutter,没有了CSS,以上的问题自然荡然无存,那么描述样式会不会变得很麻烦?大道行之,我们的前辈们早就在代码上总结出很多设计模式或者技术去解决代码重用,代码冗余,模块化的问题,为什么我们不去用已经存在很久而且行之有效的技术去解决问题尼。自然把样式糅合进结构会增加信息量,对我们阅读代码可能会是一个小小的挑战,但是应该也会很快适应下来的,我相信。
我们很多时候都在创造新的工具的解决问题,其实也有可能创造出新的问题,有时候回归根本,不一定是一件坏事。

各种各样的控件

Directionality

主要控制文字方向

 Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Text('我是一段文本')
    );
  }

clipboard.png

加入控件后

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Directionality(
        textDirection: TextDirection.rtl,
        child: new Text('我是一段文本')
      )
    );
  }

clipboard.png

DefaultTextStyle

跟文本相关的还有一个DefaultTextStyle控件,提供了更多的控制选项:textAlign,softWrap,style和maxLines等,都是控制整体:换行,文字居中和多行省略等,相对style提供都是文字自身样式相关:字重,字体大小等

const TextStyle({
    this.inherit: true,
    this.color,
    this.fontSize,
    this.fontWeight,
    this.fontStyle,
    this.letterSpacing,
    this.wordSpacing,
    this.textBaseline,
    this.height,
    this.decoration,
    this.decorationColor,
    this.decorationStyle,
    this.debugLabel,
    String fontFamily,
    String package,
  })

演示一下效果:

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Directionality(
        textDirection: TextDirection.ltr,
        child: new DefaultTextStyle(
          style: new TextStyle(
            fontSize: 14.0,
            color: Colors.blue,
            decoration: TextDecoration.underline
          ),
          maxLines: 2,
          softWrap: true,
          overflow: TextOverflow.ellipsis,
          child: new Text('我是一段超长的文本啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦'
            '啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦'
            '啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦'
            '啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦')
        )
      )
    );
  }

clipboard.png

其实Text控件就已经带上这些属性:

const Text(this.data, {
    Key key,
    this.style,
    this.textAlign,
    this.textDirection,
    this.softWrap,
    this.overflow,
    this.textScaleFactor,
    this.maxLines,
  })

为什么又要独立出这些控件专门管理呢,我们知道CSS属性里面有些属性时继承父元素的,例如:字体大小,颜色等;这样的话,我们很容易统一一个模块里面的样式,并不需要每个元素都要去设置一遍,这里的这些控件也是起到这样的功能,其实除了些字体样式还有很多地方会有这种继承关系,例如:主题颜色,语言文字等等。所以后面Text控件很容易从控件树上找到这些父控件,获取它们设置的属性,就这样就可以把父控件的样式继承下来。
怎么做到的呢,无论Directionality还是DefaultTextStyle都是InheritedWidget的子类,InheritedWidget实现了一个发布/订阅的模式,当子控件调用inheritFromWidgetOfExactType方法获取父控件时,同时也把自己加入到InheritedWidget的订阅者列表里面,所以当InheritedWidget属性改变的时候,就会调起子组件didChangeDependencies方法去通知子组件。

CustomPaint

这个控件感觉必须得介绍一下,因为在前端我们有一个canvas元素,可以提供给我们直接去绘制元素,给了我们很大的灵活性,那么Flutter中对应的应该就是这个控件了。
如何使用:
先继承CustomPainter

class CustomPainterSample extends CustomPainter {

  double progress;

  CustomPainterSample({this.progress: 0.0});

  @override
  void paint(Canvas canvas, Size size) {
    Paint p = new Paint();
    p.color = Colors.green;
    p.isAntiAlias = true;
    p.style = PaintingStyle.fill;
    canvas.drawCircle(size.center(const Offset(0.0, 0.0)), size.width / 2 * progress, p);
  }

  @override
  bool shouldRepaint(CustomPainter oldDelegate) {
    return true;
  }

}

这里我画了一个绿色的圆,然后把这个CustomPainterSample传到CustomPaint控件。

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new CustomPaint(
        painter: new CustomPainterSample(progress: this.progress),
      )
    );
  }

clipboard.png

当然了,既然可以随便画点东西,做点动画也是妥妥的,好再加个放大的动画,完整代码:

class SquareFragmentState extends State<SquareFragment> with TickerProviderStateMixin {

  double progress = 0.0;

  @override
  void initState() {
    AnimationController ac = new AnimationController(
       vsync: this, 
       duration: const Duration(milliseconds: 10000)
    );
    ac.addListener(() {
      this.setState(() {
        this.progress = ac.value;
      });
    });
    ac.forward();
  }

  @override
  Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new CustomPaint(
        painter: new CustomPainterSample(progress: this.progress),
      )
    );
  }
}

这里mixin了TickerProviderStateMixin,里面有一个createTicker方法,主要是监听每一帧生成然后回调,主要是由SchedulerBinding.instance.scheduleFrameCallback方法所驱动的。

ClipRRect

剪切元素的边界,这里类似CSS的border-radius属性;

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Align(
        alignment: Alignment.center,
        child: new ClipRRect(
          borderRadius: const BorderRadius.all(const Radius.circular(30.0)),
          child: new Container(
            width: 180.0,
            height: 180.0,
            color: Colors.red,
          ),
        ),
      ),
    );
  }

效果:
图片描述

把radius值调到90,变成了圆形:
图片描述

类似的可以剪切元素的还有ClipOval,ClipPath,这里就不深入介绍了。

PhysicalModel

先看效果:

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Align(
        alignment: Alignment.center,
        child: new PhysicalModel(
          color: Colors.black,
          elevation: 6.0,
          child: new Container(
            width: 180.0,
            height: 180.0,
            color: Colors.red,
          ),
        ),
      ),
    );
  }

图片描述

可以看到红色方块底下有一个阴影,让红色方块有一种悬浮的感觉,有material design的风格。

Transform

类似于CSS的transform属性,可以提供沿着X,Y或者Z轴旋转,位移拉伸等效果。

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      child: new Align(
        alignment: Alignment.center,
        child: new Transform(
          transform: new Matrix4.rotationZ(PI / 2),
          child: new Container(
            color: Colors.black,
            child: new Text('垂直文字', style: const TextStyle(color: Colors.red),)
          )
        ),
      ),
    );
  }

图片描述

得注意一下,Transform控件中的transformHitTests属性,如果我们沿着X轴位移一个按钮,一般来说,我们照样可以直接点击位移之后的按钮,因为transformHitTests为true的时候,在hitTest会判断点击落点是否在transfrom所做的操作(旋转,拉伸或者位移等)后的区域里面,但是如果为false,此时点击按钮原来的区域仍然会触发点击事件,但是直接点击就不行了。

FractionalTranslation

可以提供位移,但是并没有Tranform控件提供那么多变换,仅仅是上下左右的位移,而且位移的基准是以child的大小进行的。

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: new FractionalTranslation(
        translation: const Offset(1.0, 0.0),
        child: new Container(
          width: 100.0,
          height: 100.0,
          color: Colors.red,
        ),
      )
    );
  }

效果:
图片描述

红色方块往右边移动了一个身位,就跟CSS中transfrom: translate(100%, 0)效果一样的。

RotatedBox

旋转盒子,可以使用quarterTurns属性控制旋转,每次旋转quarterTurns * 90度。

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: new RotatedBox(
        quarterTurns: -1,
        child: new Container(
          width: 100.0,
          height: 100.0,
          color: Colors.red,
          child: new Text('我倒转了'),
        ),
      )
    );
  }

图片描述

Padding

在前端每个元素都基本会有border, margin, padding,但是在Flutter里面可能不得不吐槽连padding都要用个控件,未免太过于麻烦。对于此框架的开发者们也有自己一套看法,在Flutter里面组合简单的控件去实现复杂的控件,而不是通过继承去实现可以说是Flutter的主要设计思想,所以你会发现尽管Container控件提供了padding的参数,但其实它也背后也是通过创建Padding控件来实现效果的。

FittedBox

在CSS中有background-position和background-size两个属性控制背景图如何平铺,例如:如果背景图比元素尺寸大或者小的时候,是否要进行拉伸,如果要拉伸,是拉伸图片宽度还是拉伸图片高度来适应等等。
而FittedBox所做的事情也是差不多,它有两个很重要的参数:aligment 和 fit。
fit可取值:

  • BoxFit.fill
  • BoxFit.contain
  • BoxFit.cover
  • BoxFit.fitWidth
  • BoxFit.fitHeight

基本这个跟CSS的background-size取值都一样的。
而aligment则是控制,当子元素大小没有完全占满父元素的时候,如何定位,是居中还是靠左靠右。

虽然拿background-size来做对比,但是background-size只是控制背景图片,而FittedBox几乎可以对任何元素起作用,因为它是通过Transform放大缩小子元素来达到刚才所说的效果。

Widget build(BuildContext context) {
    return new Container(
      color: Colors.white,
      alignment: Alignment.center,
      child: new Container(
        width: 200.0,
        height: 100.0,
        color: Colors.black
          child: new FittedBox(
            fit: BoxFit.fitHeight,
            alignment: Alignment.bottomRight,
            child: new Container(
              color: Colors.red,
              width: 300.0,
              height: 240.0,
              alignment: Alignment.center,
              child: new Text('AAA'),
            ),
          )
      )
    );
  }

效果:
图片描述

这里红盒子大小是比黑盒子大的,但是fit为BoxFit.fitHeight就会通过拉伸高度来适应黑盒子,如果把fit属性改成BoxFit.fitWidth,效果就是这样的:
图片描述

可以看到字体是被直接缩小了。

SizedBox & ConstrainedBox

为什么把两个控件一起讲呢?因为它们都依赖了相同的RenderObject:RenderConstrainedBox,而RenderConstrainedBox只有一个参数:additionalConstraints。
而这个参数在performLayout中:

void performLayout() {
    if (child != null) {
      child.layout(_additionalConstraints.enforce(constraints), parentUsesSize: true);
      size = child.size;
    } else {
      size = _additionalConstraints.enforce(constraints).constrain(Size.zero);
    }
  }

而BoxConstraints.enforce方法:

BoxConstraints enforce(BoxConstraints constraints) {
    return new BoxConstraints(
      minWidth: minWidth.clamp(constraints.minWidth, constraints.maxWidth),
      maxWidth: maxWidth.clamp(constraints.minWidth, constraints.maxWidth),
      minHeight: minHeight.clamp(constraints.minHeight, constraints.maxHeight),
      maxHeight: maxHeight.clamp(constraints.minHeight, constraints.maxHeight)
    );
  }

可见additionalConstraints是在原基础constraints增加了自己的约束,但是并不会打破原来的约束条件。

FractionallySizedBox

主要有三个参数:aligment, widthFactor 和 heightFactor。
aligment参数控制child的定位;widthFactor 和 heightFactor 控制child的约束,如果widthFactor或者heightFactor不为null,会产生一个新的BoxConstraints:它的minWidth 和 maxWidth为原BoxConstraint.maxWidth widthFactor;minHeight 和 maxHeight为原BoxConstraint.maxHeight heightFactor。
代码:

Widget build(BuildContext context) {
    return new Align(
      alignment: Alignment.center,
      child: new Container(
        color: Colors.green,
        width: 300.0,
        height: 300.0,
        child: new FractionallySizedBox(
          heightFactor: .5,
          widthFactor: .5,
          alignment: Alignment.topLeft,
          child: new Container(
            color: Colors.red,
          )
        )
      )
    );
  }

效果:
图片描述

可以看到当widthFactor和heigthFractor时,红色盒子宽高都为绿色的一半。

LimitedBox

看名称也知道跟控制尺寸有关了,这个控件主要有两个参数:maxWidth和maxHeight,当constraints是unbounded的时候,也就是maxWidth和maxHeight都是infinite的时候,会用maxWidth和maxHeight替换原来的maxWidth和maxHeight,所以如果contraints是bounded的时候并不会起作用。
关键代码:

BoxConstraints _limitConstraints(BoxConstraints constraints) {
    return new BoxConstraints(
      minWidth: constraints.minWidth,
      maxWidth: constraints.hasBoundedWidth ? constraints.maxWidth : constraints.constrainWidth(maxWidth),
      minHeight: constraints.minHeight,
      maxHeight: constraints.hasBoundedHeight ? constraints.maxHeight : constraints.constrainHeight(maxHeight)
    );
  }
@override
  void performLayout() {
    if (child != null) {
      child.layout(_limitConstraints(constraints), parentUsesSize: true);
      size = constraints.constrain(child.size);
    } else {
      size = _limitConstraints(constraints).constrain(Size.zero);
    }
  }

对比ConstrainedBox,明显使用范围就没有那么广了。

OverflowBox

从前面的几个控件:SizedBox,ConstrainedBox和LimitedBox分析知道,我们似乎没有办法打破由parent传递下来的约束条件,但是我们总会有一些情况是子组件的尺寸大于父组件的情况,那么怎么解决的尼?来,就看这里的OverflowBox控件,这个控件提供了几个参数:minWidth,minHeight,maxWidth,maxHeight 和 aligment;先看代码:

BoxConstraints _getInnerConstraints(BoxConstraints constraints) {
    return new BoxConstraints(
      minWidth: _minWidth ?? constraints.minWidth,
      maxWidth: _maxWidth ?? constraints.maxWidth,
      minHeight: _minHeight ?? constraints.minHeight,
      maxHeight: _maxHeight ?? constraints.maxHeight
    );
  }

void performLayout() {
    if (child != null) {
      child.layout(_getInnerConstraints(constraints), parentUsesSize: true);
      alignChild();
    }
  }

这里可以看到直接使用我们传入的参数替换了原本的minxWidth,maxWidth等,所以底下的组件可以根据新的约束条件来布局。
做一下demo:

Widget build(BuildContext context) {
    return new Align(
      alignment: Alignment.center,
      child: new Container(
        color: Colors.green,
        alignment: Alignment.center,
        width: 300.0,
        height: 300.0,
        child: new OverflowBox(
          maxWidth: double.INFINITY,
          maxHeight: double.INFINITY,
          child: new Container(
            height: 600.0,
            width: 200.0,
            color: Colors.red,
          ),
        )
      )
    );
  }

效果:
图片描述

如果没有OverflowBox控件,红色的盒子是不可能超过绿色盒子的;而aligment可以控制红色盒子在绿色盒子里面的定位,现在是居中显示的。

SizedOverflowBox

刚才OverflowBox是因为我们修改了约束条件所以child布局大小确实被改变了,所以会发生溢出,而SizedOverflowBox这个控件并不会改变约束条件,但是它还是可能会发生溢出,为什么尼?因为SizedOverflowBox可以让控件看上去“变小一点”,这怎样做到的尼?这个控件有一个参数:size,这个参数就是让我们决定这个控件看上去应该多大。
关键代码在RenderSizedOverflowBox类中:

 @override
  double computeMinIntrinsicWidth(double height) {
    return _requestedSize.width;
  }

  @override
  double computeMaxIntrinsicWidth(double height) {
    return _requestedSize.width;
  }

  @override
  double computeMinIntrinsicHeight(double width) {
    return _requestedSize.height;
  }

  @override
  double computeMaxIntrinsicHeight(double width) {
    return _requestedSize.height;
  }
void performLayout() {
    size = constraints.constrain(_requestedSize);
    if (child != null) {
      child.layout(constraints);
      alignChild();
    }
  }

示例代码:

Widget build(BuildContext context) {
    return new Align(
      alignment: Alignment.center,
      child: new Container(
        color: Colors.green,
        alignment: Alignment.center,
        width: 300.0,
        height: 300.0,
        child: new SizedOverflowBox(
          size: new Size(200.0, 300.0),
          child: new Container(
            color: Colors.red
          )
        )
      )
    );
  }

截图:
图片描述

Offstage

在CSS有一个属性visibility,当设置为hidden时,元素是存在但是不会绘制出来;在Flutter中Offstage也可以做到这种效果。
在RenderOffstage类中:

class RenderOffstage extends RenderProxyBox {
    ...
    @override
  void performLayout() {
    if (offstage) {
      child?.layout(constraints);
    } else {
      super.performLayout();
    }
  }

  @override
  bool hitTest(HitTestResult result, { Offset position }) {
    return !offstage && super.hitTest(result, position: position);
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    if (offstage)
      return;
    super.paint(context, offset);
  }
    ...
}

可见当offstage为true时,布局还是会继续进行的,但是paint方法里面会直接返回,hitTest方法也会直接跳过,也就是不能响应任何手势。

AspectRatio

这个控件可以用来让子控件大小维持在一个固定宽高比,例如:16:9。
直接看布局算法:

Size _applyAspectRatio(BoxConstraints constraints) {
    if (constraints.isTight)
      return constraints.smallest;

    double width = constraints.maxWidth;
    double height;

    // We default to picking the height based on the width, but if the width
    // would be infinite, that's not sensible so we try to infer the height
    // from the width.
    if (width.isFinite) {
      height = width / _aspectRatio;
    } else {
      height = constraints.maxHeight;
      width = height * _aspectRatio;
    }

    // Similar to RenderImage, we iteratively attempt to fit within the given
    // constraints while maintaining the given aspect ratio. The order of
    // applying the constraints is also biased towards inferring the height
    // from the width.

    if (width > constraints.maxWidth) {
      width = constraints.maxWidth;
      height = width / _aspectRatio;
    }

    if (height > constraints.maxHeight) {
      height = constraints.maxHeight;
      width = height * _aspectRatio;
    }

    if (width < constraints.minWidth) {
      width = constraints.minWidth;
      height = width / _aspectRatio;
    }

    if (height < constraints.minHeight) {
      height = constraints.minHeight;
      width = height * _aspectRatio;
    }

    return constraints.constrain(new Size(width, height));
  }

简单分析一下:
如果constraints是tight,那么这个控件并不会起啥作用,所以这个控件一般需要Align控件包裹一下。
如果宽度不是Inifinte,它首先会选择最大宽度,否则根据maxHeight来反推宽度。
万一高度超出约束条件,它就会反过来,选择最大的高度反推出宽度,那么万一宽度小于最小宽度,它又会根据最小宽度计算高度等等。
当然最后还是会根据约束条件来规范最终的Size,所以可能出来效果是跟我们预设的宽高比不一致,但是这种情况应该很少。
示例代码:

Widget build(BuildContext context) {
    return new Align(
      alignment: Alignment.center,
      child: new Container(
        color: Colors.green,
        alignment: Alignment.center,
        width: 300.0,
        height: 300.0,
        child: new AspectRatio(
          aspectRatio: 2.0,
          child: new Container(
            color: Colors.red,
          ),
        )
      )
    );
  }

截图:
图片描述

IntrinsicWidth & IntrinsicHeight

Sizes its child's width to the child's maximum intrinsic width.

说实在这个控件看了半天没想出用于哪些场景,搜了一下代码,基本都用在一些浮窗上。布局过程是调用getMaxIntrinsicWidth方法递归询问子控件最大的intrinsicWidth,因为这个方法需要递归下去,如果每个控件都调用比较耗性能,当获取到intrinsicWidth,就会使用这个值作为约束条件(当然也受到原始的约束条件约束),然后传递给child,所以正如上面的话所说,但是还是想不到哪些场景会需要。

Baseline

图片描述

正如图上,基线可以影响着文字水平排布;如果两段文字的基线不一样,两段文字的可能会出现一上一下,并不是在同一水平线上排布,就像这样:
图片描述

这是两个Text控件,文字大小分别是12dp和32dp,所以他们的基线位置是不一样的,所以这样的排布并不是我们想要的,所以我们可以使用Baseline控件让他们都在一样的基线上,修改后:
图片描述

这才是我们常见的,代码如下:

Widget build(BuildContext context) {
    return new Wrap(
      children: <Widget>[
        new Baseline(
          baseline: 30.0,
          baselineType: TextBaseline.alphabetic,
          child:
          new Text(
            'AAAAA',
            style: new TextStyle(
              fontSize: 12.0,
              textBaseline: TextBaseline.alphabetic,
            ),
          )
        ),
        new Baseline(
          baseline: 30.0,
          baselineType: TextBaseline.alphabetic,
          child:
          new Text(
            'BBB',
            style: new TextStyle(
              fontSize: 32.0,
              textBaseline: TextBaseline.alphabetic,
            ),
          ),
        )
      ],
    );

把基线的位置都定义为30,两段文字都会在来30的水平线上排布,就可以看到现在整齐的效果。

查看原文

赞 27 收藏 27 评论 1

认证与成就

  • 获得 14 次点赞
  • 获得 4 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 4 枚铜徽章

擅长技能
编辑

(゚∀゚ )
暂时没有

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2014-10-15
个人主页被 376 人浏览