那些你應(yīng)該知道卻不一定知道的——View坐標(biāo)分析匯總

前方高能~
有問(wèn)題邪财,歡迎指正
本文版權(quán)所有袒餐,轉(zhuǎn)載請(qǐng)注明:http://www.reibang.com/p/ce05e06676b2

一.概述

網(wǎng)上關(guān)于Android 的view坐標(biāo)挺多的游昼,寫(xiě)這篇的目的是因?yàn)榫W(wǎng)上搜到的文章大多較簡(jiǎn)單页眯,幾乎都是簡(jiǎn)單的介紹下獲取的幾個(gè)方法坐標(biāo)的幾個(gè)方法罷了宗挥,但在實(shí)戰(zhàn)中靶壮,你會(huì)發(fā)現(xiàn)可能你學(xué)會(huì)的那幾個(gè)獲取坐標(biāo)的方法并沒(méi)有正確的使用维费,導(dǎo)致當(dāng)你要計(jì)算坐標(biāo)的時(shí)候可能會(huì)試過(guò)幾遍才找到正確的辦法(其實(shí)這也正是我容易混淆的地方果元,所以特地寫(xiě)篇博客記錄下)

關(guān)于那幾個(gè)獲取坐標(biāo)的方法我就懶得說(shuō)了
(這篇博客有記載,大家可以去看看http://blog.csdn.net/jason0539/article/details/42743531

大體的方法就是這些了

這里寫(xiě)圖片描述

下面借用那篇博客的一張圖:

這里寫(xiě)圖片描述

view提供的方法

getTop:獲取到的犀盟,是view自身的頂邊到其父布局頂邊的距離
getLeft:獲取到的而晒,是view自身的左邊到其父布局左邊的距離
getRight:獲取到的,是view自身的右邊到其父布局左邊的距離
getBottom:獲取到的阅畴,是view自身的底邊到其父布局頂邊的距離

MotionEvent提供的方法

getX():獲取點(diǎn)擊事件相對(duì)控件左邊的x軸坐標(biāo)倡怎,即點(diǎn)擊事件距離控件左邊的距離
getY():獲取點(diǎn)擊事件相對(duì)控件頂邊的y軸坐標(biāo),即點(diǎn)擊事件距離控件頂邊的距離
getRawX():獲取點(diǎn)擊事件相對(duì)整個(gè)屏幕左邊的x軸坐標(biāo)贱枣,即點(diǎn)擊事件距離整個(gè)屏幕左邊的距離
getRawY():獲取點(diǎn)擊事件相對(duì)整個(gè)屏幕頂邊的y軸坐標(biāo)监署,即點(diǎn)擊事件距離整個(gè)屏幕頂邊的距離

下面做個(gè)測(cè)試

這里寫(xiě)圖片描述

分別點(diǎn)擊A點(diǎn),B點(diǎn)后效果

這里寫(xiě)圖片描述

這里需要注意的是:

點(diǎn)擊B點(diǎn)后(可以看到先是回調(diào)TestTextView中的onTouchEvent方法纽哥,然后才是MainActivity中的onTouchEvent钠乏,因?yàn)槲以诙叩膐nTouchEvent方法中都沒(méi)有進(jìn)行點(diǎn)擊事件的消費(fèi)處理,所以會(huì)往上傳遞春塌,突然扯到了事件分發(fā)機(jī)制晓避,2333~這里就是突然想補(bǔ)充一點(diǎn),還是扯回坐標(biāo)吧)

1.TestTextView中g(shù)etY和getRawY取得的值不一樣只壳,這點(diǎn)我們可以理解

2.MainActivity中g(shù)etY和getRawY取得的值一樣G喂啊(我們注意到點(diǎn)擊A,B點(diǎn)都是如此)

這里我們得到一條啟發(fā):

getY和getRawY這一系動(dòng)作都是由MotionEvent來(lái)定義產(chǎn)生的。是得看最后MotionEvent是被哪個(gè)View所消耗吼句。如果MotionEvent沒(méi)有被任何View所消耗锅必,最終返回Activity則getY和getRawY則一致。如果被View所消耗惕艳,則具體情況具體分析搞隐,getY,getRawY可能一致尔艇,也可能不一致

測(cè)試2:類(lèi)似在ListView這種有滾動(dòng)軸的控件中會(huì)是什么樣的呢尔许?

這里寫(xiě)圖片描述
這里寫(xiě)圖片描述

(PS:這時(shí)可能會(huì)有點(diǎn)好奇,我們明明點(diǎn)擊的接近是item7的頂部终娃,為啥得到的Y指卻不是接近0呢味廊,原因后面講)

這里我們得到一條啟示:

對(duì)于這種滑動(dòng)的ViewGroup,我們?cè)讷@取ViewGroup的坐標(biāo)值時(shí)并不需要考慮它到底滑動(dòng)了多少(實(shí)際滑動(dòng)的我們應(yīng)該看作是ViewGroup中的View在滑動(dòng))

二.獲取

在上面我們留下了一個(gè)疑問(wèn):我們明明點(diǎn)擊的接近是item7的頂部,得到的Y指卻不是接近0余佛。
原因在于getRawY返回的是點(diǎn)擊事件距離整個(gè)屏幕頂邊的距離柠新,所以點(diǎn)擊item7的頂部,得到的Y值其實(shí)就是狀態(tài)欄的值辉巡。

當(dāng)然我們有時(shí)候碰到的不僅就只有狀態(tài)欄恨憎,有時(shí)候是狀態(tài)欄與標(biāo)題欄并存的。所以我們?cè)讷@取ViewGroup的Y值是一定要注意是否需要減去狀態(tài)欄郊楣,標(biāo)題欄(如果有)的高度憔恳,否則計(jì)算得到的Y值并不是正確的。

這里為了讓大家更清晰的了解净蚤,大家可以看看這篇文章http://bbs.51cto.com/thread-1072344-1.html(Android4.0窗口機(jī)制和創(chuàng)建過(guò)程分析 )

下面我們用圖來(lái)初略說(shuō)明(這是我的理解钥组,有誤歡迎指正)

這里寫(xiě)圖片描述

現(xiàn)在我們?cè)賮?lái)說(shuō)說(shuō)怎么取得坐標(biāo)值的時(shí)機(jī)

因?yàn)镸otionEvent提供的獲取坐標(biāo)的方法是在頁(yè)面完完全全顯示在用戶眼前且用戶點(diǎn)擊后才會(huì)使用到的方法,所以并不存在獲取不到的問(wèn)題今瀑,下面就論述下
view提供的獲取坐標(biāo)方法

看到這程梦,你可能會(huì)說(shuō),那還不簡(jiǎn)單當(dāng)布局被加載出來(lái)的時(shí)候橘荠,我們?nèi)カ@取不就OK了嗎屿附?
于是我們就會(huì)看到這樣的錯(cuò)誤:在一個(gè)Activity的onCreate方法中,設(shè)置完setContentView后哥童,就開(kāi)始View的getLeft挺份,getTop等方法,結(jié)果發(fā)現(xiàn)為0如蚜,為啥压恒?

原因就是:

對(duì)于View,ViewGroup來(lái)說(shuō)错邦,width探赫、height、top撬呢、left等屬性值是在Measure與Layout過(guò)程完成之后才開(kāi)始正確賦值的伦吠,而Measure與Layout卻都晚于onCreate方法執(zhí)行,所以onCreate中g(shù)etLeft根本就取不到值魂拦!

那要是我們想要在onCreate中取到我們想要的值毛仪,我們應(yīng)該怎么做呢?
大家可以參考這兩篇博文
http://www.cnblogs.com/kissazi2/p/4133927.html
http://blog.csdn.net/codezjx/article/details/45341309

我覺(jué)得寫(xiě)得很好了

歸納如下:

  1. 監(jiān)聽(tīng)Draw/Layout事件:ViewTreeObserver
  2. 將一個(gè)runnable添加到Layout隊(duì)列中:View.post()
  3. 重寫(xiě)View的onLayout方法
  4. 重寫(xiě)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等  
} 

三.計(jì)算

現(xiàn)在對(duì)坐標(biāo)系是咋樣的,我們已經(jīng)了解了荷愕。

對(duì)啥時(shí)候獲取衡怀,以及獲取后是否需要糾正得到正確的Y棍矛,我們也已經(jīng)分析了。

下面就來(lái)說(shuō)說(shuō)本文的重頭戲 ——— 坐標(biāo)的計(jì)算抛杨。

(之所以說(shuō)是重頭戲够委,是因?yàn)槲覀冎暗玫降亩际且粋€(gè)點(diǎn)的正確坐標(biāo),現(xiàn)在我們需要做的是在得到多個(gè)正確坐標(biāo)后怖现,進(jìn)行正確的計(jì)算茁帽,這樣我們才能實(shí)現(xiàn)滑動(dòng)這樣炫酷的效果`(∩_∩)′)

首先我們需要建立一個(gè)概念

在Android的坐標(biāo)系中,原點(diǎn)在屏幕左上角屈嗤,向右x為正潘拨,向下y為正。

這里寫(xiě)圖片描述

(為了好計(jì)算恢共,圖片中的坐標(biāo)單位是px)

(下面就以這個(gè)為例战秋,我們要將View從原點(diǎn)移動(dòng)到(200,400)的位置,即B點(diǎn)與C點(diǎn)重合)

想實(shí)現(xiàn)View移動(dòng)大致有這幾種方式(代碼見(jiàn)下面)

這里寫(xiě)圖片描述

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也很簡(jiǎn)單讨韭,就是

/**
 * 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("移動(dòng)后 getX " + getX() + "  getY " + getY());
                LogUtil.m("移動(dòng)后 getLeft " + getLeft() + "tv getTop " + getTop()
                        + " tv getRight " + getRight() + " tv getBottom " + getBottom());
                break;
        }
        return true;
    }
}

我們移動(dòng)到指定位置的有7種方式##


1.layout

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

移動(dòng)后getLeft等值改變

這里寫(xiě)圖片描述

2.offsetLeftAndRight、offsetTopAndBottom

offsetLeftAndRight(200);
offsetTopAndBottom(400);

移動(dòng)后getLeft等值改變

這里寫(xiě)圖片描述

3.修改LayoutParams

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

移動(dòng)后getLeft等值不改變

這里寫(xiě)圖片描述

4.scrollTo

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

移動(dòng)后getLeft等值不改變

這里寫(xiě)圖片描述

5.scrollBy

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

移動(dòng)后getLeft等值不改變

這里寫(xiě)圖片描述

對(duì)于scrollTo癣蟋、scrollBy需要注意的有兩個(gè)問(wèn)題
問(wèn)題1:

移動(dòng)計(jì)算值 = 最開(kāi)始點(diǎn)坐標(biāo) - 最后移動(dòng)到的坐標(biāo)
原因是因?yàn)樽罱K會(huì)調(diào)用這個(gè)方法
—— invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
其中l(wèi),t,r,b為原來(lái)坐標(biāo)點(diǎn)透硝,scrollX,scrollY為目標(biāo)坐標(biāo)點(diǎn),只有當(dāng)目標(biāo)坐標(biāo)點(diǎn)值是負(fù)數(shù)時(shí)疯搅,移動(dòng)到的位置才為正數(shù)濒生!
例如scrollTo ,我們要從(0,0)移動(dòng)到(200,400)這個(gè)點(diǎn)幔欧,根據(jù)上面的公式可知為負(fù)值

問(wèn)題2

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

TestTextView本身是View罪治,scrollTo、scrollBy移動(dòng)的都是View的Content礁蔗,如果不加的話觉义,使用的效果則是TestTextView的文字位置變化,而TestTextView本身不會(huì)變化浴井。
如果在ViewGroup中使用scrollTo晒骇、scrollBy,則移動(dòng)的是ViewGroup中的View.我們這里需要讓TestTextView移動(dòng)磺浙,則需要先 ((View)getParent())洪囤,然后再((View)getParent()).scrollTo...


6.屬性動(dòng)畫(huà)

我就以O(shè)bjectAnimator為例子

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

                );
                set.start();

移動(dòng)后getLeft等值不改變

這里寫(xiě)圖片描述

7.位移動(dòng)畫(huà)

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

移動(dòng)后getLeft等值不改變

這里寫(xiě)圖片描述

關(guān)于位移動(dòng)畫(huà)的補(bǔ)充點(diǎn):

我們經(jīng)常用這樣的需求,要求一個(gè)popupwindow從屏幕底部彈出或者從屏幕頂部彈出撕氧。
這里的位移設(shè)置同樣還是如此(原點(diǎn)在屏幕左上角瘤缩,向右x為正,向下y為正)

這篇博文可以參考學(xué)習(xí)下伦泥,下面這張神圖也是來(lái)自這篇博文(Android動(dòng)畫(huà)之translate(位移動(dòng)畫(huà)))
http://www.cnblogs.com/bavariama/archive/2013/01/29/2881225.html

這里寫(xiě)圖片描述

注意點(diǎn)

屬性動(dòng)畫(huà)是真實(shí)改變View的位置的剥啤,雖然屬性動(dòng)畫(huà)何暮、位移動(dòng)畫(huà)的getLeft等沒(méi)有改變,但是屬性動(dòng)畫(huà)的getX铐殃、getY是改變了的海洼,位移動(dòng)畫(huà)的getX、getY仍未改變富腊!


最后再來(lái)回顧下這張圖

這里寫(xiě)圖片描述

四.小結(jié)#

坐標(biāo)分析上面都分析完了坏逢,基本上涵蓋了自定義View坐標(biāo)計(jì)算、滑動(dòng)赘被、事件分發(fā)等常見(jiàn)場(chǎng)景的坐標(biāo)問(wèn)題是整,希望大家能從中得到收獲。
水平很菜民假,有錯(cuò)誤的地方歡迎指正浮入,大家一起學(xué)習(xí)進(jìn)步!

? 反正擼完這篇羊异,我算是對(duì)于坐標(biāo)系有了更深刻的認(rèn)識(shí)事秀,給自己點(diǎn)個(gè)贊~?
csdn同步博文地址:http://blog.csdn.net/mr_immortalz/article/details/51168278

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市野舶,隨后出現(xiàn)的幾起案子易迹,更是在濱河造成了極大的恐慌,老刑警劉巖平道,帶你破解...
    沈念sama閱讀 212,332評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件睹欲,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡一屋,警方通過(guò)查閱死者的電腦和手機(jī)窘疮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)冀墨,“玉大人闸衫,你說(shuō)我怎么就攤上這事≡唬” “怎么了楚堤?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,812評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)含懊。 經(jīng)常有香客問(wèn)我身冬,道長(zhǎng),這世上最難降的妖魔是什么岔乔? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,607評(píng)論 1 284
  • 正文 為了忘掉前任酥筝,我火速辦了婚禮,結(jié)果婚禮上雏门,老公的妹妹穿的比我還像新娘嘿歌。我一直安慰自己掸掏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布宙帝。 她就那樣靜靜地躺著丧凤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪步脓。 梳的紋絲不亂的頭發(fā)上愿待,一...
    開(kāi)封第一講書(shū)人閱讀 49,919評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音靴患,去河邊找鬼仍侥。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鸳君,可吹牛的內(nèi)容都是我干的农渊。 我是一名探鬼主播,決...
    沈念sama閱讀 39,071評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼或颊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼砸紊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起饭宾,我...
    開(kāi)封第一講書(shū)人閱讀 37,802評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤批糟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后看铆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡盛末,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評(píng)論 2 327
  • 正文 我和宋清朗相戀三年弹惦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片悄但。...
    茶點(diǎn)故事閱讀 38,712評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡棠隐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出檐嚣,到底是詐尸還是另有隱情助泽,我是刑警寧澤,帶...
    沈念sama閱讀 34,389評(píng)論 4 332
  • 正文 年R本政府宣布嚎京,位于F島的核電站嗡贺,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏鞍帝。R本人自食惡果不足惜诫睬,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望帕涌。 院中可真熱鬧摄凡,春花似錦续徽、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至床绪,卻和暖如春客情,著一層夾襖步出監(jiān)牢的瞬間裹匙,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,026評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工铃将, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绘盟,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,473評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子祭钉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容