Как правильно прокручивать ScrollView с помощью 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 к SeekBar в onStart моего фрагмента:

       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, кажется, реагирует только на прикосновения к его нижней половине, а тень большого пальца по-прежнему прокручивается горизонтально за пределы экрана. ScrollView немного перемещается, но только в одном направлении в начале прокрутки. Что я делаю неправильно?


person andrewedgar    schedule 10.06.2019    source источник
comment
Какая в этом необходимость? Вы можете изменить большой палец прокрутки.   -  person Ankit    schedule 10.06.2019
comment
Я поменял большой палец, у меня проблема с прокруткой. Я хочу прокручивать текст с помощью SeekBar.   -  person andrewedgar    schedule 10.06.2019
comment
Есть ли причина, по которой указанный text не содержится в многострочном TextView, который может... предоставить собственную панель поиска для прокрутки? (Мне любопытно)   -  person Martin Marconcini    schedule 10.06.2019
comment
Клиенту нужен дискретный SeekBar. Это только в дизайне. Это будет встроено в машину и не будет прокручиваться обычным способом.   -  person andrewedgar    schedule 10.06.2019


Ответы (1)



Проверьте обновленный ответ внизу для решения


Кто мы такие, чтобы судить, что вам нужно сделать, клиент почти всегда прав!

В любом случае, если вы все еще хотите сделать это (это может сработать, а может и не сработать), вот как я думаю, что архитектура должна выглядеть так:

  1. SeekBar глуп, НИЧЕГО не должен знать о том, что он делает. Вы даете ему мин (0?) и МАКС (n). Вы подписываетесь на его слушателя и получаете обновления «прогресс». (ха, это самая легкая часть)
  2. ScrollView, содержащий текст, также очень неразумен за пределами своих основ, ему можно приказать прокрутить до позиции (я думаю, это потребует некоторой работы, но я уверен, что это не сложно).
  3. Вычисление максимального значения будет определять, насколько сложным будет шаг 2. Если вы используете каждый «шаг» в своем прокручиваемом контенте как «строку текста», то это нормально, но вам нужно будет учитывать изменения макета и повторные измерения, которые могут изменить размер текста. Не должно быть «безумием», но имейте это в виду, иначе вы получите свой первый отчет об ошибке, как только пользователь повернет телефон :)
  4. Если текст является динамическим (может меняться в режиме реального времени), ваша функция для шага 3 должна быть максимально эффективной, вам нужны эти числа «как можно скорее».
  5. Реагировать на изменения прогресса с панели поиска и прокручивать прокручиваемый контент будет либо очень просто (вы нашли способ сделать это очень легко), либо сложно, если вы не можете заставить ScrollView вести себя так, как вы хотите.

Альтернативный подход

Если ваш текст очень длинный, держу пари, что вы будете иметь лучшую производительность, если вы разделите свой текст на строки и используете recyclerview для его отображения, что затем сделает прокрутку «немного проще», поскольку вы можете переместить свой recyclerview в позицию (линия!).

ОБНОВИТЬ

Итак, из любопытства я попробовал это. Я запустил AS, создал новый проект с «Пустой активностью» и добавил ScrollView с TextView внутри и SeekBar внизу (горизонтально).

Вот суть со всеми соответствующими битами:

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

По своей сути решение работает из коробки:

scrollView.scrollTo(0, progress), где progress возвращается вам обратным вызовом фреймворка в файле seekBar onProgressChanged().

Теперь главное установить правильный «Макс» для панели поиска.

Это достигается путем уклонения приватного метода в ScrollView следующим образом:

(кредит, где это необходимо, я получил эту идею от здесь)

    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 измерит/макетирует (поэтому мне пришлось сделать это в treeObserver).

person Martin Marconcini    schedule 10.06.2019
comment
Благодарю за ваш ответ. У нас схожие взгляды на архитектуру. Я установил максимальное значение SeekBar в maxScrollAmount ScrollView. Я еще даже не дошел до того момента, когда я могу увидеть, сработало ли это, поскольку я не верю, что SeekBar еще не отрисован правильно. Что бы это ни стоило, это приложение встраивается на машину с одним размером экрана и без изменений конфигурации. - person andrewedgar; 10.06.2019
comment
Что вы используете для прокрутки? ScrollView с TextView внутри? - person Martin Marconcini; 10.06.2019
comment
Да, текстовое представление в прокрутке было рекомендовано в другом месте. - person andrewedgar; 10.06.2019
comment
из любопытства, какие значения прокрутки сообщают как maxScrollAmount? Вы уверены, что это правильное значение? (Я знаю, что ты еще не добрался туда) - person Martin Marconcini; 10.06.2019
comment
335 — максимальное количество прокруток. Он остается таким, даже когда я заставляю весь текст отображаться в ScrollView, поэтому я подозреваю, что это неправильное значение для использования, несмотря на то, что я нашел его в этом примере (stackoverflow.com/questions/50927190/), который, по утверждению автора, работает, хотя я не знаю, откуда он берет значение progress_value. - person andrewedgar; 10.06.2019
comment
Хорошо, у меня все работает, посмотрите обновленный ответ;) - person Martin Marconcini; 10.06.2019
comment
Я помечаю этот ответ как правильный, так как теперь я понимаю, что мой код имеет несколько недостатков, и вы определенно решили один из них. Большое спасибо. Если вы хотите задержаться, я загрузил видео об отображении другого плохого поведения SeekBar (youtube.com/watch?v=1XGPXF28ONg&feature=youtu.be) - person andrewedgar; 10.06.2019
comment
Ха! Спасибо, и я рад, что вы нашли недостатки в своем коде, я только пишу о недостатках. :) В любом случае, спасибо, что поделились видео, действительно странное поведение! Отметьте меня еще раз, если вы хотите, чтобы я изучил что-то еще. Удачи! :) - person Martin Marconcini; 10.06.2019
comment
Как раз этот кусочек в видео, если в следующий раз попадется момент. Я подозреваю, что это как-то связано с пользовательским классом SeekBar, но я не уверен, что именно. Я возился со значениями там, но любое изменение обычно приводит к исчезновению всей панели поиска. Я думаю, что задам еще один вопрос о SO - person andrewedgar; 10.06.2019
comment
Хорошо, моя работает лучше: youtu.be/pvk4EKbnAe4 ;) Что касается вашей панели поиска, то, честно говоря, я не уверен, что происходит, не видя более широкой картины. SeekBars - странные компоненты: p, я посмотрю еще раз. - person Martin Marconcini; 10.06.2019
comment
Я заметил в вашей пользовательской панели поиска... вы переопределяете onMeasure (нет необходимости в этой вещи @Synchronized, это все потоки пользовательского интерфейса), но затем вы вызываете setMeasuredDimension(measuredHeight, measuredWidth) Я думаю, что они инвертированы, подпись для метода protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { это преднамеренно? - person Martin Marconcini; 10.06.2019
comment
Хотя я скопировал это из другого сообщения, в котором утверждалось, что я делаю то, что я пытаюсь сделать, я действительно считаю, что инверсия является преднамеренной, поскольку она является частью вертикального, а не горизонтального SeekBar. Переворачивание превращает панель поиска в небольшую заглушку, хотя она по-прежнему прокручивается вертикально. - person andrewedgar; 10.06.2019
comment
Давайте продолжим обсуждение в чате. - person Martin Marconcini; 10.06.2019