在《Android开发艺术探索》中有提到过,View 的位置主要由其四个顶点来决定,分别对应 View 的四个属性:top、left、right、bottom
,且这四个点是相对于 View 的父容器来说的,是一种相对坐标。
另外,在 Android 3.0 以后,View 还增加了几个参数:x、y、translationX、translationY
。x、y
是 View 左上角相对于父容器的坐标,而 translationX、translationY
是 View 左上角相对于父容器的偏移量。
x = left + translationX
y = top + translationY
View 在平移的过程中,top 和 left 表示的是原始左上角的位置信息,其值是不会改变的,此时发生改变的是 x、y、translationX、translationY。
这里需要突出的是平移,因为下面需要说到 View 使用 scrollTo/scrollBy
进行滑动。
首先,需要强调的一点 ,scrollTo/scrollBy
只能滑动 View 的内容,而不能滑动 View 本身 (即滑动的是下面所说的绘图层,而不能滑动 layout 层)。同时,还需要先了解清楚接下来说明的一些概念,有些名词不一定准确,主要是为来辅助概念的讲解。
比如 TextView 的话滑动的就是里面的文本内容,ViewGroup 的话就是滑动内部的子 View。
在 View 的scrollTo 和scrollBy 一文中,有如下的图:
红色框框代表 TextView 的空间视图(即图中的 View 视图,也就是绘画层、绘画视图),该层是没有边界的,可以无限延伸(用红色框框只是为了凸显出来)。
但是在实际中,TextView 在使用的时候都会依附于一个父 View,此时就存在 TextView 的 layout 视图,即图中的黑色框框(框框的左上角坐标即 TextView 的 (left,top)
),是具有具体的边界的(如下代码中 TextView 设置的 layout_XXX 属性,就是用来框定 TextView 的 layout 视图的,此视图即代表在父布局中实际的边界),在绘画层中的内容只有同时处于 layout 视图中才会被显示出来。
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="match_parent">
<TextView
android:layout_width="50dp"
android:layout_height="100dp"
android:text="X"/>
</LinearLayout>
对于 Layout 来说(如 LinearLayout
),因为内部排放的是子 View,所以,子 View 是在 Layout 的绘图层进行绘画的,但是只有当绘画的子 View 处于 Layout 的 layout 视图范围内时,子 View 才会被显示出来(更进一步的说,此时显示的就是子 View 的 layout 视图处于父 Layout 的 layout 视图中)。
像下面这种情况:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/rootView"
android:layout_width="800dp"
android:orientation="horizontal"
android:id="@+id/parent"
android:layout_height="800dp">
......
</LinearLayout>
此时,因为 LinearLayout
设置的 android:layout_width
与android:layout_height
过大,并不能在手机屏幕上完全显示出来,这里需要注意,该布局文件对应的只是 ContentView
(即通过 setContentView 设置的),在该 View 外层还有包裹一层系统的 Layout 的,而此时即使 rootView 设置了 800 * 800dp,但是因为有一部分是不在系统Layout 中的 layout 视图中的,所以才没有在手机屏幕上完全显示出来。
使用scrollTo 、scrollBy
方法滑动时,涉及到 View 内部的两个属性 mScrollX
和 mScrollY
(两者的单位为像素,可通过 getScrollX() 和 getScrollY()
获得)的改变。
当 View 没有经过 scroll 方法滑动内部内容时,处于原始状态时,此时,View 的绘画层与 layout 层左上角重叠的点为参考点(如图中着重标记的黑点),有 mScrollX = 0 && mScrollY = 0
,当滑动之后,该参考点与 layout 层左上角点的距离即为 mScrollX、mScrollY
的值。(因为 layout 层是固定不动,滑动的只是绘图层)。
scrollTo
实现基于所传递参数的绝对滑动,即把上面所说的参考点滑动到指定的位置。scrollBy
实际上也是调用 scrollTo,实现基于当前位置的相对滑动,即把上面所说的参考点根据原有位置滑动相应的距离。
如果绘图层向左滑,mScrollX
为正,如果绘图层向上滑,mScrollY
为正。如下图演示:
此外,还需要注意,当一个 Layout 在使用 scroll 滑动时,并不会改变其内部子 View 的 x、left、translationX
等属性。
例如有如下布局:
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#feb4b4"
android:layout_width="match_parent"
android:orientation="horizontal"
android:id="@+id/parent"
android:layout_height="wrap_content">
<Button
android:id="@+id/bt"
android:onClick="test"
android:layout_marginLeft="100dp"
android:layout_width="50dp"
android:layout_height="100dp"
android:text="X"/>
</LinearLayout>
且为 Button 实现点击事件:
public class MainActivity extends AppCompatActivity {
private LinearLayout layout;
private Button testBt;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
layout = findViewById(R.id.parent);
testBt = findViewById(R.id.bt);
}
public void test(View view) {
Log.d("test", "getX() = "+testBt.getX());
Log.d("test", "getLeft() = "+testBt.getLeft());
Log.d("test", "getTranslationX() = "+testBt.getTranslationX());
layout.scrollBy(50,50);
Log.d("test", "getX() = "+testBt.getX());
Log.d("test", "getLeft() = "+testBt.getLeft());
Log.d("test", "getTranslationX() = "+testBt.getTranslationX());
}
}
scrollBy()
前后,打印的值相等。
(需要注意第一次打印不能在 Activity 的 onCrate 方法中,因为此时可能控件还没有完全初始化,导致 getX()
和 getLeft()
为 0)。