那些你应该知道却不一定知道的——View坐标分析汇总

前方高能~
有问题,欢迎指正
本文版权所有,转载请注明:http://www.jianshu.com/p/ce05e06676b2

一.概述

网上关于Android 的view坐标挺多的,写这篇的目的是因为网上搜到的文章大多较简单,几乎都是简单的介绍下获取的几个方法坐标的几个方法罢了,但在实战中,你会发现可能你学会的那几个获取坐标的方法并没有正确的使用,导致当你要计算坐标的时候可能会试过几遍才找到正确的办法(其实这也正是我容易混淆的地方,所以特地写篇博客记录下)

关于那几个获取坐标的方法我就懒得说了
(这篇博客有记载,大家可以去看看http://blog.csdn.net/jason0539/article/details/42743531

大体的方法就是这些了

这里写图片描述

下面借用那篇博客的一张图:

这里写图片描述

view提供的方法

getTop:获取到的,是view自身的顶边到其父布局顶边的距离
getLeft:获取到的,是view自身的左边到其父布局左边的距离
getRight:获取到的,是view自身的右边到其父布局左边的距离
getBottom:获取到的,是view自身的底边到其父布局顶边的距离

MotionEvent提供的方法

getX():获取点击事件相对控件左边的x轴坐标,即点击事件距离控件左边的距离
getY():获取点击事件相对控件顶边的y轴坐标,即点击事件距离控件顶边的距离
getRawX():获取点击事件相对整个屏幕左边的x轴坐标,即点击事件距离整个屏幕左边的距离
getRawY():获取点击事件相对整个屏幕顶边的y轴坐标,即点击事件距离整个屏幕顶边的距离

下面做个测试

这里写图片描述

分别点击A点,B点后效果

这里写图片描述

这里需要注意的是:

点击B点后(可以看到先是回调TestTextView中的onTouchEvent方法,然后才是MainActivity中的onTouchEvent,因为我在二者的onTouchEvent方法中都没有进行点击事件的消费处理,所以会往上传递,突然扯到了事件分发机制,2333~这里就是突然想补充一点,还是扯回坐标吧)

1.TestTextView中getY和getRawY取得的值不一样,这点我们可以理解

2.MainActivity中getY和getRawY取得的值一样!(我们注意到点击A,B点都是如此)

这里我们得到一条启发:

getY和getRawY这一系动作都是由MotionEvent来定义产生的。是得看最后MotionEvent是被哪个View所消耗。如果MotionEvent没有被任何View所消耗,最终返回Activity则getY和getRawY则一致。如果被View所消耗,则具体情况具体分析,getY,getRawY可能一致,也可能不一致

测试2:类似在ListView这种有滚动轴的控件中会是什么样的呢?

这里写图片描述
这里写图片描述

(PS:这时可能会有点好奇,我们明明点击的接近是item7的顶部,为啥得到的Y指却不是接近0呢,原因后面讲)

这里我们得到一条启示:

对于这种滑动的ViewGroup,我们在获取ViewGroup的坐标值时并不需要考虑它到底滑动了多少(实际滑动的我们应该看作是ViewGroup中的View在滑动)

二.获取

在上面我们留下了一个疑问:我们明明点击的接近是item7的顶部,得到的Y指却不是接近0。
原因在于getRawY返回的是点击事件距离整个屏幕顶边的距离,所以点击item7的顶部,得到的Y值其实就是状态栏的值。

当然我们有时候碰到的不仅就只有状态栏,有时候是状态栏与标题栏并存的。所以我们在获取ViewGroup的Y值是一定要注意是否需要减去状态栏,标题栏(如果有)的高度,否则计算得到的Y值并不是正确的。

这里为了让大家更清晰的了解,大家可以看看这篇文章http://bbs.51cto.com/thread-1072344-1.html(Android4.0窗口机制和创建过程分析 )

下面我们用图来初略说明(这是我的理解,有误欢迎指正)

这里写图片描述

现在我们再来说说怎么取得坐标值的时机

因为MotionEvent提供的获取坐标的方法是在页面完完全全显示在用户眼前且用户点击后才会使用到的方法,所以并不存在获取不到的问题,下面就论述下
view提供的获取坐标方法

看到这,你可能会说,那还不简单当布局被加载出来的时候,我们去获取不就OK了吗?
于是我们就会看到这样的错误:在一个Activity的onCreate方法中,设置完setContentView后,就开始View的getLeft,getTop等方法,结果发现为0,为啥?

原因就是:

对于View,ViewGroup来说,width、height、top、left等属性值是在Measure与Layout过程完成之后才开始正确赋值的,而Measure与Layout却都晚于onCreate方法执行,所以onCreate中getLeft根本就取不到值!

那要是我们想要在onCreate中取到我们想要的值,我们应该怎么做呢?
大家可以参考这两篇博文
http://www.cnblogs.com/kissazi2/p/4133927.html
http://blog.csdn.net/codezjx/article/details/45341309

我觉得写得很好了

归纳如下:

  1. 监听Draw/Layout事件:ViewTreeObserver
  2. 将一个runnable添加到Layout队列中:View.post()
  3. 重写View的onLayout方法
  4. 重写Activity的onWindowFocusChanged方法,在该方法中获取

这里我推荐2,4这两种,即

view.post(new Runnable() {  
    @Override  
    public void run() {  
        view.getHeight();  
    }  
});

或者

@Override  
public void onWindowFocusChanged(boolean hasFocus) {  
    super.onWindowFocusChanged(hasFocus);  
    //此处可以正常获取width、height等  
} 

三.计算

现在对坐标系是咋样的,我们已经了解了。

对啥时候获取,以及获取后是否需要纠正得到正确的Y,我们也已经分析了。

下面就来说说本文的重头戏 ——— 坐标的计算。

(之所以说是重头戏,是因为我们之前得到的都是一个点的正确坐标,现在我们需要做的是在得到多个正确坐标后,进行正确的计算,这样我们才能实现滑动这样炫酷的效果`(∩_∩)′)

首先我们需要建立一个概念

在Android的坐标系中,原点在屏幕左上角,向右x为正,向下y为正。

这里写图片描述

(为了好计算,图片中的坐标单位是px)

(下面就以这个为例,我们要将View从原点移动到(200,400)的位置,即B点与C点重合)

想实现View移动大致有这几种方式(代码见下面)

这里写图片描述

XML文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <LinearLayout
        android:id="@+id/ly"
        android:background="#EFAA88"
        android:layout_centerInParent="true"
        android:layout_width="300px"
        android:layout_height="500px">
        <mr_immortalz.com.testlocation.TestTextView
            android:background="#aabbcc"
            android:id="@+id/tv"
            android:text="你好"
            android:layout_width="100px"
            android:layout_height="100px" />
    </LinearLayout>

</LinearLayout>

TestTextView也很简单,就是

/**
 * Created by Mr_immortalZ on 2016/4/16.
 * email : mr_immortalz@qq.com
 */
public class TestTextView extends TextView {
    public TestTextView(Context context) {
        super(context);
    }

    public TestTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public TestTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                /*LogUtil.m("TestTextView  getX "+event.getX()+" getY "+event.getY());
                LogUtil.m("TestTextView  getrawx "+event.getRawX()+" getrawy "+event.getRawY());*/
                //layout(getLeft() + 200, getTop() + 400, getRight() + 200, getBottom() + 400);

                /*offsetLeftAndRight(200);
                offsetTopAndBottom(400);*/
               /* ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
                lp.leftMargin = getLeft() + 200;
                lp.topMargin = getTop() + 400;
                setLayoutParams(lp);*/

                //((View)getParent()).scrollTo(-200,-400);

                //scrollTo(-50,-10);

                //scrollTo(300, 500);

                //((View)getParent()).scrollBy(-200,-400);
                /*AnimatorSet set = new AnimatorSet();
                set.playTogether(
                        ObjectAnimator.ofFloat(this, "translationX", 200),
                        ObjectAnimator.ofFloat(this,"translationY", 400)

                );
                set.start();*/
                TranslateAnimation anim = new TranslateAnimation(0, 200, 0, 400);
                anim.setFillAfter(true);
                startAnimation(anim);

                LogUtil.m("移动后 getX " + getX() + "  getY " + getY());
                LogUtil.m("移动后 getLeft " + getLeft() + "tv getTop " + getTop()
                        + " tv getRight " + getRight() + " tv getBottom " + getBottom());
                break;
        }
        return true;
    }
}

我们移动到指定位置的有7种方式##


1.layout

layout(getLeft() + 200, getTop() + 400, getRight() + 200, getBottom() + 400);

移动后getLeft等值改变

这里写图片描述

2.offsetLeftAndRight、offsetTopAndBottom

offsetLeftAndRight(200);
offsetTopAndBottom(400);

移动后getLeft等值改变

这里写图片描述

3.修改LayoutParams

 ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
                lp.leftMargin = getLeft() + 200;
                lp.topMargin = getTop() + 400;
                setLayoutParams(lp);

移动后getLeft等值不改变

这里写图片描述

4.scrollTo

((View)getParent()).scrollTo(-200,-400);

移动后getLeft等值不改变

这里写图片描述

5.scrollBy

((View)getParent()).scrollBy(-200,-400);

移动后getLeft等值不改变

这里写图片描述

对于scrollTo、scrollBy需要注意的有两个问题
问题1:

移动计算值 = 最开始点坐标 - 最后移动到的坐标
原因是因为最终会调用这个方法
—— invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
其中l,t,r,b为原来坐标点,scrollX,scrollY为目标坐标点,只有当目标坐标点值是负数时,移动到的位置才为正数!
例如scrollTo ,我们要从(0,0)移动到(200,400)这个点,根据上面的公式可知为负值

问题2

为什么需要加上 ((View)getParent())

TestTextView本身是View,scrollTo、scrollBy移动的都是View的Content,如果不加的话,使用的效果则是TestTextView的文字位置变化,而TestTextView本身不会变化。
如果在ViewGroup中使用scrollTo、scrollBy,则移动的是ViewGroup中的View.我们这里需要让TestTextView移动,则需要先 ((View)getParent()),然后再((View)getParent()).scrollTo...


6.属性动画

我就以ObjectAnimator为例子

AnimatorSet set = new AnimatorSet();
                set.playTogether(
                        ObjectAnimator.ofFloat(this, "translationX", 200),
                        ObjectAnimator.ofFloat(this,"translationY", 400)

                );
                set.start();

移动后getLeft等值不改变

这里写图片描述

7.位移动画

TranslateAnimation anim = new TranslateAnimation(0,200,0,400);
                anim.setFillAfter(true);
                startAnimation(anim);

移动后getLeft等值不改变

这里写图片描述

关于位移动画的补充点:

我们经常用这样的需求,要求一个popupwindow从屏幕底部弹出或者从屏幕顶部弹出。
这里的位移设置同样还是如此(原点在屏幕左上角,向右x为正,向下y为正)

这篇博文可以参考学习下,下面这张神图也是来自这篇博文(Android动画之translate(位移动画))
http://www.cnblogs.com/bavariama/archive/2013/01/29/2881225.html

这里写图片描述

注意点

属性动画是真实改变View的位置的,虽然属性动画、位移动画的getLeft等没有改变,但是属性动画的getX、getY是改变了的,位移动画的getX、getY仍未改变!


最后再来回顾下这张图

这里写图片描述

四.小结#

坐标分析上面都分析完了,基本上涵盖了自定义View坐标计算、滑动、事件分发等常见场景的坐标问题,希望大家能从中得到收获。
水平很菜,有错误的地方欢迎指正,大家一起学习进步!

☻ 反正撸完这篇,我算是对于坐标系有了更深刻的认识,给自己点个赞~❤
csdn同步博文地址:http://blog.csdn.net/mr_immortalz/article/details/51168278

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,734评论 6 505
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,931评论 3 394
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,133评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,532评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,585评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,462评论 1 302
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,262评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,153评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,587评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,792评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,919评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,635评论 5 345
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,237评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,855评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,983评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,048评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,864评论 2 354

推荐阅读更多精彩内容