簡介
在自學鴻蒙開發(fā)的過程中柳琢,記錄下自己遇到的問題以及解決辦法,持續(xù)更新W咴健?贩耐!
環(huán)境信息:
- 系統(tǒng):Mac 13.5 (22G74)
- IDE信息:
- DevEco Studio 3.1.1 Release
- Build Version: 3.1.0.501, built on June 20, 2023
一、學習文檔
1厦取、HarmonyOS 第一課
2潮太、鴻蒙開發(fā)者文檔指南
3、鴻蒙開發(fā)者文檔API參考
4虾攻、華為開發(fā)者論壇
5铡买、鴻蒙開發(fā)基礎認證和高級認證
6、鴻蒙應用發(fā)布
二霎箍、遇到的問題
1奇钞、DevEco Studio自動生成.js唇聘、.js.map文件問題
通過插件(ArkCompilerSupport)搞定
2涯雅、DevEco Studio無法自動監(jiān)測到本地模擬器?
每次點擊DeviceManager啟動模擬器后粱哼,DevEco Studio都不會自動檢測到模擬器問題顶别,可以參考以下代碼片段進行開發(fā)環(huán)境配置谷徙。
# 鴻蒙腳本工具集
export PATH=/Users/XXX/HMOSProject/EnvInfo/sdk/openharmony/9/toolchains:$PATH
export PATH=/Users/XXX/HMOSProject/EnvInfo/ohpm/bin:$PATH
# HDC是為開發(fā)者提供HarmonyOS應用/服務的調(diào)試工具,為方便使用HDC工具驯绎,請為HDC端口號設置環(huán)境變量完慧。
export HDC_SERVER_PORT=7035
launchctl setenv HDC_SERVER_PORT $HDC_SERVER_PORT
export HDC_SERVER_PORT
3、網(wǎng)絡請求如何實現(xiàn)剩失?
- 方法1:第三方組件:axios
- 方法2:系統(tǒng)API:
http
屈尼,封裝示例如下:
import http from '@ohos.net.http';
import http from '@ohos.net.http';
// 標準網(wǎng)絡請求返回數(shù)據(jù)結(jié)構(gòu)
export interface SJRequestConfig extends http.HttpRequestOptions{
url?:string; // url參數(shù)
}
// 全局配置對象
export interface SJGlobalConfig {
// baseUrl
baseUrl?: string;
// 是否使用緩存:默認為true
usingCache?: boolean;
// 全局header册着,默認值為 'content-type': 'application/json'
header?: Object;
// 讀取超時時間:默認60000,即60s
readTimeout?: number; // default is 60s
// 連接超時時間:默認60000脾歧,即60s
connectTimeout?: number; // default is 60s.
// HTTP協(xié)議版本甲捏,默認由系統(tǒng)決定
usingProtocol?: http.HttpProtocol;
}
// 標準網(wǎng)絡請求返回數(shù)據(jù)結(jié)構(gòu)
export class SJResponse<T> {
status?: number;
message?: string;
data?: T;
}
// 網(wǎng)絡庫返回的錯誤對象
export class SJError<R> {
code:Number; // HTTP錯誤碼
message?:string; // HTTP錯誤信息
businessCode?:Number; // 業(yè)務code
businessMessage?:string; // 業(yè)務message
data?:R; //原始數(shù)據(jù)
}
export class SJHttp {
request<T,R= string | Object | ArrayBuffer>(config:SJRequestConfig): Promise<T> {
// 每一個httpRequest對應一個HTTP請求任務,不可復用
let httpRequest = http.createHttp();
// 請求參數(shù)配置
let httpResponse = httpRequest.request(config.url,config)
return new Promise((resolve,reject)=>{
httpResponse.then((data: http.HttpResponse) => {
let httpCode = data.responseCode;
if (httpCode == 200) {
// data.result為HTTP響應內(nèi)容涨椒,可根據(jù)業(yè)務需要進行解析
let resultStr = `${data.result}`
let response:SJResponse<T> = JSON.parse(resultStr)
if (response.status == 200) {
resolve(response.data);
} else {
var sjErr:SJError<R> = new SJError();
sjErr.code = httpCode;
sjErr.message = "";
sjErr.businessCode = response.status;
sjErr.businessMessage = response.message;
sjErr.data = data.result as R;
reject(sjErr)
}
}
var sjErr:SJError<R> = new SJError();
sjErr.code = httpCode;
sjErr.message = `${data.result}`;
sjErr.businessCode = null;
sjErr.businessMessage = null;
sjErr.data = data.result as R;
reject(sjErr)
}).catch((err) => {
console.info('error:' + JSON.stringify(err));
// 取消訂閱HTTP響應頭事件
httpRequest.off('headersReceive');
// 當該請求使用完畢時摊鸡,調(diào)用destroy方法主動銷毀。
httpRequest.destroy();
var sjErr:SJError<R> = new SJError();
sjErr.code = err.code;
sjErr.message = err.message;
sjErr.businessCode = null;
sjErr.businessMessage = null;
sjErr.data = null;
reject(sjErr)
})
})
}
}
export class SJRequest {
// 單例對象
static instance: SJRequest = new SJRequest();
// 全局配置對象
public globalConfig:SJGlobalConfig;
constructor() {
this.globalConfig = {
usingCache:true,
readTimeout:60000,
connectTimeout:60000,
usingProtocol:http.HttpProtocol.HTTP1_1,
}
}
get<T>(config:SJRequestConfig): Promise<T> {
config.method = http.RequestMethod.GET;
return this.request(config);
}
post<T>(config:SJRequestConfig): Promise<T> {
config.method = http.RequestMethod.POST;
return this.request(config);
}
delete<T>(config:SJRequestConfig): Promise<T> {
config.method = http.RequestMethod.DELETE;
return this.request(config);
}
put<T>(config:SJRequestConfig): Promise<T> {
config.method = http.RequestMethod.PUT;
return this.request(config);
}
request<T>(config:SJRequestConfig): Promise<T> {
this.handleGlobalConfig(config);
let sjReq = new SJHttp();
return sjReq.request<T>(config)
}
private isEmpty(obj:any):boolean{
if (obj == undefined || obj == null) {
return true;
}
return false;
}
private handleGlobalConfig(config:SJRequestConfig):SJRequestConfig{
let globalConfig:SJGlobalConfig = this.globalConfig;
// 1.處理url
let url = config.url;
let urlBase = globalConfig.baseUrl;
let urlLower = url.toLowerCase();
let isHttpUrl = urlLower.startsWith('http') || urlLower.startsWith('https');
if (!isHttpUrl) {
config.url = `${urlBase}${url}`
}
// 2蚕冬、處理其他參數(shù)
if (this.isEmpty(config.usingCache)) {
config.usingCache = globalConfig.usingCache;
}
if (this.isEmpty(config.header)) {
config.header = globalConfig.header;
}
if (this.isEmpty(config.readTimeout)) {
config.readTimeout = globalConfig.readTimeout;
}
if (this.isEmpty(config.connectTimeout)) {
config.connectTimeout = globalConfig.connectTimeout;
}
if (this.isEmpty(config.usingProtocol)) {
config.usingProtocol = globalConfig.usingProtocol;
}
return config;
}
}
export class SJRequestManager {
// 全局配置
static configGlobal(callback:(config:SJGlobalConfig)=>void){
callback(SJRequest.instance.globalConfig);
}
static get<T>(config:SJRequestConfig): Promise<T> {
return SJRequest.instance.get(config);
}
static post<T>(config:SJRequestConfig): Promise<T> {
return SJRequest.instance.post(config);
}
static delete<T>(config:SJRequestConfig): Promise<T> {
return SJRequest.instance.delete(config);
}
static put<T>(config:SJRequestConfig): Promise<T> {
return SJRequest.instance.put(config);
}
static request<T>(config:SJRequestConfig): Promise<T> {
return SJRequest.instance.request(config);
}
}
4免猾、List上拉加載,下拉刷新如何實現(xiàn)囤热?
參考第三方組件: PullToRefresh
5猎提、頁面導航欄如何實現(xiàn)?
通過系統(tǒng)的Navigation組件即可實現(xiàn)
@Builder content(){
Navigation(){
Column() {
this.appGridView()
// LayoutGridDratEditPage()
}
}
.mode(NavigationMode.Auto)
.titleMode(NavigationTitleMode.Mini)
.title('編輯應用')
.menus(this.rightBtns())
}
6旁蔼、grid拖動編輯如何實現(xiàn)锨苏?有什么要注意的?
1)設置編輯模式屬性為true
//設置Grid是否進入編輯模式棺聊,進入編輯模式可以拖拽Grid組件內(nèi)部GridItem
.editMode(this.isEditable)
2)剛開始拖拽的時候伞租,設置拖拽懸浮展示的UI
//第一次拖拽此事件綁定的組件時,觸發(fā)回調(diào)
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
//設置拖拽過程中顯示的圖片
return this.pixelMapBuilder()
})
//拖拽過程中展示的樣式
@Builder pixelMapBuilder() {
Column() {
Text(this.text).gridItemStyle()
}
}
3)拖拽到目標gridItem時限佩,根據(jù)條件交換數(shù)組中的元素
//綁定此事件的組件可作為拖拽釋放目標葵诈,當在本組件范圍內(nèi)停止拖拽行為時,觸發(fā)回調(diào)
//itemIndex為拖拽起始位置祟同,insertIndex為拖拽插入位置
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
let isValidInserIndex = insertIndex < this.numbers.length;
if (isSuccess && insertIndex < this.numbers.length) {
// if (isSuccess && isValidInserIndex) {
//不支持拖拽到已有內(nèi)容以外的位置
this.changeIndex(itemIndex, insertIndex)
} else {
Logger.warn('griditem交換失敗');
}
})
//交換數(shù)組中元素位置
changeIndex(index1: number, index2: number) {
Logger.warn('交換前:' + this.numbers.toString());
[this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
Logger.warn('交換后:' + this.numbers.toString());
}
4)完整代碼如下:
@Extend(Grid) function gridStyle() {
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(10)
.width('100%')
.backgroundColor(0xFAEEE0)
.height('100%')
}
@Extend(Text) function gridItemStyle() {
.fontSize(16)
.backgroundColor(0xF9CF93)
.width(80)
.height(80)
.textAlign(TextAlign.Center)
}
@Entry
@Component
struct LayoutGridDratEditPage {
@State numbers: String[] = ['1','2','3','4','5','6','7','8','9','10']
scroller: Scroller = new Scroller()
@State text: string = 'drag'
// 是否是編輯模式
@State isEditable: boolean = true
//拖拽過程中展示的樣式
@Builder pixelMapBuilder() {
Column() {
Text(this.text).gridItemStyle()
}
}
aboutToAppear() {
// for (let i = 1;i <= 15; i++) {
// this.numbers.push(i.toString())
// }
Logger.warn('初始化后:' + this.numbers.toString());
}
//交換數(shù)組中元素位置
changeIndex(index1: number, index2: number) {
Logger.warn('交換前:' + this.numbers.toString());
[this.numbers[index1], this.numbers[index2]] = [this.numbers[index2], this.numbers[index1]];
Logger.warn('交換后:' + this.numbers.toString());
}
build() {
Column() {
this.dragDemo()
// this.animalTest()
}
}
@Builder dragDemo() {
Column({ space: 5 }) {
Text(this.isEditable?'編輯模式':'非編輯模式').fontSize(28).fontWeight(FontWeight.Bold)
.onClick(()=>{
this.isEditable = !this.isEditable;
})
Grid(this.scroller) {
ForEach(this.numbers, (day: string,index:number) => {
GridItem() {
Stack(){
Text(day).gridItemStyle()
// .rotate({ angle: this.rotateAngle })
.onTouch((event: TouchEvent) => {
if (event.type === TouchType.Down) {
this.text = day
}
})
if (this.isEditable) {
Image($r('app.media.btn_Closed')).width(15).height(15).onClick(()=>{
// Array
Logger.warn('移除前:' + this.numbers.toString());
this.numbers.splice(index,1)
Logger.warn('移除后:' + this.numbers.toString());
})
}
}.alignContent(Alignment.TopEnd)
}
})
}
.gridStyle()
.onScrollIndex((first: number) => {
console.info(first.toString())
})
//設置Grid是否進入編輯模式作喘,進入編輯模式可以拖拽Grid組件內(nèi)部GridItem
.editMode(this.isEditable)
//第一次拖拽此事件綁定的組件時,觸發(fā)回調(diào)
.onItemDragStart((event: ItemDragInfo, itemIndex: number) => {
//設置拖拽過程中顯示的圖片
return this.pixelMapBuilder()
})
//綁定此事件的組件可作為拖拽釋放目標晕城,當在本組件范圍內(nèi)停止拖拽行為時泞坦,觸發(fā)回調(diào)
//itemIndex為拖拽起始位置,insertIndex為拖拽插入位置
.onItemDrop((event: ItemDragInfo, itemIndex: number, insertIndex: number, isSuccess: boolean) => {
let isValidInserIndex = insertIndex < this.numbers.length;
if (isSuccess && insertIndex < this.numbers.length) {
// if (isSuccess && isValidInserIndex) {
//不支持拖拽到已有內(nèi)容以外的位置
this.changeIndex(itemIndex, insertIndex)
} else {
Logger.warn('griditem交換失敗');
}
})
}
.width('100%')
}
}
5)注意事項
- griditem最好設置寬高砖顷,否則可能拖拽不成功贰锁,具體現(xiàn)象就是onItemDrop方法中的insertIndex為-1,isSuccess為false
7滤蝠、子組件占滿主軸方向剩余區(qū)域如何設置豌熄?
- 方法1:在row和column的子組件中設置layoutWeight
Column() {
Row({space:10}){
Image($r('app.media.logo')).width(90).height(90)
Column(){
Text(title).fontSize('14').width('100%')
Blank()
Row(){
Text('我是按鈕前文')
Blank()
Button('立即查看',{type:ButtonType.Capsule}).width(80).height(35).onClick(()=>{
Logger.debug('當前索引值:'+ index.toString() + ',當前item:' + title)
})
}
.width('100%')
.backgroundColor(Color.Orange)
}
.layoutWeight(1) // 占滿主軸方向剩余空間
.height(80)
.justifyContent(FlexAlign.SpaceBetween)
.backgroundColor(Color.Pink)
}
.width('100%')
.padding(10)
Divider().color('#F2F2F2').strokeWidth(1).margin({left:10,right:10})
}.width('100%')
- 方法2:使用flext布局,設置flexGrow
Flex({direction:FlexDirection.Row,alignItems:ItemAlign.Center}){
Flex(){
Stack(){
Image($r('app.media.logo')).width(90).height(90)
}.width(90).height(90)
// .backgroundColor(Color.Blue)
}
// .flexBasis(120)
.width(120)
// .backgroundColor(Color.Green)
Flex({direction:FlexDirection.Column,justifyContent:FlexAlign.SpaceBetween,alignItems:ItemAlign.Start}){
Text(title).fontSize('14')
Flex({direction:FlexDirection.Row,justifyContent:FlexAlign.SpaceBetween,alignItems:ItemAlign.Center}){
Text('按鈕前文案')
Button('立即查看',{type:ButtonType.Capsule}).width(80).height(35).onClick(()=>{
Logger.debug('當前索引值:'+ index.toString() + '几睛,當前item:' + title)
})
}
.backgroundColor(Color.Orange)
}
.flexGrow(2) // column占滿剩余空間
// .flexShrink(1)
.backgroundColor(Color.Pink)
.height(80)
.margin({left:10})
}
.width('100%')
.padding(10)
// .backgroundColor(Color.Green)
// 2房轿、最下面的橫線
Divider().color('#F2F2F2').strokeWidth(1).margin({left:10,right:10})
}.width('100%')
二粤攒、常用快捷鍵
以下快捷鍵是能提效的所森,部分快捷鍵是我自定義的囱持,大家自行參考。
1焕济、com+shift+j【自定義成Xcode一致】
快速打開當前文件左側(cè)文件樹位置
2纷妆、com+7
打開structure
3、OPT+F7
提供Find Usages代碼引用查找功能晴弃,幫助開發(fā)者快速查看某個對象(變量掩幢、函數(shù)或者類等)被引用的地方,用于后續(xù)的代碼重構(gòu)上鞠,可以極大的提升開發(fā)者的開發(fā)效率际邻。
4、com+[ 和 com+]
代碼后退和代碼前進
5芍阎、兩次shift
全局搜索
6世曾、com+/
代碼注釋
7、com+左右箭頭
某行代碼谴咸,光標移到行首和行尾
8轮听、com+shift+上下方向鍵
某行代碼上移和下移
9、com+點擊(某個屬性或者方法)
快速跳轉(zhuǎn)到定義的位置岭佳,一般可以配合com+[
或 com+]
使用
10血巍、com+刪除按鈕
快速刪除某行
11、com+D
復制某行或者某段代碼
12珊随、com+上下箭頭【自定義的述寡,默認沒有】
快速滾動到文件頂部和底部<BR>
需要自定義快捷鍵,在自定義快捷鍵界面中的Scroll To Top
和Scroll To Bottom
自定義快捷鍵