如何用 Andriod Studio 做一个简易学习 App - 入门
第一次写博客, 可能会有很多不足, 学校的项目设计迫在眉睫, 虽然我已经学习 andriod 快两个月了, 视频推荐看小破站的天哥在奔跑.
因为我实在太菜了, 一直以来入了很多坑, 本博客也是给出自己的一个小例子.
有一些方法和类是借鉴的网上的资料, 如果侵权的, 联系我删除.
有一些界面还没有实现, 如果大家有更好的办法, 求联系, 一起改进.
我的设计感很差, 可能会比较丑, 因为我是兼了 UI 设计, hhh
希望大家有问题直接在这个上面留言, 我这周还有一个任务要做, 所以会比较忙, 但是有时间会和大家一起探讨.
文件目录
我刚开始接触的时候, 看网上大佬们写的文件, 都不知道该放在哪里, 所以我这里先把文件所在的目录贴出来.
整个软件的设计思路
我就直接把 mindmap 贴出来了, 太难的打字了, 现在已经 12 点了.
正题开始 - 登录和注册界面
设计的很丑, 先看整体效果
那么这个界面的 layout 文件是怎么实现的
需要一个 textview 放我们的标题
需要两个 edittext 作为密码和账号的输入
需要两个按钮实现登录和注册的操作
下面展示一些 内联代码片, 这个具体的原因我就不解释了.
- <?xml version="1.0" encoding="utf-8"?>
- <RelativeLayout xmlns:Android="http://schemas.android.com/apk/res/android"
- xmlns:App="http://schemas.android.com/apk/res-auto"
- xmlns:tools="http://schemas.android.com/tools"
- Android:layout_width="match_parent"
- Android:layout_height="match_parent"
- Android:background="@drawable/background"
- tools:context=".MainActivity"
- Android:paddingTop="100dp">
- <TextView
- Android:id="@+id/tv_title"
- Android:layout_width="match_parent"
- Android:layout_height="wrap_content"
- Android:text="E.va"
- Android:gravity="center"
- Android:textColor="@color/colorBlue"
- Android:textSize="20sp"
- />
- <EditText
- Android:id="@+id/et_1"
- Android:layout_width="match_parent"
- Android:layout_height="50dp"
- Android:textSize="16sp"
- Android:textColor="#000000"
- Android:hint="用户名"
- Android:background="@drawable/bg_username"
- Android:layout_marginLeft="10dp"
- Android:layout_marginRight="10dp"
- Android:maxLines="1"
- Android:paddingLeft="10dp"
- Android:paddingRight="10dp"
- Android:layout_marginTop="10dp"
- Android:layout_below="@id/tv_title"
- />
- <EditText
- Android:id="@+id/et_2"
- Android:layout_width="match_parent"
- Android:layout_height="50dp"
- Android:textSize="16sp"
- Android:textColor="#000000"
- Android:hint="密 码"
- Android:layout_marginLeft="10dp"
- Android:layout_marginRight="10dp"
- Android:inputType="textPassword"
- Android:layout_below="@id/et_1"
- Android:layout_marginTop="10dp"
- Android:background="@drawable/bg_username"
- Android:maxLines="1"
- Android:paddingLeft="10dp"
- Android:paddingRight="10dp"
- />
- <LinearLayout
- Android:layout_width="match_parent"
- Android:layout_height="wrap_content"
- Android:orientation="horizontal"
- Android:layout_below="@id/et_2"
- Android:paddingLeft="10dp"
- Android:paddingRight="10dp"
- >
- <Button
- Android:id="@+id/btn_login"
- Android:layout_width="0dp"
- Android:layout_weight="1"
- Android:layout_height="50dp"
- Android:gravity="center"
- Android:layout_marginTop="20dp"
- Android:text="登录"
- Android:textSize="18sp"
- Android:textColor="#000000"
- Android:background="@drawable/bg_btn4"
- />
- <Button
- Android:id="@+id/ben_register"
- Android:layout_width="0dp"
- Android:layout_weight="1"
- Android:layout_height="50dp"
- Android:gravity="center"
- Android:layout_marginTop="20dp"
- Android:text="注册"
- Android:textSize="18sp"
- Android:textColor="#000000"
- Android:background="@drawable/bg_btn4"
- />
- </LinearLayout>
- </RelativeLayout>
那么怎么实现登录和注册呢, 太巧了, 我现在也不是很会服务器端的登录和注册, 如果有会的, 希望我们一起探讨. 那么下面我给出一种, 已知账号和密码的登录方式
实现 button 的跳转, 都是很固定的操作, 可以通过下面的探讨和前面说的视频学习
下面展示一些 内联代码片.
package com.example.eva; import Android.content.Intent; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.view.View; import Android.widget.Button; import Android.widget.EditText; import com.example.eva.util.ToastUtil; public class MainActivity extends AppCompatActivity implements View.OnClickListener { // 给控件声明 private Button mBtnLogin; private Button mBtnRegister; private EditText mTUserName; private EditText mTPassword; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 找到控件 mBtnLogin = findViewById(R.id.btn_login); mBtnRegister = findViewById(R.id.ben_register); mTUserName = findViewById(R.id.et_1); mTPassword = findViewById(R.id.et_2); // 暂时只对登录设置了情况, 采用提前注入信息的形式 mBtnLogin.setOnClickListener(this); } @Override public void onClick(View v) { // 获取输入的信息 String username = mTUserName.getText().toString(); String password = mTPassword.getText().toString(); String ok = "登陆成功"; String fail = "密码错误, 登陆失败"; Intent intent = null; if(username.equals("lyh")&&password.equals("123456")){ // 这个类是自己封装的一个类 ToastUtil.showMsg(MainActivity.this,ok); // 下面实现的是登陆界面的跳转 intent = new Intent(MainActivity.this, UiActivity.class); startActivity(intent); }else{ // 登录成功或者失败我们都需要弹出一个信息来告诉用户是成功还是失败 ToastUtil.showMsg(MainActivity.this,fail); } } @Override public void onPointerCaptureChanged(boolean hasCapture) { } }
登陆界面说完, 我们来说具体的几个功能
还是先把功能的图片附上吧
刚开始
看图片我们可以得出这个界面我们一共需要一个 textview 和四个 button, 并实现对应 button 的跳转, 我在想要不要讲一下跳转, 我们先放在这把, 如果后面我写完还没到三点的话, 我就给大家讲一下我的理解.
我们看一下这个部分的布局文件
下面展示一些 内联代码片.
<TextView Android:layout_width="match_parent" Android:layout_height="30dp" Android:text="选择学习类型" Android:gravity="center" Android:layout_marginTop="200dp" Android:textColor="#000000"/> <Button Android:id="@+id/btn_1" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="背单词" Android:textColor="#000000" Android:layout_marginTop="10dp" Android:background="@drawable/bg_btn4" /> <Button Android:id="@+id/btn_2" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="阅读理解" Android:textColor="#000000" Android:background="@drawable/bg_btn4" /> <Button Android:id="@+id/btn_3" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="听力测试" Android:background="@drawable/bg_btn4" Android:textColor="#000000" /> <Button Android:id="@+id/btn_4" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="今日小测" Android:background="@drawable/bg_btn4" Android:textColor="#000000" />
在这还是讲一下 button 里面的各种性质吧
id 名, 如果你要设置 button 点击事件的话, 这个是必须要的哈, 不然找不到文件的
width 和 height 顾名思义就是高度和宽度的设置, match_parent 的意思就是和父类的一样, wrap_content 你可以理解为有多宽显示多宽
text 还有一个是可以放图片的 Android:drawableLeft, 如果想要 button 里面有一个图片的话, 可以用这个
界面说完了, 我们来看一下, 这个的跳转怎么实现的, 我方的基本都是源码, 没有修改的, 如果直接赋值的话, 可能会报很多错误, 因为很多文件你还没有去创建, 慢慢创建, 最后就不会出错了.
这个代码, 我建议大家可以保存一下, 因为这个监听事件写的真的是太漂亮了, 不管是排版还是可读性来看, 下面展示一些 内联代码片.
package com.example.eva; import Android.content.Intent; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.view.View; import Android.widget.Button; public class UiActivity extends AppCompatActivity { private Button mBtnWord; private Button mBtnRead; private Button mBtnListen; private Button mBtnTest; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_ui_acticity); mBtnWord = findViewById(R.id.btn_1); mBtnRead = findViewById(R.id.btn_2); mBtnListen = findViewById(R.id.btn_3); mBtnTest = findViewById(R.id.btn_4); // 设置监听事件 setListener(); } private void setListener(){ OnClick onClick = new OnClick(); mBtnWord.setOnClickListener(onClick); mBtnRead.setOnClickListener(onClick); mBtnListen.setOnClickListener(onClick); mBtnTest.setOnClickListener(onClick); } private class OnClick implements View.OnClickListener{ @Override public void onClick(View v) { Intent intent = null; switch (v.getId()) { case R.id.btn_1: intent = new Intent(UiActivity.this, WordActivity.class); break; case R.id.btn_2: intent = new Intent(UiActivity.this, CompreActivity.class); break; case R.id.btn_3: intent = new Intent(UiActivity.this, ListenActivity.class); break; case R.id.btn_4: intent = new Intent(UiActivity.this, TestActivity.class); break; } startActivity(intent); } } }
主界面说完了, 我们来看看具体的小界面是怎么做的
好吧, 才过了 20 分钟, 是我讲的太水了吗? 哎, 初版先这个亚子吧, 后面我们在一起修改, 毕竟听学长说在项目设计这块, 通院还是挺团结的, hhh
第一个界面, 背单词界面, 仿造的是百词斩, 很多部分还没做全, 我就不一直解释了, 以后我们再来改, 先看图.
第一个界面用的都是我们之前讲的知识, textview 和一个 button, 但是这里有一个比较巧妙地地方, 我还是说一下, 大家可以看到今日单词和学习天数是并列和平分的对吧, 这里其实是一个权重的应用, 大家都是工科生, 应该还是明白的吧... 我把代码贴出来, 大家仔细理解一下
下面展示一些 内联代码片.
<LinearLayout Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:orientation="horizontal" Android:layout_below="@+id/tv_word" Android:layout_marginTop="30dp" Android:id="@+id/ll_1" > <--- 首先我们在最开始的 LinearLayout 文件下套一个 LinearLayout---> <TextView Android:layout_width="0dp" Android:layout_weight="1" Android:layout_height="100dp" Android:text="今日单词: 20" Android:textColor="@color/colorBlack" Android:gravity="center" Android:textSize="20sp" Android:background="@drawable/bg_username" Android:paddingLeft="10dp" /> <--- 大家仔细看这里的两个的 width 和之前有什么不同, 并且多了 weight 这个, 这便是我们刚刚说的权重的问题, 两个权重一样, 自然是评分, 三个自然就是各占 1\3---> <TextView Android:layout_width="0dp" Android:layout_weight="1" Android:layout_height="100dp" Android:text="学习天数: 100" Android:textColor="@color/colorBlack" Android:gravity="center" Android:textSize="20sp" Android:background="@drawable/bg_username" Android:paddingRight="10dp" /> </LinearLayout>
这个按钮的跳转
下面展示一些 内联代码片.
package com.example.eva; import Android.content.Intent; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.view.View; import Android.widget.Button; public class WordActivity extends AppCompatActivity { private Button mBtnWordStart; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_word); mBtnWordStart = findViewById(R.id.btn_wordstart); mBtnWordStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = null; intent = new Intent(WordActivity.this,WordTestActivity.class); startActivity(intent); } }); } }
下面展示一些 内联代码片.
<ImageView Android:id="@+id/iv_grid" Android:layout_width="160dp" Android:layout_height="120dp" Android:scaleType="centerCrop" Android:background="@drawable/plane" Android:layout_gravity="center" />
那么第二个切单词的是怎么做的呢, 仔细一看, 你会发现, 这里面怎么又图片, 我怎么把图片加载进去呢?
第一步, 把你需要的图片加入到 drawable 下面的 drawable_xhdpi, 不要问我为甚这么命名, 我也不知道, hh, 教程和一些书籍都是这样命名的, 记住, 千万不要直接放在 drawable 下面, 如果不信, 你们可以试一试, 原因, 百度就知道了哈.
然后我们来分析这个布局应该怎么去写.
这里介绍一个 GridView, 顾名思义, 表格视图, 比如可以联系我们的这个图片, 这里我需要四个表格来放四张图片
下面展示一些 内联代码片.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android" xmlns:App="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" Android:layout_width="match_parent" Android:layout_height="match_parent" tools:context=".WordTestActivity" Android:background="@drawable/background_2" Android:orientation="vertical"> <TextView Android:id="@+id/tv_1" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="technology" Android:textSize="30sp" Android:textColor="@color/colorBlack" Android:gravity="center" Android:layout_marginTop="170dp" /> <GridView Android:id="@+id/gv" Android:layout_width="match_parent" Android:layout_height="400dp" Android:numColumns="2" Android:horizontalSpacing="10dp" Android:verticalSpacing="10dp" Android:layout_marginTop="20dp"/> </LinearLayout>
显然到这里我们还没有实现将图片放进去的操作, 大家可以把它想象成我先把空间划分好, 具体的内容我需要到另一个文件中添加, 那么这个文件是什么呢? 我也把代码给大家附在下面, 新建一个 layout_grid_item 文件, 在该文件下放 item 的样子下面展示一些 内联代码片.
<ImageView Android:id="@+id/iv_grid" Android:layout_width="160dp" Android:layout_height="120dp" Android:scaleType="centerCrop" Android:background="@drawable/plane" Android:layout_gravity="center" />
我们只需要一个图片, 那么这个问价下面自然只需要一个放图片的控件就可以., 啊, 我好困, 但这个真的很不好理解, 我学的时候都想了好久好久
接下来, 我们文件写好了, 就要在对应的 java 文件中去实现下面展示一些 ` 内联代码片.
package com.example.eva; import Android.App.Activity; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.text.TextUtils; import Android.view.View; import Android.widget.AdapterView; import Android.widget.GridView; import Android.widget.SimpleAdapter; import com.example.eva.util.ToastUtil; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class WordTestActivity extends Activity { private GridView mGv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_word_test); mGv = findViewById(R.id.gv); mGv.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { // 去判断他点击的是不是正确的图片, 这里是举例, 直接给定的结果 if(position == 1){ ToastUtil.showMsg(WordTestActivity.this,"恭喜你, 选择正确"); }else{ ToastUtil.showMsg(WordTestActivity.this,"选择错误, 请仔细一点哦"); } } }); int[] imageAry = new int[]{ R.drawable.plane,R.drawable.technology,R.drawable.cat,R.drawable.battle }; List list = new ArrayList(); for (int i = 0;i<imageAry.length;i++){ Map map = new HashMap(); map.put("image",imageAry[i]); list.add(map); } // 实例化 SimpleAdapter 适配器的对象 SimpleAdapter adapter = new SimpleAdapter(this,list,R.layout.layout_grid_item, new String[]{"image"},new int[]{R.id.iv_grid}); // 获得 GridView 组件 GridView gridView = (GridView) findViewById(R.id.gv); // 向 GridView 中添加内容 gridView.setAdapter(adapter); } }
11.emm... 这个 adapter 大家要是不明白的话建议看一下天哥的视频, 到现在已经又 13000 多字了, 如果解释的话, 会有些冗杂
12. 到现在为止呢, 你已经实现了我们斩单词界面的设计了, 如果成功的话, 你会很有成就感的, hhh
接下来, 我就挑一下八, 讲一下这个听力测试的这个部分
还是一样, 我们先把图片放出来, 先思考一下这个界面我们应该怎么去设计
我这里比较简洁哈
两个 textview, 三个 button, 以及一个我们之前都没有讲到的多选框, 因为单独讲单选和多选太累了, 后面在添加吧.
实现多选呢, 我这里用了四个 checkbox 来实现
最后结果, 这里简单, 我是只要提交都是 100 分, 大家可以去判断哪个复选框选中来给出正确还是失败, 学习一直都是模仿到自己做, 也是给自己的一个锻炼把, 说了这么多, 其实是因为我也还没有写, hhh
activity_listen.xml 文件 内联代码片.
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:Android="http://schemas.android.com/apk/res/android" xmlns:App="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" Android:layout_width="match_parent" Android:layout_height="match_parent" Android:orientation="vertical" Android:background="@drawable/background_2" tools:context=".ListenActivity" Android:padding="20dp"> <TextView Android:id="@+id/tv_listen_title" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="听力测试" Android:textColor="@color/colorBlack" Android:gravity="center_horizontal" Android:textSize="25sp" Android:layout_marginTop="20dp"/> <Button Android:id="@+id/btn_play" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="播放听力" Android:textColor="@color/colorBlack" Android:layout_marginTop="10dp"/> <Button Android:id="@+id/btn_pause" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="暂停播放" Android:textColor="@color/colorBlack" /> <LinearLayout Android:id="@+id/ll_listen_question" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:orientation="vertical" Android:padding="20dp"> <TextView Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="Section A" Android:textAllCaps="false" Android:textColor="@color/colorBlack" Android:textSize="20sp" Android:gravity="center" Android:layout_marginTop="15dp"/> <TextView Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="Question 1 to 4 are based on the conversation you have just heard" Android:textColor="@color/colorBlack" Android:layout_marginTop="10dp" Android:layout_marginBottom="10dp"/> <CheckBox Android:id="@+id/cb_1" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="Buy a used car" Android:textColor="@color/colorBlack"/> <CheckBox Android:id="@+id/cb_2" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:layout_below="@id/cb_1" Android:text="Have his car repaired" Android:textColor="@color/colorBlack"/> <CheckBox Android:id="@+id/cb_3" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:layout_below="@id/cb_2" Android:text="Pass a driving test" Android:textColor="@color/colorBlack"/> <CheckBox Android:id="@+id/cb_4" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:layout_below="@id/cb_3" Android:text="Sell a car" Android:textColor="@color/colorBlack"/> <Button Android:id="@+id/btn_listen_submit" Android:layout_width="match_parent" Android:layout_height="wrap_content" Android:text="提交" Android:textColor="@color/colorBlack" Android:layout_below="@id/cb_4" Android:layout_marginTop="10dp"/> </LinearLayout> </LinearLayout>
那么来到了这几个 button
播放音频和暂停音频的 button 大家参考如何给 button 添加音效
最后一个 button, 如果不去判断正确性的话就很简单, 监听事件即可
ListenActivity 的代码
下面展示一些 内联代码片.
package com.example.eva; import Android.content.Context; import Android.media.AudioManager; import Android.media.SoundPool; import Android.support.v7.App.AppCompatActivity; import Android.os.Bundle; import Android.view.View; import Android.widget.Button; import Android.widget.LinearLayout; import com.example.eva.util.ToastUtil; import java.util.HashMap; public class ListenActivity extends AppCompatActivity { private Button mBtnPlay,mBtnPause,mBtnListenSubmit; SoundPool sp;// 声明 soundpool HashMap<Integer,Integer> hm;//hashmap 用于存放音频文件 int currStreamId;// 当前正在播放的 streamId @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_listen); mBtnPlay = findViewById(R.id.btn_play); mBtnPause = findViewById(R.id.btn_pause); mBtnListenSubmit = findViewById(R.id.btn_listen_submit); initSoundPool();// 调用方法, 初始化 // 两个按钮的点击事件 mBtnPlay.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { playSound(1,0); ToastUtil.showMsg(ListenActivity.this,"播放开始"); } }); mBtnPause.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sp.stop(currStreamId);// 停止正在播放的 ToastUtil.showMsg(ListenActivity.this,"播放已暂停"); } }); mBtnListenSubmit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { ToastUtil.showMsg(ListenActivity.this,"提交成功"); } }); } private void playSound(int sound ,int loop){ //AudioManager 的使用 AudioManager am = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE); // 获取当前音量 float streamVolumeCurrent = am.getStreamVolume(AudioManager.STREAM_MUSIC); // 获取 z 最大音量 float streamVolumeMax = am.getStreamMaxVolume(AudioManager.STREAM_MUSIC); // 计算一下播放的音量 float volume = streamVolumeCurrent / streamVolumeMax; // 调用方法来播放声音文件 currStreamId = sp.play(hm.get(sound), volume , volume , 1, loop ,1.0f); } private void initSoundPool() { sp = new SoundPool(4, AudioManager.STREAM_MUSIC,0);// 创建对象 hm = new HashMap<Integer, Integer>();//create 对象来放音频 // 加载声音, 并将声音放入 hm 中 hm.put(1,sp.load(this,R.raw.listen,1)); } }
我已经不记得编号是多少了, 晕, 到这里呢, 我们已经实现了两个界面的跳转了, 剩下的两个我就不演示了, 因为很多东西都是重复的, 而且大家做的东西还是有一些不太一样, 只要学会了方法, 很多东西都是举一反三的.
里面有一个封装的 ToastUtil 的类
我把源码放在这了, 比较我也是参照天哥的代码写的, 相互帮助最可爱啦
下面展示一些 内联代码片.
package com.example.eva.util; import Android.content.Context; import Android.widget.Toast; // 进行一个简单的封装 public class ToastUtil { public static Toast mToast; public static void showMsg(Context context, String msg){ if ((mToast == null)){ mToast = Toast.makeText(context,msg,Toast.LENGTH_LONG); }else { mToast.setText(msg); } mToast.show(); } }
关于源码
我本来是想传到 GitHub 的, 不过看了一下我自己的界面设计, 实在是太难看了, 我也才学了 2 个月, 很多地方还有一些不足, 所以暂时先不上传了.
这篇文章是我第一次写博客, 很多地方写的不是很好, 有些东西解释的也不是很清楚, 但是大家可以作为茶余饭后看一看, 也求指导一下我不会的地方.
后面如果有更新我再放在这个 csdn 上, 快 2 万字了, 又饿又困.
声明
拒绝一切啥都不改的白嫖党哈, 每个东西要有自己的特色才有意义.
转发请标明出处
联系方式
我的邮箱 578018334@qq.com,emm... 如果有更好的办法或者错误纠正, 欢迎呀
我还是希望大家在这个 csdn 上留言, 这样会的都可以相互指导, 人多了, 项目设计自然就不是问题啦.
来源: https://blog.csdn.net/weixin_43589465/article/details/105355053