【发布时间】:2021-02-27 03:19:46
【问题描述】:
希望有人可以提供帮助。我正在创建一个应用程序,用户将通过触摸一系列图像来旋转它们。我正在尝试做的事情。用户旋转到特定位置后突出显示图像。
这可能吗?如果,那么任何提示都非常感谢。
【问题讨论】:
标签: kotlin animation rotation imageview position
希望有人可以提供帮助。我正在创建一个应用程序,用户将通过触摸一系列图像来旋转它们。我正在尝试做的事情。用户旋转到特定位置后突出显示图像。
这可能吗?如果,那么任何提示都非常感谢。
【问题讨论】:
标签: kotlin animation rotation imageview position
编辑 - 好的,这里有一个例子!
首先,最简单的方法,基于您刚刚发布的代码示例:
r1c1.setOnClickListener {
r1c1.animate().apply{ duration = 100 rotationBy(270f) }.start()
}
所以这里的问题是,当视图旋转到 90 度时,您想突出显示它,对吗?但它有一个动画要先完成。你真的有三个选择
if (r1c1.rotation + 270f == 90) 的操作并在动画开始时突出显示,这可能看起来很奇怪withEndAction 运行突出显示代码withEndAction 进行检查和突出显示后者可能是最有意义的——在动画结束后,检查它的显示状态是否需要改变。应该是这样的:
r1c1.animate().setDuration(100).rotationBy(270f).withEndAction {
// need to do modulo so 720 == 360 == 0 etc
if (r1c1.rotation % 360 == TARGET_ROTATION) highlight(r1c1)
}.start()
我假设您有某种方法可以突出显示 ImageViews,而您并不是在寻求方法来做到这一点!
不幸的是,这里的问题是,如果用户在动画制作过程中点击视图,它将取消该动画并开始一个新动画,包括 rotationBy(270) 来自当前视图的旋转在。双击,你会得到一个角度的视图,现在它几乎永远不会匹配 90 度的值!这就是为什么只保存状态、将其更改为固定的有效数量并告诉视图它应该是什么样子会更容易的原因。
因此,您将获得当前轮换的值,对其进行更新,并将其用于突出显示检查:
# var stored outside the click listener - this is your source of truth
viewRotation += 270f
# using rotation instead of rotationBy - we're setting a specific value, not an offset
r1c1.animate().setDuration(100).rotation(viewRotation).withEndAction {
// checking our internal rotation state, not the view!
if (viewRotation % 360 == TARGET_ROTATION) highlight(r1c1)
}.start()
我并不是说像那样有一个单一的旋转变量 - 你可以,但请看下一点 - 如果你有很多 ImageViews 要争吵,它会很快变得一团糟。但这只是为了演示基本思想 - 您拥有自己的状态值,您可以控制它可以设置的值,而 View 只是反映该状态,而不是相反。
好的,所以组织 - 我从 r1c1 猜测您有一个单元格网格,所有单元格都具有相同的一般行为。这意味着大量重复代码,除非您尝试对其进行概括并将其粘贴在一个地方 - 就像单击监听器一样,它会做同样的事情,只是在它被点击的任何视图上
(我知道你说你是一个初学者,我不喜欢一次在某人身上加载太多概念,但听起来你这样做可能会变得非常臃肿并且很难真正快速地工作,所以这很重要!)
基本上,View.onClickListener 的 onClick 函数作为参数传入被点击的视图 - 基本上这样你就可以做我一直在说的事情,重用相同的点击监听器并根据不同的情况做不同的事情什么是传入的。代替 lambda({ } 中的代码,基本上是您在一个地方使用的快速而肮脏的函数),您可以创建一个通用的单击侦听器函数,您可以在所有 ImageViews
fun spin(view: View) {
// we need to store and look up a rotation for each view, like in a Map
rotations[view] = rotations[view] + 270f
// no explicit references like r1c1 now, it's "whatever view was passed in"
view.animate().setDuration(100).rotation(rotations[view]).withEndAction {
// Probably need a different target rotation for each view too?
if (rotations[view] % 360 == targetRotations[view]) highlight(view)
}.start()
}
那么你的点击监听器设置会是这样的
r1c1.setOnClickListener { spin(it) }
或者您可以将其作为函数引用传递(这已经太长了,无法解释,但是在这种情况下有效,因此您可以根据需要使用它)
r1c1.setOnClickListener(::spin)
我建议您在查找时生成所有 ImageView 单元格的列表(有几种方法可以处理此类事情),但是拥有一个集合可以让您执行以下操作
allCells.forEach { it.setOnClickListener(::spin) }
现在所有点击监听器都设置为同一个函数,该函数将处理点击的任何视图以及与之关联的状态。明白了吗?
所以你的基本结构是这样的
// maybe not vals depending on how you initialise things!
val rotations: MutableMap<View, Float>
val targetRotations: Map<View, Float>
val allCells: List<ImageView>
// or onCreateView or whatever
fun onCreate() {
...
allCells.forEach { it.setOnClickListener(::spin) }
}
fun spin(view: View) {
rotations[view] = rotations[view] + 270f
view.animate().setDuration(100).rotation(rotations[view]).withEndAction {
val highlightActive = rotations[view] % 360 == targetRotations[view]
highlight(view, highlightActive)
}.start()
}
fun highlight(view: View, enable: Boolean) {
// do highlighting on view if enable is true, otherwise turn it off
}
我没有进入整个“ImageView 保存其所有状态的包装类”的事情,这可能是一个更好的方法,但我不想走得太远并使事情复杂化。这已经是一个愚蠢的长度。我可能会做一个快速的回答,只是作为演示或其他什么
【讨论】:
另一个答案已经足够长了,但这就是我关于封装事物的意思
class RotatableImageView(val view: ImageView, startRotation: Rotation, val targetRotation: Rotation) {
private var rotation = startRotation.degrees
init {
view.rotation = rotation
view.setOnClickListener { spin() }
updateHighlight()
}
private fun spin() {
rotation += ROTATION_AMOUNT
view.animate().setDuration(100).rotation(rotation)
.withEndAction(::updateHighlight).start()
}
private fun updateHighlight() {
val highlightEnabled = (rotation % 360f) == targetRotation.degrees
// TODO: highlighting!
}
companion object {
const val ROTATION_AMOUNT = 90f
}
}
enum class Rotation(var degrees: Float) {
ROT_0(0f), ROT_90(90f), ROT_180(180f), ROT_270(270f);
companion object {
// just avoids creating a new array each time we call random()
private val rotations = values()
fun random() = rotations.random()
}
}
基本上,不是将Views 映射到当前旋转值,而是将Views 映射到目标值等,每个View 的所有状态都只是捆绑到一个对象中。一切都在内部处理,您需要从外部做的就是在布局中找到您的ImageViews,并将它们传递给RotatableImageView 构造函数。这将设置一个点击侦听器并在必要时处理突出显示其ImageView,您无需执行任何其他操作!
enum 只是创建表示有效值的类型的示例 - 当您创建 RotatableImageView 时,您必须传入其中一个,并且唯一可能的值是有效的旋转量。你也可以给他们默认值(如果你愿意,可以是Rotation.random())所以构造函数调用可以只是RotatableImageView(imageView)
(你可以更多地使用这种东西,比如也将它用于内部旋转量,但在这种情况下它很尴尬,因为在动画视图时 0 与 360 不同,它可能会旋转错误方式 - 所以你几乎必须跟踪你在视图上设置的实际旋转值)
作为一个快速的 FYI(这就是为什么我说你正在做的事情可能会变得笨拙以至于值得学习一些技巧),而不是在大量 ID 上执行findViewById,它可以更容易只需找到所有 ImageViews - 将它们包装在带有 ID 的布局中(比如可能是 GridLayout?)可以更轻松地找到您想要的东西
val cells = findViewById<ViewGroup>(R.id.grid).children.filterIsInstance<ImageView>()
然后你可以做类似的事情
rotatables = cells.map { RotatableImageView(it) }
取决于您需要做什么,但这是一种可能的方式。基本上,如果您发现自己重复相同的事情并进行细微的更改,就像电视广告所说的那样,必须有更好的方法!
【讨论】:
ImageView 的取消/突出显示,3) 调用一些函数来检查获胜条件的整体状态(查看所有旋转并弹出一个 toast)。当您拥有所有 ImageViews 的集合时,后者是最简单的,因为您可以遍历您的集合以检查每个项目的获胜状态