前方高能~
有問(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)
大體的方法就是這些了
下面借用那篇博客的一張圖:
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è)試
分別點(diǎn)擊A點(diǎn),B點(diǎn)后效果
這里需要注意的是:
點(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ì)是什么樣的呢尔许?
(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à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ě)得很好了
歸納如下:
- 監(jiān)聽(tīng)Draw/Layout事件:ViewTreeObserver
- 將一個(gè)runnable添加到Layout隊(duì)列中:View.post()
- 重寫(xiě)View的onLayout方法
- 重寫(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為正。
(為了好計(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)下面)
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等值改變
2.offsetLeftAndRight、offsetTopAndBottom
offsetLeftAndRight(200);
offsetTopAndBottom(400);
移動(dòng)后getLeft等值改變
3.修改LayoutParams
ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams) getLayoutParams();
lp.leftMargin = getLeft() + 200;
lp.topMargin = getTop() + 400;
setLayoutParams(lp);
移動(dòng)后getLeft等值不改變
4.scrollTo
((View)getParent()).scrollTo(-200,-400);
移動(dòng)后getLeft等值不改變
5.scrollBy
((View)getParent()).scrollBy(-200,-400);
移動(dòng)后getLeft等值不改變
對(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等值不改變
7.位移動(dòng)畫(huà)
TranslateAnimation anim = new TranslateAnimation(0,200,0,400);
anim.setFillAfter(true);
startAnimation(anim);
移動(dòng)后getLeft等值不改變
關(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
注意點(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)回顧下這張圖
四.小結(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