ohos扩展包—— ScrollHelper

jacksky
发布于 2021-9-27 18:36
浏览
0收藏

滚动逻辑重写帮助类ScrollHelper

由于在ohos中,我们无法重写系统控件的触摸事件,只能通过setTouchListener来设置一个回调,这意味着我们无法侵入到ohos系统控件原有的滚动处理逻辑内。因此我们如果需要实现NestedScrollingChild接口,就需要重写控件的滚动逻辑。

ScrollHelper类封装了一套比较通用的滚动处理逻辑,并且内部维护了NestedScrollingChildHelper实例对象,自定义控件只需要实现ScrollingView与NestedScrollingChild等接口,并转发NestedScrollingChild中的函数至ScrollHelper的同名函数即可重写滚动逻辑。

注意:ScrollHelper仅仅处理滚动相关的逻辑,比如事件分发、触摸事件处理、嵌套滚动,其主要作用是帮助现有的可滚动的组件实现嵌套滚动。如果想要实现滚动本身,可以借助ScrollApiHelper

使用

  1. 实现ViewGroup、ScrollingView与NestedScrollingChild接口
    public class NestedListContainer extends ListContainer
            implements Component.TouchEventListener, ViewGroup,
            NestedScrollingChild, ScrollingView ​
  2. 初始化ScrollHelper
private final ScrollHelper helper;
helper = new ScrollHelper(this);

3、设置setTouchEventListener,并转发ohos的onTouchEvent至ScrollHelper中

setTouchEventListener(this);

@Override
public boolean onTouchEvent(Component component, TouchEvent ev) {
    return helper.onTouchEvent(component, ev);
}

4、转发ViewGroup接口至ScrollHelper的同名函数中:

   // ViewGroup

    @Override
    public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
        helper.requestDisallowInterceptTouchEvent(disallowIntercept);
    }

    @Override
    public boolean onInterceptTouchEvent(TouchEvent ev) {
        return helper.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean dispatchTouchEvent(TouchEvent ev) {
        return helper.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(TouchEvent ev) {
        return helper.onTouchEvent(ev);
    }

    @Override
    public boolean isConsumed() {
        return helper.isConsumed();
    }

5、转发NestedScrollingChild至ScrollHelper的同名函数中:

   // NestedScrollingChild

    @Override
    public void setNestedScrollingEnabled(boolean enabled) {
        helper.setNestedScrollingEnabled(enabled);
    }

    @Override
    public boolean isNestedScrollingEnabled() {
        return helper.isNestedScrollingEnabled();
    }

    @Override
    public boolean startNestedScroll(int axes, int type) {
        return helper.startNestedScroll(axes, type);
    }

    @Override
    public void stopNestedScroll(int type) {
        helper.stopNestedScroll(type);
    }

    @Override
    public boolean hasNestedScrollingParent(int type) {
        return helper.hasNestedScrollingParent(type);
    }

    @Override
    public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow, int type, int[] consumed) {
        return helper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed,
                offsetInWindow, type, consumed);
    }

    @Override
    public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow, int type) {
        return helper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type);
    }

    @Override
    public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
        return helper.dispatchNestedFling(velocityX, velocityY, consumed);
    }

    @Override
    public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
        return helper.dispatchNestedPreFling(velocityX, velocityY);
    }

6、实现ScrollView逻辑,具体含义可以参考该接口的注释。

    // ScrollingView

    @Override
    public ComponentContainer getContainer() {
        return this;
    }

    @Override
    public int getScrollY() {
        return scrollY;
    }

    @Override
    public int getScrollX() {
        return scrollX;
    }

    @Override
    public int getDirection() {
        return getOrientation() + 1;
    }

    @Override
    public void scrollByInternal(int deltaX, int deltaY) {
        // 需要自行计算scrollX、scrollY,因为有时候getScrollValue函数返回有异常
        if (getDirection() == NestedScrollingHelper.SCROLL_AXIS_VERTICAL) {
            scrollY += deltaY;
            if (scrollY < 0) {
                scrollY = 0;
            }
        } else {
            scrollX += deltaX;
            if (scrollX < 0) {
                scrollX = 0;
            }
        }
        scrollTo(scrollX, scrollY);

        if (scrollY > getScrollValue(VERTICAL)) {
            scrollY = getScrollValue(VERTICAL);
        }

        if (scrollX > getScrollValue(HORIZONTAL)) {
            scrollX = getScrollValue(HORIZONTAL);
        }
    }

    @Override
    public boolean disallowInterceptWhenMoving() {
        return true;
    }

 

滚动api帮助类 ScrollApiHelper

由于鸿蒙中普通控件与布局的scrollBy、scrollTo函数不生效,因此使用此类帮助控件滚动

使用

ScrollApiHelper scrollApiHelper = new ScrollApiHelper(this);

// 滚动
scrollApiHelper.scrollTo(x, y);
scrollApiHelper.scrollBy(deltaX, deltaY);

// 获取当前的滚动距离
scrollApiHelper.getScrollX();
scrollApiHelper.getScrollY();

// 设置滚动边界
scrollApiHelper.setScrollRangeX();
scrollApiHelper.setScrollRangeY();

// 如果想要scroll到负数
scrollApiHelper.setMinScrollRangeX(-100);
scrollApiHelper.setMinScrollRangeY(-100);

原理

通过将该控件的所有child通过setComponentPosition改变位置,达到整体滚动的效果。

  • child的top:子控件的初始top + scrollY
  • child的left:子控件的初始left + scrollX

scrollApiHelper会缓存所有child的初始位置信息,并于当前的滚动距离相加,计算得出child的新的top或者left并设置进去。

注意

由于是通过setComponentPosition来实现的滚动,因此获取child的top、left、right、bottom等会出现数值不是预期的情况,真实的值应该是:

  • top: Component.getTop + ScrollApiHelper.getScrollY
  • left: Component.getLeft + ScrollApiHelper.getScrollX
  • right: Component.getRight + ScrollApiHelper.getScrollX
  • bottom: Component.getBottom + ScrollApiHelper.getScrollY

关于onArrange

由于是使用setComponentPosition来实现滚动,而非控件内的scroll来实现,因此在控件发生重新布局后,滚动会跳到最顶部。这是因为onArrange会把所有子控件的位置还原。

如果我们自定义了控件,并且确定在滚动途中会发生重新布局,或者调用了postLayout或者invalidate函数,那么我们可以通过在onArrange内,将child的top或者left加上我们当前的滚动距离,就可以达到滚动不往顶部跳的目的。

但是这样会产生另外一个问题就是滚动值的计算会出现问题,因为所有child的初始top或者left已经发生了改变。这时我们需要为ScrollApiHelper设置一个偏移量,来中和初始top或者left的改变,并且重新缓存child的位置信息。

即在onArrange内:

  1. 将child的top或者left加上我们当前的滚动距离
  2. 为ScrollApiHelper设置一个偏移量,值为当前的滚动距离
  3. 重新缓存child的位置信息

以垂直方向滚动举例(不完整代码):

@Override
public boolean onArrange(int l, int t, int width, int height) {
    int childTop;
    int scrollY = scrollApiHelper.getScrollY();
    for(...) {
        int childHeight = child.getEstimatedHeight();
        child.arrange(0, childTop + scrollY, child.getEstimatedWidth(), childHeight);
        childTop += childHeight;
    }

    scrollApiHelper.resetSavedPositions();
    scrollApiHelper.setOffsetToScrollY(scrollY);
}

已于2021-9-27 18:36:56修改
收藏
回复
举报
回复
    相关推荐