[Daily HarmonyOS Next Knowledge] Dark Mode Resources, Caching Examples, Focus Acquisition, Cross-Module Navigation, Tabs Component Issues
1. Does HarmonyOS dark mode adaptation require preparing a separate set of resources?
For dark mode adaptation, should another set of resources be prepared?
There are two scenarios:
-
The app follows the system to switch between dark mode and non-dark mode:
-
Color adaptation: Customize two sets of color resources (resources/dark/element/color.json and resources/base/element/color.json), and load color resource keys via
$r
. System-provided resources can also be used directly, which are layered parameters. The same resource ID has different values under different configurations (e.g., device type, light/dark mode). Using system resources allows developers to create apps with consistent visual styles without customizing two color resource sets, as colors will automatically switch between light and dark modes. https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-resource-manager-V5#resource9 -
Image resources: Use resource qualifier directories. Similar to color adaptation, place dark-mode images with the same name in the
dark/media
directory, and load image resource keys via$r
. The system will automatically load corresponding values when switching between light and dark modes. For simple SVG icons, thefillColor
attribute can be combined with system resources to change the drawing color, achieving dark mode adaptation without two sets of image resources.
-
Color adaptation: Customize two sets of color resources (resources/dark/element/color.json and resources/base/element/color.json), and load color resource keys via
-
The app does not follow the system and switches between dark mode and non-dark mode internally:
- The app actively sets the light/dark mode via system APIs, such as
setColorMode
: https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-inner-application-applicationcontext-V5 - Listen for light/dark mode switch events and implement the mode manually using
this.context.config.colorMode
andonConfigurationOnUpdate
.
- The app actively sets the light/dark mode via system APIs, such as
2. HarmonyOS caching demo verification?
Reference code:
import { promptAction } from '@kit.ArkUI';
import { preferences } from '@kit.ArkData';
let context = getContext(this);
let preference: preferences.Preferences;
let preferenceTemp: preferences.Preferences;
// Object class
export class ImageModel {
constructor(id: string, tokenInfo: string, imgInfo: string, urlInfo: string) {
this.id = id
this.token = tokenInfo
this.img = imgInfo
this.url = urlInfo
}
id?: string = '';
token: string = '';
img: string = '';
url: string = ''
}
// Preference utility class
class PreferenceModel {
// Initialization
async getPreferencesFromStorage() {
try {
preference = await preferences.getPreferences(context,'ImageUrlData.db');
} catch (err) {
console.error('[PreferenceModel]', `Failed to get preferences, Cause: ${err}`);
}
}
// Delete preferences
async deletePreferences() {
try {
await preferences.deletePreferences(context, 'ImageUrlData.db');
} catch(err) {
console.error('[PreferenceModel]', `Failed to delete preferences, Cause: ${err}`);
};
preference = preferenceTemp;
this.showToastMessage('删除数据库成功');
}
// Insert value
async putPreference(Image:string, ImageInfo: ImageModel) {
if (!preference) {
await this.getPreferencesFromStorage();
}
try {
await preference.put(Image, JSON.stringify(ImageInfo));
} catch (err) {
console.error('[PreferenceModel]', `Failed to put value, Cause: ${err}`);
}
await preference.flush();
}
// Get value
async getPreference(Image: string) {
let ImageInfo = '';
if (!preference) {
await this.getPreferencesFromStorage();
}
try {
ImageInfo = (await preference.get(Image, '')).toString();
} catch (err) {
console.error('[PreferenceModel]', `Failed to get value, Cause: ${err}`);
}
if (ImageInfo === '') {
this.showToastMessage('数据为空,请先写入数据');
return;
}
this.showToastMessage('写入数据成功');
return JSON.parse(ImageInfo);
}
async getImageData(Image: string) {
return await this.getPreference(Image);
}
// Write data
writeData(Image:string, ImageInfo: ImageModel) {
this.putPreference(Image, ImageInfo);
this.showToastMessage('写入数据成功');
}
showToastMessage(message: string) {
promptAction.showToast({
message: message,
duration: 3000
});
};
}
/**
* Preference operation
*/
export class LocalDataManager {
preferenceModel = new PreferenceModel()
private static localDataManager: LocalDataManager;
private constructor() {
this.preferenceModel.getPreferencesFromStorage()
}
static instance() {
if (!LocalDataManager.localDataManager) {
LocalDataManager.localDataManager = new LocalDataManager();
}
return LocalDataManager.localDataManager;
}
async queryImageInfo(Image:string) {
return await this.preferenceModel.getImageData(Image)
}
insertImageInfo(Image:string, ImageInfo: ImageModel) {
this.preferenceModel.writeData(Image, ImageInfo)
return Image.length
}
updateImageInfo(Image:string, ImageInfo: ImageModel) {
this.preferenceModel.writeData(Image, ImageInfo)
return Image.length
}
deleteImageInfo() {
this.preferenceModel.deletePreferences()
}
}
// Use preferences for caching
@Entry
@Component
struct NeiMengGuNongJi {
private localDataManager: LocalDataManager = LocalDataManager.instance();
@State token_Info: string = ''
private img: string = 'image00'
private url: string = 'url000'
@State num: number = 0
@State keyInfo: string = ''
@State imageData: ImageModel = new ImageModel('','','','')
build() {
Column() {
Button('点击存入数据库')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(()=> {
for (let i = 1; i <= 10; i++) {
let tokenInfo = "token00"+i
let image = this.img+i
let urlInfo = this.url+i
let ImageInfo = new ImageModel("", tokenInfo, image, urlInfo);
this.localDataManager.insertImageInfo(image, ImageInfo)
}
})
TextInput({placeholder:'输入key值:例如image001'})
.type(InputType.Normal)
.onChange((key: string) => {
this.keyInfo = key
})
Button('通过key值:'+this.keyInfo+", 获取value值")
.fontSize(18)
.fontWeight(FontWeight.Bold)
.onClick(async()=> {
if(this.keyInfo !== ''){
this.imageData = await this.localDataManager.queryImageInfo(this.keyInfo) as ImageModel
console.info("imageData="+JSON.stringify(this.imageData))
}
})
Text("key: "+this.keyInfo+", value: " + (this.imageData.token === ''? '' : JSON.stringify(this.imageData))).fontSize(40)
}
.width('100%')
}
}
3. Why does focusControl.requestFocus(id: string) lose focus after navigating back to the page in HarmonyOS?
When using focusControl.requestFocus(id: string)
, the focus is lost after navigating back to the page.
Solution: Mark a status in onAppear
when the page returns to maintain focus.
4. Can HarmonyOS router navigate between different modules?
The entry module depends on the feature module basecore, which has a BaseIndex.ets in its pages. Using router.pushUrl({url:‘pages/BaseIndex’}, router.RouterMode.Standard ,(err)=>{})
in the entry module has no effect.
- A feature is an ability-type module, and each feature maintains its own routing stack. Navigating to other pages requires using
startAbility
, whilerouter
is for intra-page navigation. These cannot be mixed. - To avoid cross-module navigation, consider a single UIAbility with multiple Hsps.
For cross-module navigation, refer to the document:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/uiability-intra-device-interaction-V5
When an app contains multiple UIAbilities, there may be scenarios where one UIAbility starts another. For example, starting a payment UIAbility from the entry UIAbility in a payment app.
Suppose the app has two UIAbilities: EntryAbility and FuncAbility (in the same or different modules). To start FuncAbility from EntryAbility's page:
In EntryAbility, use startAbility()
with a want
object as the entry parameter, including bundleName
(target app's bundle name), abilityName
(target ability name), moduleName
(if target UIAbility is in a different module), and parameters
(custom parameters). The context
can be obtained as shown:
import { common, Want } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
import { BusinessError } from '@kit.BasicServicesKit';
const TAG: string = '[Page_UIAbilityComponentsInteractive]';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Page_UIAbilityComponentsInteractive {
private context = getContext(this) as common.UIAbilityContext;
build() {
Column() {
//...
List({ initialIndex: 0 }) {
ListItem() {
Row() {
//...
}
.onClick(() => {
let wantInfo: Want = {
deviceId: '', // Empty deviceId means local device
bundleName: 'com.samples.stagemodelabilitydevelop',
moduleName: 'entry', // moduleName is optional
abilityName: 'FuncAbilityA',
parameters: {
// Custom information
info: '来自EntryAbility Page_UIAbilityComponentsInteractive页面'
},
};
this.context.startAbility(wantInfo).then(() => {
hilog.info(DOMAIN_NUMBER, TAG, 'startAbility success.');
}).catch((error: BusinessError) => {
hilog.error(DOMAIN_NUMBER, TAG, 'startAbility failed.');
});
})
}
//...
}
//...
}
//...
}
}
In FuncAbility's onCreate()
or onNewWant()
lifecycle callback, receive parameters from EntryAbility:
import { AbilityConstant, UIAbility, Want } from '@kit.AbilityKit';
export default class FuncAbilityA extends UIAbility {
onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void {
// Receive parameters from the calling UIAbility
let funcAbilityWant = want;
let info = funcAbilityWant?.parameters?.info;
}
//...
}
After completing operations in FuncAbility, stop the current UIAbility instance using terminateSelf()
:
import { common } from '@kit.AbilityKit';
import { hilog } from '@kit.PerformanceAnalysisKit';
const TAG: string = '[Page_FromStageModel]';
const DOMAIN_NUMBER: number = 0xFF00;
@Entry
@Component
struct Page_FromStageModel {
build() {
Column() {
//...
Button('FuncAbilityB')
.onClick(() => {
let context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext;
context.terminateSelf((err) => {
if (err.code) {
hilog.error(DOMAIN_NUMBER, TAG, `Failed to terminate self. Code is ${err.code}, message is ${err.message}`);
return;
}
});
})
}
//...
}
}
To close all UIAbility instances of the app, use ApplicationContext.killAllProcesses()
.
5. Issues with TabContent in HarmonyOS Tabs component
Is the content of TabContent in the Tabs component automatically centered?
TabContent content is centered by default. Solutions:
- Adjust the child component's height freely via the
margin
attribute (suitable when child components do not change). - Add a Column container in TabContent and configure its attributes to achieve the layout.
Top comments (0)