Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,32 +22,45 @@ import com.tencent.kuikly.compose.ui.node.requireLayoutNode
import com.tencent.kuikly.core.views.ScrollerView

fun Modifier.bouncesEnable(
enable: Boolean
): Modifier = this.then(BouncesEnableElement(enable))
enable: Boolean,
limitHeaderBounces: Boolean = false,
limitFooterBounces: Boolean = false
): Modifier = this.then(BouncesEnableElement(enable, limitHeaderBounces, limitFooterBounces))

private class BouncesEnableElement(
val bouncesEnable: Boolean
val bouncesEnable: Boolean,
val limitHeaderBounces: Boolean,
val limitFooterBounces: Boolean
) : ModifierNodeElement<BouncesEnableNode>() {
override fun create(): BouncesEnableNode = BouncesEnableNode(bouncesEnable)
override fun create(): BouncesEnableNode = BouncesEnableNode(bouncesEnable, limitHeaderBounces, limitFooterBounces)

override fun hashCode(): Int {
return bouncesEnable.hashCode()
var result = bouncesEnable.hashCode()
result = 31 * result + limitHeaderBounces.hashCode()
result = 31 * result + limitFooterBounces.hashCode()
return result
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is BouncesEnableElement) return false
return bouncesEnable == other.bouncesEnable
return bouncesEnable == other.bouncesEnable &&
limitHeaderBounces == other.limitHeaderBounces &&
limitFooterBounces == other.limitFooterBounces
}

override fun update(node: BouncesEnableNode) {
node.bouncesEnable = bouncesEnable
node.limitHeaderBounces = limitHeaderBounces
node.limitFooterBounces = limitFooterBounces
node.update()
}
}

private class BouncesEnableNode(
var bouncesEnable: Boolean
var bouncesEnable: Boolean,
var limitHeaderBounces: Boolean,
var limitFooterBounces: Boolean
) : Modifier.Node() {

override fun onAttach() {
Expand All @@ -60,7 +73,7 @@ private class BouncesEnableNode(
val kNode = layoutNode as? KNode<*> ?: return
val scrollerView = kNode.view as? ScrollerView<*, *> ?: return
scrollerView.getViewAttr().run {
bouncesEnable(bouncesEnable)
bouncesEnable(bouncesEnable, limitHeaderBounces, limitFooterBounces)
}
}
}

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ internal class OverScrollHandler(
!recyclerView.limitHeaderBounces && isInStart() && (offset > 0 || currentTranslation > 0)

private fun needInEndTranslate(offset: Float, currentTranslation: Float): Boolean =
isInEnd() && (offset < 0 || currentTranslation < 0)
!recyclerView.limitFooterBounces && isInEnd() && (offset < 0 || currentTranslation < 0)

private fun processPointerUpEVent(activeIndex: Int, event: MotionEvent): Boolean {
pointerDataMap.remove(event.getPointerId(activeIndex))
Expand Down
11 changes: 11 additions & 0 deletions core-render-ios/Extension/Components/KRScrollView.m
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ @interface KRScrollView()<UIScrollViewDelegate, KRScrollViewOffsetAnimatorDelega
@property (nonatomic, strong) NSNumber *KUIKLY_PROP(dynamicSyncScrollDisable);
/** attr is minContentOffset */
@property (nonatomic, strong) NSNumber *KUIKLY_PROP(limitHeaderBounces);
/** attr is limitFooterBounces */
@property (nonatomic, strong) NSNumber *KUIKLY_PROP(limitFooterBounces);
/** attr nestedScroll */
@property (nonatomic, strong) NSString *KUIKLY_PROP(nestedScroll);
/** event is scroll */
Expand Down Expand Up @@ -184,6 +186,15 @@ - (void)setContentOffset:(CGPoint)contentOffset {
contentOffset = CGPointMake(contentOffset.x, MAX(contentOffset.y, 0));
}
}
if ([_css_limitFooterBounces boolValue]) { // 禁止底部回弹
if ([_css_directionRow boolValue]) {
CGFloat maxOffsetX = MAX(0, self.contentSize.width - CGRectGetWidth(self.frame));
contentOffset = CGPointMake(MIN(contentOffset.x, maxOffsetX), contentOffset.y);
} else {
CGFloat maxOffsetY = MAX(0, self.contentSize.height - CGRectGetHeight(self.frame));
contentOffset = CGPointMake(contentOffset.x, MIN(contentOffset.y, maxOffsetY));
}
}
[super setContentOffset:contentOffset];
[self p_dispatchScrollEventIfNeed];
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ constexpr char kPropNameVerticalBounces[] = "verticalbounces";
constexpr char kPropNameHorizontalBounces[] = "horizontalbounces";
constexpr char kPropNameBouncesEnable[] = "bouncesEnable";
constexpr char kPropNameLimitHeaderBounces[] = "limitHeaderBounces";
constexpr char kPropNameLimitFooterBounces[] = "limitFooterBounces";
constexpr char kPropNameShowScrollerIndicator[] = "showScrollerIndicator";
constexpr char kPropNameNestedScroll[] = "nestedScroll";
constexpr char kPropNameFlingEnable[] = "flingEnable";
Expand Down Expand Up @@ -187,6 +188,8 @@ bool KRScrollerView::SetProp(const std::string &prop_key, const KRAnyValue &prop
didHanded = RegisterWillDragEndEvent(event_call_back);
} else if (kuikly::util::isEqual(prop_key, kPropNameLimitHeaderBounces)) {
didHanded = SetLimitHeaderBounces(prop_value);
} else if (kuikly::util::isEqual(prop_key, kPropNameLimitFooterBounces)) {
didHanded = SetLimitFooterBounces(prop_value);
} else if (kuikly::util::isEqual(prop_key, kPropNameNestedScroll)) {
didHanded = SetNestedScroll(prop_value);
} else if (kuikly::util::isEqual(prop_key, kPropNameFlingEnable)) {
Expand Down Expand Up @@ -361,6 +364,12 @@ bool KRScrollerView::SetLimitHeaderBounces(const KRAnyValue &value) {
return true;
}

bool KRScrollerView::SetLimitFooterBounces(const KRAnyValue &value) {
limit_footer_bounces_ = value->toBool();
RegisterEvent(NODE_SCROLL_EVENT_ON_SCROLL_EDGE);
return true;
}

bool KRScrollerView::SetShowScrollerIndicator(const KRAnyValue &value) {
auto enable = value->toBool();
kuikly::util::SetArkUIShowScrollerIndicator(GetNode(), enable);
Expand Down Expand Up @@ -513,6 +522,7 @@ void KRScrollerView::OnScrollStop(ArkUI_NodeEvent *event) {

void KRScrollerView::OnWillScroll(ArkUI_NodeEvent *event) {
AdjustHeaderBouncesEnableWhenWillScroll(event);
AdjustFooterBouncesEnableWhenWillScroll(event);

auto new_scroll_state = kuikly::util::GetArkUIScrollerState(event, 2);
if (new_scroll_state == current_scroll_state_) {
Expand Down Expand Up @@ -610,10 +620,50 @@ void KRScrollerView::AdjustHeaderBouncesEnableWhenWillScroll(ArkUI_NodeEvent *ev
return;
}
auto content_offset = kuikly::util::GetArkUIScrollContentOffset(GetNode());
// 只处理顶部的情况,底部的情况由 AdjustFooterBouncesEnableWhenWillScroll 处理
if (content_offset.y <= 0) {
InnerSetBouncesEnable(false);
} else if (!limit_footer_bounces_) {
// 如果底部没有限制,且不在顶部,则恢复回弹
InnerSetBouncesEnable(bounces_enabled_);
}
// 如果底部有限制,让 AdjustFooterBouncesEnableWhenWillScroll 来决定
}

void KRScrollerView::AdjustFooterBouncesEnableWhenWillScroll(ArkUI_NodeEvent *event) {
if (!limit_footer_bounces_) {
return;
}
if (!content_view_) {
return;
}
auto content_offset = kuikly::util::GetArkUIScrollContentOffset(GetNode());
auto frame = GetFrame();
auto content_view_frame = content_view_->GetFrame();

// 计算是否到达底部
bool isReachEnd = false;
if (content_view_frame.height > frame.height) {
// 垂直滚动
float maxOffsetY = content_view_frame.height - frame.height;
isReachEnd = content_offset.y >= maxOffsetY;
} else if (content_view_frame.width > frame.width) {
// 横向滚动
float maxOffsetX = content_view_frame.width - frame.width;
isReachEnd = content_offset.x >= maxOffsetX;
}

if (isReachEnd) {
InnerSetBouncesEnable(false);
} else {
InnerSetBouncesEnable(true);
// 如果不在底部,检查是否在顶部(顶部限制优先)
if (limit_header_bounces_ && content_offset.y <= 0) {
// 顶部限制生效,保持禁用
InnerSetBouncesEnable(false);
} else {
// 既不在顶部也不在底部,恢复回弹
InnerSetBouncesEnable(bounces_enabled_);
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ class KRScrollerView : public IKRRenderViewExport {
bool SetPagingEnabled(const KRAnyValue &value);
bool SetBouncesEnable(const KRAnyValue &value);
bool SetLimitHeaderBounces(const KRAnyValue &value);
bool SetLimitFooterBounces(const KRAnyValue &value);
bool SetShowScrollerIndicator(const KRAnyValue &value);
bool RegisterOnScrollEvent(const KRRenderCallback event_call_back);
bool RegisterOnDragBeginEvent(const KRRenderCallback event_callback);
Expand Down Expand Up @@ -127,6 +128,7 @@ class KRScrollerView : public IKRRenderViewExport {
void ApplyContentInsetWhenDragEnd();
void InnerSetBouncesEnable(bool enable);
void AdjustHeaderBouncesEnableWhenWillScroll(ArkUI_NodeEvent *event);
void AdjustFooterBouncesEnableWhenWillScroll(ArkUI_NodeEvent *event);
void DispatchDidScrollToObservers(KRPoint point);
bool SetFlingEnable(bool enable);

Expand All @@ -139,6 +141,7 @@ class KRScrollerView : public IKRRenderViewExport {
std::shared_ptr<KRScrollerContentView> content_view_;
bool bounces_enabled_ = true;
bool limit_header_bounces_ = false;
bool limit_footer_bounces_ = false;
bool current_bounces_enabled_ = false;
bool is_dragging_ = false;
bool is_set_frame_ = false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -369,15 +369,38 @@ open class ScrollerAttr : ContainerAttr() {
fun scrollEnable(value: Boolean) {
SCROLL_ENABLED with value.toInt()
}
/*
* 是否允许边界回弹效果
* @param bouncesEnable 是否允许边界回弹
* @param limitHeaderBounces 是否禁止顶部回弹(如bouncesEnable为false,该值就无效)

/**
* 设置是否允许边界回弹效果,以及是否限制顶部或底部的回弹。
*
* 当 [bouncesEnable] 为 `false` 时,整个滚动视图将禁用回弹效果,此时 [limitHeaderBounces] 和 [limitFooterBounces] 参数将无效。
* 当 [bouncesEnable] 为 `true` 时,可以通过 [limitHeaderBounces] 和 [limitFooterBounces] 参数分别控制顶部和底部的回弹行为。
*
* @param bouncesEnable 是否允许边界回弹效果。默认为 `true`。
* @param limitHeaderBounces 是否禁止顶部回弹。当滚动到顶部时,如果此参数为 `true`,则禁止向上拖拽时的回弹效果。
* 注意:如果 [bouncesEnable] 为 `false`,该参数将无效。默认为 `false`。
* @param limitFooterBounces 是否禁止底部回弹。当滚动到底部时,如果此参数为 `true`,则禁止向下拖拽时的回弹效果。
* 注意:如果 [bouncesEnable] 为 `false`,该参数将无效。默认为 `false`。
*
* @sample 示例:启用回弹但禁止顶部和底部回弹
* ```
* scrollerView.attr {
* bouncesEnable(true, limitHeaderBounces = true, limitFooterBounces = true)
* }
* ```
*
* @sample 示例:完全禁用回弹效果
* ```
* scrollerView.attr {
* bouncesEnable(false)
* }
* ```
*/
fun bouncesEnable(bouncesEnable: Boolean, limitHeaderBounces: Boolean = false) {
fun bouncesEnable(bouncesEnable: Boolean, limitHeaderBounces: Boolean = false, limitFooterBounces: Boolean = false) {
this.bouncesEnable = bouncesEnable
BOUNCES_ENABLE with bouncesEnable.toInt()
LIMIT_BOUNCES_ENABLE with limitHeaderBounces.toInt()
LIMIT_FOOTER_BOUNCES with limitFooterBounces.toInt()
}
// 是否显示滚动指示进度条(默认显示)
fun showScrollerIndicator(value: Boolean) {
Expand Down Expand Up @@ -449,6 +472,7 @@ open class ScrollerAttr : ContainerAttr() {
const val SCROLL_ENABLED = "scrollEnabled"
const val BOUNCES_ENABLE = "bouncesEnable"
const val LIMIT_BOUNCES_ENABLE = "limitHeaderBounces"
const val LIMIT_FOOTER_BOUNCES = "limitFooterBounces"
const val SHOW_SCROLLER_INDICATOR = "showScrollerIndicator"
const val PAGING_ENABLED = "pagingEnabled"
const val DIRECTION_ROW = "directionRow"
Expand Down
Loading