From ac4c6d730a079ebfb9510c6bcef2f008710f5785 Mon Sep 17 00:00:00 2001 From: gaojianming108 Date: Wed, 20 Oct 2021 14:23:52 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=BB=84=E4=BB=B6=E5=B7=A5?= =?UTF-8?q?=E7=A8=8B=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ohos/speeddial/sample/CustomAdapter.java | 40 + .../ohos/speeddial/sample/MainAbility.java | 13 + .../ohos/speeddial/sample/MenuAdapter.java | 48 + .../ohos/speeddial/sample/MenuInfo.java | 37 + .../speeddial/sample/MenuInfoConstants.java | 49 + .../ohos/speeddial/sample/MenuUtils.java | 85 ++ .../ohos/speeddial/sample/MyApplication.java | 10 + .../sample/slice/MainAbilitySlice.java | 273 ++++++ .../speeddial/sample/ExampleOhosTest.java | 14 + .../ohos/speeddial/sample/ExampleTest.java | 9 + gradlew | 183 ++++ gradlew.bat | 103 +++ .../speeddial/AnimStateChangedListener.java | 51 ++ .../leinardi/ohos/speeddial/AttrUtils.java | 156 ++++ .../com/leinardi/ohos/speeddial/CardView.java | 98 ++ .../ohos/speeddial/FabWithLabelView.java | 373 ++++++++ .../ohos/speeddial/FloatingActionButton.java | 386 ++++++++ .../com/leinardi/ohos/speeddial/LogUtil.java | 75 ++ .../com/leinardi/ohos/speeddial/SnackBar.java | 186 ++++ .../ohos/speeddial/SpeedDialActionItem.java | 360 ++++++++ .../speeddial/SpeedDialOverlayLayout.java | 117 +++ .../ohos/speeddial/SpeedDialView.java | 841 ++++++++++++++++++ .../com/leinardi/ohos/speeddial/UiUtils.java | 263 ++++++ .../ohos/speeddial/ViewGroupUtils.java | 89 ++ .../leinardi/ohos/speeddial/ExampleTest.java | 9 + package.json | 1 + 26 files changed, 3869 insertions(+) create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/CustomAdapter.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MainAbility.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuAdapter.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfo.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfoConstants.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuUtils.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/MyApplication.java create mode 100644 entry/src/main/java/com/leinardi/ohos/speeddial/sample/slice/MainAbilitySlice.java create mode 100644 entry/src/ohosTest/java/com/leinardi/ohos/speeddial/sample/ExampleOhosTest.java create mode 100644 entry/src/test/java/com/leinardi/ohos/speeddial/sample/ExampleTest.java create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/AnimStateChangedListener.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/AttrUtils.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/CardView.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/FabWithLabelView.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/FloatingActionButton.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/LogUtil.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/SnackBar.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialActionItem.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialOverlayLayout.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialView.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/UiUtils.java create mode 100644 library/src/main/java/com/leinardi/ohos/speeddial/ViewGroupUtils.java create mode 100644 library/src/test/java/com/leinardi/ohos/speeddial/ExampleTest.java create mode 100644 package.json diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/CustomAdapter.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/CustomAdapter.java new file mode 100644 index 0000000..37cfa6a --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/CustomAdapter.java @@ -0,0 +1,40 @@ +package com.leinardi.ohos.speeddial.sample; + +import ohos.agp.components.*; +import ohos.app.Context; + +public class CustomAdapter extends BaseItemProvider { + private final Context context; + + public CustomAdapter(Context context){ + this.context = context; + } + + @Override + public int getCount() { + return 60; + } + + @Override + public Object getItem(int i) { + return null; + } + + @Override + public long getItemId(int i) { + return 0; + } + + @Override + public Component getComponent(int i, Component convertComponent, ComponentContainer componentContainer) { + final Component cpt; + if (convertComponent == null) { + cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_list_item, null, false); + } else { + cpt = convertComponent; + } + Text text = (Text) cpt.findComponentById(ResourceTable.Id_text); + text.setText("This is element #" + i); + return cpt; + } +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MainAbility.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MainAbility.java new file mode 100644 index 0000000..b5f8b23 --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MainAbility.java @@ -0,0 +1,13 @@ +package com.leinardi.ohos.speeddial.sample; + +import com.leinardi.ohos.speeddial.sample.slice.MainAbilitySlice; +import ohos.aafwk.ability.Ability; +import ohos.aafwk.content.Intent; + +public class MainAbility extends Ability { + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setMainRoute(MainAbilitySlice.class.getName()); + } +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuAdapter.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuAdapter.java new file mode 100644 index 0000000..1c2599d --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuAdapter.java @@ -0,0 +1,48 @@ +package com.leinardi.ohos.speeddial.sample; + +import ohos.agp.components.*; +import ohos.app.Context; + +import java.util.List; + +public class MenuAdapter extends BaseItemProvider { + private final Context context; + private final List menuInfoList; + + public MenuAdapter(Context context, List menuInfoList) { + this.context = context; + this.menuInfoList = menuInfoList; + } + + @Override + public int getCount() { + return menuInfoList.size(); + } + + @Override + public Object getItem(int i) { + return null; + } + + @Override + public long getItemId(int i) { + return 0; + } + + @Override + public Component getComponent(int i, Component convertComponent, ComponentContainer componentContainer) { + MenuInfo menuInfo = menuInfoList.get(i); + final Component cpt; + if (convertComponent == null) { + cpt = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_popmenu_item_layout, null, false); + } else { + cpt = convertComponent; + } + cpt.setId(menuInfo.getId()); + Text text = (Text) cpt.findComponentById(ResourceTable.Id_text); + text.setText(menuInfo.getTitle()); + Image image = (Image) cpt.findComponentById(ResourceTable.Id_arrowImage); + image.setVisibility(menuInfo.isMore() ? Component.VISIBLE : Component.HIDE); + return cpt; + } +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfo.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfo.java new file mode 100644 index 0000000..bc531b2 --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfo.java @@ -0,0 +1,37 @@ +package com.leinardi.ohos.speeddial.sample; + +public class MenuInfo { + private int id; + private String title; + private boolean isMore; + + public MenuInfo(int id, String title, boolean isMore) { + this.id = id; + this.title = title; + this.isMore = isMore; + } + + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public boolean isMore() { + return isMore; + } + + public void setMore(boolean more) { + isMore = more; + } +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfoConstants.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfoConstants.java new file mode 100644 index 0000000..a8d9e56 --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuInfoConstants.java @@ -0,0 +1,49 @@ +package com.leinardi.ohos.speeddial.sample; + +import java.util.ArrayList; +import java.util.List; + +public class MenuInfoConstants { + public static final List expandModeList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_expansion_mode_top, "Top", false)); + add(new MenuInfo(ResourceTable.Integer_action_expansion_mode_left, "Left", false)); + add(new MenuInfo(ResourceTable.Integer_action_expansion_mode_bottom, "Bottom", false)); + add(new MenuInfo(ResourceTable.Integer_action_expansion_mode_right, "Right", false)); + }}; + + public static final List rotateDegreeList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_rotation_angle_0, "0 degrees", false)); + add(new MenuInfo(ResourceTable.Integer_action_rotation_angle_45, "45 degrees", false)); + add(new MenuInfo(ResourceTable.Integer_action_rotation_angle_90, "90 degrees", false)); + add(new MenuInfo(ResourceTable.Integer_action_rotation_angle_180, "180 degrees", false)); + }}; + + public static final List moreList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_main_fab_color, "Main FAB color", true)); + add(new MenuInfo(ResourceTable.Integer_action_toggle_list, "Toggle list", false)); + add(new MenuInfo(ResourceTable.Integer_action_toggle_reverse_animation, "Toggle reverse animation on close", false)); + add(new MenuInfo(ResourceTable.Integer_action_add_item, "Add action item", false)); + add(new MenuInfo(ResourceTable.Integer_action_remove_item, "Remove action item", false)); + }}; + + public static final List mainFabColorList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_open, "Main FAB color open", true)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_close, "Main FAB color close", true)); + }}; + + public static final List mainFabCloseColorList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_closed_primary, "Primary", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_closed_orange, "Orange", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_closed_purple, "Purple", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_closed_white, "White", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_closed_none, "None", false)); + }}; + + public static final List mainFabOpenColorList = new ArrayList() {{ + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_opened_primary, "Primary", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_opened_orange, "Orange", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_opened_purple, "Purple", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_opened_white, "White", false)); + add(new MenuInfo(ResourceTable.Integer_action_main_fab_background_color_opened_none, "None", false)); + }}; +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuUtils.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuUtils.java new file mode 100644 index 0000000..8d3105d --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MenuUtils.java @@ -0,0 +1,85 @@ +package com.leinardi.ohos.speeddial.sample; + +import com.leinardi.ohos.speeddial.SpeedDialActionItem; +import com.leinardi.ohos.speeddial.SpeedDialView; +import com.leinardi.ohos.speeddial.UiUtils; + +public class MenuUtils { + + public static void handlerMenuSetting(SpeedDialView speedDialView, long id) { + switch ((int) id) { + case ResourceTable.Integer_action_expansion_mode_top: + speedDialView.setExpansionMode(SpeedDialView.ExpansionMode.TOP); + break; + case ResourceTable.Integer_action_expansion_mode_left: + speedDialView.setExpansionMode(SpeedDialView.ExpansionMode.LEFT); + break; + case ResourceTable.Integer_action_expansion_mode_bottom: + speedDialView.setExpansionMode(SpeedDialView.ExpansionMode.BOTTOM); + break; + case ResourceTable.Integer_action_expansion_mode_right: + speedDialView.setExpansionMode(SpeedDialView.ExpansionMode.RIGHT); + break; + case ResourceTable.Integer_action_rotation_angle_0: + speedDialView.setMainFabAnimationRotateAngle(0f); + break; + case ResourceTable.Integer_action_rotation_angle_45: + speedDialView.setMainFabAnimationRotateAngle(45f); + break; + case ResourceTable.Integer_action_rotation_angle_90: + speedDialView.setMainFabAnimationRotateAngle(90f); + break; + case ResourceTable.Integer_action_rotation_angle_180: + speedDialView.setMainFabAnimationRotateAngle(180f); + break; + case ResourceTable.Integer_action_toggle_reverse_animation: + speedDialView.setUseReverseAnimationOnClose(!speedDialView.getUseReverseAnimationOnClose()); + break; + case ResourceTable.Integer_action_add_item: + speedDialView.addActionItem(new SpeedDialActionItem.Builder( + (int)System.currentTimeMillis(), + ResourceTable.Graphic_ic_pencil_alt_white_24dp).create() + ); + break; + case ResourceTable.Integer_action_remove_item: + int size = speedDialView.getActionItems().size(); + if (size > 0) { + speedDialView.removeActionItem(size - 1); + } + break; + case ResourceTable.Integer_action_main_fab_background_color_closed_primary: + speedDialView.setMainFabClosedBackgroundColor(UiUtils.getPrimaryColor(speedDialView.getContext())); + break; + case ResourceTable.Integer_action_main_fab_background_color_closed_orange: + speedDialView.setMainFabClosedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_orange_500)); + break; + case ResourceTable.Integer_action_main_fab_background_color_closed_purple: + speedDialView.setMainFabClosedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_purple_500)); + break; + case ResourceTable.Integer_action_main_fab_background_color_closed_white: + speedDialView.setMainFabClosedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_white_1000)); + break; + case ResourceTable.Integer_action_main_fab_background_color_closed_none: + speedDialView.setMainFabClosedBackgroundColor(0); + break; + case ResourceTable.Integer_action_main_fab_background_color_opened_primary: + speedDialView.setMainFabOpenedBackgroundColor(UiUtils.getPrimaryColor(speedDialView.getContext())); + break; + case ResourceTable.Integer_action_main_fab_background_color_opened_orange: + speedDialView.setMainFabOpenedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_orange_500)); + break; + case ResourceTable.Integer_action_main_fab_background_color_opened_purple: + speedDialView.setMainFabOpenedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_purple_500)); + break; + case ResourceTable.Integer_action_main_fab_background_color_opened_white: + speedDialView.setMainFabOpenedBackgroundColor(UiUtils.getColor(speedDialView.getContext(), ResourceTable.Color_material_white_1000)); + break; + case ResourceTable.Integer_action_main_fab_background_color_opened_none: + speedDialView.setMainFabOpenedBackgroundColor(0); + break; + default: + break; + } + } + +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MyApplication.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MyApplication.java new file mode 100644 index 0000000..0bea7a8 --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/MyApplication.java @@ -0,0 +1,10 @@ +package com.leinardi.ohos.speeddial.sample; + +import ohos.aafwk.ability.AbilityPackage; + +public class MyApplication extends AbilityPackage { + @Override + public void onInitialize() { + super.onInitialize(); + } +} diff --git a/entry/src/main/java/com/leinardi/ohos/speeddial/sample/slice/MainAbilitySlice.java b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/slice/MainAbilitySlice.java new file mode 100644 index 0000000..75021c8 --- /dev/null +++ b/entry/src/main/java/com/leinardi/ohos/speeddial/sample/slice/MainAbilitySlice.java @@ -0,0 +1,273 @@ +package com.leinardi.ohos.speeddial.sample.slice; + +import com.leinardi.ohos.speeddial.sample.ResourceTable; +import com.leinardi.ohos.speeddial.*; +import com.leinardi.ohos.speeddial.sample.*; +import ohos.aafwk.ability.AbilitySlice; +import ohos.aafwk.content.Intent; +import ohos.agp.components.*; +import ohos.agp.components.element.PixelMapElement; +import ohos.agp.utils.Color; +import ohos.agp.utils.LayoutAlignment; +import ohos.agp.utils.TextTool; +import ohos.agp.window.dialog.PopupDialog; +import ohos.agp.window.dialog.ToastDialog; +import ohos.global.resource.NotExistException; + +import java.io.IOException; +import java.util.List; + +public class MainAbilitySlice extends AbilitySlice { + private static final int ADD_ACTION_POSITION = 4; + + private SpeedDialView mSpeedDialView; + + private ToastDialog mToast; + + private ListContainer mListContainer; + + private CustomAdapter mProvider; + + private SnackBar mSnackBar; + + private boolean isIdle = true; + + @Override + public void onStart(Intent intent) { + super.onStart(intent); + super.setUIContent(ResourceTable.Layout_ability_main); + try { + UiUtils.setStatusBarColor(UiUtils.getColor(this, ResourceTable.Color_inbox_primary_dark)); + initSpeedDialView(); + initListener(); + initListContainer(); + } catch (NotExistException | IOException e) { + e.printStackTrace(); + } + } + + private void initSpeedDialView() throws NotExistException, IOException { + mSpeedDialView = (SpeedDialView) findComponentById(ResourceTable.Id_SpeedDialView); + + mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_no_label, ResourceTable.Graphic_ic_link_white_24dp) + .create()); + + PixelMapElement element = new PixelMapElement(getResourceManager().getResource(ResourceTable.Media_ic_custom_color)); + FabWithLabelView fabWithLabelView = mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_custom_color, element) + .setFabImageTintColor(ResourceTable.Color_inbox_primary) + .setLabel(ResourceTable.String_label_custom_color) + .setLabelColor(Color.WHITE.getValue()) + .setLabelBackgroundColor(UiUtils.getColor(getContext(), ResourceTable.Color_inbox_primary)) + .create()); + + SpeedDialActionItem mActionItem = fabWithLabelView.getSpeedDialActionItemBuilder().setFabBackgroundColor(UiUtils.getColor(this, ResourceTable.Color_material_white_1000)).create(); + fabWithLabelView.setSpeedDialActionItem(mActionItem); + + PixelMapElement element2 = new PixelMapElement(getResourceManager().getResource(ResourceTable.Media_ic_lorem_ipsum)); + mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_long_label, element2) + .setFabSize(FloatingActionButton.SIZE_NORMAL) + .setLabel("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor " + + "incididunt ut labore et dolore magna aliqua.") + .create()); + + mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_add_action, ResourceTable.Graphic_ic_add_white_24dp) + .setFabBackgroundColor(UiUtils.getColor(getContext(), ResourceTable.Color_material_green_500)) + .setLabel(ResourceTable.String_label_add_action) + .setLabelBackgroundColor(Color.TRANSPARENT.getValue()) + .create()); + + mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_custom_theme, ResourceTable.Graphic_ic_theme_white_24dp) + .setFabBackgroundColor(UiUtils.getColor(getContext(), ResourceTable.Color_material_purple_500)) + .setLabel(getString(ResourceTable.String_label_custom_theme)) + .create()); + + mSpeedDialView.setOnChangeListener(new SpeedDialView.OnChangeListener() { + @Override + public boolean onMainActionSelected() { + showToast("Main action clicked!"); + return false; + } + + @Override + public void onToggleChanged(boolean isOpen) { + + } + }); + + mSpeedDialView.setOnActionSelectedListener(actionItem -> { + switch (actionItem.getId()) { + case ResourceTable.Integer_fab_no_label: + showToast("No label action clicked!\nClosing with animation"); + mSpeedDialView.close(); + return true; + case ResourceTable.Integer_fab_long_label: + showSnackBar(actionItem.getLabel(this) + " clicked!"); + break; + case ResourceTable.Integer_fab_custom_color: + showToast(actionItem.getLabel(this) + " clicked!\nClosing without animation."); + return false; + case ResourceTable.Integer_fab_custom_theme: + showToast(actionItem.getLabel(this) + " clicked!"); + break; + case ResourceTable.Integer_fab_add_action: + mSpeedDialView.addActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_replace_action, + ResourceTable.Graphic_ic_replace_white_24dp) + .setFabBackgroundColor(UiUtils.getColor(this, ResourceTable.Color_material_orange_500)) + .setLabel(getString(ResourceTable.String_label_replace_action)) + .create(), ADD_ACTION_POSITION); + break; + case ResourceTable.Integer_fab_replace_action: + mSpeedDialView.replaceActionItem(new SpeedDialActionItem.Builder(ResourceTable.Integer_fab_remove_action, ResourceTable.Graphic_ic_delete_white_24dp) + .setLabel(getString(ResourceTable.String_label_remove_action)) + .setFabBackgroundColor(UiUtils.getColor(this, ResourceTable.Color_inbox_accent)) + .create(), ADD_ACTION_POSITION); + break; + case ResourceTable.Integer_fab_remove_action: + mSpeedDialView.removeActionItemById(ResourceTable.Integer_fab_remove_action); + break; + default: + break; + } + return true; + }); + } + + private void initListener() { + findComponentById(ResourceTable.Id_showLayout).setClickedListener(component -> { + if(isIdle) { + if (mSpeedDialView.getVisibility() == Component.VISIBLE) { + mSpeedDialView.hide(); + } else { + mSpeedDialView.show(); + } + } + }); + + findComponentById(ResourceTable.Id_snackLayout).setClickedListener(component -> { + showSnackBar("Test snackbar"); + }); + + findComponentById(ResourceTable.Id_expansionLayout).setClickedListener(component -> { + showPopMenu(component, MenuInfoConstants.expandModeList, ""); + }); + + findComponentById(ResourceTable.Id_rotationLayout).setClickedListener(component -> { + showPopMenu(component, MenuInfoConstants.rotateDegreeList, ""); + }); + + findComponentById(ResourceTable.Id_moreLayout).setClickedListener(component -> { + showPopMenu(component, MenuInfoConstants.moreList, ""); + }); + } + + private void initListContainer() { + mListContainer = (ListContainer) findComponentById(ResourceTable.Id_listContainer); + mProvider = new CustomAdapter(this); + mListContainer.setItemProvider(mProvider); + + mListContainer.addScrolledListener(new Component.ScrolledListener() { + @Override + public void onContentScrolled(Component component, int scrollX, int scrollY, int oldScrollX, int oldScrollY) { + if(scrollY > oldScrollY) { + //上滑,消失 + mSpeedDialView.hide(); + }else { + //下滑,显示 + mSpeedDialView.show(); + } + } + + @Override + public void scrolledStageUpdate(Component component, int newStage) { + isIdle = newStage == Component.SCROLL_IDLE_STAGE; + } + }); + } + + private void showToast(String msg) { +// if(mToast != null) { +// mToast.cancel(); +// } + DirectionalLayout layout = (DirectionalLayout) LayoutScatter.getInstance(getContext()) + .parse(ResourceTable.Layout_layout_toast, null, false); + Text text = (Text) layout.findComponentById(ResourceTable.Id_msg_toast); + text.setText(msg); + + if(mToast == null) { + mToast = new ToastDialog(getContext()); + mToast.setSize(DirectionalLayout.LayoutConfig.MATCH_CONTENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT); + mToast.setAlignment(LayoutAlignment.BOTTOM); + mToast.setOffset(0, AttrHelper.fp2px(50, this)); + mToast.setAutoClosable(true); + } + mToast.setContentCustomComponent(layout); + mToast.show(); + } + + private void showSnackBar(String text) { + if(mSnackBar != null) { + mSnackBar.close(false); + } + mSnackBar = SnackBar.make(findComponentById(ResourceTable.Id_parent), text, SnackBar.LENGTH_SHORT); + mSnackBar.setActionListener("CLOSE", component -> mSnackBar.close(true)); + mSnackBar.show(); + } + + private void showPopMenu(Component archComponent, List menuInfoList, String title) { + PopupDialog popupDialog = new PopupDialog(this, archComponent); + + Component component = LayoutScatter.getInstance(this).parse(ResourceTable.Layout_popmenu_layout, null, false); + ListContainer listContainer = (ListContainer) component.findComponentById(ResourceTable.Id_listContainer); + listContainer.setEnabled(false); + listContainer.setItemClickedListener(new ListContainer.ItemClickedListener() { + @Override + public void onItemClicked(ListContainer listContainer, Component component, int position, long id) { + if(id == ResourceTable.Integer_action_toggle_list) { + if (mListContainer.getItemProvider() == null) { + mListContainer.setItemProvider(mProvider); + } else { + mListContainer.setItemProvider(null); + mListContainer.invalidate(); + if (mSpeedDialView.getVisibility() != Component.VISIBLE) { + mSpeedDialView.show(); + } + } + }else if(id == ResourceTable.Integer_action_main_fab_color) { + showPopMenu(archComponent, MenuInfoConstants.mainFabColorList, "Main FAB color"); + }else if(id == ResourceTable.Integer_action_main_fab_background_color_open) { + showPopMenu(archComponent, MenuInfoConstants.mainFabOpenColorList, "Main FAB color open"); + }else if(id == ResourceTable.Integer_action_main_fab_background_color_close) { + showPopMenu(archComponent, MenuInfoConstants.mainFabCloseColorList, "Main FAB color close"); + }else { + MenuUtils.handlerMenuSetting(mSpeedDialView, id); + } + popupDialog.hide(); + } + }); + listContainer.setItemProvider(new MenuAdapter(this, menuInfoList)); + + Text mTitleText = (Text) component.findComponentById(ResourceTable.Id_title); + if(TextTool.isNullOrEmpty(title)) { + mTitleText.setVisibility(Component.HIDE); + }else { + mTitleText.setVisibility(Component.VISIBLE); + mTitleText.setText(title); + } + + popupDialog.setCustomComponent(component); + popupDialog.setHasArrow(false); + popupDialog.setAutoClosable(true); + popupDialog.setOffset(0, -80); + popupDialog.show(); + } + + @Override + public void onActive() { + super.onActive(); + } + + @Override + public void onForeground(Intent intent) { + super.onForeground(intent); + } +} diff --git a/entry/src/ohosTest/java/com/leinardi/ohos/speeddial/sample/ExampleOhosTest.java b/entry/src/ohosTest/java/com/leinardi/ohos/speeddial/sample/ExampleOhosTest.java new file mode 100644 index 0000000..b7603b2 --- /dev/null +++ b/entry/src/ohosTest/java/com/leinardi/ohos/speeddial/sample/ExampleOhosTest.java @@ -0,0 +1,14 @@ +package com.leinardi.ohos.speeddial.sample; + +import ohos.aafwk.ability.delegation.AbilityDelegatorRegistry; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ExampleOhosTest { + @Test + public void testBundleName() { + final String actualBundleName = AbilityDelegatorRegistry.getArguments().getTestBundleName(); + assertEquals("com.leinardi.ohos.speeddial.sample", actualBundleName); + } +} \ No newline at end of file diff --git a/entry/src/test/java/com/leinardi/ohos/speeddial/sample/ExampleTest.java b/entry/src/test/java/com/leinardi/ohos/speeddial/sample/ExampleTest.java new file mode 100644 index 0000000..dd927eb --- /dev/null +++ b/entry/src/test/java/com/leinardi/ohos/speeddial/sample/ExampleTest.java @@ -0,0 +1,9 @@ +package com.leinardi.ohos.speeddial.sample; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..536f027 --- /dev/null +++ b/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG=$(dirname "$PRG")"/$link" + fi +done +SAVED="$(pwd)" +cd "$(dirname \"$PRG\")/" >/dev/null +APP_HOME="$(pwd -P)" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=$(basename "$0") + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn() { + echo "$*" +} + +die() { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$(uname)" in +CYGWIN*) + cygwin=true + ;; +Darwin*) + darwin=true + ;; +MINGW*) + msys=true + ;; +NONSTOP*) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ]; then + if [ -x "$JAVA_HOME/jre/sh/java" ]; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ]; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ]; then + MAX_FD_LIMIT=$(ulimit -H -n) + if [ $? -eq 0 ]; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ]; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ]; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ]; then + APP_HOME=$(cygpath --path --mixed "$APP_HOME") + CLASSPATH=$(cygpath --path --mixed "$CLASSPATH") + JAVACMD=$(cygpath --unix "$JAVACMD") + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=$(find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null) + SEP="" + for dir in $ROOTDIRSRAW; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ]; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@"; do + CHECK=$(echo "$arg" | egrep -c "$OURCYGPATTERN" -) + CHECK2=$(echo "$arg" | egrep -c "^-") ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ]; then ### Added a condition + eval $(echo args$i)=$(cygpath --path --ignore --mixed "$arg") + else + eval $(echo args$i)="\"$arg\"" + fi + i=$(expr $i + 1) + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save() { + for i; do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/"; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..62bd9b9 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,103 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/AnimStateChangedListener.java b/library/src/main/java/com/leinardi/ohos/speeddial/AnimStateChangedListener.java new file mode 100644 index 0000000..541f484 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/AnimStateChangedListener.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.animation.Animator; + +public class AnimStateChangedListener implements Animator.StateChangedListener { + + @Override + public void onStart(Animator animator) { + + } + + @Override + public void onStop(Animator animator) { + + } + + @Override + public void onCancel(Animator animator) { + + } + + @Override + public void onEnd(Animator animator) { + + } + + @Override + public void onPause(Animator animator) { + + } + + @Override + public void onResume(Animator animator) { + + } +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/AttrUtils.java b/library/src/main/java/com/leinardi/ohos/speeddial/AttrUtils.java new file mode 100644 index 0000000..9e9aae6 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/AttrUtils.java @@ -0,0 +1,156 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.components.Attr; +import ohos.agp.components.AttrSet; +import ohos.agp.components.element.Element; +import ohos.agp.utils.Color; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +/** + * 获取自定义属性工具类,如果没有配置这个自定义属性则使用默认值。 + * + * @since 2021-03-01 + */ +public class AttrUtils { + private static final HiLogLabel LABEL = new HiLogLabel(HiLog.LOG_APP, 0x78002, AttrUtils.class.getSimpleName()); + + private AttrUtils() { + } + + /** + * 获取Float资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static float getFloatValueByAttr(AttrSet attrSet, String filedName, float defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getFloatValue(); + } + return defaultValue; + } + + /** + * 获取Int资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static int getIntValueByAttr(AttrSet attrSet, String filedName, int defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getIntegerValue(); + } + return defaultValue; + } + + /** + * 获取Color资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static Color getColorValueByAttr(AttrSet attrSet, String filedName, Color defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getColorValue(); + } + HiLog.info(LABEL, "getColorValueByAttr return defaultValue"); + return defaultValue; + } + + /** + * 获取Dimension资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static int getDimensionValueByAttr(AttrSet attrSet, String filedName, int defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getDimensionValue(); + } + return defaultValue; + } + + /** + * 获取String资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static String getStringValueByAttr(AttrSet attrSet, String filedName, String defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getStringValue(); + } + return defaultValue; + } + + /** + * 获取Boolean资源值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 资源值 + */ + public static boolean getBooleanValueByAttr(AttrSet attrSet, String filedName, boolean defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getBoolValue(); + } + return defaultValue; + } + + /** + * 获取Element元素值 + * + * @param attrSet 属性集合 + * @param filedName 属性名称 + * @param defaultValue 默认值 + * @return 元素值 + */ + public static Element getElementValueByAttr(AttrSet attrSet, String filedName, Element defaultValue) { + Attr attr = get(attrSet, filedName); + if (attr != null) { + return attr.getElement(); + } + return defaultValue; + } + + private static Attr get(AttrSet attrSet, String filedName) { + if (attrSet != null && attrSet.getAttr(filedName).isPresent()) { + return attrSet.getAttr(filedName).get(); + } + return null; + } + +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/CardView.java b/library/src/main/java/com/leinardi/ohos/speeddial/CardView.java new file mode 100644 index 0000000..801836f --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/CardView.java @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.components.*; +import ohos.agp.components.element.Element; +import ohos.agp.render.Canvas; +import ohos.agp.utils.Color; +import ohos.app.Context; + +public class CardView extends StackLayout implements Component.DrawTask, Component.BindStateChangedListener { + private static final String fab_shadowCorner = "fab_shadowCorner"; + private static final String fab_showShadow = "fab_showShadow"; + private static final String fab_showShadowColor = "fab_showShadowColor"; + private static final String fab_shadowXOffset = "fab_shadowXOffset"; + private static final String fab_shadowYOffset = "fab_shadowYOffset"; + + private boolean mShowShadow; + private int mShadowCorner = AttrHelper.vp2px(4f, getContext()); + private int mShadowColor; + private int mShadowXOffset = AttrHelper.vp2px(0f, getContext()); + private int mShadowYOffset = AttrHelper.vp2px(2f, getContext()); + + public CardView(Context context) { + super(context); + initWithContext(context, null); + } + + public CardView(Context context, AttrSet attrSet) { + super(context, attrSet); + initWithContext(context, attrSet); + } + + public CardView(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + initWithContext(context, attrSet); + } + + private void initWithContext(Context context, AttrSet attrSet) { + setClipEnabled(false); + mContext = context; + if (attrSet != null) { + mShadowCorner = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowCorner, mShadowCorner); + mShowShadow = AttrUtils.getBooleanValueByAttr(attrSet, fab_showShadow, true); + mShadowColor = AttrUtils.getColorValueByAttr(attrSet, fab_showShadowColor, new Color(0x66000000)).getValue(); + mShadowXOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowXOffset, mShadowXOffset); + mShadowYOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowYOffset, mShadowYOffset); + } else { + mShowShadow = true; + mShadowColor = 0x66000000; + } + + setBindStateChangedListener(this); + addDrawTask(this, Component.DrawTask.BETWEEN_BACKGROUND_AND_CONTENT); + + } + + @Override + public void onDraw(Component component, Canvas canvas) { +// if(mShowShadow) { +// Paint mShadowPaint = new Paint(); +// mShadowPaint.setColor(new Color(mShadowColor)); +// mShadowPaint.setAntiAlias(true); +// mShadowPaint.setMaskFilter(new MaskFilter(AttrHelper.fp2px(6f, mContext), MaskFilter.Blur.NORMAL)); +// +// RectFloat rectFloat = new RectFloat(mShadowXOffset, mShadowYOffset, getWidth() - mShadowXOffset, getHeight() - mShadowYOffset); +// +// canvas.drawRoundRect(rectFloat, mShadowCorner, mShadowCorner, mShadowPaint); +// } + } + + @Override + public void setBackground(Element element) { + super.setBackground(element); + } + + @Override + public void onComponentBoundToWindow(Component component) { + } + + @Override + public void onComponentUnboundFromWindow(Component component) { + + } +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/FabWithLabelView.java b/library/src/main/java/com/leinardi/ohos/speeddial/FabWithLabelView.java new file mode 100644 index 0000000..7d0e738 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/FabWithLabelView.java @@ -0,0 +1,373 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.colors.RgbColor; +import ohos.agp.components.*; +import ohos.agp.components.element.Element; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.utils.Color; +import ohos.agp.utils.LayoutAlignment; +import ohos.agp.utils.TextTool; +import ohos.app.Context; + +/** + * View that contains fab button and its label. + */ +public class FabWithLabelView extends DirectionalLayout { + private static final String TAG = FabWithLabelView.class.getSimpleName(); + + private Text mLabelTextView; + private FloatingActionButton mFab; + private CardView mLabelCardView; + private boolean mIsLabelEnabled; + private SpeedDialActionItem mSpeedDialActionItem; + private SpeedDialView.OnActionSelectedListener mOnActionSelectedListener; + private int mCurrentFabSize; + private float mLabelCardViewElevation; + private Element mLabelCardViewBackground; + + public FabWithLabelView(Context context) { + super(context); + init(context, null); + } + + public FabWithLabelView(Context context, AttrSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public FabWithLabelView(Context context, AttrSet attrs, String styleName) { + super(context, attrs, styleName); + init(context, attrs); + } + + /** + * Init custom attributes. + * + * @param context context. + * @param attrs attributes. + */ + private void init(Context context, AttrSet attrs) { + Component rootView = LayoutScatter.getInstance(context).parse(ResourceTable.Layout_sd_fab_with_label_view, this, true); + rootView.setFocusable(FOCUS_DISABLE); + rootView.setTouchFocusable(false); + + mFab = (FloatingActionButton)rootView.findComponentById(ResourceTable.Id_sd_fab); + mLabelTextView = (Text) rootView.findComponentById(ResourceTable.Id_sd_label); + mLabelCardView = (CardView) rootView.findComponentById(ResourceTable.Id_sd_label_container); + + mLabelTextView.setMaxTextWidth(AttrHelper.fp2px(192, context)); + mLabelTextView.setTruncationMode(Text.TruncationMode.ELLIPSIS_AT_END); + + setFabSize(FloatingActionButton.SIZE_MINI); + setOrientation(DirectionalLayout.HORIZONTAL); + setClipEnabled(false); + rootView.setClipEnabled(false); + try { + int src = AttrUtils.getIntValueByAttr(attrs, "iconSrc", SpeedDialActionItem.RESOURCE_NOT_SET); + SpeedDialActionItem.Builder builder = new SpeedDialActionItem.Builder(getId(), src); + String labelText = AttrUtils.getStringValueByAttr(attrs, "fabLabel", ""); + builder.setLabel(labelText); + int fabBackgroundColor = UiUtils.getPrimaryColor(context); + fabBackgroundColor = AttrUtils.getIntValueByAttr(attrs, "fabBackgroundColor", fabBackgroundColor); + builder.setFabBackgroundColor(fabBackgroundColor); + int labelColor = SpeedDialActionItem.RESOURCE_NOT_SET; + labelColor = AttrUtils.getIntValueByAttr(attrs, "fabLabelColor", labelColor); + builder.setLabelColor(labelColor); + int labelBackgroundColor = SpeedDialActionItem.RESOURCE_NOT_SET; + labelBackgroundColor = AttrUtils.getIntValueByAttr(attrs, "fabLabelBackgroundColor", labelBackgroundColor); + builder.setLabelBackgroundColor(labelBackgroundColor); + boolean labelClickable = AttrUtils.getBooleanValueByAttr(attrs, "fabLabelClickable", true); + builder.setLabelClickable(labelClickable); + setSpeedDialActionItem(builder.create()); + } catch (Exception e) { + LogUtil.debug(TAG, "Failure setting FabWithLabelView icon"); + } + } + + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + getFab().setVisibility(visibility); + if (isLabelEnabled()) { + getLabelBackground().setVisibility(visibility); + } + } + + @Override + public void setOrientation(int orientation) { + super.setOrientation(orientation); + setFabSize(mCurrentFabSize); + if (orientation == VERTICAL) { + setLabelEnabled(false); + } else { + setLabel(mLabelTextView.getText()); + } + } + + /** + * Return true if button has label, false otherwise. + */ + public boolean isLabelEnabled() { + return mIsLabelEnabled; + } + + /** + * Enables or disables label of button. + */ + private void setLabelEnabled(boolean enabled) { + mIsLabelEnabled = enabled; + mLabelCardView.setVisibility(enabled ? Component.VISIBLE : Component.HIDE); + } + + /** + * Returns FAB labels background card. + */ + public CardView getLabelBackground() { + return mLabelCardView; + } + + /** + * Returns the {@link FloatingActionButton}. + */ + public FloatingActionButton getFab() { + return mFab; + } + + public SpeedDialActionItem getSpeedDialActionItem() { + if (mSpeedDialActionItem == null) { + throw new IllegalStateException("SpeedDialActionItem not set yet!"); + } + return mSpeedDialActionItem; + } + + /** + * Returns an instance of the {@link SpeedDialActionItem.Builder} initialized with the current instance of the + * {@link SpeedDialActionItem} to make it easier to modify the current Action Item settings. + */ + public SpeedDialActionItem.Builder getSpeedDialActionItemBuilder() { + return new SpeedDialActionItem.Builder(getSpeedDialActionItem()); + } + + public void setSpeedDialActionItem(SpeedDialActionItem actionItem) { + mSpeedDialActionItem = actionItem; + if (actionItem.getFabType().equals(SpeedDialActionItem.TYPE_FILL)) { + this.removeComponent(mFab); + Component view = LayoutScatter.getInstance(getContext()).parse(ResourceTable.Layout_sd_fill_fab, this, true); + FloatingActionButton newFab = (FloatingActionButton)view.findComponentById(ResourceTable.Id_sd_fab_fill); + mFab = newFab; + } + setId(actionItem.getId()); + setLabel(actionItem.getLabel(getContext())); + setFabContentDescription(actionItem.getContentDescription(getContext())); + SpeedDialActionItem speedDialActionItem = getSpeedDialActionItem(); + setLabelClickable(speedDialActionItem != null && speedDialActionItem.isLabelClickable()); + setFabIcon(actionItem.getFabImageDrawable(getContext())); + int imageTintColor = actionItem.getFabImageTintColor(); + if (imageTintColor == SpeedDialActionItem.RESOURCE_NOT_SET) { + imageTintColor = UiUtils.getOnSecondaryColor(getContext()); + } + boolean imageTint = actionItem.getFabImageTint(); + if (imageTint) { + setFabImageTintColor(imageTintColor); + } + int fabBackgroundColor = actionItem.getFabBackgroundColor(); + if (fabBackgroundColor == SpeedDialActionItem.RESOURCE_NOT_SET) { + fabBackgroundColor = UiUtils.getPrimaryColor(getContext()); + } + setFabBackgroundColor(fabBackgroundColor); + int labelColor = actionItem.getLabelColor(); + if (labelColor == SpeedDialActionItem.RESOURCE_NOT_SET) { + labelColor = UiUtils.getColor(getContext(), ResourceTable.Color_sd_label_text_color); + } + setLabelColor(labelColor); + int labelBackgroundColor = actionItem.getLabelBackgroundColor(); + if (labelBackgroundColor == SpeedDialActionItem.RESOURCE_NOT_SET) { + labelBackgroundColor = UiUtils.getColor(getContext(), ResourceTable.Color_sd_label_background_color); + } + setLabelBackgroundColor(labelBackgroundColor); + if (actionItem.getFabType().equals(SpeedDialActionItem.TYPE_FILL)) { + getFab().setButtonSize(FloatingActionButton.SIZE_MINI); + } else { + getFab().setButtonSize(actionItem.getFabSize()); + } + setFabSize(actionItem.getFabSize()); + } + + /** + * Set a listener that will be notified when a menu fab is selected. + * + * @param listener listener to set. + */ + public void setOnActionSelectedListener(SpeedDialView.OnActionSelectedListener listener) { + mOnActionSelectedListener = listener; + if (mOnActionSelectedListener != null) { + setClickedListener(view -> { + SpeedDialActionItem speedDialActionItem = getSpeedDialActionItem(); + if (mOnActionSelectedListener != null + && speedDialActionItem != null) { + if (speedDialActionItem.isLabelClickable()) { + //UiUtils.performTap(getLabelBackground()); + } else { + //UiUtils.performTap(getFab()); + } + } + }); + getFab().setClickedListener(view -> { + SpeedDialActionItem speedDialActionItem = getSpeedDialActionItem(); + if (mOnActionSelectedListener != null + && speedDialActionItem != null) { + mOnActionSelectedListener.onActionSelected(speedDialActionItem); + } + }); + + getLabelBackground().setClickedListener(view -> { + SpeedDialActionItem speedDialActionItem = getSpeedDialActionItem(); + if (mOnActionSelectedListener != null + && speedDialActionItem != null + && speedDialActionItem.isLabelClickable()) { + mOnActionSelectedListener.onActionSelected(speedDialActionItem); + } + }); + } else { + getFab().setClickedListener(null); + getLabelBackground().setClickedListener(null); + } + + } + + private void setFabSize(int fabSize) { + try { + int normalFabSizePx = (int) getResourceManager().getElement(ResourceTable.Float_sd_fab_normal_size).getFloat(); + int miniFabSizePx = (int) getResourceManager().getElement(ResourceTable.Float_sd_fab_mini_size).getFloat(); + int fabSideMarginPx = (int) getResourceManager().getElement(ResourceTable.Float_sd_fab_side_margin).getFloat(); + int fabSizePx = fabSize == FloatingActionButton.SIZE_NORMAL ? normalFabSizePx : miniFabSizePx; + LayoutConfig rootLayoutParams; + LayoutConfig fabLayoutParams = (LayoutConfig) mFab.getLayoutConfig(); + if (getOrientation() == HORIZONTAL) { + rootLayoutParams = new LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT, fabSizePx); + rootLayoutParams.alignment = LayoutAlignment.END; + + if (fabSize == FloatingActionButton.SIZE_NORMAL) { + int excessMargin = (normalFabSizePx - miniFabSizePx) / 2; + fabLayoutParams.setMargins(fabSideMarginPx - excessMargin, 0, fabSideMarginPx - excessMargin, 0); + } else { + fabLayoutParams.setMargins(fabSideMarginPx, 0, fabSideMarginPx, 0); + } + } else { + rootLayoutParams = new LayoutConfig(fabSizePx, ComponentContainer.LayoutConfig.MATCH_CONTENT); + rootLayoutParams.alignment = LayoutAlignment.VERTICAL_CENTER; + fabLayoutParams.setMargins(0, 0, 0, 0); + } + + setLayoutConfig(rootLayoutParams); + mFab.setLayoutConfig(fabLayoutParams); + mFab.updateFabMargin(getOrientation()); + mCurrentFabSize = fabSize; + }catch (Exception e){ + e.printStackTrace(); + } + } + + /** + * Sets fab drawable. + * + * @param mDrawable drawable to set. + */ + private void setFabIcon(Element mDrawable) { + mFab.setImageElement(mDrawable); + } + + /** + * Sets fab label․ + * + * @param sequence label to set. + */ + private void setLabel(CharSequence sequence) { + if (!TextTool.isNullOrEmpty(sequence)) { + mLabelTextView.setText(sequence.toString()); + setLabelEnabled(getOrientation() == HORIZONTAL); + } else { + setLabelEnabled(false); + } + } + + private void setLabelClickable(boolean clickable) { + getLabelBackground().setClickable(clickable); + getLabelBackground().setFocusable(clickable? FOCUS_ENABLE : FOCUS_DISABLE); + getLabelBackground().setEnabled(clickable); + } + + /** + * Sets fab content description․ + * + * @param sequence content description to set. + */ + private void setFabContentDescription(CharSequence sequence) { + if (!TextTool.isNullOrEmpty(sequence)) { + mFab.setComponentDescription(sequence); + } + } + + /** + * Sets fab image tint color in floating action menu. + * + * @param color color to set. + */ + private void setFabImageTintColor(int color) { + //ImageViewCompat.setImageTintList(mFab, ColorStateList.valueOf(color)); + // HarmonyOS暂时未找到对应实现方法,后续维护该功能 + } + + /** + * Sets fab color in floating action menu. + * + * @param color color to set. + */ + private void setFabBackgroundColor(int color) { + mFab.setColorNormal(color); + } + + private void setLabelColor(int color) { + mLabelTextView.setTextColor(new Color(color)); + } + + private void setLabelBackgroundColor(int color) { + if (color == Color.TRANSPARENT.getValue()) { + ShapeElement normalBackground = new ShapeElement(); + normalBackground.setRgbColor(RgbColor.fromArgbInt(Color.TRANSPARENT.getValue())); + + mLabelCardView.setBackground(normalBackground); + //mLabelCardViewElevation = mLabelCardView.getElevation(); +// mLabelCardView.setElevation(0); + } else { + ShapeElement normalBackground = new ShapeElement(); + normalBackground.setStroke(AttrHelper.fp2px(0.5f, mContext), new RgbColor(0xCCCCCC88)); + normalBackground.setRgbColor(RgbColor.fromArgbInt(color)); + normalBackground.setCornerRadius(AttrHelper.fp2px(4f, mContext)); + + mLabelCardView.setBackground(normalBackground); + +// if (mLabelCardViewElevation != 0) { +// mLabelCardView.setElevation(mLabelCardViewElevation); +// mLabelCardViewElevation = 0; +// } + } + } +} + diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/FloatingActionButton.java b/library/src/main/java/com/leinardi/ohos/speeddial/FloatingActionButton.java new file mode 100644 index 0000000..da56153 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/FloatingActionButton.java @@ -0,0 +1,386 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.leinardi.ohos.speeddial; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.colors.RgbColor; +import ohos.agp.components.*; +import ohos.agp.components.element.Element; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.components.element.StateElement; +import ohos.agp.render.Canvas; +import ohos.agp.render.MaskFilter; +import ohos.agp.render.Paint; +import ohos.agp.utils.Circle; +import ohos.agp.utils.Color; +import ohos.app.Context; + +public class FloatingActionButton extends StackLayout implements Component.DrawTask { + // Size is 56vp, default value + public static final int SIZE_NORMAL = 0; + // Size is 40vp + public static final int SIZE_MINI = 1; + // Animation duration + private static final int ANIM_DURATION = 150; + + // custom attributes + private static final String fab_colorNormal = "fab_colorNormal"; + private static final String fab_colorPressed = "fab_colorPressed"; + private static final String fab_showShadow = "fab_showShadow"; + private static final String fab_showShadowColor = "fab_showShadowColor"; + private static final String fab_shadowXOffset = "fab_shadowXOffset"; + private static final String fab_shadowYOffset = "fab_shadowYOffset"; + private static final String fab_size = "fab_size"; + + private int mFabSize = SIZE_NORMAL; + + private boolean mShowShadow; + private int mShadowColor; + private int mShadowXOffset = AttrHelper.vp2px(1f, getContext()); + private int mShadowYOffset = AttrHelper.vp2px(4f, getContext()); + + private int mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right); + private int mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom); + + private int mColorNormal; + private int mColorPressed; + + private Component mBgComponent; + private Image mIconImage; + + private AnimatorProperty mAnimatorIn; + private AnimatorProperty mAnimatorOut; + + public FloatingActionButton(Context context) { + super(context); + initWithContext(context, null); + } + + public FloatingActionButton(Context context, AttrSet attrSet) { + super(context, attrSet); + initWithContext(context, attrSet); + } + + public FloatingActionButton(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + initWithContext(context, attrSet); + } + + private void initWithContext(Context context, AttrSet attrSet) { + mContext = context; + if (attrSet != null) { + mColorNormal = AttrUtils.getColorValueByAttr(attrSet, fab_colorNormal, new Color(UiUtils.getAccentColor(context))).getValue(); + mColorPressed = AttrUtils.getColorValueByAttr(attrSet, fab_colorPressed, new Color(getDefaultPressColor())).getValue(); + mShowShadow = AttrUtils.getBooleanValueByAttr(attrSet, fab_showShadow, false); + mShadowColor = AttrUtils.getColorValueByAttr(attrSet, fab_showShadowColor, new Color(0x42000000)).getValue(); + mShadowXOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowXOffset, mShadowXOffset); + mShadowYOffset = AttrUtils.getDimensionValueByAttr(attrSet, fab_shadowYOffset, mShadowYOffset); + mFabSize = AttrUtils.getIntValueByAttr(attrSet, fab_size, SIZE_NORMAL); + } else { + mColorNormal = 0xFFDA4336; + mColorPressed = 0xFFE75043; + mShowShadow = false; + mShadowColor = 0x42000000; + mFabSize = SIZE_NORMAL; + } + + onMeasure(); + + setClipEnabled(false); + setClickable(true); + addDrawTask(this, Component.DrawTask.BETWEEN_BACKGROUND_AND_CONTENT); + + // add background component + addBackgroundComponent(); + // add icon + addIconImage(); + } + + private void addBackgroundComponent() { + mBgComponent = new Component(mContext); + + ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig(); + layoutConfig.width = calculateMeasuredWidth(); + layoutConfig.height = calculateMeasuredHeight(); + setLayoutConfig(layoutConfig); + + addComponent(mBgComponent, layoutConfig); + mBgComponent.setMarginsLeftAndRight(mMarginLeftAndRight, mMarginLeftAndRight); + mBgComponent.setMarginsTopAndBottom(mMarginTopAndBottom, mMarginTopAndBottom); + } + + private void addIconImage() { + mIconImage = new Image(mContext); + mIconImage.setScaleMode(Image.ScaleMode.STRETCH); + int size = (int) UiUtils.getDimensionValue(mContext, ResourceTable.Float_fab_icon_size); + StackLayout.LayoutConfig layoutConfig = new StackLayout.LayoutConfig(size, size); + //layoutConfig.alignment = LayoutAlignment.VERTICAL_CENTER; + layoutConfig.setMarginTop((calculateMeasuredHeight() - size) / 2 + mMarginTopAndBottom); + layoutConfig.setMarginLeft((calculateMeasuredWidth() - size) / 2 + mMarginLeftAndRight); + mIconImage.setLayoutConfig(layoutConfig); + addComponent(mIconImage, layoutConfig); + } + + @Override + public void onDraw(Component component, Canvas canvas) { + // draw shadow + if (mShowShadow) { + Paint mShadowPaint = new Paint(); + mShadowPaint.setColor(new Color(mShadowColor)); + mShadowPaint.setAntiAlias(true); + mShadowPaint.setMaskFilter(new MaskFilter(AttrHelper.fp2px(10f, mContext), MaskFilter.Blur.NORMAL)); + canvas.drawCircle(new Circle(getCircleSize() / 2f + mMarginLeftAndRight + mShadowXOffset, getCircleSize() / 2f + + mMarginTopAndBottom + mShadowYOffset, getCircleSize() / 2f), mShadowPaint); + } + + onMeasure(); + updateBackground(); + } + + private void onMeasure() { + if (mBgComponent != null) { + ComponentContainer.LayoutConfig layoutConfig = mBgComponent.getLayoutConfig(); + layoutConfig.width = calculateMeasuredWidth(); + layoutConfig.height = calculateMeasuredHeight(); + mBgComponent.setLayoutConfig(layoutConfig); + } + } + + public int calculateMeasuredWidth() { + return getCircleSize() + calculateShadowWidth(); + } + + public int calculateMeasuredHeight() { + return getCircleSize() + calculateShadowHeight(); + } + + private int getCircleSize() { + return (int) UiUtils.getDimensionValue(mContext, + mFabSize == SIZE_NORMAL ? ResourceTable.Float_fab_size_normal : + ResourceTable.Float_fab_size_mini); + } + + private int calculateShadowWidth() { + //return hasShadow() ? getShadowX() * 2 : 0; + return 0; + } + + private int calculateShadowHeight() { + //return hasShadow() ? getShadowY() * 2 : 0; + return 0; + } + + private int getShadowX() { + return Math.abs(mShadowXOffset); + } + + private int getShadowY() { + return Math.abs(mShadowYOffset); + } + + public void setShowShadow(boolean show) { + if (mShowShadow != show) { + mShowShadow = show; + updateBackground(); + } + } + + public boolean hasShadow() { + return mShowShadow; + } + + public void setButtonSize(int size) { + if (size != SIZE_NORMAL && size != SIZE_MINI) { + throw new IllegalArgumentException("Use @FabSize constants only!"); + } + + if (mFabSize != size) { + mFabSize = size; + updateBackground(); + } + } + + public void setColorNormal(int color) { + if (mColorNormal != color) { + mColorNormal = color; + // 自动更新按压颜色值 + mColorPressed = getDefaultPressColor(); + updateBackground(); + } + } + + public void setColorNormalResId(int colorResId) { + setColorNormal(UiUtils.getColor(getContext(), colorResId)); + } + + public int getColorNormal() { + return mColorNormal; + } + + public void setColorPressed(int color) { + if (color != mColorPressed) { + mColorPressed = color; + updateBackground(); + } + } + + public void setColorPressedResId(int colorResId) { + setColorPressed(UiUtils.getColor(getContext(), colorResId)); + } + + public int getColorPressed() { + return mColorPressed; + } + + public void setImageElement(Element element) { + if (mIconImage != null && element != null) { + mIconImage.setImageElement(element); + } + } + + public Image getIconImage() { + return mIconImage; + } + + public void show(OnVisibilityChangedListener listener) { + if (getVisibility() != VISIBLE) { + setVisibility(VISIBLE); + if (mAnimatorIn == null) { + mAnimatorIn = new AnimatorProperty(this); + mAnimatorIn.setDuration(ANIM_DURATION); + mAnimatorIn.setCurveType(Animator.CurveType.DECELERATE); + mAnimatorIn.setStateChangedListener(new AnimStateChangedListener() { + @Override + public void onStart(Animator animator) { + if (listener != null) { + listener.onShown(FloatingActionButton.this); + } + } + }); + } + mAnimatorIn.scaleXFrom(getScaleX()).scaleX(1f); + mAnimatorIn.scaleYFrom(getScaleY()).scaleY(1f); + if (mAnimatorOut != null && mAnimatorOut.isRunning()) { + mAnimatorOut.stop(); + } + if (!mAnimatorIn.isRunning()) { + mAnimatorIn.start(); + } + } + } + + public void hide(OnVisibilityChangedListener listener) { + if (getVisibility() != HIDE) { + if (mAnimatorOut == null) { + mAnimatorOut = new AnimatorProperty(this); + mAnimatorOut.setDuration(ANIM_DURATION); + mAnimatorOut.setCurveType(Animator.CurveType.ACCELERATE); + mAnimatorOut.setStateChangedListener(new AnimStateChangedListener() { + @Override + public void onEnd(Animator animator) { + setVisibility(HIDE); + if (listener != null) { + listener.onHidden(FloatingActionButton.this); + } + } + }); + } + mAnimatorOut.scaleXFrom(getScaleX()).scaleX(0f); + mAnimatorOut.scaleYFrom(getScaleY()).scaleY(0f); + if (mAnimatorIn != null && mAnimatorIn.isRunning()) { + mAnimatorIn.stop(); + } + if (!mAnimatorOut.isRunning()) { + mAnimatorOut.start(); + } + } + } + + public void updateFabMargin(int orientation) { + if (orientation == HORIZONTAL) { + mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right); + mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom); + } else { + mMarginLeftAndRight = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_top_and_bottom); + mMarginTopAndBottom = (int) UiUtils.getDimensionValue(getContext(), ResourceTable.Float_fab_margin_left_and_right); + } + mBgComponent.setMarginsLeftAndRight(mMarginLeftAndRight, mMarginLeftAndRight); + mBgComponent.setMarginsTopAndBottom(mMarginTopAndBottom, mMarginTopAndBottom); + + updateFabIconMargin(); + } + + private void updateFabIconMargin() { + LayoutConfig layoutConfig = (StackLayout.LayoutConfig) mIconImage.getLayoutConfig(); + int size = (int) UiUtils.getDimensionValue(mContext, ResourceTable.Float_fab_icon_size); + layoutConfig.setMarginTop((calculateMeasuredHeight() - size) / 2 + mMarginTopAndBottom); + layoutConfig.setMarginLeft((calculateMeasuredWidth() - size) / 2 + mMarginLeftAndRight); + mIconImage.setLayoutConfig(layoutConfig); + } + + private void updateBackground() { + if (mBgComponent != null) { + mBgComponent.setBackground(createFillDrawable()); + } + } + + private Element createFillDrawable() { + StateElement drawable = new StateElement(); + drawable.addState(new int[]{ComponentState.COMPONENT_STATE_PRESSED}, createCircleDrawable(mColorPressed)); + drawable.addState(new int[]{}, createCircleDrawable(mColorNormal)); + + return drawable; + } + + private Element createCircleDrawable(int color) { + CircleDrawable shapeDrawable = new CircleDrawable(ShapeElement.OVAL); + shapeDrawable.setRgbColor(RgbColor.fromArgbInt(color)); + return shapeDrawable; + } + + private int getDefaultPressColor() { + RgbColor rgbColor = RgbColor.fromArgbInt(mColorNormal); + return Color.argb((int) (rgbColor.getAlpha() * 0.7), rgbColor.getRed(), rgbColor.getGreen(), rgbColor.getBlue()); + } + + @Override + public void setClickedListener(ClickedListener listener) { + if (mBgComponent != null) { + mBgComponent.setClickedListener(listener); + } + } + + private class CircleDrawable extends ShapeElement { + private CircleDrawable(int shape) { + super(); + setShape(shape); + } + + @Override + public void drawToCanvas(Canvas canvas) { + setBounds(0, 0, calculateMeasuredWidth(), calculateMeasuredHeight()); + super.drawToCanvas(canvas); + } + } + + public static class OnVisibilityChangedListener { + public void onShown(FloatingActionButton fab) { + } + + public void onHidden(FloatingActionButton fab) { + } + } +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/LogUtil.java b/library/src/main/java/com/leinardi/ohos/speeddial/LogUtil.java new file mode 100644 index 0000000..fb78137 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/LogUtil.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2020 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +public class LogUtil { + private static final String TAG_LOG = "FloatingActionButton"; + + private static final int DOMAIN_ID = 0xD000F00; + + private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, DOMAIN_ID, LogUtil.TAG_LOG); + + private static final String LOG_FORMAT = "%{public}s: %{public}s"; + + private LogUtil() { + /* Do nothing */ + } + + /** + * Print debug log + * + * @param tag log tag + * @param msg log message + */ + public static void debug(String tag, String msg) { + HiLog.debug(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print info log + * + * @param tag log tag + * @param msg log message + */ + public static void info(String tag, String msg) { + HiLog.info(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print warn log + * + * @param tag log tag + * @param msg log message + */ + public static void warn(String tag, String msg) { + HiLog.warn(LABEL_LOG, LOG_FORMAT, tag, msg); + } + + /** + * Print error log + * + * @param tag log tag + * @param msg log message + */ + public static void error(String tag, String msg) { + HiLog.error(LABEL_LOG, LOG_FORMAT, tag, msg); + } + +} + diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/SnackBar.java b/library/src/main/java/com/leinardi/ohos/speeddial/SnackBar.java new file mode 100644 index 0000000..7953587 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/SnackBar.java @@ -0,0 +1,186 @@ +/* + * Copyright (C) 2021 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.leinardi.ohos.speeddial; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.agp.utils.LayoutAlignment; +import ohos.app.Context; +import ohos.eventhandler.EventHandler; +import ohos.eventhandler.EventRunner; + +/** + * 封装一个简单的SnackBar + */ +public class SnackBar extends StackLayout { + private static final int DURATION = 150; + + public static final int LENGTH_SHORT = -1; + public static final int LENGTH_LONG = -2; + public static final int LENGTH_INDEFINITE = -3; + + private static int DEFAULT_SHOW_TIME_MS = 3000; + + private Text mText; + private Button mActionButton; + + private EventHandler mHandler; + + private final Runnable mCloseRunnable = () -> close(true); + + public SnackBar(Context context) { + super(context); + initWithContext(context); + } + + public SnackBar(Context context, AttrSet attrSet) { + super(context, attrSet); + initWithContext(context); + } + + public SnackBar(Context context, AttrSet attrSet, String styleName) { + super(context, attrSet, styleName); + initWithContext(context); + } + + private void initWithContext(Context context) { + mHandler = new EventHandler(EventRunner.getMainEventRunner()); + + LayoutScatter.getInstance(context).parse(ResourceTable.Layout_snackbar_layout, this, true); + mText = (Text)findComponentById(ResourceTable.Id_text); + mActionButton = (Button)findComponentById(ResourceTable.Id_snackbar_action); + + mText.setTruncationMode(Text.TruncationMode.ELLIPSIS_AT_END); + } + + public void setMessage(String message) { + mText.setText(message); + } + + public void setActionListener(String text, ClickedListener listener) { + mActionButton.setText(text); + mActionButton.setClickedListener(listener); + } + + public void setDuration(int duration) { + if(duration > 0) { + if(duration < 1500) { + throw new IllegalArgumentException("Min duration is 1500ms"); + } + DEFAULT_SHOW_TIME_MS = duration; + }else { + switch (duration) { + case LENGTH_SHORT: + DEFAULT_SHOW_TIME_MS = 3000; + break; + case LENGTH_LONG: + DEFAULT_SHOW_TIME_MS = 5000; + break; + case LENGTH_INDEFINITE: + DEFAULT_SHOW_TIME_MS = Integer.MAX_VALUE; + break; + default: + break; + } + } + } + + public void show() { + setVisibility(VISIBLE); + AnimatorProperty property = new AnimatorProperty(this); + property.alphaFrom(0.0f).alpha(1.0f); + property.scaleXFrom(0.5f).scaleX(1.0f); + property.scaleYFrom(0.0f).scaleY(1.0f); + property.setDuration(DURATION); + property.setCurveType(Animator.CurveType.LINEAR); + property.start(); + + //自动消失 + autoClose(); + } + + public void close(boolean isAnim) { + if(getVisibility() == VISIBLE) { + if(isAnim) { + AnimatorProperty property = new AnimatorProperty(this); + property.alphaFrom(1.0f).alpha(0.0f); + property.scaleXFrom(1.0f).scaleX(0.5f); + property.scaleYFrom(1.0f).scaleY(0.0f); + property.setDuration(DURATION); + property.setCurveType(Animator.CurveType.LINEAR); + property.setStateChangedListener(new AnimStateChangedListener(){ + @Override + public void onEnd(Animator animator) { + SnackBar.this.getComponentParent().removeComponent(SnackBar.this); + } + }); + property.start(); + }else { + if(getComponentParent() != null) { + getComponentParent().removeComponent(this); + } + } + } + //移除mCloseRunnable + mHandler.removeTask(mCloseRunnable); + } + + private void autoClose() { + if(DEFAULT_SHOW_TIME_MS != Integer.MAX_VALUE) { + mHandler.postTask(mCloseRunnable, DEFAULT_SHOW_TIME_MS); + } + } + + public static SnackBar make(Component component, String text, int duration) { + SnackBar snackBar = new SnackBar(component.getContext()); + if(component instanceof DirectionalLayout) { + DirectionalLayout parent = (DirectionalLayout)component; + DirectionalLayout.LayoutConfig layoutConfig = new DirectionalLayout.LayoutConfig( + DirectionalLayout.LayoutConfig.MATCH_PARENT, DirectionalLayout.LayoutConfig.MATCH_CONTENT); + layoutConfig.alignment = LayoutAlignment.BOTTOM; + int margin = AttrHelper.fp2px(10f, component.getContext()); + layoutConfig.setMargins(margin, margin, margin, margin); + parent.addComponent(snackBar, layoutConfig); + }else if(component instanceof DependentLayout) { + DependentLayout parent = (DependentLayout)component; + DependentLayout.LayoutConfig layoutConfig = new DependentLayout.LayoutConfig( + DependentLayout.LayoutConfig.MATCH_PARENT, DependentLayout.LayoutConfig.MATCH_CONTENT); + layoutConfig.addRule(DependentLayout.LayoutConfig.ALIGN_BOTTOM); + int margin = AttrHelper.fp2px(10f, component.getContext()); + layoutConfig.setMargins(margin, margin, margin, margin); + parent.addComponent(snackBar, layoutConfig); + }else if(component instanceof StackLayout) { + StackLayout parent = (StackLayout)component; + StackLayout.LayoutConfig layoutConfig = new StackLayout.LayoutConfig( + StackLayout.LayoutConfig.MATCH_PARENT, StackLayout.LayoutConfig.MATCH_CONTENT); + layoutConfig.alignment = LayoutAlignment.BOTTOM; + int margin = AttrHelper.fp2px(10f, component.getContext()); + layoutConfig.setMargins(margin, margin, margin, margin); + parent.addComponent(snackBar, layoutConfig); + }else if(component instanceof ComponentContainer) { + ComponentContainer parent = (ComponentContainer)component; + ComponentContainer.LayoutConfig layoutConfig = new ComponentContainer.LayoutConfig( + ComponentContainer.LayoutConfig.MATCH_PARENT, ComponentContainer.LayoutConfig.MATCH_CONTENT); + int margin = AttrHelper.fp2px(10f, component.getContext()); + layoutConfig.setMargins(margin, margin, margin, margin); + parent.addComponent(snackBar, layoutConfig); + } + snackBar.setMessage(text); + snackBar.setDuration(duration); + return snackBar; + } + +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialActionItem.java b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialActionItem.java new file mode 100644 index 0000000..0f73b36 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialActionItem.java @@ -0,0 +1,360 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.components.element.Element; +import ohos.agp.components.element.VectorElement; +import ohos.app.Context; +import ohos.utils.Parcel; +import ohos.utils.Sequenceable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +public class SpeedDialActionItem implements Sequenceable { + public static final int RESOURCE_NOT_SET = Integer.MIN_VALUE; + + @Retention(RetentionPolicy.RUNTIME) + public @interface FabType { }; + public static final String TYPE_NORMAL = "normal"; + public static final String TYPE_FILL = "fill"; + + private int mId; + private String mLabel; + private int mLabelRes; + private String mContentDescription; + private int mContentDescriptionRes; + private int mFabImageResource; + private Element mFabImageDrawable; + private int mFabImageTintColor; + private boolean mFabImageTint; + private String mFabType; + private int mFabBackgroundColor; + private int mLabelColor; + private int mLabelBackgroundColor; + private boolean mLabelClickable; + private int mFabSize; + private int mTheme; + + private SpeedDialActionItem() { + } + + private SpeedDialActionItem(Builder builder) { + mId = builder.mId; + mLabel = builder.mLabel; + mLabelRes = builder.mLabelRes; + mContentDescription = builder.mContentDescription; + mContentDescriptionRes = builder.mContentDescriptionRes; + mFabImageTintColor = builder.mFabImageTintColor; + mFabImageTint = builder.mFabImageTint; + mFabType = builder.mFabType; + mFabImageResource = builder.mFabImageResource; + mFabImageDrawable = builder.mFabImageDrawable; + mFabBackgroundColor = builder.mFabBackgroundColor; + mLabelColor = builder.mLabelColor; + mLabelBackgroundColor = builder.mLabelBackgroundColor; + mLabelClickable = builder.mLabelClickable; + mFabSize = builder.mFabSize; + mTheme = builder.mTheme; + } + + public int getId() { + return mId; + } + + public String getLabel(Context context) { + if (mLabel != null) { + return mLabel; + } else if (mLabelRes != RESOURCE_NOT_SET) { + return context.getString(mLabelRes); + } else { + return null; + } + } + + public String getContentDescription(Context context) { + if (mContentDescription != null) { + return mContentDescription; + } else if (mContentDescriptionRes != RESOURCE_NOT_SET) { + return context.getString(mContentDescriptionRes); + } else { + return null; + } + } + + /** + * Gets the current Drawable, or null if no Drawable has been assigned. + * + * @param context A context to retrieve the Drawable from (needed for SpeedDialActionItem.Builder(int, int). + * @return the speed dial item drawable, or null if no drawable has been assigned. + */ + public Element getFabImageDrawable(Context context) { + if (mFabImageDrawable != null) { + return mFabImageDrawable; + } else if (mFabImageResource != RESOURCE_NOT_SET) { + return new VectorElement(context, mFabImageResource); + } else { + return null; + } + } + + public int getFabImageTintColor() { + return mFabImageTintColor; + } + + public boolean getFabImageTint() { + return mFabImageTint; + } + + @FabType + public String getFabType() { + return mFabType; + } + + public int getFabBackgroundColor() { + return mFabBackgroundColor; + } + + public int getLabelColor() { + return mLabelColor; + } + + public int getLabelBackgroundColor() { + return mLabelBackgroundColor; + } + + public boolean isLabelClickable() { + return mLabelClickable; + } + + public FabWithLabelView createFabWithLabelView(Context context) { + FabWithLabelView fabWithLabelView = new FabWithLabelView(context); + fabWithLabelView.setSpeedDialActionItem(this); + return fabWithLabelView; + } + + public int getFabSize() { + return mFabSize; + } + + public static class Builder { + private final int mId; + private final int mFabImageResource; + private final Element mFabImageDrawable; + private int mFabImageTintColor = RESOURCE_NOT_SET; + private boolean mFabImageTint = true; + + private String mFabType = TYPE_NORMAL; + private String mLabel; + private int mLabelRes = RESOURCE_NOT_SET; + private String mContentDescription; + private int mContentDescriptionRes = RESOURCE_NOT_SET; + private int mFabBackgroundColor = RESOURCE_NOT_SET; + private int mLabelColor = RESOURCE_NOT_SET; + private int mLabelBackgroundColor = RESOURCE_NOT_SET; + private boolean mLabelClickable = true; + private int mFabSize = FloatingActionButton.SIZE_MINI; + private int mTheme = RESOURCE_NOT_SET; + + /** + * Creates a builder for a speed dial action item that uses a media as icon. + * + * @param id the identifier for this action item. The identifier must be unique to the instance + * of {@link SpeedDialView}. The identifier should be a positive number. + * @param fabImageResource resId the resource identifier of the drawable + */ + public Builder(int id, int fabImageResource) { + mId = id; + mFabImageResource = fabImageResource; + mFabImageDrawable = null; + } + + /** + * Creates a builder for a speed dial action item that uses a {@link Element} as icon. + *

{@link Element} are not parcelables so is not possible to restore them when the view is + * recreated for example after an orientation change. If possible always use the {@link #Builder(int, int)}

+ * + * @param id the identifier for this action item. The identifier must be unique to the instance + * of {@link SpeedDialView}. The identifier should be a positive number. + * @param drawable the Drawable to set, or null to clear the content + */ + public Builder(int id, Element drawable) { + mId = id; + mFabImageDrawable = drawable; + mFabImageResource = RESOURCE_NOT_SET; + } + + /** + * Creates a builder for a speed dial action item that uses a {@link SpeedDialActionItem} instance to + * initialize the default values. + * + * @param speedDialActionItem it will be used for the default values of the builder. + */ + public Builder(SpeedDialActionItem speedDialActionItem) { + mId = speedDialActionItem.mId; + mLabel = speedDialActionItem.mLabel; + mLabelRes = speedDialActionItem.mLabelRes; + mContentDescription = speedDialActionItem.mContentDescription; + mContentDescriptionRes = speedDialActionItem.mContentDescriptionRes; + mFabImageResource = speedDialActionItem.mFabImageResource; + mFabImageDrawable = speedDialActionItem.mFabImageDrawable; + mFabImageTintColor = speedDialActionItem.mFabImageTintColor; + mFabImageTint = speedDialActionItem.mFabImageTint; + mFabType = speedDialActionItem.mFabType; + mFabBackgroundColor = speedDialActionItem.mFabBackgroundColor; + mLabelColor = speedDialActionItem.mLabelColor; + mLabelBackgroundColor = speedDialActionItem.mLabelBackgroundColor; + mLabelClickable = speedDialActionItem.mLabelClickable; + mFabSize = speedDialActionItem.mFabSize; + mTheme = speedDialActionItem.mTheme; + } + + public Builder setLabel(String label) { + mLabel = label; + if (mContentDescription == null || mContentDescriptionRes == RESOURCE_NOT_SET) { + mContentDescription = label; + } + return this; + } + + public Builder setLabel(int labelRes) { + mLabelRes = labelRes; + if (mContentDescription == null || mContentDescriptionRes == RESOURCE_NOT_SET) { + mContentDescriptionRes = labelRes; + } + return this; + } + + public Builder setContentDescription(String contentDescription) { + mContentDescription = contentDescription; + return this; + } + + public Builder setContentDescription(int contentDescriptionRes) { + mContentDescriptionRes = contentDescriptionRes; + return this; + } + + public Builder setFabImageTintColor(Integer fabImageTintColor) { + if (fabImageTintColor == null) { + mFabImageTint = false; + } else { + mFabImageTint = true; + mFabImageTintColor = fabImageTintColor; + } + return this; + } + + /** + * set SpeedDialActionItem size. + * SpeedDialActionItem.TYPE_NORMAL Use normal Fab. + * SpeedDialActionItem.TYPE_FILL Set Floating Action Button image to fill the button. + */ + public Builder setFabType(@FabType String fabType) { + mFabType = fabType; + return this; + } + + public Builder setFabBackgroundColor(int fabBackgroundColor) { + mFabBackgroundColor = fabBackgroundColor; + return this; + } + + public Builder setLabelColor(int labelColor) { + mLabelColor = labelColor; + return this; + } + + public Builder setLabelBackgroundColor(int labelBackgroundColor) { + mLabelBackgroundColor = labelBackgroundColor; + return this; + } + + public Builder setLabelClickable(boolean labelClickable) { + mLabelClickable = labelClickable; + return this; + } + + public Builder setTheme(int mTheme) { + this.mTheme = mTheme; + return this; + } + + public SpeedDialActionItem create() { + return new SpeedDialActionItem(this); + } + + public Builder setFabSize(int fabSize) { + mFabSize = fabSize; + return this; + } + + } + + @Override + public boolean marshalling(Parcel dest) { + dest.writeInt(this.mId); + dest.writeString(this.mLabel); + dest.writeInt(this.mLabelRes); + dest.writeString(this.mContentDescription); + dest.writeInt(this.mContentDescriptionRes); + dest.writeInt(this.mFabImageResource); + dest.writeInt(this.mFabImageTintColor); + dest.writeByte(this.mFabImageTint ? (byte) 1 : (byte) 0); + dest.writeString(this.mFabType); + dest.writeInt(this.mFabBackgroundColor); + dest.writeInt(this.mLabelColor); + dest.writeInt(this.mLabelBackgroundColor); + dest.writeByte(this.mLabelClickable ? (byte) 1 : (byte) 0); + dest.writeInt(this.mFabSize); + dest.writeInt(this.mTheme); + return true; + } + + @Override + public boolean unmarshalling(Parcel in) { + this.mId = in.readInt(); + this.mLabel = in.readString(); + this.mLabelRes = in.readInt(); + this.mContentDescription = in.readString(); + this.mContentDescriptionRes = in.readInt(); + this.mFabImageResource = in.readInt(); + this.mFabImageDrawable = null; + this.mFabImageTintColor = in.readInt(); + this.mFabImageTint = in.readByte() != 0; + this.mFabType = in.readString(); + this.mFabBackgroundColor = in.readInt(); + this.mLabelColor = in.readInt(); + this.mLabelBackgroundColor = in.readInt(); + this.mLabelClickable = in.readByte() != 0; + this.mFabSize = in.readInt(); + this.mTheme = in.readInt(); + return true; + } + + @Override + public boolean hasFileDescriptor() { + return false; + } + + public static final Sequenceable.Producer PRODUCER = in -> { + SpeedDialActionItem instance = new SpeedDialActionItem(); + instance.unmarshalling(in); + return instance; + }; + +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialOverlayLayout.java b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialOverlayLayout.java new file mode 100644 index 0000000..f22f3f6 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialOverlayLayout.java @@ -0,0 +1,117 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.colors.RgbColor; +import ohos.agp.components.AttrSet; +import ohos.agp.components.DependentLayout; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.utils.Color; +import ohos.app.Context; +import ohos.hiviewdfx.HiLog; +import ohos.hiviewdfx.HiLogLabel; + +@SuppressWarnings({"unused", "WeakerAccess"}) +public class SpeedDialOverlayLayout extends DependentLayout { + private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD000F00, "SpeedDialOverlayLayout"); + + private static final String TAG = SpeedDialOverlayLayout.class.getSimpleName(); + private boolean mClickableOverlay; + private int mAnimationDuration; + private ClickedListener mClickListener; + + public SpeedDialOverlayLayout(Context context) { + super(context); + init(context, null); + } + + public SpeedDialOverlayLayout(Context context, AttrSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public SpeedDialOverlayLayout(Context context, AttrSet attrs, String styleName) { + super(context, attrs, styleName); + init(context, attrs); + } + + public boolean hasClickableOverlay() { + return mClickableOverlay; + } + + /** + * Enables or disables the click on the overlay view. + * + * @param clickableOverlay True to enable the click, false otherwise. + */ + public void setClickableOverlay(boolean clickableOverlay) { + mClickableOverlay = clickableOverlay; + setClickedListener(mClickListener); + } + + public void setAnimationDuration(int animationDuration) { + mAnimationDuration = animationDuration; + } + + public void show() { + show(true); + } + + public void show(boolean animate) { + if (animate) { + UiUtils.fadeInAnim(this); + } else { + setVisibility(VISIBLE); + } + } + + public void hide() { + hide(true); + } + + public void hide(boolean animate) { + if (animate) { + UiUtils.fadeOutAnim(this); + } else { + setVisibility(HIDE); + } + } + + @Override + public void setClickedListener(ClickedListener listener) { + mClickListener = listener; + super.setClickedListener(hasClickableOverlay() ? listener : null); + } + + private void init(Context context, AttrSet attrs) { + int overlayColor = 0; + try { + overlayColor = UiUtils.getColor(context, ResourceTable.Color_sd_overlay_color); + overlayColor = AttrUtils.getColorValueByAttr(attrs, "background_element", new Color(overlayColor)).getValue(); + mClickableOverlay = AttrUtils.getBooleanValueByAttr(attrs, "clickable_overlay", true); + } catch (Exception e) { + HiLog.error(LABEL_LOG, "Failure setting FabOverlayLayout attrs:"+e.getMessage()); + } + //setElevation(getResources().getDimension(R.dimen.sd_overlay_elevation)); + ShapeElement bg = new ShapeElement(); + bg.setRgbColor(RgbColor.fromArgbInt(overlayColor)); + setBackground(bg); + + setVisibility(HIDE); + mAnimationDuration = 500; + } +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialView.java b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialView.java new file mode 100644 index 0000000..9aba778 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/SpeedDialView.java @@ -0,0 +1,841 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.components.*; +import ohos.agp.components.element.Element; +import ohos.agp.components.element.ShapeElement; +import ohos.agp.utils.Color; +import ohos.agp.utils.LayoutAlignment; +import ohos.app.Context; +import ohos.utils.Parcel; +import ohos.utils.Sequenceable; + +import java.lang.annotation.Retention; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +import static com.leinardi.ohos.speeddial.SpeedDialView.ExpansionMode.BOTTOM; +import static com.leinardi.ohos.speeddial.SpeedDialView.ExpansionMode.LEFT; +import static com.leinardi.ohos.speeddial.SpeedDialView.ExpansionMode.RIGHT; +import static com.leinardi.ohos.speeddial.SpeedDialView.ExpansionMode.TOP; +import static java.lang.annotation.RetentionPolicy.SOURCE; + +public class SpeedDialView extends DirectionalLayout implements Component.BindStateChangedListener { + private static final String TAG = SpeedDialView.class.getSimpleName(); + private static final int DEFAULT_ROTATE_ANGLE = 45; + private static final int ACTION_ANIM_DELAY = 25; + private static final int MAIN_FAB_HORIZONTAL_MARGIN_IN_DP = 4; + private static final int MAIN_FAB_VERTICAL_MARGIN_IN_DP = 4; + private static final int CLICK_TIME_INTERVAL = 200; + + private final InstanceState mInstanceState = new InstanceState(); + private Context mContext; + private final List mFabWithLabelViews = new ArrayList<>(); + private Element mMainFabClosedDrawable = null; + private Element mMainFabOpenedDrawable = null; + private Element mMainFabCloseOriginalDrawable; + private FloatingActionButton mMainFab; + private int mOverlayLayoutId; + private long clickTimeMs; + private SpeedDialOverlayLayout mOverlayLayout; + private OnChangeListener mOnChangeListener; + private OnActionSelectedListener mOnActionSelectedListener; + + private final OnActionSelectedListener mOnActionSelectedProxyListener = new OnActionSelectedListener() { + @Override + public boolean onActionSelected(SpeedDialActionItem actionItem) { + if (mOnActionSelectedListener != null) { + boolean consumed = mOnActionSelectedListener.onActionSelected(actionItem); + if (!consumed) { + close(false); + } + return consumed; + } else { + return false; + } + } + }; + + public SpeedDialView(Context context) { + super(context); + init(context, null); + } + + public SpeedDialView(Context context, AttrSet attrs) { + super(context, attrs); + init(context, attrs); + } + + public SpeedDialView(Context context, AttrSet attrs, String styleName) { + super(context, attrs, styleName); + init(context, attrs); + } + + public boolean getUseReverseAnimationOnClose() { + return mInstanceState.mUseReverseAnimationOnClose; + } + + public void setUseReverseAnimationOnClose(boolean useReverseAnimation) { + mInstanceState.mUseReverseAnimationOnClose = useReverseAnimation; + } + + @ExpansionMode + public int getExpansionMode() { + return mInstanceState.mExpansionMode; + } + + public void setExpansionMode(@ExpansionMode int expansionMode) { + setExpansionMode(expansionMode, false); + } + + private void setExpansionMode(@ExpansionMode int expansionMode, boolean force) { + if (mInstanceState.mExpansionMode != expansionMode || force) { + mInstanceState.mExpansionMode = expansionMode; + switch (expansionMode) { + case TOP: + case BOTTOM: + setOrientation(VERTICAL); + for (FabWithLabelView fabWithLabelView : mFabWithLabelViews) { + fabWithLabelView.setOrientation(HORIZONTAL); + } + break; + case LEFT: + case RIGHT: + setOrientation(HORIZONTAL); + for (FabWithLabelView fabWithLabelView : mFabWithLabelViews) { + fabWithLabelView.setOrientation(VERTICAL); + } + break; + } + close(false); + ArrayList actionItems = getActionItems(); + clearActionItems(); + addAllActionItems(actionItems); + } + } + + @Override + public void setOrientation(int orientation) { + super.setOrientation(orientation); + } + + public void show() { + mMainFab.show(new FloatingActionButton.OnVisibilityChangedListener() { + @Override + public void onShown(FloatingActionButton fab) { + setVisibility(VISIBLE); + } + }); + } + + public void hide() { + if (isOpen()) { + close(); + } + mMainFab.hide(new FloatingActionButton.OnVisibilityChangedListener() { + @Override + public void onHidden(FloatingActionButton fab) { + setVisibility(INVISIBLE); + } + }); + } + + public SpeedDialOverlayLayout getOverlayLayout() { + return mOverlayLayout; + } + + /** + * Add the overlay/touch guard view to appear together with the speed dial menu. + * + * @param overlayLayout The view to add. + */ + public void setOverlayLayout(SpeedDialOverlayLayout overlayLayout) { + if (mOverlayLayout != null) { + setClickedListener(null); + } + mOverlayLayout = overlayLayout; + if (overlayLayout != null) { + overlayLayout.setClickedListener(view -> close()); + showHideOverlay(isOpen(), false); + } + } + + /** + * Appends all of the {@link SpeedDialActionItem} to the end of the list, in the order that they are returned by + * the specified + * collection's Iterator. + * + * @param actionItemCollection collection containing {@link SpeedDialActionItem} to be added to this list + * @return a collection containing the instances of {@link FabWithLabelView} added. + */ + public Collection addAllActionItems(Collection actionItemCollection) { + ArrayList fabWithLabelViews = new ArrayList<>(); + for (SpeedDialActionItem speedDialActionItem : actionItemCollection) { + fabWithLabelViews.add(addActionItem(speedDialActionItem)); + } + return fabWithLabelViews; + } + + /** + * Appends the specified {@link SpeedDialActionItem} to the end of this list. + * + * @param speedDialActionItem {@link SpeedDialActionItem} to be appended to this list + * @return the instance of the {@link FabWithLabelView} if the add was successful, null otherwise. + */ + public FabWithLabelView addActionItem(SpeedDialActionItem speedDialActionItem) { + return addActionItem(speedDialActionItem, mFabWithLabelViews.size()); + } + + /** + * Inserts the specified {@link SpeedDialActionItem} at the specified position in this list. Shifts the element + * currently at that position (if any) and any subsequent elements to the right (adds one to their indices). + * + * @param actionItem {@link SpeedDialActionItem} to be appended to this list + * @param position index at which the specified element is to be inserted + * @return the instance of the {@link FabWithLabelView} if the add was successful, null otherwise. + */ + public FabWithLabelView addActionItem(SpeedDialActionItem actionItem, int position) { + return addActionItem(actionItem, position, true); + } + + /** + * Inserts the specified {@link SpeedDialActionItem} at the specified position in this list. Shifts the element + * currently at that position (if any) and any subsequent elements to the right (adds one to their indices). + * + * @param actionItem {@link SpeedDialActionItem} to be appended to this list + * @param position index at which the specified element is to be inserted + * @param animate true to animate the insertion, false to insert instantly + * @return the instance of the {@link FabWithLabelView} if the add was successful, null otherwise. + */ + public FabWithLabelView addActionItem(SpeedDialActionItem actionItem, int position, boolean animate) { + FabWithLabelView oldView = findFabWithLabelViewById(actionItem.getId()); + if (oldView != null) { + return replaceActionItem(oldView.getSpeedDialActionItem(), actionItem); + } else { + FabWithLabelView newView = actionItem.createFabWithLabelView(mContext); + newView.setOrientation(getOrientation() == VERTICAL ? HORIZONTAL : VERTICAL); + newView.setOnActionSelectedListener(mOnActionSelectedProxyListener); + int layoutPosition = getLayoutPosition(position); + LayoutConfig layoutConfig = new LayoutConfig(LayoutConfig.MATCH_CONTENT, LayoutConfig.MATCH_CONTENT); + layoutConfig.alignment = LayoutAlignment.VERTICAL_CENTER | LayoutAlignment.END; + addComponent(newView, layoutPosition, layoutConfig); + mFabWithLabelViews.add(position, newView); + if (isOpen()) { + if (animate) { + showWithAnimationFabWithLabelView(newView, 0); + } + } else { + newView.setVisibility(Component.HIDE); + } + return newView; + } + } + + /** + * Removes the {@link SpeedDialActionItem} at the specified position in this list. Shifts any subsequent elements + * to the left (subtracts one from their indices). + * + * @param position the index of the {@link SpeedDialActionItem} to be removed + * @return the {@link SpeedDialActionItem} that was removed from the list + */ + public SpeedDialActionItem removeActionItem(int position) { + SpeedDialActionItem speedDialActionItem = mFabWithLabelViews.get(position).getSpeedDialActionItem(); + removeActionItem(speedDialActionItem); + return speedDialActionItem; + } + + /** + * Removes the specified {@link SpeedDialActionItem} from this list, if it is present. If the list does not + * contain the element, it is unchanged. + *

+ * Returns true if this list contained the specified element (or equivalently, if this list changed + * as a result of the call). + * + * @param actionItem {@link SpeedDialActionItem} to be removed from this list, if present + * @return true if this list contained the specified element + */ + public boolean removeActionItem(SpeedDialActionItem actionItem) { + return actionItem != null && removeActionItemById(actionItem.getId()) != null; + } + + /** + * Finds and removes the first {@link SpeedDialActionItem} with the given ID, if it is present. If the list does not + * contain the element, it is unchanged. + * + * @param idRes the ID to search for + * @return the {@link SpeedDialActionItem} that was removed from the list, or null otherwise + */ + public SpeedDialActionItem removeActionItemById(int idRes) { + return removeActionItem(findFabWithLabelViewById(idRes)); + } + + /** + * Replace the {@link SpeedDialActionItem} at the specified position in this list with the one provided as + * parameter. + * + * @param newActionItem {@link SpeedDialActionItem} to use for the replacement + * @param position the index of the {@link SpeedDialActionItem} to be replaced + * @return the instance of the new {@link FabWithLabelView} if the replace was successful, null otherwise. + */ + public FabWithLabelView replaceActionItem(SpeedDialActionItem newActionItem, int position) { + return replaceActionItem(mFabWithLabelViews.get(position).getSpeedDialActionItem(), newActionItem); + } + + /** + * Replace an already added {@link SpeedDialActionItem} with the one provided as parameter. + * + * @param oldSpeedDialActionItem the old {@link SpeedDialActionItem} to remove + * @param newSpeedDialActionItem the new {@link SpeedDialActionItem} to add + * @return the instance of the new {@link FabWithLabelView} if the replace was successful, null otherwise. + */ + public FabWithLabelView replaceActionItem(SpeedDialActionItem oldSpeedDialActionItem, + SpeedDialActionItem newSpeedDialActionItem) { + if (oldSpeedDialActionItem == null) { + return null; + } else { + FabWithLabelView oldView = findFabWithLabelViewById(oldSpeedDialActionItem.getId()); + if (oldView != null) { + int index = mFabWithLabelViews.indexOf(oldView); + if (index < 0) { + return null; + } + removeActionItem(findFabWithLabelViewById(newSpeedDialActionItem.getId()), null, false); + removeActionItem(findFabWithLabelViewById(oldSpeedDialActionItem.getId()), null, false); + return addActionItem(newSpeedDialActionItem, index, false); + } else { + return null; + } + } + } + + /** + * Removes all of the {@link SpeedDialActionItem} from this list. + */ + public void clearActionItems() { + Iterator it = mFabWithLabelViews.iterator(); + while (it.hasNext()) { + FabWithLabelView fabWithLabelView = it.next(); + removeActionItem(fabWithLabelView, it, true); + } + } + + public ArrayList getActionItems() { + ArrayList speedDialActionItems = new ArrayList<>(mFabWithLabelViews.size()); + for (FabWithLabelView fabWithLabelView : mFabWithLabelViews) { + speedDialActionItems.add(fabWithLabelView.getSpeedDialActionItem()); + } + return speedDialActionItems; + } + + /** + * Set a listener that will be notified when a menu fab is selected. + * + * @param listener listener to set. + */ + public void setOnActionSelectedListener(OnActionSelectedListener listener) { + mOnActionSelectedListener = listener; + + for (final FabWithLabelView fabWithLabelView : mFabWithLabelViews) { + fabWithLabelView.setOnActionSelectedListener(mOnActionSelectedProxyListener); + } + } + + /** + * Set Main FloatingActionButton ClickMOnOptionFabSelectedListener. + * + * @param onChangeListener listener to set. + */ + public void setOnChangeListener(final OnChangeListener onChangeListener) { + mOnChangeListener = onChangeListener; + } + + /** + * Opens speed dial menu. + */ + public void open() { + toggle(true, true); + } + + public void open(boolean animate) { + toggle(true, animate); + } + + /** + * Closes speed dial menu. + */ + public void close() { + toggle(false, true); + } + + public void close(boolean animate) { + toggle(false, animate); + } + + /** + * Toggles speed dial menu. + */ + public void toggle() { + toggle(!isOpen(), true); + } + + public void toggle(boolean animate) { + toggle(!isOpen(), animate); + } + + /** + * Return returns true if speed dial menu is open,false otherwise. + */ + public boolean isOpen() { + return mInstanceState.mIsOpen; + } + + public FloatingActionButton getMainFab() { + return mMainFab; + } + + public float getMainFabAnimationRotateAngle() { + return mInstanceState.mMainFabAnimationRotateAngle; + } + + public void setMainFabAnimationRotateAngle(float mainFabAnimationRotateAngle) { + mInstanceState.mMainFabAnimationRotateAngle = mainFabAnimationRotateAngle; + } + + public void setMainFabClosedDrawable(Element drawable) { + mMainFabClosedDrawable = drawable; + updateMainFabDrawable(false); + } + + public void setMainFabOpenedDrawable(Element drawable) { + mMainFabCloseOriginalDrawable = drawable; + if (mMainFabCloseOriginalDrawable == null) { + mMainFabOpenedDrawable = null; + } else { + //mMainFabOpenedDrawable = UiUtils.getRotateDrawable(mMainFabCloseOriginalDrawable, -getMainFabAnimationRotateAngle()); + mMainFabOpenedDrawable = mMainFabCloseOriginalDrawable; + } + updateMainFabDrawable(false); + } + + public int getMainFabClosedBackgroundColor() { + return mInstanceState.mMainFabClosedBackgroundColor; + } + + public void setMainFabClosedBackgroundColor(int mainFabClosedBackgroundColor) { + mInstanceState.mMainFabClosedBackgroundColor = mainFabClosedBackgroundColor; + updateMainFabBackgroundColor(); + } + + public int getMainFabOpenedBackgroundColor() { + return mInstanceState.mMainFabOpenedBackgroundColor; + } + + public void setMainFabOpenedBackgroundColor(int mainFabOpenedBackgroundColor) { + mInstanceState.mMainFabOpenedBackgroundColor = mainFabOpenedBackgroundColor; + updateMainFabBackgroundColor(); + } + + public int getMainFabClosedIconColor() { + return mInstanceState.mMainFabClosedIconColor; + } + + public void setMainFabClosedIconColor(int mainFabClosedIconColor) { + mInstanceState.mMainFabClosedIconColor = mainFabClosedIconColor; + updateMainFabIconColor(); + } + + public int getMainFabOpenedIconColor() { + return mInstanceState.mMainFabOpenedIconColor; + } + + public void setMainFabOpenedIconColor(int mainFabOpenedIconColor) { + mInstanceState.mMainFabOpenedIconColor = mainFabOpenedIconColor; + updateMainFabIconColor(); + } + + @Override + public void onComponentBoundToWindow(Component component) { + if (mOverlayLayout == null) { + SpeedDialOverlayLayout overlayLayout = (SpeedDialOverlayLayout) getRootView().findComponentById(mOverlayLayoutId); + if (overlayLayout != null) { + setOverlayLayout(overlayLayout); + } + } + } + + @Override + public void onComponentUnboundFromWindow(Component component) { + + } + + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + getMainFab().setEnabled(enabled); + } + + private Component getRootView() { + Component parent = this; + + while (parent.getComponentParent() != null && parent.getComponentParent() instanceof Component) { + parent = (Component) parent.getComponentParent(); + } + + return parent; + } + + private int getLayoutPosition(int position) { + if (getExpansionMode() == TOP || getExpansionMode() == LEFT) { + return mFabWithLabelViews.size() - position; + } else { + return position + 1; + } + } + + private SpeedDialActionItem removeActionItem(FabWithLabelView view, Iterator it, boolean animate) { + if (view != null) { + SpeedDialActionItem speedDialActionItem = view.getSpeedDialActionItem(); + if (it != null) { + it.remove(); + } else { + mFabWithLabelViews.remove(view); + } + + if (isOpen()) { + if (mFabWithLabelViews.isEmpty()) { + close(); + } + if (animate) { + UiUtils.shrinkAnim(view, true); + } else { + removeComponent(view); + } + } else { + removeComponent(view); + } + return speedDialActionItem; + } else { + return null; + } + } + + private SpeedDialActionItem removeActionItem(FabWithLabelView view) { + return removeActionItem(view, null, true); + } + + private void init(Context context, AttrSet attrs) { + mContext = context; + mMainFab = createMainFab(); + addComponent(mMainFab); + setClipEnabled(false); + setBindStateChangedListener(this); + try { + setEnabled(AttrUtils.getBooleanValueByAttr(attrs, "ohos_enabled", isEnabled())); + setUseReverseAnimationOnClose(AttrUtils.getBooleanValueByAttr(attrs, "sdUseReverseAnimationOnClose", getUseReverseAnimationOnClose())); + setMainFabAnimationRotateAngle(AttrUtils.getFloatValueByAttr(attrs, "sdMainFabAnimationRotateAngle", getMainFabAnimationRotateAngle())); + Element openElement = AttrUtils.getElementValueByAttr(attrs, "sdMainFabClosedSrc", new ShapeElement()); + setMainFabClosedDrawable(openElement); + + Element closeElement = AttrUtils.getElementValueByAttr(attrs, "sdMainFabOpenedSrc", new ShapeElement()); + setMainFabOpenedDrawable(closeElement); + + setExpansionMode(AttrUtils.getIntValueByAttr(attrs, "sdExpansionMode", getExpansionMode()), true); + + setMainFabClosedBackgroundColor(AttrUtils.getColorValueByAttr(attrs, "sdMainFabClosedBackgroundColor", + new Color(getMainFabClosedBackgroundColor())).getValue()); + setMainFabOpenedBackgroundColor(AttrUtils.getColorValueByAttr(attrs, "sdMainFabOpenedBackgroundColor", + new Color(getMainFabOpenedBackgroundColor())).getValue()); + setMainFabClosedIconColor(AttrUtils.getColorValueByAttr(attrs, "sdMainFabClosedIconColor", + new Color(getMainFabClosedIconColor())).getValue()); + setMainFabOpenedIconColor(AttrUtils.getColorValueByAttr(attrs, "sdMainFabOpenedIconColor", + new Color(getMainFabOpenedIconColor())).getValue()); + mOverlayLayoutId = AttrUtils.getIntValueByAttr(attrs, "sdOverlayLayout", SpeedDialActionItem.RESOURCE_NOT_SET); + } catch (Exception e) { + LogUtil.debug(TAG, "Failure setting SpeedDialView attrs:" + e.getMessage()); + } + } + + private FloatingActionButton createMainFab() { + FloatingActionButton floatingActionButton = new FloatingActionButton(getContext()); + DirectionalLayout.LayoutConfig layoutParams = new DirectionalLayout.LayoutConfig(ComponentContainer.LayoutConfig.MATCH_CONTENT, + ComponentContainer.LayoutConfig.MATCH_CONTENT); + layoutParams.alignment = LayoutAlignment.END; + int marginHorizontal = UiUtils.dpToPx(getContext(), MAIN_FAB_HORIZONTAL_MARGIN_IN_DP); + int marginVertical = UiUtils.dpToPx(getContext(), MAIN_FAB_VERTICAL_MARGIN_IN_DP); + layoutParams.setMargins(marginHorizontal, marginVertical, marginHorizontal, marginVertical); + floatingActionButton.setId(ResourceTable.Integer_sd_main_fab); + floatingActionButton.setLayoutConfig(layoutParams); + floatingActionButton.setClickable(true); + floatingActionButton.setFocusable(FOCUS_ENABLE); + floatingActionButton.setShowShadow(true); + floatingActionButton.setButtonSize(FloatingActionButton.SIZE_NORMAL); + floatingActionButton.setClickedListener(component -> { + if (System.currentTimeMillis() - clickTimeMs <= CLICK_TIME_INTERVAL) { + return; + } + clickTimeMs = System.currentTimeMillis(); + if (isOpen()) { + if (mOnChangeListener == null || !mOnChangeListener.onMainActionSelected()) { + close(); + } + } else { + open(); + } + }); + return floatingActionButton; + } + + private void toggle(boolean show, boolean animate) { + if (show && mFabWithLabelViews.isEmpty()) { + show = false; + if (mOnChangeListener != null) { + mOnChangeListener.onMainActionSelected(); + } + } + if (isOpen() == show) { + return; + } + mInstanceState.mIsOpen = show; + visibilitySetup(show, animate, mInstanceState.mUseReverseAnimationOnClose); + updateMainFabDrawable(animate); + updateMainFabBackgroundColor(); + updateMainFabIconColor(); + showHideOverlay(show, animate); + if (mOnChangeListener != null) { + mOnChangeListener.onToggleChanged(show); + } + } + + private void updateMainFabDrawable(boolean animate) { + if (isOpen()) { + if (mMainFabOpenedDrawable != null) { + mMainFab.setImageElement(mMainFabOpenedDrawable); + } + UiUtils.rotateForward(mMainFab, getMainFabAnimationRotateAngle(), animate); + } else { + UiUtils.rotateBackward(mMainFab, getMainFabAnimationRotateAngle(), animate); + mMainFab.setImageElement(mMainFabClosedDrawable); + } + } + + private void updateMainFabBackgroundColor() { + int color; + if (isOpen()) { + color = getMainFabOpenedBackgroundColor(); + } else { + color = getMainFabClosedBackgroundColor(); + } + if (color != SpeedDialActionItem.RESOURCE_NOT_SET) { + mMainFab.setColorNormal(color); + } else { + mMainFab.setColorNormal(UiUtils.getAccentColor(getContext())); + } + } + + private void updateMainFabIconColor() { + int color; + if (isOpen()) { + color = getMainFabOpenedIconColor(); + } else { + color = getMainFabClosedIconColor(); + } + if (color != SpeedDialActionItem.RESOURCE_NOT_SET) { + mMainFab.setColorNormal(color); + } + } + + private void showHideOverlay(boolean show, boolean animate) { + if (mOverlayLayout != null) { + if (show) { + mOverlayLayout.show(animate); + } else { + mOverlayLayout.hide(animate); + } + } + } + + private FabWithLabelView findFabWithLabelViewById(int id) { + for (FabWithLabelView fabWithLabelView : mFabWithLabelViews) { + if (fabWithLabelView.getId() == id) { + return fabWithLabelView; + } + } + return null; + } + + /** + * Set menus visibility (visible or invisible). + */ + private void visibilitySetup(boolean visible, boolean animate, boolean reverseAnimation) { + int size = mFabWithLabelViews.size(); + if (visible) { + for (int i = 0; i < size; i++) { + FabWithLabelView fabWithLabelView = mFabWithLabelViews.get(i); + fabWithLabelView.setAlpha(1); + fabWithLabelView.setVisibility(Component.VISIBLE); + if (animate) { + showWithAnimationFabWithLabelView(fabWithLabelView, i * ACTION_ANIM_DELAY); + } + if (i == 0) { + fabWithLabelView.getFab().requestFocus(); + } + } + } else { + for (int i = 0; i < size; i++) { + int index = reverseAnimation ? size - 1 - i : i; + FabWithLabelView fabWithLabelView = mFabWithLabelViews.get(index); + if (animate) { + if (reverseAnimation) { + hideWithAnimationFabWithLabelView(fabWithLabelView, i * ACTION_ANIM_DELAY); + } else { + UiUtils.shrinkAnim(fabWithLabelView, false); + } + } else { + fabWithLabelView.setAlpha(0); + fabWithLabelView.setVisibility(Component.HIDE); + } + } + } + } + + private void showWithAnimationFabWithLabelView(FabWithLabelView fabWithLabelView, int delay) { + UiUtils.enlargeAnim(fabWithLabelView.getFab(), delay); + if (fabWithLabelView.isLabelEnabled()) { + StackLayout labelBackground = fabWithLabelView.getLabelBackground(); + + AnimatorProperty animatorProperty = new AnimatorProperty(labelBackground); + animatorProperty.setDuration(150); + animatorProperty.setDelay(delay); + animatorProperty.alphaFrom(0f).alpha(1f); + animatorProperty.start(); + } + } + + private void hideWithAnimationFabWithLabelView(final FabWithLabelView fabWithLabelView, int delay) { + UiUtils.shrinkAnim(fabWithLabelView.getFab(), delay); + if (fabWithLabelView.isLabelEnabled()) { + final StackLayout labelBackground = fabWithLabelView.getLabelBackground(); + + AnimatorProperty animatorProperty = new AnimatorProperty(labelBackground); + animatorProperty.setDuration(150); + animatorProperty.setDelay(delay); + animatorProperty.alphaFrom(1.0f).alpha(0f); + animatorProperty.setStateChangedListener(new AnimStateChangedListener() { + @Override + public void onEnd(Animator animator) { + labelBackground.setVisibility(Component.HIDE); + } + }); + animatorProperty.start(); + } + } + + /** + * Listener for handling events on option fab's. + */ + public interface OnChangeListener { + /** + * Called when the main action has been clicked. + * + * @return true to keep the Speed Dial open, false otherwise. + */ + boolean onMainActionSelected(); + + /** + * Called when the toggle state of the speed dial menu changes (eg. it is opened or closed). + * + * @param isOpen true if the speed dial is open, false otherwise. + */ + void onToggleChanged(boolean isOpen); + } + + /** + * Listener for handling events on option fab's. + */ + public interface OnActionSelectedListener { + /** + * Called when a speed dial action has been clicked. + * + * @param actionItem the {@link SpeedDialActionItem} that was selected. + * @return true to keep the Speed Dial open, false otherwise. + */ + boolean onActionSelected(SpeedDialActionItem actionItem); + } + + @Retention(SOURCE) + public @interface ExpansionMode { + int TOP = 0; + int BOTTOM = 1; + int LEFT = 2; + int RIGHT = 3; + } + + private static class InstanceState implements Sequenceable { + private boolean mIsOpen = false; + private int mMainFabClosedBackgroundColor = SpeedDialActionItem.RESOURCE_NOT_SET; + private int mMainFabOpenedBackgroundColor = SpeedDialActionItem.RESOURCE_NOT_SET; + private int mMainFabClosedIconColor = SpeedDialActionItem.RESOURCE_NOT_SET; + private int mMainFabOpenedIconColor = SpeedDialActionItem.RESOURCE_NOT_SET; + @ExpansionMode + private int mExpansionMode = TOP; + private float mMainFabAnimationRotateAngle = DEFAULT_ROTATE_ANGLE; + private boolean mUseReverseAnimationOnClose = false; + private ArrayList mSpeedDialActionItems = new ArrayList<>(); + + @Override + public boolean marshalling(Parcel dest) { + dest.writeByte(this.mIsOpen ? (byte) 1 : (byte) 0); + dest.writeInt(this.mMainFabClosedBackgroundColor); + dest.writeInt(this.mMainFabOpenedBackgroundColor); + dest.writeInt(this.mMainFabClosedIconColor); + dest.writeInt(this.mMainFabOpenedIconColor); + dest.writeInt(this.mExpansionMode); + dest.writeFloat(this.mMainFabAnimationRotateAngle); + dest.writeByte(this.mUseReverseAnimationOnClose ? (byte) 1 : (byte) 0); + dest.writeSequenceableList(this.mSpeedDialActionItems); + return true; + } + + @Override + public boolean unmarshalling(Parcel in) { + this.mIsOpen = in.readByte() != 0; + this.mMainFabClosedBackgroundColor = in.readInt(); + this.mMainFabOpenedBackgroundColor = in.readInt(); + this.mMainFabClosedIconColor = in.readInt(); + this.mMainFabOpenedIconColor = in.readInt(); + this.mExpansionMode = in.readInt(); + this.mMainFabAnimationRotateAngle = in.readFloat(); + this.mUseReverseAnimationOnClose = in.readByte() != 0; + this.mSpeedDialActionItems = in.createSequenceable(ClassLoader.getSystemClassLoader()); + return true; + } + + @Override + public boolean hasFileDescriptor() { + return false; + } + + public InstanceState() { + } + } + +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/UiUtils.java b/library/src/main/java/com/leinardi/ohos/speeddial/UiUtils.java new file mode 100644 index 0000000..f8ec9aa --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/UiUtils.java @@ -0,0 +1,263 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.animation.Animator; +import ohos.agp.animation.AnimatorProperty; +import ohos.agp.animation.AnimatorValue; +import ohos.agp.components.AttrHelper; +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.Image; +import ohos.agp.window.service.WindowManager; +import ohos.app.Context; + +public class UiUtils { + + private UiUtils() { + } + + /** + * 设置状态栏颜色 + * + * @param color 状态栏颜色 + */ + public static void setStatusBarColor(int color) { + WindowManager.getInstance().getTopWindow().get().setStatusBarColor(color); + } + + public static int getColor(Context context, int colorRes) { + int color = 0; + try { + color = context.getResourceManager().getElement(colorRes).getColor(); + } catch (Exception e) { + e.printStackTrace(); + } + return color; + } + + public static float getDimensionValue(Context context, int dimenRes) { + float value = 0; + try { + value = context.getResourceManager().getElement(dimenRes).getFloat(); + } catch (Exception e) { + e.printStackTrace(); + } + return value; + } + + public static int getPrimaryColor(Context context) { + int color = 0; + try { + color = context.getResourceManager().getElement(ResourceTable.Color_inbox_primary).getColor(); + } catch (Exception e) { + e.printStackTrace(); + } + return color; + } + + public static int getOnSecondaryColor(Context context) { + int color = 0; + try { + color = context.getResourceManager().getElement(ResourceTable.Color_material_white_1000).getColor(); + } catch (Exception e) { + e.printStackTrace(); + } + return color; + } + + public static int getAccentColor(Context context) { + int color = 0; + try { + color = context.getResourceManager().getElement(ResourceTable.Color_colorAccent).getColor(); + } catch (Exception e) { + e.printStackTrace(); + } + return color; + } + + public static int dpToPx(Context context, float dp) { + return AttrHelper.vp2px(dp, context); + } + + /** + * Fade out animation. + * + * @param view view to animate. + */ + public static void fadeOutAnim(final Component view) { + view.setAlpha(1F); + view.setVisibility(Component.VISIBLE); + fadeAnim(view, false); + } + + /** + * Fade in animation. + * + * @param view view to animate. + */ + public static void fadeInAnim(final Component view) { + view.setAlpha(0); + view.setVisibility(Component.VISIBLE); + fadeAnim(view, true); + } + + private static void fadeAnim(final Component view, boolean isFadeIn) { + AnimatorValue animatorValue = new AnimatorValue(); + animatorValue.setDuration(isFadeIn? + getConfigDuration(view.getContext(), ResourceTable.Integer_sd_open_animation_duration): + getConfigDuration(view.getContext(), ResourceTable.Integer_sd_close_animation_duration)); + animatorValue.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE); + animatorValue.setValueUpdateListener((animatorValue1, v) -> view.setAlpha(isFadeIn ? v : (1 - v))); + animatorValue.setStateChangedListener(new AnimStateChangedListener(){ + @Override + public void onEnd(Animator animator) { + if(!isFadeIn) { + view.setVisibility(Component.HIDE); + } + } + }); + animatorValue.start(); + } + + /** + * SpeedDial opening animation. + * + * @param view view that starts that animation. + * @param startOffset a delay in time to start the animation + */ + public static void enlargeAnim(Component view, long startOffset) { + view.setVisibility(Component.VISIBLE); + + AnimatorProperty property = new AnimatorProperty(); + property.setDuration(getConfigDuration(view.getContext(), ResourceTable.Integer_sd_open_animation_duration)); + property.setTarget(view); + property.setDelay(startOffset); + property.scaleXFrom(0.5f).scaleX(1f); + property.scaleYFrom(0.5f).scaleY(1f); + property.alphaFrom(0f).alpha(1f); + property.moveFromY(getDimensionValue(view.getContext(), ResourceTable.Float_fab_margin_top_and_bottom)).moveToY(0f); + property.start(); + } + + /** + * SpeedDial closing animation. + * + * @param view view that starts that animation. + * @param startOffset a delay in time to start the animation + */ + public static void shrinkAnim(final Component view, long startOffset) { + view.setVisibility(Component.VISIBLE); + + AnimatorProperty property = new AnimatorProperty(); + property.setDuration(getConfigDuration(view.getContext(), ResourceTable.Integer_sd_open_animation_duration)); + property.setTarget(view); + property.setDelay(startOffset); + property.scaleXFrom(1f).scaleX(0.5f); + property.scaleYFrom(1f).scaleY(0.5f); + property.alphaFrom(1f).alpha(0f); + property.moveFromY(0f).moveToY(getDimensionValue(view.getContext(), ResourceTable.Float_fab_margin_top_and_bottom)); + property.setStateChangedListener(new AnimStateChangedListener(){ + @Override + public void onEnd(Animator animator) { + view.setVisibility(Component.HIDE); + } + }); + property.start(); + } + + /** + * Closing animation. + * + * @param view view that starts that animation. + * @param removeView true to remove the view when the animation is over, false otherwise. + */ + public static void shrinkAnim(final Component view, final boolean removeView) { + try{ + AnimatorProperty property = new AnimatorProperty(); + property.setDuration(getConfigDuration(view.getContext(), ResourceTable.Integer_sd_close_animation_duration)); + property.setTarget(view); + property.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE); + property.alphaFrom(1.0f).alpha(0f); + property.setStateChangedListener(new AnimStateChangedListener() { + @Override + public void onEnd(Animator animator) { + if (removeView) { + ComponentContainer parent = (ComponentContainer) view.getComponentParent(); + if (parent != null) { + parent.removeComponent(view); + } + } else { + view.setVisibility(Component.HIDE); + } + } + }); + property.start(); + }catch (Exception e){ + e.printStackTrace(); + } + + } + + /** + * Rotate a view of the specified degrees. + * + * @param floatingActionButton The FloatingActionButton to rotate. + * @param animate true to animate the rotation, false to be instant. + * @see #rotateBackward(FloatingActionButton, float, boolean) + */ + public static void rotateForward(FloatingActionButton floatingActionButton, float angle, boolean animate) { + Image iconImage = floatingActionButton.getIconImage(); + iconImage.setRotation(-angle); + + AnimatorProperty property = new AnimatorProperty(); + property.setDuration(animate ? getConfigDuration(floatingActionButton.getContext(), ResourceTable.Integer_sd_rotate_animation_duration) : 0); + property.setTarget(iconImage); + property.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE); + property.rotate(0); + property.start(); + } + + /** + * Rotate a view back to its default angle (0°). + * + * @param floatingActionButton The FloatingActionButton to rotate. + * @param animate true to animate the rotation, false to be instant. + */ + public static void rotateBackward(FloatingActionButton floatingActionButton, float angle, boolean animate) { + Image iconImage = floatingActionButton.getIconImage(); + iconImage.setRotation(angle); + + AnimatorProperty property = new AnimatorProperty(); + property.setDuration(animate ? getConfigDuration(floatingActionButton.getContext(), ResourceTable.Integer_sd_rotate_animation_duration) : 0); + property.setTarget(iconImage); + property.setCurveType(Animator.CurveType.ACCELERATE_DECELERATE); + property.rotate(0); + property.start(); + } + + private static int getConfigDuration(Context context, int resId) { + int duration = 0; + try { + duration = context.getResourceManager().getElement(resId).getInteger(); + }catch (Exception e){ + e.printStackTrace(); + } + return duration; + } + +} diff --git a/library/src/main/java/com/leinardi/ohos/speeddial/ViewGroupUtils.java b/library/src/main/java/com/leinardi/ohos/speeddial/ViewGroupUtils.java new file mode 100644 index 0000000..91c9961 --- /dev/null +++ b/library/src/main/java/com/leinardi/ohos/speeddial/ViewGroupUtils.java @@ -0,0 +1,89 @@ +/* + * Copyright 2021 Roberto Leinardi. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.leinardi.ohos.speeddial; + +import ohos.agp.components.Component; +import ohos.agp.components.ComponentContainer; +import ohos.agp.components.ComponentParent; +import ohos.agp.utils.Matrix; +import ohos.agp.utils.Rect; +import ohos.agp.utils.RectFloat; + +class ViewGroupUtils { + private static final ThreadLocal MATRIX_THREAD_LOCAL = new ThreadLocal<>(); + private static final ThreadLocal RECT_F = new ThreadLocal<>(); + + private ViewGroupUtils() { + } + + /** + * This is a port of the common + * from the framework, but adapted to take transformations into account. The result + * will be the bounding rect of the real transformed rect. + * + * @param descendant view defining the original coordinate system of rect + * @param rect (in/out) the rect to offset from descendant to this view's coordinate system + */ + static void offsetDescendantRect(ComponentContainer parent, Component descendant, Rect rect) { + Matrix m = MATRIX_THREAD_LOCAL.get(); + if (m == null) { + m = new Matrix(); + MATRIX_THREAD_LOCAL.set(m); + } else { + m.reset(); + } + + offsetDescendantMatrix(parent, descendant, m); + + RectFloat rectF = RECT_F.get(); + if (rectF == null) { + rectF = new RectFloat(); + RECT_F.set(rectF); + } + rectF.modify(rect); + m.mapRect(rectF); + rect.set((int) (rectF.left + 0.5f), (int) (rectF.top + 0.5f), + (int) (rectF.right + 0.5f), (int) (rectF.bottom + 0.5f)); + } + + /** + * Retrieve the transformed bounding rect of an arbitrary descendant view. + * This does not need to be a direct child. + * + * @param descendant descendant view to reference + * @param out rect to set to the bounds of the descendant view + */ + static void getDescendantRect(ComponentContainer parent, Component descendant, Rect out) { + out.set(0, 0, descendant.getWidth(), descendant.getHeight()); + offsetDescendantRect(parent, descendant, out); + } + + private static void offsetDescendantMatrix(ComponentContainer target, Component view, Matrix m) { + final ComponentParent parent = view.getComponentParent(); + if (parent instanceof Component && parent != target) { + final Component vp = (Component) parent; + offsetDescendantMatrix(target, vp, m); + m.preTranslate(-vp.getScrollValue(Component.AXIS_X), -vp.getScrollValue(Component.AXIS_Y)); + } + + m.preTranslate(view.getLeft(), view.getTop()); + +// if (!view.getMatrix().isIdentity()) { +// m.preConcat(view.getMatrix()); +// } + } +} diff --git a/library/src/test/java/com/leinardi/ohos/speeddial/ExampleTest.java b/library/src/test/java/com/leinardi/ohos/speeddial/ExampleTest.java new file mode 100644 index 0000000..f409ece --- /dev/null +++ b/library/src/test/java/com/leinardi/ohos/speeddial/ExampleTest.java @@ -0,0 +1,9 @@ +package com.leinardi.ohos.speeddial; + +import org.junit.Test; + +public class ExampleTest { + @Test + public void onStart() { + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} -- Gitee