diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_item_pattern.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_item_pattern.h index 607c7e3af85..28b0f5ce45d 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_item_pattern.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_item_pattern.h @@ -39,6 +39,11 @@ public: return true; } + FocusPattern GetFocusPattern() const override + { + return { FocusType::SCOPE, true }; + } + OPINC_TYPE_E OpIncType() override { return OPINC_NODE; diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp index 61ecf84078e..36687b41719 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.cpp @@ -674,11 +674,10 @@ void WaterFlowPattern::DumpAdvanceInfo() property->IsReverse() ? DumpLog::GetInstance().AddDesc("isReverse:true") : DumpLog::GetInstance().AddDesc("isReverse:false"); info->itemStart_ ? DumpLog::GetInstance().AddDesc("itemStart:true") - : DumpLog::GetInstance().AddDesc("itemStart:false"); - info->itemEnd_ ? DumpLog::GetInstance().AddDesc("itemEnd:true") - : DumpLog::GetInstance().AddDesc("itemEnd:false"); + : DumpLog::GetInstance().AddDesc("itemStart:false"); + info->itemEnd_ ? DumpLog::GetInstance().AddDesc("itemEnd:true") : DumpLog::GetInstance().AddDesc("itemEnd:false"); info->offsetEnd_ ? DumpLog::GetInstance().AddDesc("offsetEnd:true") - : DumpLog::GetInstance().AddDesc("offsetEnd:false"); + : DumpLog::GetInstance().AddDesc("offsetEnd:false"); footer_.Upgrade() ? DumpLog::GetInstance().AddDesc("footer:true") : DumpLog::GetInstance().AddDesc("footer:false"); property->GetItemMinSize().has_value() @@ -692,7 +691,7 @@ void WaterFlowPattern::DumpAdvanceInfo() DumpLog::GetInstance().AddDesc("-----------start print sections_------------"); std::string res = std::string(""); int32_t index = 0; - for (auto §ion : sections_->GetSectionInfo()) { + for (auto& section : sections_->GetSectionInfo()) { res.append("[section:" + std::to_string(index) + "]"); res.append("{ itemCount:" + std::to_string(section.itemsCount) + " },") .append("{ crossCount:" + std::to_string(section.crossCount.value_or(1)) + " },") @@ -706,4 +705,78 @@ void WaterFlowPattern::DumpAdvanceInfo() DumpLog::GetInstance().AddDesc("-----------end print sections_------------"); } } + +ScopeFocusAlgorithm WaterFlowPattern::GetScopeFocusAlgorithm() +{ + return { layoutInfo_->axis_ == Axis::VERTICAL, true, ScopeType::OTHERS, + [wp = WeakClaim(this)]( + FocusStep step, const WeakPtr& currFocusNode, WeakPtr& nextFocusNode) { + auto self = wp.Upgrade(); + if (self) { + nextFocusNode = self->GetNextFocusNode(step, currFocusNode); + } + } }; +} + +WeakPtr WaterFlowPattern::GetNextFocusNode(FocusStep step, const WeakPtr& currentFocusNode) +{ + auto cur = currentFocusNode.Upgrade(); + CHECK_NULL_RETURN(cur, nullptr); + auto host = GetHost(); + CHECK_NULL_RETURN(host, nullptr); + int32_t curIdx = host->GetChildTrueIndex(cur->GetFrameNode()); + int32_t diff = 0; + switch (step) { + case FocusStep::DOWN: + case FocusStep::DOWN_END: + case FocusStep::RIGHT: + case FocusStep::RIGHT_END: + case FocusStep::TAB: + diff = 1; + break; + case FocusStep::LEFT: + case FocusStep::LEFT_END: + case FocusStep::UP: + case FocusStep::UP_END: + case FocusStep::SHIFT_TAB: + diff = -1; + break; + default: + return currentFocusNode; + } + int32_t idx = curIdx + diff; + int32_t footerOffset = layoutInfo_->footerIndex_ + 1; // 1 if footer present, 0 if not + while (idx - footerOffset >= 0 && idx < GetChildrenCount()) { + int32_t itemIdx = idx - footerOffset; + if (itemIdx >= layoutInfo_->endIndex_ || itemIdx <= layoutInfo_->startIndex_) { + ScrollToIndex(itemIdx, false, ScrollAlign::AUTO); + host->SetActive(); + host->CreateLayoutTask(); + } + auto next = host->GetChildByIndex(idx); + CHECK_NULL_RETURN(next, nullptr); + auto focus = next->GetHostNode()->GetFocusHub(); + if (focus && focus->IsFocusable()) { + return focus; + } + idx += diff; + } + return nullptr; +} + +std::function WaterFlowPattern::GetScrollIndexAbility() +{ + return [wp = WeakClaim(this)](int32_t index) -> bool { + auto self = wp.Upgrade(); + CHECK_NULL_RETURN(self, false); + if (index == FocusHub::SCROLL_TO_HEAD) { + self->ScrollToEdge(ScrollEdgeType::SCROLL_TOP, false); + } else if (index == FocusHub::SCROLL_TO_TAIL) { + self->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + } else { + self->ScrollToIndex(index, false, ScrollAlign::AUTO); + } + return true; + }; +} } // namespace OHOS::Ace::NG diff --git a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h index 6153fae0210..ccee5207c18 100644 --- a/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h +++ b/frameworks/core/components_ng/pattern/waterflow/water_flow_pattern.h @@ -17,11 +17,11 @@ #define FOUNDATION_ACE_FRAMEWORKS_CORE_COMPONENTS_NG_PATTERNS_WATERFLOW_WATER_FLOW_PATTERN_H #include "core/components_ng/pattern/scrollable/scrollable_pattern.h" +#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_algorithm_base.h" +#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h" #include "core/components_ng/pattern/waterflow/water_flow_accessibility_property.h" #include "core/components_ng/pattern/waterflow/water_flow_content_modifier.h" #include "core/components_ng/pattern/waterflow/water_flow_event_hub.h" -#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_algorithm_base.h" -#include "core/components_ng/pattern/waterflow/layout/water_flow_layout_info_base.h" #include "core/components_ng/pattern/waterflow/water_flow_layout_property.h" #include "core/components_ng/pattern/waterflow/water_flow_sections.h" @@ -72,7 +72,7 @@ public: } void TriggerModifyDone(); - + RefPtr CreateNodePaintMethod() override; bool UpdateStartIndex(int32_t index); @@ -148,11 +148,18 @@ public: * @param start the index of the first modified section. */ void OnSectionChanged(int32_t start); - void OnSectionChangedNow(int32_t start); void DumpAdvanceInfo() override; + // ------------------------ Focus adapter -------------------------------- + FocusPattern GetFocusPattern() const override + { + return { FocusType::SCOPE, true }; + } + ScopeFocusAlgorithm GetScopeFocusAlgorithm() override; + std::function GetScrollIndexAbility() override; + // ------------------------ Focus ^^^ -------------------------------- private: DisplayMode GetDefaultScrollBarDisplayMode() const override { @@ -171,6 +178,14 @@ private: void OnScrollEndCallback() override; bool ScrollToTargetIndex(int32_t index); bool NeedRender(); + + /** + * @param step FocusStep + * @param currentFocusNode the currently focused FlowItem. + * @return WeakPtr of the next FlowItem to focus on. + */ + WeakPtr GetNextFocusNode(FocusStep step, const WeakPtr& currentFocusNode); + std::optional targetIndex_; RefPtr layoutInfo_ = WaterFlowLayoutInfoBase::Create(LayoutMode::TOP_DOWN); RefPtr sections_; diff --git a/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp b/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp index 6a2ee025714..61f54242eba 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_scroller_test_ng.cpp @@ -539,4 +539,53 @@ HWTEST_F(WaterFlowScrollerTestNg, ScrollToIndex003, TestSize.Level1) FlushLayoutTask(frameNode_); EXPECT_FLOAT_EQ(pattern_->finalPosition_, 2100.f); } + +/** + * @tc.name: Focus001 + * @tc.desc: Test WaterFlow scroll during focus change + * @tc.type: FUNC + */ +HWTEST_F(WaterFlowScrollerTestNg, Focus001, TestSize.Level1) +{ + Create([](WaterFlowModelNG model) { + model.SetColumnsTemplate("1fr 1fr"); + CreateFocusableItem(30); + }); + + auto next = pattern_->GetNextFocusNode(FocusStep::DOWN, GetChildFocusHub(frameNode_, 5)).Upgrade(); + auto cmp = GetChildFocusHub(frameNode_, 6); + EXPECT_EQ(AceType::RawPtr(next), AceType::RawPtr(cmp)); + + cmp = GetChildFocusHub(frameNode_, 4); + next = pattern_->GetNextFocusNode(FocusStep::UP, GetChildFocusHub(frameNode_, 5)).Upgrade(); + EXPECT_EQ(AceType::RawPtr(next), AceType::RawPtr(cmp)); + auto info = pattern_->layoutInfo_; + + EXPECT_EQ(info->startIndex_, 0); + EXPECT_EQ(info->endIndex_, 10); + + next = pattern_->GetNextFocusNode(FocusStep::LEFT, GetChildFocusHub(frameNode_, 0)).Upgrade(); + EXPECT_FALSE(next); + + EXPECT_EQ(info->startIndex_, 0); + EXPECT_EQ(info->endIndex_, 10); + next = pattern_->GetNextFocusNode(FocusStep::RIGHT, GetChildFocusHub(frameNode_, 10)).Upgrade(); + EXPECT_EQ(GetChildRect(frameNode_, 11).Bottom(), WATERFLOW_HEIGHT); + cmp = GetChildFocusHub(frameNode_, 11); + EXPECT_EQ(AceType::RawPtr(next), AceType::RawPtr(cmp)); + + pattern_->ScrollToEdge(ScrollEdgeType::SCROLL_BOTTOM, false); + FlushLayoutTask(frameNode_); + next = pattern_->GetNextFocusNode(FocusStep::LEFT, GetChildFocusHub(frameNode_, 29)).Upgrade(); + cmp = GetChildFocusHub(frameNode_, 28); + EXPECT_EQ(AceType::RawPtr(next), AceType::RawPtr(cmp)); + next = pattern_->GetNextFocusNode(FocusStep::DOWN_END, GetChildFocusHub(frameNode_, 29)).Upgrade(); + EXPECT_FALSE(next); + + EXPECT_EQ(info->startIndex_, 19); + next = pattern_->GetNextFocusNode(FocusStep::UP_END, GetChildFocusHub(frameNode_, info->startIndex_)).Upgrade(); + cmp = GetChildFocusHub(frameNode_, 18); + EXPECT_EQ(AceType::RawPtr(next), AceType::RawPtr(cmp)); + EXPECT_EQ(GetChildY(frameNode_, 18), 0.0f); +} } // namespace OHOS::Ace::NG diff --git a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp index 1c4b47eea6b..d4f10cfe3f3 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.cpp @@ -36,6 +36,7 @@ #include "core/components/button/button_theme.h" #include "core/components/common/layout/constants.h" #include "core/components_ng/base/view_stack_processor.h" +#include "core/components_ng/pattern/button/button_model_ng.h" #include "core/components_ng/pattern/linear_layout/row_model_ng.h" #include "core/components_ng/pattern/scrollable/scrollable.h" #include "core/components_ng/pattern/waterflow/water_flow_accessibility_property.h" @@ -142,6 +143,29 @@ void WaterFlowTestNg::CreateItem(int32_t number) } } +void WaterFlowTestNg::CreateFocusableItem(int32_t number) +{ + for (int32_t i = 0; i < number; i++) { + WaterFlowItemModelNG waterFlowItemModel; + waterFlowItemModel.Create(); + ViewAbstract::SetWidth(CalcLength(FILL_LENGTH)); + // set irregular height + int32_t two = 2; + if (i % two == 0) { + ViewAbstract::SetHeight(CalcLength(Dimension(ITEM_HEIGHT))); + } else { + ViewAbstract::SetHeight(CalcLength(Dimension(BIG_ITEM_HEIGHT))); + } + { + ButtonModelNG buttonModelNG; + std::list> buttonChildren; + buttonModelNG.CreateWithLabel({ .label = "label" }, buttonChildren); + ViewStackProcessor::GetInstance()->Pop(); + } + ViewStackProcessor::GetInstance()->Pop(); + } +} + void WaterFlowTestNg::CreateRandomItem(int32_t number) { for (int32_t i = 0; i < number; i++) { diff --git a/test/unittest/core/pattern/waterflow/water_flow_test_ng.h b/test/unittest/core/pattern/waterflow/water_flow_test_ng.h index 817ae576cf2..38d1f585e6d 100644 --- a/test/unittest/core/pattern/waterflow/water_flow_test_ng.h +++ b/test/unittest/core/pattern/waterflow/water_flow_test_ng.h @@ -58,6 +58,7 @@ protected: void CreateWithItem(const std::function& callback = nullptr); static void CreateItem(int32_t number = 10); static void CreateRandomItem(int32_t number); + static void CreateFocusableItem(int32_t number); static void CreateItemWithHeight(float height); void UpdateCurrentOffset(float offset, int32_t source = SCROLL_FROM_UPDATE); void MouseSelect(Offset start, Offset end);