不忘初心 砥礪前行, Tomorrow Is Another Day !html
本文概要:git
適合對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註釋"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);
}
//...省略部分代碼
}
複製代碼
對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);
}
複製代碼
對佈局參數有影響
示例代碼
private void scrollMethodOnLP(int offsetX, int offsetY) {
//在不清楚父View是什麼類型時,可使用ViewGroup.MarginLayoutParams
RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) getLayoutParams();
layoutParams.leftMargin += offsetX;
layoutParams.topMargin += offsetY;
setLayoutParams(layoutParams);
}
複製代碼
- 視圖動畫適合沒有交互性的View
- 屬性動畫適合有交互性的View,只對View具體屬性值有影響
下一篇詳細講解動畫相關.
實現彈性滑動
使用步驟:
示例代碼
//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();
}
}
複製代碼
原理:
具體流程能夠看源碼,比較簡單這裏就不單獨分析了.
根據上面Scroller的原理能夠總結出一個彈性滑動的核心思想,那就是在一個時間段裏,將一次大的滑動分解成屢次小的滑動,除了系統的提供的Scrooler,還可使用動畫與延遲策略去實現.
View系統配置信息
速度追蹤,通常用於識別快速滑動
使用步驟:
手勢識別
使用步驟:
參考: blog.csdn.net/lfdfhl/arti…
通常用於自定義ViewGroup中處理子View的拖動.
使用步驟:
參考:
只是將View位置改變,不會觸發View的重繪,這是與前面ScrollTo的最大區別.
請求父元素不要攔截個人事件
對應源碼
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) {
}
複製代碼
另外有一種特殊狀況直接採用三個參數的方法時, 3. 當root不爲空,attachToRoot爲false時,這時不會將解析的xml佈局添加到根節點root中,最後返回xml中的根節點.
這時root的做用,僅僅只是爲了給xml佈局中的根節點提供layoutParams的參數屬性,不然layoutParams的參數屬性會失效,由於其xml中的根節點壓根不知道本身父容器是誰.具體用例好比在已經退出歷史舞臺的ListView中getView時,inflate防止item的佈局參數失效.
若是想詳細的瞭解inflate的源碼實現細節,能夠參考郭嬸的博客,地址blog.csdn.net/guolin_blog…
當xml佈局文件被解析完成時.
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();
}
});
複製代碼
觸發測量和佈局過程,不會觸發重繪.
使用場景:
通常用於View的位置和大小改變時.
View的大小發生改變時.
在View進行佈局過程會被調用.在layout-setFrame-onSizeChange
使用場景:
通常用於初始化與View的大小相關成員變量.
是否不繪製,默認是true
因爲ViewGroup默認是不繪製本身的,除非設置了背景或者調用了setWillNotDraw設置爲false.纔會繪製本身
使用場景:
通常用於自定義ViewGroup而且想要實現它自己的繪製時,就能夠設置一個背景或者直接調用setWillNotDraw(false)
觸發View的重繪,但不會調用測量和佈局過程.
二者區別:
將普通數值轉換成對應數據類型的數值
示例代碼
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());
複製代碼
除了系統提供的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);
}
複製代碼
在前面幾篇文章中有幾個問題一直未進行解釋,下面對此依次說明.
解答: 默認返回的是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);
}
}
複製代碼
解答: 產生時機不一樣,前者產生於measure過程,後者產生於layout過程
解答: 經過了解ActivityThread過程能夠得知,activity生命週期方法和view的measure過程不是同步的.
解決方式: 示例代碼
@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());
}
}
複製代碼
@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.
}
複製代碼
//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文檔使用.
因爲本人技術有限,若有錯誤的地方,麻煩你們給我提出來,本人不勝感激,你們一塊兒學習進步.
參考連接: