Qeuroal's Blog

静幽正治

简单介绍

  • HTML + CSS + JavaScript
  • 结构 + 表现 + 交互

如何学习

  1. css 是什么
  2. css怎么用(快速入门)
  3. css选择器(重点+难点)
  4. 美化网页(文字、阴影、超链接、列表、渐变……)
  5. 盒子模型
  6. 浮动
  7. 定位
  8. 网页动画(特效效果),教程:菜鸟教程

什么是css

  • cssCascading Style Sheet层叠(级联)样式表

  • css:表现(美化网页):字体、颜色、边距、高度、宽度、背景图片、网页定位、网页浮动……

发展史

  • css1.0
  • css2.0DIV(块)+ CSS,提出了htmlcss 结构分离的思想,网页变得简单,利于 SEO
  • css2.1:浮动和定位
  • css3.0:圆角、阴影、动画……(浏览器兼容性)

练习格式

快速入门

style

  • 基本入门

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>

    <!--规范,<style>可以编写css代码,每一个声明最好使用分号结尾
    语法:
    选择器 {
    声明1;
    声明2;
    声明3;
    }
    -->
    <style>
    h1 {
    color: rebeccapurple;
    }
    </style>

    </head>
    <body>
    <h1>我是标题</h1>
    </body>
    </html>
  • 建议使用这种方式

css优势

  • 内容和表现分离
  • 网页结构表现统一,可以实现复用
  • 样式十分丰富
  • 建议使用独立于 HTMLCSS 文件
  • 利于 SEO,容易被搜索引擎收录

css的3种导入方式

行内样式

1
2
<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1 style="color:aqua;">我是标题</h1>

内部样式:style标签

外部样式(推荐)

优先级:

就近原则:最近的是:行内样式,然后看内部样式和外部样式谁在上面

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*注释*/
h1 {
color: green;
}
</style>

<!--外部样式-->
<link rel="stylesheet" href="css/style.css">
</head>
<body>

<!--行内样式:在标签元素中,编写一个style属性,编写样式即可-->
<h1>我是标题</h1>
</body>
</html>

拓展:外部样式两种写法

  • 链接式:html(推荐)

    1
    <link rel="stylesheet" href="css/style.css">
  • 导入式:css2.1css2.1 特有的

    1
    2
    3
    <style>
    @import url("css/style.css");
    </style>
  • 缺点:导入式会先显示结构,再去渲染;链接式是一起生效

选择器

作用:选择页面上的某一个或者某一类元素

基本选择器

标签选择器

选择一类标签

1
2
3
4
5
6
7
8
9
10
/*标签选择器,会选择到页面上所有的这个标签的元素*/
h1 {
color: red;
background: antiquewhite;
border-radius: 24px;
}
p {
font-size: 80px;

}

类选择器:class

  • 语法

    1
    2
    3
    4
    类选择器的格式:
    .类的名称 {
    样式
    }
  • 好处

    好处:可以多个标签归类,是同一个class,可以复用;可以跨标签

  • 示例

    1
    2
    3
    4
    5
    6
    7
    */
    .title1 {
    color: red;
    }
    .title2 {
    color: blueviolet;
    }

ID选择器

  • ID 一定要唯一

  • 语法

    1
    2
    3
    4
    5
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    /*
    id选择器:id必须保证全局唯一
    格式:
    #id名称 {

    }
    */
    #title1 {
    color: blueviolet;
    }

    .title2 {
    color: red;
    }

    h1 {
    color: green;
    }
    </style>
    </head>
    <body>
    <h1 id="title1" class="title2">标题1</h1>
    <h1 class="title2">标题2</h1>
    <h1 class="title3">标题3</h1>
    <h1>标题4</h1>
    <h1>标题5</h1>
    </body>
    </html>

优先级:

不遵循就近原则,是固定的:

id选择器 > class选择器 > 标签选择器

层次选择器

模型

后代选择器:空格

在某个元素的后面,如:祖爷爷->爷爷->爸爸->你

1
2
3
4
/* 后代选择器*/
body p {
background: red;
}

子选择器: >

只有一代,即儿子

1
2
3
4
/*子选择器*/
body>p {
background: blueviolet;
}

相邻兄弟选择器:+

同辈:哥哥,姐姐

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*相邻兄弟选择器:只有一个,相邻:对下*/
.activate + p {
background: aqua;
}
</style>
</head>
<body>
<p>p0</p>
<p class="activate">p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>
<p>p4</p>
</li>
<li>
<p>p5</p>
</li>
<li>
<p>p6</p>
</li>
</ul>

<p class="activate">p7</p>
<p>p8</p>
</body>
</html>

通用选择器:~

1
2
3
4
/*通用兄弟选择器:当前选中元素的向下的所有兄弟元素*/
.activate ~ p {
background: green;
}

结构伪类选择器

伪类选择器:xx:xxx {}

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--避免使用class和id选择器-->
<style>
/*1. ul的第一个子元素*/
ul li:first-child {
background: red;
}
/*2. ul的最后一个子元素*/
ul li:last-child {
background: green;
}
/*3. 选中p1:定位到父元素,选择当前的第一个元素*/
/*选择当前p元素的父级元素,选中父级元素的第一个元素,并且是当前元素才生效*/
p:nth-child(1) {
background: aqua;
}
/*选择当前p元素的父级元素,选中父级元素类型是当前元素的第2个元素*/
p:nth-of-type(2) {
background: blueviolet;
}
</style>
</head>
<body>
<h1>h1</h1>
<p>p1</p>
<p>p2</p>
<p>p3</p>
<ul>
<li>li1</li>
<li>li2</li>
<li>li3</li>
</ul>

</body>
</html>

属性选择器(常用)

idclass 结合

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
.demo a {
float: left;
display: block;
height: 50px;
width: 50px;
border-radius: 10px;
background: blue;
text-align: center;
color: white;
text-decoration: none;
margin-right: 5px;
font: blod 20px/50px Arial;
line-height: 50px;
}


/*存在id属性的元素
格式:
a[属性名、属性名=属性值(值可以用正则)、属性名*=属性值(值可以用正则)] {}
=绝对等于
*= 包含这个值
^= 以这个值开头
$= 以这个结尾
*/
a[id] {
background: black;
}
/*id=first的元素*/
a[id="first"] {
background: red;
}
/*class有links的元素*/
a[class*="links"] {
background: yellow;
}
/*选中href中以http开头的元素*/
a[href^="http"] {
background: blueviolet;
}
a[href$="pdf"] {
background: rebeccapurple;
}
</style>
</head>
<body>

<p class="demo">
<a href="http://www.baidu.com" class="links item first" id="first">1</a>
<a href="https://qeuroal.top" class="links item activate" target="_blank" title="test">2</a>
<a href="images/123.html" class="links item">3</a>
<a href="images123.png" class="links item">4</a>
<a href="images123.png" class="links item">5</a>
<a href="abc">6</a>
<a href="/a.pdf">7</a>
<a href="/abc.pdf">8</a>
<a href="/abc.doc">9</a>
<a href="/abcd.doc" class="links item last">10</a>
</p>

</body>
</html>

美化网页元素

为什么要美化网页

  • 有效的传递页面信息
  • 美化网页,页面漂亮才能吸引用户
  • 凸显页面的主题
  • 提高用户的体验

span标签

重点要突出的字,使用 span 套起来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#title1 {
font-size: 50px;
}
</style>
</head>
<body>
欢迎学习 <span id="title1">css</span>
</body>
</html>

字体样式

  • px:像素

  • em:缩进

  • 分开写:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    <!--
    font-family 字体
    font-size 字体大小
    font-weight 字体粗细
    color 字体颜色
    -->
    <style>
    body {
    color: #a13d30;
    font-family: "Consolas", 楷体;
    }
    h1 {
    font-size: 50px;
    }
    .p1 {
    font-weight: bold;
    }
    </style>
  • 合并写

    1
    2
    3
    4
    5
    6
    <!--字体风格-->
    <style>
    p {
    font: oblique bolder 18px "楷体" ;
    }
    </style>

文本样式

颜色

color

  • 单词
  • rgb0~F
  • rgba:透明度, 0~1

文本对齐方式

text-align=center排版:居中、左、右

首行缩进

text-indent: 段落首行缩进,一般是2em(首行缩进2字符)

块高

height 块高

行高

line-height:单行文字上下居中line-height=height

装饰

text-decoration

文本图片水平对齐

vertical-align: middle;

示例

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
75
76
77
78
79
80
81
82
83
84
85
86
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--
颜色:
单词
RGB:0~F
RGBA:透明度, 0~1

text-align 排版:居中、左、右
text-indent 段落首行缩进,一般是2em(首行缩进2字符)
height 块高
line-height 行高;和块的高度一致,就可以上下居中
-->
<style>
h1 {
/*color: rgba(0, 255, 255, 0.5);*/
color: rgb(0, 255, 255);
text-align: center;
}

.p1 {
text-indent: 2em;
}
.p3 {
background: red;
height: 300px;
line-height: 300px;
}

/*下划线*/
.l1 {
text-decoration: underline;
}
/*中划线*/
.l2 {
text-decoration: line-through;
}
/*上划线*/
.l2 {
text-decoration: underline;
}
/*a标签(超链接)去下划线*/
a {
text-decoration: none;
}
.img1 {
width: 500px;
height: 400px;
}
/*水平对齐:参照物,a和b对齐*/
img, span {
vertical-align: middle;
}
</style>
</head>
<body>

<h1>故事介绍</h1>
<a href="">hell</a>
<p class="l1">123123421</p>
<p class="l2">123123421</p>
<p class="l3">123123421</p>

<p class="p1">
平静安详的元泱境界,每隔333年,总会有一个神秘而恐怖的异常生物重生,它就是魁拔!魁拔的每一次出现,都会给元泱境界带来巨大的灾难!即便是天界的神族,也在劫难逃。在天地两界各种力量的全力打击下,魁拔一次次被消灭,但又总是按333年的周期重新出现。魁拔纪元1664年,天神经过精确测算后,在第六代魁拔苏醒前一刻对其进行毁灭性打击。但谁都没有想到,由于一个差错导致新一代魁拔成功地逃脱了致命一击。很快,天界魁拔司和地界神圣联盟均探测到了魁拔依然生还的迹象。因此,找到魁拔,彻底消灭魁拔,再一次成了各地热血勇士的终极目标。
</p>

<p class="p2">
在偏远的兽国窝窝乡,蛮大人和蛮吉每天为取得象征成功和光荣的妖侠纹耀而刻苦修炼,却把他们生活的村庄搅得鸡犬不宁。村民们绞尽脑汁把他们赶走。一天,消灭魁拔的征兵令突然传到窝窝乡,村长趁机怂恿蛮大人和蛮吉从军参战。然而,在这个一切都凭纹耀说话的世界,仅凭蛮大人现有的一块杂牌纹耀,不要说参军,就连住店的资格都没有。受尽歧视的蛮吉和蛮大人决定,混上那艘即将启程去消灭魁拔的巨型战舰,直接挑战魁拔,用热血换取至高的荣誉。
</p>

<p class="p3">
If you eone to wipe your tears, guess what? I'll be there. William Shakespeare
</p>

<p>
<img src="images/img1.png" alt="" class="img1">
<span>aaaaaaaaaaa</span>
</p>


</body>
</html>

阴影

1
2
3
4
5
6
/*text-shadow
params: 阴影颜色,水平偏移,垂直偏移,阴影半径
*/
#price {
text-shadow: #20c1b4 10px 10px 10px;
}

超链接伪类

正常情况下,aa:hover

1
2
3
4
5
6
7
8
9
10
11
12
/*鼠标悬浮的状态(只需要记住hover)*/
a:hover {
color: orange;
font-size: 20px;
}
/*鼠标按住未释放的状态*/
a:active {
color: mediumseagreen;
}
a:visited {
color: blueviolet;
}

列表

1
2
3
4
5
6
7
8
9
10
11
12
/*
list-style:
none 去掉圆点
circle 空心圆
decimal 有序列表(数字)
square 正方形
*/
ul li {
height: 30px;
list-style: none;
text-indent: 1em;
}

背景

背景颜色

1
background: #a0a0a0;

背景图片

法一

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div {
width: 1000px;
height: 700px;
border: 1px solid red;
/*默认:全部平铺 repeat*/
background-image: url("images/pdx.jpg");
}

.div1 {
background-repeat: repeat-x;
}
.div2 {
background-repeat: repeat-y;
}
.div3 {
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div class="div1"></div>
<div class="div2"></div>
<div class="div3"></div>
</body>
</html>

法二

1
2
/*颜色 图片 图片位置 平铺方式*/
background: red url("../images/down.png") 270px 10px no-repeat;

渐变

网站

盒子模型

什么是盒子

  • margin:外边距

    所有的 bodymargin 默认为8

  • padding:内边距

  • border:边框

边框

  • 边框的粗细
  • 边框的样式
  • 边框的颜色

实现

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
/*常见操作*/
h1, ul, li, a, body {
/*body总有一个默认的外边距margin: 0*/
margin: 0;
padding: 0;
text-decoration: none;
}
#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
color: white;
}
form {
background: #a0a0a0;
}
div:nth-of-type(1) input {
border: 2px solid black;
}
div:nth-of-type(2) input {
border: 2px dashed blueviolet;
}
div:nth-of-type(3) input {
border: 2px dashed green;
}

内外边距

margin, padding 参数

  • 一个参数:上下左右
  • 两个参数:上下、左右
  • 四个参数:上、左、下、右(顺时针)

盒子的计算方式

你这个元素到底多大?

美工+前端

盒子最终大小:margin + border + padding + 内容的大小(上图内容的大小为:296*22

示例

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<!--外边距的妙用:居中-->
<style>

#app {
width: 300px;
/*border
params:粗细,样式(实线/虚线),颜色
*/
border: 3px solid red;
padding: 0 0 0 0;
/*margin
一个参数:上下左右
两个参数:上下、左右
四个参数:上、左、下、右(顺时针)
*/
margin: 0 auto ;
}

h2 {
font-size: 18px;
background: #4986ee;
text-align: center;
line-height: 40px;
/*上下左右都为0*/
margin: 0;
color: white;
}
form {
background: #a0a0a0;
}
input {
border: 1px solid black;
}
div:nth-of-type(1) {
border: 1px solid blueviolet;
padding: 10px;
}
</style>
</head>
<body>
<!--div为了层次更清楚;id="app":在vue里面id设置为app,代表一个应用,整个应用不是在body里面,而是在div里面,是需要定位的-->
<div id="app">
<h2>会员登录</h2>
<form action="#">
<!--里面的属性都用div包起来-->
<div>
<span>姓名:</span>
<input type="text">
</div>
<div>
<span>密码:</span>
<input type="password">
</div>
<div>
<span>邮箱:</span>
<input type="text">
</div>
</form>
</div>
</body>
</html>

圆角边框

有4个角

1
2
3
4
5
6
7
8
9
10
11
12
div {
width: 100px;
height: 100px;
border: 10px solid red;
/*
一个参数:所有角
两个参数:左上 右下、右上 左下
四个参数:左上 右上 右下 左下(顺时针方向);而且一个参数管一个角,以及对应的两边的边长如下图
*/
/*圆圈:圆角=宽度的一半*/
border-radius: 50px 20px;
}

圆形

1
2
3
4
5
6
7
8
9
10
11
12
13
14
div {
width: 50px;
height: 50px;
margin: 100px;
background: red;
/*border不在设定的长宽,所以得用border-radius也得算上去,这里给去了
/*border: 10px solid red;*/
border-radius: 50px 0px 0px 0px;
}
img {
width: 50px;
height: 50px;
border-radius: 25px;
}

这里是半圆形:可以这样理解:border-radius只控制半径为参数的那个 1/4的圆,widthheight 控制的是区域的大小

阴影

补充

  • img是内联元素 ,所以无法使用 margin: 0 auto; 居中

    1
    2
    3
    4
    5
    6
    7
    img {
    width: 150px;
    height: 150px;
    border-radius: 75px;
    margin: 0 auto;
    box-shadow: 10px 10px 100px yellow;
    }

    所以用 margin: 0 auto; 居中

    要求:

    • 外面是块元素
    • 块元素有固定的宽度

div的外层是body可以使用 margin: 0 auto;居中,因为body 是块元素,如果想让body里面的所有元素居中,可以这样

1
2
3
4
5
6
div {
width: 1000px;
border: 10px solid red;
margin: 0 auto;
text-align: center;
}

设置:text-align:center

  • 让内联 元素居中方法:

    • 块元素设置 text-align:center,使块内所有元素居中

    • 将内联元素设置为块元素: display: block

      1
      2
      3
      4
      5
      6
      7
      8
      img {
      width: 150px;
      height: 150px;
      border-radius: 75px;
      margin: 0 auto;
      box-shadow: 10px 10px 100px yellow;
      display: block;
      }

浮动

标准文档流

  • 不局部:自上而下的拼

  • 布局过:

  • 块级元素:独占一行,且只有块元素设置大小才能被显示

    • h1~h6
    • p
    • div
    • 列表
    • ……
  • 行内元素(内联元素):不独占一行

    • span
    • a
    • img
    • strong
    • ……

行内元素可以包含在块级元素里面,但是块级元素不能包含在行内元素里面

display

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
div {
width: 100px;
height: 100px;
border: 1px solid red;
display: inline-block;
}

span {
width: 100px;
height: 100px;
border: 1px solid red;
/*
inline-block 既是行内元素又是块元素(保持块元素的特性,可以写在一行)
block 块元素
inline 行内元素
none 消失
*/
display: inline-block;
}

这个也是一种实现行内元素排列的方式,但我们很多情况都是用 float

float

  • 左右浮动:float

    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
    div {
    margin: 10px;
    padding: 5px;
    }
    #app {
    border: 1px #000 solid;
    }

    .layer01 {
    border: 1px #f00 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }
    .layer02 {
    border: 1px #00f dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer03 {
    border: 1px #060 dashed;
    display: inline-block;
    float: right;
    clear:both;

    }

    .layer04 {
    border: 1px #666 dashed;
    font-size: 12px; display: inline-block;
    /*float: right;*/
    line-height: 23px;
    clear:both;
    }

父级边框塌陷的问题

clear

  • right: 右侧不允许有浮动元素,如果有,排到下面去(下同)
  • left: 左侧不允许有浮动元素
  • both: 两侧允许有浮动元素
  • none:

解决方案

  1. 增加父级元素高度

    1
    2
    3
    4
    #app {
    border: 1px #000 solid;
    height: 800px;
    }
  2. 增加一个空的 div 标签,清除浮动

    1
    2
    3
    4
    5
    6
    7
    8
    9
    <div id="app">
    <div class="layer01"><img src="images/1.jpg" alt=""></div>
    <div class="layer02"><img src="images/5.jpg" alt=""></div>
    <div class="layer03"><img src="images/3.jpg" alt=""></div>
    <div class="layer04">
    浮动的盒子可以向左浮动,也可以向右浮动,直到它的外边缘碰到包含框或另一个浮动盒子位置
    </div>
    <div class="clear"></div>
    </div>
    1
    2
    3
    4
    5
    .clear {
    clear: both;
    margin: 0;
    padding: 0;
    }
  3. overflow

    • params:

      1
      2
      3
      hidden: 隐藏
      scroll: 滚动条
      auto:
    • 在父级元素中增加一个overflow: hidden

      1
      2
      3
      4
      5
      #app {
      border: 1px #000 solid;
      /*height: 800px;*/
      overflow: hidden;
      }
  4. 父类添加一个伪类:after (推荐)

    可以避免空 div

    1
    2
    3
    4
    5
    #app:after {
    content: "";
    display: block;
    clear: both;
    }
  5. 小结

    • 浮动元素后面增加空 div

      优势

      • 简单

      劣势:

      • 代码中尽量避免空 div
    • 设置父元素的高度

      优势:

      • 简单

      劣势:

      • 元素假设有了固定的高度,就会被限制
    • overflow: hidden

      优势:

      • 简单

      劣势:

      • 下拉的一些场景避免使用

      • 如果超出,就会被切掉,但是宁愿被切掉,也不要滚动条,很诡异

    • 父类添加一个伪类:after

      优势:

      • 写法稍微复杂一点,但是没有副作用,推荐使用

对比

  • display
    • 方向不可控
    • 不用管理父级边框塌陷
  • float
    • 浮动起来的话会脱离标准文档流,所以要解决父级边框塌陷的问题

定位

相对定位

  • position: reletive

  • 相对定位:相对于自己原来的位置进行偏移,相对定位的话,它仍然在标准文档流中,它原来的位置会被保留

    1
    2
    3
    4
    5
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    bottom: -10px;
    right: 20px;
  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <!--相对定位:相对于自己原来的位置进行偏移-->
    <style>
    body {
    padding: 20px;
    }
    div {
    margin: 10px;
    padding: 5px;
    font-size: 12px;
    line-height: 25px;
    }

    #app {
    border: 1px solid #666;
    padding: 0;
    }
    #first {
    background-color: #a0a0a0;
    border: 1px dashed #b61818;
    /*相对定位:上下左右*/
    position: relative;
    /*距离上面-20px*/
    top: -20px;
    left: 10px;
    }
    #second {
    background-color: #e2cccc;
    border: 1px dashed #59b631;
    }
    #third {
    background-color: #eade86;
    border: 1px dashed #bb21d5;
    position: relative;
    bottom: -10px;
    right: 20px;
    }
    </style>

    </head>
    <body>

    <div id="app">
    <div id="first">第1个盒子</div>
    <div id="second">第2个盒子</div>
    <div id="third">第3个盒子</div>
    </div>
    </body>
    </html>

练习

实现

代码

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
#app {
width: 300px;
height: 300px;
border: 2px solid red;
padding: 10px;
}
a {
width: 100px;
height: 100px;
text-align: center;
line-height: 100px;
background-color: #ea85c2;
/*加了border位置就不准了,因为border+内容的大小才是整个元素的大小*/
/*border: 1px solid #000000;*/
color: white;
text-decoration: none;
display: block;
}

a:hover {
background-color: #0c91ea;
}

.a2, .a4 {
position: relative;
left: 200px;
top: -100px;
}
.a3 {
position: relative;
}
.a5 {
position: relative;
left: 100px;
top: -300px;
}
</style>
</head>
<body>
<div id="app">
<a href="#" class="a1">链接1</a>
<a href="#" class="a2">链接2</a>
<a href="#" class="a3">链接3</a>
<a href="#" class="a4">链接4</a>
<a href="#" class="a5">链接5</a>
</div>
</body>
</html>

绝对定位

  • 定位:基于xxx定位,只有上下左右
    • 没有父级元素定位的前提下,相对于浏览器定位;
    • 父级元素存在定位,我们通常相对于父级元素进行偏移;
    • 在父级元素范围内移动
  • 相对于父级或浏览器的位置进行偏移,绝对定位的话,它不在标准文档流中,它原来的位置不会被保留

固定定位

  • 定位:无论怎么走,都在固定位置

  • 示例

    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
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
    body {
    height: 1000px;
    }
    div:nth-of-type(1) {
    width: 100px;
    height: 100px;
    background-color: #ef67ad;
    text-align: center;
    line-height: 100px;
    /*绝对定位:相对于浏览器*/
    position: absolute;
    right: 0;
    bottom: 0;
    display: block;
    }

    div:nth-of-type(2) {
    width: 50px;
    height: 50px;
    background-color: #3383d9;
    /*固定定位:定死了;很多导航栏不动,就是通过固定定位来实现的*/
    position: fixed;
    right: 0;
    bottom: 0;
    display: block;
    }
    </style>
    </head>
    <body>

    <div>div1</div>
    <div>div2</div>

    </body>
    </html>

z-index

理解图层

实践

z-index:默认是0,最高无限;习惯用999代表最高层

  • html

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="css/style.css">
    </head>
    <body>

    <div id="app">
    <ul>
    <li><img src="images/bg.webp" alt=""></li>
    <li class="tipText">学习微服务,找狂神</li>
    <li class="tipBg"></li>
    <li>时间:2021-1-1</li>
    <li>地点:卢浮宫</li>
    </ul>
    </div>
    </body>
    </html>
  • css

    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
    #app {
    width: 320px;
    padding: 0;
    margin: 0;
    overflow: hidden;
    font-size: 12px;
    line-height: 25px;
    border: 1px black solid;
    }

    ul, li {
    padding: 0;
    margin: 0;
    list-style: none;
    }

    /*父级元素相对定位*/
    #app ul {
    position: relative;
    }

    .tipText, .tipBg {
    position: absolute;
    width: 320px;
    height: 25px;
    top: 167px;
    color: white;
    }

    .tipText {
    /*最低为0*/
    /*z-index: 999;*/
    }

    .tipBg {
    background: black;
    /*背景透明度*/
    opacity: 0.5;
    /*现在一般不可以用,早期可以用*/
    filter: alpha(opacity=50);
    }

动画

菜鸟教程

canvas 动画:点击这里

卡巴斯基安全监控网站点击这里

源码之家点击这里

前端

  • HTML:网页基本结构

  • CSS:美化网页

  • JS:使得网页动起来产生交互性行为

初识HTML

什么是HTML

Hyper Text Markup Language(超文本标记语言)

  • 超文本:文字、图片、音频、视频、动画等

W3C标准

w3c: 万维网联盟

标准:

  • 结构化标准语言(HTMLXML)
  • 表现标准语言(css
  • 行为标准 (DOMECMAScript

基本信息

  • 注释:<!-- 注释 -->
  • <!DOCTYPE html>:告诉浏览器,我们要使用什么规范
  • <html> </html>: 总标签,只有在里面写才有意义
  • head标签:网页头部
  • body标签:网页主体
  • title标签:网页标题
  • meta 标签:描述性标签,用来描述网站的一些信息,可以用来被搜索到;一般用来做 SEO

基本标签

标题标签

1
2
3
4
5
6
<h1>1级标签</h1>
<h2>2级标签</h2>
<h3>3级标签</h3>
<h4>4级标签</h4>
<h5>5级标签</h5>
<h6>6级标签</h6>

段落标签

1
2
3
<p>hello world</p>
<p>hello world2</p>
<p>hello world3</p>

换行标签

即便换行了,也是一个段落

1
2
3
hello world <br/>
hello world2 <br/>
hello world3 <br/>

水平线标签

1
<hr/>

字体样式标签

  • 粗体斜体

    1
    2
    粗体:<strong>Hello html</strong>
    斜体:<em>Hello html</em>

注释和特殊符号

注释

1
<!--注释-->

特殊符号

  • 空格:空 格:空&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;格

  • 大于号:&gt;

  • 小于号:&lt;

  • 版权符号:&copy;

  • 记忆方式:

    • &字母:可以直接调
    • 百度查(不推荐)

图像标签

常见图像格式

  • JPG
  • GIF
  • PNG
  • BMP:位图
  • ……

嵌入图片

1
2
3
4
5
6
7
8
<!--
src:图片地址(必填):
绝对地址:E:\SourceCode\HTMLSpace\learnProject\resources\images\pdx.jpg
相对路径:../resources/images/pdx.jpg

alt: 图片名字(必填)
-->
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="悬停文字" width="300" height="480">

链接标签

可以点的东西都是链接

页面间跳转:从一个页面跳转到另一个页面

文本超链接

1
2
3
4
5
6
7
8
<!--a标签
href:必填,表示要跳转到哪个页面
target:表示窗口在哪里打开
_blank: 在新标签页中打开
_self:在自己的网页打开
-->
<a href="test1.html" target="_blank">点击我跳转test1</a>
<a href="https://qeuroal.top/" target="_self">点击我跳转到主页</a>

图像超链接

1
2
3
<a href="test1.html">
<img src="../resources/images/pdx.jpg" alt="Qeuroal" title="点击我跳转test1" width="300" height="480">
</a>

页面内跳转:页面内跳转

锚链接

1
2
3
4
5
6
7
8
9
10
<!--使用name作为标记-->
<a name="top">顶部</a>

<!--锚链接:页面内跳转
1. 需要一个锚标记
2. 跳转到标记
3. #: 通过#跳到标记
-->
<a href="#top">回到顶部</a>
<a href="链接标签.html#down">跳转</a>

功能性连接

邮件链接

1
2
3
4
<!--功能性连接
邮件链接:mailto
-->
<a href="mailto:xxxxx@163.com"></a>

qq链接

1
2
3
<a target="_blank" href="http://wpa.qq.com/msgrd?v=3&uin=&site=qq&menu=yes">
<img border="0" src="http://wpa.qq.com/pa?p=2::53" alt="你好,加我领取资料" title="你好,加我领取资料"/>
</a>

块元素和行内元素

块元素

  • 无论内容多少,该元素独占一行
  • ph1-h6、…)

行内元素

  • 内容撑开宽度,左右都是行内元素的可以排在一行
  • astrongem、…)

列表标签

什么是列表

列表的分类

有序列表

1
2
3
4
5
6
7
8
9
10
<!--有序列表
应用:试卷、问答……
-->
<ol>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ol>

无序列表

1
2
3
4
5
6
7
8
9
10
<!--无序列表
应用:导航、侧边栏……
-->
<ul>
<li>java</li>
<li>python</li>
<li>运维</li>
<li>前端</li>
<li>c/c++</li>
</ul>

自定义列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--自定义列表
dl:标签
dt:列表名称
dd:列表选项

应用:公司网站底部
-->
<dl>
<dt>学科</dt>
<dd>java</dd>
<dd>python</dd>
<dd>Linux</dd>
<dd>C</dd>

<dt>位置</dt>
<dd>唐山</dd>
<dd>石家庄</dd>
</dl>

表格标签

为什么使用表格

  • 简单通用
  • 结构稳定

基本结构

  • 单元格
  • 跨行
  • 跨列

实现

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
<!--表格table
行:tr
列:td
-->
<table border="1px">
<tr>
<!--colspan 跨列-->
<td colspan="4">1</td>
<td>2</td>
<td>3</td>
<td>123</td>

</tr>
<tr>
<!--colspan 跨行-->
<td rowspan="2">4</td>
<td>5</td>
<td>6</td>
</tr>
<tr>
<td>7</td>
<td>8</td>
<td>9</td>
</tr>
</table>

媒体元素

视频元素video

1
2
3
4
5
6
<!--视频
src: 资源路径
controls: 控制条
autoplay: 自动播放
-->
<video src="../resources/video/起风了.mp4" controls autoplay></video>

音频元素audio

1
2
<!--音频-->
<audio src="../resources/audio/起风了.mp3" controls autoplay></audio>

页面结构分析

1
2
3
4
5
6
7
8
9
10
11
<header>
<h2>网页头部</h2>
</header>

<section>
<h2>网页主体</h2>
</section>

<footer>
<h2>网页脚部</h2>
</footer>

iframe内联框架

1
2
3
4
5
6
7
8
<!--iframe内联框架
src: 地址
frameborder:
w-h:宽度-高度
-->
<iframe src="https://www.baidu.com" name="baidu" frameborder="0" width="1000px" height="800px"></iframe>

<a href="https://qeuroal.top" target="baidu">点击跳转</a>

表单语法

规定

  • input标签 都要有一个name

表单元素格式

文本输入框

1
2
3
4
5
6
7
8
<!--文本输入框:input type="text"
value="Qeuroal" 默认初始值
maxlength="8" 最长能写几个字符
size="30" 文本框的长度
-->
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" ></p>
<!--密码框:input type="password"-->
<p>密码:<input type="password" placeholder="请输入密码" name="pwd"></p>

单选框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--单选框
input type="radio"
value: 单选框的值
name: 表示组,组一样才能选择一个
-->
<p>性别:
<input type="radio" value="boy" name="sex" checked/>
<input type="radio" value="girl" name="sex"/>
</p>

<p>
<input type="submit">
<input type="reset">
</p>

多选框

1
2
3
4
5
6
7
8
9
<!--多选框
input type="checkbox"
-->
<p> 爱好:
<input type="checkbox" value="sleep" name="hobby">睡觉
<input type="checkbox" value="chat" name="hobby">聊天
<input type="checkbox" value="code" name="hobby" checked>码代码
<input type="checkbox" value="game" name="hobby">游戏
</p>

按钮

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<!--
按钮
input type="button" 普通按钮
input type="image" 图像按钮
input type="submit" 提交按钮
input type="reset" 充值
-->
<p>按钮:
<input type="button" name="btn1" value="点击变长">
<input type="image" name="imgBtn1" src="../resources/images/pdx.jpg">
</p>

<p>
<input type="submit">
<input type="reset" value="清空表单">
</p>

下拉框(列表框)

1
2
3
4
5
6
7
8
9
10
<!--下拉框,列表框
-->
<p>国家:
<select name="countries">
<option value="china">中国</option>
<option value="eth" selected>瑞士ETH</option>
<option value="us">美国</option>
<option value="dg">德国</option>
</select>
</p>

文本域

1
2
3
<p>反馈 <br>
<textarea name="textarea" cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
</p>

文件域

1
2
3
4
<p>
<input type="file" name="files">
<input type="button" value="上传" name="upload">
</p>

搜索框及简单验证

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--邮件验证-->
<p> 邮箱:
<input type="email" name="email">
</p>

<!--url-->
<p> url:
<input type="url" name="url">
</p>

<!--数字验证-->
<p> 数字:
<input type="number" name="num" max="100" min="0" step="1">
</p>

<!--滑块-->
<p> 音量:
<input type="range" name="voice" min="0" max="100">
</p>

<!--搜索框-->
<p> 搜索:
<input type="search" name="search">
</p>

表单的应用

只读 readonly

1
<p>用户名:<input type="text" placeholder="请输入用户名" name="username" value="admin" readonly></p>

禁用 disabled

1
<input type="radio" value="boy" name="sex" checked disabled/>

隐藏域 hidden

1
<p>密码:<input type="password" placeholder="请输入密码" name="pwd" hidden></p>

效果

增强鼠标可用性

1
2
3
4
<p>
<label for="mark">你点我试试</label>
<input type="text" id="mark">
</p>

表单初级验证

为什么要进行表单验证

  • 减轻服务器的压力
  • 保证数据的安全性

常用方式

  • placeholder :提示信息

    1
    <textarea name="textarea"  cols="30" rows="10" placeholder="请输入反馈信息"></textarea>
  • required:非空判断

    1
    <input type="search" name="search" required>
  • pattern:正则表达式

    • 如何查:常用正则表达式点击这里

    • 使用:

      1
      2
      3
      4
      <!--自定义邮箱-->
      <p>自定义邮箱
      <input type="email" name="diyMail" pattern="\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*">
      </p>

总结

第一个flask程序

Flask(__name__)

1
2
3
4
5
# 初始化一个Flask对象
# Flask()
# 需要传递一个参数 __name__
# 1. 方便flask框架去寻找资源
# 2. 方便flask插件比如Flask-Sqlalchemy出现错误的时候,好去寻找问题所在的位置

@app.route()

1
2
3
4
5
6
7
# @app.route是一个装饰器(可以看成url的后缀:127.0.0.1:5000/login 的 /login)
# @开头,并且在函数的上面,说明是装饰器
# 这个装饰器的作用是做一个url和视图函数(习惯性的命名,就是这里的hello_world函数)的映射
# 127.0.0.1:5000 -> 去请求hello_world这个函数然后将结果返回给浏览器
@app.route('/')
def hello_world():
return “我是第一个flask程序”

app.run():

启动一个应用服务器,来接收用户的请求。

类似于:

1
2
while (True):
listen()

设置debug模式

  1. app.run() 中传入一个关键字参数debugapp.run(debug=True), 就设置当前项目为 debug 模式
  2. debug 模式的两大功能
    • 当程序出现问题时,可以在也米那种看到错误信息和出错的位置
    • 只要修改了项目中的 Python 文件,程序会自动加载,不需要手动重新启动服务器

使用配置文件

  1. 新建一个 .py文件,这里设置为 config.py

  2. 然后设置大写,如:DEBUG=True

  3. app进行关联

    1
    2
    3
    4
    # 假设配置文件为config
    import config

    app.config.from_object(config)
  4. 还有许多的其他参数,都是放在这个配置文件中,比如 SECRET_KEYSQLALCHEMY 这些配置都是在这个文件中

URL传参

  1. 参数的作用:可以在相同的url,但是指定不同的参数,来加载不同的数据

  2. 在flask中如何使用参数

    1
    2
    3
    @app.route("/article/<id>")
    def article(id):
    return "您请求的参数是:%s" %id
    • 参数需要放在两个尖括号中
    • 视图函数中需要放和url中的参数同名的参数

反转URL

正转

通过url可以取到视图函数的内容

反转

  1. 从视图函数到url的转换叫做反转url(通过视图函数的名称,可以得到指向的url
  2. 用处
    • 在页面重定向的时候,会使用url反转
    • 在模板中也会使用url反转

函数讲解

  • url_for():
    • params:视图函数名称, 视图函数对应的参数(如果视图函数有参数的话),如 url_for("article", id="123")
    • return: 视图函数对应的url

页面跳转和重定向

  1. 用处:在用户访问一些需要登录的页面的时候,如果用户没有登录,那么可以让他重定向到登录页面

  2. 实现

    1
    2
    3
    4
    5
    6
    7
    8
    from flask import Flask, redirect, url_for

    @app.route("/question/<isLogin>/")
    def question(isLogin):
    if isLogin == '1':
    return "这是发布问答页面"
    else:
    return redirect(url_for("login"))

函数讲解

  • redirect()
    • params: url,如 redirect("/login123/")或者 redirect(url_for("login")),建议使用第二种,只要视图函数名字不变, @app.route(/login123/)里面的 login123怎么变都没关系

模板渲染和参数

如何渲染模板

  1. 模板放在 templates 文件夹下

  2. flask 中导入 render_template 函数

  3. 在试图函数中,使用 render_template 函数渲染模板。

    注意:只需要填写模板的名字,不要填写 templates 这个文件夹的路径。如果在 templates 文件夹下创建了一个新的文件夹,那么就要添加上文件名(render_template() 函数的文件名是相对路径)

模板传参

  • 如果只有一个或者少量参数,直接在 render_template() 函数中添加关键字参数就可以了
  • 如果有多个参数的时候,那么可以先把所有的参数放在字典中,然后在 render_template 中,使用 **字典名 把字典转换为关键参数传递进去,这样的代码更方便管理和应用

函数讲解

  • render_template()
    • params: 渲染文件名(渲染文件放在template文件夹下), username = 用户名 (username 保证和渲染文件中的变量名保持一致), … 。如 render_template("index.html", username = "Qeuroal")
    • 如果参数特别多的话,可以先定义一个字典(context),然后把字典传入函数内,如 render_template("index.html", **contet)

模板访问属性和字典

在模板中如果要使用一个变量,语法是 {{ params }}

  • 访问类的属性:
    • 和在Python文件中一致
    • 或者类似于这样: Person["name"]
  • 访问字典:
    • py文件一样: dict[key]
    • dict.key

实现

template1.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@app.route("/")
def index():
class Person():
name = "Qeuroal"
age = 24

p = Person()
testDict = {
"key": "key",
"value": "value"
}
context = {
"username": "Qeuroal",
"gender": "男",
"age": 24,
"person": p,
"testDict": testDict
}

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
这是HTML文件中出现的文字
<p>用户名: {{ username }}</p>
<hr>
<p>用户名: {{ person.name }}</p>
<p>年龄: {{ person["age"] }}</p>
<hr>
<p>key: {{ testDict.key }}</p>
<p>value: {{ testDict['value'] }}</p>
</body>
</html>

HTML

if判断

语法

1
2
3
4
5
6
7
{% if xxx and xxx %}
xxxx
{% elif xxx and xxx%}
xxxx
{% else %}
xxxx
{% endif %}

if的使用和python中相差无几。

for循环遍历列表和字典

字典遍历

字典的遍历,语法和 python 一样,可以使用 items(), keys(), values(), iteritems(), iterkeys(), itervalues()

1
2
3
{% for k,v in user.items() %}
<p>{{ k }}: {{ v }}</p>
{% endfor%}

列表遍历

语法和 python 一样

1
2
3
{% for website in websites %}
<p>{{ website }}</p>
{% endfor %}

过滤器

作用对象是模板中的变量:

介绍和语法

  • 介绍:过滤器可以处理变量,把原始的遍历经过处理后再展示出来。作用的对象是变量

  • 语法:

    1
    {{ avator|default("xxx")}}

default过滤器

如果当前变量不存在,这时候可以指定默认值。

length过滤器

求列表,字符串,字典,元组的长度。类似于:python 中的 len()

常用的过滤器

  • abs(value)
  • default(value, defaultValue, boolean=false)
  • escape(value)
  • first(value)
  • format(value, *args, **kwargs)
  • last(value)
  • length(value)
  • join(value, d="")
  • safe(value)

继承和block

继承

  • 作用:可以把一些公共的代码放在父模板中,避免每个模板写同样的代码

  • 语法:

    1
    {% extends "base.html" %}
  • 如果想在子模板中实现自己的内容:需要定义接口,类似于 java 的接口:html子模板需要实现自己内容,必须要放在父模板定义的接口里面,即block,不能在接口外面写代码

block

  • 作用:可以让子模板实现一些自己的需求。父模板需要提前定义好。
  • 注意点:
    • 子模板中的代码,必须放在block块中;
    • 可以定义多个 block

URL链接

使用 url_for(视图函数名称) 可以反转成url。

加载静态文件

1
2
3
4
5
6
<style>
a {
background: red;
color: black;
}
</style>

样式代码一般不会写在模板中,因为:维护性差,一般样式文件抽取成 css 文件(static 文件夹中是存放静态文件的)

语法

url_for("static", filename="路径")

  • 如:url_for("static", filename="css/index.css")

  • 静态文件,flask会从 static 文件夹中开始寻找,所以不需要写 static 这个路径了

  • 可以加载 css文件, js 文件,image 文件。

示例代码

1
2
3
4
5
6
{# 加载css文件 #}
<link rel="stylesheet" href="{{ url_for("static", filename="css/index.css") }}">
{# 加载js文件 #}
<script src="{{ url_for("static", filename="js/index.js") }}"></script>
{# 加载image文件 #}
<img src="{{ url_for("static", filename="images/pdx.jpg") }}" alt="">

注意点

装饰器

  • 装饰器为 @app.route('/login/'),网址可以是:http://127.0.0.1:5000/loginhttp://127.0.0.1:5000/login/
  • 装饰器为 @app.route('/login'),网址只能是:http://127.0.0.1:5000/login/

MySQL

安装(win10)

  • 我选择的是 Server only

  • 设置密码:

  • 打开命令行,并输入上一步设置好的密码

  • 下载地址

  • 一般直接下一步

  • 设置初始密码命令:

    1
    mysqladmin -uroot password [password]
  • 如果没有安装 .net Framework 4,就在那个提示框中,找到下载的 url 下载;

  • 如果没有安装 Microsoft visual C++ x64,那么就需要谷歌或者百度下载这个软件进行安装即可。

MySQL-python中间件介绍与安装

linux, macos

  1. 进入虚拟环境
  2. 输入 pip install mysql-python

windows系统

  • windows 在这里下载,看 python 安装的是32位还是64位,对应下载
  • 进入虚拟环境: conda activate 虚拟环境名
  • 安装:pip install mysqlclient-1.4.6-cp37-cp37m-win_amd64.whl

Flask-SQLAlchemy的介绍与安装

  • ORM: Object Relationship Mapping (关系模型映射);

  • flask-sqlalchemy 是一套 ORM框架

  • 模型关系映射

    • delete(article1):把 article表中和article1属性对应相同的那条数据删除。
    • 好处:ORM使得我们操作数据库非常简单,就像操作对象是一样的,非常方便。因为一个表就抽象成了一个类,一条数据就抽象成了该类的一个对象。
  • 安装:pip install flask-sqlalhemy,如果是在 macos 或者 linux 中没有权限的话这样安装: sudo pip install flask-sqlalhemy

Flask-SQLAlchemy的使用

  1. 初始化和设置数据库配置信息:

    • 使用 flask_sqlalchemy 中的 SQLAlchemy 进行初始化

      1
      2
      3
      from flask_sqlalchemy import SQLAlchemy
      app = Flask(__name__)
      db = SQLAlchemy(app)
  2. 设置配置信息:在 config.py 文件中添加以下配置信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    # dialect+driver://username:password@host:port/database
    DIALECT = "mysql"
    DRIVER = "mysqldb"
    USERNAME = "root"
    PASSWORD = "toor"
    HOST = "127.0.0.1"
    PORT = "3306"
    DATABASE = "dbDemo1"

    SQLALCHEMY_DATABASE_URI = "{}+{}://{}:{}@{}:{}/{}?charset=utf8".format(DIALECT, DRIVER, USERNAME, PASSWORD, HOST, PORT, DATABASE)
    # 去除警告
    SQLALCHEMY_TRACK_MODIFICATIONS = False
  3. 添加配置文件

    1
    2
    3
    import config

    app.config.from_object(config)
  4. 测试是否连接成功

    1
    db.create_all()

    如果没有报错,说明配置没有问题,如果有错误,可以根据错误进行修改。

建表

  1. cmd 进入 mysqlmysql -uroot -p
  2. create database dbDemo1 charset utf8

SQLAlchemy创建模型与表的映射

步骤

  1. 模型需要继承自 db.Model,然后需要映射到表中的属性,必须写成 db.Column()的数据类型

  2. 数据类型:

    • db.Integer: 整型
    • db.Sting: varcharchar,需要指定最长的长度
    • db.Text: text
  3. 其他参数

    • primary_key: 将这个字段设置为主键
    • autoincrement: 这个主键为自增长
    • nullable: 这个字段是否可以为空,默认为空,可以将这个值设置为 False ,在数据库中,这个值就不能为空了
  4. 最后需要调用 db.create_all() 来将模型真正的创建到数据库中,db.create_all()只会映射一次。

实例

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
from flask import Flask, render_template
from flask_sqlalchemy import SQLAlchemy
import config

app = Flask(__name__)
app.config.from_object(config)
db = SQLAlchemy(app)

"""
创建article表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null
)
"""
# 自己写的模型一定要继承sqlalchemy属性下的model模型, 字段->属性
class Article(db.Model):
# 如果不指定表名,默认为类名
__tablename__ = "article"
# 属性id映射到字段id
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
# varchar, char -> db.String(长度)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)

# 所有的模型映射到数据库的一张表
db.create_all()


@app.route('/')
def index():
return "index"

if __name__ == '__main__':
app.run()

SQLAlchemy数据增删改查

1
2
3
4
5
# 增加
article1 = Article(title="aaa", content="bbb")
db.session.add(article1)
# 事务
db.session.commit()

1
2
3
4
5
6
# 返回的是Query(<==> list); .all()查找所有数据, .first()返回的第一条数据,如果没有,返回null
result = Article.query.filter(Article.title == "aaa").all()
print(result[0], type(result[0]))
article1 = result[0]
print(article1.title)
print(article1.content)

1
2
3
4
5
6
# 1. 先把要修改的地方查找出来
article1 = Article.query.filter(Article.title == "aaa").first()
# 2. 把这条数据,你需要修改的地方进行修改
article1.title = "new title"
# 3. 做事务的提交
db.session.commit()

1
2
3
4
5
6
# 1. 把需要删除的数据查找出来
article1 = Article.query.filter(Article.content == "bbb").first()
# 2. 把这条数据删除掉
db.session.delete(article1)
# 3. 做事务提交
db.session.commit()

注意

  • 如果把增删改查放在 @app.route("/") 类似的视图函数中,访问一次 url,就会执行一遍相应的增删改查

Flask-SQLAlchemy外键及其关系

外键

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
"""
用户表
sql语句:
create table users (
id int primary key autoincrement,
username varchar(100) not null
)
"""
class User(db.Model):
__tablename__ = "user"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
username = db.Column(db.String(100), nullable=False)
# password = db.Column(db.String(100), nullable=False)

"""
文章表
create table article (
id int primary key autoincrement,
title varchar(100) not null,
content text not null,
authorId int,
foreign key "authorId" references "user.id"
)
"""
class Article(db.Model):
__tablename__ = "article"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
# 设置外键
authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
# params: 模型名, backref:反向引用;db.backref(), params: 反向引用的名字,
author = db.relationship("User", backref=db.backref("articles"))

解释

  • authorId = db.Column(db.Integer, db.ForeignKey("user.id"))
    • Article 这个模型添加一个 author 属性,可以访问这篇文章的作者的数据,像访问普通模型一样
    • backref:定义反向引用,可以通过 User.articles 这个模型访问这个模型缩写的所有文章

补充

  • 添加数据(方法二):

    1
    2
    3
    4
    article = Article(title="aaa", content="bbb")
    article.author = User.query.filter(User.id==1).first()
    db.session.add(article)
    db.session.commit()

多对多

例子

在这张图中:

使用

  • 多对多的关系,要通过一个中间表进行关联;

  • 中间表,不能通过 class 的方式实现,只能通过 db.Table 的方式实现

  • 设置关联:

    1
    tags = db.relationship("Tag", secondary=articleTagRelation, backref=db.backref("articles"))

    需要使用一个关键字参数 secondary=中间表 进行关联

  • 访问和添加可以通过以下方式操作:

    • 添加数据

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      article1 = Article(title="aaa")
      article2 = Article(title="bbb")
      tag1 = Tag(name="111")
      tag2 = Tag(name="222")

      article1.tags.append(tag1)
      article1.tags.append(tag2)

      article2.tags.append(tag1)
      article2.tags.append(tag2)

      db.session.add(article1)
      db.session.add(article2)
      db.session.add(tag1)
      db.session.add(tag2)
      db.session.commit()
    • 访问数据

      1
      2
      3
      4
      article1 = Article.query.filter(Article.title=="aaa").first()
      tags = article1.tags
      for tag in tags:
      print(tag.name)

Flask-Script的介绍和安装

介绍

Flask-Script 的作用是可以通过命令行的形式来操作 Flask。例如通过命令跑一个开发版本的服务器、设置数据库、定时任务。

安装

  1. 进入到虚拟环境中
  2. pip install flask-script进行安装

使用

  1. 如果直接在主 manage.py 中写命令,那么在终端就只需要 python manage.py commandName 就可以了。

  2. 如果把一些命令集中在一个文件中,那么在终端就需要输入一个父命令,如 python manage.py db init

  3. 例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    from flask_script import Manager
    from flaskScriptDemo import app
    from dbScript import dbManager

    manager = Manager(app)

    @manager.command
    def runserver():
    print("服务器跑起来了")

    # params: 前缀(类似于别名),dbManager
    # 使用:python manage.py db init
    manager.add_command("db", dbManager)

    if __name__ == '__main__':
    manager.run()
  4. 有子命令的例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    from flask_script import Manager

    dbManager = Manager()

    @dbManager.command
    def init():
    print("数据库初始化完成")

    @dbManager.command
    def migrate():
    print("数据表迁移成功")

分开 models 及解决循环引用

循环引用

解决办法:

db 放在一个单独的文件中,切断循环引用的线条就可以了。

分开 models

  • 目的:为了让代码更加方便的管理

注意:

  • 使用了 db.init_app(app),就不能直接使用 db.create_all()了,会报错。

    1
    2
    3
    app = Flask(__name__)
    app.config.from_object(modelSepConfig)
    db.init_app(app)

    因为涉及到了上下文的问题:

    解决办法看 flask-migrate

flask-migrate

db.init_app(app)不能直接使用 db.create_all() 解决办法

手动推动 appapp栈

介绍

因为采用 db.create_all() 在后期修改字段的时候,不会自动的映射到数据库中,必须删除表,然后重新运行 db.create_all() 才会重新映射,这样不符合我们的需求。因此 flask-migrate 就是为了解决这个问题,它可以在每次修改模型后,可将修改的东西映射到数据库中

安装

  1. 进入虚拟环境
  2. pip install flask-migrate

使用

使用 flask_migrate 必须借助 flask-scripts,这个包的 MigrateCommand 中包含了所有和数据库相关的命令。

相关命令

  • python manage.py db init: 初始化一个迁移脚本的环境,只需要执行一次;
  • python manage.py db migrate: 将模型生成迁移文件,只要模型更改了,就需要执行一遍这个命令;
  • python manage.py db upgrade: 将掐你文件真正映射到数据库中,每次运行了 migrate 命令后,就记得要运行这个命令

注意点

  • 如果需要将你想要映射到数据库中的模型,都要导入到 manage.py 文件中,如果没有导入进去,就不会映射到数据库中

manage.py 的相关代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from flask_script import Manager
from migrateDemo import app
from flask_migrate import Migrate, MigrateCommand
from exts import db
from models import Article

# 模型 => 迁移文件 => 表
# init: 初始化迁移的环境
# migrate: 将模型生成迁移文件
# upgrade: 将迁移文件映射成表

manager = Manager(app)

# 1. 要使用 flask_migrate,必须绑定app和db
migrate = Migrate(app, db)
# 2. 把MigrateCommand命令添加到manager中
manager.add_command("db", MigrateCommand)




if __name__ == '__main__':
manager.run()

cookie和session

  • 出现的原因:在网站中,http 请求是无状态的。也就是说即使第一次和服务器连接后并且登录成功后,第二次请求服务器依然不知道当前请求是哪个用户。cookie的出现就是我了解决这个问题,第一次登录后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当该用户发送第二次请求的时候,就会自动的把上次请求存储的 cookie 数据自动的携带给服务器,服务器通过浏览器携带的数据就能判断当前用户是那个了;

  • 图解:

  • 如果服务器返回了 cookie 给浏览器,那么浏览器下次再请求相同的服务器的时候,就会自动的把 cookie 发送给服务器,这个过程,用户根本不需要管;

  • cookie 是保存在浏览器中的,相对对的是浏览器;

session

  • 介绍:sessioncookie 的作用有点类似,都是为了存储用户相关的信息。不同的是,cookie 是存储在本地服务器的,而 session 存储在服务器。存储在服务器的数据会更加安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源,但现在服务器已经发展至今,一些 session 信息还是绰绰有余的。

  • 图解:

  • 使用 session 的好处:

    • 敏感数据不是直接发送给服务器,而是发送回一个 session_id ,服务器将 session_id 和敏感数据做一个映射存储在 session (服务器上面)中,更加安全;
    • session 可以设置过期时间,也可以从另外一方面,保证用户的账号安全。

注意

  • session_id:返回给浏览器的时候,是放在 cookie 中的。

flask中session工作机制

  • 讲解:把敏感数据经过加密后放入 session 中,然后再把 session 存放到 cookie 中,下次请求的时候,再从浏览器发送过来的 cookie 中读取 session,然后再从 session 中读取敏感数据,并进行解密,获取最终的用户数据;

  • flask 的这种 session 机制,可以节省服务器的开销,以内把所有的信息都存储到了客户端(浏览器);

  • 安全是相对的,没有绝对的安全,把 session 放到 cookie 中,经过机密,也是比较安全的;

  • 图解:

flask操作session

session的操作方式:

  • 使用 session 需要从 flask 中导入 session,以后所有和 session 相关的操作都是通过这个变量来的;
  • 使用 session 需要设置 SECRET_KEY,用来作为加密用的。并且这个 SECRET_KEY 如果每次服务器启动后都变化的话,那么之前的 session 就不能通过当前这个 SECRET_KEY 进行解密了;
  • 操作 session ,跟操作字典是一样的;
  • 添加 sessionsession[key]=value
  • 删除:session.pop(key) 或者 del session[key]
  • 清除所有 sessionsession.clear()
  • 获取 sessionsession.get(key)

设置session的过期时间

  • 如果没有指定session的过期时间,那么默认是浏览器关闭后就自动结束

  • 如果设置了 sessionpermanent 属性为 True,那么过期时间是31天

  • 可以通过给 app.config 设置 PERMANENT_SESSION_LIFETIME 来更改过期时间,这个值的数据类型是 datatime.timedelay 类型:

    1
    2
    3
    from datetime import timedelta

    PERMANENT_SESSION_LIFETIME = timedelta(days=7)

get请求和post请求

get请求

  • 使用场景:如果只对服务器获取数据,并没有对服务器产生任何影响,那么这时候使用 get 请求
  • 传参:get请求传参是防砸url中,并且是通过 ?的形式来指定 keyvalue

post请求

  • 使用场景:如果要对服务器产生影响,那么使用post请求,如: 登录:服务器要记录用户什么时候登陆过,要把数据保存在本地;
  • 传参:post请求传参不是放在 url 中,而是通过 form data 的形式发送给服务器的

获取参数

  • get请求:通过 flask.request.args 来获取
  • post请求:通过 flask.request.form 来获取

注意

  • post请求在模板中要注意几点:

    • input标签中,要写name来标识这个 valuekey,方便后台获取

    • 在写 form 表单的时候,要指定 method='post',并且要指定 action='/login/'

    • 示例代码

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      <form action="{{ url_for("login") }}" method="post">
      <table>
      <tbody>
      <tr>
      <td>用户名</td>
      <td><input type="text" placeholder="请输入用户名" name="username"></td>
      </tr>

      <tr>
      <td>密码</td>
      <td><input type="text" placeholder="请输入密码" name="password"></td>
      </tr>

      <tr>
      <td></td>
      <td><input type="submit" placeholder="登录"></td>
      </tr>

      </tbody>
      </table>
      </form>

保存全局遍历的g属性

g: global

  • g对象 是专门用来保存用户的数据的
  • g对象 在一次请求中的所有的代码的地方,都是可以使用的

钩子函数(hook)

钩子函数

正常执行情况:先执行A,然后再执行B。

钩子函数:可以在运行时,把C直接插入到AB之间

before_request

  • before_request:在请求之前执行的函数,且每次请求都会执行一遍
  • before_request是在视图函数执行之前执行的
  • before_request这个函数只是一个装饰器,他可以把需要设置为钩子函数的代码放到视图函数执行之前来执行

context_processor(上下文处理器)

  • 出现的原因:多个不同的页面需要相同的参数,如:username
  • 上下文处理器应该返回一个字典,字典中的 key 会被模板当成变量来渲染
  • 上下文处理器中返回的字典,在所有页面中都是可用的。
  • 被这个装饰器修饰的钩子函数,必须要返回一个字典,即使为空,也要返回

装饰器

  • 需求1:在打印run之前,先要打印hello world

    1
    2
    3
    4
    5
    def run():
    print("hello world")
    print("run")

    run()
  • 需求2:在所有函数执行之前,都要打印一个 hello world

    • 方法一:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      def run():
      print("hello world")
      print("run")

      def run1():
      print("hello world")
      print("run1")

      run()
    • 方法二(如果成千上万个函数,比较麻烦,且难以维护):装饰器

      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
      # 装饰器的两个特别之处:
      # 1. 参数是一个函数
      # 2. 返回值是一个函数
      def myLog(func):

      def wrapper():
      print("hello world")
      func()

      """
      补充:
      wrapper: 函数体
      wrapper(): 执行wrapper这个函数
      """
      return wrapper

      # 如何用
      @myLog
      def run():
      print("run")
      """
      等价于上面
      run = myLog(run) <==> wrapper
      run() <==> wrapper() <==> print("hello world"); func() <==> print("hello world"); print("run")
      理解:func() 执行的才是真的 run() 方法
      """
      run()

讲解

  • 装饰器的两个特别之处:
    • 参数是一个函数
    • 返回值是一个函数
  • 上面的 myLog() 是无参情况下的装饰器

进阶:有参情况下的装饰器

需求:在所有函数执行之前,都要打印一个 hello world

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 装饰器的两个特别之处:
# 1. 参数是一个函数
# 2. 返回值是一个函数
def myLog(func):

def wrapper(a, b):
print("hello world")
func(a, b)
return wrapper

@myLog
def add(a, b):
print("结果是", a + b)
"""
等价于上面
add = myLog(add) <==> wrapper
add(1, 2) <==> wrapper(1, 2) # 因此,如果wrapper()无参,就会报错 `TypeError: wrapper() takes 0 positional arguments but 2 were given`
"""
add(1, 2)

再进阶:有无参同时存在情况下的装饰器

  • 解决办法: *args, **kwargs:可以表示任何参数

    • *args: 位置参数,如:add(a, b) 中的ab,参数是一一对应的
    • **kwargs:关键字参数(key=value),如:add(a=abc)a就是传入的abc
    1
    2
    3
    4
    5
    6
    def myLog(func):

    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper

    run(), add() 同上

再进阶

  • 问题:加完装饰器后,函数名被偷偷更改掉了,因此如何保留住原来的函数的名字(把名字改掉是很危险的行为)?

    • 没有装饰器的情况

      1
      2
      3
      def run():
      print("run")
      print(run.__name__) # run.__name__代表的是run这个函数的名称

      输出:

      run

    • 有装饰器的情况

      1
      2
      3
      4
      @myLog
      def run():
      print("run")
      print(run.__name__)

      输出:

      wrapper

  • 解决办法:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    func(*args, **kwargs)
    return wrapper
    • 没加 @wraps 的情况
      run() <=> myLog(run)() <=> wrapper()

    • 加了 @wraps(func) 的情况

      • run <=> myLog(run) <=> @wraps(func)装饰的wrapper <=> wraps(wrapper) <=> wrapsFunction(这个函数是透明的,即看不到的,但是不管怎么样,返回的函数的__name__是run) => wrapsFunction.__name__ == "run"
      • ``run() <=> myLog(run)() <=> @wraps装饰的wrapper() <=> wraps(wrapper)() <=> wrapsFunction()`

再进阶

  • 需求:如果run()有返回值,该怎么办?

  • 解决办法:在 wrapper()return func(*args, **kwargs)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    from functools import wraps

    def myLog(func):

    @wraps(func)
    def wrapper(*args, **kwargs):
    print("hello world")
    return func(*args, **kwargs)
    return wrapper

    @myLog
    def run():
    return 1+1
  • 讲解:

    1
    2
    3
    4
    5
    run = myLog(run) = wrapper
    ==>(推出,下同) run = wrapper
    ==> run() = wrapper() = print("hello world"); return run() = print("hello world"); return 1+1
    因为run()需要返回1+1,所以wrapper()也需要返回1+1
    如果wrapper没有return,那么func(*args, **kwargs),就只是执行了一下1+1,别的什么都没有了

小结

  • 装饰器的使用是通过 @ 符号,放在函数上面;
  • 装饰器中定义的函数,要使用 *args**kwargs 两对兄弟的组合,并且在这个函数中执行原始函数的时候也要把 *args**kwargs 传进去;
  • 需要使用 functools.wraps 在装饰器中的函数上把传进来的这个函数进行一个包裹,这样就不会丢失原来的函数 __name__ 等属性

实践

概述

地球村:现代科技缩小世界的时空距离

信件 ——> 网络编程

计算机网络

计算机网络是指将地理位置不同的具有独立功能的多台计算机机器外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及光网络通信协议(类似于方言、语言)的管理和协调下,实现资源共享和信息传递的计算机系统。

网络编程的目的

无线电台… :传播交流信息,数据交换。(通信

想要达到这个效果需要什么

  1. 如何准确的定位网络上的一台主机 :192.168.16.124:port,定位到这个计算机上的某个资源
  2. 找到了这个主机,如何传输数据呢?

javaweb: 网页编程 B/S架构

网络编程: TCP/IP

网络通信的要素

如何实现网络的通信?

通信双方的地址

  • ip (唯一(指的是公网,不是局域网))
  • port
  • 192.168.16.124:5900ip:port):就可以定位到某台计算机上的某一个应用

规则:网络通信的协议

http, ftp, smtp, tcp, udp, ….

TCP/IP参考模型:

本章目的:

小结

  • 网络编程中有两个主要的问题
    • 如何准确的定位网络上的一台或多台主机
    • 找到主机之后如何进行通信
  • 网络编程中的要素
    • IPportip
    • 网络通信协议:udp, tcp
  • 万物皆对象

IP

ip地址:InetAddress

用处

  • 唯一定位一台网络上计算机
  • 127.0.0.1(localhost): 本机
  • ip地址的分类
    • IP地址分类:IPV4/IPV6
      • IPV4: 如127.0.0.1。4个字节(32位)组成(0-255),全球42亿个(30亿都在北美,亚洲4亿,2011年就用尽了)
      • IPV6: 如 2001:0bb2:aaaa:0015:0000:00000:1aaa:1312。16字节(128位)组成,8个 无符号整数(4个字节),用的是16进制(16进制占4位)。
    • 公网(互联网使用)和私网(局域网使用)
      • 192.168.xx.xx:局域网,专门给组织内部使用
      • ABCD类地址
    • 域名:记忆 IP 问题
      • IP: www.vip.com

端口

端口表示计算机上的一个程序的进程(类似于:一栋楼代表一个 ip,门牌号代表 端口)

  • 不同的进程有不同的端口号,用来区分软件

  • 被规定:0~65535

  • TCP/UDP端口: 65535 * 2,单个协议下端口号不能冲突,不同协议下,端口可以冲突

  • 端口分类

    • 公有端口:(0~1023)最好不要用

      • HTTP: 80
      • HTTPS:43
      • FTP:21
      • SSH:22
      • Telent: 23
    • 程序注册端口:1024~49151,分配给用户或者程序,建议不要用

      • Tomcat: 8080
      • Mysql:3306
      • Oracle: 1521
    • 动态、私有:49152~65535,建议不要用

      • Idea:63342

      • 查看所有接口

        1
        2
        3
        netstat -ano # 查看所有端口
        netstat -ano|findstr "5900"# 管道流: |,查看指定的端口
        tasklist|findstr "8696" # 查看指定端口的进程

        打开任务管理器: ctrl+shift+esc

    • 代码

      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
      package com.kuangstudy.net.module4;

      import java.net.InetSocketAddress;

      /**
      * @author Qeuroal
      * @date 2021-03-21 16:15
      * @description
      * @since
      */
      public class TestInetSocketAddress {
      public static void main(String[] args) {

      InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8080);
      System.out.println(inetSocketAddress);
      InetSocketAddress inetSocketAddress2 = new InetSocketAddress("localhost", 8080);
      System.out.println(inetSocketAddress2);

      System.out.println(inetSocketAddress.getAddress());
      // 地址,可以更改hosts文件来更改映射
      System.out.println(inetSocketAddress.getHostName());
      // 端口
      System.out.println(inetSocketAddress.getPort());
      }
      }
    • 图片

通信协议

  • 协议: 约定,就好比我们现在说的普通话
  • 网络通信协议:针对于网络所产生的协议,如:速率,传输码率,代码结构,传输控制……
  • 问题:非常的复杂
  • 大事化小:分层
  • TCP/IP协议簇:实际上是一组协议,不止两个协议。

重要的协议:

  • TCP: 用户传输协议,类似于打电话
  • UDP: 用户数据报协议,类似于发短信

出名的协议:

  • TCP: 用户传输协议
  • IP:网络互连协议

TCP, UDP对比

  • TCP: 打电话

    • 连接:稳定

    • 连接:三次握手四次挥手

      • 三次握手:

        最少需要三次,保证稳定连接!

        A:你瞅啥?

        B:瞅你咋地?

        A:干一场!

      • 四次挥手

        A:我要走了!

        B:你真的要走了吗!

        B:你真的真的要走了吗?

        A:我真的要走了!

    • 客户端、服务端连接

    • 传输完成,释放连接,效率低

  • UDP: 发短信

    • 不连接:不稳定
    • 客户端、服务端连接:没有明确的界限
    • 不管有没有准备好,都可以发给你
    • 类似于导弹攻击
    • DDOS: 洪水攻击(饱和攻击)

TCP

客户端

  1. 连接服务器 socket
  2. 发送消息
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
package com.kuangstudy.net.module6;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-21 16:53
* @description 客户端
* @since
*/
public class TestTcpClient {
public static void main(String[] args) {
Socket socket = null;
OutputStream os = null;
try {
// 1. 要知道服务器的地址
InetAddress serverIP = InetAddress.getByName("127.0.0.1");
// 2. 端口号
int port = 9999;
// 3. 创建一个socket连接
socket = new Socket(serverIP, port);
// 4. 发送消息: io流
os = socket.getOutputStream();
os.write("你好,欢迎学习网络编程".getBytes());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

服务器

  1. 建立服务的端口 ServerSocket
  2. 等待用户的连接 accept
  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
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
75
package com.kuangstudy.net.module6;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-21 16:53
* @description 服务端
* @since
*/
public class TestTcpSever {
public static void main(String[] args) {
ServerSocket serverSocket = null;
Socket socket = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 1. 我得有一个地址
serverSocket = new ServerSocket(9999);
while (true) {
// 2. 等待客户端连接过来
socket = serverSocket.accept();
// 3. 读取客户端的消息
is = socket.getInputStream();

// 管道流
baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
System.out.println(baos.toString());
}

} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭资源
if (baos != null) {
try {
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}

if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}

文件上传

读取文件->流->传出去

服务器端

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
package com.kuangstudy.net.module7;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-22 16:13
* @description
* @since
*/
public class TestTcpFileServer {
public static void main(String[] args) throws Exception {
// 1. 创建服务
ServerSocket serverSocket = new ServerSocket(9000);
// 2. 监听客户端的连接
Socket socket = serverSocket.accept();// 阻塞式监听,会一直等待客户端连接
// 3. 获取输入流
InputStream is = socket.getInputStream();
// 4. 文件输出
FileOutputStream fos = new FileOutputStream(new File("receive.png"));
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
fos.write(buffer, 0, len);

}
// 5. 通知客户端接收完毕
OutputStream os = socket.getOutputStream();
os.write("我接收完毕了,你可以断开了".getBytes());
// 6. 关闭资源
fos.close();
is.close();
socket.close();
serverSocket.close();
}
}

客户端

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
package com.kuangstudy.net.module7;

import java.io.*;
import java.net.InetAddress;
import java.net.Socket;

/**
* @author Qeuroal
* @date 2021-03-22 16:07
* @description
* @since
*/
public class TestTcpFileClient {
public static void main(String[] args) throws Exception {
// 1. 创建一个socket连接
Socket socket = new Socket(InetAddress.getByName("127.0.0.1"), 9000);
// 2. 创建一个输出流
OutputStream os = socket.getOutputStream();
// 3. 读取文件
FileInputStream fis = new FileInputStream(new File("src/resource/xly2.png"));
// 4. 写出文件
byte[] buffer = new byte[1024];
int len;
while ((len = fis.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
// 5. 通知服务器,我已经结束了
socket.shutdownOutput(); // 我已经传输完了
// 5. 确定服务器接收完毕,才能够断开连接
InputStream is = socket.getInputStream();
// String byte[]
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer2 = new byte[1024];
int len2;
while ((len2 = is.read(buffer2)) != -1) {
baos.write(buffer2, 0, len2);
}
System.out.println(baos.toString());

// 5. 关闭资源
baos.close();
is.close();
fis.close();
os.close();
socket.close();
}
}

Tomcat

服务端

  • 自定义 S
  • Tomcat服务器 S: Java后台开发!

客户端

  • 自定义 S
  • 浏览器 B

UDP

发短信:不用连接,需要知道对方的地址


涉及到两个类:

  • DatagramPacket
  • DatagramSocket

发送端

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
package com.kuangstudy.net.module9;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
* @author Qeuroal
* @date 2021-03-22 17:29
* @description 不需要连接服务器
* @since
*/
public class TestUdpClient {
public static void main(String[] args) throws Exception {
// 1. 建立一个Socket
DatagramSocket socket = new DatagramSocket(); // 用来发东西的
// 2. 建个包
String msg = "你好啊,服务器!";
// 3. 发送给谁
InetAddress localhost = InetAddress.getByName("localhost");
int port = 9090;
// 数据,数据的长度起始,要发送给谁
DatagramPacket packet = new DatagramPacket(msg.getBytes(), 0, msg.getBytes().length, localhost, port);
// 4. 发送包
socket.send(packet);

// 5. 关闭
socket.close();
}
}

接收端

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
package com.kuangstudy.net.module9;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

/**
* @author Qeuroal
* @date 2021-03-22 17:35
* @description 还是要等待客户端的连接
* @since
*/
public class TestUdpServer {
public static void main(String[] args) throws Exception {
// 开放端口
DatagramSocket socket = new DatagramSocket(9090);
// 接收数据包
byte[] buffer = new byte[1024];
DatagramPacket packet = new DatagramPacket(buffer, 0, buffer.length); // 接收
socket.receive(packet); // 阻塞接收
System.out.println(packet.getAddress().getHostName());
System.out.println(new String(packet.getData(), 0, packet.getLength()));

// 关闭连接
socket.close();

}
}

本质上没有服务器:因为可以互相发送,因此就没有服务器的概念

咨询

类似于:广告的客服

xxx: 你好

xxx: 你好

  • BufferedReader : 包装流包装 System.in,为了控制台读取

    1
    new BufferedReader(new InputStreamReader(System.in))

循环发送消息

1

循环接收消息

1

在线咨询

两个人都是发送方,同时也都是接收方

TalkSend

1

TalkReceive

1

TalkStudent

1

TalkTeacher

1

URL

如:https://www.baidu.com/

统一资源定位符:定位资源的,定位互联网上的某一个资源

DNS域名解析: 将 www.baidu.com ==> xxx.xx.xx.xx

组成(可以少,但不能多)

1
协议://ip地址:端口号/项目名/资源
  • URL() : 网络类,代表一个地址
    • param: String
    • url.getProtocol: 得到协议名
    • url.getHost(): 得到主机ip
    • url.getPort(): 得到端口
    • url.getPath(): 文件地址
    • url.getFile(): 得到文件全路径
    • url.getQuery: 得到参数(如:查询的名字)
    • url.openConnection(): 打开连接
    • urlConnection.getInputStream(): 得到流
    • urlConnection.disconnect(): 断开连接

下载文件

  1. 下载地址
  2. 连接到这个资源,用 HTTP 连接
  3. 下载

getResource

getResource读取的是 out 下的文件,即 classpath

  • 相对路径: 即在当前包内的路径,如: Test.class.getResource("xly2.png");, xly2.png在当前包内,或者说和运行的class在同一目录下
  • 绝对路径: 用 / 表示,代表是当前项目下,如 Test.class.getResource("/resource/xly2.png"),如上图可见 resource 的位置

new File

读取的是 目录文件,如下所示

1
new FileInputStream(new File("src/resource/xly2.png"));

GUI简介

简介

GUI核心开发技术:Swing、AWT
不流行原因:

  • 界面不美观
  • 需要 jre 环境

为什么要学习?

  1. 可以写出自己心中想要的小工具
  2. 工作时候,也可能小维护到 swing 界面,概率极小
  3. 了解MVC架构,了解监听

AWT

AWT介绍

  1. 包含了很多类和接口用于GUI编程!GUI:图形用户界面
  2. 元素:窗口,按钮,文本框
  3. java.awt

组件和容器

Frame

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
import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 17:04
* @TODO GUI的第一个界面
* @since
*/
public class TestFrame {
public static void main(String[] args) {
// Frame 看源码
Frame frame = new Frame("我的第一个Java图像界面窗口");
// 设置可见性
frame.setVisible(true);
// 设置大小
frame.setSize(400, 400);
frame.setBackground(new Color(0, 255, 0));
// 设置弹出的初始位置
frame.setLocation(200, 200);
// 设置大小固定
frame.setResizable(false);

}

}

尝试封装

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
package com.kuangstudy.gui.module3;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 17:24
* @TODO
* @since
*/
public class TestFrame2 {
public static void main(String[] args) {
MyFrame myFrame1 = new MyFrame(100, 100, 200, 200, Color.BLUE);
MyFrame myFrame2 = new MyFrame(300, 100, 200, 200, Color.YELLOW);
MyFrame myFrame3 = new MyFrame(100, 300, 200, 200, Color.RED);
MyFrame myFrame4 = new MyFrame(300, 300, 200, 200, Color.MAGENTA);

}
}

class MyFrame extends Frame {
// 可能存在多个窗口,需要一个计数器
static int id = 0;
public MyFrame(int x, int y, int w, int h, Color color) {
super("MyFrame" + (++id));
setVisible(true);
setBounds(x, y, w, h);
setBackground(color);

}
}

面板Panel

Panel 可以看成是一个空间,但是不能单独存在,需要放在 Frame

解决了窗口关闭事件

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
package com.kuangstudy.gui.module4;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author QeuroIzo
* @date 2021-03-03 18:38
* @TODO
* @since
*/
public class TestPanel1 {
public static void main(String[] args) {
Frame frame = new Frame();
frame.setTitle("hello");
// 布局的概念
Panel panel = new Panel();

// 设置布局
frame.setLayout(null);
// 坐标
frame.setBounds(300, 300, 500, 500);
frame.setBackground(new Color(0, 255, 0));
// panel 设置坐标,相对位置
panel.setBounds(50, 50, 400, 400);
panel.setBackground(new Color(255, 0, 0));

// frame 添加 panel
frame.add(panel);
// 设置可见
frame.setVisible(true);

// 监听事件:监听窗口关闭事件 System.exit(0)
// 适配器模式:
frame.addWindowListener(new WindowAdapter() {
// 窗口点击关闭的时候需要做的事情
@Override
public void windowClosing(WindowEvent e) {
// 结束程序
System.exit(0);

}
});
}
}

布局管理器

  • 流式布局
  • 东西南北中
  • 表格布局

流式布局

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 19:01
* @TODO 流式布局
* @since
*/
public class TestFlowLayout1 {
public static void main(String[] args) {
Frame frame = new Frame();

// 组件-按钮
Button button1 = new Button("Button1");
Button button2 = new Button("Button2");
Button button3 = new Button("Button3");

// 设置为流式布局
// frame.setLayout(new FlowLayout()); // 默认center
// frame.setLayout(new FlowLayout(FlowLayout.LEFT));
frame.setLayout(new FlowLayout(FlowLayout.RIGHT));
frame.setSize(200, 200);
// 把按钮添加上去
frame.add(button1);
frame.add(button2);
frame.add(button3);
frame.setVisible(true);
}
}

东西南北中

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:02
* @TODO 东西南北中
* @since
*/
public class TestBorderLayout {
public static void main(String[] args) {
Frame frame = new Frame("Test");

Button east = new Button("East");
Button west = new Button("West");
Button south = new Button("South");
Button north = new Button("North");
Button center = new Button("Center");

frame.add(east, BorderLayout.EAST);
frame.add(west, BorderLayout.WEST);
frame.add(south, BorderLayout.SOUTH);
frame.add(north, BorderLayout.NORTH);
frame.add(center, BorderLayout.CENTER);

frame.setSize(200, 200);
frame.setVisible(true);



}
}

表格布局

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
package com.kuangstudy.gui.module5;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:20
* @TODO
* @since
*/
public class TestGridLayout {
public static void main(String[] args) {
Frame frame = new Frame("GridLayout");

Button btn1 = new Button("btn1");
Button btn2 = new Button("btn2");
Button btn3 = new Button("btn3");
Button btn4 = new Button("btn4");
Button btn5 = new Button("btn5");
Button btn6 = new Button("btn6");

frame.setLayout(new GridLayout(3, 2));
frame.add(btn1);
frame.add(btn2);
frame.add(btn3);
frame.add(btn4);
frame.add(btn5);
frame.add(btn6);

// java函数:自动布局
frame.pack();
frame.setSize(300, 300);
frame.setVisible(true);

}
}

练习

切记直接动手

正常的方式:构思(80%) -> 代码(20%)

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
package com.kuangstudy.gui.module6;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-03 21:32
* @TODO
* @since
*/
public class TestDemo {
public static void main(String[] args) {
Frame frame = new Frame("表格布局测试");
frame.setLayout(new GridLayout(2, 3));

Panel panelUp = new Panel();
panelUp.setLayout(new GridLayout(2, 1));
Panel panelDown = new Panel(new GridLayout(2, 2));
Button btn1 = new Button("btn");
Button btn2 = new Button("btn");
Button btn3 = new Button("btn");
Button btn4 = new Button("btn");
Button btn5 = new Button("btn");
Button btn6 = new Button("btn");
Button btn7 = new Button("btn");
Button btn8 = new Button("btn");
Button btn9 = new Button("btn");
Button btn10 = new Button("btn");

// panelUp 添加 btn
panelUp.add(btn2);
panelUp.add(btn3);
// panelDown 添加 Btn
panelDown.add(btn6);
panelDown.add(btn7);
panelDown.add(btn8);
panelDown.add(btn9);

frame.add(btn1);
frame.add(panelUp);
frame.add(btn4);
frame.add(btn5);
frame.add(panelDown);
frame.add(btn10);
frame.pack();

frame.setBounds(300, 300, 500, 400);
frame.setBackground(Color.BLUE);
frame.setVisible(true);
}
}

总结

  1. Frame 是一个顶级窗口
  2. Panel 无法单独显示,必须添加到某个容器中
  3. 布局管理器
    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
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
package com.kuangstudy.gui.module7;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author QeuroIzo
* @date 2021-03-05 15:39
* @TODO
* @since
*/
public class TestActionEvent {
public static void main(String[] args) {
// 按下按钮是,触发一些事件
Frame frame = new Frame();
// frame.
Button button = new Button("button");
// 因为 addActionListener() 需要一个 ActionListener,所以我们需要构造一个ActionListener
MyActionListener myActionListener = new MyActionListener();
button.addActionListener(myActionListener);

frame.add(button, BorderLayout.CENTER);
frame.pack();
frame.setVisible(true);
windowClose(frame);

}

/**
* 关闭窗体的事件
* @param frame Frame
*/
private static void windowClose(Frame frame) {
frame.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
}

/**
* 事件监听
*/
class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
System.out.println("aaa");
}
}

多个按钮共享一个事件

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
package com.kuangstudy.gui.module7;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 16:20
* @TODO
* @since
*/
public class TestActionEvent2 {
public static void main(String[] args) {
// 两个按钮,实现同一个监听
// 开始-停止
Frame frame = new Frame("开始-停止");
Button beginButton = new Button("start");
Button stopButton = new Button("stop");

// 可以显示的定义触发会返回的命令,如果不显示定义,则会走默认的只
// 可以多个按钮只写一个监听类
stopButton.setActionCommand("button-stop");
MyMonitor myMonitor = new MyMonitor();
beginButton.addActionListener(myMonitor);
stopButton.addActionListener(myMonitor);

frame.add(beginButton, BorderLayout.NORTH);
frame.add(stopButton, BorderLayout.SOUTH);

frame.pack();
frame.setVisible(true);


}
}

class MyMonitor implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// e.getActionCommand() 获得按钮的信息
System.out.println("按钮被点击了:msg=>" + e.getActionCommand());

}
}

输入框 TextField、监听

main方法里面只有一行代码:启动

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
package com.kuangstudy.gui.module8;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 16:32
* @TODO
* @since
*/
public class TestTextField {
public static void main(String[] args) {
// 启动
new MyFrame();
}
}

class MyFrame extends Frame {
public MyFrame() {
TextField textField = new TextField();
add(textField);
// 监听这个文本框输入的文字
MyActionListener myActionListener = new MyActionListener();
// 按下enter,就会触发这个输入框的事件
textField.addActionListener(myActionListener);
// 设置替换编码
textField.setEchoChar('*');

setVisible(true);
pack();
}
}

class MyActionListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// 获得一些资源,返回的一个对象(为什么Object可以向下转型,有的时候不是会报错吗- runtime error! ClassCastException?)
TextField field = (TextField) e.getSource();
// 获得输入框中的文本
System.out.println(field.getText());
// 设置清空
field.setText("");
}
}

简单计算器、组合+内部类回顾

oop原则:组合大于继承

继承

1
2
3
class A extends B {

}

组合

1
2
3
class A {
public B b;
}

目前代码

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
75
76
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {
public Calculator() {
// 三个文本框
TextField num1 = new TextField(10);
TextField num2 = new TextField(10);
TextField num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
button.addActionListener(new MyCalculatorListener(num1, num2, num3));
// 一个标签
Label label = new Label("+");

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}
}

/**
* 监听器类
*/
class MyCalculatorListener implements ActionListener {

/**
* 获取三个变量
*/
private TextField num1, num2, num3;
public MyCalculatorListener(TextField num1, TextField num2, TextField num3) {
this.num1 = num1;
this.num2 = num2;
this.num3 = num3;
}

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());

// 2. 将这个值加法运算后,放到第三个框
num3.setText("" + (n1 + n2));
// 3. 清楚前两个框
num1.setText("");
num2.setText("");
}
}

优化代码

在一个类中调用另一个类的引用

多用组合,最好不要用继承、多态:继承,增强了代码的耦合性;多态,使代码更麻烦,理解容易出错。使用组合的方式把代码拿过来。

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
75
76
77
78
79
80
81
82
83
84
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {

/**
* 属性
*/
public TextField num1, num2, num3;

/**
* 方法
*/
public void loadFrame() {
// 三个文本框
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
// 一个标签
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener(this));

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}

}

/**
* 监听器类
*/
class MyCalculatorListener implements ActionListener {

/**
* 获取计算器这个对象,在一个类中组合另外一个类
*/
private Calculator calculator = null;
public MyCalculatorListener(Calculator calculator) {
this.calculator = calculator;
}

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
// 2. 将这个值加法运算后,放到第三个框
// 3. 清楚前两个框

int n1 = Integer.parseInt(calculator.num1.getText());
int n2 = Integer.parseInt(calculator.num2.getText());
calculator.num3.setText("" + (n1 + n2));
calculator.num1.setText("");
calculator.num2.setText("");
}
}

完全改造为OOP写法

内部类

  • 更好的包装
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
75
76
package com.kuangstudy.gui.module9;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author QeuroIzo
* @date 2021-03-05 18:06
* @TODO 简易计算器
* @since
*/
public class CalculateDemo {
public static void main(String[] args) {
new Calculator().loadFrame();
}
}

/**
* 计算器类
*/
class Calculator extends Frame {

/**
* 属性
*/
private TextField num1, num2, num3;

/**
* 方法
*/
public void loadFrame() {
// 三个文本框
num1 = new TextField(10);
num2 = new TextField(10);
num3 = new TextField(20);
// 一个按钮
Button button = new Button("=");
// 一个标签
Label label = new Label("+");
button.addActionListener(new MyCalculatorListener());

// 布局
setLayout(new FlowLayout());

// 添加组件
add(num1);
add(label);
add(num2);
add(button);
add(num3);

pack();
setVisible(true);
}

/**
* 监听器(内部类)
* 内部类最大的好处,就是可以畅通无阻的访问外部类的属性和方法
*/
private class MyCalculatorListener implements ActionListener {

@Override
public void actionPerformed(ActionEvent e) {
// 1. 获得加数和被加数
// 2. 将这个值加法运算后,放到第三个框
// 3. 清楚前两个框

int n1 = Integer.parseInt(num1.getText());
int n2 = Integer.parseInt(num2.getText());
num3.setText("" + (n1 + n2));
num1.setText("");
num2.setText("");
}
}
}

画笔

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
package com.kuangstudy.gui.module10;

import java.awt.*;

/**
* @author QeuroIzo
* @date 2021-03-05 20:18
* @TODO
* @since
*/
public class TestPaint {
public static void main(String[] args) {
new MyPaint().loadFrame();
}
}

class MyPaint extends Frame {

public void loadFrame() {
setBounds(200, 200, 600, 500);
setVisible(true);
}
/**
* 画笔
* @param g
*/
@Override
public void paint(Graphics g) {
// super.paint(g);
// 画笔,需要有颜色,可以画画
g.setColor(Color.RED);
g.drawOval(100, 100, 100, 200);
// 实心圆
g.fillOval(100, 300, 100, 100);

g.setColor(Color.GREEN);
g.fillRect(200, 100, 100, 100);

// 养成习惯:画笔用完,将它还原到最初的颜色。
}
}

鼠标监听

目的:想要实现鼠标画画

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
75
76
77
78
79
package com.kuangstudy.gui.module11;

import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.Iterator;

/**
* @author QeuroIzo
* @date 2021-03-05 21:54
* @TODO 测试鼠标监听事件
* @since
*/
public class TestMouseListener {
public static void main(String[] args) {
new MyFrame("画图");
}
}

class MyFrame extends Frame {
// 画画需要画笔,需要监听鼠标当前的位置,需要集合来存储这个点
/**
* 存鼠标点击的点
*/
private ArrayList<Point> points;
public MyFrame(String title) {
super(title);
setBounds(200, 200, 600, 500);
// 存鼠标的点
points = new ArrayList<>();
// points.add(new Point(100, 100));
points.add(new Point(0, 0));
// points.add(new Point(200, 200));
// points.add(new Point(300, 300));


// 鼠标监听器,针对这个窗口
this.addMouseListener(new MyMouseListener());

setVisible(true);
}

@Override
public void paint(Graphics g) {
// 画画需要监听鼠标的事件
Iterator iterator = points.iterator();
while (iterator.hasNext()) {
Point point = (Point) iterator.next();
g.setColor(Color.BLUE);
g.fillOval(point.x, point.y, 100, 100);
}
}

/**
* 添加一个点到界面上
*/
public void addPaint(Point point) {
points.add(point);
}

/**
* 适配器模式
*/
private class MyMouseListener extends MouseAdapter {
// 只需要鼠标按下、弹起、按住不放

@Override
public void mouseClicked(MouseEvent e) {
MyFrame myFrame = (MyFrame) e.getSource();
// 点击的时候,就会在界面上产生一个点!
// 这个点就是鼠标的点
myFrame.addPaint(new Point(e.getX(), e.getY()));

// 每次点击鼠标都需要重画一遍(《=》刷新),每秒刷新 30帧或60帧
myFrame.repaint();
}
}
}

代码的思维导图,下图:

窗口监听

关掉某个窗口:即隐藏这个窗口

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
75
76
77
78
79
80
81
82
83
package com.kuangstudy.gui.module12;

import java.awt.*;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

/**
* @author Qeuroal
* @date 2021-03-09 16:28
* @description
* @since
*/
public class TestWindowListener {
public static void main(String[] args) {
new WindowFrame();
}
}

class WindowFrame extends Frame {
public WindowFrame() {
setVisible(true);
setBounds(200, 300, 300, 400);
setBackground(Color.RED);
// addWindowListener(new MyWindowListener());
//最好使用匿名内部类
this.addWindowListener(new WindowAdapter() {
// 监听不到
// @Override
// public void windowOpened(WindowEvent e) {
// System.out.println("windowOpened");
// }

/**
* 关闭窗口
* @param e
*/
@Override
public void windowClosing(WindowEvent e) {
System.out.println("windowClosing");
System.exit(0);
}

// 监听不到
// @Override
// public void windowClosed(WindowEvent e) {
// System.out.println("windowClosed");
// }

/**
* 激活窗口
* @param e
*/
@Override
public void windowActivated(WindowEvent e) {
// 获取事件所作用的对象,(获得事件监听的对象)即你所与该事件绑定的控件,例如你点击了按钮,那么得到的source就是按钮对象了
WindowFrame source = (WindowFrame) e.getSource();
source.setTitle("被激活了");
System.out.println("windowActivated");
}

/**
* 未被激活窗口,即切出去了
* @param e
*/
@Override
public void windowDeactivated(WindowEvent e) {
System.out.println("windowDeactivated");
}
});
}

/** 成员内部类
class MyWindowListener extends WindowAdapter {
@Override
public void windowClosing(WindowEvent e) {
// 隐藏窗口,通过按钮隐藏窗口
setVisible(false);
// 正常退出:0,非正常退出:1
System.exit(0);
}
}
*/
}

键盘监听

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
package com.kuangstudy.gui.module13;

import java.awt.*;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;

/**
* @author Qeuroal
* @date 2021-03-09 17:04
* @description
* @since
*/
public class TestKeyListener {
public static void main(String[] args) {
new KeyFrame();
}
}

class KeyFrame extends Frame {
public KeyFrame() {
setBounds(300, 400, 300, 400);
setVisible(true);

this.addKeyListener(new KeyAdapter() {
/**
* 键盘按下
* @param e
*/
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘下的键是哪个,当前键盘的码
int keyCode = e.getKeyCode();
// 不需要记录这个数值,直接使用静态属性 VK_xxx
System.out.println(keyCode);
if (keyCode == KeyEvent.VK_UP) {
System.out.println("你按下了上键");
}
// 根据按下的不同操作,产生不同结果
}
});
}
}

Swing

AWT 是底层,Swing 是给封装了

窗口、面板

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
package com.kuangstudy.gui.module14;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 17:17
* @description
* @since
*/
public class TestJFrame {
/**
* 初始化
*/
public void init() {
// 顶级窗口
JFrame jf = new JFrame("这是一个JFrame窗口");
jf.setVisible(true);
jf.setBounds(100, 100, 400, 300);

// 设置文字: Jlabel
JLabel label = new JLabel("欢迎来到JAVA GUI");

jf.add(label);

// 容器:需要实例化,JFrame本身也是一个容器,需要实例化
Container contentPane = jf.getContentPane();
contentPane.setBackground(Color.RED);

// 关闭事件
jf.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}
public static void main(String[] args) {
// 建立一个窗口
new TestJFrame().init();
}
}

标签居中

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
package com.kuangstudy.gui.module14;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 17:32
* @description
* @since
*/
public class TestJFrame2 {
public static void main(String[] args) {
new MyJFrame2().init();
}
}

class MyJFrame2 extends JFrame {
public void init() {
this.setVisible(true);
setBounds(300, 300, 400, 300);
// 设置文字: Jlabel
JLabel label = new JLabel("欢迎来到JAVA GUI");
// add(label) 和 this.add(label) 一样
add(label);
//设置水平对齐
label.setHorizontalAlignment(SwingConstants.CENTER);
// 获得一个容器
Container contentPane = this.getContentPane();
contentPane.setBackground(Color.RED);
}
}

弹窗

JDialog,用来被弹出,默认就有关闭事件

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
package com.kuangstudy.gui.module15;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

/**
* @author Qeuroal
* @date 2021-03-09 20:46
* @description 主窗口
* @since
*/
public class TestDialog extends JFrame {
public TestDialog() {
this.setVisible(true);
this.setBounds(400, 400, 400, 300);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// JFrame放东西:容器
Container contentPane = this.getContentPane();
// 绝对布局
contentPane.setLayout(null);

// 按钮
JButton jButton = new JButton("点击弹出一个对话框");
jButton.setBounds(30, 30, 200, 50);

// 点击这个按钮的时候,弹出一个弹窗
jButton.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
// 弹窗
new MyDialogDemo();
}
});
contentPane.add(jButton);
}

public static void main(String[] args) {
new TestDialog();
}
}

/**
* 弹窗的窗口
*/
class MyDialogDemo extends JDialog{
public MyDialogDemo() {
this.setVisible(true);
this.setBounds(300, 300, 300, 200);
// this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

Container contentPane = this.getContentPane();
contentPane.setLayout(null);

contentPane.add(new Label("学Swing"));
}
}

标签

label

  • 创建
1
new JLabel("xxx")
  • 实例
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
package com.kuangstudy.gui.module16;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 21:49
* @description 图标是一个接口,需要实现类,Frame继承
* @since
*/
public class TestIcon extends JFrame implements Icon {

public static void main(String[] args) {
// 首先生成TestIcon实例,通过这个实例再去生成新的TestIcon实例
new TestIcon().init();
}

private int width;
private int height;

public TestIcon() {}

public TestIcon(int width, int height) {
this.width = width;
this.height = height;
}

public void init() {
TestIcon testIcon = new TestIcon(30, 30);
// 图标放在标签上,也可以放在按钮上
JLabel iconTest = new JLabel("iconTest", testIcon, SwingConstants.CENTER);
Container contentPane = getContentPane();
contentPane.add(iconTest);
setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

@Override
public void paintIcon(Component c, Graphics g, int x, int y) {
g.fillOval(x,y,width,height);
}

@Override
public int getIconWidth() {
return width;
}

@Override
public int getIconHeight() {
return height;
}
}

Icon

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
package com.kuangstudy.gui.module16;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-09 22:14
* @description
* @since
*/
public class TestImageIcon extends JFrame {
public TestImageIcon() {
JLabel imageIconLabel = new JLabel("ImageIcon");
// 获取图片的地址
System.out.println(TestImageIcon.class);
URL resourceURL = TestImageIcon.class.getResource("/resource/xly2.png");
// 命名不要冲突了
ImageIcon imageIcon = new ImageIcon(resourceURL);

imageIconLabel.setIcon(imageIcon);
imageIconLabel.setHorizontalAlignment(SwingConstants.CENTER);

Container container = getContentPane();
container.add(imageIconLabel);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(100, 100, 300, 300);

}


public static void main(String[] args) {
new TestImageIcon();
}
}

面板

JPanel

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:36
* @description
* @since
*/
public class TestJPanel extends JFrame {
public TestJPanel() {
Container container = getContentPane();
//后面参数的意思是间距
container.setLayout(new GridLayout(2, 1, 10, 10));

JPanel panel1 = new JPanel(new GridLayout(1, 3));
JPanel panel2 = new JPanel(new GridLayout(1, 2));
JPanel panel3 = new JPanel(new GridLayout(2, 2));

panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel1.add(new JButton("1"));
panel2.add(new JButton("2"));
panel2.add(new JButton("2"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));
panel3.add(new JButton("3"));

container.add(panel1);
container.add(panel2);
container.add(panel3);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestJPanel();
}
}

JScrollPanel

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:45
* @description
* @since
*/
public class TestJScrollPanel extends JFrame {
public static void main(String[] args) {
new TestJScrollPanel();
}

public TestJScrollPanel() {
Container container = getContentPane();

// 文本域
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("请输入文本");

// Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 30);
}
}

按钮

图片按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:34
* @description
* @since
*/
public class TestButton extends JFrame {

public TestButton() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 把图标放在按钮上
JButton btn = new JButton();
btn.setIcon(imageIcon);
btn.setToolTipText("图片按钮");

// add
container.add(btn);
this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton();
}
}

单选按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:42
* @description
* @since
*/
public class TestButton2 extends JFrame {

public TestButton2() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 单选框
JRadioButton jRadioButton1 = new JRadioButton("JRadioButton1");
JRadioButton jRadioButton2 = new JRadioButton("JRadioButton2");
JRadioButton jRadioButton3 = new JRadioButton("JRadioButton3");

// 由于单选框只能选个一个,所以:分组,一个组中只能选一个
ButtonGroup buttonGroup = new ButtonGroup();
buttonGroup.add(jRadioButton1);
buttonGroup.add(jRadioButton2);
buttonGroup.add(jRadioButton3);

container.add(jRadioButton1, BorderLayout.CENTER);
container.add(jRadioButton2, BorderLayout.NORTH);
container.add(jRadioButton3, BorderLayout.SOUTH);

this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton2();
}
}

复选按钮

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
package com.kuangstudy.gui.module18;

import javax.swing.*;
import java.awt.*;
import java.net.URL;

/**
* @author Qeuroal
* @date 2021-03-15 22:48
* @description
* @since
*/
public class TestButton3 extends JFrame {

public TestButton3() {
Container container = this.getContentPane();
// 将一个图片变为图标
URL resource = TestButton.class.getResource("/resource/xly2.png");
Icon imageIcon = new ImageIcon(resource);

// 多选框
JCheckBox jCheckBox1 = new JCheckBox("jCheckBox1");
JCheckBox jCheckBox2 = new JCheckBox("jCheckBox2");

container.add(jCheckBox1, BorderLayout.NORTH);
container.add(jCheckBox2, BorderLayout.SOUTH);

this.setVisible(true);
this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
this.setBounds(300, 300, 400, 300);
}

public static void main(String[] args) {
new TestButton3();
}
}

列表

下拉框

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
package com.kuangstudy.gui.module19;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 22:53
* @description
* @since
*/
public class TestCombobox extends JFrame {
public TestCombobox() {
super("TestCombobox");
Container container = this.getContentPane();

JComboBox status = new JComboBox();
status.addItem(null);
status.addItem("正在热播");
status.addItem("已下架");
status.addItem("即将上映");

container.add(status);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestCombobox();
}
}

列表框

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
package com.kuangstudy.gui.module19;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:03
* @description
* @since
*/
public class TestCombobox2 extends JFrame {
public TestCombobox2() {
super("TestCombobox");
Container container = this.getContentPane();

// 生成列表的内容
String[] contents = {"1", "2", "3"};

JList jList = new JList(contents);
container.add(jList);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestCombobox2();
}
}
  • 应用场景
    • 下拉框:选择地区,或者一些单个选项
    • 列表框:展示信息,一般是动态扩容

文本框

文本框

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
package com.kuangstudy.gui.module20;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:11
* @description
* @since
*/
public class TestText extends JFrame {
public TestText() {
super("TestCombobox");
Container container = this.getContentPane();
container.setLayout(null);

JTextField jTextField1 = new JTextField("hello");
JTextField jTextField2 = new JTextField("world", 20);

// 东西南北中布局,会自动填充满
container.add(jTextField1, BorderLayout.NORTH);
container.add(jTextField2, BorderLayout.SOUTH);


setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestText();
}
}

密码框

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
package com.kuangstudy.gui.module20;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:14
* @description
* @since
*/
public class TestText2 extends JFrame {
public TestText2() {
super("TestCombobox");
Container container = this.getContentPane();

// 默认 ····
JPasswordField jPasswordField = new JPasswordField();
// 手动设置 ***
jPasswordField.setEchoChar('*');

container.add(jPasswordField);

setVisible(true);
setBounds(300, 300, 500, 300);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
}

public static void main(String[] args) {
new TestText2();
}
}

文本域

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
package com.kuangstudy.gui.module17;

import javax.swing.*;
import java.awt.*;

/**
* @author Qeuroal
* @date 2021-03-09 22:45
* @description
* @since
*/
public class TestJScrollPanel extends JFrame {
public static void main(String[] args) {
new TestJScrollPanel();
}

public TestJScrollPanel() {
Container container = getContentPane();

// 文本域
JTextArea jTextArea = new JTextArea(20, 50);
jTextArea.setText("请输入文本");

// Scroll面板
JScrollPane jScrollPane = new JScrollPane(jTextArea);
container.add(jScrollPane);

setVisible(true);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBounds(300, 300, 400, 30);
}
}

游戏实践:贪吃蛇

如果时间片足够小,就是动画:一秒30帧(人眼就是动画了)

连起来是动画,拆开就是静态的图片。如:动漫,1秒24张画

键盘监听

定时器 Timer

代码

StartGame

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
package com.kuangstudy.gui.snake;

import javax.swing.*;

/**
* @author Qeuroal
* @date 2021-03-15 23:29
* @description 游戏的主启动类
* @since
*/
public class StartGame {
public static void main(String[] args) {
JFrame frame = new JFrame();

// 是算出来的,不能被拉伸,否则就会变形了
frame.setBounds(200, 100, 900, 720);
// 窗口大小不可变
frame.setResizable(false);
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

// 整车游戏界面都在面板上
frame.add(new GamePanel());

frame.setVisible(true);
}
}

GamePanel

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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
package com.kuangstudy.gui.snake;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;

/**
* @author Qeuroal
* @date 2021-03-15 23:32
* @description 游戏的面板
* @since
*/
public class GamePanel extends JPanel implements KeyListener, ActionListener {

/**
* 定义蛇的数据结构
*/
// 蛇的长度
int length;
// 蛇的x坐标 25*25
int[] snakeX = new int[100];
// 蛇的Y坐标 25*25
int[] snakeY = new int[100];
// 初始方向
String fx;
// 游戏当前状态:开始,停止
boolean isStart= false;
// 食物的坐标
int foodX;
int foodY;
Random random = new Random();
// 积分
int score;
// 游戏失败状态
boolean isFail = false;
// 定时器:ms为单位,监听this这个对象。100毫秒执行一次。
Timer timer = new Timer(100, this);
/**
* 构造器
*/
public GamePanel() {
init();
// 获得焦点事件
this.setFocusable(true);
// 获取键盘事件
this.addKeyListener(this);
// 游戏一开始定时器就启动
timer.start();
}


/**
* 初始化方法
*/
public void init() {
length = 3;
// 脑袋的坐标
snakeX[0] = 100; snakeY[0] = 100;
// 第一个身体的坐标
snakeX[1] = 75; snakeY[1] = 100;
// 第二个身体的坐标
snakeX[2] = 50; snakeY[2] = 100;
fx = "R";
// 把食物随机放在界面上
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
// 积分
score = 0;
}


/**
* 绘制面板,我们游戏中的所有东西,都是用这个笔来画
* @param g
*/
@Override
protected void paintComponent(Graphics g) {
// 清屏
super.paintComponent(g);
setBackground(Color.WHITE);
// 绘制静态面板,头部广告栏画上去
Data.header.paintIcon(this, g, 25, 11);
// 默认的游戏界面
g.fillRect(25, 75, 850, 600);

// 画积分
g.setColor(Color.WHITE);
g.setFont(new Font("微软雅黑", Font.BOLD, 15));
g.drawString("长度: " + length,750, 35 );
g.drawString("分数: " + score, 750, 50);

// 画食物
Data.food.paintIcon(this, g, foodX, foodY);

// 把小蛇画上去
if (fx.equals("R")) {
Data.right.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("L")) {
Data.left.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("U")) {
Data.up.paintIcon(this, g, snakeX[0], snakeY[0]);
} else if (fx.equals("D")) {
Data.down.paintIcon(this, g, snakeX[0], snakeY[0]);
}
for (int i = 1; i < length; i++) {
Data.body.paintIcon(this, g, snakeX[i], snakeY[i]);
}

// 游戏状态
if (isStart == false) {
g.setColor(Color.WHITE);
// 设置字体
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("按下空格开始游戏", 300, 300);
}

if (isFail) {
g.setColor(Color.RED);
// 设置字体
g.setFont(new Font("微软雅黑", Font.BOLD, 40));
g.drawString("失败,按下空格重新开始游戏", 300, 300);
}
}



/**
* 键盘监听事件
* @param e
*/
@Override
public void keyPressed(KeyEvent e) {
// 获得键盘按键是哪一个
int keyCode = e.getKeyCode();
// 如果按下的是空格键
if (keyCode == KeyEvent.VK_SPACE) {
if (isFail) {
// 重新开始
isFail = false;
init();
} else {
isStart = !isStart;
}
repaint();

}
// 小蛇移动
if (keyCode == KeyEvent.VK_UP) {
fx = "U";
} else if (keyCode == KeyEvent.VK_DOWN) {
fx = "D";
} else if (keyCode == KeyEvent.VK_LEFT) {
fx = "L";
} else if (keyCode == KeyEvent.VK_RIGHT) {
fx = "R";
}
}

@Override
public void keyReleased(KeyEvent e) {
}
@Override
public void keyTyped(KeyEvent e) {
}

/**
* 事件监听——需要通过固定事件来刷新:10次/1s
* @param e
*/
@Override
public void actionPerformed(ActionEvent e) {
// 如果游戏是开始状态,就让小蛇动起来
if (isStart && isFail == false) {
// 吃食物
if (snakeX[0] == foodX && snakeY[0] == foodY) {
// 长度+1
length++;
// 分数+10
score += 10;
// 重新生成食物
foodX = 25 + 25 * random.nextInt(34);
foodY = 75 + 25 * random.nextInt(24);
}

// 移动:后一节移到前一节的位置
for (int i = length - 1; i > 0; i--) {
snakeX[i] = snakeX[i - 1];
snakeY[i] = snakeY[i - 1];
}
// 走向
if (fx.equals("R")) {
snakeX[0] += 25;
// 边界判断
if (snakeX[0] > 850) {
snakeX[0] = 25;
}
} else if (fx.equals("L")){
snakeX[0] -= 25;
if (snakeX[0] < 25) {
snakeX[0] = 850;
}
} else if (fx.equals("U")){
snakeY[0] -= 25;
if (snakeY[0] < 75) {
snakeY[0] = 650;
}
} else if (fx.equals("D")){
snakeY[0] += 25;
if (snakeY[0] > 650) {
snakeY[0] = 75;
}
}

// 失败判定:撞到自己就算失败
for (int i = 1; i < length; i++) {
if (snakeX[0] == snakeX[i] && snakeY[0] == snakeY[i]) {
isFail = true;
}
}

// 重画页面
repaint();
}
// 定时器开始
timer.start();
}
}

总结

补充

C/S:客户端+服务器 (主流:C++)

B/S:浏览器+服务器 (主流:Java)

0%