接觸鴻蒙開發已經有 3 個來月了,最近開始在看鴻蒙卡片開發。因為之前的開發大都是基于 Java UI,但按官方的說法,JS 卡片相比 Java 卡片有更大的優勢,故決定寫個 JS 卡片的 demo 來練練手。
碰巧,前幾天和媳婦兒在散步時撿到 1 元錢,沒能交給警察叔叔,媳婦兒就提議“我們把它昧了吧,買張彩票。”
由于不是老 CM,沒有關注開獎的習慣,想著要是能把開獎結果放在手機桌面顯示就好了,這樣就不會錯過我一夜暴富的機會了。有了需求就開擼,然后就有了這篇文章!
項目簡介
①本項目基于 API 6 開發,demo 運行在 API 6 以下的手機上會出現部分功能不能使用的現象。
②卡片功能上實現了***、福彩 3D 的最近一期開獎結果查詢,點擊卡片“刷新”按鈕,調用接口更新最新數據;點擊“查看更多”按鈕,跳轉至應用主界面。
③應用主界面上實現了***、福彩 3D 近 50 期開獎結果查看。
④以上數據均使用了聚合數據的 https://www.juhe.cn/docs/api/id/300 免費接口。
需申請 key,一個 key 一天可免費調用 100 次,如遇 key使 用次數過多導致接口請求失敗情況時,開發者可自行申請 key 并替換 Constants.java 文件下的 JH_KEY 常量值),數據可能會有延遲。
⑤卡片開發部分使用了卡片的 JS UI 框架,但由于系統 PA 與 FA 相互調用的限制問題,卡片的業務邏輯部分仍然采用 Java 代碼編寫。
PS:最開始的想法是盡量可能的少依賴 Java 代碼,故嘗試了 JS FA 與 Java PA 相互調用的方式,但當應用進程被干掉時,Java 端無法再調用到 JS 端方法。
這樣就導致 JS 只能寫 UI 部分,業務邏輯還得 Java 層實現。有知道解決辦法的也麻煩告知一聲。
⑥卡片業務層使用 Java 開發,采用了簡單的 MVP 架構,網絡請求和數據處理部分使用了 rxjava3+retrofit 框架。
⑦應用主界面使用了 JS-UI 框架實現。
實現效果
請忽略我粗陋的 UI 設計和 GIF 的渣渣像素。
:
項目代碼結構分析
base:
IBasePresenter:MVP 架構中 presenter 基類接口
IBaseView:MVP 架構中 view 基類接口
network:
bean,LotteryBean:彩票詳情接口返回對應 model
CachedLotteryDetailUtil:彩票詳情接口請求工具類,主要作用是防止重復調用詳情接口
LogInterceptor:OKhttp 日志攔截工具類
LotteryAPI:接口請求類,通過 retrofit 注解,將接口返回數據轉化為實體類
Services:配置 Retrofit 并提供
presenter:
IMainContract:MVP 中 view 與 presenter 的橋梁
MainPresenter:提供彩票詳情接口的請求,并處理接口返回數據為卡片需要的 ZSONObject 對象
utils:
LogUtil:日志打印工具類
widget:
controller:FormController:創建卡片時自動生成,卡片管理器的抽象基類;FormControllerManager:創建卡片時自動生成,管理各個 FormController 的工具類
dltwidget.DltWidgetImpl:大樂透卡片管理類,提供了創建卡片、更新卡片、刪除卡片、卡片點擊事件等行為的回調方法
fcsdwidget.FcsdWidgetImpl:福彩 3D 卡片管理類,提供了創建卡片、更新卡片、刪除卡片、卡片點擊事件等行為的回調方法
ssqwidget.SsqWidgetImpl:雙色球卡片管理類,提供了創建卡片、更新卡片、刪除卡片、卡片點擊事件等行為的回調方法
Constants:常量工具類。
MainAbility:HAP 的入口 ability,由 DevEco Studio 自動生成。同時也是各個卡片對應的 Ability,用來項各個 FormController 分發事件。
default:
common,component/lottery:應用首頁列表 item 組件;images :資源圖片
pages,home:應用首頁
dlt_widget:
common:資源圖片存放目錄
pages/index,index.css :大樂透卡片 css 樣式;index.hml:大樂透卡片布局文件;index.json:包含頁面默認值
fcsd_widget:目錄結構同 dlt_widget。
ssq_widget:目錄結構同 dlt_widget。
詳細實現過程
①創建雙色球卡片
在目錄 entry 上點擊右鍵,在彈出的菜單中選擇 New,然后在彈出的子菜單中點擊 Service Widget,如下圖所示:
在模板選擇界面,選擇基本的模板 Grid Pattern,點擊按鈕 Next,進入到卡片配置界面:
首先配置卡片的名稱和描述;然后配置卡片關聯的 Page Ability;然后配置卡片的編程語言類型是 JS;接下來配置卡片的 JS 組件名稱;最后配置卡片支持的規格,勾選支持 2x4、4x4 規格。
重復上述步驟,創建出大樂透和雙色球卡片。運行項目,長按圖標打開卡片管理界面,我們能看到剛創建的 3 類卡片,且每類卡片對應 3 種不同樣式,如下圖所示:
②繪制雙色球卡片 UI
我們編寫雙色球界面:
《!--index.hml--》《div class=“container”》
《div》
《text class=“text”》雙色球《/text》
《text class=“text-small” style=“margin-left : 15px;” if=“{{ cardType2x4 || cardType4x4 }}”》每周二、四、日開獎《/text》
《text class=“text” style=“margin-left : 75px;” if=“{{ cardType2x4 || cardType4x4 }}” onclick=“showMore”》 查看更多
《/text》
《/div》
《div style=“margin-top : 10px; align-content : center;”》
《text class=“text-small” style=“margin-right : 10px;”》第{{ lotteryData.lottery_no }}期《/text》
《text class=“text-small” if=“{{ cardType2x4 || cardType4x4 }}”》 開獎日期:《/text》
《text class=“text-small”》 {{ lotteryData.lottery_date }}《/text》
《/div》
《div style=“flex-wrap : wrap; margin-top : 10px;”》
《text class=“ball” for=“{{ lotteryRed }}” tid=“id”》{{ $item }}《/text》
《text class=“ball” style=“background-color : blue;” for=“{{ lotteryBlue }}” tid=“id”》{{ $item }}《/text》
《/div》
《div class=“amount-box” if=“{{ cardType2x4 || cardType4x4 }}”》
《div style=“flex-direction : column;”》
《text class=“text-small”》本期全國銷量《/text》
《text class=“text-amount”》{{ lotteryData.lottery_sale_amount }}《/text》
《/div》
《text class=“diver”》《/text》
《div style=“flex-direction : column;”》
《text class=“text-small”》累計獎池《/text》
《text class=“text-amount”》{{ lotteryData.lottery_pool_amount }}《/text》
《/div》
《/div》
《div style=“flex-direction : column; padding-top : 10px; margin-bottom: 20px;” if=“{{ cardType4x4 }}”》
《div style=“background-color : green;”》
《text class=“text-prize”》獎項《/text》
《text class=“text-prize”》中獎條件《/text》
《text class=“text-prize”》中獎注數《/text》
《text class=“text-prize”》單注金額(元)《/text》
《/div》
《div for=“{{ lottery_prize }}”》
《text class=“text-prize”》{{ $item.prize_name }}《/text》
《text class=“text-prize”》{{ $item.prize_require }}《/text》
《text class=“text-prize”》{{ $item.prize_num }}《/text》
《text class=“text-prize”》{{ $item.prize_amount }}《/text》
《/div》
《/div》
《div》
《text class=“text-small” if=“{{ cardType2x4 || cardType4x4 }}”》更新時間:《/text》
《text class=“text-small”》{{ updateTime }}《/text》
《image class=“refresh” src=“/common/image_1.png” onclick=“updateData”》《/image》
《/div》《/div》
《!--index.json--》
{
“data”: {
“cardType2x2”: true,
“cardType2x4”: false,
“cardType4x4”: false,
“lotteryData”: {
“lottery_no”: “21081”,
“lottery_date”: “2021-07-20”,
“lottery_sale_amount”:“344,437,194”,
“lottery_pool_amount”:“997,378,346”
},
“lotteryRed”:[“01”,“03”,“05”,“18”,“22”,“23”],
“lotteryBlue”:[“01”],
“updateTime”: “2021/07/07 1459”,
“lottery_prize”:[
{
“prize_name”:“一等獎”,
“prize_num”:“4”,
“prize_amount”:“10,000,000”,
“prize_require”:“6+1”
},
{
“prize_name”:“二等獎”,
“prize_num”:“135”,
“prize_amount”:“207,725”,
“prize_require”:“6+0”
},
{
“prize_name”:“三等獎”,
“prize_num”:“879”,
“prize_amount”:“3,000”,
“prize_require”:“5+1”
},
{
“prize_name”:“四等獎”,
“prize_num”:“45659”,
“prize_amount”:“200”,
“prize_require”:“5+0,4+1”
},
{
“prize_name”:“五等獎”,
“prize_num”:“1001881”,
“prize_amount”:“10”,
“prize_require”:“4+0,3+1”
},
{
“prize_name”:“六等獎”,
“prize_num”:“6962930”,
“prize_amount”:“5”,
“prize_require”:“2+1,1+1,0+1”
}
]
},
“actions”: {
}
}
《!--index.css--》
.container {
width: 100%;
height: 100%;
padding: 10px;
flex-direction: column;
}
.text {
font-size: 15px;
}
.text-small {
font-size: 11px;
}
.text-amount {
font-size: 14px;
font-weight: bold;
color: darkred;
}
.text-prize {
font-size: 11px;
flex-weight: 25;
min-height: 20px;
text-align: center;
}
.ball {
width: 20px;
height: 20px;
text-align: center;
font-size: 12px;
margin: 4px;
color: #FFFFFF;
border-radius: 20px;
background-color: red;
}
.amount-box {
flex-direction: row;
align-items: center;
height: 30px;
}
.diver {
width: 1px;
height: 20px;
background-color: red;
margin-left: 15px;
margin-right: 15px;
}
.refresh {
width: 22px;
height: 22px;
}
編寫完成后,重新運行,不出意外你應該能看到如下效果:
這里提下卡片 JS-UI 框架的坑:
框架提供了原子布局來控制元素在不同尺寸布局上的隱藏和展示,但怎么說呢 ,一句話概括就是:你以為的并不是你以為的。
可以看到我這里放棄了 display-index 的使用,而采用通過 JAVA 端卡片的類型,來適配不同的 UI。
不同于應用開發中的 JS UI 框架,這里的條件渲染不支持表達式。
css 不支持標簽選擇器。
部分 css 樣式不能被繼承,例如給父 div 元素設置了 font-size,你會發現 div 中的 text 組件并沒繼承上述樣式。
列表渲染的 for 循環的數組必須是 index.json data 對象的最外層。
不支持雙層 for 循環。
api6 不兼容 api5 的設備。
你會發現卡片 JSON 文件中的 data 對象數據結構同接口返回的數據結構有些差異,就是因為上述原因導致的。
③獲取網絡數據
簡單的 MVP 架構,采用 Retrofit+RxJava 作為異步網絡請求框架。
build.gradle 中添加 RxJava 和 Retrofit 的依賴:
// RxJava
api ‘io.reactivex.rxjava33.0.3’
implementation ‘io.openharmony.tpc.thirdlib1.0.0’// retrofit
implementation ‘com.squareup.retrofit22.7.0’
implementation “com.squareup.retrofit22.1.0”
implementation ‘com.squareup.retrofit22.9.0’
config.json 文件中添加網絡權限:
// config.json“deviceConfig”: {
“default”: {
“network”: {
“cleartextTraffic”: true,
“securityConfig”: {
“domainSettings”: {
“cleartextPermitted”: true,
“domains”: [
{
“subdomains”: true,
“name”: “apis.juhe.cn”
},
{
“subdomains”: true,
“name”: “v.juhe.cn”
}
]
}
}
}
}
},
“module”: {
。..
“reqPermissions”: [
{
“name”: “ohos.permission.GET_NETWORK_INFO”
},
{
“name”: “ohos.permission.SET_NETWORK_INFO”
},
{
“name”: “ohos.permission.INTERNET”
}
],
。..
}
創建接口請求:
// LotteryAPI.javapublic interface LotteryAPI {
@POST(“/lottery/query”)
@FormUrlEncoded
Observable《LotteryBean》 queryDetail(@Field(“lottery_id”) String lottery_id, @Field(“lottery_no”) String lottery_no, @Field(“key”) String key);
}
P 層和 V 層的接口比較簡單,不在羅列。編寫 P 層業務邏輯:
//MainPresenter.java@Overridepublic void loadLotteryData(long formId, String lotteryId) {
CachedLotteryDetailUtil.getLotteryDetail(lotteryId)
.flatMap((Function《LotteryBean, ObservableSource《ZSONObject》》) res -》 {
if (0 != res.getError_code()) {
Throwable throwable = new Throwable(res.getReason());
return Observable.error(throwable);
}
ZSONObject zsonObject = buildDataByResult(res);
return Observable.just(zsonObject);
})
.subscribeOn(Schedulers.io())
.observeOn(OpenHarmonySchedulers.mainThread())
.subscribe(new Observer《ZSONObject》() {
@Override
public void onSubscribe(@NonNull Disposable d) {
}
@Override
public void onNext(@NonNull ZSONObject result) {
if (mView != null) {
mView.onLoadDataSuccess(formId, result);
}
}
@Override
public void onError(@NonNull Throwable e) {
if (mView != null) {
mView.onLoadDataFailed(e);
}
}
@Override
public void onComplete() {
}
});
}
/**
* 根據接口返回的數據,組裝成JS卡片需要的數據
*
* @param result
* @return
*/private ZSONObject buildDataByResult(LotteryBean result) {
LotteryBean.DetailBean detailBean = result.getResult();
ZSONObject data = new ZSONObject();
ZSONObject lotteryData = new ZSONObject();
lotteryData.put(“lottery_no”, detailBean.getLottery_no());
lotteryData.put(“lottery_date”, detailBean.getLottery_date());
lotteryData.put(“lottery_sale_amount”, detailBean.getLottery_sale_amount());
lotteryData.put(“lottery_pool_amount”, detailBean.getLottery_pool_amount());
data.put(“lotteryData”, lotteryData);
String[] ballArray = detailBean.getLottery_res().split(“,”);
if (“ssq”.equalsIgnoreCase(detailBean.getLottery_id())){
data.put(“lotteryRed”, Arrays.copyOfRange(ballArray, 0, 6));
data.put(“lotteryBlue”, Arrays.copyOfRange(ballArray, 6, 7));
} else if (“dlt”.equalsIgnoreCase(detailBean.getLottery_id())){
data.put(“lotteryRed”, Arrays.copyOfRange(ballArray, 0, 5));
data.put(“lotteryBlue”, Arrays.copyOfRange(ballArray, 5, 7));
} else if (“fcsd”.equalsIgnoreCase(detailBean.getLottery_id())){
data.put(“lotteryRed”, ballArray);
}
data.put(“updateTime”, new SimpleDateFormat(“yyyy-MM-dd HHss”).format(new Date()));
ZSONArray zsonArray = new ZSONArray();
for (LotteryBean.PrizeBean prizeBean : detailBean.getLottery_prize()) {
ZSONObject prize = new ZSONObject();
prize.put(“prize_name”, prizeBean.getPrize_name());
prize.put(“prize_num”, prizeBean.getPrize_num());
prize.put(“prize_amount”, prizeBean.getPrize_amount());
prize.put(“prize_require”, prizeBean.getPrize_require());
zsonArray.add(prize);
}
data.put(“lottery_prize”, zsonArray);
return data;
}
④V 層調用 P 層,更新卡片
這里提下 MainAbility 的 onCreateForm 方法,它在進入卡片管理界面時被調用,內部通過 FormControllerManager 獲取了卡片對應的 FormController,并調用了其 bindFormData() 方法。
由于我們卡片展示的數據來源于網絡請求,在對應的 FormController 實現類中不太好獲得 formId,所以我們稍微改造一下 FormController 的 bindFormData 方法,把 formId 給傳進去。
public abstract ProviderFormInfo bindFormData(long formId);
//MainAbility.javapublic class MainAbility extends AceAbility implements IMainContract.View {
private IMainContract.Presenter mPresenter;
@Override
public void onStart(Intent intent) {
。..
}
@Override
public void onStop() {
super.onStop();
}
@Override
protected ProviderFormInfo onCreateForm(Intent intent) {
HiLog.info(TAG, “onCreateForm”);
long formId = intent.getLongParam(AbilitySlice.PARAM_FORM_IDENTITY_KEY, INVALID_FORM_ID);
String formName = intent.getStringParam(AbilitySlice.PARAM_FORM_NAME_KEY);
int dimension = intent.getIntParam(AbilitySlice.PARAM_FORM_DIMENSION_KEY, DEFAULT_DIMENSION_2X2);
HiLog.info(TAG, “onCreateForm: formId=” + formId + “,formName=” + formName);
FormControllerManager formControllerManager = FormControllerManager.getInstance(this);
FormController formController = formControllerManager.getController(formId);
formController = (formController == null) ? formControllerManager.createFormController(formId,
formName, dimension) : formController;
if (formController == null) {
HiLog.error(TAG, “Get null controller. formId: ” + formId + “, formName: ” + formName);
return null;
}
return formController.bindFormData(formId);
}
@Override
protected void onUpdateForm(long formId) {
。..
}
@Override
protected void onDeleteForm(long formId) {
。..
}
@Override
protected void onTriggerFormEvent(long formId, String message) {
。..
}
@Override
public void onNewIntent(Intent intent) {
。..
}
private boolean intentFromWidget(Intent intent) {
。..
}
private String getRoutePageSlice(Intent intent) {
。..
}
private IMainContract.Presenter getPresenter() {
if (mPresenter == null) {
mPresenter = new MainPresenter(this);
}
return mPresenter;
}
/**
* 查詢彩票開獎詳情
*
* @param formId
* @param lotteryId
*/
public void loadData(long formId, String lotteryId) {
getPresenter().loadLotteryData(formId, lotteryId);
}
@Override
public void onLoadDataSuccess(long formId, ZSONObject data) {
try {
// 更新卡片
updateForm(formId, new FormBindingData(data));
} catch (FormException e) {
e.printStackTrace();
}
}
@Override
public void onLoadDataFailed(Throwable exception) {
。..
}
}
//SsqWidgetImpl.javapublic class SsqWidgetImpl extends FormController{
private static final HiLogLabel TAG = new HiLogLabel(HiLog.DEBUG, 0x0, SsqWidgetImpl.class.getName());
public SsqWidgetImpl(Context context, String formName, Integer dimension) {
super(context, formName, dimension);
}
@Override
public ProviderFormInfo bindFormData(long formId) {
HiLog.info(TAG, “===== ssq bind form data”);
ZSONObject zsonObject = new ZSONObject();
ProviderFormInfo providerFormInfo = new ProviderFormInfo();
boolean is2x2 = dimension == DEFAULT_DIMENSION_2X2;
boolean is2x4 = dimension == DIMENSION_2X4;
boolean is4x4 = dimension == DIMENSION_4X4;
zsonObject.put(“cardType2x2”, is2x2);
zsonObject.put(“cardType2x4”, is2x4);
zsonObject.put(“cardType4x4”, is4x4);
providerFormInfo.setJsBindingData(new FormBindingData(zsonObject));
// 調用接口更新卡片數據
((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);
return providerFormInfo;
}
@Override
public void updateFormData(long formId, Object.。. vars) {
}
@Override
public void onTriggerFormEvent(long formId, String message) {
}
@Override
public Class《? extends AbilitySlice》 getRoutePageSlice(Intent intent) {
HiLog.info(TAG, “get the default page to route when you click card.”);
return null;
}
}
⑤簡單的網絡優化
由于 onCreateForm(Intent intent)方法會被調用多次,而每個類型的卡片請求的數據一樣,聚合 api 一天 100 次免費請求的次數一會兒就用完了,故對此進行一個簡單的優化。
優先看內存緩存中是否有,有且為過期(緩存默認 10 分鐘有效期)則直接返回,否則阻塞等待接口請求完成,對于并發請求,若請求隊列已有相同的請求,則阻塞,否則創建新的請求。
若不關心接口調用的可略過本節:
// CachedLotteryDetailUtil.java/**
* CachedLotteryDetailUtil
* 緩存彩票詳情工具類,防止在卡片創建時,重復調用接口
*
* @author:xwg
* @since 2021-07-30
*/public class CachedLotteryDetailUtil {
//緩存的有效時間 默認10分鐘
private static final long CACHE_TIME = 10 * 60 * 1000;
// 緩存的彩票結果
private static volatile ConcurrentHashMap《String, LotteryBean》 cacheResMap = new ConcurrentHashMap《》();
//緩存的請求時間
private static volatile ConcurrentHashMap《String, Long》 reqTimeMap = new ConcurrentHashMap《》();
//正在請求 對應的lotteryId
private static volatile List《String》 requestingLotteryId;
/**
* 獲取彩票詳情
*
* @param lotteryId
* @return
*/
public static Observable《LotteryBean》 getLotteryDetail(String lotteryId) {
if (cacheResMap.get(lotteryId) != null) {
if (!isExpired(lotteryId)) {
return Observable.create(emitter -》 {
if (!emitter.isDisposed()) {
emitter.onNext(cacheResMap.get(lotteryId));
}
});
} else {
cacheResMap.remove(lotteryId);
}
}
if (isRequesting(lotteryId)) {
return waitToRequestEnd(lotteryId);
} else {
return requestDetail(lotteryId);
}
}
/**
* 是否正在請求
*
* @param lotteryId
* @return
*/
private static boolean isRequesting(String lotteryId) {
if (requestingLotteryId == null || lotteryId.isEmpty()) {
return false;
}
return requestingLotteryId.contains(lotteryId);
}
/**
* 阻塞等待請求結果
*
* @param lotteryId
* @return
*/
private static Observable《LotteryBean》 waitToRequestEnd(final String lotteryId) {
return Observable.create(emitter -》 {
while (isRequesting(lotteryId)) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
}
}
if (!emitter.isDisposed()) {
try {
if (cacheResMap != null && cacheResMap.get(lotteryId) != null && !isExpired(lotteryId)) {
LotteryBean lotteryBean = cacheResMap.get(lotteryId);
emitter.onNext(lotteryBean);
if (!emitter.isDisposed()) {
emitter.onComplete();
}
} else {
Throwable throwable = new Throwable(“請求異常,請稍后再試”);
emitter.onError(throwable);
}
} catch (Exception e) {
emitter.onError(e);
}
}
});
}
/**
* 聯網請求彩票詳情
*
* @param lotteryId
* @return
*/
private static Observable《LotteryBean》 requestDetail(final String lotteryId) {
if (requestingLotteryId == null) {
requestingLotteryId = new LinkedList《》();
}
if (!lotteryId.isEmpty() && !requestingLotteryId.contains(lotteryId)) {
requestingLotteryId.add(lotteryId);
}
return Services.createAPI(LotteryAPI.class).queryDetail(lotteryId, “”, Constants.JH_KEY)
.doOnError(throwable -》 {
requestingLotteryId.remove(lotteryId);
reqTimeMap.remove(lotteryId);
})
.doAfterNext(lotteryBean -》 {
requestingLotteryId.remove(lotteryId);
cacheResMap.put(lotteryId, lotteryBean);
reqTimeMap.put(lotteryId, System.currentTimeMillis());
});
}
/**
* 緩存是否已經過期
*
* @return
*/
private static boolean isExpired(String lotteryId) {
if (reqTimeMap == null || reqTimeMap.get(lotteryId) == null) {
return true;
}
return System.currentTimeMillis() - reqTimeMap.get(lotteryId) 》 CACHE_TIME;
}
}
⑥給卡片增加刷新事件和查看更多事件
給卡片的 index.json 文件添加 actions,定義 showMore 為 router 事件,觸發這個事件會跳轉到指定的 abilityName 對應的 Ability。
updateData 為 message 事件,觸發該事件,會回調 Ability 中的 onTriggerFormEvent(long formId, String message)方法。
// index.json
{
“data”: {
。..
},
“actions”: {
“showMore”: {
“action”: “router”,
“abilityName”: “com.xwg.lotteryquery.MainAbility”,
“params”: {
“message”: “fcsd”
}
},
“updateData”: {
“action”: “message”,
“params”: {
“key”: “fcsd”
}
}
}
}
卡片的布局文件中添加點擊事件:
《!--跳轉應用首頁事件--》《text class=“text” style=“margin-left : 120px;” if=“{{ cardType2x4 || cardType4x4 }}” onclick=“showMore”》 查看更多
《/text》
《!--點擊刷新按鈕事件--》《image class=“refresh” src=“/common/refresh.png” onclick=“updateData”》《/image》
// SsqWidgetImpl.java@Overridepublic void onTriggerFormEvent(long formId, String message) {
HiLog.info(TAG, “======== ssq handle card click event.”);
((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);
}
添加定時刷新:打開 config.json,對于標簽“scheduledUpdateTime”設定的時刻,當到達之后,MainAbility 中卡片的回調方法 onUpdateForm() 就會被自動調用。
updateDuration 默認為 1,下面配置表示:雙色球卡片允許定時刷新,從 10:30 開始,每隔半小時刷新一次。
// config.json“forms”: [
{
“jsComponentName”: “ssq_widget”,
“isDefault”: true,
“scheduledUpdateTime”: “10:30”,
“defaultDimension”: “2*2”,
“name”: “SsqWidget”,
“description”: “雙色球”,
“colorMode”: “auto”,
“type”: “JS”,
“supportDimensions”: [
“2*2”,
“2*4”,
“4*4”
],
“updateEnabled”: true,
“updateDuration”: 1
}
。..
]
FormController 直接調用 MainAbility 的獲取數據方法:
// SsqWidgetImpl.java@Overridepublic void updateFormData(long formId, Object.。. vars) {
HiLog.info(TAG, “======== ssq update form data timing, default 30 minutes”);
((MainAbility)context).loadData(formId,Constants.LOTTERY_ID_SSQ);
}
⑦大樂透和福彩 3D 卡片的實現
這兩個卡片的實現過程和雙色球卡片基本一致,主要是 UI 上有些區別。大樂透卡片 4X4 樣式中,由于中獎信息列表較長,引入了 list 和 list-item 組件,讓其可在卡片內上下滾動,具體實現此處不再贅述,有興趣可閱讀源碼。
⑧歷史開獎列表的實現
這個界面也相對簡單,使用了 swiper 組件,左右滑動或點擊頭部標簽欄,完成***、福彩 3D 標簽頁的切換,每個標簽頁展示對應的近 50 期開獎結果,使用 list 和 list-item 組件渲染。
由于各列表 item 展示 UI 相近,故將其抽成了組件放置于 */common/component/lottery* 目錄下。
lottery 子組件部分,lottery.js:通過 props 接收外部傳入的 lotteryData 數據。
// lottery.jsexport default {
props: {
lotteryData: {}
}
}
lottery.hml 組件布局代碼:
《!--lottery.hml--》《div class=“item”》
《div class=“item-header”》
《text style=“font-weight : bold;”》第{{ lotteryData.lottery_no }}期《/text》
《text class=“remarks”》開獎日期:{{ lotteryData.lottery_date }}《/text》
《/div》
《div style=“margin-left : 10px;”》
《text for=“{{ lotteryData.ballList }}”
class=“ball”
style=“background-color : {{ $idx 》= lotteryData.redBallCount ? ‘#6666FF’ : ‘#FF0033’ }};”
》{{ $item }}《/text》
《/div》
《div class=“amount-box”》
《div style=“flex-direction : column;”》
《text class=“text-small”》本期全國銷量《/text》
《text class=“text-amount”》{{ lotteryData.lottery_sale_amount }}《/text》
《/div》
《text class=“diver”》《/text》
《div style=“flex-direction : column;”》
《text class=“text-small”》累計獎池《/text》
《text class=“text-amount”》{{ lotteryData.lottery_pool_amount || ‘0’ }}《/text》
《/div》
《/div》《/div》
css 比較簡單,這里不再給出。
父組件實現:
《!--home.hml--》《element name=‘lottery’ src=‘。./。./common/component/lottery/lottery.hml’》《/element》《div class=“container”》
《div class=“title-container”》
《text class=“title {{ currentIdx == 0 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(0)”》雙色球《/text》
《text class=“title {{ currentIdx == 1 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(1)”》大樂透《/text》
《text class=“title {{ currentIdx == 2 ? tabSelected : tabUnSelected }}” onclick=“onTabClick(2)”》福彩3D《/text》
《/div》
《swiper class=“swiper” id=“swiper” index=“0” indicator=“false” loop=“true”
digital=“false” on:change=“onChange”》
《div class=“swiperContent”》
《list class=“”》
《list-item for=“{{ ssqResList }}” class=“lottery-item”》
《lottery lottery-data = “{{$item}}”》《/lottery》
《/list-item》
《/list》
《/div》
《div class=“swiperContent”》
《list class=“”》
《list-item for=“{{ dltResList }}” class=“lottery-item”》
《lottery lottery-data = “{{$item}}”》《/lottery》
《/list-item》
《/list》
《/div》
《div class=“swiperContent”》
《list class=“”》
《list-item for=“{{ fcsdResList }}” class=“lottery-item”》
《lottery lottery-data = “{{$item}}”》《/lottery》
《/list-item》
《/list》
《/div》
《/swiper》《/div》
《!--home.js--》
import http from ‘@ohos.net.http’;
const JH_URL = ‘http://apis.juhe.cn’const JH_KEY = ‘4931b786dd99c28e7e9990fb75c39fad’export default {
data: {
currentIdx: 0,
tabSelected: ‘tabSelected’,
tabUnSelected: ‘tabUnSelected’,
ssqResList: null, // 雙色球近期開獎結果列表
dltResList: null, // 大樂透近期開獎結果列表
fcsdResList: null, // 3D球近期開獎結果列表
},
onInit() {
this.querySsqHis();
this.queryDltHis();
this.queryFcsdHis();
},
// 查詢雙色球歷史開獎
querySsqHis() {
console.info(‘xwg== querySsqHis ----------’);
let _t = this
let httpRequest = http.createHttp();
let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY
httpRequest.request(url, (err, data) =》 {
if (err == null) {
let lotteryResList = JSON.parse(data.result).result.lotteryResList
_t.ssqResList = lotteryResList.map(item =》 {
item.ballList = item.lottery_res.split(“,”)
item.redBallCount = 6
return item
})
console.info(‘xwg== ssqResList:’ + JSON.stringify(_t.ssqResList));
} else {
console.info(‘xwg== error:’ + JSON.stringify(err));
}
});
},
// 查詢大樂透歷史開獎
queryDltHis() {
let _t = this
let httpRequest = http.createHttp();
let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY
httpRequest.request(url, (err, data) =》 {
if (err == null) {
let dltRes = JSON.parse(data.result).result.lotteryResList
_t.dltResList = dltRes.map(item =》 {
item.ballList = item.lottery_res.split(“,”)
item.redBallCount = 5
return item
})
} else {
console.info(‘xwg== error:’ + JSON.stringify(err));
}
});
},
// 查詢福彩3D歷史開獎
queryFcsdHis() {
let _t = this
let httpRequest = http.createHttp();
let url = JH_URL + ‘/history’ + ‘?lottery_id=ssq&page_size=50&page=&key=’ + JH_KEY
httpRequest.request(url, (err, data) =》 {
if (err == null) {
let fcsdRes = JSON.parse(data.result).result.lotteryResList
_t.fcsdResList = fcsdRes.map(item =》 {
item.ballList = item.lottery_res.split(“,”)
item.redBallCount = 3
return item
})
} else {
console.info(‘xwg== error:’ + JSON.stringify(err));
}
});
},
// swiper滑動監聽
onChange(value) {
this.currentIdx = value.index
},
// tab click事件
onTabClick(idx) {
this.currentIdx = idx
this.$element(‘swiper’).swipeTo({
index: idx
});
},
computed: {}
}
尚未解決的問題:這里引入了 http 組件進行網絡請求,但在請求聚合接口時,失敗率很高,但嘗試請求別的網站的 api 時沒有此現象,目前尚不知原因。
總結
由于這也是我第一次使用 JS UI 框架進行卡片開發的項目,水平有限,難免會對官方部分 API 理解不到位甚至理解有誤的地方,希望大家也多多指正,共同進步。
這一路上雖然磕磕巴巴,也有很多吐槽,但我們從卡片 JS-UI API 5 到 API 6 功能上逐漸靠攏應用 JS-UI 上也能看出來鴻蒙的努力,給它點時間,相信它功能上會變得更強大、完善;對于開發也會變得更快捷、簡單。
最后附上項目地址:lottery-query,請自行下載資源,歡迎交流學習。
https://gitee.com/chinasoft6_ohos/lottery-query
責任編輯:haq
-
鴻蒙系統
+關注
關注
183文章
2634瀏覽量
66309 -
HarmonyOS
+關注
關注
79文章
1974瀏覽量
30149
原文標題:超實用!鴻蒙彩票查詢卡片
文章出處:【微信號:gh_834c4b3d87fe,微信公眾號:OpenHarmony技術社區】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論