如何使用 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
略微移动,但仅在滚动开始时朝一个方向移动。我做错了什么?
检查底部的更新答案以获得解决方案
我们凭什么来判断您需要做什么,客户几乎总是对的!
无论如何,如果您仍然想这样做,(它可能有效也可能无效)我认为架构应该看起来像这样:
- SeekBar 很愚蠢,不应该知道它在做什么。你给它一个最小值(0?)和一个最大值(n)。您订阅它的侦听器并接收 "progress" 更新。 (哈,这是最简单的部分)
- 包含文本的ScrollView,除了它的基础之外也很不智能,可以告诉它滚动到一个位置(我想这会涉及一些工作,但我相信这并不难)。
- 计算最大值将决定第 2 步的难度。如果您将滚动内容中的每个 "step" 用作 "Line of text",那么这很好,但您需要考虑布局更改和可能更改文本大小的 re-measures .不应该是 "crazy",但是请记住这一点,否则一旦用户旋转 phone :)
,您就会收到第一份错误报告
- 如果文本是动态的(可以更改 real-time),您的第 3 步函数应该尽可能高效,您需要这些数字 "asap"。
- 响应搜索栏的进度变化并使可滚动内容滚动将非常简单(您找到了一种非常容易做到的方法),如果您不能使 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 中执行此操作)。
我正在尝试创建一个自定义的垂直 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
略微移动,但仅在滚动开始时朝一个方向移动。我做错了什么?
检查底部的更新答案以获得解决方案
我们凭什么来判断您需要做什么,客户几乎总是对的!
无论如何,如果您仍然想这样做,(它可能有效也可能无效)我认为架构应该看起来像这样:
- SeekBar 很愚蠢,不应该知道它在做什么。你给它一个最小值(0?)和一个最大值(n)。您订阅它的侦听器并接收 "progress" 更新。 (哈,这是最简单的部分)
- 包含文本的ScrollView,除了它的基础之外也很不智能,可以告诉它滚动到一个位置(我想这会涉及一些工作,但我相信这并不难)。
- 计算最大值将决定第 2 步的难度。如果您将滚动内容中的每个 "step" 用作 "Line of text",那么这很好,但您需要考虑布局更改和可能更改文本大小的 re-measures .不应该是 "crazy",但是请记住这一点,否则一旦用户旋转 phone :) ,您就会收到第一份错误报告
- 如果文本是动态的(可以更改 real-time),您的第 3 步函数应该尽可能高效,您需要这些数字 "asap"。
- 响应搜索栏的进度变化并使可滚动内容滚动将非常简单(您找到了一种非常容易做到的方法),如果您不能使 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 中执行此操作)。