android开发&自定义View实现ios滑动回弹

在IOS中,在过度滑动时,整体布局会进行偏移,松手后,会出现回弹效果

安卓中则大多数控件都没有这种功能,在这里,可以自定义一个ViewGroup容器,针对该容器包裹的内容,可以进行过度滚动

android开发&自定义View实现ios滑动回弹

为了实现,我 们需要进行接下来的处理

一、 创建ViewGroup布局

因为是要实现一个容器,因此需要自定义一个ViewGroup,然后重载构造函数,这里定义该控件名字为:OverScrollContainer

/**
 * Created on 2018/10/10  15:31
 * function : 包裹容器;用于在view外部形成一个可以over-scroll的视图
 *
 *
 * 只能有一个子view,否则,视图将加载错乱
 * 仅支持纵轴过度滑动
 *
 * @author mnlin
 */
class OverScrollContainer : ViewGroup {
    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
}

如代码注释描述,该控件只支持纵轴方向的滚动,只能有一个Child

二、 定义可使用的属性

在初始阶段,我们定义的属性会简单一些,只要可以满足基本的需要就好。

纵轴滚动有两种方式:向上、向下

那么对应的,我们需要暴露以下的基本属性:

<!--弹性 布局 距离-->
<declare-styleable name="OverScrollContainer">
    <!--弹性滚动顶部的距离-->
    <attr name="overScrollDimen_top" format="dimension"/>
    <!--弹性滚动底部的距离-->
    <attr name="overScrollDimen_bottom" format="dimension"/>
    <!--超出滚动部分的颜色-->
    <attr name="overScroll_bg_color" format="color"/>
    <!--顶部过度滚动效果-->
    <attr name="enable_top" format="boolean"/>
    <!--底部过度滚动效果-->
    <attr name="enable_bottom" format="boolean"/>
</declare-styleable>

这样,在使用时,就可以根据说明,轻易的配置控件显示效果

接下来我们需要考虑,怎么来使用这些属性;

毋庸置疑的是,这些属性都将作用于OverScrollContainer控件,如果我们定义的是View控件,而非ViewGroup,那就没的商量,只要把这些 属性在xml布局中,<OverScrollContainer>标签上配置一下就好。但对于一个ViewGroup来说,为了使用这些属性,一般都会定义自己的LayoutParams,通过LayoutParams来处理效果的显示

就像FrameLayout一样:

/**
 * Per-child layout information for layouts that support margins.
 * See {@link android.R.styleable#FrameLayout_Layout FrameLayout Layout Attributes}
 * for a list of all child view attributes that this class supports.
 *
 * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
 */
public static class LayoutParams extends MarginLayoutParams {
    /**
     * Value for {@link #gravity} indicating that a gravity has not been
     * explicitly specified.
     */
    public static final int UNSPECIFIED_GRAVITY = -1;

    /**
     * The gravity to apply with the View to which these layout parameters
     * are associated.
     * <p>
     * The default value is {@link #UNSPECIFIED_GRAVITY}, which is treated
     * by FrameLayout as {@code Gravity.TOP | Gravity.START}.
     *
     * @see android.view.Gravity
     * @attr ref android.R.styleable#FrameLayout_Layout_layout_gravity
     */
    public int gravity = UNSPECIFIED_GRAVITY;

    public LayoutParams(@NonNull Context c, @Nullable AttributeSet attrs) {
        super(c, attrs);

        final TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.FrameLayout_Layout);
        gravity = a.getInt(R.styleable.FrameLayout_Layout_layout_gravity, UNSPECIFIED_GRAVITY);
        a.recycle();
    }

    public LayoutParams(int width, int height) {
        super(width, height);
    }

    /**
     * Creates a new set of layout parameters with the specified width, height
     * and weight.
     *
     * @param width the width, either {@link #MATCH_PARENT},
     *              {@link #WRAP_CONTENT} or a fixed size in pixels
     * @param height the height, either {@link #MATCH_PARENT},
     *               {@link #WRAP_CONTENT} or a fixed size in pixels
     * @param gravity the gravity
     *
     * @see android.view.Gravity
     */
    public LayoutParams(int width, int height, int gravity) {
        super(width, height);
        this.gravity = gravity;
    }

    public LayoutParams(@NonNull ViewGroup.LayoutParams source) {
        super(source);
    }

    public LayoutParams(@NonNull ViewGroup.MarginLayoutParams source) {
        super(source);
    }

    /**
     * Copy constructor. Clones the width, height, margin values, and
     * gravity of the source.
     *
     * @param source The layout params to copy from.
     */
    public LayoutParams(@NonNull LayoutParams source) {
        super(source);

        this.gravity = source.gravity;
    }
}

这样一来,在FrameLayout布局中,子View只需要指定gravity,就可以调整自己在父布局中的显示效果。

更重要的是,使用LayoutParams的话,将来想要添加一些新的属性时,会方便的多。

我们仿照FrameLayout,来实现OverScrollContainer对应的LayoutParams

/**
 * 自定义layoutParams,便于实现自定义的属性
 */
class LayoutParams : ViewGroup.MarginLayoutParams {
    /**
     * over-scroll的距离
     */
    var overScrollDimenTop: Int = 96
    var overScrollDimenBottom: Int = 96

    /**
     * 是否可以回弹
     */
    var topEnable = true
    var bottomEnable = true

    /**
     * 滚动后留存的背景颜色
     */
    var bgColor = 0
    var bgDrawable: Drawable = ColorDrawable(Color.TRANSPARENT)

    constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
        val a = c.obtainStyledAttributes(attrs, R.styleable.OverScrollContainer)
        overScrollDimenTop = a.getDimensionPixelSize(R.styleable.OverScrollContainer_overScrollDimen_top, 96)
        overScrollDimenBottom = a.getDimensionPixelSize(R.styleable.OverScrollContainer_overScrollDimen_bottom, 96)
        topEnable = a.getBoolean(R.styleable.OverScrollContainer_enable_top, true)
        bottomEnable = a.getBoolean(R.styleable.OverScrollContainer_enable_bottom, true)
        bgColor = a.getColor(R.styleable.OverScrollContainer_overScroll_bg_color, 0)
        bgDrawable = ColorDrawable(bgColor)
        a.recycle()
    }

    constructor(width: Int, height: Int) : super(width, height)

    constructor(source: ViewGroup.LayoutParams) : super(source)

    constructor(source: ViewGroup.MarginLayoutParams) : super(source)

    constructor(source: OverScrollContainer.LayoutParams) : super(source) {
        overScrollDimenTop = source.overScrollDimenTop
        overScrollDimenBottom = source.overScrollDimenBottom
        topEnable = source.topEnable
        bottomEnable = source.bottomEnable
        bgColor = source.bgColor
        bgDrawable = source.bgDrawable
    }
}

然后在自定义的 ViewGroup 中,重写 LayoutParams的生成方法:

override fun generateDefaultLayoutParams(): OverScrollContainer.LayoutParams {
    return OverScrollContainer.LayoutParams(MATCH_PARENT, MATCH_PARENT)
}

override fun generateLayoutParams(attrs: AttributeSet): OverScrollContainer.LayoutParams {
    return OverScrollContainer.LayoutParams(context, attrs)
}

override fun generateLayoutParams(lp: ViewGroup.LayoutParams): ViewGroup.LayoutParams {
    if (lp is OverScrollContainer.LayoutParams) {
        return OverScrollContainer.LayoutParams(lp)
    } else if (lp is ViewGroup.MarginLayoutParams) {
        return OverScrollContainer.LayoutParams(lp)
    }
    return OverScrollContainer.LayoutParams(lp)
}

三、自定义 measure 处理

前面也说了,在初期,我们规定该布局只能管理一个可滚动的View(或者ViewGroup),因此,measure 时,只需要看一下第一个布局的宽高,然后让自身宽高等于子布局的宽高即可:

/**
 * 测量子view高度
 */
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    if (childCount == 1) {
        //自身尺寸遵循唯一子布局的尺寸
        val child = getChildAt(0)
        val lp = child.layoutParams as LayoutParams
        measureChild(child, widthMeasureSpec, heightMeasureSpec)
        setMeasuredDimension(child.measuredWidth + lp.marginStart + lp.marginEnd + paddingStart + paddingEnd,
                child.measuredHeight + lp.topMargin + lp.bottomMargin + paddingTop + paddingBottom)
    } else {
        setMeasuredDimension(0, 0)
    }
}

这里设定自身的宽为:child.measuredWidth + lp.marginStart + lp.marginEnd + paddingStart + paddingEnd
设定自身高为:child.measuredHeight + lp.topMargin + lp.bottomMargin + paddingTop + paddingBottom

当子布局不存在或者超过一个,那么自身宽高皆为 0 ,将不会显示出来

四、自定义 layout 处理

measure 之后,我们确定一下唯一子view的上下左右位置。

/**
 * 规范子view的坐标位置
 */
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    val childCount = childCount
    if (childCount == 1) {
        val child = getChildAt(0)
        val lp = child.layoutParams as LayoutParams
        val left_p = lp.marginStart + paddingStart
        val top_p = lp.topMargin + paddingTop
        val right_p = left_p + child.measuredWidth
        val bottom_p = top_p + child.measuredHeight
        child.layout(left_p, top_p, right_p, bottom_p)
    } else {
        //当子布局不存在,或者超过一个,则不进行正常布局
        //setFrame(0,0,0,0);
    }
}

同样的,我们也考虑了paddingmargin对布局造成的影响

五、自定义 draw 处理

在处理draw流程时,需要考虑ScrollXScrollY对布局造成的影响,同时,需要绘制出滚动显示的背景,以及自定义添加的一些文字提示,因此首先定义一些局部变量:


    /**
     * 手指按下的坐标
     * 当前的坐标
     */
    private var down_y: Float = 0.toFloat()
    private var current_y: Float = 0.toFloat()

    /**
     * 控制器
     */
    private var controlListener: ControlInterceptListener? = null

    /**
     * 文字画笔
     */
    private var singlePaint = TextPaint().also {
        it.textSize = 28.toFloat()
        it.color = Color.RED
    }

    /**
     * 平滑滚动控制
     */
    private val singleScroller = Scroller(this.context)

然后根据 scrollY 来绘制背景,scrollY 实际是指该方法的返回值:

/**
 * Return the scrolled top position of this view. This is the top edge of
 * the displayed part of your view. You do not need to draw any pixels above
 * it, since those are outside of the frame of your view on screen.
 *
 * @return The top edge of the displayed part of your view, in pixels.
 */
public final int getScrollY() {
    return mScrollY;
}

根据view的内容滚动原理:

  • scrollY小于0时,表示向下滑动手指,顶部出现滚动区域;
  • scrollY大于0时,表示向上滑动手指;

/**
 * 添加头部的填充内容
 */
override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)

    //存在滚动效果时,进行绘制
    if (scrollY != 0) {
        (getChildAt(0)?.layoutParams as LayoutParams?)?.let { lp ->
            //上部过度滚动效果
            if (scrollY < 0) {
                //绘制默认顶部图形
                lp.bgDrawable.run {
                    setBounds(lp.marginStart + paddingStart, -lp.overScrollDimenTop, width - lp.marginEnd - paddingEnd, lp.topMargin + paddingTop)
                    draw(canvas)
                }

                //绘制文字,保证滑动时,跟随在最顶部
                "你是最棒的!!!".let {
                    singlePaint.color = Color.argb((-1.0 * scrollY / lp.overScrollDimenTop * 0xFF).toInt(), 0xFF, 0x00, 0x00)
                    canvas.drawText(it, (width - singlePaint.measureText(it)) / 2, scrollY - singlePaint.fontMetrics.top, singlePaint)
                }
            }


            //下部过度滚动效果
            if (scrollY > 0) {
                //绘制默认顶部图形
                lp.bgDrawable.run {
                    setBounds(lp.marginStart + paddingStart, height - lp.bottomMargin - paddingBottom, width - lp.marginEnd - paddingEnd, height + lp.overScrollDimenBottom)
                    draw(canvas)
                }

                //绘制文字,保证滑动时,跟随在最顶部
                "我是最棒的!!!".let {
                    singlePaint.color = Color.argb((1.0 * scrollY / lp.overScrollDimenBottom * 0xFF).toInt(), 0xFF, 0x00, 0x00)
                    canvas.drawText(it, (width - singlePaint.measureText(it)) / 2, height + scrollY - singlePaint.fontMetrics.bottom, singlePaint)
                }
            }
        }
    }
}

六、拦截事件,获取坐标信息

在上面的代码中,我们使用了 画笔 singlePaint 等对象,还有一些变量如:手指按压位置坐标当前手指坐标 等数据,在触发滑动操作时,是需要考虑在内的,因此需要不断更新。

当然,还有点击事件的处理,现在不妨考虑的简单一些:所有发生在 OverScrollContainer 上的滑动事件,都会被拦截进行处理。

为了灵活性,我们还向外部提供一个接口,可以控制是否可以拦截事件,不设置监听器的的话默认的话,默认事件会被OverScrollContainer拦截


/**
 * 判断是否可以拦截事件
 */
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
    when (ev.action) {
        MotionEvent.ACTION_DOWN -> {
            down_y = ev.y
        }
    }

    //在滚动时,指定所有父布局不能拦截自身的点击事件
    return if (ev.action == MotionEvent.ACTION_MOVE
            && childCount != 0
            && (getChildAt(0).layoutParams as LayoutParams).let { it.bottomEnable or it.topEnable }
            && controlListener?.canIntercept(ev) != false) {
        var temp_parent = parent ?: null
        while (temp_parent != null) {
            parent.requestDisallowInterceptTouchEvent(true)
            temp_parent = temp_parent.parent ?: null
        }
        true
    } else {
        false
    }
}

/**
 * 处理滑动逻辑
 */
override fun onTouchEvent(event: MotionEvent): Boolean {
    when (event.action) {
        MotionEvent.ACTION_MOVE -> {
            current_y = event.y
            val result = (down_y - current_y).toInt()

            (getChildAt(0)?.layoutParams as LayoutParams?)?.let { lp ->
                //判断当前是否可以滑动
                when {
                    result < 0 && lp.topEnable && lp.overScrollDimenTop > 0 -> lp.overScrollDimenTop
                    result > 0 && lp.bottomEnable && lp.overScrollDimenBottom > 0 -> lp.overScrollDimenBottom
                    else -> null
                }?.let {
                    scrollTo(0, if (Math.abs(result) > it) result / Math.abs(result) * it else result)
                }
            }
        }

        MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
            //回归原来位置,平滑滚动
            singleScroller.startScroll(scrollX, scrollY, -scrollX, -scrollY, 500)
            invalidate()
        }
    }
    return true
}

然后重写computeScroll方法,保证可以缓慢滑动:


/**
 * smooth-scroll
 */
override fun computeScroll() {
    super.computeScroll()

    //判断动画是否完成
    if (singleScroller.computeScrollOffset()) {
        scrollTo(singleScroller.currX, singleScroller.currY)

        //通知重绘
        invalidate()
    }
}
    

ControlInterceptListener接口代码为:


/**
 * function : 控制OverScrollContainer是否可以拦截事件
 *
 * Created on 2018/10/15  17:44
 * @author mnlin
 */
public interface ControlInterceptListener {
    /**
     * 是否可以拦截
     *
     * @return true表示可以(但实际情况可能并不会去拦截)
     */
    default boolean canIntercept(MotionEvent me){
        return true;
    }
}

这样一来,就可以完成一个简单的回弹效果,效果如下:

android开发&自定义View实现ios滑动回弹

在这基础上,还可以添加一些可以修改字体颜色的属性,或者屏蔽水平竖直滚动的比例来判断,当前滚动发生的方向等等

七、布局文件与类文件

最后贴出使用的xml布局,以及源码

xml文件:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <LinearLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <com.example.test.tv.test_view.wrapper.OverScrollContainer
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_marginBottom="20dp"
            android:layout_marginEnd="5dp"
            android:layout_marginStart="5dp"
            android:layout_marginTop="16dp"
            android:background="#5500FFFF"
            android:paddingBottom="14dp"
            android:paddingEnd="10dp"
            android:paddingStart="16dp"
            android:paddingTop="20dp">

            <ImageView
                android:layout_width="300dp"
                android:layout_height="300dp"
                android:layout_margin="10dp"
                android:gravity="center"
                android:src="#22FF4081"
                android:text="456123"
                android:textSize="20sp"
                app:enable_bottom="true"
                app:enable_top="true"
                app:overScrollDimen_bottom="48dp"
                app:overScrollDimen_top="48dp"
                app:overScroll_bg_color="#55aa00aa"/>
        </com.example.test.tv.test_view.wrapper.OverScrollContainer>
    </LinearLayout>
</ScrollView>

OverScrollContainer源码:


/**
 * function : 包裹容器;用于在view外部形成一个可以over-scroll的视图
 *
 *
 * 只能有一个子view,否则,视图将加载错乱
 * 仅支持纵轴过度滑动
 */
class OverScrollContainer : ViewGroup {
    /**
     * 手指按下的坐标
     * 当前的坐标
     */
    private var down_y: Float = 0.toFloat()
    private var current_y: Float = 0.toFloat()

    /**
     * 控制器
     */
    private var controlListener: ControlInterceptListener? = null

    /**
     * 文字画笔
     */
    private var singlePaint = TextPaint().also {
        it.textSize = 28.toFloat()
        it.color = Color.RED
    }

    /**
     * 平滑滚动控制
     */
    private val singleScroller = Scroller(this.context)

    constructor(context: Context) : super(context)

    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)

    constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)

    /**
     * 判断是否可以拦截事件
     */
    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        when (ev.action) {
            MotionEvent.ACTION_DOWN -> {
                down_y = ev.y
            }
        }

        //在滚动时,指定所有父布局不能拦截自身的点击事件
        return if (ev.action == MotionEvent.ACTION_MOVE
                && childCount != 0
                && (getChildAt(0).layoutParams as LayoutParams).let { it.bottomEnable or it.topEnable }
                && controlListener?.canIntercept(ev) != false) {
            var temp_parent = parent ?: null
            while (temp_parent != null) {
                parent.requestDisallowInterceptTouchEvent(true)
                temp_parent = temp_parent.parent ?: null
            }
            true
        } else {
            false
        }
    }

    /**
     * 处理滑动逻辑
     */
    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_MOVE -> {
                current_y = event.y
                val result = (down_y - current_y).toInt()

                (getChildAt(0)?.layoutParams as LayoutParams?)?.let { lp ->
                    //判断当前是否可以滑动
                    when {
                        result < 0 && lp.topEnable && lp.overScrollDimenTop > 0 -> lp.overScrollDimenTop
                        result > 0 && lp.bottomEnable && lp.overScrollDimenBottom > 0 -> lp.overScrollDimenBottom
                        else -> null
                    }?.let {
                        scrollTo(0, if (Math.abs(result) > it) result / Math.abs(result) * it else result)
                    }
                }
            }

            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
                //回归原来位置,平滑滚动
                singleScroller.startScroll(scrollX, scrollY, -scrollX, -scrollY, 500)
                invalidate()
            }
        }
        return true
    }

    /**
     * smooth-scroll
     */
    override fun computeScroll() {
        super.computeScroll()

        //判断动画是否完成
        if (singleScroller.computeScrollOffset()) {
            scrollTo(singleScroller.currX, singleScroller.currY)

            //通知重绘
            invalidate()
        }
    }

    /**
     * 测量子view高度
     */
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        if (childCount == 1) {
            //自身尺寸遵循唯一子布局的尺寸
            val child = getChildAt(0)
            val lp = child.layoutParams as LayoutParams
            measureChild(child, widthMeasureSpec, heightMeasureSpec)
            setMeasuredDimension(child.measuredWidth + lp.marginStart + lp.marginEnd + paddingStart + paddingEnd,
                    child.measuredHeight + lp.topMargin + lp.bottomMargin + paddingTop + paddingBottom)
        } else {
            setMeasuredDimension(0, 0)
        }
    }

    /**
     * 规范子view的坐标位置
     */
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
        val childCount = childCount
        if (childCount == 1) {
            val child = getChildAt(0)
            val lp = child.layoutParams as LayoutParams
            val left_p = lp.marginStart + paddingStart
            val top_p = lp.topMargin + paddingTop
            val right_p = left_p + child.measuredWidth
            val bottom_p = top_p + child.measuredHeight
            child.layout(left_p, top_p, right_p, bottom_p)
        } else {
            //当子布局不存在,或者超过一个,则不进行正常布局
            //setFrame(0,0,0,0);
        }
    }

    /**
     * 添加头部的填充内容
     */
    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)

        //存在滚动效果时,进行绘制
        if (scrollY != 0) {
            (getChildAt(0)?.layoutParams as LayoutParams?)?.let { lp ->
                //上部过度滚动效果
                if (scrollY < 0) {
                    //绘制默认顶部图形
                    lp.bgDrawable.run {
                        setBounds(lp.marginStart + paddingStart, -lp.overScrollDimenTop, width - lp.marginEnd - paddingEnd, lp.topMargin + paddingTop)
                        draw(canvas)
                    }

                    //绘制文字,保证滑动时,跟随在最顶部
                    "你是最棒的!!!".let {
                        singlePaint.color = Color.argb((-1.0 * scrollY / lp.overScrollDimenTop * 0xFF).toInt(), 0xFF, 0x00, 0x00)
                        canvas.drawText(it, (width - singlePaint.measureText(it)) / 2, scrollY - singlePaint.fontMetrics.top, singlePaint)
                    }
                }


                //下部过度滚动效果
                if (scrollY > 0) {
                    //绘制默认顶部图形
                    lp.bgDrawable.run {
                        setBounds(lp.marginStart + paddingStart, height - lp.bottomMargin - paddingBottom, width - lp.marginEnd - paddingEnd, height + lp.overScrollDimenBottom)
                        draw(canvas)
                    }

                    //绘制文字,保证滑动时,跟随在最顶部
                    "我是最棒的!!!".let {
                        singlePaint.color = Color.argb((1.0 * scrollY / lp.overScrollDimenBottom * 0xFF).toInt(), 0xFF, 0x00, 0x00)
                        canvas.drawText(it, (width - singlePaint.measureText(it)) / 2, height + scrollY - singlePaint.fontMetrics.bottom, singlePaint)
                    }
                }
            }
        }
    }

    /**
     * 添加控制器,可以根据子布局的状态来控制OverScrollContainer是否可以滑动
     */
    fun setControlInterceptListener(listener: ControlInterceptListener?) {
        this.controlListener = listener
    }

    override fun generateDefaultLayoutParams(): OverScrollContainer.LayoutParams {
        return OverScrollContainer.LayoutParams(MATCH_PARENT, MATCH_PARENT)
    }

    override fun generateLayoutParams(attrs: AttributeSet): OverScrollContainer.LayoutParams {
        return OverScrollContainer.LayoutParams(context, attrs)
    }

    override fun generateLayoutParams(lp: ViewGroup.LayoutParams): ViewGroup.LayoutParams {
        if (lp is OverScrollContainer.LayoutParams) {
            return OverScrollContainer.LayoutParams(lp)
        } else if (lp is ViewGroup.MarginLayoutParams) {
            return OverScrollContainer.LayoutParams(lp)
        }
        return OverScrollContainer.LayoutParams(lp)
    }

    /**
     * 自定义layoutParams,便于实现自定义的属性
     */
    class LayoutParams : ViewGroup.MarginLayoutParams {
        /**
         * over-scroll的距离
         */
        var overScrollDimenTop: Int = 96
        var overScrollDimenBottom: Int = 96

        /**
         * 是否可以回弹
         */
        var topEnable = true
        var bottomEnable = true

        /**
         * 滚动后留存的背景颜色
         */
        var bgColor = 0
        var bgDrawable: Drawable = ColorDrawable(Color.TRANSPARENT)

        constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
            val a = c.obtainStyledAttributes(attrs, R.styleable.OverScrollContainer)
            overScrollDimenTop = a.getDimensionPixelSize(R.styleable.OverScrollContainer_overScrollDimen_top, 96)
            overScrollDimenBottom = a.getDimensionPixelSize(R.styleable.OverScrollContainer_overScrollDimen_bottom, 96)
            topEnable = a.getBoolean(R.styleable.OverScrollContainer_enable_top, true)
            bottomEnable = a.getBoolean(R.styleable.OverScrollContainer_enable_bottom, true)
            bgColor = a.getColor(R.styleable.OverScrollContainer_overScroll_bg_color, 0)
            bgDrawable = ColorDrawable(bgColor)
            a.recycle()
        }

        constructor(width: Int, height: Int) : super(width, height)

        constructor(source: ViewGroup.LayoutParams) : super(source)

        constructor(source: ViewGroup.MarginLayoutParams) : super(source)

        constructor(source: OverScrollContainer.LayoutParams) : super(source) {
            overScrollDimenTop = source.overScrollDimenTop
            overScrollDimenBottom = source.overScrollDimenBottom
            topEnable = source.topEnable
            bottomEnable = source.bottomEnable
            bgColor = source.bgColor
            bgDrawable = source.bgDrawable
        }
    }
}