View的基礎知識介紹

轉載請以連接形式標明出處: 本文出自:103style的博客html

《Android開發藝術探索》 學習記錄java


能夠帶着如下問題來看本文:android

  • View的座標系和座標,平移等動畫改變的是什麼屬性?
  • View有哪些事件?
  • 若是獲取系統可識別的最短滑動距離?
  • 若是計算滑動的速度?
  • 單擊、雙擊、長按等事件的監聽?
  • 彈性滑動的實現?

目錄

  • View 與 ViewGroup
  • View 的位置參數
  • MotionEvent 和 TouchSlop
  • VelocityTracker
  • GestureDetector
  • Scroller

View與ViewGroup

Viewbash

public class Viewide

extends Object implements Drawable.Callback,KeyEvent.Callback,AccessibilityEventSource函數

java.lang.Object佈局

android.view.Viewpost

Known direct subclasses學習

AnalogClock , ImageView,KeyboardViewMediaRouteButtonProgressBar , Space , SurfaceView , TextViewTextureViewViewGroup,ViewStub.測試

Known indirect subclasses

AbsListViewAbsSeekBarAbsSpinner , AbsoluteLayoutAutoCompleteTextViewButtonCalendarViewCheckBoxCheckedTextViewChronometer, and 57 others..

ViewGroup

public abstract class ViewGroup

extends View implements ViewParent, ViewManager

java.lang.Object

android.view.View

android.view.ViewGroup

Known direct subclasses

AbsoluteLayout, AdapterView<T extends Adapter>, FragmentBreadCrumbs, FrameLayout, GridLayout, LinearLayout, RelativeLayout, SlidingDrawer, Toolbar, TvView.

Known indirect subclasses

AbsListView, AbsSpinner, CalendarView, DatePicker, ExpandableListView, Gallery, GridView, HorizontalScrollView,ImageSwitcher, and 26 others.

經過上面的官方介紹,咱們能夠看到,View 是咱們日常看到的視圖上全部元素的父類,按鈕Button、文本TextView、圖片ImageView 等。 ViewGroup 也是 View 的子類,ViewGroup 至關與 View 的容器,能夠包含不少的 View.


View的位置參數

View的座標系以下圖:

View座標系

左上角爲原點O(0,0),X、Y軸分別向右向下遞增。 圖中 View 和 ViewGroup 的位置由其四個頂點決定,以View爲例,分別對應四個屬性:LeftTopRightBottom. 因此 Width = Right - Left, Height = Bottom - Top.

Android 3.0 開始,View又增長了 xytranslationXtranslationY 四個參數。 xy 即爲上圖中的A點,分別對應A點在View座標系中的X、Y軸上的座標。 translationXtranslationY則爲相對於父容器ViewGroup的偏移量,默認爲 0。 他們的關係爲: x = left + tranlastionXy = top + tranlastionY.

須要注意的是:在平移過程當中,top 和 left 表示的是原始左上角的位置信息,是不變的,發生改變的是 x、y、translationX、translationY

下面咱們來測試看看:

<!--  activity_main.xml -->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="8dp"
        android:padding="8dp"
        android:text="Hello World!" />
</LinearLayout>
複製代碼
//MainActivity.java
public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        tv.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.e(TAG, "tv.getLeft() = " + tv.getLeft());
                Log.e(TAG, "tv.getTop() = " + tv.getTop());
                Log.e(TAG, "tv.getRight() = " + tv.getRight());
                Log.e(TAG, "tv.getBottom() = " + tv.getBottom());
                Log.e(TAG, "tv.getWidth() = " + tv.getWidth());
                Log.e(TAG, "tv.getHeight() = " + tv.getHeight());
                Log.e(TAG, "tv.getX() = " + tv.getX());
                Log.e(TAG, "tv.getY() = " + tv.getY());
                Log.e(TAG, "tv.getTranslationX() = " + tv.getTranslationX());
                Log.e(TAG, "tv.getTranslationY() = " + tv.getTranslationY());
            }
        });
    }
}
複製代碼

點擊按鈕,打印日誌以下:

MainActivity: tv.getLeft() = 21
MainActivity: tv.getTop() = 21
MainActivity: tv.getRight() = 263
MainActivity: tv.getBottom() = 114
MainActivity: tv.getWidth() = 242
MainActivity: tv.getHeight() = 93
MainActivity: tv.getX() = 21.0
MainActivity: tv.getY() = 21.0
MainActivity: tv.getTranslationX() = 0.0
MainActivity: tv.getTranslationY() = 0.0
複製代碼

咱們能夠看到 left、top、right、bottom 是整形的, 而 x、y、translationX、translationY 是浮點型的


MotionEvent 和 TouchSlop

MotionEvent 即爲咱們點擊屏幕所產生的一些列事件,主要有如下幾個:

  • ACTION_DOWN:手指剛接觸屏幕。
  • ACTION_MOVE:手指在屏幕上滑動。
  • ACTION_UP:手指離開屏幕的一瞬間。
  • ACTION_CANCEL:消耗了DOWN事件卻沒有消耗UP事件,再次觸發DOWN時,會先觸發CANCEL事件。

通常依次點擊屏幕操做,會產生一些列事件:DOWN → 0個或多個 MOVE → UP。 經過MotionEvent 咱們能夠知道事件發生的 x , y 座標, 能夠經過系統提供的 getX()/getY()getRawX()/getRawY()獲取。 getX()/getY()是對於當前View左上角的座標. getRawX()/getRawY()則是對於屏幕左上點的座標.

TouchSlop 則是系統所能識別的最短的滑動距離, 這個距離能夠經過 ViewConfiguration.get(getContext()).getScaledTouchSlop() 得到。 在 Genymotion上的 Google pixel 9.0系統 420dpi 的模擬器上獲得的值以下:

MainActivity: getScaledTouchSlop = 21
複製代碼

VelocityTracker

VelocityTracker 是用來記錄手指滑動過程當中的速度的,包括水平方向和數值方向。 能夠經過以下方式來獲取當前事件的滑動速度:

tv.setOnTouchListener(new View.OnTouchListener() {
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                VelocityTracker velocityTracker = VelocityTracker.obtain();
                velocityTracker.addMovement(event);
                velocityTracker.computeCurrentVelocity(1000);
                float vX = velocityTracker.getXVelocity();
                float vY = velocityTracker.getYVelocity();
                Log.e(TAG, "vX = " + vX + ", vY = " + vY);
                velocityTracker.clear();
                velocityTracker.recycle();
                break;
        }
        return true;
    }
});
複製代碼
MainActivity: vX = 542.164, vY = 271.18683
MainActivity: vX = 2257.9578, vY = 291.47467
MainActivity: vX = 2237.9333, vY = 379.69537
MainActivity: vX = 1676.5919, vY = 697.79443
MainActivity: vX = 1672.0844, vY = 288.5999
MainActivity: vX = 645.7418, vY = 322.51065
MainActivity: vX = 810.2783, vY = 270.19778
複製代碼

固然最後,在不用的時候記得調用如下代碼重置並回收掉 VelocityTracker:

velocityTracker.clear();
velocityTracker.recycle();
複製代碼

GestureDetector

GestureDetector 即手勢檢測,用於輔助咱們捕獲用戶的 單擊、雙擊、滑動、長按等行爲。

使用也很簡單,只須要建立一個下面來看個示例。 在構造函數中建立 經過 gestureDetector = new GestureDetector(context, this) 建立 GestureDetector, 而後實現 GestureDetector.OnGestureListenerGestureDetector.OnDoubleTapListener 接口, 而後在 onTouchEvent 中 返回 gestureDetector.onTouchEvent(event)

public class TestGestureDetector extends View implements GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {
    private static final String TAG = "TestGestureDetector";
    GestureDetector gestureDetector;
    public TestGestureDetector(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gestureDetector = new GestureDetector(context, this);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return gestureDetector.onTouchEvent(event);
    }
    @Override
    public boolean onDown(MotionEvent e) {
        Log.e(TAG, "onDown: action = " + e.getAction());
        return false;
    }
    @Override
    public void onShowPress(MotionEvent e) {
        Log.e(TAG, "onShowPress:");
    }
    @Override
    public boolean onSingleTapUp(MotionEvent e) {
        Log.e(TAG, "onSingleTapUp: " + e.getAction());
        return false;
    }
    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
        Log.e(TAG, "onScroll: e1.action = " + e1.getAction() + ", e2.action = " + e2.getAction());
        return false;
    }
    @Override
    public void onLongPress(MotionEvent e) {
        Log.e(TAG, "onLongPress: action = " + e.getAction());
    }
    @Override
    public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
        Log.e(TAG, "onFling: e1.action = " + e1.getAction() + ", e2.action = " + e2.getAction());
        return false;
    }
    @Override
    public boolean onSingleTapConfirmed(MotionEvent e) {
        Log.e(TAG, "onSingleTapConfirmed: action = " + e.getAction());
        return false;
    }
    @Override
    public boolean onDoubleTap(MotionEvent e) {
        Log.e(TAG, "onDoubleTap: action = " + e.getAction());
        return false;
    }
    @Override
    public boolean onDoubleTapEvent(MotionEvent e) {
        Log.e(TAG, "onDoubleTapEvent: action = " + e.getAction());
        return false;
    }
}
複製代碼

而後在佈局中讓它佔滿屏幕。

tips:

action = 0DOWN 事件

action = 1UP 事件

action = 2MOVE 事件

運行程序,咱們執行一次單擊,一次長按單擊,而後雙擊一次,發下打印日誌以下:

//第一次單擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0
//第一次長按單擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0
//第一次雙擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onLongPress: action = 0
複製代碼

經過上面的日誌信息咱們能夠知道 : 一次 單擊長按單擊 操做會觸發 onDownonShowPressonLongPress三個回調。 雙擊 操做則會依次觸發 onDownonShowPressonDownonShowPressonLongPress 五次回調。

顯示單擊出現 onLongPress 是不合理的,咱們能夠經過 gestureDetector.setIsLongpressEnabled(false) 禁用掉,並且咱們也沒有監聽到 單機和雙擊等其餘回調,這是爲何呢?

這是由於咱們 沒有消耗掉 DOWN 事件,這涉及到事件分發相關的知識了,這裏先不說,後面會寫文章單獨講解。那怎麼消耗掉 DOWN 事件呢?很簡單,只要在 onDown 中返回 true。 修改上述代碼以下,只貼出修改的部分,

public class TestGestureDetector extends View implements GestureDetector.OnGestureListener,
        GestureDetector.OnDoubleTapListener {
    ...
    public TestGestureDetector(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        gestureDetector = new GestureDetector(context, this);
        gestureDetector.setIsLongpressEnabled(false);
    }
    @Override
    public boolean onDown(MotionEvent e) {
        Log.e(TAG, "onDown: action = " + e.getAction());
        return true;
    }
    ...
}
複製代碼

運行程序,在執行一次單擊,一次長按單擊和一次雙擊,日誌以下:

//第一次單擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onSingleTapConfirmed: action = 0
//第一次長按單擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onSingleTapConfirmed: action = 1
//第一次雙擊
TestGestureDetector: onDown: action = 0
TestGestureDetector: onSingleTapUp: 1
TestGestureDetector: onDoubleTap: action = 0
TestGestureDetector: onDoubleTapEvent: action = 0
TestGestureDetector: onDown: action = 0
TestGestureDetector: onDoubleTapEvent: action = 1
複製代碼

咱們能夠看到如今一次單擊則會觸發onDownonSingleTapUponSingleTapConfirmed 這三個回調。 一次長按單擊則會觸發onDownonShowPressonSingleTapUponSingleTapConfirmed 這四個回調。 一次雙擊則會一次觸發onDownonSingleTapUponDoubleTaponDoubleTapEventonDownonDoubleTapEvent 這六個回調。

而咱們在屏幕上快速滑動時,則會觸發 onDownonShowPressonScrollonScrollonFling這五個回調,onShowPress 取決於你在按下和開始滑動以前的時間間隔,短的話就不會有, 是否有 onFling 取決於滑動的距離和速度

TestGestureDetector: onDown: action = 0
TestGestureDetector: onShowPress:
TestGestureDetector: onScroll: e1.action = 0, e2.action = 2
TestGestureDetector: onScroll: e1.action = 0, e2.action = 2
TestGestureDetector: onFling: e1.action = 0, e2.action = 1
複製代碼

下面咱們來統一介紹下這些回調具體的含義把:

方法名 描述 所屬接口
onDown 觸摸View的瞬間,由一個 DOWN 觸發 OnGestureListener
onShowPress 觸摸View未鬆開或者滑動時觸發 OnGestureListener
onSingleTapUp 觸摸後鬆開,在onDown的基礎上加了個 UP 事件,
屬於單擊行爲
OnGestureListener
onScroll 按下並拖動,由一個 DOWN 和 多個 MOVE 組成,
屬於拖動行爲
OnGestureListener
onLongPress 長按事件 OnGestureListener
onFling 快速滑動後鬆開,須要滑動必定的距離 OnGestureListener
onSingleTapConfirmed 嚴格的單擊行爲,
onSingleTapUp以後只能是onSingleTapConfirmed
或 onDoubleTap 中 的一個
OnDoubleTapListener
onDoubleTap 雙擊行爲,和 onSingleTapConfirmed 不共存 OnDoubleTapListener
onDoubleTapEvent 表示雙擊行爲的發生,
一次雙擊行爲會觸發屢次onDoubleTapEvent
OnDoubleTapListener

Scroller

Scroller 用於實現View的彈性滑動,當咱們使用View的 scrollToscrollBy 方法進行滑動時,滑動時瞬間完成的,沒有過渡效果使得用戶體驗很差,這個時候就可使用 Scroler 來解決這一用戶體驗差的問題。 Scroller自己沒法讓View彈性滑動,須要配合View的 computeScroll 方法。

那若是使用Scroller呢? 它的典型代碼是固定的,以下所示。 至於爲何可以實現,咱們下篇文章介紹 View的滑動 的時候再具體分析。

public class TestScroller extends View {
    private static final String TAG = "TestScroller";
    Scroller mScroller;
    public TestScroller(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mScroller = new Scroller(context);
    }
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            postInvalidate();
        }
    }
    private void smoothScrollTo(int destX, int destY) {
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        int deltaX = destX - scrollX;
        int deltaY = destY - scrollY;
        mScroller.startScroll(scrollX, 0, deltaX, 0, 1000);
        mScroller.startScroll(0, scrollY, 0, deltaY, 1000);
        invalidate();
    }
}
複製代碼

若是以爲不錯的話,請幫忙點個讚唄。

以上


掃描下面的二維碼,關注個人公衆號 Android1024, 點關注,不迷路。

Android1024

`

相關文章
相關標籤/搜索