自學HarmonyOS應用開發(67)- 自定義佈局(2)

自學HarmonyOS應用開發(67)- 自定義佈局(2)

佈局檔案示例

接下來使用一個實際的佈局為例,介紹動態調整元件高度的實現方法。佈局內容如下:

<?xml version=“1。0” encoding=“utf-8”?>

DynamicLayout正常動作需要幾個必要條件:

需要進行調整的元件透過高度固定的LayoutSeparator元件分隔。這裡高度固定是一個必要條件。

LayoutSeparator上下元件高度的指定方式必須相同:要麼都是直接用height屬性指定高度,要麼都是用weight指定佔比。

除了這兩個限制之外,調整物件元件的型別/個數,分隔的元件的高度都可以任意指定。夠靈活了吧。

LayoutSeparator元件

public class LayoutSeparator extends Component { public LayoutSeparator(Context context, AttrSet attrSet) { super(context, attrSet); }}

這個元件沒有特別實現任何功能,只是獲得了一個用於表明拖動物件的型別。它會在後面的說明中用到。

處理拖動動作

下面是DynamicLayout中實現的DraggedListener:

onDragDown方法用於處理拖動按下操作,內容是找到按下動作物件的LayoutSeparator並改變其顏色,如果按下物件不是LayoutSeparator,就當什麼也沒發生。

onDragStart方法呼叫DynamicLayout的onSeparatorDragStart方法並記錄拖動的開始位置。接下來的onDragUpdate會在呼叫DynamicLayout的onSeparatorDragUpdate時使用這個開始位置資訊。

Component。DraggedListener dragListener = new Component。DraggedListener(){ Point dragStart = null; LayoutSeparator draggedSeparator = null; @Override public void onDragDown(Component component, DragInfo dragInfo) { //HiLog。info(LABEL, “DynamicLayout。onDragDown!”); draggedSeparator = null; dragStart = null; for (int idx = 1; idx < getChildCount()-1; idx++) { Component childView = DynamicLayout。this。getComponentAt(idx); if (childView instanceof LayoutSeparator) { LayoutSeparator separator = (LayoutSeparator) childView; Rect visibleRect = new Rect(separator。getLeft(), separator。getTop(), separator。getRight(), separator。getBottom()); if(visibleRect。isInclude(dragInfo。downPoint)){ draggedSeparator = separator; ShapeElement bg = new ShapeElement(); bg。setRgbColor(RgbPalette。GREEN); bg。setShape(ShapeElement。RECTANGLE); draggedSeparator。setBackground(bg); } } } } @Override public void onDragStart(Component component, DragInfo dragInfo) { if(draggedSeparator != null){ DynamicLayout。this。onSeparatorDragStart(draggedSeparator); dragStart = dragInfo。startPoint; } } @Override public void onDragUpdate(Component component, DragInfo dragInfo) { if(draggedSeparator != null) { Size offset = new Size((int) (dragInfo。updatePoint。getPointX() - dragStart。getPointX()), (int) (dragInfo。updatePoint。getPointY() - dragStart。getPointY())); DynamicLayout。this。onSeparatorDragUpdate(draggedSeparator, offset); } } @Override public void onDragEnd(Component component, DragInfo dragInfo) { //HiLog。info(LABEL, “DynamicLayout。onDragEnd!”); if(draggedSeparator != null){ ShapeElement bg = new ShapeElement(); bg。setRgbColor(RgbPalette。LIGHT_GRAY); bg。setShape(ShapeElement。RECTANGLE); draggedSeparator。setBackground(bg); } draggedSeparator = null; } @Override public void onDragCancel(Component component, DragInfo dragInfo) { //HiLog。info(LABEL, “DynamicLayout。onDragCancel!”); draggedSeparator = null; invalidate(); } @Override public boolean onDragPreAccept(Component component, int dragDirection) { return true; }};

onDragEnd負責最後恢復LayoutSeparator的顏色。

處理開始拖動動作

以下是開始拖動時的處理:

public void onSeparatorDragStart(LayoutSeparator separator){ up_height = -1; down_height = -1; up_weight = -1; down_weight = -1; for (int idx = 1; idx < getChildCount()-1; idx++) { Component childView = getComponentAt(idx); if(childView == separator) { Component comp_up = getComponentAt(idx - 1); DynamicLayout。LayoutConfig lc_up = (DynamicLayout。LayoutConfig)comp_up。getLayoutConfig(); Component comp_down = getComponentAt(idx + 1); DynamicLayout。LayoutConfig lc_down = (DynamicLayout。LayoutConfig)comp_down。getLayoutConfig(); if(lc_up。height >= 0 && lc_down。height >= 0) { up_height = comp_up。getHeight(); down_height = comp_down。getHeight(); } up_weight = lc_up。weight; down_weight = lc_down。weight; } }}

內容很簡單:就是找到LayoutSeparator上下都元件並記錄它們的高度資訊。

處理拖動過程

拖動過程中就是根據DraggedListener傳來的拖動距離資訊調整LayoutSeparator相鄰元件的高度。無論是使用height屬性還是weight屬性表示高度,都會保證調整前後的合計值不變。這樣可以保證位置調整不會影響其他元件。

public void onSeparatorDragUpdate(LayoutSeparator separator, Size offset){ //HiLog。info(LABEL, “DynamicLayout。onSeparatorDragUpdate!offset。height=%{public}d”, offset。height); if((up_height > 0 && (up_height + offset。height) >= 0) && (down_height >0 && (down_height - offset。height) >= 0)) { for (int idx = 1; idx < getChildCount() - 1; idx++) { Component childView = getComponentAt(idx); if (childView == separator) { adjustHeight(getComponentAt(idx - 1), getComponentAt(idx + 1), offset。height); break; } } }}void adjustHeight(Component up, Component down, int offset){ DynamicLayout。LayoutConfig lc_up = (DynamicLayout。LayoutConfig)up。getLayoutConfig(); DynamicLayout。LayoutConfig lc_down = (DynamicLayout。LayoutConfig)down。getLayoutConfig(); if(lc_up。height > 0 && lc_down。height > 0){ lc_up。height = up_height + offset; lc_down。height = down_height - offset; } else if(lc_up。height == 0 && lc_down。height==0 && weight_rate > 0){ offset = (int)(offset / weight_rate); lc_up。weight = up_weight + offset; lc_down。weight = down_weight - offset; } else{ //do nothing。 } arrange();}

arrange的功能是主動發起佈局調整,這個方法使用了之前的佈局計算過程中預留的資料:

public void arrange(){ onEstimateSize(lastConfigWidth, lastConfigHeight); onArrange(layoutLeft, layoutTop, layoutWidth, layoutHeight);}

作者沒有找到更簡單的發起佈局計算的方法,先用這種方法將就一下吧。

參考資料

自定義佈局

https://developer。harmonyos。com/cn/docs/documentation/doc-guides/ui-java-custom-layouts-0000001092683918

參考程式碼

完整程式碼可以從以下連結下載:

https://github。com/xueweiguo/Harmony/tree/master/FileBrowser

作者著作介紹

《實戰Python設計模式》是作者去年3月份出版的技術書籍,該書利用Python 的標準GUI 工具包tkinter,透過可執行的示例對23 個設計模式逐個進行說明。這樣一方面可以使讀者瞭解真實的軟體開發工作中每個設計模式的運用場景和想要解決的問題;另一方面透過對這些問題的解決過程進行說明,讓讀者明白在編寫程式碼時如何判斷使用設計模式的利弊,併合理運用設計模式。

自學HarmonyOS應用開發(67)- 自定義佈局(2)

對設計模式感興趣而且希望隨學隨用的讀者透過本書可以快速跨越從理解到運用的門檻;希望學習Python GUI 程式設計的讀者可以將本書中的示例作為設計和開發的參考;使用Python 語言進行影象分析、資料處理工作的讀者可以直接以本書中的示例為基礎,迅速構建自己的系統架構。

覺得本文有幫助?請分享給更多人。

關注微信公眾號【面向物件思考】輕鬆學習每一天!

面向物件開發,面向物件思考!