HarmonyOS实战—将CSDN博文搬上鸿蒙卡片

Posted 李元静

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了HarmonyOS实战—将CSDN博文搬上鸿蒙卡片相关的知识,希望对你有一定的参考价值。


HarmonyOS实战

目前,CSDN官方App并没有适配鸿蒙系统,但是我们是程序员,完全可以自己开发,何须等待CSDN呢?

自己动手丰衣足食,今天,我们来实现一个有趣的鸿蒙卡片。也就是将自己的最新的博文前10篇搬上鸿蒙卡片,并展示给大家。


除了将10篇最新的博文搬上鸿蒙卡片之外,我们还需要给鸿蒙的卡片提供可编辑的功能,让用户替换博主,自动替换对应博主的前10篇博文。

下面,我们来一一实现这些功能。

创建4*4的卡片

我们首先需要观察一下CSDN博文的标题长度,可以发现有些CSDN标题还是很长的,如果用小卡片肯定连标题都显示不下。所以,我们需要提供4*4的长卡片。

创建步骤如下,这里我们首先通过DevEco Studio创建一个纯JS项目,如下图所示:



项目创建完成之后,我们会进入项目开发页面。这里选择entry-src右键创建JS的4*4的卡片内容,具体创建步骤如下所示:




这样,我们就完成了卡片的创建。但是这里一般来说,因为我们刚创建项目的时候,没有默认的2*2卡片,所以这里会创建2*2和4*4两个卡片。

不过,2*2太小,并不能完整显示博文列表。下面,我们来实现博文浏览的4*4功能卡片。

实现4*4博文列表

首先,我们可以回博文的最上面看看最终的实现效果。可以发现,我们的4*4卡片有头像、姓名、简介以及一个最新的博文列表。

博文卡片的定义

所以,我们需要创建这样一个布局,来完美搭建这些信息,并完成博文点击的交互。首先,是我们需要实现的界面布局代码(index.hml):

<div class="div_layout" >
    <div style="flex-direction: row;height: 80px;margin: 12px;" >
        <image class="head_img" src="随便填一个图片链接"></image>
        <div style="flex-direction: column;margin-left: 12px;">
            <text class="head_name" >{{name}}</text>
            <text class="head_content" >{{content}}</text>
        </div>
    </div>
    <list>
        <list-item for="{{ blogList }}" style="flex-direction: column;">
            <div class="list_item_container" onclick="sendRouteEvent" >
                <text class="item_title">{{ $item.title }}</text>
            </div>
            <divider class="divider"></divider>
        </list-item>
    </list>
</div>

这里,我们的image组件头像用的是固定的image图片。因为获取到的CSDN头像图片,image组件不更新,只有Java卡片目前能完美实现该功能。(js卡片好像只有展示第一次能显示图片,后面更新图片都不显示)

这也是我期望反馈给鸿蒙官方的问题。所以,这里我们用固定的头像替代,除了头像图片无法替换之外,其他信息可以完美替换。

接着,我们需要实现样式(index.css)代码:

.div_layout{
    flex-direction: column;
    width: 100%;
    height: 100%;
    background-image: url("common/background.png");
}

.list_item_container{
    margin: 12px;
    flex-direction: column;
}

.item_title{
    font-size: 15px;
    font-weight: bold;
}

.divider {
    min-height: 0.75px;
    background-color: #11000000;
    margin-left: 12px;
    margin-right: 12px;
}

.head_img{
    border-radius: 30px;
    width: 60px;
    height: 60px;
}

.head_name{
    margin-top: 13px;
    font-size: 12px;
    font-weight: bold;
}

.head_content{
    font-size: 12px;
}

最后,就是完成交互信息的反馈。卡片的交互变量以及交互跳转界面都是通过index.json文件进行定义的,代码如下:

{
  "data": {
    "blogList": "",
    "head": "https://avatar.csdnimg.cn/6/8/2/3_liyuanjinglyj_1623337572.jpg",
    "name": "李元静",
    "content": "一个能让你轻松学习鸿蒙与Python的博主"
  },
  "actions": {
    "sendRouteEvent": {
      "action": "router",
      "bundleName": "com.liyuanjinglyj.csdncard",
      "abilityName": "com.liyuanjinglyj.csdncard.WebViewPage",
      "params": {
        "url": "{{$item.url}}"
      }
    }
  }
}

其中,blogList是博文列表信息,head是头像,但因为image不更新,这里忽略。name是博文的归属者昵称,content是博主的简介。

actions这里只定义了一个跳转界面的交互,也就是用户点击博文信息,然后就跳转到博文的详细页面。参数为博文的链接,在博文详细页面通过WebView进行加载。

博文信息的获取

既然要获取到博文首页的博文信息以及用户的资料,这就涉及到爬虫解析。而Java比较好用的爬虫解析包是jsoup。

而后面我们选择替换博主博文信息,也是同一个方法。所以,需要将解析的代码独立出来,减少代码的冗余程度。具体的帮助类为LYJUtils.java:

public class LYJUtils {
	public static void getData(ZSONObject zsonObject,ZSONArray zsonArray,String url) {
        try {
            Connection connect = Jsoup.connect(url);
            Connection conheader = connect.header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (Khtml, like Gecko) Chrome/91.0.4472.124 Safari/537.36");
            Document doc = conheader.get();
            //获取头像
            Element head_img=doc.selectFirst(".avatar_pic");
            String head_img_url=head_img.attr("src");
            //获取博主名称
            Element name_ele=doc.selectFirst(".title-box");
            String name=name_ele.selectFirst("a").text();
            //获取博主简介
            String content=name_ele.selectFirst("p").text();
            //zsonObject.put("head",head_img_url);
            zsonObject.put("name",name);
            if(content.length()>20){
                zsonObject.put("content",content.substring(0,20));
            }else{
                zsonObject.put("content",content);
            }
            Elements list_blog = doc.select(".article-item-box");
            int i=0;
            for (Element div : list_blog) {
                if(i==10){
                    break;
                }
                String titleStr = div.select("h4 a").text();
                String contentStr = div.select(".content").text();
                ZSONObject zsonObject1=new ZSONObject();
                zsonObject1.put("title",titleStr.replace("原创 ",""));
                zsonObject1.put("content",contentStr.substring(0,20));
                zsonObject1.put("url",div.select("h4 a").attr("href"));
                zsonArray.add(zsonObject1);
                i++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

而卡片的数据初始化方法在WidgetImpl.java类之中,它的初始化代码如下所示:

public class WidgetImpl extends FormController {
	//其他代码省略,基本都是创建卡片的默认代码
	private ZSONObject zsonObject = new ZSONObject();
    private ZSONArray zsonArray=new ZSONArray();
    @Override
    public ProviderFormInfo bindFormData() {
        HiLog.info(TAG, "bind form data");
        LYJUtils.getData(zsonObject,zsonArray,"https://blog.csdn.net/liyuanjinglyj");
        zsonObject.put("blogList",zsonArray);
        ProviderFormInfo providerFormInfo = new ProviderFormInfo();
        providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));
        return providerFormInfo;
    }
}

这样运行之后,我们的初始化卡片博文样式就完美实现了。

卡片交互

到这里,我们仅仅实现了卡片的数据展示。但我们看一个博主的博文并不是只看标题的,而是要看自己感兴趣的内容。所以,点击博文标题应该实现跳转到博文详情界面。

跳转到博文

首先,我们实现点击博文标题跳转到博文。读者可以往博文前面看一下,是不是有index.json文件中有一个actions定义,这里的类就是跳转的界面。

WebViewPageSlice.java代码如下所示:

public class WebViewPageSlice extends AbilitySlice {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, WebViewPageSlice.class.getName());
    private WebView webView;
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_web_view_page);
        this.webView=(WebView)findComponentById(ResourceTable.Id_ability_web_view_page_webview);
        WindowManager.getInstance().getTopWindow().get().setStatusBarColor(Color.BLUE.getValue()); // 设置状态栏颜色
        getWindow().addFlags(WindowManager.LayoutConfig.MARK_TRANSLUCENT_STATUS);//沉浸式状态栏
        if(intent != null) {
            HiLog.info(TAG, String.valueOf(1111));
            String param = intent.getStringParam("params");//从intent中获取 跳转事件定义的params字段的值
            String url = "";
            if(param !=null){
                ZSONObject data = ZSONObject.stringToZSON(param);
                url = data.getString("url");
            }
            HiLog.info(TAG, url);
            this.webView.getWebConfig().setjavascriptPermit(true);
            this.webView.load(url);
        }
    }

}

这个界面的内容很简单,就是获取跳转传递过来的网址参数信息。然后WebView组件,根据网址的内容即可,当然需要支持JavaScript,不然加载出来的界面非常难看。

博文的XML布局文件代码如下所示(ability_web_view_page.xml):

<?xml version="1.0" encoding="utf-8"?>
<DirectionalLayout
    xmlns:ohos="http://schemas.huawei.com/res/ohos"
    ohos:height="match_parent"
    ohos:width="match_parent"
    ohos:alignment="center"
    ohos:orientation="vertical">

    <ohos.agp.components.webengine.WebView
        ohos:id="$+id:ability_web_view_page_webview"
        ohos:height="match_parent"
        ohos:width="match_parent"/>

</DirectionalLayout>

编辑替换卡片内容

不过,这些内容都是博主自己的。肯定有读者也对本博主的内容不感兴趣,想要看其他博主的内容怎么办?

我们这里,可以提供一个滑动可选组件,让用户选择自己感兴趣的博主。这样卡片就能完成更新,达到真实意义上的交互。

首先,我们需要定义卡片的编辑跳转界面,需要在config.json文件中定义,代码如下:

{
        "name": "com.liyuanjinglyj.csdncard.widget.WidgetAbility",
        "icon": "$media:icon",
        "description": "$string:widget_widgetability_description",
        "formsEnabled": true,
        "label": "$string:entry_WidgetAbility",
        "type": "page",
        "forms": [
          {
            "jsComponentName": "widget",
            "isDefault": true,
            "formConfigAbility": "ability://com.liyuanjinglyj.csdncard.CSDNChoiceBlogAbility",
            "scheduledUpdateTime": "10:30",
            "defaultDimension": "4*4",
            "name": "widget",
            "description": "This is a service widget",
            "colorMode": "auto",
            "type": "JS",
            "supportDimensions": [
              "4*4"
            ],
            "updateEnabled": true,
            "updateDuration": 1
          }
        ],
        "launchType": "singleton"
      },

这里,主要的定义代码是formConfigAbility,它负责提供卡片的编辑交互功能的跳转界面,与之前跳转界面一样,就是一个普通的Java界面类。

下面,我们实现这里编辑界面,并提供完成交互的能力。

public class CSDNChoiceBlogAbilitySlice extends AbilitySlice {
    private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, CSDNChoiceBlogAbilitySlice.class.getName());
    private Picker picker;
    private Button button;
    private Long formId;
    private Map<String,String> csdnBlog=new HashMap<>();
    private String url="https://blog.csdn.net/qq_39046854";
    private static List<String> headImgUrl=new ArrayList<>();
    @Override
    public void onStart(Intent intent) {
        super.onStart(intent);
        super.setUIContent(ResourceTable.Layout_ability_csdnchoice_blog);
        WindowManager.getInstance().getTopWindow().get().setTransparent(true);
        formId=intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, -1);
        init();
        getWindow().addFlags(WindowManager.LayoutConfig.MARK_TRANSLUCENT_STATUS);
        this.button=(Button)findComponentById(ResourceTable.Id_ability_csdnchoice_blog_button);
        this.picker=(Picker) findComponentById(ResourceTable.Id_ability_csdnchoice_blog_picker);
        String[] keyStr= Arrays.stream(csdnBlog.keySet().toArray()).toArray(String[]::new);
        this.picker.setDisplayedData(keyStr);
        this.picker.setWheelModeEnabled(true);
        this.picker.setMaxValue(csdnBlog.size()-1);
        this.picker.setValueChangedListener(new Picker.ValueChangedListener() {
            @Override
            public void onValueChanged(Picker picker, int i, int i1) {
                if(i1>=0 && i1<csdnBlog.size()){
                    url=csdnBlog.get(keyStr[i1]);
                }

            }
        });
        this.button.setClickedListener(new Component.ClickedListener() {
            @Override
            public void onClick(Component component) {
                try {
                    HiLog.info(TAG, url);
                    TaskDispatcher globalTaskDispatcher = getGlobalTaskDispatcher(TaskPriority.DEFAULT);
                    Revocable revocable = globalTaskDispatcher.asyncDispatch(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                ZSONArray zsonArray=HarmonyOS实战:基于鸿蒙服务卡片的分布式游戏

我的HarmonyOS实战 — 一篇文章讲明白什么是鸿蒙2.0服务卡片

HarmonyOS实战—卡片的样式设计

HarmonyOS实战—可编辑的卡片交互

HarmonyOS实战—欧洲杯还可以这么玩?

HarmonyOS实战—探索鸿蒙OS