在上一节讲解了layout
、offset
、layoutParam
的使用,本节说明scrollTo
、scrollBy
、Scroller
。
1、scrollTo
、scrollBy
view
体系中,可以使用scrollTo
和scrollBy
移动view
的位置,他们两个的区别也很简单。
scrollTo(x, y)
将view
移动到一个具体的坐标点scrollBy(x, y)
将view在现有坐标基础上进行增量
移动
scrollBy
本质上是内部调用的scrollTo
实现。
public void scrollBy(int x, int y) {
scrollTo(mScrollX + x, mScrollY + y);
}
看到这里是不是感觉会了,想要宠宠欲动了,尝试之后发现view在scrollTo
和scrollBy
的时候并没有发生移动。这是因为scrollBy
和scrollTo
作用的对象是view
的内容。如果你想要view
移动,则需要通过view
的父类viewGroup
来触发移动。
(view.parent as ViewGroup).scrollBy(x, y)
再次尝试,你就会发现发生了移动,但是当你设置移动scrollBy(20, 20)
,视图本身的移动却与你想要的相反了。其实出现这种情况的原因是,移动操作作用的是父布局,你可以理解为移动的是背景幕布,当幕布移动的时候,视觉上你的位置其实就是和幕布相反的。
所以就会出现这样的结果:
- 当你在
scrollBy/scrollTo
设置负数的时候,content其实是沿着正方向移动 - 当你在
scrollBy/scrollTo
设置正数的时候,content其实是沿着负方向移动
下面移动一个简单的实例:
class ScrollerByOrToView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
private var mLastX = 0F
private var mLastY = 0F
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
this.performClick()
mLastX = event.x
mLastY = event.y
}
MotionEvent.ACTION_MOVE -> {
val offsetX = event.x - mLastX
val offsetY = event.y - mLastY
(parent as ViewGroup).scrollBy(-offsetX.toInt(), -offsetY.toInt())
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
(parent as ViewGroup).scrollTo(0, 0)
}
}
return true
}
}
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.fireking.basic.animator.basic.widget.ScrollerByOrToView
android:id="@+id/scrollTouchView"
android:layout_width="90dp"
android:layout_height="90dp"
android:background="#ff723d"
android:gravity="center"
android:text="Touch"
android:textColor="@android:color/white"
android:textSize="12dp" />
<TextView
android:layout_width="90dp"
android:layout_height="90dp"
android:background="#80008800"
android:gravity="center"
android:text="我是一个参照物,我和滚动布局在同一个父容器"
android:textColor="@android:color/white"
android:textSize="12dp" />
</LinearLayout>
2、scroller
使用scrollBy
和scrollTo
帮我们完成很多移动动画效果,但是使用的过程中,发现移动时瞬间完成的,这样明显是很丑陋的。
这个时候,就需要用到scroller
登场了,使用scroller
可以实现平滑移动效果。
下面简单介绍下scroller
使用
- 初始化
Scroller
//两个参数分别是上下文对象 和 插值器
//Scroller(Context context, Interpolator interpolator)
private var mScroller: Scroller = Scroller(context, AccelerateDecelerateInterpolator())
- 使用
startScroll(int startX, int startY, int dx, int dy, int duration)
方法开始平滑移动
val viewGroup = parent as ViewGroup
//5个参数分别是移动起始位置,移动偏移量,移动动画持续时长
mScroller.startScroll(
viewGroup.scrollX,
viewGroup.scrollY,
-viewGroup.scrollX,
-viewGroup.scrollY,
1000
)
- 重写
override fun computeScroll()
方法
这个方法中会首先判断动画是否结束,没有结束的话,则需要调用scrollTo
进行移动,而移动的距离则是mScroller.currX
和mScroller.currY
。同时需要调用invalidate
。
override fun computeScroll() {
super.computeScroll()
if (mScroller.computeScrollOffset()) {
(parent as ViewGroup).scrollTo(mScroller.currX, mScroller.currY)
invalidate()
}
}
最后是一个简单的例子。
class ScrollerView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attrs, defStyleAttr) {
private var mLastX = 0F
private var mLastY = 0F
private var mScroller: Scroller = Scroller(context, AccelerateDecelerateInterpolator())
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
this.performClick()
mLastX = event.x
mLastY = event.y
}
MotionEvent.ACTION_MOVE -> {
val offsetX = event.x - mLastX
val offsetY = event.y - mLastY
(parent as ViewGroup).scrollBy(-offsetX.toInt(), -offsetY.toInt())
}
MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {
val viewGroup = parent as ViewGroup
mScroller.startScroll(
viewGroup.scrollX,
viewGroup.scrollY,
-viewGroup.scrollX,
-viewGroup.scrollY,
1000
)
invalidate()
}
}
return true
}
override fun computeScroll() {
super.computeScroll()
if (mScroller.computeScrollOffset()) {
(parent as ViewGroup).scrollTo(mScroller.currX, mScroller.currY)
invalidate()
}
}
}
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<org.fireking.basic.animator.basic.widget.ScrollerView
android:layout_width="90dp"
android:layout_height="90dp"
android:background="#9f723d"
android:gravity="center"
android:text="Touch"
android:textColor="@android:color/white"
android:textSize="12dp" />
<TextView
android:layout_width="90dp"
android:layout_height="90dp"
android:background="#80008800"
android:gravity="center"
android:text="我是一个参照物,我和滚动布局在同一个父容器"
android:textColor="@android:color/white"
android:textSize="12dp" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:text="我和滚动布局的父布局平级,\n用来区分平移。\n使用scrollBy和scrollTo滑动,\n移动的是所在的父容器"
android:textColor="@color/black"
android:textSize="12dp" />
</FrameLayout>
拖动移动一段距离放手之后,布局会平滑的移动到初始位置。