一:概述
下面借用那篇博客的一張圖:
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方法中都沒有進(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取得的值一樣!(我們注意到點(diǎn)擊A,B點(diǎn)都是如此)
這里我們得到一條啟發(fā):
getY和getRawY這一系動(dòng)作都是由MotionEvent來定義產(chǎn)生的榜跌。是得看最后MotionEvent是被哪個(gè)View所消耗闪唆。如果MotionEvent沒有被任何View所消耗,最終返回Activity則getY和getRawY則一致钓葫。如果被View所消耗悄蕾,則具體情況具體分析,getY础浮,getRawY可能一致帆调,也可能不一致。
測(cè)試2:類似在ListView這種有滾動(dòng)軸的控件中會(huì)是什么樣的呢霸旗?
這個(gè)打印我忘記截圖完整了贷帮,這里我說明下打印的log我是在ListView所在的activity中的dispatchTouchEvent,之所以不在activity的onTouchEvent中打印是因?yàn)長(zhǎng)istView中的item消費(fèi)了事件诱告,那么這個(gè)activity的onTouchEvent就不會(huì)打印出Log了撵枢。 貼上代碼
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
LogUtil.m("SecondActivity getX " + event.getX() + " getY " + event.getY());
LogUtil.m("SecondActivity getrawx " + event.getRawX() + " getrawy " + event.getRawY());
}
return super.dispatchTouchEvent(event);
}
這次我們點(diǎn)擊的是item7的頂部。 (PS:這時(shí)可能會(huì)有點(diǎn)好奇精居,我們明明點(diǎn)擊的接近是item7的頂部锄禽,為啥得到的Y指卻不是接近0呢,原因后面講)
這里我們得到一條啟發(fā):
對(duì)于這種滑動(dòng)的ViewGroup靴姿,我們?cè)讷@取ViewGroup的坐標(biāo)值時(shí)并不需要考慮它到底滑動(dòng)了多少(實(shí)際滑動(dòng)的我們應(yīng)該看作是ViewGroup中的View在滑動(dòng))
二.獲取
在上面我們留下了一個(gè)疑問:我們明明點(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)建過程分析 )
下面我們用圖來初略說明(這是我的理解,有誤歡迎指正)
現(xiàn)在我們?cè)賮碚f說怎么取得坐標(biāo)值的時(shí)機(jī)
因?yàn)镸otionEvent提供的獲取坐標(biāo)的方法是在頁(yè)面完完全全顯示在用戶眼前且用戶點(diǎn)擊后才會(huì)使用到的方法铝侵,所以并不存在獲取不到的問題灼伤,下面就論述下 view提供的獲取坐標(biāo)方法
看到這,你可能會(huì)說咪鲜,那還不簡(jiǎn)單當(dāng)布局被加載出來的時(shí)候狐赡,我們?nèi)カ@取不就OK了嗎? 于是我們就會(huì)看到這樣的錯(cuò)誤:在一個(gè)Activity的onCreate方法中嗜诀,設(shè)置完setContentView后猾警,就開始View的getLeft,getTop等方法隆敢,結(jié)果發(fā)現(xiàn)為0发皿,為啥?
原因就是:
對(duì)于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)
我覺得寫得很好了
歸納如下:
監(jiān)聽Draw/Layout事件:ViewTreeObserver
將一個(gè)runnable添加到Layout隊(duì)列中:View.post()
重寫View的onLayout方法
重寫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)分析了械媒。
下面就來說說本文的重頭戲 ——— 坐標(biāo)的計(jì)算。
(之所以說是重頭戲评汰,是因?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為正。
(下面就以這個(gè)為例踪央,我們要將View從原點(diǎn)移動(dòng)到(200,400)的位置臀玄,即B點(diǎn)與C點(diǎn)重合)
想實(shí)現(xiàn)View移動(dòng)大致有這幾種方式(代碼見下面)
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.java
/**
* 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è)問題 問題1:
移動(dòng)計(jì)算值 = 最開始點(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為原來坐標(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ù)值
問題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)畫
我就以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)畫
TranslateAnimation anim = new TranslateAnimation(0,200,0,400); anim.setFillAfter(true); startAnimation(anim);
移動(dòng)后getLeft等值不改變
[圖片上傳中。卖氨。会烙。(7)]
關(guān)于位移動(dòng)畫的補(bǔ)充點(diǎn):
我們經(jīng)常用這樣的需求,要求一個(gè)popupwindow從屏幕底部彈出或者從屏幕頂部彈出双泪。 這里的位移設(shè)置同樣還是如此(原點(diǎn)在屏幕左上角持搜,向右x為正,向下y為正)
這篇博文可以參考學(xué)習(xí)下焙矛,下面這張神圖也是來自這篇博文(Android動(dòng)畫之translate(位移動(dòng)畫)) http://www.cnblogs.com/bavariama/archive/2013/01/29/2881225.html
注意點(diǎn)
屬性動(dòng)畫是真實(shí)改變View的位置的葫盼,雖然屬性動(dòng)畫、位移動(dòng)畫的getLeft等沒有改變村斟,但是屬性動(dòng)畫的getX贫导、getY是改變了的,位移動(dòng)畫的getX蟆盹、getY仍未改變孩灯!
最后再來回顧下這張圖