简介这是一款基于Android Studio开发的星座运势App界面简洁美观、功能丰富完整非常适合安卓初学者用来巩固开发技能也适合作为课程设计项目参考。App主要包含引导欢迎页、星座运势查询、星座详细解析和星座配对四大模块其中星座解析采用解析本地JSON数据实现星座运势与配对则通过调用第三方API接口、发送HTTP请求并解析返回的JSON数据完成覆盖了数据解析、网络请求、UI搭建等核心知识点。压缩包共包含797个文件大小约18.5MB以XML布局、Java源码、JSON数据以及PNG图片为主附带可直接运行的APK安装包代码配有详细注释能够保证百分百运行。目前已有2967人学习/下载作者博客中还提供了完整的运行演示过程方便对照学习。1. 做一个星座App值不值先看它要跨过的三道坎星座类App差不多是Android新手项目里最能同时练到“UI 异步数据 列表 多页面”几个硬点的品类。用Android Studio从零搭一个星座App绝不只是查个运势——背后是日期算法、数据建模、Material Design界面的组织、网络请求的接入还有发布前的工程化收尾。我把这条链路按自己验证过的顺序拆给你先本地跑通逻辑再把UI做美观最后接API。适合已经把Android Studio跑通、想做一个真能装进手机里的项目新人。三道坎分别是星座判断逻辑、界面好看程度、以及数据从哪来。这篇文章就是逐个把它们拆开讲。2. 日期转星座算法与数据模型用本地数据先跑通核心逻辑星座App里唯一绕不开的“硬逻辑”就是日期转星座这个错了后面全错。很多新手上来就写十二个if分支或者用switch-case硬编码代码又长又容易漏边界。先把这个算法做对再定数据模型后面接网络、接数据库都会顺很多。2.1 日期转星座一个边界数组搞定12个判断星座切换日是按公历固定的1月20日起水瓶座、2月19日起双鱼座……12月22日起摩羯座。与其写一堆判断不如把每个月“切换日”存成一个数组用一次比较定位星座。public class ConstellationUtil { // 每个月份中星座切换的起始日下标0对应1月 private static final int[] DAY_EDGE { 20, // 1月20日起水瓶座 19, // 2月19日起双鱼座 21, // 3月21日起白羊座 20, // 4月20日起金牛座 21, // 5月21日起双子座 22, // 6月22日起巨蟹座 23, // 7月23日起狮子座 23, // 8月23日起处女座 23, // 9月23日起天秤座 24, // 10月24日起天蝎座 23, // 11月23日起射手座 22 // 12月22日起摩羯座 }; private static final String[] NAMES { 水瓶座, 双鱼座, 白羊座, 金牛座, 双子座, 巨蟹座, 狮子座, 处女座, 天秤座, 天蝎座, 射手座, 摩羯座 }; public static String getConstellation(int month, int day) { if (month 1 || month 12 || day 1 || day 31) { return 未知; } if (day DAY_EDGE[month - 1]) { // 还没到切换日属于上一个星座 return NAMES[(month - 2 12) % 12]; } return NAMES[month - 1]; } }逻辑说明DAY_EDGE存的是“切换日”。比如1月20日当天及之后进入水瓶座所以1月19日仍然要算摩羯座。代码里先比大小如果当前日期小于切换日就回退到上一个星座用(month - 2 12) % 12是为了让1月回退到12月时不越界否则就直接取当月对应的星座名。参数说明month传1到12对应自然月不是Java Calendar里那个从0开始的month。day传当天是几号。用这个方法前记得把Calendar取出的MONTH加1这是最容易翻车的地方。这个算法用一张表就能验证输入日期输出星座1月19日摩羯座1月20日水瓶座2月18日水瓶座2月19日双鱼座12月21日射手座12月22日摩羯座2.2 数据模型先行运势字段这样设计才扛得住扩展界面好看是后话先把数据模型定下来。星座运势一般包含综合、爱情、事业、财运四个指数再加上幸运色、幸运数字、速配星座和一段当日概述这就是最常见的“六维运势”结构。字段定好了后面Gson解析、数据库缓存、图表绘制全跟着它走。public class HoroscopeBean { private String constellation; // 星座名如白羊座 private String date; // 运势日期格式 yyyy-MM-dd private int overall; // 综合指数0-100 private int love; // 爱情指数0-100 private int career; // 事业指数0-100 private int money; // 财运指数0-100 private String luckyColor; // 幸运色如红色 private String luckyNumber; // 幸运数字如7 private String luckyStar; // 速配星座如狮子座 private String summary; // 今日概述 public HoroscopeBean() {} public HoroscopeBean(String constellation, String date, int overall, int love, int career, int money, String luckyColor, String luckyNumber, String luckyStar, String summary) { this.constellation constellation; this.date date; this.overall overall; this.love love; this.career career; this.money money; this.luckyColor luckyColor; this.luckyNumber luckyNumber; this.luckyStar luckyStar; this.summary summary; } // getter / setter 省略Android Studio里 AltInsert 一键生成 }这里两个细节值得说指数为什么用0到100的整数而不是0到10因为第三方接口返回的往往是百分制就算返回0到10乘10也能转成百分制后面画进度条、折线图都方便。另一个细节是luckyColor只存“红色”这样的名称不要在业务模型里直接存色值——将来界面要显示色块时在UI层做一张颜色映射表简单、灵活也不会让模型被UI绑架。2.3 离线数据优先assets里的JSON怎么读出来我做一个联网类App的习惯是先放一份本地数据把UI跑通再接网络。新项目最常见的翻车不是界面丑而是网络接好了却拿不到数据调试时间全花在环境问题上。离线模式能让列表页、详情页、点击跳转从头到尾先跑一遍确认逻辑没问题再碰网络。在assets/目录下新建horoscope.json先给12个星座各写一份当天的运势片段长这样[ { constellation: 白羊座, date: 2025-07-01, overall: 82, love: 90, career: 75, money: 68, luckyColor: 红色, luckyNumber: 7, luckyStar: 狮子座, summary: 今天适合主动推进拖延已久的事沟通方面容易得到支持。 } ]读取assets里的JSON用Android自带的org.json解析就够不用为离线数据引入额外依赖public class LocalHoroscopeRepository { public ListHoroscopeBean loadFromAssets(Context context) { ListHoroscopeBean list new ArrayList(); try { InputStream is context.getAssets().open(horoscope.json); int size is.available(); byte[] buffer new byte[size]; is.read(buffer); is.close(); String json new String(buffer, StandardCharsets.UTF_8); JSONArray array new JSONArray(json); for (int i 0; i array.length(); i) { JSONObject obj array.getJSONObject(i); HoroscopeBean bean new HoroscopeBean(); bean.setConstellation(obj.getString(constellation)); bean.setDate(obj.getString(date)); bean.setOverall(obj.getInt(overall)); bean.setLove(obj.getInt(love)); bean.setCareer(obj.getInt(career)); bean.setMoney(obj.getInt(money)); bean.setLuckyColor(obj.getString(luckyColor)); bean.setLuckyNumber(obj.getString(luckyNumber)); bean.setLuckyStar(obj.getString(luckyStar)); bean.setSummary(obj.getString(summary)); list.add(bean); } } catch (IOException | JSONException e) { Log.e(LocalRepo, load horoscope failed, e); } return list; } }参数说明is.available()在这里没问题因为asset文件不大如果哪天换成几百KB的大JSON就该用BufferedReader逐行读别一次塞进byte数组。循环里obj.getString要求字段必须存在一旦JSON里少了一个键就会抛JSONException这也是好事——离线阶段就能把字段不齐的问题暴露出来。用assets而不是res/raw的原因很实际以后切换成网络数据时Repository接口不变只换实现类UI层完全不用动。3. 用Material Design把UI做美观从主框架到星座卡片的三步星座App的UI好不好看直接决定用户第一眼愿不愿意留下来。没有设计师配合的小白项目用Material Components加上主题色和渐变背景就能撑住场面而且代码量不大。关键是先搭对骨架再做细节。3.1 单Activity 底部导航先搭一个不丑的骨架主界面建议用“单Activity 多Fragment BottomNavigationView”的结构底部放四个Tab分别是今日运势、星座列表、本周运势、我的。相比开多个Activity这种结构切换快、返回栈好控制也是现在新项目的常见做法。新建项目时选定带BottomNavigationView的模板或者手动在build.gradle里加上material和recyclerview依赖。主布局这样写LinearLayout xmlns:androidhttp://schemas.android.com/apk/res/android android:layout_widthmatch_parent android:layout_heightmatch_parent android:orientationvertical FrameLayout android:idid/fragment_container android:layout_widthmatch_parent android:layout_height0dp android:layout_weight1 / com.google.android.material.bottomnavigation.BottomNavigationView android:idid/bottom_nav android:layout_widthmatch_parent android:layout_heightwrap_content app:menumenu/menu_bottom_nav / /LinearLayout注意FrameLayout用layout_weight1占满底部导航上方的剩余空间这是最简单的布局方式不用ConstraintLayout也能对齐。menu里四个item要写android:title和android:icon图标可以用系统自带drawable比如android:drawable/ic_menu_week这类占位后期再换正式图标。MainActivity里做Fragment切换public class MainActivity extends AppCompatActivity { private FragmentManager fragmentManager; Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); fragmentManager getSupportFragmentManager(); BottomNavigationView nav findViewById(R.id.bottom_nav); if (savedInstanceState null) { switchFragment(new TodayFragment()); } nav.setOnItemSelectedListener(item - { if (item.getItemId() R.id.nav_today) { switchFragment(new TodayFragment()); } else if (item.getItemId() R.id.nav_list) { switchFragment(new ConstellationListFragment()); } else if (item.getItemId() R.id.nav_week) { switchFragment(new WeekFragment()); } else if (item.getItemId() R.id.nav_mine) { switchFragment(new MineFragment()); } return true; }); } private void switchFragment(Fragment fragment) { fragmentManager.beginTransaction() .replace(R.id.fragment_container, fragment) .commit(); } }说明切换用replace而不是add这样每个Fragment只有一个实例在栈里不会出现返回时连续退两层的问题。savedInstanceState null的判断防止屏幕旋转时重复创建Fragment。如果你的material版本较老没有setOnItemSelectedListener方法改用setOnNavigationItemSelectedListener即可写法一样。3.2 星座主题色与渐变背景一张图片都不用星座给人最直接的视觉印象就是颜色白羊热情、双鱼梦幻、狮子亮黄。给每个星座抽一个主色和一个辅色存进colors.xmlUI里所有卡片、背景、进度条都从这套色系取色整体感立刻就出来了。color namearies_primary#E53935/color color namearies_secondary#FF8A65/color color nametaurus_primary#43A047/color color nametaurus_secondary#AED581/color color namegemini_primary#1E88E5/color color namegemini_secondary#80DEEA/color背景不要用整张位图用GradientDrawable在代码里生成渐变省APK体积也免去适配不同分辨率屏幕的麻烦。private void applyGradient(View rootView, int primary, int secondary) { GradientDrawable drawable new GradientDrawable( GradientDrawable.Orientation.TL_BR, new int[]{primary, secondary}); drawable.setCornerRadius(0f); rootView.setBackground(drawable); }调用方式在Fragment的onViewCreated里拿到根布局从getResources().getColor(primary, getTheme())取颜色后传进去。参数说明TL_BR表示渐变从左上到右下适合竖屏卡片如果页面要横屏适配改成LEFT_RIGHT会更自然。想要柔和的背景效果把两个颜色的差值拉大一点比如白羊座从大红渐变到浅橙比纯色背景耐看。色值映射建议在UI层单独做一个工具类别塞进业务模型public class ConstellationTheme { public static int primaryColor(String constellation) { switch (constellation) { case 白羊座: return R.color.aries_primary; case 金牛座: return R.color.taurus_primary; case 双子座: return R.color.gemini_primary; // 其余星座按同规律补充 default: return R.color.default_primary; } } }这样做有个好处详情页、卡片、进度条都调同一个方法以后想换配色只改一处。3.3 RecyclerView网格卡片列表页决定第一印象首页12个星座用两列网格展示卡片上放星座符号、名称和日期区间。这个页面是用户打开App的第一屏值得多花心思。item布局用MaterialCardViewcom.google.android.material.card.MaterialCardView xmlns:androidhttp://schemas.android.com/apk/res/android xmlns:apphttp://schemas.android.com/apk/res-auto android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_margin8dp app:cardBackgroundColor#FFFFFF app:cardCornerRadius16dp app:cardElevation2dp LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationvertical android:gravitycenter_horizontal android:padding16dp TextView android:idid/tv_symbol android:layout_widthwrap_content android:layout_heightwrap_content android:textSize32sp android:text♈ / TextView android:idid/tv_name android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginTop8dp android:textSize16sp android:textStylebold android:text白羊座 / TextView android:idid/tv_date_range android:layout_widthwrap_content android:layout_heightwrap_content android:textSize12sp android:text3.21 - 4.19 / /LinearLayout /com.google.android.material.card.MaterialCardView星座符号直接用的Unicode字符♈♉♊♋♌♍♎♏♐♑♒♓不用切图。注意不同厂商系统字体对Unicode符号的渲染可能有细微差异如果介意可以换成矢量图但新手阶段用字符最省事。适配器骨架public class ConstellationAdapter extends RecyclerView.AdapterConstellationAdapter.VH { private final ListConstellation data; public ConstellationAdapter(ListConstellation data) { this.data data; } NonNull Override public VH onCreateViewHolder(NonNull ViewGroup parent, int viewType) { View view LayoutInflater.from(parent.getContext()) .inflate(R.layout.item_constellation, parent, false); return new VH(view); } Override public void onBindViewHolder(NonNull VH holder, int position) { Constellation item data.get(position); holder.tvName.setText(item.getName()); holder.tvSymbol.setText(item.getSymbol()); holder.tvDateRange.setText(item.getDateRange()); holder.itemView.setOnClickListener(v - { // 跳详情页传入星座名参数 }); } static class VH extends RecyclerView.ViewHolder { TextView tvSymbol, tvName, tvDateRange; VH(View itemView) { super(itemView); tvSymbol itemView.findViewById(R.id.tv_symbol); tvName itemView.findViewById(R.id.tv_name); tvDateRange itemView.findViewById(R.id.tv_date_range); } } }参数说明网格列数在布局里用RecyclerView的spanCount控制写2就是两列。卡片间距直接用item的layout_margin实现就行不需要再写ItemDecoration。还有一点onBindViewHolder里只做setText这类轻量操作不要在这里做文件读取或复杂计算不然列表快速滑动时必卡。4. 给星座App接上真实运势Retrofit Gson 的最小网络层界面能跑了数据还是写死的下一步就是接真实接口。常见做法是找第三方聚合平台申请一个星座运势接口的key注册后平台会给你base url、路径和参数说明。接完网络这个App才真正算“功能丰富”。4.1 本地预置还是免费API按你的目标选数据路线两条路线的取舍很明确。本地预置数据稳定、不依赖网络、不用申请key适合Demo和离线场景但运势永远不更新。接免费API数据每天更新用户会觉得App是“活的”但免费接口通常有调用次数限制字段还可能说变就变。我给的建议是两条路都保留底层定义成一个Repository接口本地实现和网络实现各写一个类UI层只依赖接口。切换数据源只改一行注入代码本地数据留着还能当接口挂掉时的兜底方案。后面所有网络相关的坑也都不至于影响到已经跑通的界面逻辑。4.2 Retrofit接口与Gson字段映射解析别人家JSON的常规操作Retrofit是目前主流的网络库搭配Gson解析JSON代码量比手写HttpURLConnection少一大半。接口定义先写出来public interface HoroscopeApi { GET(constellation/get) CallHoroscopeResponse getDaily( Query(key) String key, Query(constellation) String name, Query(type) String type ); }路径和参数名以申请平台文档为准不同平台差异很大但结构基本都是这样?keyxxxconstellationxxxtypetoday。Query(key)对应URL里的key参数Query(type)对应今日、本周、本月的类型。Retrofit实例的构建public class ApiClient { // 替换成你申请的接口平台地址注意要以/结尾 private static final String BASE_URL https://example.com/; public static HoroscopeApi getApi() { OkHttpClient client new OkHttpClient.Builder() .connectTimeout(10, TimeUnit.SECONDS) .readTimeout(10, TimeUnit.SECONDS) .build(); Retrofit retrofit new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .build(); return retrofit.create(HoroscopeApi.class); } }参数说明connectTimeout管的是建立连接的超时readTimeout管的是拿到响应数据的超时。星座接口普遍响应不算快10秒是经验值设太短容易在弱网环境误报超时。超时后回调里会收到SocketTimeoutException后面做错误提示用得上。第三方返回的JSON字段名经常和Java驼峰风格不一致比如用name表示星座名、用all表示综合指数这就靠SerializedName映射public class HoroscopeResponse { SerializedName(name) private String constellation; SerializedName(datetime) private String date; SerializedName(all) private int overall; SerializedName(love) private int love; SerializedName(work) private int career; SerializedName(money) private int money; SerializedName(color) private String luckyColor; SerializedName(number) private String luckyNumber; SerializedName(QFriend) private String luckyStar; SerializedName(summary) private String summary; }平台文档怎么定字段名这里就怎么映射。某些接口字段命名很怪比如QFriend直接原样写在注解里不要自己改成qfRiend之类的大小写差一个字母Gson就解析不出结果而且不报错数据全是null这种坑最浪费生命。发起请求的异步回调写法ApiClient.getApi().getDaily(key, 白羊座, today) .enqueue(new CallbackHoroscopeResponse() { Override public void onResponse(CallHoroscopeResponse call, ResponseHoroscopeResponse response) { if (response.isSuccessful() response.body() ! null) { HoroscopeResponse body response.body(); // 这里拿到数据更新UI } else { // 非2xx状态码常见是key失效或调用次数用完 } } Override public void onFailure(CallHoroscopeResponse call, Throwable t) { // 断网、超时、域名解析失败都走这里 } });enqueue是异步请求不阻塞UI线程。注意回调默认在子线程不能直接改控件要么用runOnUiThread要么用Handler切回主线程。如果项目用了协程也可以包一层suspend函数但在入门阶段回调版更容易看懂。4.3 把加载状态做进UI别让用户面对白屏接了网络以后页面就多了三种状态加载中、加载失败、加载成功。不做状态处理的话用户打开App只会看到一片空白还以为程序死了。用三套View切换是最直接的做法private void showLoading(boolean loading, boolean error) { if (loading) { loadingView.setVisibility(View.VISIBLE); contentView.setVisibility(View.GONE); errorView.setVisibility(View.GONE); } else if (error) { loadingView.setVisibility(View.GONE); contentView.setVisibility(View.GONE); errorView.setVisibility(View.VISIBLE); } else { loadingView.setVisibility(View.GONE); contentView.setVisibility(View.VISIBLE); errorView.setVisibility(View.GONE); } }加载中显示一个ProgressBar失败页放一句提示和一个“重试”按钮点击后重新发起请求。代码逻辑很简单加载优先于错误成功时全部恢复正常。还有一个细节接口返回的summary可能是null、空串甚至带HTML标签赋值给TextView前务必判空有必要时用最简单的Html.fromHtml过滤一下富文本。5. 星座App翻车避坑指南5个小白必踩的坑项目做到这里环境、代码、数据都已经跑通了。但有几类问题几乎每个第一次做联网App的人都会遇到。按“现象 → 原因 → 解决”的路线列出来能省下不少排查时间。5.1 环境与构建Gradle慢、SDK不匹配、权限没加坑1Android Studio创建项目后第一次构建卡很久甚至失败。现象进度条长时间停在“Downloading gradle-xxx.zip”或者依赖拉取阶段最后可能直接报连接超时。原因Gradle发行包和依赖默认从国外服务器下载国内网络直连经常超时。解决打开gradle/wrapper/gradle-wrapper.properties把distributionUrl换成国内镜像地址然后在项目根build.gradle的仓库配置里加上阿里云镜像maven { url https://maven.aliyun.com/repository/google } maven { url https://maven.aliyun.com/repository/public }注意改了镜像后依赖版本不要随意动AGP和Gradle版本是配套的乱升版本号会冒出一堆编译错误。坑2Manifest里忘了加网络权限请求安静地失败。现象运行不崩但所有网络请求都失败logcat里出现UnknownHostException或Permission denied。原因联网权限需要在Manifest里声明漏了它应用在安装时就不会被授予网络权限。解决在AndroidManifest.xml的manifest标签下加一行uses-permission android:nameandroid.permission.INTERNET /加完重新构建即可不需要重启模拟器。坑3Android 9以上访问http明文接口直接失败。现象接口在浏览器能正常打开App里Retrofit却回调onFailure错误提示CLEARTEXT communication to xxx not permitted by network security policy。原因Android 9API 28起默认禁止明文HTTP流量而不少免费星座接口还在用http。解决如果接口确实没有https可以在application标签加android:usesCleartextTraffictrue临时放开要上线的应用建议用network_security_config.xml只对指定域名放行明文别全局打开。提示usesCleartextTraffic全开最简单但不推荐上架应用这么干按域名放行是更规范的做法。5.2 界面与数据处理卡顿、字段解析、日期混用坑4列表滚动卡顿尤其从详情页返回列表时。现象RecyclerView快速滑动掉帧低端测试机上尤其明显甚至掉到个位数帧率。原因onBindViewHolder里做了耗时操作比如读取文件、解析JSON、创建大对象卡片里用了超大位图或者每帧都新建背景Drawable。解决图片统一用Glide加载并设置占位图onBindViewHolder只做setText渐变背景在Fragment里缓存成字段别每次滚动都重新new GradientDrawable。卡顿明显时用Layout Inspector看看View层级层级过深就重构布局。坑5用户填的生日是农历星座判断结果差一个。现象用户农历腊月二十五生日按公历算出来是水瓶座用户觉得不准来差评。原因星座本来就是按公历生日算的很多人习惯填农历另外DatePicker拿到的month是0到11直接传给算法会整体错一个月。解决在界面明确标注“公历生日”并默认填入当天日期从DatePicker取到month后先加1再传给ConstellationUtil。想支持农历生日的话要先把农历转成公历再交给算法不能拿农历月份直接套边界数组。还有个隐藏细节DatePicker的初始值就是今天如果用户没手动选日期就点确定要确保onDateChanged回调里已经把值存住别等确定按钮才读。6. 进阶三件套图表、桌面小组件与发布前收尾基础版本能跑、能看、能查运势之后想让项目更像一个“产品”还有三件事值得做。6.1 一周运势走势图顺带理解图表库的封装思路加了真实API后本周运势Tab只显示文字太干。常见做法是接一个折线图库把一周的综合指数画成走势。用法套路基本固定先构造数据实体列表再setData最后刷新图表。我的建议是把图表封装成独立的WeekTrendChartView组件外部只传ListHoroscopeBean组件内部处理坐标轴、颜色和动画。选库时挑轻量的能画折线、能取到点击位置的值就够别一上来追求大而全的图表框架入门阶段会花大量时间在调样式上。6.2 桌面小组件把今日运势放到主屏幕桌面小组件是“功能丰富”最直观的加分项主屏幕上直接显示今日星座和综合指数点击打开App对应页面。核心要写三个东西继承AppWidgetProvider的类、res/xml/下的widget配置宽高、更新周期、预览图、以及一个只支持有限控件的RemoteViews布局。注意RemoteViews里不能用自定义ViewTextView、ImageView都没问题数据更新用WorkManager定时拉最新运势再调updateAppWidget不要直接在Provider里做网络请求。新手第一次加小组件经常遇到桌面上空白一片的情况先看logcat有没有RemoteViewsException多半是布局里用了不支持的控件。6.3 发布前收尾版本号、签名、图标与Git做到这里建议认真收个尾。versionCode每次发布自增versionName按语义化版本写签名用Android Studio生成keystore后一定要备份签名丢了就是给已上线的App断了“后悔药”图标用Image Asset生成一套自适应图标这是提升专业感最划算的一步。项目从第一天就该用Git每完成一小步commit一次哪天改崩了还能退回去。Android Studio里把界面语言切换成中文Settings里搜语言包插件对新手也友好得多。我自己做这类项目时踩得最狠的跟星座一点关系都没有全是网络层和签名。Retrofit回调里直接改UI导致的崩溃调了一晚上最后发现是没切回主线程签名文件丢过一次重签后老用户覆盖安装失败只能卸载重装。这些经历比App本身更值钱。做星座App最大的收获是它逼着你把一条完整链路走完从界面到数据从本地到远程从开发到打包。希望帮到你。本文还有配套的精品资源点击获取