三. View的經常使用API

不忘初心 砥礪前行, Tomorrow Is Another Day !html

相關文章

本文概要:git

  1. 觸摸事件相關
  2. View工做流程相關
  3. 其餘工具
  4. 常見問題

一. 觸摸事件相關

1.1 ScrollTo和ScrollBy

適合對View內容的滑動,只對View的scrollX、scrollY有影響,對View的大小和位置沒有影響.github

對應源碼canvas

//增量滾動,增量是指在已有的滾動偏移量的增量
public void scrollBy(int x, int y) {
        scrollTo(mScrollX + x, mScrollY + y);
}

//絕對滾動
public void scrollTo(int x, int y) {
        if (mScrollX != x || mScrollY != y) {
            int oldX = mScrollX;
            int oldY = mScrollY;
            mScrollX = x;
            mScrollY = y;
            invalidateParentCaches();
            onScrollChanged(mScrollX, mScrollY, oldX, oldY);
            if (!awakenScrollBars()) {
                postInvalidateOnAnimation();
            }
        }
}

複製代碼

源碼中兩個比較重要的參數:bash

  • mScrollX : 水平方向滾動的偏移量(相似絕對座標,能夠理解爲絕對偏移量)
    • 計算方式: mScrollX = 0(初始位置) - 100(結束位置) = -100
  • mScrollY : 豎直方向滾動的偏移量

題外話:網上部分博客說成是座標,看了mScrollX註釋"The offset, in pixels, by which the content of this view is scrolled horizontally." 用個人渣英語一看,特麼不說的是"View的內容在水平方向滾動的偏移量"嗎,因此我的以爲說偏移量更加嚴謹.若有不對歡迎拍磚,請指點.app

由於內部調用invalidate致使重繪,不會走測量佈局過程,因此纔有上述結論.因爲是對View的內容進行滑動,因此需注意滑動方向的問題.ide

對應源碼工具

//由父元素調用dispatchDraw,而後調用三個參數的draw方法.
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {

        //...省略部分代碼
        int sx = 0;
        int sy = 0;
        if (!drawingWithRenderNode) {
            computeScroll();
            sx = mScrollX;
            sy = mScrollY;
        }

        final boolean drawingWithDrawingCache = cache != null && !drawingWithRenderNode;
        final boolean offsetForScroll = cache == null && !drawingWithRenderNode;

        int restoreTo = -1;
        if (!drawingWithRenderNode || transformToApply != null) {
            restoreTo = canvas.save();
        }
        if (offsetForScroll) {
            //平移的是畫布,這就解釋了爲何傳入的方向值要相反.
            canvas.translate(mLeft - sx, mTop - sy);
        } 
            //...省略部分代碼       
}        
複製代碼
1.2 layout 、offsetLeftAndRight與offsetTopAndBottom

對View的L、T、R、B屬性有影響.佈局

示例代碼post

private void scrollMethodOnLayout(int offsetX, int offsetY) {
        //layout(getLeft() + offsetX, getTop() + offsetY, getRight() + offsetX, getBottom() + offsetY);
        offsetLeftAndRight(offsetX);
        offsetTopAndBottom(offsetY);
    }

複製代碼
1.3 LayoutParams

對佈局參數有影響

示例代碼

private void scrollMethodOnLP(int offsetX, int offsetY) {
        //在不清楚父View是什麼類型時,可使用ViewGroup.MarginLayoutParams
        RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
        layoutParams.leftMargin += offsetX;
        layoutParams.topMargin += offsetY;
        setLayoutParams(layoutParams);
    }
複製代碼
1.4 動畫
  • 視圖動畫適合沒有交互性的View
  • 屬性動畫適合有交互性的View,只對View具體屬性值有影響

下一篇詳細講解動畫相關.

1.5 Scroller

實現彈性滑動

使用步驟:

  1. 初始化Scroller
  2. startScroll開啓滑動過程
  3. 在View中重寫computeScroll方法

示例代碼

//1. 第一步
mScroller = new Scroller(context);

//2. 第二步
private void scrollMethodOnScroller(int offsetX, int offsetY) {
        mScroller.startScroll(((View) getParent()).getScrollX(), ((View) getParent()).getScrollY(),
                offsetX, offsetY, 3000);
        invalidate();
}

//3. 第三步
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            ((View) getParent()).scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }

複製代碼

原理:

  1. 首先經過startScroll保存相關的滑動座標信息.
  2. 當咱們調用invalidate進行重繪時,系統會回調computeScroll方法.
  3. 在computeScrollOffset方法里根據時間的流逝,去計算是否滑動完成.
    • 未完成,則繼續invalidate進行重繪.

具體流程能夠看源碼,比較簡單這裏就不單獨分析了.

根據上面Scroller的原理能夠總結出一個彈性滑動的核心思想,那就是在一個時間段裏,將一次大的滑動分解成屢次小的滑動,除了系統的提供的Scrooler,還可使用動畫與延遲策略去實現.

1.6 ViewConfiguration

View系統配置信息

參考:blog.csdn.net/lfdfhl/arti…

1.7 VelocityTracker

速度追蹤,通常用於識別快速滑動

使用步驟:

  1. 初始化VelocityTracker
  2. 接管觸摸事件,獲取當前滑動速度
  3. 重置並回收.

參考:blog.csdn.net/lfdfhl/arti…

1.8 GestureDetector

手勢識別

使用步驟:

  1. 初始化GestureDetector
  2. 接管觸摸事件
  3. 處理手勢識別回調

參考: blog.csdn.net/lfdfhl/arti…

1.9 ViewDragHelper

通常用於自定義ViewGroup中處理子View的拖動.

使用步驟:

  1. 初始化ViewDragHelper
  2. 接管觸摸事件
  3. 處理回調

參考:

1.10 setTranslationX和setTranslationY

只是將View位置改變,不會觸發View的重繪,這是與前面ScrollTo的最大區別.

  • setTranslationX : 設置View在水平方向,相對於它的left位置偏移量.
  • setTranslationY : 同理
1.11 requestDisallowInterceptTouchEvent

請求父元素不要攔截個人事件

二. View工做流程相關

2.1 inflate(佈局解析器)

對應源碼

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        //調用了三個參數的方法
        return inflate(resource, root, root != null);
}

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
}
複製代碼
  1. 當root爲空時,那麼會解析xml佈局,最後返回xml中的根節點.
  2. 當root不爲空時,那麼會解析xml佈局而且添加到根節點root下,最後返回這個根節點root.

另外有一種特殊狀況直接採用三個參數的方法時, 3. 當root不爲空,attachToRoot爲false時,這時不會將解析的xml佈局添加到根節點root中,最後返回xml中的根節點.

這時root的做用,僅僅只是爲了給xml佈局中的根節點提供layoutParams的參數屬性,不然layoutParams的參數屬性會失效,由於其xml中的根節點壓根不知道本身父容器是誰.具體用例好比在已經退出歷史舞臺的ListView中getView時,inflate防止item的佈局參數失效.

若是想詳細的瞭解inflate的源碼實現細節,能夠參考郭嬸的博客,地址blog.csdn.net/guolin_blog…

2.2 onFinishInflate

當xml佈局文件被解析完成時.

2.3 ViewTreeObserver

View樹觀察者,包括佈局、繪製、觸摸事件變化等.

示例代碼

  • 獲取view的寬高
ViewTreeObserver observer = view.getViewTreeObserver();
        observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {

            @SuppressWarnings("deprecation")
            @Override
            public void onGlobalLayout() {
                view.getViewTreeObserver().removeGlobalOnLayoutListener(this);
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
複製代碼
2.4 requestLayout

觸發測量和佈局過程,不會觸發重繪.

使用場景:

通常用於View的位置和大小改變時.

2.5 onSizeChange

View的大小發生改變時.

在View進行佈局過程會被調用.在layout-setFrame-onSizeChange

使用場景:

通常用於初始化與View的大小相關成員變量.

2.6 setWillNotDraw

是否不繪製,默認是true

因爲ViewGroup默認是不繪製本身的,除非設置了背景或者調用了setWillNotDraw設置爲false.纔會繪製本身

使用場景:

通常用於自定義ViewGroup而且想要實現它自己的繪製時,就能夠設置一個背景或者直接調用setWillNotDraw(false)

2.7 invalidate與postInvalidate

觸發View的重繪,但不會調用測量和佈局過程.

二者區別:

  • invalidate : 只能用於主線程
  • postInvalidate : 能夠用於子線程更新UI
    • 內部原理最終仍是經過handler發送message到主線程,而後調用invalidate.
2.8 onAttachFromWindow與onDetachFromWindow
  • onAttachFromWindow : 當一個View綁定到Window上時的調用.
    • 通常用於初始化一些任務等等
  • onDetachFromWindow :同理.

三.其餘工具

3.1 TypeValue

將普通數值轉換成對應數據類型的數值

示例代碼

TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,666,getResources().getDisplayMetrics());
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,666,getResources().getDisplayMetrics());
        TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX,666,getResources().getDisplayMetrics());
複製代碼
3.2 DisplayMetrics

除了系統提供的TypeValue,還能夠本身實現轉換.

示例代碼

public static int dp2Px(Context context, float dp) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (dp * scale + 0.5f);
    }


    public static int px2Dp(Context context, float px) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (px / scale + 0.5f);
    }

    public static int px2Sp(float pxValue, Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pxValue / scale + 0.5f);
    }


    public static int sp2Px(float spValue, Context context) {
        final float scale = context.getResources().getDisplayMetrics().density;
        return (int) (spValue * scale + 0.5f);
    }
    
複製代碼

四.常見問題

在前面幾篇文章中有幾個問題一直未進行解釋,下面對此依次說明.

  • 爲何須要針對wrap_content時進行處理,否則效果等同match_parent?

解答: 默認返回的是specSize(parentSize父元素可用空間)

解決方式: 示例代碼

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        
        //處理wrap_content,設置一個默認的寬或高
        if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, 200);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(200, heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(widthSize, 200);
        }

    }
複製代碼
  • getMeasureWidth和getWidth的區別?

解答: 產生時機不一樣,前者產生於measure過程,後者產生於layout過程

  • onCreate/onStart/onResume中沒法獲取寬高?

解答: 經過了解ActivityThread過程能夠得知,activity生命週期方法和view的measure過程不是同步的.

解決方式: 示例代碼

  1. Activity/View#onWindowFocusChanged
@Override
    public void onWindowFocusChanged(boolean hasFocus) {
        super.onWindowFocusChanged(hasFocus);
        if (hasFocus) {
            int width = view.getMeasuredWidth();
            int height = view.getMeasuredHeight();
            Log.d(TAG, "onWindowFocusChanged, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
        }
    }
複製代碼
  1. view.post(runnable)與ViewTreeObserver
@Override
    protected void onStart() {
        super.onStart();
        //*********
        view.post(new Runnable() {

            @Override
            public void run() {
                int width = view.getMeasuredWidth();
                int height = view.getMeasuredHeight();
            }
        });
        //ViewTreeObserver已經說明,見本文2.3.
    }
複製代碼
  1. 手動view.measure(int widthMeasureSpec, int heightMeasureSpec) 須要根據view的layoutParams區分
//match_parent,沒法得知measureSpec的parentSize


private void measureView() {
        //wrap_content
        int widthMeasureSpec = MeasureSpec.makeMeasureSpec((1 << 30) - 1, MeasureSpec.AT_MOST);
        //具體數值
        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY);
        view.measure(widthMeasureSpec, heightMeasureSpec);
        Log.d(TAG, "measureView, width= " + view.getMeasuredWidth() + " height= " + view.getMeasuredHeight());
    }

複製代碼

關於經常使用工具暫時先到這了,將來也會持續更新,用做實際開發中API文檔使用.

因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.

參考連接:

相關文章
相關標籤/搜索