Android 自定义最简单的ScrollView,附带拉动回弹

本文介绍如何在Android中创建一个自定义的ScrollView,并实现拉动回弹的效果。首先,从布局样式开始,然后讲解如何通过构造方法和继承LinearLayout进行初始化,确保控件按纵向排列。接着,详细阐述重新测量控件大小的步骤。最后,提供了demo的下载链接供读者实践。

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

布局样式

<com.example.test.myapplication.customview.MyScrollView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Hello World!" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Hello World!" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Hello World!" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Hello World!" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="Hello World!" />

      <!--省略若干控件 -->

</com.example.test.myapplication.customview.MyScrollView>

构造方法 继承Linearlayout 初始化 为纵向排列

public class MyScrollView extends LinearLayout {

    private static final String TAG = "MyScrollView";

    /**
     * 内容高度
     */
    int measuredHeight;
    /**
     * 显示高度
     */
    int height;

    /**
     * 滑动辅助类
     */
    Scroller mScroller;
    /**
     * 瞬时滑动速度测量类
     */
    VelocityTracker mVelocityTracker;

    public MyScrollView(Context context, AttributeSet attrs) {
        super(context, attrs);
        setOrientation(VERTICAL);
        mScroller = new Scroller(context);
    }


先重新测量控件的大小

@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measuredHeight = 0;
        //得到控件原始显示高度
        height = getMeasuredHeight();
        Log.i(TAG, "onMeasure: " + getMeasuredHeight());
        //得到高度模式 如果是未指定大小 那么重新测量控件的大小
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (heightMode == MeasureSpec.UNSPECIFIED) {
            int count = getChildCount();
            //循环所有子控件重新测量并获得高度
            for (int i = 0; i < count; i++) {
                View v = getChildAt(i);
                measureChild(v, widthMeasureSpec, heightMeasureSpec);
                //注意获取子控件的margin
                MarginLayoutParams lp = (MarginLayoutParams) v.getLayoutParams();
                //加上所有的子控件的高度 就是我们当前控件所展示的高度
                measuredHeight += v.getMeasuredHeight() + lp.bottomMargin + lp.topMargin;
            }
            //调用此方法 重新更改高度
            setMeasuredDimension(getMeasuredWidth(), measuredHeight);
        } else {
            measuredHeight = height;
        }
        Log.i(TAG, "onMeasure: " + getMeasuredHeight());
    }
手势处理 如果手指拉动到正常内容以外 则回弹

float downY;

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        float y = event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (null == mVelocityTracker) {
                    mVelocityTracker = VelocityTracker.obtain();
                }
                //每当手指按下屏幕时 停止一切的滚动
                mScroller.forceFinished(true);

                mVelocityTracker.clear();
                //添加坐标 以便以得到手指滑动速度
                mVelocityTracker.addMovement(event);
                downY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                mVelocityTracker.addMovement(event);
                int move = (int) (downY - y);
                if (move < 0) {
                    //手指向下滑动  允许滑动超过正常范围的-500
                    if (getScrollY() > -500) {
                        //当滑动超过正常范围之后 滑动效果减为四分之一
                        // 造成滑动阻力的样子(这里为了简单方便直接减少为1/4,就不用通过距离算阻尼系数) 否则正常滑动
                        if (getScrollY() < 0) {
                            scrollBy(0, move * 1 / 4);
                            downY = y;
                        } else {
                            scrollBy(0, move);
                            downY = y;
                        }
                    }
                } else if (move > 0) {
                    //手指向上   允许滑动超过正常范围500
                    if (getScrollY() < measuredHeight - height + 500) {
                        //当滑动超过正常范围之后 滑动效果减为四分之一 造成滑动阻力的样子 否则正常滑动
                        if (getScrollY() > measuredHeight - height) {
                            scrollBy(0, move * 1 / 4);
                            downY = y;
                        } else {
                            scrollBy(0, move);
                            downY = y;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //当手指离开屏幕的时候 如果控件已经偏移到内容之外那么就滚动复原 反之就继续惯性滚动
                if (getScrollY() > measuredHeight - height || getScrollY() < 0) {
                    scrollReset();
                } else {
                    scrollFling();
                }
                break;
        }

        return true;
    }


    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }


    /**
     * 滚动复位
     */
    private void scrollReset() {
        int scrollY = getScrollY();
        if (scrollY < 0) {
            mScroller.startScroll(0, scrollY, 0, -scrollY);
            invalidate();
        } else {
            mScroller.startScroll(0, scrollY, 0, -(scrollY - (measuredHeight - height)));
            invalidate();
        }
    }

    /**
     * 惯性滚动
     */
    private void scrollFling() {
        mVelocityTracker.computeCurrentVelocity(1000);
        float yVelocity = mVelocityTracker.getYVelocity();
        /**
         * fling 方法参数注解
         *
         * startX 滚动起始点X坐标
         * startY 滚动起始点Y坐标
         * velocityX   当滑动屏幕时X方向初速度,以每秒像素数计算
         * velocityY   当滑动屏幕时Y方向初速度,以每秒像素数计算
         * minX    X方向的最小值,scroller不会滚过此点。
         * maxX    X方向的最大值,scroller不会滚过此点。
         * minY    Y方向的最小值,scroller不会滚过此点。
         * maxY    Y方向的最大值,scroller不会滚过此点。
         */
        mScroller.fling(0, getScrollY(), 0, (int) -yVelocity * 2, 0, 0, 0, measuredHeight - height);
        invalidate();
    }

以上已基本就是全部代码了。

demo下载地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值