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

前方高能~
有問題,歡迎指正
本文版權(quán)所有,轉(zhuǎn)載請注明:http://www.lxweimin.com/p/ce05e06676b2

一.概述

網(wǎng)上關(guān)于Android 的view坐標(biāo)挺多的,寫這篇的目的是因為網(wǎng)上搜到的文章大多較簡單,幾乎都是簡單的介紹下獲取的幾個方法坐標(biāo)的幾個方法罷了,但在實戰(zhàn)中,你會發(fā)現(xiàn)可能你學(xué)會的那幾個獲取坐標(biāo)的方法并沒有正確的使用,導(dǎo)致當(dāng)你要計算坐標(biāo)的時候可能會試過幾遍才找到正確的辦法(其實這也正是我容易混淆的地方,所以特地寫篇博客記錄下)

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

大體的方法就是這些了

這里寫圖片描述

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

這里寫圖片描述

view提供的方法

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

MotionEvent提供的方法

getX():獲取點擊事件相對控件左邊的x軸坐標(biāo),即點擊事件距離控件左邊的距離
getY():獲取點擊事件相對控件頂邊的y軸坐標(biāo),即點擊事件距離控件頂邊的距離
getRawX():獲取點擊事件相對整個屏幕左邊的x軸坐標(biāo),即點擊事件距離整個屏幕左邊的距離
getRawY():獲取點擊事件相對整個屏幕頂邊的y軸坐標(biāo),即點擊事件距離整個屏幕頂邊的距離

下面做個測試

這里寫圖片描述

分別點擊A點,B點后效果

這里寫圖片描述

這里需要注意的是:

點擊B點后(可以看到先是回調(diào)TestTextView中的onTouchEvent方法,然后才是MainActivity中的onTouchEvent,因為我在二者的onTouchEvent方法中都沒有進(jìn)行點擊事件的消費處理,所以會往上傳遞,突然扯到了事件分發(fā)機(jī)制,2333~這里就是突然想補(bǔ)充一點,還是扯回坐標(biāo)吧)

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

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

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

getY和getRawY這一系動作都是由MotionEvent來定義產(chǎn)生的。是得看最后MotionEvent是被哪個View所消耗。如果MotionEvent沒有被任何View所消耗,最終返回Activity則getY和getRawY則一致。如果被View所消耗,則具體情況具體分析,getY,getRawY可能一致,也可能不一致

測試2:類似在ListView這種有滾動軸的控件中會是什么樣的呢?

這里寫圖片描述
這里寫圖片描述

(PS:這時可能會有點好奇,我們明明點擊的接近是item7的頂部,為啥得到的Y指卻不是接近0呢,原因后面講)

這里我們得到一條啟示:

對于這種滑動的ViewGroup,我們在獲取ViewGroup的坐標(biāo)值時并不需要考慮它到底滑動了多少(實際滑動的我們應(yīng)該看作是ViewGroup中的View在滑動)

二.獲取

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

當(dāng)然我們有時候碰到的不僅就只有狀態(tài)欄,有時候是狀態(tài)欄與標(biāo)題欄并存的。所以我們在獲取ViewGroup的Y值是一定要注意是否需要減去狀態(tài)欄,標(biāo)題欄(如果有)的高度,否則計算得到的Y值并不是正確的。

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

下面我們用圖來初略說明(這是我的理解,有誤歡迎指正)

這里寫圖片描述

現(xiàn)在我們再來說說怎么取得坐標(biāo)值的時機(jī)

因為MotionEvent提供的獲取坐標(biāo)的方法是在頁面完完全全顯示在用戶眼前且用戶點擊后才會使用到的方法,所以并不存在獲取不到的問題,下面就論述下
view提供的獲取坐標(biāo)方法

看到這,你可能會說,那還不簡單當(dāng)布局被加載出來的時候,我們?nèi)カ@取不就OK了嗎?
于是我們就會看到這樣的錯誤:在一個Activity的onCreate方法中,設(shè)置完setContentView后,就開始View的getLeft,getTop等方法,結(jié)果發(fā)現(xiàn)為0,為啥?

原因就是:

對于View,ViewGroup來說,width、height、top、left等屬性值是在Measure與Layout過程完成之后才開始正確賦值的,而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

我覺得寫得很好了

歸納如下:

  1. 監(jiān)聽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等  
} 

三.計算

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

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

下面就來說說本文的重頭戲 ——— 坐標(biāo)的計算。

(之所以說是重頭戲,是因為我們之前得到的都是一個點的正確坐標(biāo),現(xiàn)在我們需要做的是在得到多個正確坐標(biāo)后,進(jìn)行正確的計算,這樣我們才能實現(xiàn)滑動這樣炫酷的效果`(∩_∩)′)

首先我們需要建立一個概念

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

這里寫圖片描述

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

(下面就以這個為例,我們要將View從原點移動到(200,400)的位置,即B點與C點重合)

想實現(xiàn)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:

移動計算值 = 最開始點坐標(biāo) - 最后移動到的坐標(biāo)
原因是因為最終會調(diào)用這個方法
—— invalidateInternal(l - scrollX, t - scrollY, r - scrollX, b - scrollY, true, false);
其中l(wèi),t,r,b為原來坐標(biāo)點,scrollX,scrollY為目標(biāo)坐標(biāo)點,只有當(dāng)目標(biāo)坐標(biāo)點值是負(fù)數(shù)時,移動到的位置才為正數(shù)!
例如scrollTo ,我們要從(0,0)移動到(200,400)這個點,根據(jù)上面的公式可知為負(fù)值

問題2

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

TestTextView本身是View,scrollTo、scrollBy移動的都是View的Content,如果不加的話,使用的效果則是TestTextView的文字位置變化,而TestTextView本身不會變化。
如果在ViewGroup中使用scrollTo、scrollBy,則移動的是ViewGroup中的View.我們這里需要讓TestTextView移動,則需要先 ((View)getParent()),然后再((View)getParent()).scrollTo...


6.屬性動畫

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

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等值不改變

這里寫圖片描述

關(guān)于位移動畫的補(bǔ)充點:

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

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

這里寫圖片描述

注意點

屬性動畫是真實改變View的位置的,雖然屬性動畫、位移動畫的getLeft等沒有改變,但是屬性動畫的getX、getY是改變了的,位移動畫的getX、getY仍未改變!


最后再來回顧下這張圖

這里寫圖片描述

四.小結(jié)#

坐標(biāo)分析上面都分析完了,基本上涵蓋了自定義View坐標(biāo)計算、滑動、事件分發(fā)等常見場景的坐標(biāo)問題,希望大家能從中得到收獲。
水平很菜,有錯誤的地方歡迎指正,大家一起學(xué)習(xí)進(jìn)步!

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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