Blogz

The subtitle of your website

0%

LeetCode 159 至多包含两个不同字符的最长子串


题目:
给你一个字符串 s ,请你找出 至多 包含 两个不同字符 的最长子串,并返回该子串的长度。
 

示例 1:

输入:s = "eceba"
输出:3
解释:满足题目要求的子串是 "ece" ,长度为 3 。
示例 2:

输入:s = "ccaabbb"
输出:5
解释:满足题目要求的子串是 "aabbb" ,长度为 5 。
 

提示:

1 <= s.length <= 105
s 由英文字母组成

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/longest-substring-with-at-most-two-distinct-characters
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解:
此题使用双指针加滑动窗口来求解,主要需要注意边界条件
解题思路:
1.因为题目要求字串包含至多两个不同字符,我们使用长度为2的数据保存当前计数的字符 twoChar
2.使用快慢指针,快指针遍历,慢指针总是指向和快指针上一个计数字符的起点
  比如 aabbccd 如果快指针此刻正在计数c,那么慢指针指向第一个b位置,
  这样我们可以保证从快指针到慢指针的范围内,一定只有b和c两种字符
3.每次当快指针指向和当前计数的两种字符都不同的字符时候,我们需要计算之前计数的长度,然后替换
  新的计数字符,且开始计数的值需加上快慢指针之间的字符数

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public int lengthOfLongestSubstringTwoDistinct(String s) {
if (s.length() == 1) return 1;
char[] towC = new char[]{'-', '-'};
char[] chars = s.toCharArray();
int p1 = 0;
int p2 = 0;
towC[0] = chars[0];
int maxLen = 1;
int len = 1;
while (++p2 < chars.length) {
if (chars[p2] == towC[0] || chars[p2] == towC[1]) {
if (chars[p2] != chars[p2 - 1]) {
p1 = p2;
}
len++;
}else if (towC[1] == '-') {
towC[1] = chars[p2];
p1 = p2;
len++;
}else{
maxLen = Math.max(maxLen, len);
len = p2 - p1 + 1;
if (towC[0] == chars[p1]) {
towC[1] = chars[p2];
} else {
towC[0] = chars[p2];
}
p1 = p2;
}
}
maxLen = Math.max(maxLen, len);
return maxLen;
}

LeetCode 24 两两交换链表中的节点


题目:
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。

示例 1:


输入:head = [1,2,3,4]
输出:[2,1,4,3]
示例 2:

输入:head = []
输出:[]
示例 3:

输入:head = [1]
输出:[1]

题解:
此题思路很简单,定义一个哨兵节点,然后交换链表节点重新拼接即可

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ListNode swapPairs(ListNode head) {
if (head == null || head.next == null) return head;
ListNode root = new ListNode(0);
ListNode p = root;
p.next = head;
while (p.next != null && p.next.next != null) {
ListNode node = p.next;
p.next = node.next;
node.next = p.next.next;
p.next.next = node;
p = p.next.next;
}
return root.next;
}

LeetCode 23 合并K个升序链表


题目:
给你一个链表数组,每个链表都已经按升序排列。
请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
1->4->5,
1->3->4,
2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6
示例 2:

输入:lists = []
输出:[]
示例 3:

输入:lists = [[]]
输出:[]


提示:

k == lists.length
0 <= k <= 10^4
0 <= lists[i].length <= 500
-10^4 <= lists[i][j] <= 10^4
lists[i] 按 升序 排列
lists[i].length 的总和不超过 10^4

题解:
此题我们使用小顶堆数据结构巧解此事:

解题步骤如下:
1.创建K个指针依次指向K个链表的表头
2.定义小顶堆,比较对象是K指针指向的节点的值
3.将K个指针放入小顶堆
4.定义哨兵节点Head
5.取小顶堆顶部节点H,此节点为当前堆中最小值,将此节点链接到哨兵节点之后,
  然后将此指针后移(如果有),然后重新放入小顶堆中重拍
6.重复步骤5直至堆中节点全部推出

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
private ListNode process(ListNode[] lists){
ListNode head = new ListNode(0);
ListNode p = head;
if (lists.length == 0) return null;
PriorityQueue<ListNode> priorityQueue = new PriorityQueue<>(lists.length, Comparator.comparingInt(o -> o.val));
for (ListNode node : lists) {
if (node != null) {
priorityQueue.offer(node);
}
}
while (!priorityQueue.isEmpty()) {
ListNode node = priorityQueue.poll();
ListNode next = priorityQueue.peek();
if (next == null) {
p.next = node;
break;
}
ListNode pNode = node;
while (pNode.next != null && pNode.next.val <= next.val) {
pNode = pNode.next;
}
if (pNode.next != null) {
priorityQueue.add(pNode.next);
}
pNode.next = null;
p.next = node;
p = pNode;
}
return head.next;
}

LeetCode 22 括号生成


题目:
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例 1:

输入:n = 3
输出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:

输入:n = 1
输出:["()"]

提示:
1 <= n <= 8

题解:
经典DFS算法 
根据 LC20_IsValid 我们定义的有效括号的特性可得到以下规律:

1.我们必须保证每次放入后,右括号数必须小于或者等于左括号,即每个右括号一定要能匹配一个左括号
2.如果一个括号组放入的左括号组的数量等于我们定义的括号对数量,则只能放入右括号
3.其他情况即可以放入左括号,也可以放入右括号

图解:
图解

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public List<String> generateParenthesis(int n) {
List<String> list = new ArrayList<>();
dfs(list, 0, n, new StringBuilder());
return list;
}

private void dfs(List<String> list, int put, int remain, StringBuilder s) {
if (put == 0 && remain == 0) {
list.add(s.toString());
} else if (put == 0 && remain > 0) {
dfs(list, put + 1, remain - 1, s .append('('));
} else if (put > 0 && remain == 0) {
dfs(list, put - 1, remain, s.append(')'));
} else {
dfs(list, put + 1, remain - 1, new StringBuilder(s.toString()).append('('));
dfs(list, put - 1, remain, new StringBuilder(s).append(')'));
}
}

LeetCode 20 有效的括号


题目:
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效
有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。

示例 1:

输入:s = "()"
输出:true
示例 2:

输入:s = "()[]{}"
输出:true
示例 3:

输入:s = "(]"
输出:false
示例 4:

输入:s = "([)]"
输出:false
示例 5:

输入:s = "{[]}"
输出:true


提示:

1 <= s.length <= 104
s 仅由括号 '()[]{}' 组成

题解:
此题我们使用数据结构中的栈结构来巧解此题
我们知道,一个有效的括号组合有以下特性

1.括号组的数量一定是偶数
2.括号组的数量如果是0则一定有效
3.每种类型的左括号和右括号数量相同
4.每一对括号内要么没有其他括号组,如果有,其他括号组一定也是有效括号组

我们按照以下规则处理:

剪枝
1.如果括号组数量是奇数直接返回False
2.如果括号组的数量如果是0直接返回True

遍历
1.如果括号是左括号,直接入栈
2.如果括号是右括号:
  a.如果栈顶括号是与之匹配的左括号,栈顶括号出栈
  b.其他所有情况直接返回False
3.如果括号组全部匹配完毕返回True

图解:

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public boolean isValid(String s) {
int len = s.length();
if (len == 0) return true;
if (len % 2 != 0) return false;
char[] chars = s.toCharArray();
Stack<Character> stack = new Stack<>();
String signal = "{[(}])";
for (int i = 0; i < len; i++) {
int index = signal.indexOf(chars[i]);
if (index < 3) {
stack.add(chars[i]);
} else {
if (stack.isEmpty()) return false;
char c = stack.pop();
if (c != signal.charAt(index - 3)) {
return false;
}
}
}
return stack.isEmpty();
}

LeetCode 18 四数之和


题目:
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。
请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] 
(若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。


示例 1:

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]


提示:

1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

题解:
四数之和和三数之和原理一致,也是利用双指针法求解
详解请参照  [三数之和详解]
此题不给出详细的分析过程

代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public List<List<Integer>> fourSum(int[] nums, int target) {
List<List<Integer>> result = new ArrayList<>();
int len = nums.length;
if (len < 4) return result;
Arrays.sort(nums);
for (int i = 0; i < len - 3; i++) {
if (i > 0 && nums[i - 1] == nums[i]) continue;
if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) break;
if ((long) nums[i] + nums[len - 1] + nums[len - 2] + nums[len - 3] < target) continue;
for (int j = i + 1; j < len - 2; j++) {
if (j > i + 1 && nums[j - 1] == nums[j]) continue;
if ((long) nums[j] + nums[j + 1] + nums[j + 2] > target - nums[i]) break;
if ((long) nums[j] + nums[len - 1] + nums[len - 2] < target - nums[i]) continue;
long remain = (long) target - nums[i] - nums[j];
int p1 = j + 1;
int p2 = len - 1;
while (p1 < p2) {
if (nums[p1] + nums[p2] == remain) {
result.add(Arrays.asList(nums[i], nums[j], nums[p1], nums[p2]));
while (++p1 <= p2 && nums[p1] == nums[p1 - 1]) {
}
while (--p2 >= p1 && nums[p2] == nums[p2 + 1]) {
}
}else if (nums[p1] + nums[p2] < remain) {
while (++p1 <= p2 && nums[p1] == nums[p1 - 1]) {
}
} else {
while (--p2 >= p1 && nums[p2] == nums[p2 + 1]) {
}
}
}
}
}
return result;
}

概要

在运行时,FragmentManager 可以添加、删除、替换和使用片段执行其他操作以响应用户交互。
您提交的每组片段更改称为一个事务,您可以使用 FragmentTransaction 该类提供的 API 指定在事务中执行的操作
您可以将多个操作分组到一个事务中——例如,一个事务可以添加或替换多个片段

创建和操作事务

通过调用 FragmentManagerbeginTransaction() 获取FragmentTransaction的实例,如下例所示:
1
2
FragmentManager fragmentManager = ...
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
每个人的最终调用都FragmentTransaction必须提交事务。该commit()调用FragmentManager表示所有操作都已添加到事务中
1
2
3
4
5
6
FragmentManager fragmentManager = ...
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();

// Add operations here

fragmentTransaction.commit();

片段状态更改重新排序

每个都 FragmentTransaction 应该使用 setReorderingAllowed(true) 允许片段状态更改重排序
1
2
3
4
5
FragmentManager fragmentManager = ...
fragmentManager.beginTransaction()
...
.setReorderingAllowed(true)
.commit();
为了行为兼容性,默认情况下不启用重新排序标志。
但是,它需要允许FragmentManager正确执行您的FragmentTransaction,
特别是当它在后台堆栈上运行并运行动画和过渡时。
启用该标志可确保如果多个事务一起执行,任何中间片段(即添加然后立即替换的片段)
不会经历生命周期更改或执行其动画或转换。

请注意,此标志会影响事务的初始执行和使用 撤销事务popBackStack()

添加和删​​除片段

调用 add()将片段添加到 FragmentManager。此方法接收片段容器的 ID ,以及您希望添加的片段的类名。
添加的片段被移动到 RESUMED 状态。
强烈建议容器是 FragmentContainerView 视图层次结构的一部分。

要从主机中删除片段,请调用 remove() 方法传入一个片段实例
该实例是由 findFragmentById() or findFragmentByTag()方法找到的
如果片段的视图先前已添加到容器中,则此时视图将从容器中删除。移除的片段被移动到 DESTROYED 状态。

用于 replace() 将容器中的现有片段替换为您提供的新片段类的实例。
调用replace()相当于调用 remove() 先移除容器中的片段,然后将新片段添加到同一个容器中。

以下代码片段显示了如何将一个片段替换为另一个片段:
1
2
3
4
5
6
7
8
9
10
// Create new fragment and transaction
FragmentManager fragmentManager = ...
FragmentTransaction transaction = fragmentManager.beginTransaction();
transaction.setReorderingAllowed(true);

// Replace whatever is in the fragment_container view with this fragment
transaction.replace(R.id.fragment_container, ExampleFragment.class, null);

// Commit the transaction
transaction.commit();
在这个例子中,一个新的实例 ExampleFragment 替换了当前位于由 
标识的布局容器中的片段(如果有的话) R.id.fragment_container
注意:强烈建议始终使用采用 Class 而不是片段实例的片段操作,
以确保创建片段的相同机制也用于从保存状态恢复片段。
有关更多详细信息,请参阅 FragmentManager
默认情况下,在 FragmentTransaction 中所做的更改不会添加到后台堆栈。
要保存这些更改,您可以调用 addToBackStack()

提交异步

调用 commit() 不会立即执行事务。事务被安排在主 UI 线程上运行,只要它能够这样做。
但是,如有必要,您可以调用 commitNow() 以立即在您的 UI 线程上运行片段事务。

请注意,commitNow 与 addToBackStack 是不兼容的. 或者,您可以通过调用 executePendingTransactions() 
执行所有尚未运行的由FragmentTransactions提交的事务。
这种方法与 addToBackStack 是兼容的

对于绝大多数用例,使用 commit() 就足够了

执行顺序

在 FragmentTransaction 中执行操作的顺序很重要,
尤其是在使用setCustomAnimations(). 
此方法将给定的动画应用于其后的所有片段操作
1
2
3
4
5
6
getSupportFragmentManager().beginTransaction()
.setCustomAnimations(enter1, exit1, popEnter1, popExit1)
.add(R.id.container, ExampleFragment.class, null) // gets the first animations
.setCustomAnimations(enter2, exit2, popEnter2, popExit2)
.add(R.id.container, ExampleFragment.class, null) // gets the second animations
.commit()

显示和隐藏

使用 FragmentTransaction 中的 show() hide() 方法显示和隐藏已添加到容器的片段的视图
这些方法设置片段视图的可见性,而不影响片段的生命周期

虽然您不需要使用片段事务来切换片段中视图的可见性,
但这些方法对于您希望更改可见性状态与后台堆栈上的事务相关联的情况很有用

附加和分离

FragmentTransaction 方法 detach() 将片段与 UI 分离,破坏其视图层次结构。
片段保持与放入回退栈时相同的状态(STOPPED)。这意味着片段已从 UI 中删除,但仍由片段管理器管理。

attach() 方法重新附加之前分离的片段。这会导致其视图层次结构被重新创建、附加到 UI 并显示。

由于 FragmentTransaction 被视为单个原子操作集,
因此对同一事务中的同一片段实例的同时调用 detach 和 attach 会相互抵消,从而避免了片段 UI 的破坏和立即重建。
如果想要实现立即 detach 然后 attach 操作需使用单独的事务分别 commit()
然后使用 executePendingOperations() 方法立即执行挂起的事务

概要

FragmentManager 类负责在应用的 fragment 上执行一些操作,
如添加、移除或替换操作,以及将操作添加到返回堆栈

访问 FragmentManager

我们可以在Activity和Fragment中访问FragmentManager,下面依次介绍两种访问方式和联系
1.在 Activity 中访问
每个 FragmentActivity 及其子类(如 AppCompatActivity)
都可以通过 getSupportFragmentManager() 方法访问 FragmentManager
2.在 Fragment 中访问
Fragment 也能够托管一个或多个子 fragment。在 fragment 内,
您可以通过 getChildFragmentManager() 获取对管理 fragment 子级的 FragmentManager 的引用。
如果您需要访问其宿主 FragmentManager,可以使用 getParentFragmentManager()

托管 Fragment

fragment、其宿主以及与每个 fragment 关联的 FragmentManager 实例之间的关系如下图所示
两个界面布局示例,显示了 fragment 与其宿主 activity 之间的关系。每个示例中都有一个 activity 宿主。
这两个示例中的宿主 activity 都以 BottomNavigationView 的形式向用户显示顶级导航,
该视图负责以应用中的不同屏幕换出宿主 fragment,其中每个屏幕都实现为单独的 fragment

示例 1 中的宿主 fragment 托管两个子 fragment,这些子 fragment 构成拆分视图屏幕。
示例 2 中的宿主 fragment 托管一个子 fragment,该子 fragment 构成滑动视图的显示 fragment。

基于此设置,您可以将每个宿主视为具有与其关联的 FragmentManager,用于管理其子 fragment。
下图说明了这一点,并显示了 supportFragmentManager、parentFragmentManager 和 childFragmentManager 之间的属性映射
每个宿主都有与其关联的 FragmentManager,用于管理其子 fragment。
需要引用的相应 FragmentManager 属性取决于调用点在 fragment 层次结构中的位置,
以及您尝试访问的 fragment 管理器。对 FragmentManager 进行引用后,您就可以使用它来操纵向用户显示的 fragment。

子 fragment 的管理

一般来说,应用应由应用项目中的一个或少数几个 activity 组成,其中每个 activity 表示一组相关的屏幕。
activity 可能会提供一个点来放置顶级导航,并提供一个位置来限定 ViewModels 以及 fragment 之间的其他视图状态的范围。
应用中的每个目的地应由一个 fragment 来表示。

如果您想要一次显示多个 fragment(如在拆分视图中或信息中心内),应使用子 fragment,
它们由目的地 fragment 及其子 fragment 管理器进行管理。子 fragment 的其他用例可能包括:

1.屏幕滑动,其中父 fragment 中的 ViewPager2 管理一系列子 fragment 视图。
2.一组相关屏幕中的子导航。
3.Jetpack Navigation 将子 fragment 用作各个目的地。一个 activity 托管一个父 NavHostFragment,
    并在用户浏览应用时以不同的子目的地 fragment 填充它的空间。

执行事务

如需在布局容器中显示 fragment,请使用 FragmentManager 创建 FragmentTransaction。
在事务中,您随后可以对容器执行 add() 或 replace() 操作。

例如,一个简单的 FragmentTransaction 可能如下所示:
1
2
3
4
5
6
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
.setReorderingAllowed(true)
.addToBackStack("name") // name can be null
.commit();
在本例中,ExampleFragment 会替换当前在布局容器中的 fragment(如有),
该布局容器由 R.id.fragment_container ID 进行标识。
将 fragment 的类提供给 replace() 方法可让 FragmentManager 使用其 FragmentFactory 处理实例化

setReorderingAllowed(true) 可优化事务中涉及的 fragment 的状态变化,
以使动画和过渡正常运行。如需详细了解如何使用动画和过渡进行导航

addToBackStack() 会将事务提交到返回堆栈。用户稍后可以通过按“返回”按钮反转事务,并恢复上一个 fragment。
如果您在一个事务中添加或移除了多个 fragment,弹出返回堆栈时,所有这些操作都会撤消。
在 addToBackStack() 调用中提供的可选名称使您能够使用 popBackStack() 弹回到该特定事务

如果您在执行移除 fragment 的事务时未调用 addToBackStack(),则提交事务时会销毁已移除的 fragment,
用户无法返回到该 fragment。如果您在移除某个 fragment 时调用了 addToBackStack(),
则该 fragment 只会 STOPPED,稍后当用户返回时它会 RESUMED。请注意,在这种情况下,其视图会被销毁

查找现有 fragment

可以通过 findFragmentById() 或者 findFragmentByTag() 获取对布局容器中当前 fragment 的引用
findFragmentById()
可使用 findFragmentById() 按给定的 ID 查找 fragment;
在 FragmentTransaction 中添加时,可使用它按容器 ID 进行查找
1
2
3
4
5
6
7
8
9
10
11
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
.setReorderingAllowed(true)
.addToBackStack(null)
.commit();

...

ExampleFragment fragment =
(ExampleFragment) fragmentManager.findFragmentById(R.id.fragment_container);
findFragmentByTag()
可以为 fragment 分配一个唯一的标记,并使用 findFragmentByTag() 获取引用。
您可以在布局中定义的 fragment 上使用 android:tag XML 属性来分配标记,
也可以在 FragmentTransaction 中的 add() 或 replace() 操作期间分配标记
1
2
3
4
5
6
7
8
9
10
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null, "tag")
.setReorderingAllowed(true)
.addToBackStack(null)
.commit();

...

ExampleFragment fragment = (ExampleFragment) fragmentManager.findFragmentByTag("tag");

有关子 fragment 和同级 fragment 的特殊注意事项

1.在任何给定的时间点,只允许一个 FragmentManager 控制 fragment 返回堆栈。

2.如果应用在屏幕上同时显示多个同级 fragment,或者应用使用子 fragment,
  则必须指定一个 FragmentManager 来处理应用的主要导航。  

3.如需在 fragment 事务内定义主要导航 fragment,请对事务调用 setPrimaryNavigationFragment() 方法,
  并传入一个 fragment 的实例,该 fragment 的 childFragmentManager 应具有主要控制权。

4.将导航结构视为一系列层,其中 activity 作为最外层,封装下面的每一层子 fragment。
  每一层都必须有一个主要导航 fragment。当发生返回事件时,最内层控制导航行为。
  一旦最内层再也没有可从其弹回的 fragment 事务,控制权就会向外回一层,此过程会一直重复,直至到达 activity 为止。

5.当同时显示两个或更多 fragment 时,其中只有一个可以是主要导航 fragment。

6.如果将某个 fragment 设为主要导航 fragment,会移除对先前 fragment 的指定。在上例中,
  如果您将详情 fragment 设为主要导航 fragment,就会移除对主 fragment 的指定。

支持多个返回堆栈

在某些情况下,您的应用可能需要支持多个返回堆栈。一个常见示例是,您的应用使用底部导航栏。
FragmentManager 可让您通过 saveBackStack() 和 restoreBackStack() 方法支持多个返回堆栈。
这两种方法使您可以通过保存一个返回堆栈并恢复另一个返回堆栈来在返回堆栈之间进行交换。

saveBackStack() 的工作方式类似于使用可选 name 参数调用 popBackStack():
弹出指定事务以及堆栈上在此之后的所有事务。不同之处在于 saveBackStack() 
会保存弹出事务中所有 fragment 的状态。

例如,假设您之前使用 addToBackStack() 提交 FragmentTransaction,从而将 fragment 添加到返回堆栈
1
2
3
4
5
6
7
supportFragmentManager.beginTransaction()
.replace(R.id.fragment_container, ExampleFragment.class, null)
// setReorderingAllowed(true) and the optional string argument for
// addToBackStack() are both required if you want to use saveBackStack().
.setReorderingAllowed(true)
.addToBackStack("replacement")
.commit();
在这种情况下,您可以通过调用 saveState() 来保存此 fragment 事务和 ExampleFragment 的状态:
1
supportFragmentManager.saveBackStack("replacement");
注意:您只能将 saveBackStack() 用于调用 setReorderingAllowed(true) 的事务,
         以确保可以将事务还原为单一原子操作
您可以使用相同的名称参数调用 restoreBackStack(),以恢复所有弹出的事务以及所有保存的 fragment 状态
1
supportFragmentManager.restoreBackStack("replacement");
注意:除非使用 addToBackStack() 传递 fragment 事务的可选名称,
否则不能使用 saveBackStack() 和 restoreBackStack()

为 fragment 提供依赖项

添加 fragment 时,您可以手动实例化 fragment 并将其添加到 FragmentTransaction
1
2
3
4
5
ExampleFragment myFragment = new ExampleFragment();
fragmentManager.beginTransaction()
.add(R.id.fragment_view_container, myFragment)
.setReorderingAllowed(true)
.commit();
当您提交 fragment 事务时,您创建的 fragment 实例就是使用的实例。不过,在配置更改期间,
activity 及其所有 fragment 都会被销毁,然后使用最适用的 Android 资源重新创建。
FragmentManager 会为您处理所有这些操作。
它会重新创建 fragment 的实例,将其附加到宿主,并重新创建返回堆栈状态。

默认情况下,FragmentManager 会使用框架提供的 FragmentFactory 来实例化 fragment 的新实例。
此默认工厂使用反射来查找和调用 fragment 的无参数构造函数。
这意味着,您无法使用此默认工厂为 fragment 提供依赖项。
这也意味着,默认情况下,在重新创建过程中,不会使用您首次创建 fragment 时所用的任何自定义构造函数。

如需为 fragment 提供依赖项或使用任何自定义构造函数,
您必须创建自定义 FragmentFactory 子类,然后替换 FragmentFactory.instantiate。
您随后可以将 FragmentManager 的默认工厂替换为您的自定义工厂,该自定义工厂随后将用于实例化 fragment

假设您有一个 DessertsFragment,负责显示您家乡受欢迎的甜点。
我们假设 DessertsFragment 依赖于 DessertsRepository 类,该类可为其提供向用户显示正确界面所需的信息。

您可以将 DessertsFragment 定义为在其构造函数中需要 DessertsRepository 实例
1
2
3
4
5
6
7
8
9
10
11
12
public class DessertsFragment extends Fragment {
private DessertsRepository dessertsRepository;

public DessertsFragment(DessertsRepository dessertsRepository) {
super();
this.dessertsRepository = dessertsRepository;
}

// Getter omitted.

...
}
FragmentFactory 的简单实现可能与以下代码类似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class MyFragmentFactory extends FragmentFactory {
private DessertsRepository repository;

public MyFragmentFactory(DessertsRepository repository) {
super();
this.repository = repository;
}

@NonNull
@Override
public Fragment instantiate(@NonNull ClassLoader classLoader, @NonNull String className) {
Class<? extends Fragment> fragmentClass = loadFragmentClass(classLoader, className);
if (fragmentClass == DessertsFragment.class) {
return new DessertsFragment(repository);
} else {
return super.instantiate(classLoader, className);
}
}
}
此示例创建了 FragmentFactory 的子类,替换了 instantiate() 方法,
以便为 DessertsFragment 提供自定义 fragment 创建逻辑。
其他 fragment 类通过 super.instantiate() 由 FragmentFactory 的默认行为进行处理。

您随后可以通过在 FragmentManager 上设置一个属性,
将 MyFragmentFactory 指定为要在构造应用的 fragment 时使用的工厂。
您必须在 activity 的 super.onCreate() 之前设置此属性,
以确保在重新创建 fragment 时使用 MyFragmentFactory
1
2
3
4
5
6
7
8
public class MealActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
DessertsRepository repository = DessertsRepository.getInstance();
getSupportFragmentManager().setFragmentFactory(new MyFragmentFactory(repository));
super.onCreate(savedInstanceState);
}
}
请注意,在 activity 中设置 FragmentFactory 会替换整个 activity 的 fragment 层次结构中
 fragment 的创建。换句话说,您添加的任何子 fragment 的 childFragmentManager 
 都会使用此处设置的自定义 fragment 工厂,除非在较低的级别被替换

概要

1.Fragment 英文原意是“片断”或“碎片”,在 Android 系统中代表用户界面的局部区域,它可以是一个活动的部分,
    可以单独设置局部用户界面,也可以单独接收用户输入和处理事件消息,与用户交互

2.Android 在 Android 3.0(API 级别 11)中引入了片段,主要目的是为大屏幕(如平板电脑)上
    更加动态和灵活的界面设计提供支持

3.Fragment 的出现是为了适应平板电脑等大屏幕设备,改变了早期因手机屏幕较小而将整个屏幕作为一个整体界面
    处理的状况,Fragment 组件可以将屏幕分成多个相对独立的区域,用户界面的更新可以在>局部区域发生,使得用户
    界面切换方式更加高效、丰富

4.每个片段都会通过各自的生命周期回调来定义自己的布局和行为,您可以将一个片段加入多个 Activity,因此,
    您应采用可复用式设计,避免直接通过某个片段操纵另一个片段

创建片段

如要创建片段,您必须创建 Fragment 的子类(或已有其子类)。Fragment 类的代码与 Activity 非常相似。
它包含与 Activity 类似的回调方法,如 onCreate()、onStart()、onPause() 和 onStop()。实际上,
如果您要将现有 Android 应用转换为使用片段,可能只需将代码从 Activity 的回调方法移入片段相应的回调方法中。

片段通常用作 Activity 界面的一部分,并且会将其自己的布局融入 Activity。
如要为片段提供布局,您必须实现 onCreateView() 回调方法,Android 系统会在片段需要绘制其布局时调用该方法。
此方法的实现所返回的 View 必须是片段布局的根视图。

如要从 onCreateView() 返回布局,您可以通过 XML 中定义的布局资源来扩展布局。为帮助您执行此操作,
onCreateView() 提供了一个 LayoutInflater 对象。

例:
1
2
3
4
5
6
7
8
public static class ExampleFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
传递至 onCreateView() 的 container 参数是您的片段布局将插入到的父级 ViewGroup(来自 Activity 的布局)。
savedInstanceState 参数是在恢复片段时,提供上一片段实例相关数据的 Bundle

inflate() 方法带有三个参数:
1.您想要扩展的布局的资源 ID。
2.将作为扩展布局父项的 ViewGroup。传递 container 对系统向扩展布局的根视图(由其所属的父视图指定)
    应用布局参数具有重要意义。
3.指示是否应在扩展期间将扩展布局附加至 ViewGroup(第二个参数)的布尔值。(在本例中,此值为 false,
    因为系统已将扩展布局插入 container,而传递 true 值会在最终布局中创建一个多余的视图组。)

添加片段

通常,片段会向宿主 Activity 贡献一部分界面,作为 Activity 整体视图层次结构的一部分嵌入到 Activity 中。
可以通过两种方式向 Activity 布局添加片段:
1.在 Activity 的布局文件内声明片段
例如,以下是拥有两个片段的 Activity 的布局文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:name="com.example.news.ArticleListFragment"
android:id="@+id/list"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="match_parent" />
<fragment android:name="com.example.news.ArticleReaderFragment"
android:id="@+id/viewer"
android:layout_weight="2"
android:layout_width="0dp"
android:layout_height="match_parent" />
</LinearLayout>
 中的 android:name 属性指定要在布局中进行实例化的 Fragment 类。创建此 Activity 布局时,
系统会将布局中指定的每个片段实例化,并为每个片段调用 onCreateView() 方法,以检索每个片段的布局。
系统会直接插入片段返回的 View,从而代替  元素。
2.通过编程方式将片段添加到某个现有 ViewGroup
在 Activity 运行期间,您可以随时将片段添加到 Activity 布局中。您只需指定要将片段放入哪个 ViewGroup。
如要在您的 Activity 中执行片段事务(如添加、移除或替换片段),则必须使用 FragmentTransaction 中的 API。
如下所示,您可以从 FragmentActivity 获取一个 FragmentTransaction 实例
1
2
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
然后,您可以使用 add() 方法添加一个片段,指定要添加的片段以及将其插入哪个视图。例如:
1
2
3
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
传递到 add() 的第一个参数是 ViewGroup,即应放置片段的位置,由资源 ID 指定,第二个参数是要添加的片段。
一旦您通过 FragmentTransaction 做出了更改,就必须调用 commit() 以使更改生效。

生命周期

Fragment生命周期图示如下,与Activity有所不同

Fragment.jpg

Activity.jpg
下面来分析生命周期
onAttach()
onAttach()回调将在Fragment与其Activity关联之后调用。
需要使用Activity的引用或者使用Activity作为其他操作的上下文,将在此回调方法中实现
onCreate(Bundle savedInstanceState)
此时的Fragment的onCreat回调时,该fragmet还没有获得Activity的onCreate()已完成的通知,
所以不能将依赖于Activity视图层次结构存在性的代码放入此回调方法中。在onCreate()回调方法中,
我们应该尽量避免耗时操作。此时的bundle就可以获取到activity传来的参数
onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState)
其中的Bundle为状态包与上面的bundle不一样。
注意的是:不要将视图层次结构附加到传入的ViewGroup父元素中,该关联会自动完成。
如果在此回调中将碎片的视图层次结构附加到父元素,很可能会出现异常。
这句话什么意思呢?就是不要把初始化的view视图主动添加到container里面,
因为这会系统自带,所以inflate函数的第三个参数必须填false,而且不能出现container.addView(v)的操作。
1
View v = inflater.inflate(R.layout.hello_world, container, false);
onActivityCreated()
onActivityCreated()回调会在Activity完成其onCreate()回调之后调用。
在调用onActivityCreated()之前,Activity的视图层次结构已经准备好了,
这是在用户看到用户界面之前你可对用户界面执行的最后调整的地方。

Note:如果Activity和它的Fragment是从保存的状态重新创建的,此回调尤其重要,
也可以在这里确保此Activity的其他所有Fragment已经附加到该Activity中了
onStart()\onResume()\onPause()\onStop()
这四种生命周期和Activity的回调方法进行绑定,也就是说与Activity中对应的生命周期相同,因此不做过多介绍
onDestroyView()
该回调方法在视图层次结构与Fragment分离之后调用
onDestroy()
不再使用Fragment时调用。(备注:Fragment仍然附加到Activity并任然可以找到,但是不能执行其他操作)
onDetach()
Fragme生命周期最后回调函数,调用后,Fragment不再与Activity绑定,释放资源。
setRetainInstance()
在使用Fragment时,我发现了一个金矿,那就是setRetainInstance()方法,此方法可以有效地提高系统的运行效率,
对流畅性要求较高的应用可以适当采用此方法进行设置。Fragment有一个非常强大的功能——就是可以在Activity重新创建时
可以不完全销毁Fragment,以便Fragment可以恢复。在onCreate()方法中调用setRetainInstance(true/false)方法是最佳位置。
当Fragment恢复时的生命周期如上图所示,注意图中的红色箭头。当在onCreate()方法中调用了setRetainInstance(true)后,
Fragment恢复时会跳过onCreate()和onDestroy()方法,因此不能在onCreate()中放置一些初始化逻辑,切忌!

概要

1.Activity是Android组件中最基本也是最为常见用的四大组件(Activity,Service,Content Provider,BroadcastReceiver)之一 

2.Activity是一个应用程序组件,提供一个屏幕,用户可以用来交互为了完成某项任务

3.Activity中所有操作都与用户密切相关,是一个负责与用户交互的组件,可以通过setContentView(View)来显示指定控件

4.在一个android应用中,一个Activity通常就是一个单独的屏幕,它上面可以显示一些控件也可以监听并处理用户的事件做出响应。Activity之间通过Intent进行通信

状态

在android 中,Activity 拥有四种基本状态:
1.Active/Running
一个新 Activity 启动入栈后,它显示在屏幕最前端,处理是处于栈的最顶端(Activity栈顶)
此时它处于可见并可和用户交互的激活状态,叫做活动状态或者运行状态(active or running)
2. Paused
当 Activity失去焦点, 被一个新的非全屏的Activity 或者一个透明的Activity 被放置在栈顶
此时的状态叫做暂停状态(Paused)。此时它依然与窗口管理器保持连接,
Activity依然保持活力(保持所有的状态,成员信息,和窗口管理器保持连接)
但是在系统内存极端低下的时候将被强行终止掉。所以它仍然可见,但已经失去了焦点故不可与用户进行交互
3. Stopped
如果一个Activity被另外的Activity完全覆盖掉,叫做停止状态(Stopped)
它依然保持所有状态和成员信息,但是它不再可见,所以它的窗口被隐藏
当系统内存需要被用在其他地方的时候,Stopped的Activity将被强行终止掉
4. Killed
如果一个Activity是Paused或者Stopped状态,系统可以将该Activity从内存中删除
Android系统采用两种方式进行删除,要么要求该Activity结束,要么直接终止它的进程
当该Activity再次显示给用户时,它必须重新开始和重置前面的状态

状态转换

Activity在四种基本状态之间的转换如下图所示:
如上所示,Android 程序员可以决定一个 Activity 的“生”,但不能决定它的“死”,
也就是说程序员可以启动一个 Activity,但是却不能手动的“结束”一个 Activity。
当你调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:
告诉 Activity Manager 该 Activity 实例完成了相应的工作,可以被“回收”。
随后 Activity Manager 激活处于栈第二层的 Activity 并重新入栈,
同时原 Activity 被压入到栈的第二层,从 Active 状态转到 Paused 状态。
例如:从 Activity1 中启动了 Activity2,则当前处于栈顶端的是 Activity2,
第二层是 Activity1,当我们调用 Activity2.finish()方法时,
Activity Manager 重新激活 Activity1 并入栈,
Activity2 从 Active 状态转换 Stoped 状态,
Activity1. onActivityResult(int requestCode, int resultCode, Intent data)方法被执行,
Activity2 返回的数据通过data参数返回给 Activity1

Activity栈

Android 是通过一种 Activity 栈的方式来管理 Activity 的,一个 Activity 的实例的状态决定它在栈中的位置。
处于前台的 Activity 总是在栈的顶端,当前台的 Activity 因为异常或其它原因被销毁时,
处于栈第二层的 Activity 将被激活,上浮到栈顶。当新的 Activity 启动入栈时,
原 Activity 会被压入到栈的第二层。一个 Activity 在栈中的位置变化反映了它在不同状态间的转换。
Activity 的状态与它在栈中的位置关系如下图所示:
如上所示,除了最顶层即处在 Active 状态的 Activity 外,其它的 Activity 都有可能在系统内存不足时被回收,
一个 Activity 的实例越是处在栈的底层,它被系统回收的可能性越大。系统负责管理栈中 Activity 的实例,
它根据 Activity 所处的状态来改变其在栈中的位置

Activity 生命周期

下图示展示了从  Activity1创建 -> 打开Activity2 -> Activity2返回Activity1 过程
Activity1 和 Activity2 的生命周期
Activity1Activity2
创建打开Activity1onCreate()
onStart()
onResume()
Activity1打开Activity2onPause()
onCreate()
onStart()
onResume()
onStop()
Activity2返回Activity1onPause()
onStart()
onResume()
onStop()
onDestroy()

Activity 启动模式

Activity总共有四大启动模式:
1.标准模式(standard)
是Activity默认的启动模式,每当启动一个Activity时,就会在返回栈栈顶创建一个Activity的实例,
不论这个Activity在返回栈中是否有实例。我们在开发中大多都用这种模式
2.栈顶复用(singleTop)
如果这个Activity已经存在于返回栈的栈顶,那么当重新打开这个Activity时,并不会重新创建它的实例,
而是去回调onNewIntent方法,接着执行Activity的onRestart->onStart->onResume

这两种启动模式都不涉及两个App之间调用对方Activity的情况。即使打开了另一个App的Activity,
也还是在当前App的Task里,返回栈还是当前Task的返回栈。如果多个App同时打开了一个App中的Activity,
他们是不会相互影响的
3.栈内复用(singleTask)
每次启动Activity时,都会在返回栈内检查是否有该Activity的实例,如果发现有则直接使用该实例,
并把在这个Activity之上的所有Activity统统出栈。如果没有,就会新创建一个Activity
4.全局唯一模式(singleInstance)
这是我们最后的一种启动模式,也是我们最恶心的一种模式:在该模式下,我们会为目标Activity分配一个新的affinity,
并创建一个新的Task栈,将目标Activity放入新的Task,并让目标Activity获得焦点。新的Task有且只有这一个Activity实例。       
如果已经创建过目标Activity实例,则不会创建新的Task,而是将以前创建过的Activity唤醒(对应Task设为Foreground状态)

Activity 状态保存

Activity状态保存如下图所示:
这张图非常重要,可以帮我们解决异常情况下activity如何正常回复的问题,当系统停止activity时,
它会调用onSaveInstanceState()(过程1),如果activity被销毁了,但是需要创建同样的实例,
系统会把过程1中的状态数据传给onCreate()和onRestoreInstanceState(),
所以我们要在onSaveInstanceState()内做保存参数的动作,在onRestoreInstanceState()做获取参数的动作。