一. View位置参数与坐标分析
View是Android中所有控件的基类,是一种界面层的控件的一种抽象,代表一个控件,常见的获取位置参数的方法有以下三种:
1.View的getTop(),getLeft(), getRight(),getBottom()
View的位置主要由它的四个顶点来决定,分别对应View的四个属性:top,left,right,bottom,其中top是左上角纵坐标 ,left是左上角横坐标,right是右下角横坐标,bottom是右下角纵坐标。需要注意的是,这些坐标都是相对于View的父容器来说的,因此它是一种相对坐标。具体参数看下图:
在Android中,坐标系的原点在屏幕的左上角(不包括状态栏与标题栏的部分),x轴和y轴的正方向分别为向右和向下,这一点很重要。
线性布局竖直排列,从上到下依次为一个Button,一个包括TextView的线性布局,宽度,高度,边距的设置如图所示。看看在Activity中怎么获取它的位置参数:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
}
private void initView() {
mainBtn = (Button) findViewById(R.id.main_btn);
WindowManager windowManager = (WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
int width = windowManager.getDefaultDisplay().getWidth();
int height = windowManager.getDefaultDisplay().getHeight();
Log.e("log", "屏幕宽度:" + width);
Log.e("log", "屏幕高度:" + height);
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
Log.e("log", "按钮的getTop():" + mainBtn.getTop());
Log.e("log", "按钮的getBottom():" + mainBtn.getBottom());
Log.e("log", "按钮的getLeft():" + mainBtn.getLeft());
Log.e("log", "按钮的getRight():" + mainBtn.getRight());
Log.e("log", "按钮的getWidth(px):" + mainBtn.getWidth());
Log.e("log", "按钮的getHeight(px):" + mainBtn.getHeight());
Log.e("log", "按钮的getWidth(dp):" + px2dp(getApplicationContext(), mainBtn.getWidth()));
Log.e("log", "按钮的getHeight(dp):" + px2dp(getApplicationContext(), mainBtn.getHeight()));
Log.e("log", "文字的getTop():" + mainTxt.getTop());
Log.e("log", "文字的getBottom():" + mainTxt.getBottom());
Log.e("log", "文字的getLeft():" + mainTxt.getLeft());
Log.e("log", "文字的getRight():" + mainTxt.getRight());
}
//px转dp
public static int px2dp(Context context, float pxValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (pxValue / scale + 0.5f);
}
获取屏幕的高度与宽度,然后重写Activity的onWindowFocusChanged方法,获取View的位置参数。刚开始我把这些方法直接写在onCreate中,获取到的值都是0。后来才知道,width、height、top、left等属性值是在Measure与Layout过程完成之后才开始正确赋值的,而Measure与Layout都晚于onCreate方法执行,所以onCreate中根本就取不到值!
关于正确获取组件的宽高,可参考以下这篇博文:
我们一起看一下测试结果:
这里的单位默认都是px,从以上结果我们可以得到以下结论:
View的位置参数是相对于自身的父容器来说的,是相对坐标可以看到测试文字的坐标参数是相对于自己的父容器计算的。
View的宽高与坐标的关系:
width = right - left
height = bottom - top
最后我将得到的宽高单位从(px)转换成了(dp),可以看到大小与我们在xml中设置的一致,完美~
2.View的getLocationInWindow()
这个方法获取的是View在当前窗口的绝对坐标,看一下测试代码:
int[] btnWindowInt = new int[2];
mainTxt.getLocationInWindow(btnWindowInt);
Log.e("log", "文字在当前窗口内的绝对横坐标:" + btnWindowInt[0]);
Log.e("log", "文字在当前窗口内的绝对纵坐标:" + btnWindowInt[1]);
int[] llWindowInt = new int[2];
maniLinearLayout.getLocationInWindow(llWindowInt);
Log.e("log", "线性布局在当前窗口内的绝对横坐标:" + llWindowInt[0]);
Log.e("log", "线性布局在当前窗口内的绝对纵坐标:" + llWindowInt[1]);
测试结果:
打印结果里的线性布局就是父线性布局。可以看到,这个线性布局的绝对纵坐标包括了通知栏与状态栏的高度,我们在实际项目运用这个方法的过程中,一定记得减去这个高度。因为Android坐标系的原点在屏幕的左上角(不包括状态栏与标题栏的部分)。
3.MotionEvent的getX(),getY(),getRawX(),getRawY()
首先理解这四个参数的意义:
getX():获取点击事件相对控件左边的x轴坐标,即点击事件距离控件左边的距离
getY():获取点击事件相对控件顶边的y轴坐标,即点击事件距离控件顶边的距离
getRawX():获取点击事件相对整个屏幕左边的x轴坐标,即点击事件距离整个屏幕左边的距离
getRawY():获取点击事件相对整个屏幕顶边的y轴坐标,即点击事件距离整个屏幕顶边的距离
测试代码:
还是之前的布局,我们给测试文字添加一个触摸监听:
mainTxt.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent event) {
int x = (int) event.getX();
int y = (int) event.getY();
int xRaw = (int) event.getRawX();
int yRaw = (int) event.getRawY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
Log.e("log", "触摸事件相对控件坐标为:" + x + "," + y);
Log.e("log", "触摸事件相对屏幕坐标为:" + xRaw + "," + yRaw);
break;
case MotionEvent.ACTION_MOVE:
break;
case MotionEvent.ACTION_UP:
break;
}
return false;
}
});
然后点击测试文字,看一下打印的结果:
那么这种结果在可以滑动的控件中是怎样的呢,比如ListView,RecyclerView。其实对于这种滑动的ViewGroup,我们在获取ViewGroup的坐标值时并不需要考虑它到底滑动了多少(实际滑动的我们应该看作是ViewGroup中的View在滑动)。获取到的结果与这里还是一样的,具体应用案例可参考我之前一篇博客:
二. View的最小滑动距离
TouchSlop是系统所能识别出的最小的滑动距离,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
ViewConfiguration.get(getApplicationContext()).getScaledTouchSlop();
可通过以上方法获取到这个常量,打印出来大小是8dp。当我们处理滑动时,可以利用这个常量来做一些过滤,比如当两次滑动事件的距离小于这个值,我们就可以认为没有达到滑动距离的临界值,因此可以认为它们不是滑动的。这样做可以有更好的用户体验。
三.View滑动全解析
目前Android中实现View的滑动可以分为三种方式:
通过改变View的布局参数使得View重新布局从而实现滑动
通过scrollTo/scrollBy方法来实现View的滑动
通过动画给View施加平移效果来实现滑动
这里将一一解析这三种方式的用法与区别:
首先看一下我们测试的布局文件
然后就是Activity里的代码:
private void init() {
testBtn = (Button) findViewById(R.id.second_btn);
textView = (TextView) findViewById(R.id.second_txt);
testBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.e("log", "测试文字的getTop():" + textView.getTop());
Log.e("log", "测试文字的getBottom():" + textView.getBottom());
Log.e("log", "测试文字的getLeft():" + textView.getLeft());
Log.e("log", "测试文字的getRight():" + textView.getRight());
}
});
textView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Toast.makeText(getApplicationContext(), "测试文字的点击事件", Toast.LENGTH_SHORT).show();
}
});
}
点击按钮的时候,滑动测试文字textView,打印textView的位置参数,并且监听textView的点击事件。
测试文字的初始位置参数如下所示:
通过添加不同的方法来滑动测试文字,并且打印位置参数。所有方法的测试结果的示例图如下:
一起看看怎么实现的:
1.通过改变View的布局参数使得View重新布局从而实现滑动
1.使用layout(int l, int t, int r, int b)方法重新布局:
textView.layout(textView.getLeft() + 50, textView.getTop() + 50, textView.getRight() + 50, textView.getBottom() + 50);
测试文字位置参数改变:
测试文字点击事件有效
2.使用offsetTopAndBottom(int offset)与offsetLeftAndRight(int offset)方法进行偏移:
textView.offsetTopAndBottom(50);
textView.offsetLeftAndRight(50);
测试文字位置参数改变:
测试文字点击事件有效
3.使用LayoutParams方法动态修改布局参数:
LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams) textView.getLayoutParams();
layoutParams.leftMargin = textView.getLeft() + 50;
layoutParams.topMargin = textView.getTop() + 50;
textView.setLayoutParams(layoutParams);
测试文字位置参数不变:
测试文字点击事件有效
2.通过scrollTo/scrollBy方法来实现View的滑动
4.使用scrollTo(int x, int y)方法进行移动:
关于view的scrollTo方法,我在 RecyclerView学习(三)—-高仿知乎的侧滑删除 这篇博客中有很详细的介绍,大家也可以参考启舰大神的ListView滑动删除实现之二——scrollTo、scrollBy详解。
//布局文件
<LinearLayout
android:id="@+id/second_ll"
android:layout_width="match_parent"
android:layout_height="300dp"
android:orientation="horizontal">
<TextView
android:id="@+id/second_txt"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/colorAccent"
android:gravity="center"
android:text="测试文字" />
</LinearLayout>
linearLayout = (LinearLayout) findViewById(R.id.second_ll);
linearLayout.scrollTo(-50, -50);
之所以加上一个父布局,是因为scrollTo/scrollBy方法只能滑动view的内容,并不能滑动view本身。
测试文字位置参数不变:
测试文字点击事件有效
5.使用scrollBy(int x, int y)方法进行移动:
linearLayout.scrollBy(-50, -50);
测试文字位置参数不变:
测试文字点击事件有效
3. 通过动画给View施加平移效果来实现滑动
6.使用补间动画实现view的移动:
TranslateAnimation translateAnimation = new TranslateAnimation(0, 200, 0, 200);
translateAnimation.setDuration(1000);
translateAnimation.setFillAfter(true);
textView.startAnimation(translateAnimation);
测试文字位置参数不变:
使用补间动画实现的测试文字滑动,会导致测试文字的点击事件无效,只有点击原区域,事件才会有效!!!
7.使用属性动画实现view的移动:
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(textView, "translationX", 0, 200),
ObjectAnimator.ofFloat(textView, "translationY", 0, 200)
);
set.start();
测试文字位置参数不变:
使用属性动画实现的测试文字滑动,点击事件依然有效!!!
那么综合对比这三种方式,各自的特点是什么呢:
通过改变View的布局参数使得View重新布局从而实现滑动 ,操作稍微复杂,适用于有交互的View
通过scrollTo/scrollBy方法来实现View的滑动,操作简单,适合对View内容的滑动
通过动画给View施加平移效果来实现滑动,操作简单,主要适用于没有交互的View和实现复杂的动画效果