基于Java的Android计算器设计与实现
目录
一、设计分析 1
1.1 题目重述 1
1.2 设计思路 1
二、程序结构 2
三、各模块的功能及程序说明 4
3.1 计算器界面设计 4
3.2 界面各模块功能 8
3.3 ScienceCalculator接口 27
3.4 BaseCalculator 接口 32
四、操作流程 37
4.1 操作流程图 37
4.2 操作流程步骤 37
五、测试 38
5.1 弧度角度运算 38
5.2 数学表达式 40
5.3 包含科学计算的数学表达式 40
5.4 保留相应小数位数 41
5.5 处理异常 42
5.6 保存运算过程到文件 43
六、实验心得 45
一、设计分析
1.1 题目重述
本次实验为了实现一个保存计算过程的计算器,主要有以下三个要求:
1.仿照真实的计算器实现其功能。
2.在左上方的文本框中显示当前的计算过程,在右边的文本区中显示以往的计算过程。
3.单击“保存”按钮可以将文本区中的全部计算过程保存到文件;单击“复制”按钮可以将文本区中选中的文本复制到剪贴本;单击“清除”按钮可以清除文本区的全部内容。
1.2 设计思路
考虑到计算器的美观和易用性,我决定把计算器做在 Android 端,Android 系统的 App 的底层功能由 Java 实现,所以工作主要分为两部分:
1.设计计算器界面 (activity_main.xml) 计算器界面用xml文件编写,包括手机竖屏和横屏两个布局文件: 竖屏:activity_main.xml 布局为 portrait 横屏:activity_main_land.xml 布局为 landscape 竖屏模式可以完成基本的四则运算,不涉及科学计算 横屏模式除了完成基本的四则运算,还添加了科学运算
2.编写计算接口 (ScienceCalculator.java) ScienceCalculator 可以完成包含科学运算函数的 math,先实现可以完成基本四则运算的 BaseCalculator,在此基础上,实现 ScienceCalculator。 运算的思路是先通过 ScienceCalculator 完成math中需要科学计算函数的部分,本文转载自http://www.biyezuopin.vip/onews.asp?id=16770再用这些部分计算的结果替换原 math 中的这些部分,使包含科学计算函数的 math 转变成可用 BaseCalculator 计算的 math。
4.2 操作流程步骤
1.程序开始;
2.在手机上点击计算器APP,进入默认的计算器竖屏界面,通过点击按钮输入math表达式,按钮设置了响应事件的场景,避免了一些math 表达式的格式错误,最后完成math 表达式的输入;
3.点击 = 按钮进行计算,如果运算过程中出现除以0的情况或者格式错误的math表达式,输出Math Error,正常情况下完成math计算,输出计算结果;
4.此时用户有5个选择:
o继续输入math表达式计算
o点击保存按钮将文本区中的全部计算过程保存到文件
o点击复制按钮将文本区中选中的文本复制到剪贴本
o点击清除按钮将文本区的全部内容清除
o点击系统返回键退出计算器
5.用户在完成(3)中的1,2,3,4任意一个之后均可以点击系统返回键退出计算器;
6.用户将手机横屏,App切换到科学计算器的界面,同样完成(1),(2),(3),(4)操作;
7.程序结束。

package com.example.eric.mycal;import android.content.ClipboardManager;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Environment;
import android.support.v7.app.AppCompatActivity;
import android.text.method.ScrollingMovementMethod;
import android.view.View;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;import java.io.FileOutputStream;import butterknife.BindView;
import butterknife.ButterKnife;public class MainActivity extends AppCompatActivity {//tvNow,tvPast显示运算过程@BindView(R.id.tv_past)TextView tvPast;@BindView(R.id.tv_now)AutoScaleTextView tvNow; //继承TextView自适应屏幕宽度设置字体大小//保存,复制,清空tvPast中的运算过程@BindView(R.id.btn_save)Button btnSave;@BindView(R.id.btn_copy)Button btnCopy;@BindView(R.id.btn_clear)Button btnClear;//数字btns@BindView(R.id.btn_7)Button btn7;@BindView(R.id.btn_8)Button btn8;@BindView(R.id.btn_9)Button btn9;@BindView(R.id.btn_4)Button btn4;@BindView(R.id.btn_5)Button btn5;@BindView(R.id.btn_6)Button btn6;@BindView(R.id.btn_1)Button btn1;@BindView(R.id.btn_2)Button btn2;@BindView(R.id.btn_3)Button btn3;@BindView(R.id.btn_0)Button btn0;@BindView(R.id.btn_dot)Button btnDot;//基本运算符: + - × / ( ) =@BindView(R.id.btn_add)Button btnAdd;@BindView(R.id.btn_sub)Button btnSub;@BindView(R.id.btn_mul)Button btnMul;@BindView(R.id.btn_div)Button btnDiv;@BindView(R.id.btn_bracket)Button btnBracket;@BindView(R.id.btn_equal)Button btnEqual;//基本计算器功能:删除一个字符,清空当前math@BindView(R.id.btn_del)Button btnDel;@BindView(R.id.btn_clc)Button btnClc;//存储过去和当前的mathprivate String mathPast = "";private String mathNow = "";private int precision = 6;private int equal_flag = 0; //设置flag值判断是否需要清空mathNow进行新的运算private ScienceCalculator scienceCalculator = new ScienceCalculator(); //一个科学计算器//监听设备当前朝向@Overridepublic void onConfigurationChanged(Configuration newConfig) {super.onConfigurationChanged(newConfig);//如果为landscape,切换activityif (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {Intent intent = new Intent(MainActivity.this, LandActivity.class);intent.putExtra("main", tvPast.getText().toString()); //把当前的tvPast中的运算过程传递给LandActivitystartActivity(intent);finish(); //退出当前的activity}}@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//隐藏状态栏 manifest的application中要设置appTheme为NoActionBargetWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN);setContentView(R.layout.activity_main);ButterKnife.bind(this); //初始化控件//设置控件属性initTvPast();initNumBtns();initBaseOpers();initThreeFunctions();initBaseCalculatorFunction();}//初始化tvPastpublic void initTvPast() {//设置tvPast一些属性tvPast.setMovementMethod(ScrollingMovementMethod.getInstance()); //内容自动滚动到最新的一行tvPast.setTextIsSelectable(true); //长按复制//获取界面切换的tvPast的内容Intent intent = this.getIntent();String tvPastContent = intent.getStringExtra("land");//如果当前的界面是启动界面,不是从MainActivity切换来的,上面的mathPast就为null了,要处理这种异常if (tvPastContent == null) {tvPast.setText("");} else {String[] maths = tvPastContent.split("\n");int i;for (i = 0; i < maths.length - 1; i++) {tvPast.append(maths[i] + "\n");}tvPast.append(maths[i]); //最后一个math不用加换行}}//初始化数字btnspublic void initNumBtns() {btn0.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//如果flag=1,表示要输入新的运算式,清空mathNow并设置flag=0if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {                    //1.mathNow为空,+0mathNow += "0";} else if (mathNow.length() == 1) {             //2.mathNow 长度为1if (mathNow.charAt(0) == '0') {                 //2.1 如果该字符为0,不加mathNow += "";} else if (isNum(mathNow.charAt(0))) {          //2.2 如果该字符为1-9,+0mathNow += "0";}} else if (!isNum(mathNow.charAt(mathNow.length() - 2)) && mathNow.charAt(mathNow.length() - 1) == '0') {mathNow += "";                              //3.属于2.1的一般情况,在math中间出现 比如:×0 +0} else {                                        //4.除此之外,+0mathNow += "0";}tvNow.setText(mathNow);}});//btn 1-9 输入条件相同btn1.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "1";} else {//math的最后一个字符是:1-9, oper, (, .char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "1";}tvNow.setText(mathNow);}});btn2.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "2";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "2";}tvNow.setText(mathNow);}});btn3.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "3";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "3";}tvNow.setText(mathNow);}});btn4.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "4";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "4";}tvNow.setText(mathNow);}});btn5.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "5";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "5";}tvNow.setText(mathNow);}});btn6.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "6";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "6";}tvNow.setText(mathNow);}});btn7.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "7";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "7";}tvNow.setText(mathNow);}});btn8.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "8";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "8";}tvNow.setText(mathNow);}});btn9.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {mathNow += "9";} else {char ch = mathNow.charAt(mathNow.length() - 1);if (isNum(ch) || isOper(ch) || ch == '(' || ch == '.')mathNow += "9";}tvNow.setText(mathNow);}});btnDot.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {                                //1.mathNow为空,+0.mathNow += "0.";} else if (isOper(mathNow.charAt(mathNow.length() - 1))) {  //2.mathNow的最后一个字符为oper,+0.mathNow += "0.";} else if (isNum(mathNow.charAt(mathNow.length() - 1))) {   //3.mathNow的最后一个字符为num,+.mathNow += ".";} else {                                                    //4.除此之外,不加mathNow += "";}tvNow.setText(mathNow);}});}//初始化基本的运算符public void initBaseOpers() {btnAdd.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mathNow.length() == 0) {mathNow += "+";} else {if (isNum(mathNow.charAt(mathNow.length() - 1))|| mathNow.charAt(mathNow.length() - 1) == ')'|| mathNow.charAt(mathNow.length() - 1) == '('|| mathNow.charAt(mathNow.length() - 1) == 'π'|| mathNow.charAt(mathNow.length() - 1) == 'e')mathNow += "+";}tvNow.setText(mathNow);equal_flag = 0; //可能用运算结果直接运算,flag直接设0}});btnSub.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mathNow.length() == 0) {mathNow += "-";} else {if (isNum(mathNow.charAt(mathNow.length() - 1))|| mathNow.charAt(mathNow.length() - 1) == ')'|| mathNow.charAt(mathNow.length() - 1) == '('|| mathNow.charAt(mathNow.length() - 1) == 'π'|| mathNow.charAt(mathNow.length() - 1) == 'e')mathNow += "-";}tvNow.setText(mathNow);equal_flag = 0;}});btnMul.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mathNow.length() != 0) {if (isNum(mathNow.charAt(mathNow.length() - 1))|| mathNow.charAt(mathNow.length() - 1) == ')'|| mathNow.charAt(mathNow.length() - 1) == 'π'|| mathNow.charAt(mathNow.length() - 1) == 'e')mathNow += "×";}tvNow.setText(mathNow);equal_flag = 0;}});btnDiv.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mathNow.length() != 0) {if (isNum(mathNow.charAt(mathNow.length() - 1))|| mathNow.charAt(mathNow.length() - 1) == ')'|| mathNow.charAt(mathNow.length() - 1) == 'π'|| mathNow.charAt(mathNow.length() - 1) == 'e')mathNow += "/";}tvNow.setText(mathNow);equal_flag = 0;}});btnBracket.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (equal_flag == 1) {mathNow = "";equal_flag = 0;}if (mathNow.length() == 0) {                                //1.mathNow为空,+(mathNow += "(";} else if (isOper(mathNow.charAt(mathNow.length() - 1))) {  //2.mathNow最后一个字符是oper,+(mathNow += "(";} else if (isNum(mathNow.charAt(mathNow.length() - 1))      //3.mathNow最后一个字符是num, π, e|| mathNow.charAt(mathNow.length() - 1) == 'π'|| mathNow.charAt(mathNow.length() - 1) == 'e') {if (!hasLeftBracket(mathNow))                               //3.1 没有(, 加 ×(mathNow += "×(";else                                                        //3.2 已有(, 加 )mathNow += ")";} else if (mathNow.charAt(mathNow.length() - 1) == ')') {   //4.mathNow最后一个字符是),说明用户是在补全右括号,+)mathNow += ')';}tvNow.setText(mathNow);}});btnEqual.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//右括号自动补全int leftNum = 0;int rightNum = 0;for (int i = 0; i < mathNow.length(); i++) {if (mathNow.charAt(i) == '(')leftNum++;if (mathNow.charAt(i) == ')')rightNum++;}int missingNum = leftNum - rightNum; //缺失的 ) 数量while (missingNum > 0) {mathNow += ')';missingNum--;}tvNow.setText(mathNow);mathPast = "\n" + mathNow; //使得呈现的mathPast自动换行double result = scienceCalculator.cal(mathNow, precision, 0); //调用科学计算器if (result == Double.MAX_VALUE)mathNow = "Math Error";else {mathNow = String.valueOf(result);if (mathNow.charAt(mathNow.length() - 2) == '.' && mathNow.charAt(mathNow.length() - 1) == '0') {mathNow = mathNow.substring(0, mathNow.length() - 2);}}mathPast = mathPast + "=" + mathNow;//用tvPast.set(mathPast)不能实现自动滚动到最新运算过程tvPast.append(mathPast); //添加新的运算过程//tvPast滚动到最新的运算过程int offset = tvPast.getLineCount() * tvPast.getLineHeight();if (offset > tvPast.getHeight()) {tvPast.scrollTo(0, offset - tvPast.getHeight());}tvNow.setText(mathNow);equal_flag = 1; //设置flag=1}});}//保存,复制,清空public void initThreeFunctions() {//保存btnSave.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {//保存文件到sd卡 manifest文件中也要添加2个permissionif (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {String path = Environment.getExternalStorageDirectory().getPath() + "/math.txt"; //设置保存路径和文件名try {FileOutputStream outputStream = new FileOutputStream(path);outputStream.write(tvPast.getText().toString().getBytes()); //写字节outputStream.close(); //关闭输出流} catch (Exception e) {e.printStackTrace();}Toast.makeText(MainActivity.this, "保存到" + path, Toast.LENGTH_SHORT).show();}}});//复制btnCopy.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {ClipboardManager cm = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); //采用ClipboardManager类cm.setText(tvPast.getText());Toast.makeText(MainActivity.this, "已复制到剪切板", Toast.LENGTH_SHORT).show();}});//清空btnClear.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mathPast = "";tvPast.setText(mathPast);Toast.makeText(MainActivity.this, "计算过程已经清除", Toast.LENGTH_SHORT).show();}});}//初始化计算器基本Buttonpublic void initBaseCalculatorFunction() {btnClc.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {mathNow = "";tvNow.setText(mathNow);}});btnDel.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {if (mathNow.length() != 0) {mathNow = mathNow.substring(0, mathNow.length() - 1);tvNow.setText(mathNow);}}});}//判断当前字符是否为数字private boolean isNum(char c) {char num[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};int i = 0;for (; i < num.length; i++) {if (num[i] == c)break;}return i != num.length;}//判断当前字符是否为运算符private boolean isOper(char c) {char oper[] = {'+', '-', '×', '/'};int i = 0;for (; i < oper.length; i++) {if (oper[i] == c)break;}return i != oper.length;}//判断当前math是否有')'private boolean hasLeftBracket(String s) {int i = 0;for (; i < s.length(); i++) {if (s.charAt(i) == '(')break;}return i != s.length();}
}
















基于Java的Android计算器设计与实现相关推荐

  1. 简单计算器的设计java_(基于java的简易计算器的设计.doc

    (基于java的简易计算器的设计 基于java的简易计算器的设计 摘要 自从java语言诞生以来,java语言就以不可抵挡的趋势很快成为国际上广泛流行的面向对象编程语言,它既具有高级语言的特点,又少了 ...

  2. 基于java平台简易计算器_基于java的简易计算器的设计

    基于java的简易计算器的设计 基于java的简易计算器的设计 摘要 自从java语言诞生以来,java语言就以不可抵挡的趋势很快成为国际上广泛流行的面向对象编程语言,它既具有高级语言的特点,又少了C ...

  3. java技术论坛的毕业设计_基于java的bbs论坛设计,软件毕业设计

    基于java的bbs论坛设计,软件毕业设计 XXX毕 业 设 计 任 务 书专业 软件技术 年级 xx 级 班级 二班姓名 xx 学号 20 xx0205xx3威 海 职 业 学 院 教 务 处 编 ...

  4. 基于FPGA的电子计算器设计(下)

    今天给大侠带来基于FPGA的电子计算器设计,由于篇幅较长,分三篇.今天带来第三篇,下篇,话不多说,上货. 导读 本篇介绍了一个简单计算器的设计,基于 FPGA 硬件描述语言 Verilog HDL,系 ...

  5. 基于Java的Android区块链开发之生成助记词(位数可选)

    基于Java的Android区块链开发之生成助记词 位数可选 具体实现代码 这里使用bitcoinj库,来实现生成bip39的12个助记词,引用库 implementation 'org.bitcoi ...

  6. 基于FPGA的电子计算器设计(上)

    今天给大侠带来基于FPGA的电子计算器设计,由于篇幅较长,分三篇.今天带来第一篇,上篇,话不多说,上货. 导读 本篇介绍了一个简单计算器的设计,基于 FPGA 硬件描述语言 Verilog HDL,系 ...

  7. 基于JAVA读书网络社区设计计算机毕业设计源码+系统+lw文档+部署

    基于JAVA读书网络社区设计计算机毕业设计源码+系统+lw文档+部署 基于JAVA读书网络社区设计计算机毕业设计源码+系统+lw文档+部署 本源码技术栈: 项目架构:B/S架构 开发语言:Java语言 ...

  8. 基于Java毕业设计宠物论坛设计网站测试视频2021源码+系统+mysql+lw文档+部署软件

    基于Java毕业设计宠物论坛设计网站测试视频2021源码+系统+mysql+lw文档+部署软件 基于Java毕业设计宠物论坛设计网站测试视频2021源码+系统+mysql+lw文档+部署软件 本源码技 ...

  9. java程序实现剪刀石头布功能_基于JAVA的剪刀石头布游戏设计——Java课程设计报告_...

    基于JAVA的剪刀石头布游戏设计 1剪刀石头布游戏设计思路阐述 在设计游戏时,我们要充分考虑到剪刀石头布游戏的特性,以及多种技术的实现: ⑴构造创建服务器端和客户端 ⑵创建输入输出流 ⑶编写服务器端处 ...

  10. 基于Java日程表系统的设计与实现

    基于Java日程表系统的设计与实现 编写语言是Java Swing,使用Mybatis框架操作数据库.部分界面效果图如下: 登录界面: 用户在没有账号之前,通过输入用户信息(用户ID,用户呢称,性别, ...

最新文章

  1. centos安装配置nginx,ssl生产和配置教程
  2. 石家庄学院c语言试题,谁会高级语言程序设计?要求用C语言,帮帮我把,愁死我啦...
  3. 人工智能的概念和知识构架_概念验证:玩! 构架
  4. linux安装虚拟环境virtualenv
  5. Atitit.vod 视频播放系统 影吧系统的架构图 架构体系 解决方案
  6. 如果编程语言是女孩,你最喜欢哪一个?
  7. [转万一] 不使用标题栏拖动窗体
  8. 数学_最小二乘问题的求解
  9. MyBatis使用总结+整合Spring
  10. 代写品牌故事四步写作方法
  11. 极客空间-MySQL实战45天-第一天
  12. php codeigniter 语言,CodeIgniter多语言实现方法详解
  13. 麒麟桌面系统时间相关命令介绍
  14. 交通模拟仿真平台指南(含数据说明)
  15. 我的世界服务器回到死亡位置,我的世界返回死亡地点指令是什么
  16. C语言课程设计——学生成绩管理系统(详细报告)
  17. 2021 上半年,写给自己
  18. ABAP编程语言-概览
  19. 如何解决问题并给出解决方案
  20. RIGOL示波器上位机程序DEMO v1.0

热门文章

  1. 电源管理总线 (PMBus)
  2. C#andSql获取当前日期格式
  3. JAVA实现MD5算法、SHA1算法和SHA256算法
  4. matlab空间直线拟合,使用matlab进行空间拟合
  5. VIIRS和DMSP夜间灯光数据(1992-2020)
  6. TSC TTP-244 Pro标签打印机安装说明
  7. p67对应的服务器芯片组,使用 H67/P67 高速芯片组 Express 芯片组测试了适用于英特尔®台式机主板的外设...
  8. Odoo14免费开源ERP实施手册:销售功能高级应用
  9. 【IT基础】如何休息(每天都很累,但你真的会正确的休息方式么)
  10. 三步完成自动抢票,又一款春节免费抢票神器推荐