欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Android 菜單欄DIY實現(xiàn)效果詳解

 更新時間:2022年09月14日 14:39:16   作者:流浪漢kylin  
這篇文章主要為大家介紹了Android 菜單欄DIY實現(xiàn)效果詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

個人打算開發(fā)個視頻編輯的APP,然后把一些用上的技術(shù)總結(jié)一下,這次主要是APP的底部菜單欄用到了一個自定義View去繪制實現(xiàn)的,所以這次主要想講講自定義View的一些用到的點(diǎn)和自己如何去DIY一個不一樣的自定義布局。

實現(xiàn)的效果和思路

可以先看看實現(xiàn)的效果

兩個頁面的內(nèi)容還沒做,當(dāng)前就是一個Demo,可以看到底部的菜單欄是一個繪制出來的不規(guī)則的一個布局,那要如何實現(xiàn)呢??梢韵葋砜纯此囊粋€繪制區(qū)域:

就是一個底部的布局和3個子view,底部的區(qū)域當(dāng)然也是個規(guī)則的區(qū)域,只不過我們是在這塊區(qū)域上去進(jìn)行繪制。

可以把整個過程分為幾個步驟:

1. 繪制底部布局

  • (1) 繪制矩形區(qū)域
  • (2) 繪制外圓形區(qū)域
  • (3) 繪制內(nèi)圓形區(qū)域

2. 添加子view進(jìn)行布局

3. 處理事件分發(fā)的區(qū)域 (底部菜單上邊的白色區(qū)域不觸發(fā)菜單的事件)

4. 寫個動畫意思意思

1. 繪制底部布局

這里做的話就沒必要手動去添加view這些了,直接全部手動繪制就行。

companion object{
    const val DIMENS_64 = 64.0
    const val DIMENS_96 = 96.0
    const val DIMENS_50 = 50.0
    const val DIMENS_48 = 48.0
    interface OnChildClickListener{
        fun onClick(index : Int)
    }
}
private var paint : Paint ?= null  // 繪制藍(lán)色區(qū)域的畫筆
private var paint2 : Paint ?= null  // 繪制白色內(nèi)圓的畫筆
private var allHeight : Int = 0  // 總高度,就是繪制的范圍
private var bgHeight : Int = 0  // 背景的高度,就是藍(lán)色矩陣的范圍
private var mRadius : Int = 0  // 外圓的高度
private var mChildSize : Int = 0
private var mChildCenterSize : Int = 0
private var mWidthZone1 : Int = 0
private var mWidthZone2 : Int = 0
private var mChildCentre : Int = 0
private var childViews : MutableList<View> = mutableListOf()
private var objectAnimation : ObjectAnimator ?= null
var onChildClickListener : OnChildClickListener ?= null
init {
    initView()
}
private fun initView(){
    val lp = ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
        DimensionUtils.dp2px(context, DIMENS_64).toInt())
    layoutParams = lp
    allHeight = DimensionUtils.dp2px(context, DIMENS_96).toInt()
    bgHeight = DimensionUtils.dp2px(context, DIMENS_64).toInt()
    mRadius = DimensionUtils.dp2px(context, DIMENS_50).toInt()
    mChildSize = DimensionUtils.dp2px(context, DIMENS_48).toInt()
    mChildCenterSize = DimensionUtils.dp2px(context, DIMENS_64).toInt()
    setWillNotDraw(false)
    initPaint()
}
private fun initPaint(){
    paint = Paint()
    paint?.isAntiAlias = true
    paint?.color = context.resources.getColor(R.color.kylin_main_color)
    paint2 = Paint()
    paint2?.isAntiAlias = true
    paint2?.color = context.resources.getColor(R.color.kylin_third_color)
}

上邊是先把一些尺寸給定義好(我這邊是沒有設(shè)計圖,自己去直接調(diào)整的,所以可能有些視覺效果不太好,如果有設(shè)計師幫忙的話效果肯定會好些),繪制流程就是繪制3個形狀,然后代碼里也加了些注釋哪個變量有什么用,這步應(yīng)該不難,沒什么可以多解釋的。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    val wSize = MeasureSpec.getSize(widthMeasureSpec)
    // 拿到子view做操作的,和這步無關(guān),可以先不看
    if (childViews.size <= 0) {
        for (i in 0 until childCount) {
            val cView = getChildAt(i)
            initChildView(cView, i)
            childViews.add(cView)
            if (i == childCount/2){
                val ms: Int = MeasureSpec.makeMeasureSpec(mRadius, MeasureSpec.AT_MOST)
                measureChild(cView, ms, ms)
            }else {
                val ms: Int = MeasureSpec.makeMeasureSpec(mChildSize, MeasureSpec.AT_MOST)
                measureChild(cView, ms, ms)
            }
        }
    }
    setMeasuredDimension(wSize, allHeight)
}

這步其實也很簡單,就是說給當(dāng)前自定義view設(shè)置高度為allHeight

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    // 繪制長方形區(qū)域
    canvas?.drawRect(left.toFloat(), ((allHeight - bgHeight).toFloat()),
        right.toFloat(), bottom.toFloat(), paint!!)
    // 繪制圓形區(qū)域
    paint?.let {
        canvas?.drawCircle(
            (width/2).toFloat(), mRadius.toFloat(),
            mRadius.toFloat(),
            it
        )
    }
    // 繪制內(nèi)圓區(qū)域
    paint2?.let {
        canvas?.drawCircle(
            (width/2).toFloat(), mRadius.toFloat(),
            (mRadius - 28).toFloat(),
            it
        )
    }
}

最后進(jìn)行繪制, 就是上面說的繪制3個圖形,代碼里的注釋也說得很清楚。

2. 添加子view

我這里是外面布局去加子view的,想弄得靈活點(diǎn)(但感覺也不太好,后面還是想改成里面定義一套規(guī)范來弄會好些,如果自由度太高的話去做自定義就很麻煩,而且實際開發(fā)中這種需求也沒必要把擴(kuò)展性做到這種地步,基本就是整個APP只有一個地方使用)

但是這邊也只是一個Demo先做個演示。

<com.kylin.libkcommons.widget.BottomMenuBar
    android:id="@+id/bv_content"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_alignParentBottom="true"
    >
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/home"
        />
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/video"
        />
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/more"
        />
</com.kylin.libkcommons.widget.BottomMenuBar>
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    val wSize = MeasureSpec.getSize(widthMeasureSpec)
    if (childViews.size <= 0) {
        for (i in 0 until childCount) {
            val cView = getChildAt(i)
            initChildView(cView, i)
            childViews.add(cView)
            if (i == childCount/2){
                val ms: Int = MeasureSpec.makeMeasureSpec(mRadius, MeasureSpec.AT_MOST)
                measureChild(cView, ms, ms)
            }else {
                val ms: Int = MeasureSpec.makeMeasureSpec(mChildSize, MeasureSpec.AT_MOST)
                measureChild(cView, ms, ms)
            }
        }
    }
    setMeasuredDimension(wSize, allHeight)
}

拿到子view進(jìn)行一個管理,做一些初始化的操作,主要是設(shè)點(diǎn)擊事件這些,這里不是很重要。

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    if (mChildCentre == 0){
        mChildCentre = width / 6
    }
    // 輔助事件分發(fā)區(qū)域
    if (mWidthZone1 == 0 || mWidthZone2 == 0) {
        mWidthZone1 = width / 2 - mRadius / 2
        mWidthZone2 = width / 2 + mRadius / 2
    }
    // 設(shè)置每個子view的顯示區(qū)域
    for (i in 0 until childViews.size) {
        if (i == childCount/2){
            childViews[i].layout(mChildCentre*(2*i+1) - mChildCenterSize/2 ,
                allHeight/2 - mChildCenterSize/2,
                mChildCentre*(2*i+1) + mChildCenterSize/2 ,
                allHeight/2 + mChildCenterSize/2)
        }else {
            childViews[i].layout(mChildCentre*(2*i+1) - mChildSize/2 ,
                allHeight - bgHeight/2 - mChildSize/2,
                mChildCentre*(2*i+1) + mChildSize/2 ,
                allHeight - bgHeight/2 + mChildSize/2)
        }
    }
}

進(jìn)行布局,這里比較重要,因為能看出,中間的圖標(biāo)會更大一些,所以要做一些適配。其實這里就是把寬度分為6塊,然后3個view分別在1,3,5這三個左邊點(diǎn),y的話就是除中間那個,其它兩個都是bgHeight繪制高度的的一半,中間那個是allHeight總高度的一半,這樣3個view的x和y坐標(biāo)都能拿到了,再根據(jù)寬高就能算出l,t,r,b四個點(diǎn),然后布局。

3. 處理事件分發(fā)

可以看出我們的區(qū)域是一個不規(guī)則的區(qū)域,按照我們用抽象的角度去思考,我們希望這個菜單欄的區(qū)域只是顯示藍(lán)色的那個區(qū)域,所以藍(lán)色區(qū)域上面的白色區(qū)域就算是我們自定義view的范圍,他觸發(fā)的事件也應(yīng)該是后面的view的事件(Demo中后面的View是一個ViewPager),而不是菜單欄。

// 輔助事件分發(fā)區(qū)域
if (mWidthZone1 == 0 || mWidthZone2 == 0) {
    mWidthZone1 = width / 2 - mRadius / 2
    mWidthZone2 = width / 2 + mRadius / 2
}

這兩塊是圓外的x的區(qū)域。

/**
 *  判斷點(diǎn)擊事件是否在點(diǎn)擊區(qū)域中
 */
private fun isShowZone(x : Float, y : Float) : Boolean{
    if (y >= allHeight - bgHeight){
        return true
    }
    if (x >= mWidthZone1 && x <= mWidthZone2){
        // 在圓內(nèi)
        val relativeX = abs(x - width/2)
        val squareYZone = mRadius.toDouble().pow(2.0) - relativeX.toDouble().pow(2.0)
        return y >= mRadius - sqrt(squareYZone)
    }
    return false
}

先判斷y如果在背景的矩陣中(上面說了自定義view分成矩陣,外圓,內(nèi)圓),那肯定是菜單的區(qū)域。如果不在,那就要判斷y在不在圓內(nèi),這里就必須用勾股定理去判斷。

override fun onTouchEvent(event: MotionEvent?): Boolean {
    // 點(diǎn)擊區(qū)域進(jìn)行攔截
    if (event?.action == MotionEvent.ACTION_DOWN && isShowZone(event.x, event.y)){
        return true
    }
    return super.onTouchEvent(event)
}

最后做一個事件分發(fā)的攔截。除了計算區(qū)域那可能需要去想想,其它地方我覺得都挺好理解的吧。

4. 做個動畫

給子view設(shè)點(diǎn)擊事件讓外部處理,然后給中間的按鈕做個動畫效果。

private fun initChildView(cView : View?, index : Int) {
    cView?.setOnClickListener {
        if (index == childViews.size/2) {
            startAnim(cView)
        }else {
            onChildClickListener?.onClick(index)
        }
    }
}
private fun startAnim(view : View){
    if (objectAnimation == null) {
        objectAnimation = ObjectAnimator.ofFloat(view,
            "rotation", 0f, -15f, 180f, 0f)
        objectAnimation?.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(p0: Animator) {
            }
            override fun onAnimationEnd(p0: Animator) {
                onChildClickListener?.onClick(childViews.size / 2)
            }
            override fun onAnimationCancel(p0: Animator) {
                onChildClickListener?.onClick(childViews.size / 2)
            }
            override fun onAnimationRepeat(p0: Animator) {
            }
        })
        objectAnimation?.duration = 1000
        objectAnimation?.interpolator = AccelerateDecelerateInterpolator()
    }
    objectAnimation?.start()
}

注意做釋放操作。

fun onDestroy(){
    try {
        objectAnimation?.cancel()
        objectAnimation?.removeAllListeners()
    }catch (e : Exception){
        e.printStackTrace()
    }finally {
        objectAnimation = null
    }
}

5. 小結(jié)

其實代碼都挺簡單的,關(guān)鍵是你要去想出一個方法來實現(xiàn)這個場景,然后感覺這個自定義viewgroup也是比較經(jīng)典的,涉及到measure、layout、draw,涉及到動畫,涉及到點(diǎn)擊沖突。

這個Demo表示你要實現(xiàn)怎樣的效果都可以,只要是draw能畫出來的,你都能實現(xiàn),我這個是中間凸出來,你可以實現(xiàn)凹進(jìn)去,你可以實現(xiàn)波浪的樣子,可以實現(xiàn)復(fù)雜的曲線,都行,你用各種基礎(chǔ)圖形去做拼接,或者畫貝塞爾等等,其實都不難,主要是要有個計算和調(diào)試的過程。但是你的形狀要和點(diǎn)擊區(qū)域關(guān)聯(lián)起來,你設(shè)計的圖案越復(fù)雜,你要適配的點(diǎn)擊區(qū)域計算量就越大。

甚至我還能做得效果更屌的是,那3個子view的圖標(biāo),我都能畫出來,就不用ImagerView,直接手動畫出來,這樣做的好處是什么呢?我對子view的圖標(biāo)能做各種炫酷的屬性動畫,我在切換viewpager時對圖標(biāo)做屬性動畫,那不得逼格再上一層。 為什么我沒做呢,因為沒有設(shè)計,我自己做的話要花大量的時間去調(diào),要是有設(shè)計的話他告訴我尺寸啊位置啊這些信息,做起來就很快。我的APP主要是打算實現(xiàn)視頻的編輯為主,所以這些支線就沒打算花太多時間去處理。

以上就是Android 菜單欄DIY實現(xiàn)效果詳解的詳細(xì)內(nèi)容,更多關(guān)于Android 菜單欄DIY的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論