[文章]

#HarmonyOS征文#教育类社区卡片实战

2021-6-28 14:18:59  685 鸿蒙 HarmonyOS 征文
分享
4

极客爱编程教育类App卡片实战

`前言

对于教育类App来说,一般都具有课程分类,包括我们程序员学习技术,也如此。简单的说,一个程序员社区,必然有众多技术分类,比如有的学习Python,有的学习Java鸿蒙技术,有的对OpenCV感兴趣。
3.png

而教育类App,如果能通过鸿蒙的卡片功能,将课程分类放在显眼的地方,那么用户就可以很容易直达自己感兴趣的知识社区进行学习,非常的方便。

所以,掌握好鸿蒙卡片功能,能够在细微的体验上,让用户感到贴心。下面,我们来讲解这款教育类专栏分类,如何通过卡片进行直达。

创建一个Java项目与卡片

首先,我们需要创建一个纯Java项目,如下图所示:

6.png

接着,在生成的Java鸿蒙项目中,点击entry-src右键创建Service Widget一个2*4的卡片布局:
7.png

创建完成之后,项目下面会生成一个java-widget文件,以及js卡片布局文件。如下图所示:

8.png

这样你运行项目,默认会显示鸿蒙开发工具提供给你的2*4布局卡片。但是我们需要将卡片的功能换成技术社区的直接跳转,接下来,我们来构建卡片。

完成卡片布局
如上图所示,我们的整体布局并没有变,但是后面的图片以及前面的文字都发生了变化,而js文件下,有一个hml文件,这是一个类html语法的文件,我们只需要改变字符串字符串以及图片即可,代码如下(index.hml):

  1. <div class="div_basic_container">

  2.     <stack class="main_sub_container" onclick="routerEvent">
  3.         <image class="item_image" src="/common/homepage.png">
  4.         </image>

  5.         <div class="div_text_container">
  6.             <text class="title">主页
  7.             </text>
  8.         </div>
  9.     </stack>

  10.     <div class="first_sub_container">

  11.         <div class="item_first_container" onclick="routerEvent1">
  12.             <text class="text_item1">Python
  13.             </text>
  14.         </div>

  15.         <div class="item_second_container" onclick="routerEvent2">
  16.             <text class="text_item2">OpenCV
  17.             </text>
  18.         </div>
  19.     </div>

  20.     <div class="second_sub_container">

  21.         <div class="item_first_container" onclick="routerEvent3">
  22.             <text class="text_item3">鸿蒙开发
  23.             </text>
  24.         </div>

  25.         <div class="item_second_container" onclick="routerEvent4">
  26.             <text class="text_item4">量化交易
  27.             </text>
  28.         </div>
  29.     </div>
  30. </div>
复制代码

接下来,我们需要弄清楚,js卡片布局是如何跳转到Java界面的,我们从上面的js文件发现,还有一个index.json。没错,样式由index.css产生,但跳转以及与用户的交互全在index.json文件中,代码如下(index.json):
  1. {
  2.   "data": {
  3.     "title": "HomePage",
  4.     "textContent1": "Python",
  5.     "textContent2": "OpenCV",
  6.     "textContent3": "Harmony",
  7.     "textContent4": "Search"
  8.   },
  9.   "actions": {
  10.     "routerEvent": {
  11.       "action": "router",
  12.       "abilityName": "com.liyuanjinglyj.javacarddemo.widget.WidgetAbility",
  13.       "params": {
  14.         "message": "{{title}}"
  15.       }
  16.     },
  17.     "routerEvent1": {
  18.       "action": "router",
  19.       "abilityName": "com.liyuanjinglyj.javacarddemo.widget.WidgetAbility",
  20.       "params": {
  21.         "message": "{{textContent1}}"
  22.       }
  23.     },
  24.     "routerEvent2": {
  25.       "action": "router",
  26.       "abilityName": "com.liyuanjinglyj.javacarddemo.widget.WidgetAbility",
  27.       "params": {
  28.         "message": "{{textContent2}}"
  29.       }
  30.     },
  31.     "routerEvent3": {
  32.       "action": "router",
  33.       "abilityName": "com.liyuanjinglyj.javacarddemo.widget.WidgetAbility",
  34.       "params": {
  35.         "message": "{{textContent3}}"
  36.       }
  37.     },
  38.     "routerEvent4": {
  39.       "action": "router",
  40.       "abilityName": "com.liyuanjinglyj.javacarddemo.widget.WidgetAbility",
  41.       "params": {
  42.         "message": "{{textContent4}}"
  43.       }
  44.     }
  45.   }
  46. }
复制代码


如上面代码所示,action表示这是路由跳转,其中,abilityName表示关联的Java卡片生成类,而params表示传递的参数。当然,params并不是界面的跳转参数,而是告诉Java你需要跳转到哪个界面。

我们来看看WidgetImpl类的实现代码:
  1. public class WidgetImpl extends FormController {

  2.     @Override
  3.     public Class<? extends AbilitySlice> getRoutePageSlice(Intent intent) {
  4.         HiLog.info(TAG, "set route page slice.");
  5.         String param = intent.getStringParam("params");
  6.         ZSONObject zsonObject = ZSONObject.stringToZSON(param);
  7.         switch (zsonObject.getString("message")) {
  8.             case "HomePage":
  9.                 return MainAbilitySlice.class;
  10.             case "Python":
  11.                 return FunctionPythonSlice.class;
  12.             case "OpenCV":
  13.                 return FunctionOpenCVSlice.class;
  14.             case "Harmony":
  15.                 return FunctionHarmonySlice.class;
  16.             case "Search":
  17.                 return FunctionSearchSlice.class;
  18.             default:
  19.                 return null;
  20.         }
  21.     }
  22. }
复制代码
这里大部分代码我们都可以忽略,只需要关心getRoutePageSlice方法即可,可以看到,我们在index.json中params指定的参数,就是我们需要跳转的Java界面,这里通过switch寻找我们卡片传递的参数,然后跳转到指定的界面。

比如,这里用户如果需要学习python,那么就会传递python参数,然后选择FunctionPythonSlice界面进行跳转。其他的类同。

实现卡片跳转界面

对于App来说,不同的界面的社区往往会有不同的数据,而每个数据又是通过json返回的,那么就会有不同的json接口提供给我们。



这里,我们来实现FunctionPythonSlice,代码如下:
  1. public class FunctionPythonSlice extends AbilitySlice {

  2.     private String url="https://harmony-1300376177.cos.ap-shanghai.myqcloud.com/python_item.json";
  3.     @Override
  4.     public void onStart(Intent intent) {
  5.         super.onStart(intent);
  6.         super.setUIContent((ComponentContainer) LYJUtils.getListContainer(url,this));
  7.     }
  8. }
复制代码
这里通过博主自定义的工具类返回一个ListContainer界面,因为每个卡片上的功能只是跳转的知识不同,但数据样式基本一样。而ListContainer界面的生成代码如下(LYJUtils):
  1. public class LYJUtils {
  2.     public static ListContainer getListContainer(String url, AbilitySlice abilitySlice){
  3.         ListContainer listContainer=new ListContainer(null);
  4.         listContainer.setLayoutConfig(
  5.                 new StackLayout.LayoutConfig(
  6.                         ComponentContainer.LayoutConfig.MATCH_PARENT,
  7.                         ComponentContainer.LayoutConfig.MATCH_PARENT
  8.                 ));
  9.         ZZRHttp.get(url, new ZZRCallBack.CallBackString() {
  10.             @Override
  11.             public void onFailure(int code, String errorMessage) {
  12. //错误处理
  13.             }
  14.             @Override
  15.             public void onResponse(String response) {
  16.                 //http访问成功,此部分内容在主线程中工作;
  17.                 HiJson hiJson = new HiJson(response);
  18.                 int counts=hiJson.get("news_item").count();
  19.                 List<InfoItem> infoItemList=new ArrayList<>();
  20.                 for(int i=0;i<counts;i++){
  21.                     InfoItem infoItem=new InfoItem();
  22.                     infoItem.setTitle(hiJson.get("news_item").get(i).value("title"));
  23.                     infoItem.setDigest(hiJson.get("news_item").get(i).value("digest"));
  24.                     infoItem.setUrl(hiJson.get("news_item").get(i).value("url"));
  25.                     infoItem.setThumb_url(hiJson.get("news_item").get(i).value("thumb_url"));
  26.                     infoItemList.add(infoItem);
  27.                 }
  28.                 InfoItemListProvider infoItemListProvider=new InfoItemListProvider(infoItemList, abilitySlice);
  29.                 listContainer.setItemProvider(infoItemListProvider);
  30.                 listContainer.setItemClickedListener(new ListContainer.ItemClickedListener() {
  31.                     @Override
  32.                     public void onItemClicked(ListContainer listContainer, Component component, int i, long l) {
  33.                         Intent intent=new Intent();
  34.                         intent.setParam("url",infoItemList.get(i).getUrl());
  35.                         abilitySlice.present(new WebViewAbilitySlice(),intent);
  36.                     }
  37.                 });
  38.             }
  39.         });
  40.         return listContainer;
  41.     }
  42. }
复制代码
其他卡片功能的界面基本一致,唯有url提供的json接口不一样。当然,这里还涉及ListContainer适配器,代码如下(InfoItemListProvider):
  1. public class InfoItemListProvider extends BaseItemProvider {
  2.     private List<InfoItem> infoItemList=new ArrayList<>();
  3.     private AbilitySlice abilitySlice;
  4.     HiLogLabel label=new HiLogLabel(HiLog.LOG_APP, 0x00201, "TAG");
  5.     public InfoItemListProvider(List<InfoItem> infoItemList,AbilitySlice abilitySlice) {
  6.         this.infoItemList=infoItemList;
  7.         this.abilitySlice=abilitySlice;
  8.         HiLog.error(label,String.valueOf(infoItemList.size())+"11111");
  9.     }

  10.     @Override
  11.     public int getCount() {
  12.         return infoItemList == null ? 0 : infoItemList.size();
  13.     }

  14.     @Override
  15.     public Object getItem(int i) {
  16.         if (infoItemList != null && i >= 0 && i < infoItemList.size()){
  17.             return infoItemList.get(i);
  18.         }
  19.         return null;
  20.     }

  21.     @Override
  22.     public long getItemId(int i) {
  23.         return i;
  24.     }

  25.     @Override
  26.     public Component getComponent(int i, Component component, ComponentContainer componentContainer) {
  27.         final Component cpt;
  28.         if (component == null) {
  29.             cpt = LayoutScatter.getInstance(this.abilitySlice).parse(ResourceTable.Layout_infoitem_listitem, null, false);
  30.         } else {
  31.             cpt = component;
  32.         }
  33.         InfoItem infoItem = this.infoItemList.get(i);
  34.         HiLog.error(label,String.valueOf(i)+"11111");
  35.         Text title=(Text)cpt.findComponentById(ResourceTable.Id_infoitem_listitem_title);
  36.         title.setText(infoItem.getTitle());
  37.         Image image=(Image)cpt.findComponentById(ResourceTable.Id_infoitem_listitem_image);
  38.         new ImageNetWork(this.abilitySlice,image,infoItem.getThumb_url()).start();
  39.         return cpt;
  40.     }
  41. }
复制代码
获取ListContainer列表的实体类(InfoItem.java)如下所示:
  1. public class InfoItem {
  2.     String title;//标题
  3.     String digest;//描述
  4.     String url;//文章链接
  5.     String thumb_url;//文章头图链接

  6.     public InfoItem(String title,String digest,String url,String thumb_url) {
  7.         this.title=title;
  8.         this.digest=digest;
  9.         this.url=url;
  10.         this.thumb_url=thumb_url;
  11.     }

  12.     public InfoItem() {
  13.         super();
  14.     }

  15.     public String getTitle() {
  16.         return title;
  17.     }

  18.     public void setTitle(String title) {
  19.         this.title = title;
  20.     }

  21.     public String getDigest() {
  22.         return digest;
  23.     }

  24.     public void setDigest(String digest) {
  25.         this.digest = digest;
  26.     }

  27.     public String getUrl() {
  28.         return url;
  29.     }

  30.     public void setUrl(String url) {
  31.         this.url = url;
  32.     }

  33.     public String getThumb_url() {
  34.         return thumb_url;
  35.     }

  36.     public void setThumb_url(String thumb_url) {
  37.         this.thumb_url = thumb_url;
  38.     }
  39. }
复制代码

当然,这里还涉及列表的样式(infoitem_listitem.xml):
  1. <DirectionalLayout
  2.     xmlns:ohos="http://schemas.huawei.com/res/ohos"
  3.     ohos:height="match_content"
  4.     ohos:width="match_parent"
  5.     ohos:margin="10vp"
  6.     ohos:alpha="0.5"
  7.     ohos:background_element="$graphic:listitem_backgroud"
  8.     ohos:orientation="vertical">

  9.     <Image
  10.         ohos:id="$+id:infoitem_listitem_image"
  11.         ohos:height="150vp"
  12.         ohos:width="match_parent"
  13.         ohos:scale_mode="stretch"/>

  14.     <Text
  15.         ohos:id="$+id:infoitem_listitem_title"
  16.         ohos:height="match_content"
  17.         ohos:width="match_parent"
  18.         ohos:text_size="18vp"
  19.         ohos:multiple_lines="true"
  20.         ohos:text_alignment="left"
  21.         ohos:bottom_margin="5vp"
  22.         ohos:left_margin="2vp"
  23.         ohos:right_margin="2vp"
  24.         ohos:top_margin="5vp"
  25.         ohos:text_color="#0000FF"/>

  26. </DirectionalLayout>
复制代码
WebView文章详情界面

除此之外,我们还要实现ListContainer的跳转界面。因为这是网站的内容,我们只需要通过WebView进行加载即可。(WebViewAbilitySlice)代码如下:
  1. public class WebViewAbilitySlice extends AbilitySlice {
  2.     HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, "TAG");
  3.     private WebView webView;
  4.     private static String EXAMPLE_URL;
  5.     @Override
  6.     public void onStart(Intent intent) {
  7.         super.onStart(intent);
  8.         super.setUIContent(ResourceTable.Layout_ability_web_view);
  9.         this.webView=(WebView)this.findComponentById(ResourceTable.Id_ability_web_view_webview);
  10.         this.webView.getWebConfig().setJavaScriptPermit(true);
  11.         if(intent != null) {
  12.             EXAMPLE_URL = intent.getStringParam("url");
  13.             this.webView.setWebAgent(new ExampleWebAgent());
  14.             this.webView.load(EXAMPLE_URL);
  15.         }
  16.     }

  17.     private class ExampleWebAgent extends WebAgent {
  18.         @Override
  19.         public boolean isNeedLoadUrl(WebView webview, ResourceRequest request) {
  20.             Uri uri = request.getRequestUrl();
  21.             if (EXAMPLE_URL.equals(uri.getDecodedHost())) {
  22.                 // 由WebView通过默认方式处理
  23.                 return false;
  24.             }

  25.             // 增加开发者自定义逻辑
  26.             return super.isNeedLoadUrl(webview, request);
  27.         }
  28.     }

  29.     @Override
  30.     public void onActive() {
  31.         super.onActive();
  32.     }

  33.     @Override
  34.     public void onForeground(Intent intent) {
  35.         super.onForeground(intent);
  36.     }
  37. }
复制代码
鸿蒙提供给我们的Java WebView组件默认是直接跳转到浏览器的,为了让其在App内部显示,我们需要通过setWebAgent()方法进行设置。

而样式文件这里就不展示了,就只有一个WebView组件。

主页TabList与PageSlider联动

在众多的App中,我们能看到顶部标题栏可以进行滑动的切换页面,而这里我们也来认真实现卡片跳转的主页界面。

主页.gif

(MainAbilitySlice)代码如下:
  1. public class MainAbilitySlice extends AbilitySlice {
  2.     HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, "TAG");
  3.     private PageSlider pageSlider;
  4.     private TabList tabList;
  5.     private String[] tab_str_list = {"推荐", "Python", "OpenCV", "鸿蒙开发"};
  6.     @Override
  7.     public void onStart(Intent intent) {
  8.         super.onStart(intent);
  9.         super.setUIContent(ResourceTable.Layout_ability_main);
  10.         this.pageSlider = (PageSlider) findComponentById(ResourceTable.Id_ability_main_pageslider);
  11.         this.pageSlider.setProvider(new MyPageProvider(getData(), this));
  12.         this.tabList = (TabList) findComponentById(ResourceTable.Id_ability_main_tablist);
  13.         for (int i = 0; i < tab_str_list.length; i++) {
  14.             TabList.Tab tab = tabList.new Tab(getContext());
  15.             tab.setText(tab_str_list[i]);
  16.             tab.setLayoutConfig(
  17.                     new StackLayout.LayoutConfig(
  18.                             ComponentContainer.LayoutConfig.MATCH_CONTENT,
  19.                             ComponentContainer.LayoutConfig.MATCH_PARENT
  20.                     ));
  21.             this.tabList.addTab(tab);
  22.             if (i == 0) {
  23.                 tab.select();
  24.             }
  25.         }
  26.         this.tabList.addTabSelectedListener(new TabList.TabSelectedListener() {
  27.             @Override
  28.             public void onSelected(TabList.Tab tab) {
  29.                 //当某个Tab从未选中状态变为选中状态时的回调
  30.                 pageSlider.setCurrentPage(tab.getPosition());
  31.             }

  32.             @Override
  33.             public void onUnselected(TabList.Tab tab) {
  34.                 //当某个Tab从选中状态变为未选中状态时的回调
  35.             }

  36.             @Override
  37.             public void onReselected(TabList.Tab tab) {
  38.                 //当某个Tab已处于选中状态,再次被点击时的状态回调
  39.             }
  40.         });
  41.         pageSlider.addPageChangedListener(new PageSlider.PageChangedListener() {
  42.             @Override
  43.             public void onPageSliding(int itemPos, float itemPosOffset, int itemPosPixles) {

  44.             }

  45.             @Override
  46.             public void onPageSlideStateChanged(int state) {
  47.             }

  48.             @Override
  49.             public void onPageChosen(int itemPos) {
  50.                 tabList.selectTabAt(itemPos);
  51.             }
  52.         });
  53.     }

  54.     private List<String> getData(){
  55.         List<String> stringList=new ArrayList<>();
  56.         stringList.add("https://harmony-1300376177.cos.ap-shanghai.myqcloud.com/swiper_item.json");
  57.         stringList.add("https://harmony-1300376177.cos.ap-shanghai.myqcloud.com/python_item.json");
  58.         stringList.add("https://harmony-1300376177.cos.ap-shanghai.myqcloud.com/opencv_item.json");
  59.         stringList.add("https://harmony-1300376177.cos.ap-shanghai.myqcloud.com/harmony_item.json");
  60.         return stringList;
  61.     }
复制代码
主页的布局文件(ability_main.xml)代码如下所示:
  1. <?xml version="1.0" encoding="utf-8"?>
  2. <DirectionalLayout
  3.     xmlns:ohos="http://schemas.huawei.com/res/ohos"
  4.     ohos:height="match_parent"
  5.     ohos:width="match_parent"
  6.     ohos:alignment="center"
  7.     ohos:orientation="vertical">

  8.     <TabList
  9.         ohos:id="$+id:ability_main_tablist"
  10.         ohos:height="match_content"
  11.         ohos:width="match_parent"
  12.         ohos:tab_margin="12vp"
  13.         ohos:text_alignment="center"
  14.         ohos:orientation="horizontal"
  15.         ohos:fixed_mode="true"
  16.         ohos:text_size="20vp"
  17.         ohos:normal_text_color="#808080"
  18.         ohos:selected_text_color="#000000"
  19.         ohos:selected_tab_indicator_color="#FF0000"
  20.         ohos:selected_tab_indicator_height="3vp"/>

  21.     <PageSlider
  22.         ohos:id="$+id:ability_main_pageslider"
  23.         ohos:height="match_parent"
  24.         ohos:width="match_parent"
  25.         ohos:layout_alignment="center"/>

  26. </DirectionalLayout>
复制代码
ListContainer需要适配器进行适配,PageSlider同样也是需要,我们需要给PageSlider提供不同的链接,然后PageSlider单个页面的ListContainer根据这些链接接口获取不同的知识列表。(MyPageProvider)代码如下:
  1. public class MyPageProvider extends PageSliderProvider {
  2.     HiLogLabel label = new HiLogLabel(HiLog.LOG_APP, 0x00201, "TAG");
  3.     private AbilitySlice abilitySlice;
  4.     private List<String> stringList;

  5.     public MyPageProvider(List<String> list,AbilitySlice abilitySlice){
  6.         this.stringList=list;
  7.         this.abilitySlice=abilitySlice;
  8.     }

  9.     @Override
  10.     public int getCount() {
  11.         return this.stringList.size();
  12.     }

  13.     @Override
  14.     public Object createPageInContainer(ComponentContainer componentContainer, int i) {
  15.         final String url = this.stringList.get(i);
  16.         ListContainer listContainer=new ListContainer(null);
  17.         listContainer.setLayoutConfig(
  18.                 new StackLayout.LayoutConfig(
  19.                         ComponentContainer.LayoutConfig.MATCH_PARENT,
  20.                         ComponentContainer.LayoutConfig.MATCH_PARENT
  21.                 ));
  22.         ZZRHttp.get(url, new ZZRCallBack.CallBackString() {
  23.             @Override
  24.             public void onFailure(int code, String errorMessage) {
  25.                 //错误处理
  26.             }
  27.             @Override
  28.             public void onResponse(String response) {
  29.                 //http访问成功,此部分内容在主线程中工作;
  30.                 //可以更新UI等操作,但请不要执行阻塞操作。
  31.                 HiJson hiJson = new HiJson(response);
  32.                 int counts=hiJson.get("news_item").count();
  33.                 List<InfoItem> infoItemList=new ArrayList<>();
  34.                 for(int i=0;i<counts;i++){
  35.                     InfoItem infoItem=new InfoItem();
  36.                     infoItem.setTitle(hiJson.get("news_item").get(i).value("title"));
  37.                     infoItem.setDigest(hiJson.get("news_item").get(i).value("digest"));
  38.                     infoItem.setUrl(hiJson.get("news_item").get(i).value("url"));
  39.                     infoItem.setThumb_url(hiJson.get("news_item").get(i).value("thumb_url"));
  40.                     infoItemList.add(infoItem);
  41.                 }
  42.                 InfoItemListProvider infoItemListProvider=new InfoItemListProvider(infoItemList, abilitySlice);
  43.                 listContainer.setItemProvider(infoItemListProvider);
  44.                 listContainer.setItemClickedListener(new ListContainer.ItemClickedListener() {
  45.                     @Override
  46.                     public void onItemClicked(ListContainer listContainer, Component component, int i, long l) {
  47.                         Intent intent=new Intent();
  48.                         intent.setParam("url",infoItemList.get(i).getUrl());
  49.                         abilitySlice.present(new WebViewAbilitySlice(),intent);
  50.                     }
  51.                 });
  52.             }
  53.         });
  54.         componentContainer.addComponent(listContainer);
  55.         return listContainer;
  56.     }

  57.     @Override
  58.     public void destroyPageFromContainer(ComponentContainer componentContainer, int i, Object o) {
  59.         componentContainer.removeComponent((Component) o);
  60.     }

  61.     @Override
  62.     public boolean isPageMatchToObject(Component component, Object o) {
  63.         return true;
  64.     }
  65. }
复制代码
到这里,我们的教育类App卡片分类功能就全部完成了,实现的效果如顶部视频所示。

其他权限设置

当然,这款App卡片功能要能完美的运行,还不能少了部分权限具体配置文件config.json修改如下所示:
  1. "module": {
  2.     "reqPermissions": [
  3.       {
  4.         "name": "ohos.permission.INTERNET"
  5.       },
  6.       {
  7.         "name": "ohos.permission.GET_NETWORK_INFO"
  8.       },
  9.       {
  10.         "name": "ohos.permission.SET_NETWORK_INFO"
  11.       }
  12.     ],
  13.     "metaData": {
  14.       "customizeData": [
  15.         {
  16.           "name": "hwc-theme",
  17.           "value": "androidhwext:style/Theme.Emui.Light.NoTitleBar"
  18.         }
  19.       ]
  20.     },
复制代码
其中,reqPermissions是权限,这里因为获取了网络的json数据,所以必须给与网络权限。而metaData是样式,这里我们去除了默认的标题栏。
` python.gif
liyuanjinglyj 2021-6-28 14:24:09
完整的项目地址:https://gitee.com/liyuanjinglyj/JavaCardDemo
回复

举报

dianzi 2021-6-28 15:08:34
感谢分享
回复

举报

评论

您需要登录后才可以回帖 登录 | 注册

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容图片侵权或者其他问题,请联系本站作侵删。 侵权投诉
发文章
快速回复
扫一扫分享
返回顶部 返回列表