本文将探究getScrollX(),getScrollY(), getX(),getY(), getLeft(),getRight(),getTranslationX(),getTranslationY()的关系
案例简述:放置了一个水平的scrollview,内放置了长为1200px的线性布局,线性布局内放置的背景视图。
1. 静态测量:
ViewTreeObserver viewTreeObserver = mHorizontalScrollView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
@Override
public void onGlobalLayout() {
Log.e("日志", "水平滑动 = "+mHorizontalScrollView.getWidth() );
Log.e("日志", "线性布局长度 = "+mLinearLayout.getWidth() );
Log.e("日志", "scrollView的scrollX = "+mHorizontalScrollView.getScrollX() );
Log.e("日志", "线性布局 scrollX = "+mLinearLayout.getScrollX() );
mHorizontalScrollView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
scrollView长度 = 720px , LinearLayout长度 = 1200px , scrollView的scrollX = 0 , LinearLayout的scrollX = 0
2.动态执行效果
mBtnOne.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHorizontalScrollView.scrollTo(-200,0);
Log.e("日志", "-200 = "+mHorizontalScrollView.getScrollX() );
Log.e("日志", "线性scrollX = "+mLinearLayout.getScrollX() );
}
});
mBtnTwo.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mHorizontalScrollView.scrollTo(200,0);
Log.e("日志", "+200 = "+mHorizontalScrollView.getScrollX() );
Log.e("日志", "线性scrollX = "+mLinearLayout.getScrollX() );
}
});
由禁止开始进行变化: -------------------------------------
scrollTo(-200,0) ------ scroll = 0 没变化
scrollTo(200,0) ------ scroll = 200 向左滑动200px
再scrollTo(-200,0) --------向右滑了 200px
目前效果是这样的,那么我们来看看具体怎么执行的,先贴代码再分析
1.
@Override
public void scrollTo(int x, int y) {
// we rely on the fact the View.scrollBy calls scrollTo.
if (getChildCount() > 0) {
View child = getChildAt(0);
x = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth());
y = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight());
if (x != mScrollX || y != mScrollY) {
super.scrollTo(x, y);
}
}
}
2.
private static int clamp(int n, int my, int child) {
if (my >= child || n < 0) {
return 0;
}
if ((my + n) > child) {
return child - my;
}
return n;
}
//view中的scrollTo方法
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();
}
}
}
@Deprecated
public void invalidate(Rect dirty) {
final int scrollX = mScrollX;
final int scrollY = mScrollY;
invalidateInternal(dirty.left - scrollX, dirty.top - scrollY,
dirty.right - scrollX, dirty.bottom - scrollY, true, false);
}
主要分析下方法1以及方法2
这里的child自然就是我们水平scrollView的唯一子布局。好了,后面就针对clamp进行分析:
步骤1: 当禁止的时候:执行 scrollview.scrollTo(-200,0)
执行第一个循环: 720 > 1200 || -200 <0 那么 retrun 0 ,则scrollTo(0,0) 所以是没变化的; 此时scroll
步骤2: 紧接着步骤1执行 scrollView.scrollTo(200,0)
执行第一个循环 720 > 1200 || 200 <0 , 进入下一个if判断 200+ 720 > 1200 ? 1200-720 : 200是这样的一个效果,
滑动scrollX()。
那么我得出的对于scrollview的结论是:
当滑动的是负值,那么相当于是移动到起始的位置;
滑动的是是正值,当➕720px,小于线性布局 那么移动的是该值,否则是移动到 线性-720px。
那么区间也就是 0 - (线性px-720) 。 【也就是说会相对禁止向右动 】
3. 关于getLeft() getX() getTranslationX() 关系等
对于父布局静止的状况下: getX() = getLeft()+getTranslactionX();
4.部分源码分析
ScrollView#onMeasure() 方法如下所示,如果ScrollView录入Fill_viewport属性,那么会将ScrollView可获得的高度直接全部设置给第一个子view,也就是不管你写固定的高度dp还是其他的什么其实父组件都是将其修改为match_parent,而且是UNSPECIFED模式
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (!mFillViewport) {
return;
}
final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if (heightMode == MeasureSpec.UNSPECIFIED) {
return;
}
if (getChildCount() > 0) {
final View child = getChildAt(0);
final int widthPadding;
final int heightPadding;
final int targetSdkVersion = getContext().getApplicationInfo().targetSdkVersion;
final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (targetSdkVersion >= VERSION_CODES.M) {
widthPadding = mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin;
heightPadding = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin;
} else {
widthPadding = mPaddingLeft + mPaddingRight;
heightPadding = mPaddingTop + mPaddingBottom;
}
final int desiredHeight = getMeasuredHeight() - heightPadding;
if (child.getMeasuredHeight() < desiredHeight) {
final int childWidthMeasureSpec = getChildMeasureSpec(
widthMeasureSpec, widthPadding, lp.width);
final int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(
desiredHeight, MeasureSpec.EXACTLY);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
}
}
@Override
protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed,
int parentHeightMeasureSpec, int heightUsed) {
final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
+ widthUsed, lp.width);
final int usedTotal = mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin +
heightUsed;
final int childHeightMeasureSpec = MeasureSpec.makeSafeMeasureSpec(
Math.max(0, MeasureSpec.getSize(parentHeightMeasureSpec) - usedTotal),
MeasureSpec.UNSPECIFIED);
child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}