View與ViewGroup
View是Android所有控件的基類(lèi)
ViewGroup是View的組合丘损,ViewGroup可以包含很多View以及ViewGroup笑撞,而包含的ViewGroup又可以包含View和ViewGroup
坐標(biāo)系
Android系統(tǒng)中有兩種坐標(biāo)系:Android坐標(biāo)系和View坐標(biāo)系黄伊。
Android坐標(biāo)系
在Android中泪酱,將屏幕左上角的頂點(diǎn)作為Android坐標(biāo)系的原點(diǎn),這個(gè)原點(diǎn)向右是X軸正方向,向下是Y軸正方向
View坐標(biāo)系
View坐標(biāo)系與Android坐標(biāo)系并不沖突墓阀,兩者是共同存在的
View獲取自身的寬高
width=getRight()-getLeft();
height=getBottom()-getTop();
這樣做比較麻煩毡惜,因?yàn)橄到y(tǒng)已經(jīng)向我們提供了獲取View寬高的方法:getHeight()、getWidth()
View自身的坐標(biāo)
- getTop():獲取View自身頂邊到其父布局頂邊的距離
- getLeft():獲取View自身左邊到其父布局左邊的距離
- getRight():獲取View自身右邊到其父布局左邊的距離
- getBottom():獲取View自身底邊到其父布局頂邊的距離
MotionEvent
- getX() 獲取點(diǎn)擊事件距離控件左邊的距離斯撮,即視圖坐標(biāo)
- getY() 獲取點(diǎn)擊事件距離控件頂邊的距離经伙,視圖坐標(biāo)
- getRawX() 獲取點(diǎn)擊事件距離整個(gè)屏幕左邊的距離 絕對(duì)坐標(biāo)
- getRawY() 獲取點(diǎn)擊事件距離整個(gè)屏幕頂邊的距離 絕對(duì)坐標(biāo)
View的滑動(dòng)
在處理View的滑動(dòng)時(shí),基本思路都是類(lèi)似的:當(dāng)點(diǎn)擊事件傳到View時(shí)勿锅,系統(tǒng)記下觸摸點(diǎn)的坐標(biāo)帕膜,手指移動(dòng)時(shí)記下移動(dòng)后觸摸的坐標(biāo)并計(jì)算偏移量,并通過(guò)偏移量來(lái)修改View的坐標(biāo)
layout()方法
View進(jìn)行繪制的時(shí)候會(huì)調(diào)用onLayout()來(lái)設(shè)置顯示的位置溢十。
我們自定義一個(gè)view
- java代碼 CustomView.java
package com.probuing.androidlight.view;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.Nullable;
public class CustomView extends View {
private int lastX;
private int lastY;
public CustomView(Context context) {
super(context);
}
public CustomView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取手指觸摸點(diǎn)的橫坐標(biāo)和縱坐標(biāo)
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//計(jì)算移動(dòng)距離
int offsetX = x - lastX;
int offsetY = y - lastY;
//調(diào)用layout方法重新繪制位置
layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
break;
}
return true;
}
}
- 隨后在布局文件中引用自定義View
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".activity.ViewLSNActivity">
<com.probuing.androidlight.view.CustomView
android:id="@+id/customview"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_margin="50dp"
android:background="@android:color/holo_red_light"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
offsetLeftAndRight()和offsetTopAndBottom()
其實(shí)也可以用這兩種方法來(lái)替換layout()方法
- java代碼
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取手指觸摸點(diǎn)的橫坐標(biāo)和縱坐標(biāo)
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//計(jì)算移動(dòng)距離
int offsetX = x - lastX;
int offsetY = y - lastY;
offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);
//調(diào)用layout方法重新繪制位置
// layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
break;
}
return true;
}
LayoutParams(改變布局參數(shù))
LayoutParams主要保存了一個(gè)View的布局參數(shù)垮刹,因此我們可以通過(guò)LayoutParams來(lái)改變View的布局參數(shù)從而達(dá)到改變View位置的效果
因?yàn)槲覀冏远x的View的父控件是LinearLayout,所以我們使用了LinearLayout.LayoutParams张弛。
- java代碼
@Override
public boolean onTouchEvent(MotionEvent event) {
//獲取手指觸摸點(diǎn)的橫坐標(biāo)和縱坐標(biāo)
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = x;
lastY = y;
break;
case MotionEvent.ACTION_MOVE:
//計(jì)算移動(dòng)距離
int offsetX = x - lastX;
int offsetY = y - lastY;
/* offsetLeftAndRight(offsetX);
offsetTopAndBottom(offsetY);*/
//調(diào)用layout方法重新繪制位置
// layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
ConstraintLayout.LayoutParams layoutParams = (ConstraintLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin = getLeft()+offsetX;
layoutParams.topMargin = getTop() + offsetY;
setLayoutParams(layoutParams);
break;
}
return true;
}
動(dòng)畫(huà)
我們也可以采用View動(dòng)畫(huà)來(lái)移動(dòng)
- res目錄創(chuàng)建anim目錄 并創(chuàng)建translate.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:duration="1000"
android:fromXDelta="0"
android:toXDelta="300" />
</set>
View動(dòng)畫(huà)不能改變View的位置參數(shù)荒典。但是屬性動(dòng)畫(huà)可以解決位置問(wèn)題
@Override
protected void onStart() {
super.onStart();
ObjectAnimator.ofFloat(customview,"translationX",0,300).setDuration(1000)
.start();
}
scrollTo與scrollBy
scrollTo(x,y)表示移動(dòng)到一個(gè)具體的坐標(biāo)點(diǎn)。而scrollBy(dx,dy)則表示移動(dòng)的增量為dx吞鸭、dy寺董。
屬性動(dòng)畫(huà)
隨著Android3.0屬性動(dòng)畫(huà)的提出,View之前的動(dòng)畫(huà)帶來(lái)的問(wèn)題刻剥,例如響應(yīng)事件位置依然在動(dòng)畫(huà)發(fā)生前的地方遮咖,不具備交互性等也隨之解決。
ObjectAnimator
ObjectAnimator是屬性動(dòng)畫(huà)最重要的類(lèi)透敌,創(chuàng)建一個(gè)ObjectAnimator只需要通過(guò)其靜態(tài)工廠類(lèi)直接返還一個(gè)ObjectAnimator對(duì)象盯滚。參數(shù)包括一個(gè)對(duì)象和對(duì)象的屬性名字,這個(gè)屬性必須有g(shù)et和set方法
ObjectAnimator.ofFloat(customview,"translationX",0,300)
.setDuration(1000)
.start();
下面就是一些常用的可以直接使用的屬性動(dòng)畫(huà)的屬性值
- translationX和translationY:用來(lái)沿著X軸或Y軸進(jìn)行平移
- rotation、rotationX酗电、rotationY:用來(lái)圍繞View的支點(diǎn)進(jìn)行旋轉(zhuǎn)
- PrivotX和PrivotY:控制View對(duì)象的支點(diǎn)位置魄藕,圍繞這個(gè)支點(diǎn)進(jìn)行旋轉(zhuǎn)和縮放變換處理。
- alpha:透明度撵术,默認(rèn)是1,0是代表完全透明
- x和y:描述View對(duì)象在其容器中的最終位置
ValueAnimator
ValueAnimator不提供任何動(dòng)畫(huà)效果背率,它是一個(gè)數(shù)值發(fā)生器,用來(lái)產(chǎn)生一定的有規(guī)律的數(shù)字嫩与。
動(dòng)畫(huà)的監(jiān)聽(tīng)
完整的動(dòng)畫(huà)具有start寝姿、repeat、end划滋、cancel這4個(gè)過(guò)程
@Override
protected void onStart() {
super.onStart();
ObjectAnimator translationX = ObjectAnimator.ofFloat(customview, "translationX", 0, 300).setDuration(1000);
translationX.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
translationX.start();
}
一般情況下 我們比較常用的是onAnimationEnd事件饵筑,Android也提供了AnimatorListenterAdapter來(lái)讓我們選擇必要的事件進(jìn)行監(jiān)聽(tīng)
@Override
protected void onStart() {
super.onStart();
ObjectAnimator translationX = ObjectAnimator.ofFloat(customview, "translationX", 0, 300).setDuration(1000);
translationX.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
Toast.makeText(ViewLSNActivity.this, "end", Toast.LENGTH_SHORT).show();
}
});
translationX.start();
}
組合動(dòng)畫(huà)——AnimatorSet
AnimatorSet類(lèi)提供了一個(gè)play()方法,如果我們向這個(gè)方法中傳入一個(gè)Animator對(duì)象处坪,將會(huì)返回一個(gè)AnimatorSet.Builder的實(shí)例根资,每次調(diào)用方法時(shí)都會(huì)返回Builder自身用于構(gòu)建
- after(Animator anim)將現(xiàn)有動(dòng)畫(huà)插入到傳入的動(dòng)畫(huà)后執(zhí)行
- after(long delay)將現(xiàn)有動(dòng)畫(huà)延遲指定毫秒后執(zhí)行
- before(Animator anim)將現(xiàn)有動(dòng)畫(huà)插入到傳入的動(dòng)畫(huà)之前執(zhí)行
- with(Animator anim)將現(xiàn)有動(dòng)畫(huà)和傳入的動(dòng)畫(huà)同時(shí)執(zhí)行
private void animBuilder() {
ObjectAnimator animator1 = ObjectAnimator.ofFloat(customview, "translationX", 0.0f, 200.0f, 0f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(customview, "scaleX", 1.0f, 2.0f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(customview, "rotationX", 0.0f, 90.0f, 0.0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(3000);
animatorSet.play(animator1).with(animator2).after(animator3);
animatorSet.start();
}
組合動(dòng)畫(huà)——PropertyValuesHolder
除了使用AnimatorSet類(lèi)之外架专,還可以使用PropertyValuesHolder類(lèi)來(lái)實(shí)現(xiàn)組合動(dòng)畫(huà)。使用PropertyValuesHolder類(lèi)只能是多個(gè)動(dòng)畫(huà)一起執(zhí)行玄帕。使用PropertyValuesHolder只能是多個(gè)動(dòng)畫(huà)一起執(zhí)行部脚。得結(jié)合ObjectAnimator.ofPropertyValuesHolder()
private void propertyValuesHolder() {
PropertyValuesHolder valuesHolder1 = PropertyValuesHolder.ofFloat("scaleX", 1.0f, 1.5f);
PropertyValuesHolder valueHolder2 = PropertyValuesHolder.ofFloat("rotationX", 0.0f, 90.0f, 0.0f);
ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(customview, valuesHolder1, valueHolder2);
objectAnimator.setDuration(2000).start();
}
在XML中使用屬性動(dòng)畫(huà)
在res中新建animator目錄(屬性動(dòng)畫(huà)必須放在animator目錄下),新建scale.xml
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="3000"
android:propertyName="scaleX"
android:valueFrom="1.0"
android:valueTo="2.0"
android:valueType="floatType"
>
</objectAnimator>
在程序中引用XML定義得屬性動(dòng)畫(huà)
private void startXMLAnimator() {
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.scale);
animator.setTarget(customview);
animator.start();
}
View的事件分發(fā)機(jī)制
先來(lái)看看Activity組成
一個(gè)Activity包含一個(gè)Window對(duì)象裤纹,這個(gè)對(duì)象是由PhoneWindow實(shí)現(xiàn)的委刘。PhoneWindow將DecorView作為整個(gè)應(yīng)用窗口的根View。而這個(gè)DecorView又將屏幕劃分為兩個(gè)區(qū)域:一個(gè)是TitleView另一個(gè)是ContentView鹰椒。我們平常做應(yīng)用所寫(xiě)的布局就是展示在ContentView中的
解析View的事件分發(fā)機(jī)制
當(dāng)我們點(diǎn)擊屏幕時(shí)锡移,就產(chǎn)生了點(diǎn)擊事件,這個(gè)事件被封裝成了一個(gè)類(lèi):MotionEvent吹零,而當(dāng)這個(gè)MotionEvent產(chǎn)生后罩抗,那么系統(tǒng)就會(huì)將這個(gè)MotionEvent傳遞給View的層級(jí)。MotionEvent在View中的層級(jí)傳遞過(guò)程就是點(diǎn)擊事件的分發(fā)
點(diǎn)擊事件有3個(gè)重要的方法
- dispatchTouchEvent(MotionEvent ev):用于事件的分發(fā)
- onInterceptTouchEvent(MotionEvent ev):用于事件的攔截灿椅,在dispatchTouchEvent中調(diào)用
- onTouchEvent(MotionEvent ev):用來(lái)處理點(diǎn)擊事件套蒂,在dispatchTouchEvent()方法中進(jìn)行調(diào)用
View的事件分發(fā)機(jī)制
當(dāng)點(diǎn)擊事件產(chǎn)生后,事件首先會(huì)傳遞給當(dāng)前的Activity茫蛹,這會(huì)調(diào)用Activity的dispatchTouchEvent()方法(也就是交由Activity中的PhoneWindow來(lái)完成操刀,然后PhoneWindow再把事件處理工作交給DecorView,然后再由DecorView將事件處理工作交給根ViewGroup)
注意:一個(gè)完整的事件的序列是以DOWN開(kāi)始以UP結(jié)束
- 如果ViewGroup要攔截事件的時(shí)候婴洼,那么后續(xù)的事件序列都會(huì)交給它處理骨坑,而不用再調(diào)用onInterceptTouchEvent()方法了。
點(diǎn)擊事件分發(fā)的傳遞規(guī)則
偽代碼表示
public boolean dispatchTouchEvent(MotionEvent ev){
boolean result = false;
if(onInterceptTouchEvent(ev)){
result=super.onTouchEvent(ev)
}else{
result=child.dispatchTouchEvent(ev)
}
return result;
}
事件自上而下傳遞過(guò)程
當(dāng)點(diǎn)擊事件產(chǎn)生后會(huì)由Activity來(lái)處理柬采,傳遞給PhoneWindow欢唾,再傳遞給DecorView,最后傳遞給頂層的ViewGroup粉捻。
對(duì)于根ViewGroup礁遣,點(diǎn)擊事件首先傳遞給它的dispatchTouchEvent(),該ViewGroup的onInterceptTouchEvent()
- 如果返回true肩刃,則表示要攔截這個(gè)事件祟霍,這個(gè)事件就會(huì)交給它的onTouchEvent()方法處理
- 如果返回false,則表示不攔截這個(gè)事件盈包,這個(gè)事件會(huì)交給子元素的dispatchTouchEvent()處理
如此反復(fù)下去沸呐,如果傳遞給底層的View,View是沒(méi)有子View的呢燥,就會(huì)調(diào)用這個(gè)View的dispathTouchEvent()方法崭添,一般最終會(huì)調(diào)用View的onTouchEvent()
事件自下而上傳遞過(guò)程
當(dāng)點(diǎn)擊事件傳遞給底層的View時(shí),如果底層的View的onTouchEvent()方法返回true叛氨,則表示事件由底層的View消耗并處理呼渣。
如果返回false則表示該View不做處理根暑,事件會(huì)傳遞給父View的onTouchEvent()處理,如果父View的onTouchEvent()返回false表示父View也不處理徙邻,則繼續(xù)傳遞給該父View的父View處理,如此反復(fù)
- Activity
- 沒(méi)有onInterceptTouchEvent方法
- 只有dispatchTouchEvent畸裳、onTouchEvent方法
- ViewGroup
- 有 onInterceptTouchEvent缰犁、dispatchTouchEvent、onTouchEvent方法
- View
- 沒(méi)有 onInterceptTouchEvent方法
事件分發(fā)流程
Activity
dispatchTouchEvent:
- 返回值true/false:事件由自己消費(fèi)
- 返回值super:交由子ViewGroup的dispatchTouchEvent處理
ViewGroup
dispatchTouchEvent:
- 返回值true:事件由自己消費(fèi)
- 返回值false:交由父View的onTouchEvent()處理
- 返回值super:傳遞給自己的onInterceptTouchEvent()進(jìn)行分發(fā)
onInterceptTouchEvent:
- 返回值true:表示攔截事件怖糊,交由自己的onTouchEvent處理
- 返回值false/super:表示不攔截事件帅容,交由子View的dispatchTouchEvent()處理
onTouchEvent:
- 返回值true:表示事件自己處理
- 返回值false/super:將事件交由父onTouchEvent處理
View
dispatchTouchEvent:
- 返回值為true:事件由自己消費(fèi)
- 返回值為false:事件交由父View的onTouchEvent處理
- 返回值為super:交由自己的onTouchEvent處理
onTouchEvent
- 返回值true:事件自己消費(fèi)
- 返回值false、super:事件交由父view的onTouchEvent處理伍伤,直至傳遞到Activity的onTouchEvent()
OnTouchListener和onClickListener執(zhí)行順序
當(dāng)一個(gè)View需要處理事件時(shí)并徘,如果設(shè)置了OnTouchListener,那么OnTouchListener中的OnTouch會(huì)被回調(diào)扰魂。
- 如果onTouch返回false麦乞,則當(dāng)前View的onTouchEvent方法會(huì)被調(diào)用
- 如果onTouch返回true,那么onTouchEvent方法將不會(huì)調(diào)用
由此可看出onTouchListener要比onTouchEvent優(yōu)先級(jí)高
在onTouchEvent方法中劝评,如果當(dāng)前設(shè)置的有onClickListener那么onClick就會(huì)被調(diào)用姐直,由此可以看出onClick的優(yōu)先級(jí)最低,處于事件傳遞的尾端
onTouch->onTouchListener->onTouchEvent->onClick->onClickListener