Android 滑动以及动画移动的基础解析

本文探讨Android中ScrollView的滑动机制,通过案例分析getScrollX(), getScrollY(), getX(), getY(), getLeft(), getRight(), getTranslationX(), getTranslationY()之间的关系。在静态测量中,ScrollView长度720px,LinearLayout长度1200px。动态执行过程中,scrollTo方法用于改变滑动位置,负值表示向左滑动至起始位置,正值根据条件决定滑动距离。在父布局静止时,getX()等于getLeft()加上getTranslationX()。ScrollView#onMeasure()方法涉及如何处理子View的尺寸。" 82704078,7463830,QT编程:深入理解QStateMachine状态机应用,"['QT开发', '图形界面', '状态管理', '动画编程']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文将探究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);
    }

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值