Vim is a powerful and versatile text editor that offers a wide range of features and customization options. In this article, we will explore some useful tips and techniques from the book “Modern Vim” to help you become more efficient and productive in your Vim editing.

img

Introduction

Vim has a unique set of keyboard shortcuts and commands that can enhance your editing experience. Here are some key concepts and shortcuts to keep in mind:

  • <C-p> represents pressing the Control key followed by the letter “p.”

  • Operators are commands used to perform actions on text. Some common operators include:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    `c` for change
    `d` for delete
    `y` for yank (copy)
    `~` for swapping case
    `gu` for making text lowercase
    `gU` for making text uppercase
    `!` for filtering text through an external program
    `=` for text formatting
    `gq` for text formatting with line wrapping
    `>` for shifting text right
    `<` for shifting text left
    `zf` for defining a fold
    `g@` for calling a function set with the 'operatorfunc' option
  • The Meta key:

    • On macOS, it refers to the Option key.
    • On Windows, it refers to the Alt key.

Getting Modern Vim

To get the most out of Vim, it’s recommended to use Neovim and the neovim-remote tool developed by Marco Hinz. This tool allows remote control of Neovim processes.

Installing Plugins

Plugins are a great way to extend Vim’s functionality. Here are some insights on managing plugins:

  • Understanding Scripts, Plugins, and Packages:

    • You can manually load a script using the :source {path} command, where {path} is the location of the script.
    • Vim automatically sources scripts located in specific locations on disk when it starts up.
    • Your vimrc file is one of the first scripts to be loaded, making it an ideal place to configure your startup settings.
    • Prior to recent versions of Vim, managing the runtimepath to include plugins was not convenient. However, you can now use the :set runtimepath+=$VIMCONFIG/arbitrary/demo-plugin command to add a plugin to the runtimepath.
    • Pressing <C-]> will jump to the specified anchor in Vim’s documentation, and you can use <C-o> to quickly jump back to the previous location. These commands allow you to navigate Vim’s documentation similar to interacting with a web page.
    • After installing a new plugin, you only need to run :helptags once. Vim will then use the generated tags file to find the documentation for that plugin.
  • Installing Plugins to Your Package:

    • Note that if you install a new plugin into the start directory while Vim is running, you won’t be able to use it immediately. Restarting Vim will add the new plugin to the runtimepath and make it available.
    • The unimpaired plugin comes with documentation, but Vim doesn’t know where to find the appropriate files. You can fix this issue by running the :helptags ALL command (:help :helptags).
    • You can suppress error messages by running :silent helptags ALL.
    • By default, optional plugins are not loaded. Use the :packadd command to activate a plugin (e.g., :packadd vim-scriptease).
  • Managing Plugins with minpac:

    • Typing :call minpac#update() can be cumbersome. You can create custom commands to make it more convenient:
      • command! PackUpdate call minpac#update()
      • command! PackClean call minpac#clean()

Opening Files

Efficiently opening and navigating files is crucial for an effective editing workflow. Let’s explore two techniques:

  • Finding Files Using Fuzzy Path Matching:

    • You can use <C-x>, <C-v>, or <C-t> to open a file in a horizontal split, vertical split, or new tab, respectively.
    • The rg --files command (Ripgrep) filters out files ignored by Git, Mercurial, and Subversion repositories.
  • Finding Files Semantically:

    • Open files in separate windows using the -O flag. For example:

      • vim -O file1.txt file2.txt
    • You can define file-to-type mappings in a .projections.json file. For instance:

      • "app/models/*.js": { "type": "model" }
    • Vim provides navigation commands specific to file types, such as:

      • ```
        :Etype - Opens the specified type in the current window
        :Stype - Opens the specified type in a horizontal split
        :Vtype - Opens the specified type in a vertical split
        :Ttype - Opens the specified type in a new tabpage
        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
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        		
        - These navigation commands are my preferred way to navigate codebases, and you can add navigation commands as needed for different file types.

        - Jumping to an Alternate File:
        - The Projectionist plugin allows you to create links between related files. Once you specify the relationship between a file and its alternate file, you can follow the link by running the `:A` command.

        ## Working with the Quickfix List

        The quickfix list is a powerful feature in Vim that allows you to efficiently handle build errors, linting results, and file search results. Let's explore some techniques:

        - Running a Build and Navigating Failures:
        - The Dispatch plugin, introduced in 2013, provides asynchronous command execution in Vim when it didn't natively support it. Make sure to check out the plugin's release and the "dispatch.vim" screencast.
        - Linting the Current File:
        - Learn about the Asynchronous Linting Engine (ALE), a powerful plugin for linting code in Vim.
        - You can use the `]w` and `[w` mappings to quickly navigate between warnings. Error messages are displayed at the bottom of the screen as you access each warning.
        - Neomake is another linting plugin that runs asynchronously. It supports running commands across the entire project, not just on individual files.
        - Searching Files with Grep-Alikes:
        - The `:Grepper` command provides a powerful way to search for patterns in files. For example, running `:Grepper -cword` with the word "Waldo" under the cursor will prompt you for search options.

        ## Neovim's Built-In Terminal Emulator

        Neovim comes with a built-in terminal emulator that allows you to interact with programs running in the terminal. Let's dive into some terminal-related techniques:

        - Grokking Terminal Mode:
        - Neovim introduces a new mode called Terminal mode, where you can interact with programs running in the built-in terminal emulator.
        - Use the `:terminal` command to open a terminal buffer.
        - When you create a terminal buffer, you start in normal mode. Pressing `i` switches to terminal mode, indicated by the

        `-- TERMINAL --` prompt in the bottom left corner. Press `<C-><C-n>` to switch back to normal mode.
        - Running Programs in a Terminal Buffer:
        - Use the `:read !{cmd}` command to capture the output of a command in an existing buffer.
        - The `:terminal {cmd}` command is a new feature in Neovim. It runs the specified command in a new terminal buffer. You can abbreviate it as `:te {cmd}`.
        - To switch between a terminal buffer and a regular buffer, use `<C-^>` (`:h ctrl-^`).
        - Try stopping a process in the terminal buffer using the `:bwipeout!` command (e.g., `:5bwipeout` to stop the top process).
        - When you exit Neovim, any running processes in terminal buffers are also closed.
        - Note that if you suspend Neovim (`<C-z>`), all processes running in terminal buffers will be suspended as well. They will resume when you resume Neovim.

        - Managing Windows That Contain Terminal Buffers:
        - Opening a terminal buffer with the `:terminal` command takes over the current window and hides the buffer that was previously displayed. This behavior is similar to the `:edit {file}` command.
        - If you use `:te`, it creates a buffer. However, `:te` does not create a buffer.
        - Use the `tnoremap` command to create mappings that work only in terminal mode. With these mappings, you can switch to another window by pressing `<M-h>`, `<M-j>`, `<M-k>`, or `<M-l>` regardless of whether you are in normal mode or terminal mode.

        - Using Normal Mode Commands in a Terminal Buffer:
        - You can use the `yi`` command to copy the text within backticks to Vim's unnamed register and then paste it using `p` in the terminal at the cursor position.
        - You can prepend a named register (e.g., `"a`) or use special registers like `"*` to reference the system clipboard when using yank and put commands.
        - The terminal buffer is now hidden, but you can quickly switch back to it using `<C-^>` (`:h ctrl-^`).

        - Sending Commands to a Terminal Buffer:
        - Activate the window containing the terminal buffer running the web server and run the following command:
        - `:echo b:terminal_job_id`
        - This tells us that the job ID is 1, which we can use as the first argument when calling `jobsend({job}, {data})`.
        - To restart the web server, run the command:
        - `:call jobsend(1, "\<C-c>npm run server\<CR>")`

        ## Sessions

        Sessions in Vim allow you to save and restore your editing environment. Let's explore session-related techniques:

        - Saving and Restoring Sessions:
        - After opening the `app.js` and `app-test.js` files in adjacent windows, use the `:mksession!` command (`:h :mksession`) to save the session.
        - Restart Vim with the `-S` flag to load the session:
        - `vim -S`
        - If you like the idea of automatically recording sessions, consider installing Tim Pope's Obsession plugin. You can install it in your bundle directory:
        - `cd $VIMCONFIG/pack/bundle/start`
        - `git clone https://github.com/tpope/vim-obsession.git`

        - Making Undo Persist Between Sessions:
        - By default, undo history is not preserved between sessions. However, you can use autocommands to disable the undofile for files matching specific patterns. For example, the following is an example script that disables persistent undo in all files in the `/tmp` directory:
        - `--forget-undo-in-tmpfile.vim`
        - ```
        augroup vimrc
        autocmd!
        autocmd BufWritePre /tmp/* setlocal noundofile
        augroup END
  • Restarting Terminal Processes When Resuming a Session:

    • You can rename a terminal buffer using the :file {name} command (:help :file_f). Activate the window containing the terminal buffer running the web server and run:
      • :file term://PORT=3001 npm run server

Configuring Vim

Customizing Vim’s behavior can greatly enhance your editing experience. Here’s a technique to respond to events using autocommands:

  • Using Autocommands to Respond to Events:
    • Vim triggers the BufReadPost command (:h BufReadPost) after reading a file into a buffer. If the file path matches the pattern defined in our autocommand, Vim executes the specified {command}. Since we use a wildcard * in this example, the autocommand applies to all buffers.
    • Sometimes, there might be more suitable events. For such cases, you can achieve similar results by listening to the FileType event (:h FileType).
      • autocmd BufWritePre /tmp/* setlocal noundofile
    • The autocommand we defined is triggered by the User event with the pattern ProjectionistActivate. The User event doesn’t trigger automatically, but you can trigger such events yourself:
      • :doautocmd User ProjectionistActivate

In this article, we’ve explored various tips and techniques from “Modern Vim” to help you enhance your Vim editing skills. By incorporating these techniques into your workflow, you can become a more efficient and productive Vim user.

什么是Keystone?

Keystone是一个强大的Node.js内容管理系统,它是建立在Express和Mongoose ODM上的Web App框架。Mongoose ODM是面向文档映射,为那些存储到MongoDB数据库中文档,其提供了面向基于模式的解决方案来为数据和关系建模。Keystone扩展了Mongoose基于模式模型。Keystone列表利用智能字段类型帮助你构建漂亮的Admin界面。

Keystone的目标是让你更容易的构建复杂网站和应用,而不限制你自定义功能。你可以引入你自己的视图引擎,设计你想要的路由,并且修改你的数据结构来适应你的需求。

先决条件

确保你安装好了Node.js JavaScript运行环境。Keystone 4主要是在Node 6上进行测试,但是应当是和最新正式版Node是兼容的。我们推荐使用Node LTS(长期支持)版本,因为更长的支持周期和稳定性。(译者注:LTS 版本更注重稳定性和扩展支持,通常将支持 30 个月。)

你也要可以访问一个MongoDB数据库,不管是安装在本地还是部署在远程服务器上。Keystone 4兼容最新的MongoDB正式版。

你需要有一定的JavaScript、Node.js和npm的知识来使用Keystone。了解数据库概念和MongoDB则更有帮助。

从哪里开始?

快速开始指导

如果你想要在本地快速运行一个项目,我们推荐你从Keystone Yeoman生成器开始。Keystone Yeoman生成器提供了一个提示命令行来帮助你生成完整的项目,可以通过npm来定制一些特性如基础的博客,反馈表单,相册功能。

从脚手架开始设置

如果你更喜欢从脚手架编写你的代码,产看我们第四部分的设置教程。这个教程让你从核心的文件和设置来开始Keystone。

接下来去哪?

如果你想要学习更多的Keystone设置选项,请查阅我们的文档。数据库配置是一个好的起点,同样Keystone通用设置选项也是不错的选择。

给定一组单词,返回所有字母都在同一行美国键盘上的单词。

例子:

Input: ["Hello", "Alaska", "Dad", "Peace"]
Output: ["Alaska", "Dad"]

注意:

  1. 可以使用同一个键盘上字母多次
  2. 假设输入有且只有字母

解决思路有两种。最简单的方法是利用正则表达式来判断字母是否满足正则表达式,如果满足则表示单词有效。这里面有几点需要注意的。

  1. 需要一个正确的正则表达式。
  2. 判断之前需要将单词转换成小写的。
  3. 将有效的单词收集并返回
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {    
func findWords(_ words: [String]) -> [String] {
// 利用正则表达式来判断
let pattern = "^([qwertyuiop]*|[asdfghjkl]*|[zxcvbnm]*)$"
var validateWords = [String]()
for word in words {
let lowerWord = word.lowercased()

let regex = try? NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let matches = regex?.matches(in: lowerWord, options: .reportProgress, range: NSMakeRange(0, lowerWord.characters.count))
if let m = matches, m.count > 0 {
validateWords.append(word)
}
}
return validateWords
}
}

第二种方法就麻烦一点了。

  1. 首先将单词设置成有效
  2. 找到第一个字母在键盘的那一行。
  3. 接着判断所有字母是否是在那一行,如果不在则单词无效。
  4. 返回结果
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
class Solution {
func findWords(_ words: [String]) -> [String] {
// 1. 判断单词属于num行
// 2. 标示单词有效
// 3. 如果单词不属于num行
// 4. 标示单词无效
let lines: [String] = ["qwertyuiop", "asdfghjkl", "zxcvbnm"];
var validateWords = [String]()
for word in words {
let lowerWord = word.lowercased()
validateWords.append(word)

for line in lines {
if !line.characters.contains(lowerWord[lowerWord.startIndex]) {
continue
}

for c in lowerWord.characters {
if !line.characters.contains(c) {
validateWords.popLast()
break
}
}
}
}
return validateWords
}
}

给定一个正数,输出它的“补数”。求的方法是把二进制位置的数进行取反。

注意:

  1. 给定的正数在32位内
  2. 正数前面没有补0。比如2(B10),在它的前面没有0。

例子1:

输入: 5
输出: 2
解释: 5的二进制数是101 (前面不补充0), 它的补数是010。所以输出是2。

例子2:

输入: 1
输出: 0
解释: 1的二进制是1 (前面不补充0), 它的补数是0。因此输出是0。

思路

假设输入数为input, 输出为output, 设mask的二进制位数与input相等,且每一位都为1。
这里求input有两种方法。这里你可以用上面的例子数据代进去验证一下。

  1. output = input ^ mask
  2. output = mask - input

所以这个问题的关键是如何得到mask。

还有第三种思路是这样的。每次将input分别与其对应二进制位为1的数进行异或操作,就能得到output。

1
2
3
4
5
6
7
class Solution(object):
def findComplement(self, num):
i = 1
while num >= i:
num ^= i
i <<= 1
return num

计算mask方法一:

1
2
3
4
5
6
var flipMask: UInt32 = ~0
let numUInt32 = UInt32(num)
while ((numUInt32 & flipMask) != 0) {
flipMask <<= 1
}
let mask = ~flipMask;

将全是’1’的UInt32数不断的左移,直到(numUInt32 & flipMask) == 0停止,然后再对flipMask取反就能得到要求的mask

计算mask方法二:

1
2
3
4
5
6
7
int mask = 0;
int j = 0;
while (mask < num)
{
mask += Math.pow(2, j);
j++;
}

从右往左不断的增加1的位数,直到mask >= num

计算mask方法三:

1
2
3
4
5
6
int mask = num;
mask |= mask >> 1;
mask |= mask >> 2;
mask |= mask >> 4;
mask |= mask >> 8;
mask |= mask >> 16;

每次与右移后的值进行与操作,移动的位数每次能扩大2倍。这样就能保证mask的所有二进制位都是1。

计算mask方法四:

1
mask = ((2<<int(math.log(num, 2)))-1)

利用数学函数能得到input的位数(int(math.log(num, 2))+1), 这样进1位减去1就得到了mask值。

给定两个数字,求这两个数的二进制数相应位不同的总数。

注意:
$0 \leq x, y < 2^{31}$

例子:

输入: x=1, y=4
输出: 2

解释:
1 (0 0 0 1)
4 (0 1 0 0)
^ ^
上面箭头指的位置相应两个数字的二进制位不一样,一共有2处。

##解法1:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// javascript
/**
* @param {number} x
* @param {number} y
* @return {number}
*/
var hammingDistance = function(x, y) {
let xor = x ^ y;
let count = 0;
while(xor !== 0) {
count += xor & 1;
xor = xor>>1;
}
return count;
};

首先通过异或操作得到一个值xor。
在循环里面判断有几个不一样的位置。
从最后一位开始判断,直到所有位置判断完成。

##解法2:

1
2
3
4
// javascript
var hammingDistance = function(x, y) {
return (x ^ y).toString(2).replace(/0/g, '').length;
};

第一步同解法一。
第二步转换成操作字符串的操作。
删除’0’,随后判断字符串的长度,即不同位置数。

##解法3:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//javascript
/**
* @param {number} x
* @param {number} y
* @return {number}
*/
var hammingDistance = function(x, y) {
let xor = x ^ y;
let count = 0;
while(xor) {
count++;
xor &= xor-1;
}
return count;
};

其实这个问题可以简化成找出二进制数中有几个‘1’。比如异或结果是‘1010’,其中有2个‘1’表示hamming distance是2。

这段代码while循环可能一眼看上去发现自己懵逼了。其实xor &= xor-1;这个操作是移除一个’1‘。有几个就是移除几次。如果还懵逼,可以多调试几次。

给定一组数和一个目标值,求出两个加数的位置。

比如:

1
2
3
nums = [2, 7, 11, 15], target = 9
因为nums[0] + nums[1] = 2 + 7 = 9,
返回 [0, 1]

注意返回的位置是基于0的

##方法一 O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
for (var i=0; i<nums.length; i++) {
for (var j=i+1; j<nums.length; j++) {
if ((nums[i] + nums[j]) == target) {
return [i, j];
}
}
}
};

##方法二 O(n)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* @param {number[]} nums
* @param {number} target
* @return {number[]}
*/
var twoSum = function(nums, target) {
let map = new Map();
for (var i=0; i<nums.length; i++) {
let nextNum = target - nums[i]

if (map[nextNum] !== undefined) {
let result = []
result.push(map[nextNum])
result.push(i)
return result
}

map[nums[i]] = i
}
};

##总结
方法二巧妙利用hashMap取值的时间是固定的这一特点。把复杂度从O(n^2) 降到了O(n)。

从学习iOS开发就开始接触到MVC的开发模式了。随着越来越注重用户体验以及业务越来越复杂。ViewController也越来越臃肿,同时我们的工作量也越来越大。在ViewController中混杂着业务逻辑与UI处理的逻辑。完整的测试ViewController既要对UI进行测试也需要对业务逻辑进行测试。测试起来比较麻烦。

物极必反 器满则倾

接下来进入正题,看看MVVM设计模式是如何解决这些问题的。

MVVM = Model + View + ViewModel

看上去和MVC差不多,就是把ViewController替换成了ViewModel。如果是这样的话,MVVM压根就没有存在的必要了。所以并不是进行了简单的替换。MVVM由3部分组成:Model , View , ViewModel。

  • Model: 作为数据的容器
  • View: 负责界面的展示以及用户交互的处理
  • ViewModel: 负责业务逻辑处理

##iOS中的MVVM

然并卵iOS中我们没有办法绕过UIViewController。诸如UITabController, UINavigationController是非常非常常用的。没有这些容器,我们写界面时的情景简直不敢想象。IB与UIViewController的结合比较紧密。如果你通过IB来画界面的话,更离不开UIViewController。那么iOS中的MVVM是什么样的?

图:捂脸

  • Model: 作为数据的容器
  • View: 负责界面的展示以及用户交互的处理
  • ViewController: 用代码创建视图;胶水代码,连接View和ViewModel
  • ViewModel: 负责业务逻辑处理

Model,View,ViewModel的职责都没有改变。iOS的MVVM开发模式中ViewController只做与视图有关的操作,以及连接View与ViewModel的胶水代码。也就是说View部分其实是由原本的View+部分ViewController组成。

一旦我们这样组织代码,首先进行业务逻辑测试的时候我们只需要测试ViewModel就可以。测试UI只需要测试View+ViewController。对于UI的自动化测试我一直没有找到什么特别好的办法,可能主要靠手工和一些第三方的测试平台吧。其次我们在开发过程中可以进行并行开发了,即可以同时开发界面和业务逻辑。即使你是一个人开发App,那么每一次只做一件事情同样会让你的头脑更清楚。

图:开心

##胶水代码

iOS中没有胶水代码的MVVM是不完整的

胶水代码都做了哪些事情?

  1. 将控件的事件传递给ViewModel,使得ViewModel其有机会处理用户交互。
  2. 将ViewModel的内容与View进行绑定,使得View有机会显示正确的内容。

在ReactiveCocoa出来之前,胶水代码很难写的很优雅。这也是为什么以前iOS中MVVM并不怎么火的原因。关于ReactiveCocoa的学习与使用本文不会涉及。如果有需要可以自行搜索学习。它是响应式开发的利器。

iOS的MVVM开发模式不能没有”ReactiveCocoa”

没有胶水代码,你不能将业务逻辑从ViewController中抽离。

也谈谈MVVM(二)将会进行实战演练。结合一个简单的Demo进行讲解MVVM开发模式。敬请期待!另外欢迎大家能和我一起讨论交流,一起进步。

未完待续…

图:三只企鹅

  1. 是什么
  2. 能做什么
  3. 配置步骤
    devloper 配置appid
    iTunesConnect创建app
    iTunesConnect创建内购商品 注意是产品id,不是appid
  4. 开发步骤
    SKProductsRequest
    paymentQueue:updatedTransactions: 事务处理,完成之后调用finish
  5. 开发进阶/事务
  6. 开发进阶/防破解

我们公司在南京招聘iOS的时候,人真难找啊。而我也一直有想开设一个培训班的想法。乘着现在不想写代码。写一篇iOS学习路线图的文章。同时也决定出一系列的学习教程。

iOS开发的人相对于以前来说已经慢慢多起来了。但是不管从质量还是数量上来说都明显感觉不够。大学里面应该很少有iOS开发课程的。iOS开发前期投入比较高——你得先买一台苹果电脑才能开始学习iOS开发。这些限制使得iOS程序员数量跟不上企业的需求。由于物以稀为贵,这也使得iOS程序员的工资普遍也还不错。

言归正传,上学的时候我看了不少学习方法的书。其中无非就是以下几点:

  1. 学习新知识
  2. 总结新知识
  3. 重复的练习
  4. 融汇总结知识

这个系列的教材我将按照这些学习规律进行设计。本系类教材内容主要是帮助零基础的同学进入iOS开发的领域,并能通过它找到一份能让你养家糊口的工作。教材的前期会快速的带大家进入iOS开发。以便在你学习热情还没有消散的情况下让你快速入门。后面会逐渐补充上对大家以后有帮助的知识。换句话说,前面部分是让你会走,而后面部分是让你走的更远。同时在大家能做项目的时候,我也会附上我工作中的一些经历与感受。希望能让大家在工作上少走点弯路。

啰嗦了这么多,接下来看看教程的列表。

阶段一:学习语言

零基础的人首先可以快速的学习C语言,从而了解什么是编程语言。如何使用编程语言与计算机进行交流,并指导其完成机械的任务。学习完这一阶段之后我们可以使用编程语言来完成一些简单的任务。

  1. C语言
  2. Swift语言
    阅读全文 »

iOS App在打开的时候一般会显示一张启动图片。这样用户会觉得程序打开速度和响应速度很快。启动图片只有一个用途就是让用户觉得程序响应很快,除此之外没有其他作用。

启动图片的内容

启动图片能帮助我们提高App的用户体验,接下来我们看看启动图片应该是什么样子的。
在启动图片中,我们不应该用来提供这些内容:

  1. 一些进入的效果,如一个被溅射的屏幕
  2. 关于窗口
  3. 一些打上烙印的元素。(除非这些元素出现在即将出现的第一个程序界面。)
阅读全文 »
0%