简体   繁体   中英

How to properly scroll ScrollView with a SeekBar

I am attempting to create a custom, vertical SeekBar that will be used to scroll text within a ScrollView . Here is that 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
}
}

Here is my code that attempts to attach the ScrollView to the SeekBar in the onStart of my Fragment:

       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
}

The SeekBar on seems to only react to touches on its lower half, and the thumbs shadow still scrolls horizontally off the screen. The ScrollView moves slightly, but only in one direction at the beginning of the scroll. What am I doing wrong?


Check updated answer at the bottom for a solution


Who are we to judge what you need to do, client is almost always right!

Anyway, if you still wanted to do this, (it may or may not work) here's what I think the architecture should try to look like:

  1. The SeekBar is stupid, shouldn't know ANYTHING about what it is doing. You give it a min (0?) and a MAX (n). You subscribe to its listener and receive the "progress" updates. (ha, that's the easy part)
  2. ScrollView containing the text, is also very unintelligent beyond its basics, it can be told to scroll to a position (this will involve some work I guess, but I'm sure it's not difficult).
  3. Calculating the MAX value is going to be what determines how hard step 2 will be. If you use each "step" in your scrolling content as a "Line of text", then this is fine, but you'd need to account for layout changes and re-measures that may change the size of the text. Shouldn't be "crazy" but, keep it in mind or you will receive your first bug report as soon as a user rotates the phone :)
  4. If the text is dynamic (can change real-time) your function for step 3 should be as efficient as possible, you want those numbers "asap".
  5. Responding to progress changes from the seekbar and having your scrollable content scroll is going to be either super easy (you found a way to do it very easily) or complicated if you cannot make the ScrollView behave as you want.

Alternative Approach

If your text is really long, I bet you're going to have better performance if you split your text in lines and use a recyclerview to display it, which would then make scrolling "slightly easier" as you could move your recyclerview to a position (line!).

UPDATE

Ok, so out of curiosity, I tried this. I launched AS, created a new project with an "Empty Activity" and added a ScrollView with a TextView inside, and a SeekBar at the bottom (horizontal).

Here's the Gist with all the relevant bits:

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

At its core the solution works out of the box :

scrollView.scrollTo(0, progress) where progress is returned to you by the framework callback in the seekBar's onProgressChanged() .

Now the key is to set the correct "Max" to the seekbar.

This is obtained by dodging a private method in the ScrollView with this:

(credit where is due, I got this idea from 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
    }

This works fine, assuming you wait for the seekBar to measure/layout (hence why I had to do this in the treeObserver).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM