初探起因
?一開(kāi)始接觸到坐標(biāo)系相關(guān)的東西是使用ScrollView抓狭,因?yàn)镾crollView布局限制高度應(yīng)該為wrap_content,所以項(xiàng)目中實(shí)際展示出來(lái)的布局效果和理想中的布局效果發(fā)了偏差谜洽。如果給布局內(nèi)部的控件設(shè)置固定DP的話,又會(huì)有適配的問(wèn)題的出現(xiàn),所以就在java代碼使用LayoutParams中對(duì)布局高度進(jìn)行動(dòng)態(tài)賦值饥悴,達(dá)到了自己想要的效果故慈。
?后來(lái)看書看到View這部分的時(shí)候,又看到了坐標(biāo)系這一部分的知識(shí)窗慎,加上身邊大佬的Github里也有研究物喷,就打算簡(jiǎn)單先總結(jié)一下,畢竟這個(gè)東西搞懂了遮斥,自定義View的坐標(biāo)相關(guān)以及自己想實(shí)現(xiàn)一些更靈活的效果的話峦失,就更容易了。
流程步驟
?這里大致分為以下幾步:
????????????1. 了解位置參數(shù)
????????????2. 在程序里讓控件可以隨著我們的手指動(dòng)起來(lái)
????????????3. 對(duì)控件的移動(dòng)范圍給予邊界的限制
????????????4.讓控件“聽(tīng)話”
1. 了解布局參數(shù)
?首先我們需要了解一下坐標(biāo)系的概念术吗。這里我們選取了兩個(gè)參照物尉辑,是為了更好的區(qū)別后面的參數(shù)。
?這里有兩個(gè)參照坐標(biāo)系较屿,相對(duì)圖片控件來(lái)說(shuō)隧魄。一個(gè)是根布局,使用黃色標(biāo)注的布局的坐標(biāo)系隘蝎,左上角為坐標(biāo)原點(diǎn)(0,0)购啄,橫向?yàn)閄軸,縱向?yàn)閅軸嘱么。另一個(gè)是父布局狮含,使用了白色標(biāo)注。同上曼振。
?我們?cè)谶@里暫且稱圖片控件為子View几迄,我們子View的位置是由四個(gè)頂點(diǎn)決定,分別對(duì)于四個(gè)屬性:top冰评,left映胁,right,bottom甲雅。其中解孙,top是左上角縱坐標(biāo)坑填,left是左上角橫坐標(biāo),right是右下角橫坐標(biāo)弛姜,bottom是右下角縱坐標(biāo)穷遂。當(dāng)然這些是相對(duì)于我們上面提到的父布局來(lái)說(shuō)的。它是一種相對(duì)坐標(biāo)娱据。
?通俗點(diǎn)來(lái)說(shuō)的話蚪黑,
getLeft()
是子View的左邊相對(duì)于父布局的左邊的距離,getTop()
是子View的上邊相對(duì)于父布局的上邊的距離中剩,getRight()
是子View的右邊相對(duì)于父布局的左邊的距離忌穿,getBottom()
是子布局的下邊相對(duì)于父布局的上邊的距離。
?上面已經(jīng)說(shuō)到了子View的四個(gè)參數(shù)屬性结啼,因?yàn)槲覀円屪覸iew隨我們的手指移動(dòng)掠剑,所以在手指接觸屏幕后,我們可以通過(guò)MotionEvent得到點(diǎn)擊當(dāng)時(shí)的x和y坐標(biāo)郊愧,系統(tǒng)為我們提供了getX()
,getY()
,getRawX()
,getRawY()
這四個(gè)方法朴译,前兩個(gè)方法是觸摸點(diǎn)相對(duì)于子View左上角原點(diǎn)的x和y坐標(biāo),后兩個(gè)方法是相對(duì)于手機(jī)屏幕的x和y坐標(biāo)属铁。
?要想我們的控件動(dòng)起來(lái)眠寿,我們還需要了解兩個(gè)View的位置參數(shù),他們?cè)赩iew移動(dòng)的時(shí)候會(huì)用到焦蘑。這兩個(gè)方法就是
getTranslationX()
和getTranslationY()
,他們是View相對(duì)自身move后的X和Y軸的偏移量盯拱。這兩個(gè)值是相對(duì)來(lái)說(shuō)的,相對(duì)你初始化布局時(shí)候View的位置例嘱,X軸方向向左為負(fù)值狡逢,向右為正值,Y軸方向向上為負(fù)值拼卵,向下為正值奢浑。?好了,到這里腋腮,我們了解了十個(gè)參數(shù)屬性:
?
getLeft()
?
getTop()
?
getRight()
?
getBottom()
?
getX()
?
getY()
?
getRawX()
?
getRawY()
?
getTranslationX()
?
getTranslationY()
?那么接下來(lái)我們就在代碼中使用它們來(lái)讓我們的控件可以隨手指移動(dòng)雀彼。
2. 在程序里讓控件可以隨著我們的手指動(dòng)起來(lái)
?布局很簡(jiǎn)單,上面我有提到為了區(qū)分參數(shù)所以中間使用了一個(gè)父布局嵌套低葫,當(dāng)然大家也可以不用父布局详羡,直接在根布局下寫一個(gè)ImageView控件仍律。
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/rl_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorPrimary"
tools:context=".MainActivity">
<RelativeLayout
android:id="@+id/rl_parent"
android:layout_width="300dp"
android:layout_height="500dp"
android:layout_centerInParent="true"
android:background="@color/text_bg">
<ImageView
android:id="@+id/iv_test_icon"
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/head_icon" />
</RelativeLayout>
</RelativeLayout>
?寫完布局后嘿悬,我們?cè)贏S中可以預(yù)覽到是這個(gè)樣子
?然后我們?cè)贛ainActivity中就可以開(kāi)始寫我們想要的效果啦。
?因?yàn)槲覀兿胍丶S我們的手指觸摸移動(dòng)水泉,所以在這里我們用到了setOnTouchListener
這個(gè)監(jiān)聽(tīng)善涨。上面我們也提到了getX()
和getY()
這兩個(gè)方法是MotionEvent
調(diào)用得到的值窒盐。而MotionEvent
這個(gè)對(duì)象我們正好可以在setOnTouchListener
下的onTouch
方法里拿到這個(gè)對(duì)象。
?說(shuō)到onTouch
钢拧,我們還需要知道蟹漓,關(guān)于觸摸,我們會(huì)發(fā)生的狀態(tài)有三種源内,按下葡粒,移動(dòng),抬起膜钓。相應(yīng)的嗽交,在onTouch
方法里的MotionEvent
對(duì)象我們可以通過(guò)判斷ACTION_DOWN
,ACTION_MOVE
颂斜,ACTION_UP
來(lái)決定我們的代碼在哪里執(zhí)行夫壁。
?接下來(lái),我們?cè)?code>ACTION_DOWN的時(shí)候去獲取到x和y的值沃疮,然后在ACTION_MOVE
的時(shí)候先計(jì)算我們的坐標(biāo)移動(dòng)的x和y的位移距離盒让,然后再通過(guò)setTranslationX()
/setTranslationY()
去讓控件跟著改變距離就可以讓我們的控件動(dòng)起來(lái)了!
ivTestIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int xOffset = x - mLastX;//得到X的位移距離
int yOffset = y - mLastY;//得到Y(jié)的位移距離
ivTestIcon.setTranslationX(ivTestIcon.getTranslationX() + xOffset);
ivTestIcon.setTranslationY(ivTestIcon.getTranslationY() + yOffset);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
?這里需要注意一下的是onTouch
的返回值要改為true司蔬,因?yàn)閒alse的話會(huì)攔截之后的動(dòng)作邑茄,執(zhí)行ACTION_DOWN
之后就不會(huì)再執(zhí)行之后的動(dòng)作了。
?然后我們就可以看到下面的效果了
?那么現(xiàn)在手指觸摸控件是可以開(kāi)始移動(dòng)了俊啼,但是我們會(huì)發(fā)現(xiàn)一個(gè)問(wèn)題撩扒,當(dāng)我們的圖片超出父布局的范圍的部分就消失了,這樣的體驗(yàn)很不好吨些,所以搓谆,接下來(lái)我們給我們圖片的規(guī)定一個(gè)移動(dòng)范圍,讓它只能在我們的父布局內(nèi)部移動(dòng)豪墅。
3. 對(duì)控件的移動(dòng)范圍給予邊界的限制
?首先我們得獲取我們子View的寬高和父布局的寬高(這里以我的布局為例子)泉手,目的是為了計(jì)算我們子View的活動(dòng)范圍來(lái)判斷我們的邊界限制條件。然后偶器,我們用父布局的寬度減去子View的寬度就得到了它可以移動(dòng)的X軸的范圍斩萌,同理,我們得到它可以移動(dòng)的Y軸的范圍屏轰。然后因?yàn)槲覀兿鄬?duì)父布局為坐標(biāo)范圍颊郎,所以我們?cè)O(shè)定最小范圍就是0。
ivTestIcon.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View view, MotionEvent motionEvent) {
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN:
mLastX = x;
mLastY = y;
break;
case MotionEvent.ACTION_MOVE:
int xOffset = x - mLastX;//得到X的位移距離
int yOffset = y - mLastY;//得到Y(jié)的位移距離
final int oldTransX = (int) ivTestIcon.getTranslationX();
final int newTransX = oldTransX + xOffset;
final int transXLowerLimit = 0;
if (newTransX < transXLowerLimit) {
xOffset = transXLowerLimit - oldTransX;//X霎苗,left姆吭,邊界判斷
}
final int transXUpperLimit = parentW - childW;
if (newTransX > transXUpperLimit) {
xOffset = transXUpperLimit - oldTransX;//X,right,邊界判斷
}
final int oldTransY = (int) ivTestIcon.getTranslationY();
final int newTransY = oldTransY + yOffset;
final int transYLowerLimit = 0;
if (newTransY < transYLowerLimit) {
yOffset = transYLowerLimit - oldTransY;//Y唁盏,top,邊界判斷
}
final int transYUpperLimit = parentH - childH;
if (newTransY > transYUpperLimit) {
yOffset = transYUpperLimit - oldTransY;//Y内狸,bottom检眯,邊界判斷
}
ivTestIcon.setTranslationX(ivTestIcon.getTranslationX() + xOffset);
ivTestIcon.setTranslationY(ivTestIcon.getTranslationY() + yOffset);
break;
case MotionEvent.ACTION_UP:
break;
}
return true;
}
});
?然后我們就可以看到我們?nèi)ove我們的子View的時(shí)候,就不會(huì)有超出父布局的部分了昆淡。
4.讓控件“聽(tīng)話”
?這里實(shí)現(xiàn)的效果是锰瘸,我們給我們的子View去再次設(shè)定更小的范圍,讓子View在我們ACTION_UP
的時(shí)候昂灵,自己置頂或者到底部避凝。這里的子View自己移動(dòng)的部分是使用了屬性動(dòng)畫ObjectAnimator
,以后會(huì)寫一個(gè)動(dòng)畫的總結(jié)博客來(lái)總結(jié)自己動(dòng)畫的使用眨补。
final int transYLimit = parentH - childH;
final int transYHalfLimit = transYLimit / 2;
final float translationY = ivTestIcon.getTranslationY();
if (translationY == 0 || translationY == transYLimit) {
return;
}
if (translationY < transYHalfLimit) {
mTransAnimator.setDuration((long) (1 * translationY));
mTransAnimator.setFloatValues(translationY, 0);
} else {
mTransAnimator.setDuration((long) (1 * (transYLimit - translationY)));
mTransAnimator.setFloatValues(translationY, transYLimit);
}
mTransAnimator.start();
?然后運(yùn)行恕曲,我們的控件就可以聽(tīng)代碼的話,自動(dòng)置頂或者到底部了