android開發&自定義View實現ios滑動回彈

在IOS中,在過度滑動時,整體佈局會進行偏移,鬆手後,會出現回彈效果

安卓中則大多數控件都沒有這種功能,在這裏,可以自定義一個ViewGroup容器,針對該容器包裹的內容,可以進行過度滾動

在這裏插入圖片描述

爲了實現,我 們需要進行接下來的處理

一、 創建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;
    }
}

這樣一來,就可以完成一個簡單的回彈效果,效果如下:

在這裏插入圖片描述

在這基礎上,還可以添加一些可以修改字體顏色的屬性,或者屏蔽水平豎直滾動的比例來判斷,當前滾動發生的方向等等

七、佈局文件與類文件

最後貼出使用的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
        }
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章