. LRU缓存机制(JS实现)

. LRU缓存机制(JS实现)

1 题目
运用你所掌握的数据结构,设计和实现一个 LRU (*近*少使用) 缓存机制。它应该支持以下操作: 获取数据 get 和 写入数据 put 。
获取数据 get(key) – 如果关键字 (key) 存在于缓存中,则获取关键字的值(总是正数),否则返回 -1。
写入数据 put(key, value) – 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字/值」。当缓存容量达到上限时,它应该在写入新数据之前删除*久未使用的数据值,从而为新的数据值留出空间。
进阶:
你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例:
LRUCache cache = new LRUCache( 2 /* 缓存容量 */ );
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // 返回 1
cache.put(3, 3); // 该操作会使得关键字 2 作废
cache.get(2); // 返回 -1 (未找到)
cache.put(4, 4); // 该操作会使得关键字 1 作废
cache.get(1); // 返回 -1 (未找到)
cache.get(3); // 返回 3
cache.get(4); // 返回 4

2 思路
这道题的主要思路链表和哈希表来实现查找和插入删除的O(1)的时间复杂度

3代码
/**
* @param {number} capacity
*/
var LRUCache = function(capacity) {
this.capacity = capacity;
this.listHead = null;
this.listTail = null;
this.map = {};
this.len = 0;
};

/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
let node = this.map[key];
if (typeof node === ‘object’) {
if (this.listTail !== node) {
if (node.prev) {
node.prev.next = node.next;
node.next.prev = node.prev;
} else {
this.listHead = node.next;
this.listHead.prev = null;
}
this.listTail.next = node;
node.prev = this.listTail;
node.next = null;
this.listTail = node;
}

return node.val;
} else {
return -1;
}
};

/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
if (this.get(key) !== -1) {
this.map[key].val = value;
return;
}

let newNode = new Node(key, value);
this.map[key] = newNode;
if (this.len === this.capacity) {
if (!this.listHead) return;
delete this.map[this.listHead.key];
this.listHead = this.listHead.next;
if (this.listHead) this.listHead.prev = null;
this.listTail.next = newNode;
newNode.prev = this.listTail;
this.listTail = newNode;
} else {
if (!this.listHead) {
this.listHead = this.listTail = newNode;
} else {
this.listTail.next = newNode;
newNode.prev = this.listTail;
this.listTail = newNode;
}
this.len++;
}
};

function Node(key, val) {
this.key = key;
this.val = val;
this.next = null;
this.prev = null;
}

排序链表(JS实现)

排序链表(JS实现)

1 题目
在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5

2 思路
快速排序的链表方式,注意获取中点时,链表被断开成等分的两半,返回的是各部分的首节点

3代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var sortList = function(head) {

if (!head || !head.next) return head;

return sort(head);

function sort(linkHead) {
if (!linkHead || !linkHead.next) return linkHead;
let [head1, head2] = getMid(linkHead);
head1 = sort(head1);
head2 = sort(head2);
return merge(head1, head2);
}

function merge(head1, head2) {
if (!head1 || !head2) return head1 || head2;

let linkHead = null;
let list = null;
while(head1 && head2) {
if (head1.val < head2.val) {
if (!linkHead) {
linkHead = list = head1;
} else {
list.next = head1;
list = list.next;
}
head1 = head1.next;
} else {
if (!linkHead) {
linkHead = list = head2;
} else {
list.next = head2;
list = list.next;
}
head2 = head2.next;
}
}

if (head1) {
list.next = head1;
}

if (head2) {
list.next = head2;
}

return linkHead;
}

function getMid(linkHead) {
if (!linkHead) return [null, null];
let p = q = linkHead;
let pre;
while(p && q) {
pre = p;
p = p.next;
q = q.next ? q.next.next : q.next;
}
let head1 = pre && pre.next;
if (pre) pre.next = null;
return [linkHead, head1];
}
};

对链表进行插入排序(JS实现)

1 题目
对链表进行插入排序。
插入排序的动画演示如上。从*个元素开始,该链表可以被认为已经部分排序(用黑色表示)。
每次迭代时,从输入数据中移除一个元素(用红色表示),并原地将其插入到已排好序的链表中。
插入排序算法:
插入排序是迭代的,每次只移动一个元素,直到所有元素可以形成一个有序的输出列表。
每次迭代中,插入排序只从输入数据中移除一个待排序的元素,找到它在序列中适当的位置,并将其插入。
重复直到所有输入数据插入完为止。
示例 1:
输入: 4->2->1->3
输出: 1->2->3->4
示例 2:
输入: -1->5->3->4->0
输出: -1->0->3->4->5

2 思路
这道题的用两个指针sortedLinkHead,sortedLinkTail指向已排序后链表的*小和*大值,当下个待排序的值位于两者之间时,才遍历已排序的列表来执行插入操作

3代码
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/
var insertionSortList = function(head) {
if (!head || !head.next) return head;

let sortedLinkHead = head;
let sortedLinkTail = head;
let nextEle = head.next;
sortedLinkTail.next = null;

while(nextEle) {
let currentEle = nextEle;
nextEle = nextEle.next;
if (currentEle.val <= sortedLinkHead.val) {
currentEle.next = sortedLinkHead;
sortedLinkHead = currentEle;
} else if (currentEle.val >= sortedLinkTail.val) {
sortedLinkTail.next = currentEle;
currentEle.next = null;
sortedLinkTail = currentEle;
} else {
let p = sortedLinkHead;
while(currentEle.val > p.next.val) {
p = p.next;
}
currentEle.next = p.next;
p.next = currentEle;
}
}

return sortedLinkHead;
};

逆波兰表达式求值(JS实现)

逆波兰表达式求值(JS实现)

1 题目
根据 逆波兰表示法,求表达式的值。
有效的运算符包括 +, -, , / 。每个运算对象可以是整数,也可以是另一个逆波兰表达式
说明:
整数除法只保留整数部分。
给定逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。
示例 1:
输入: [“2”, “1”, “+”, “3”, “”]
输出: 9
解释: 该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:
输入: [“4”, “13”, “5”, “/”, “+”]
输出: 6
解释: 该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:
输入: [“10”, “6”, “9”, “3”, “+”, “-11”, “”, “/”, “”, “17”, “+”, “5”, “+”]
输出: 22
解释:
该算式转化为常见的中缀算术表达式为:
((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22
逆波兰表达式:
逆波兰表达式是一种后缀表达式,所谓后缀就是指算符写在后面。
平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。
该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。
逆波兰表达式主要有以下两个优点:
去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。
适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中。

2 思路
这道题本身不难,就是借助一个栈来实现计算,注意减法和除法运算时弹出两个数时的先后顺序

3代码
/**
* @param {string[]} tokens
* @return {number}
*/
var evalRPN = function(tokens) {
const stack = [];

while(tokens.length > 0) {
let flag = tokens.shift();
let handleFlag = parseInt(flag);
if (isNaN(handleFlag)) {
let num2 = stack.pop();
let num1 = stack.pop();
switch (flag) {
case ‘+’:
stack.push(num1 + num2);
break;
case ‘-‘:
stack.push(num1 – num2);
break;
case ‘*’:
stack.push(num1 * num2);
break;
case ‘/’:
stack.push(parseInt((num1 / num2)));
break;
}
} else {
stack.push(handleFlag);
}
}

return stack[0];
};

乘积*大子数组(JS实现)

乘积*大子数组(JS实现)

1 题目
给你一个整数数组 nums ,请你找出数组中乘积*大的连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积。
示例 1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有*大乘积 6。
示例 2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。

2 思路
这道题用动态规划的思路做,由于有负数的存在,两个负数相乘为正,则还必须在迭代的过程中同时维护乘积的*小值和*大值

3代码
/**
* @param {number[]} nums
* @return {number}
*/
var maxProduct = function(nums) {
let len = nums.length;
if (len === 0) return 0;
if (len === 1) return nums[0];
let max = nums[0];
let min = nums[0];
let ans = nums[0];
for (let i=1; i<len; i++) {
let maxF = Math.max(max * nums[i], nums[i], min * nums[i]);
let minF = Math.min(max * nums[i], nums[i], min * nums[i]);
max = maxF;
min = minF;
ans = Math.max(ans, max);
}
return ans;
};

寻找旋转排序数组中的*小值(JS实现)

寻找旋转排序数组中的*小值(JS实现)

1 题目
假设按照升序排序的数组在预先未知的某个点上进行了旋转。
( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
请找出其中*小的元素。
你可以假设数组中不存在重复元素。
示例 1:
输入: [3,4,5,1,2]
输出: 1
示例 2:
输入: [4,5,6,7,0,1,2]
输出: 0

2 思路
这道题从题意可知数组虽然被旋转,但局部上是有序的,因此就可以利用二分查找寻找*小值

3代码
/**
* @param {number[]} nums
* @return {number}
*/
var findMin = function(nums) {
if (nums.length === 0) return 0;
if (nums.length === 1) return nums[0];

let min = nums[0];

let low = 0;
let high = nums.length – 1;

while(low <= high) {
let mid = Math.floor((low + high) / 2);

min = Math.min(min, nums[mid]);

if (nums[mid] >= nums[0]) {
low = mid + 1;
} else {
high = mid – 1;
}
}

return min;

};

*小栈(JS实现)

*小栈(JS实现)

1 题目
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到*小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的*小元素。
示例:
输入:
[“MinStack”,“push”,“push”,“push”,“getMin”,“pop”,“top”,“getMin”]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); –> 返回 -3.
minStack.pop();
minStack.top(); –> 返回 0.
minStack.getMin(); –> 返回 -2.

2 思路
这道题主要点在于在常数时间获取当前*小的元素,方法就是在每个元素中保存一个当时的*小值,{value: x, min: y}

3代码
/**
* initialize your data structure here.
*/
var MinStack = function() {
this.stack = [];
};

/**
* @param {number} x
* @return {void}
*/
MinStack.prototype.push = function(x) {
let top = this.stack[this.stack.length – 1];
if (!top) {
this.stack.push({value: x, min: x});
} else {
this.stack.push({value: x, min: Math.min(x, top.min)});
}
};

/**
* @return {void}
*/
MinStack.prototype.pop = function() {
this.stack.pop();
};

/**
* @return {number}
*/
MinStack.prototype.top = function() {
return this.stack[this.stack.length – 1].value;
};

/**
* @return {number}
*/
MinStack.prototype.getMin = function() {
return this.stack[this.stack.length – 1].min;
};

unable to access android sdk add-on list

造成这个问题的原因可能有多种,下面两种方法,我亲自测试后可用。

错误信息:
*次安装完AS,出现这个错误提示

unable to access android sdk add-on list
%title插图%num

原因:
在*次安装AS,启动后,检测到电脑没有SDK。

知道了原因,自然有解决方法,可以在以后安装sdk,或者设置初次打开AS,不下载sdk

解决方法:
*种:
以下方法在V2.3.1测试可行

点击Cancel,在后续的界面再安装SDK。

%title插图%num

由于都是下一步,操作很简单,这里就不逐一介绍。虽然google.com被墙,但是近两年下载sdk速度还行

https://developers.google.cn/ 谷歌开发回国了,安装SDK和Android studio,还是通过google.com,但速度会不会快了呢。

第二种:设置初次打开AS,不下载sdk
1、在这个Android studio的安装目录下,找到下面这个文件

\bin\idea.properties

2、设置初次打开AS,不检测SDK。使用记事本打开,文件末尾添加一行:

disable.android.first.run=true

网上大部分介绍这种方法,但是治标不治本,SDK没有下载,就算进入界面了,也用不了。还是要去下载SDK。

Layout新姿势—ConstraintLayout

Layout新姿势—ConstraintLayout。

突然发现as的创建新项目时,导入的默认布局是个新名词——ConstraintLayout。

这个新姿势:

本篇文章的主题是ConstraintLayout。其实ConstraintLayout是Android Studio 2.2中主要的新增功能之一,也是Google在I/O大会上重点宣传的一个功能。我们都知道,在传统的Android开发当中,界面基本都是靠编写XML代码完成的,虽然Android Studio也支持可视化的方式来编写界面,但是操作起来并不方便,我也一直都不推荐使用可视化的方式来编写Android应用程序的界面。

而ConstraintLayout就是为了解决这一现状而出现的。它和传统编写界面的方式恰恰相反,ConstraintLayout非常适合使用可视化的方式来编写界面,但并不太适合使用XML的方式来进行编写。当然,可视化操作的背后仍然还是使用的XML代码来实现的,只不过这些代码是由Android Studio根据我们的操作自动生成的。

另外,ConstraintLayout还有一个优点,它可以有效地解决布局嵌套过多的问题。我们平时编写界面,复杂的布局总会伴随着多层的嵌套,而嵌套越多,程序的性能也就越差。ConstraintLayout则是使用约束的方式来指定各个控件的位置和关系的,它有点类似于RelativeLayout,但远比RelativeLayout要更强大。

其实ConstraintLayout属于Android Studio 2.2的新特性,我在去年写《第二行代码》的时候就非常想要将这部分内容加入到新书里面,但是在尝试之后还是放弃了。因为ConstraintLayout的用法很多都是对控件进行拖拽,只用文字或者是一些静态图片实在太难将它的用法表达清楚了,因此不太适合写到书上。我当时的想法就是在博客上面写一篇ConstraintLayout的用法讲解,来弥补一下《第二行代码》中缺失的这部分新特性,那么今天这篇文章来了。

开始

下面我们开始用边学边练的方式来进行学习,首先打开你的Android Studio,并新建一个ConstraintLayoutTest项目。另外,确保你的Android Studio是2.2或以上版本。

为了要使用ConstraintLayout,我们需要在app/build.gradle文件中添加ConstraintLayout的依赖,如下所示。

  1. dependencies {
  2. compile ‘com.android.support.constraint:constraint-layout:1.0.0-beta4’
  3. }

 

目前ConstraintLayout库*新的版本是1.0.0-beta4,还没有推出正式稳定版本,不过这并不影响我们提前进行学习和使用。

现在打开res/layout/activity_main.xml文件,由于这是一个新建的空项目,Android Studio会自动帮我们创建好一个布局,如下图所示。

%title插图%num不过,Android Studio自动创建的这个布局默认使用的是RelativeLayout,我们可以通过如下操作将它转换成ConstraintLayout。

%title插图%num转换完成之后,原RelativeLayout中的内容也会自动转换到ConstraintLayout中,比如图中的TextView。如果你不需要它的话,可以选中这个控件,然后按键盘上的Delete键即可删除。

%title插图%num我们可以看到,现在主操作区域内有两个类似于手机屏幕的界面,左边的是预览界面,右边的是蓝图界面。这两部分都可以用于进行布局编辑工作,区别是左边部分主要用于预览*终的界面效果,右边部分主要用于观察界面内各个控件的约束情况。

基本操作

下面我们来学习一些具体的操作吧,ConstraintLayout的基本用法很简单,比如我们想要向布局中添加一个按钮,那么只需要从左侧的Palette区域拖一个Button进去就可以了,如下图所示。

%title插图%num虽说现在Button已经添加到界面上了,但是由于我们还没有给Button添加任何的约束,因此Button并不知道自己应该出现在什么位置。现在我们在预览界面上看到的Button位置并不是它*终运行后的实际位置,如果一个控件没有添加任何约束的话,它在运行之后会自动位于界面的左上角。

那么下面我们就来给Button添加约束,每个控件的约束都分为垂直和水平两类,一共可以在四个方向上给控件添加约束,如下图所示。

%title插图%num上图中Button的上下左右各有一个圆圈,这圆圈就是用来添加约束的,我们可以将约束添加到ConstraintLayout,也可以将约束添加到另一个控件。比如说,想让Button位于布局的右下角,就可以这样添加约束,如下图所示。

%title插图%num我们给Button的右边和下边添加了约束,因此Button就会将自己定位到布局的右下角了。类似地,如果我们想要让Button居中显示,那么就需要给它的上下左右都添加约束,如下图所示。

%title插图%num这就是添加约束*基本的用法了。

除此之外,我们还可以使用约束让一个控件相对于另一个控件进行定位。比如说,我们希望再添加一个Button,让它位于*个Button的正下方,并且间距64dp,那么操作如下所示。

%title插图%num现在添加约束的方式我们已经学完了,那么该怎样删除约束呢?其实也很简单,删除约束的方式一共有三种,*种用于删除一个单独的约束,将鼠标悬浮在某个约束的圆圈上,然后该圆圈会变成红色,这个时候单击一下就能删除了,如下图所示。

%title插图%num第二种用于删除某一个控件的所有约束,选中一个控件,然后它的左下角会出现一个删除约束的图标,点击该图标就能删除当前控件的所有约束了,如下所示。

%title插图%num第三种用于删除当前界面中的所有约束,点击工具栏中的删除约束图标即可,如下图所示。

%title插图%num

Inspector

这样我们就把ConstraintLayout的基本用法学完了,接下来我们开始学习一些进阶的内容。

当你选中任意一个控件的时候,在右侧的Properties区域就会出现很多的属性选项,如下图所示。

%title插图%num在这里我们就可以设置当前控件的所有属性,如文本内容、颜色、点击事件等等。这些功能都非常简单,我就不再进行详细介绍,大家自己点一点就会操作了。

需要我们重点掌握的是Properties区域的上半部分,这部分也被称为Inspector。

%title插图%num首先可以看到,在Inspector中有一个纵向的轴和一个横向的轴,这两个轴也是用于确定控件的位置的。我们刚才给Button的上下左右各添加了一个约束,然后Button就能居中显示了,其实就是因为这里纵横轴的值都是50。如果调整了纵横轴的比例,那么Button的位置也会随之改变,如下图所示。

%title插图%num不过,虽然我们将横轴的值拖动到了100,但是Button并没有紧贴到布局的*右侧,这是为什么呢?实际上,Android Studio给控件的每个方向上的约束都默认添加了一个16dp的间距,从Inspector上面也可以明显地看出来这些间距的值。如果这些默认值并不是你想要的,可以直接在Inspector上进行修改,如下图所示:

%title插图%num可以看到,修改成0之后Button右侧的间距就没了。

接下来我们再来学习一下位于Inspector*中间的那个正方形区域,它是用来控制控件大小的。一共有三种模式可选,每种模式都使用了一种不同的符号表示,点击符号即可进行切换。

  • %title插图%num 表示wrap content,这个我们很熟悉了,不需要进行什么解释。
  • %title插图%num 表示固定值,也就是给控件指定了一个固定的长度或者宽度值。
  • %title插图%num 表示any size,它有点类似于match parent,但和match parent并不一样,是属于ConstraintLayout中特有的一种大小控制方式,下面我们来重点讲解一下。

首先需要说明,在ConstraintLayout中是有match parent的,只不过用的比较少,因为ConstraintLayout的一大特点就是为了解决布局嵌套,既然没有了布局嵌套,那么match parent也就没有多大意义了。

而any size就是用于在ConstraintLayout中顶替match parent的,先看一下我们怎样使用any size实现和match parent同样的效果吧。比如说我想让Button的宽度充满整个布局,操作如下图所示。

%title插图%num可以看到,我们将Button的宽度指定成any size,它就会自动充满整个布局了。当然还要记得将Button左侧的间距设置成0才行。

那有的朋友可能会问了,这和match parent有什么区别呢?其实*大的区别在于,match parent是用于填充满当前控件的父布局,而any size是用于填充满当前控件的约束规则。举个例子更好理解,如果我们有一个新的Button,它的其中一个约束是添加到当前这个Button上的,那么any size的效果也会发生改变,如下图所示。

%title插图%num通过上图的演示,相信你已经很好地理解any size的作用了。

Guidelines

现在你已经对ConstraintLayout比较熟悉,并且能使用ConstraintLayout来编写一些简单的界面了。不过目前有一个问题可能还比较头疼,刚才我们已经实现了让一个按钮居中对齐的功能,如果我们想让两个按钮共同居中对齐该怎么实现呢?

其实这个需求很常见,比如说在应用的登录界面,都会有一个登录按钮和一个注册按钮,不管它们是水平居中也好还是垂直居中也好,但肯定都是两个按钮共同居中的。

想要实现这个功能,仅仅用我们刚刚学的那些知识是不够的,这需要用到ConstraintLayout中的一个新的功能,Guidelines。

下面我们还是通过实际操作来学习一下Guidelines的用法吧。比如现在已经向界面中添加了登录和注册这两个按钮,如下图所示。

%title插图%num然后我们希望让这两个按钮在水平方向上居中显示,在垂直方向上都距离底部64dp,那么就需要先添加一个垂直方向上的Guideline,如下图所示。

%title插图%num我来对上图中的操作进行一下解释。首先点击通知栏中的Guidelines图标可以添加一个垂直或水平方向上的Guideline,这里我们需要的是垂直方向上的。而Guideline默认是使用的dp尺,我们需要选中Guideline,并点击一下*上面的箭头图标将它改成百分比尺,然后将垂直方向上的Guideline调整到50%的位置,这样就将准备工作做好了。

接下来我们开始实现让两个按钮在水平方向上居中显示,并距离底部64dp的功能,如下图所示。

%title插图%num可以看到,我们给登录按钮的右边向Guideline添加约束,登录按钮的下面向底部添加约束,并拖动按钮让它距离底部64dp。然后给注册按钮的左边向Guideline添加约束,注册按钮的下面向登录按钮的下面添加约束。这样就实现了让两个按钮在水平方向上居中显示,在垂直方向上都距离底部64dp的功能了。

自动添加约束

不过如果界面中的内容变得复杂起来,给每个控件一个个地添加约束也是一件很繁琐的事情。为此,ConstraintLayout中支持自动添加约束的功能,可以*大程度上简化那些繁琐的操作。

自动添加约束的方式主要有两种,一种叫Autoconnect,一种叫Inference,我们先来看*种。

想要使用Autoconnect,首先需要在工具栏中将这个功能启用,默认情况下Autoconnect是不启用的,如下图所示。

%title插图%numAutoconnect可以根据我们拖放控件的状态自动判断应该如何添加约束,比如我们将Button放到界面的正中央,那么它的上下左右都会自动地添加上约束,如下图所示。

%title插图%num然后我们在这个Button的下方再放置一个Button,效果如下。

%title插图%num可以看到,只需要将Button拖放到界面上,Autoconnect会判断我们的意图,并自动给控件添加约束。不过Autoconnect是无法保证百分百准确判断出我们的意图的,如果自动添加的约束并不是你想要的话,还可以在任何时候进行手动修改。总之,可以把它当成一个辅助工具,但不能完全靠它去添加控件的约束。

以上是Autoconnect的用法,接下来我们看一下Inference的用法。Inference也是用于自动添加约束的,但它比Autoconnect的功能要更为强大,因为AutoConnect只能给当前操作的控件自动添加约束,而Inference会给当前界面中的所有元素自动添加约束。因而Inference比较适合用来实现复杂度比较高的界面,它可以一键自动生成所有的约束。

下面我们就通过一个例子来演示一下Inference的用法,比如界面上现在有两个TextView,两个EditText,和两个Button,如下图所示。

%title插图%num接下来我们先将各个控件按照界面设计的位置进行摆放,摆放完成之后点击一下工具栏上的Infer Constraints按钮,就能为所有控件自动添加约束了,如下图所示。

%title插图%num现在运行一下程序,*终效果如下图所示:

%title插图%num

android ConstraintLayout约束布局的使用

一、概述

ConstraintLayout出现有一段时间了,不过一直没有特别去关注,也多多少少看了一些文字介绍,多数都是对使用可视化布局拖拽,个人对拖拽一直不看好,直到前段时间看到该文:

  • 解析ConstraintLayout的性能优势

非常详尽的介绍了ConstraintLayout的性能优势,于是乎开始学习了一下ConstraintLayout。

本文的重点不在与可视化界面的学习,而在于如何手写各类约束布局属性。对于可视化界面学习推荐:

  • Android新特性介绍,ConstraintLayout完全解析

下面开始进入正题,大家都知道,当布局嵌套深入比较深的时候,往往会伴随着一些性能问题。所以很多时候我们建议使用RelativeLayout或者GridLayout来简化掉布局的深度。

而对于简化布局深度,ConstraintLayout几乎可以做到*致,接下来我们通过实例来尽可能将所有常见的属性一步步的介绍清楚。

首先需要引入我们的ConstraintLayout,在build.gradle中加入:

compile 'com.android.support.constraint:constraint-layout:1.0.2'

 

二、来编写一个Feed Item

我们先看一个简单的新闻列表中常见的feed item。
%title插图%num
看到这样的布局,大家条件反射应该就是使用RelativeLayout来做,当然了,本案例我们使用ConstraintLayout来写:

<android.support.constraint.ConstraintLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#11ff0000"
    tools:context="com.zhy.constrantlayout_learn.MainActivity">


    <TextView
        android:id="@+id/tv1"
        android:layout_width="140dp"
        android:layout_height="86dp"
        android:layout_marginLeft="12dp"
        android:layout_marginTop="12dp"
        android:background="#fd3"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        />

    <TextView
        android:id="@+id/tv2"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginRight="12dp"
        android:text="马云:一年交税170多亿马云:一年交税170多亿马云:一年交税170多亿"
        android:textColor="#000000"
        android:textSize="16dp"
        app:layout_constraintLeft_toRightOf="@id/tv1"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="@id/tv1" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="8dp"
        android:layout_marginTop="12dp"
        android:text="8分钟前"
        android:textColor="#333"
        android:textSize="12dp"
        app:layout_constraintLeft_toRightOf="@id/tv1"
        app:layout_constraintBottom_toBottomOf="@id/tv1" />

</android.support.constraint.ConstraintLayout>

看上面的布局,我们好像看到了几个模式的属性:

首先是tv1,有两个没见过的属性:

  • app:layout_constraintLeft_toLeftOf=”parent”

从字面上看,指的是让该控件的左侧与父布局对齐,当我们希望控件A与控件B左侧对齐时,就可以使用该属性。

app:layout_constraintLeft_toLeftOf="@id/viewB"

 

类似的还有个相似的属性为:

  • app:layout_constraintLeft_toRightOf

很好理解,即当前属性的左侧在谁的右侧,当我们希望控件A在控件B的右侧时,可以设置:

app:layout_constraintLeft_toRightOf="@id/viewB"

 

与之类似的还有几个属性:

  • layout_constraintRight_toLeftOf
  • layout_constraintRight_toRightOf
  • layout_constraintTop_toTopOf
  • layout_constraintTop_toBottomOf
  • layout_constraintBottom_toTopOf
  • layout_constraintBottom_toBottomOf
  • layout_constraintBaseline_toBaselineOf

类推就可以了。

现在在头看刚才的布局:

tv1设置了:

app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"

tv2设置了:

app:layout_constraintLeft_toRightOf="@id/tv1"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="@id/tv1"

tv3设置了:

app:layout_constraintLeft_toRightOf="@id/tv1"
app:layout_constraintBottom_toBottomOf="@id/tv1"

 

按照我们刚才的理解,再次的解读下:

tv1应该是在父布局的左上角;

tv2在tv1的右侧,tv2的右侧和父布局对其,tv2和tv1顶部对齐;

tv3在tv1的右侧,tv3和tv1底部对其。

到这里,大家可以看到,目前我们已经可以控制任何一个控件与其他控件间的相对位置了,以及与parent间的相对位置。

和RL的差异

大家是不是觉得目前来看和RelativeLayout特别像?

其实还是有很明显的区别的,我们通过一个例子来看一下:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent" android:layout_height="match_parent">

    <Button
        android:id="@+id/id_btn01"
        android:layout_width="100dp"
        android:text="Btn01"
        android:layout_height="wrap_content" />

    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@id/id_btn01"
        android:text="Btn02"
        android:layout_alignParentRight="true"
        />

</RelativeLayout>

%title插图%num 

那么经过我们刚才的学习,把:

layout_toRightOf=”@id/id_btn01”,layout_alignParentRight=”true”

分别替换为:

app:layout_constraintLeft_toRightOf=”@id/id_btn01”,app:layout_constraintRight_toRightOf=”parent”

是不是觉得so easy ,但是我们看一下效果图:
%title插图%num
是不是和预期有一定的区别,假设你将Btn02的宽度设置的非常大,你会发现更加诡异的事情:
%title插图%num
你会发现Btn02,好像疯了一样,我们设置的在btn01右侧,和与parent右侧对齐完全失效了!!!

别怕,接下来就让你认识到为什么这个控件叫做“Constraint”Layout。

在当控件有自己设置的宽度,例如warp_content、固定值时,我们为控件添加的都是约束“Constraint”,这个约束有点像橡皮筋一样会拉这个控件,但是并不会改变控件的尺寸(RL很明显不是这样的)。

例如上例,当btn02的宽度较小时,我们为其左侧设置了一个约束(btn01右侧),右侧设置了一个约束(parent右侧对其),当两个约束同时生效的时候(你可以认为两边都是相同的一个拉力),btn02会居中。

当btn02特别大的时候,依然是这两个力,那么会发生什么?会造成左侧和右侧超出的距离一样大。

那么现在大家肯定有些疑问:

  • 怎么样才能和上面的RL一样,宽度刚好占据剩下的距离呢(btn01右侧到屏幕右侧的距离)?

这个问题,问得很好,我们刚才所有的尝试都是在控件自身拥有特定的宽度情况下执行的;那么如果希望控件的宽度根据由约束来控件,不妨去掉这个特定的宽度,即设置为0试试?

对!当我们将btn02的宽度设置为0时,一切又变得很完美。
%title插图%num
那么这里,你可能会问0值是什么含义,其实在ConstraintLayout中0代表:MATCH_CONSTRAINT,看到这个常量,是不是瞬间觉得好理解了一点。

  • *后一个问题,MATCH_PARENT哪去了?

看官网的解释:

Important: MATCH_PARENT is not supported for widgets contained in a ConstraintLayout, though similar behavior can be defined by using MATCH_CONSTRAINT with the corresponding left/right or top/bottom constraints being set to “parent”.`

所以你可以认为:在ConstraintLayout中已经不支持MATCH_PARENT这个值了,你可以通过MATCH_CONSTRAINT配合约束实现类似的效果。

好了,到这里,目前我们已经看到其已经和RelativeLayout势均力敌了,接下来我们看一下RL做不到的特性。

三、增加一个banner

我们现在以往在这个feed item顶部添加一个banner,宽度为占据整个屏幕,宽高比为16:6。

这里尴尬了,在之前的做法,很难在布局中设置宽高比,一般我们都需要在代码中显示的去操作,那么如果你用了ConstraintLayout,它就支持。

看一眼如何支持:

<android.support.constraint.ConstraintLayout 
    ...
    tools:context="com.zhy.constrantlayout_learn.MainActivity">

    <TextView
        android:id="@+id/banner"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:background="#765"
        android:gravity="center"
        android:text="Banner"
        app:layout_constraintDimensionRatio="H,16:6"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent" />


    <TextView
        android:id="@+id/tv1"
        app:layout_constraintTop_toBottomOf="@id/banner"
        ></TextView>
     ...
</...>

我们添加了一个banner,还记得我们刚才所说的么,不要使用match_parent了,而是设置match_contraint,即0,让约束来控制布局宽高。

所以我们设置了宽、高都是match_contraint,然后这两个属性:

app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"

 

让我们的宽度充满整个父布局,在添加一个:

app:layout_constraintDimensionRatio="16:6"

 

该属性指的是宽高比,所以16:6就可以完成我们的需求。

好了看下效果图:
%title插图%num
这个宽高比属性,还支持这样的写法:

app:layout_constraintDimensionRatio="W,16:6"
app:layout_constraintDimensionRatio="H,16:6"

 

可以自己试验下。

好了,到这里,我们又新增了一个属性,还是个非常实用的属性。

那么,我们继续,再看一个似曾相识的功能。

四、增加几个Tab

现在我们希望在底部增加3个tab,均分。是不是想到了LinearLayout和weight。

没错!ConstraintLayout也支持类似的属性。

虽然我知道,但是写到这我还是有点小惊喜~~

看下如何实现:

<TextView
    android:id="@+id/tab1"
    android:layout_width="0dp"
    android:layout_height="30dp"
    android:background="#f67"
    android:gravity="center"
    android:text="Tab1"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toLeftOf="@+id/tab2" />


<TextView
    android:id="@+id/tab2"
    android:layout_width="0dp"
    android:layout_height="30dp"
    android:background="#A67"
    android:gravity="center"
    android:text="Tab2"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toRightOf="@id/tab1"
    app:layout_constraintRight_toLeftOf="@+id/tab3" />


<TextView
    android:id="@+id/tab3"
    android:layout_width="0dp"
    android:layout_height="30dp"
    android:background="#767"
    android:gravity="center"
    android:text="Tab3"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toRightOf="@id/tab2"
    app:layout_constraintRight_toRightOf="parent" />

我们增加3个textview来冒充tab。我们看横向的依赖,3个tab两两设置了约束(即你在我们的左边,我在你的右边),*外层的设置了parent约束;再加上我们把宽度都设置为了match_constraint,so,这样我们就完成了3个tab等分。

看一眼效果图:
%title插图%num
你可能会说,LL配合weight更加灵活,可以单个设置占据的比例。

对,没错,我们也支持,我不是还没说完么。

现在我们可以给每个tab设置一个属性:

app:layout_constraintHorizontal_weight

 

看到这个名字,应该就明白了吧,假设我们分别设置值为2,1,1。

效果图为:
%title插图%num
是不是很惊喜,别急,刚才你说我不如LL,现在我要让你再看一些LL配合weight做不到的。

这里需要借助几张官网上的图了:

刚才我们说了,3个tab两两设置了依赖,即类似下图:
%title插图%num
横向的相当于组成了一个链(Chains)。在这个链的*左侧的元素成为链头,我们可以在其身上设置一些属性,来决定这个链的展示效果:

该属性为:

layout_constraintHorizontal_chainStyle

 

我们已经见过一种效果了,即按照weight等分,可以成为weighted chain。设置条件为:

chainStyle=”spread”,所有控件宽度设置为match_constraint,因为默认就是spread,所以我们没有显示设置。

其取值还可以为:

  • packed
  • spread_inside

我还是分别显示一下吧:

  1. spread + 宽度非0
    %title插图%num
  2. spread + 宽度为0,且可以通过weight控制分配比例(上例)
  3. spread_inside + 宽度非0
    %title插图%num
  4. packed + 宽度非0
    %title插图%num

好了,差不多了,我们可以在横向或者纵向组成一个Chain,然后在Chain head设置chainStyle来搞一些事情。

官网有个图:
%title插图%num
前四个我们都演示了,*后一个设计到一个新的bias属性,别急,咱们慢慢说~~

好了,到这里,我们再次见证了ConstraintLayout的强大。

我们*后再看一个例子。

五、增加浮动按钮

一个很常见的功能,我们现在希望在右下角增加一个浮动按钮。

看下如何实现:

<android.support.constraint.ConstraintLayout 
    ...
    tools:context="com.zhy.constrantlayout_learn.MainActivity">

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#612"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintHorizontal_bias="0.9"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.9" />

</....>

我们在*后追加一个TextView冒充我们的浮动按钮。可以看到我们设置了固定值,被设置约束为右下角。

正常情况我们可以通过margin来设置与右侧与底部的距离。

但是这里我们尝试使用量个新的属性:

layout_constraintHorizontal_bias
layout_constraintVertical_bias

 

即设置上下两侧间隙比例分别为90%与10%。这个很好理解,我们之前说了,再没有bias这个属性的时候,这两侧的拉力大小是一样的,但是你可以通过bias来控制哪一侧的力要大一些~~明白了么~

所以,该属性可以用于约束之前,控制两侧的“拉力”。

我们看一下效果图:
%title插图%num
那么到这里,ConstraintLayout的属性我们基本上介绍完了:

我们看一下:

layout_constraintLeft_toLeftOf 
layout_constraintLeft_toRightOf
layout_constraintRight_toLeftOf
layout_constraintRight_toRightOf
layout_constraintTop_toTopOf
layout_constraintTop_toBottomOf
layout_constraintBottom_toTopOf
layout_constraintBottom_toBottomOf

# 即文章的baseline对齐
layout_constraintBaseline_toBaselineOf

# 与left,right类似
layout_constraintStart_toEndOf 
layout_constraintStart_toStartOf
layout_constraintEnd_toStartOf
layout_constraintEnd_toEndOf

# margin不需要解释
android:layout_marginStart
android:layout_marginEnd
android:layout_marginLeft
android:layout_marginTop
android:layout_marginRight
android:layout_marginBottom

layout_constraintHorizontal_bias  
layout_constraintVertical_bias  

layout_constraintHorizontal_chainStyle
layout_constraintVertical_chainStyle

layout_constraintVertical_weight

Guideline 

好像,还有个比较特殊的,叫Guideline。

好吧,继续~

六、尝试使用Guideline

android.support.constraint.Guideline该类比较简单,主要用于辅助布局,即类似为辅助线,横向的、纵向的。该布局是不会显示到界面上的。

所以其有个属性为:

android:orientation取值为”vertical”和”horizontal”.

除此以外,还差个属性,决定该辅助线的位置:

  • layout_constraintGuide_begin
  • layout_constraintGuide_end
  • layout_constraintGuide_percent

可以通过上面3个属性其中之一来确定属性值位置。

begin=30dp,即可认为距离顶部30dp的地方有个辅助线,根据orientation来决定是横向还是纵向。

end=30dp,即为距离底部。
percent=0.8即为距离顶部80%。

好了,下面看一个例子,刚才我们的浮点按钮,我决定通过两根辅助线来定位,一根横向距离底部80%,一个纵向距离顶部80%,浮点按钮就定位在他们交叉的地方。

<android.support.constraint.ConstraintLayout 
    ...
    tools:context="com.zhy.constrantlayout_learn.MainActivity">

    <android.support.constraint.Guideline
        android:id="@+id/guideline_h"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        app:layout_constraintGuide_percent="0.8" />


    <android.support.constraint.Guideline
        android:id="@+id/guideline_w"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical"
        app:layout_constraintGuide_percent="0.8" />

    <TextView
        android:layout_width="60dp"
        android:layout_height="60dp"
        android:background="#612"
        app:layout_constraintLeft_toRightOf="@id/guideline_w"
        app:layout_constraintTop_toBottomOf="@id/guideline_h" />

</....>

 

我感觉都不用解释了~~看眼效果图吧:
%title插图%num
到此,属性基本上讲完啦~

可以看到,上述相当复杂的一个布局,在ConstraintLayout中完全没有嵌套!

七、总结

本文通过实际的按钮,基本上介绍了ConstraintLayout所支持的所有的属性,全文没有提及拖拽,因为当界面复杂之后,想要完美的拖拽实在是太难了,而且谁也不期望,看不懂拖拽完成后的布局属性吧~

所以,我建议还是尽可能手写,通过本文这样一个流程,虽然支持的属性有20多个,但是分类后并不难记,难记也可以拿出本文翻一翻~