Android View基礎知識總結

《Android開發者藝術》

View的位置參數
  • View初始位置主要由左上角與右下角初始座標決定(mLeft,mRight,mTop,mBottom),單位是像素.該座標系的座標原點爲View父容器的左上角.
  • getLeft():這類函數是用來獲取View控件左邊相對於父容器左邊最開始的距離.
  • getX():這類函數是用來獲取View控件移動後左邊相對於父容器左邊的距離(不是初始距離).
// View.java
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 這些都是View控件在父容器中的初始座標值,單位是像素.
    protected int mLeft;// 初始左上角X座標
    protected int mRight;// 初始右下角X座標
    protected int mTop;// 初始左上角Y座標
    protected int mBottom;// 初始右下角Y座標
    // 獲取View控件相對與父容器控件的初始左上角Y座標
    public final int getTop() {
        return mTop;
    }
    // 設置View控件相對與父容器控件的的初始左上角Y座標
    public final void setTop(int top) {
        ...
    }
    public final int getBottom() {
        return mBottom;
    }
    public final void setBottom(int bottom) {
        ...
    }
    public final int getLeft() {
        return mLeft;
    }
    public final void setLeft(int left) {
        ...
    }
    public final int getRight() {
        return mRight;
    }
    public final void setRight(int right) {
        ...
    }
    // 獲取View控件左邊距離父控件左邊的距離.
    public float getX() {
        return mLeft + getTranslationX();
    }
    public void setX(float x) {
        setTranslationX(x - mLeft);
    }
    // 獲取View控件上邊距離父控件上邊的距離.
    public float getY() {
        return mTop + getTranslationY();
    }
    public void setY(float y) {
        setTranslationY(y - mTop);
    }
    
    // 獲取View控件相對於父容器左上角X軸偏移量
    public float getTranslationX() {
        return mRenderNode.getTranslationX();
    }
    // 設置X軸偏移量
    public void setTranslationX(float translationX) {
        if (translationX != getTranslationX()) {
            invalidateViewProperty(true, false);
            mRenderNode.setTranslationX(translationX);
            invalidateViewProperty(false, true);
    
            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }

    // 獲取View控件相對於父容器左上角Y軸偏移量
    public float getTranslationY() {
        return mRenderNode.getTranslationY();
    }
    // 設置Y軸偏移量
    public void setTranslationY(float translationY) {
        if (translationY != getTranslationY()) {
            invalidateViewProperty(true, false);
            mRenderNode.setTranslationY(translationY);
            invalidateViewProperty(false, true);
    
            invalidateParentIfNeededAndWasQuickRejected();
            notifySubtreeAccessibilityStateChangedIfNeeded();
        }
    }
}

彈性滑動

  • scrollTo():實現了基於所傳遞參數,對View內容得絕對滑動(無法改變View在佈局中的初始座標).
  • scrollBy():最終調用scrollTo(),實現了基於View當前內容位置的相對滑動(無法改變View在佈局中的初始座標).
// View.java
public class View implements Drawable.Callback, KeyEvent.Callback,AccessibilityEventSource {
    // 單位像素,代表View內容左邊與View控件左邊之間的距離.
    protected int mScrollX;
    // 單位像素,代表View內容上邊與View控件上邊之間的距離.
    protected int mScrollY;
    // 基於View內容初始位置的絕對滑動
    // 參數x: x爲+,則View內容向屏幕左側移動.x爲-,則View內容向屏幕右側移動.單位爲像素
    // 參數y: y爲+,則View內容向屏幕上方移動.y爲-,則View內容向屏幕下方移動.單位爲像素
    public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            // 傳入的像素參數最後賦值給了mScrollX和mScrollY.
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
    }
    // 基於View內容當前位置的相對滑動.
    public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
    }
    // 獲取View內容左邊與View控件左邊間的距離.
    public final int getScrollX() {
        return mScrollX;
    }
    // 獲取View內容上邊與View控件上邊間的距離
    public final int getScrollY() {
        return mScrollY;
    }
}
  • 彈性滑動使用
public class MyTextView extends android.support.v7.widget.AppCompatTextView {
    /**
     * 緩慢滑動到指定位置
     *
     * @param destX View內容左邊距離View控件左邊距離
     * @param destY View內容上邊距離View控件上邊距離
     */
    private Scroller mScroller;
    private void smoothScrollTo(int destX, int destY) {
        mScroller = new Scroller(this.getContext());
        // 內容距離控件邊的距離
        int scrollX = getScrollX();
        int scrollY = getScrollY();
        // 計算需要滑動的距離
        int delteX = destX - scrollX;
        int delteY = destY - scrollY;
        // 爲Scroller設置初始值
        mScroller.startScroll(scrollX, scrollY, delteX, delteY, 1000);
        // 重新繪製
        invalidate();
    }
    @Override
    public void computeScroll() {
        // 每次View控件重繪都會調用到這個地方
        if (mScroller != null) {
            // 先計算
            // 可以得出目前時間還需不需要重繪
            // 如果需要重繪,可以算出View內容邊距離View控件邊當前的距離.
            if (mScroller.computeScrollOffset()){
                // 從mScroller中取出當前計算的值,使用scrollTo()讓內容移動
                scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
                // 移動完成之後重新繪製,這樣就能知道下一次是否停止移動了.
                postInvalidate();
            }
        }
    }
}
  • Scroller是一個工具類, 用來計算一定時間之後View內容左邊距離View控件左邊距離,還有View內容上邊距離View控件上邊距離.
public class Scroller  {
    public void startScroll(int startX, int startY, int dx, int dy, int duration) {
        mMode = SCROLL_MODE;//默認是SCROLL_MODE
        mFinished = false;
        mDuration = duration;// 滑動消耗時長
        mStartTime = AnimationUtils.currentAnimationTimeMillis();// 記錄移動開始時間
        // View內容左邊距離View控件左邊距離,也就是滑動起點距離.
        mStartX = startX;
        mStartY = startY;
        // 起點距離加上需要滑動距離,最終得到View內容左邊距離View控件左邊距離,也就是滑動終點距離.
        mFinalX = startX + dx;
        mFinalY = startY + dy;
        // 滑動距離
        mDeltaX = dx;
        mDeltaY = dy;
        // 持續時間倒數
        mDurationReciprocal = 1.0f / (float) mDuration;
    }
    public boolean computeScrollOffset() {
        if (mFinished) {
            // 滑動結束返回
            return false;
        }
        // 從開始滑動到執行到此處流失的時間
        int timePassed = (int)(AnimationUtils.currentAnimationTimeMillis() - mStartTime);
        // 如果流失的時間小於滑動總時長
        if (timePassed < mDuration) {
            switch (mMode) {
            case SCROLL_MODE:
                // mMode默認爲SCROLL_MODE
                // 流失的時間佔總時間的百分比,經過插值器運算得到一個值.
                final float x = mInterpolator.getInterpolation(timePassed * mDurationReciprocal);
                // 滑動的起點加上滑動距離乘以時間流逝百分比,得到當前View內容左邊距離View控件左邊的距離.
                mCurrX = mStartX + Math.round(x * mDeltaX);
                mCurrY = mStartY + Math.round(x * mDeltaY);
                break;
            case FLING_MODE:
                ...
                break;
            }
        }
        else {
            mCurrX = mFinalX;
            mCurrY = mFinalY;
            // 滑動結束
            mFinished = true;
        }
        return true;
    }
}
利用動畫特性來實現移動
/**
 * 緩慢滑動到指定位置
 * @param destX View內容左邊距離View控件左邊距離
 * @param destY View內容上邊距離View控件上邊距離
 */
private void smoothScrollTo(int destX, int destY) {
    // 內容距離控件邊的距離
    final int scrollX = mTextView.getScrollX();
    final int scrollY = mTextView.getScrollY();
    // 計算需要滑動的距離
    final int delteX = destX - scrollX;
    final int delteY = destY - scrollY;
    final ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 1).setDuration(1000);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            // 動畫進度百分比
            float animatedFraction = valueAnimator.getAnimatedFraction();
            int x = (int) (scrollX + (delteX * animatedFraction));
            int y = (int) (scrollY + (delteY * animatedFraction));
            mTextView.scrollTo(x,y);
        }
    });
    valueAnimator.start();
}
常見輔助類
  • MotionEvent中的方法
  1. getX()/getY(): 獲取手指觸碰屏幕點的座標,該座標系原點爲觸碰到的當前View的左上角.
  2. getRawX()/getRawY():獲取手指觸碰屏幕點的座標,該座標系原點爲手機屏幕左上角.
  • TouchSlop爲滑動最小距離,滑動時小於該值將忽略滑動.
// 獲取方法
ViewConfiguration.get(context).getScaledTouchSlop()
  • VelocityTracker:它是用來追蹤手指在滑動過程中的速度,水平和豎直速度都能獲取到.下面是使用方法.
mTextView.setOnTouchListener(new View.OnTouchListener() {
    int pointerId;
    // 獲取VelocityTracker對象
    VelocityTracker mVelocityTracker = VelocityTracker.obtain();
    @Override
    public boolean onTouch(View v, MotionEvent event) {
        mVelocityTracker.addMovement(event);
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                // 獲取第一個觸摸點Id
                pointerId = event.getPointerId(0);
                break;
            case MotionEvent.ACTION_MOVE:
                // 計算1s內的平均速度
                mVelocityTracker.computeCurrentVelocity(1000);
                // 獲取第一個觸摸點在界面滑動的速度。
                // 當從右向左,水平方向的速度爲-.
                float velocityX = mVelocityTracker.getXVelocity(pointerId);
                // 當從下向上,垂直方向的速度爲-.
                float velocityY = mVelocityTracker.getYVelocity(pointerId);
                break;
            case MotionEvent.ACTION_UP:
                // 回收
                mVelocityTracker.clear();
                mVelocityTracker.recycle();
                break;
        }
        return true;
    }
});
  • GestureDetector:手勢檢測,用於檢測用戶單擊,滑動,長按,雙擊等行爲.下面是使用方法.
  1. 實際開發中可以不用它的, 完全在OnTouchEvent中自己實現監聽滑動也可以.另外還有一個OnDoubleTapListener,是用來監聽雙擊這些的,如果實際中要監聽雙擊倒是可以用到GestureDetector.
mTv.setOnTouchListener(new View.OnTouchListener() {
    GestureDetector mGestureDetector = new GestureDetector(getApplicationContext(), new GestureDetector.OnGestureListener() {
        @Override
        public boolean onDown(MotionEvent e) {
            // 手指觸碰屏幕瞬間調用
            return false;
        }

        @Override
        public void onShowPress(MotionEvent e) {
            // 手指觸碰屏幕瞬間後沒有鬆開或者拖動
        }

        @Override
        public boolean onSingleTapUp(MotionEvent e) {
            // 手指觸碰屏幕後鬆開,單機事件
            return false;
        }

        @Override
        public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
            // 手指按下並拖動
            return false;
        }

        @Override
        public void onLongPress(MotionEvent e) {
            // 長按屏幕
        }

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            // 按下屏幕快速滑動後鬆開
            return false;
        }
    });

    @Override
    public boolean onTouch(View v, MotionEvent event) {
        // 解決長按屏幕無法拖動現象
        mGestureDetector.setIsLongpressEnabled(false);
        mGestureDetector.onTouchEvent(event);
        return true;
    }
});
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章