如何使用 SeekBar 正确滚动 ScrollView

How to properly scroll ScrollView with a SeekBar

我正在尝试创建一个自定义的垂直 SeekBar,它将用于在 ScrollView 中滚动文本。这是 SeekBar:

class CustomSeekBar : SeekBar {

constructor(context: Context) : super(context) {}

constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}

constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(h, w, oldh, oldw)
}

@Synchronized
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(heightMeasureSpec, widthMeasureSpec)
    setMeasuredDimension(measuredHeight, measuredWidth)
}

override fun onDraw(c: Canvas) {

    c.rotate(90f)
    c.translate(0f, -width.toFloat())

    super.onDraw(c)
}

override fun onTouchEvent(event: MotionEvent): Boolean {

    super.onTouchEvent(event)
    if (!isEnabled) {
        return false
    }

    when (event.action) {
        MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE, MotionEvent.ACTION_UP -> {

            val i = (max - (max * event.y / height)).toInt()
            progress = 100 - i
            onSizeChanged(width, height, 0, 0)
        }

        MotionEvent.ACTION_CANCEL -> {
        }
    }
    return true
}
}

这是我的代码,它试图将 ScrollView 附加到我片段的 onStart 中的 SeekBar

       override fun onStart() {
    super.onStart()

    val helpScrollView = view!!.findViewById<ScrollView>(R.id.helpScrollView)

    val helpSeekBar = view!!.findViewById<CustomSeekBar>(R.id.helpSeekBar)

   helpScrollView.viewTreeObserver.addOnGlobalLayoutListener {

       val scroll: Int = getScrollRange(helpScrollView)

       helpSeekBar.max = scroll
   }

    val seekBarListener = object : SeekBar.OnSeekBarChangeListener {

        override fun onProgressChanged(seekBar: SeekBar?, progress: Int, fromUser: Boolean) {

            val min = 0
            if(progress < min) {
                seekBar?.progress = min;}

            helpScrollView.scrollTo(0, progress)

        }
        override fun onStartTrackingTouch(seekBar: SeekBar?) {
        }
        override fun onStopTrackingTouch(seekBar: SeekBar?) {

        }
    }

    helpSeekBar.setOnSeekBarChangeListener(seekBarListener)

}

private fun getScrollRange(scrollView: ScrollView): Int {

    var scrollRange = 0
    if (scrollView.childCount > 0) {
        val child = scrollView.getChildAt(0)
        scrollRange =
            Math.max(0, child.height - (scrollView.height - scrollView.paddingBottom - scrollView.paddingTop))
    }

    return scrollRange
}

SeekBar 上的 SeekBar 似乎只对其下半部分的触摸有反应,并且拇指阴影仍然水平滚动离开屏幕。 ScrollView 略微移动,但仅在滚动开始时朝一个方向移动。我做错了什么?


检查底部的更新答案以获得解决方案


我们凭什么来判断您需要做什么,客户几乎总是对的!

无论如何,如果您仍然想这样做,(它可能有效也可能无效)我认为架构应该看起来像这样:

  1. SeekBar 很愚蠢,不应该知道它在做什么。你给它一个最小值(0?)和一个最大值(n)。您订阅它的侦听器并接收 "progress" 更新。 (哈,这是最简单的部分)
  2. 包含文本的ScrollView,除了它的基础之外也很不智能,可以告诉它滚动到一个位置(我想这会涉及一些工作,但我相信这并不难)。
  3. 计算最大值将决定第 2 步的难度。如果您将滚动内容中的每个 "step" 用作 "Line of text",那么这很好,但您需要考虑布局更改和可能更改文本大小的 re-measures .不应该是 "crazy",但是请记住这一点,否则一旦用户旋转 phone :)
  4. ,您就会收到第一份错误报告
  5. 如果文本是动态的(可以更改 real-time),您的第 3 步函数应该尽可能高效,您需要这些数字 "asap"。
  6. 响应搜索栏的进度变化并使可滚动内容滚动将非常简单(您找到了一种非常容易做到的方法),如果您不能使 ScrollView 的行为符合您的要求,则将变得复杂。

替代方法

如果您的文本真的很长,我敢打赌,如果您将文本分成几行并使用 recyclerview 来显示它,那么您将获得更好的性能,这将尽可能地滚动 "slightly easier"将你的 recyclerview 移动到一个位置(行!)。

更新

好的,出于好奇,我尝试了这个。我启动了 AS,创建了一个带有 "Empty Activity" 的新项目,并在内部添加了一个带有 TextView 的 ScrollView,并在底部(水平)添加了一个 SeekBar。

这是包含所有相关位的要点:

https://gist.github.com/Gryzor/5a68e4d247f4db1e0d1d77c40576af33

解决方案的核心是开箱即用

scrollView.scrollTo(0, progress) 其中 progress 由 seekBar 的 onProgressChanged().

中的框架回调返回给您

现在的关键是为搜索栏设置正确的 "Max"。

这是通过躲避 ScrollView 中的私有方法获得的:

(信用到期,我从 here 得到这个想法)

    private fun getScrollRange(scrollView: ScrollView): Int {

        var scrollRange = 0
        if (scrollView.childCount > 0) {
            val child = scrollView.getChildAt(0)
            scrollRange =
                Math.max(0, child.height - (scrollView.height - scrollView.paddingBottom - scrollView.paddingTop))
        }

        return scrollRange
    }

假设您等待 seekBar 到 measure/layout(因此我必须在 treeObserver 中执行此操作)。