Android 菜单栏DIY实现效果详解

目录
  • 前言
  • 实现的效果和思路
    • 1. 绘制底部布局
    • 2. 添加子view
    • 3. 处理事件分发
    • 4. 做个动画
    • 5. 小结

前言

个人打算开发个视频编辑的APP,然后把一些用上的技术总结一下,这次主要是APP的底部菜单栏用到了一个自定义View去绘制实现的,所以这次主要想讲讲自定义View的一些用到的点和自己如何去DIY一个不一样的自定义布局。

实现的效果和思路

可以先看看实现的效果

两个页面的内容还没做,当前就是一个Demo,可以看到底部的菜单栏是一个绘制出来的不规则的一个布局,那要如何实现呢。可以先来看看它的一个绘制区域:

就是一个底部的布局和3个子view,底部的区域当然也是个规则的区域,只不过我们是在这块区域上去进行绘制。

可以把整个过程分为几个步骤:

1. 绘制底部布局

  • (1) 绘制矩形区域
  • (2) 绘制外圆形区域
  • (3) 绘制内圆形区域

2. 添加子view进行布局

3. 处理事件分发的区域 (底部菜单上边的白色区域不触发菜单的事件)

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  // 绘制蓝色区域的画笔
private var paint2 : Paint ?= null  // 绘制白色内圆的画笔
private var allHeight : Int = 0  // 总高度,就是绘制的范围
private var bgHeight : Int = 0  // 背景的高度,就是蓝色矩阵的范围
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)
}

上边是先把一些尺寸给定义好(我这边是没有设计图,自己去直接调整的,所以可能有些视觉效果不太好,如果有设计师帮忙的话效果肯定会好些),绘制流程就是绘制3个形状,然后代码里也加了些注释哪个变量有什么用,这步应该不难,没什么可以多解释的。

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    val wSize = MeasureSpec.getSize(widthMeasureSpec)
    // 拿到子view做操作的,和这步无关,可以先不看
    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设置高度为allHeight

override fun onDraw(canvas: Canvas?) {
    super.onDraw(canvas)
    // 绘制长方形区域
    canvas?.drawRect(left.toFloat(), ((allHeight - bgHeight).toFloat()),
        right.toFloat(), bottom.toFloat(), paint!!)
    // 绘制圆形区域
    paint?.let {
        canvas?.drawCircle(
            (width/2).toFloat(), mRadius.toFloat(),
            mRadius.toFloat(),
            it
        )
    }
    // 绘制内圆区域
    paint2?.let {
        canvas?.drawCircle(
            (width/2).toFloat(), mRadius.toFloat(),
            (mRadius - 28).toFloat(),
            it
        )
    }
}

最后进行绘制, 就是上面说的绘制3个图形,代码里的注释也说得很清楚。

2. 添加子view

我这里是外面布局去加子view的,想弄得灵活点(但感觉也不太好,后面还是想改成里面定义一套规范来弄会好些,如果自由度太高的话去做自定义就很麻烦,而且实际开发中这种需求也没必要把扩展性做到这种地步,基本就是整个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进行一个管理,做一些初始化的操作,主要是设点击事件这些,这里不是很重要。

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
    if (mChildCentre == 0){
        mChildCentre = width / 6
    }
    // 辅助事件分发区域
    if (mWidthZone1 == 0 || mWidthZone2 == 0) {
        mWidthZone1 = width / 2 - mRadius / 2
        mWidthZone2 = width / 2 + mRadius / 2
    }
    // 设置每个子view的显示区域
    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)
        }
    }
}

进行布局,这里比较重要,因为能看出,中间的图标会更大一些,所以要做一些适配。其实这里就是把宽度分为6块,然后3个view分别在1,3,5这三个左边点,y的话就是除中间那个,其它两个都是bgHeight绘制高度的的一半,中间那个是allHeight总高度的一半,这样3个view的x和y坐标都能拿到了,再根据宽高就能算出l,t,r,b四个点,然后布局。

3. 处理事件分发

可以看出我们的区域是一个不规则的区域,按照我们用抽象的角度去思考,我们希望这个菜单栏的区域只是显示蓝色的那个区域,所以蓝色区域上面的白色区域就算是我们自定义view的范围,他触发的事件也应该是后面的view的事件(Demo中后面的View是一个ViewPager),而不是菜单栏。

// 辅助事件分发区域
if (mWidthZone1 == 0 || mWidthZone2 == 0) {
    mWidthZone1 = width / 2 - mRadius / 2
    mWidthZone2 = width / 2 + mRadius / 2
}

这两块是圆外的x的区域。

/**
 *  判断点击事件是否在点击区域中
 */
private fun isShowZone(x : Float, y : Float) : Boolean{
    if (y >= allHeight - bgHeight){
        return true
    }
    if (x >= mWidthZone1 && x <= mWidthZone2){
        // 在圆内
        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分成矩阵,外圆,内圆),那肯定是菜单的区域。如果不在,那就要判断y在不在圆内,这里就必须用勾股定理去判断。

override fun onTouchEvent(event: MotionEvent?): Boolean {
    // 点击区域进行拦截
    if (event?.action == MotionEvent.ACTION_DOWN && isShowZone(event.x, event.y)){
        return true
    }
    return super.onTouchEvent(event)
}

最后做一个事件分发的拦截。除了计算区域那可能需要去想想,其它地方我觉得都挺好理解的吧。

4. 做个动画

给子view设点击事件让外部处理,然后给中间的按钮做个动画效果。

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. 小结

其实代码都挺简单的,关键是你要去想出一个方法来实现这个场景,然后感觉这个自定义viewgroup也是比较经典的,涉及到measure、layout、draw,涉及到动画,涉及到点击冲突。

这个Demo表示你要实现怎样的效果都可以,只要是draw能画出来的,你都能实现,我这个是中间凸出来,你可以实现凹进去,你可以实现波浪的样子,可以实现复杂的曲线,都行,你用各种基础图形去做拼接,或者画贝塞尔等等,其实都不难,主要是要有个计算和调试的过程。但是你的形状要和点击区域关联起来,你设计的图案越复杂,你要适配的点击区域计算量就越大。

甚至我还能做得效果更屌的是,那3个子view的图标,我都能画出来,就不用ImagerView,直接手动画出来,这样做的好处是什么呢?我对子view的图标能做各种炫酷的属性动画,我在切换viewpager时对图标做属性动画,那不得逼格再上一层。 为什么我没做呢,因为没有设计,我自己做的话要花大量的时间去调,要是有设计的话他告诉我尺寸啊位置啊这些信息,做起来就很快。我的APP主要是打算实现视频的编辑为主,所以这些支线就没打算花太多时间去处理。

以上就是Android 菜单栏DIY实现效果详解的详细内容,更多关于Android 菜单栏DIY的资料请关注我们其它相关文章!

(0)

相关推荐

  • Android仿微信界面的导航以及右上角菜单栏效果

    下面是安卓开发仿微信界面的代码. 分为3步, 第一步是界面的编写; 第二步是导航界面; 第三步是右上角菜单栏. 开始第一步前先预览一下效果. 第一步,界面. 界面的思路是利用ViewPager+Fragment实现,所以activity_main.xml中添加一个ViewPager.顶部和底部include的顶部栏和底部栏后面再说. MainActivity的界面activity_main.xml: <?xml version="1.0" encoding="utf-8

  • Android底部菜单栏(RadioGroup+Fragment)美化

    众所周知,android的底部菜单栏太重要,平时项目一般都是需要用到的,但是网上关于这方面的demo做得太丑了,实在惨不忍睹,所以这里便用RadioGroup+Fragment的方式写了一个,顺便美化了一下,需要的可以看下. 效果图: 项目结构 MainActivity.java public class MainActivity extends AppCompatActivity { private FrameLayout frameLayout; private RadioGroup rad

  • Android仿微信底部菜单栏效果

    前言 在市面上,大多数的APP都需要通过底部菜单栏来将程序的功能进行分类整理,通常都是分为3-5个大模块,从而正确有效地引导用户去使用我们的APP.实现底部菜单栏的方法也有很多种. 1.仿微信底部菜单栏(ViewPager+ImagerView+TextView) ......(其他方式后续会补充) 效果预览 首先来个开胃菜,看看实现效果: 先贴出项目所需的资源文件,这些可随个人自由更改颜色和文字 colors.xml <color name="bg_line_light_gray&quo

  • Android 实现长按弹出PopupMenu 菜单栏

    在Android中的SDK3.0版本以后加入了一个特殊的菜单效果,它可以在任何的View上显示,根据View的位置显示菜单效果. res/menu/menu.xml <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="

  • android实现上滑屏幕隐藏底部菜单栏的示例

    本篇文章引用github上一个仿今日头条项目,项目地址: https://github.com/iMeiji/Toutiao ,主要实现的功能是底部菜单栏随用户手势滑动而变化可见状态 布局代码 这个功能实现起来比较简单,主要利用了CoordinatorLayout的 layout_behavior 的属性.具体代码如下: <android.support.design.widget.CoordinatorLayout android:layout_width="match_parent&q

  • Android底部菜单栏实现的实例代码

     Android 使用RadioGroup 实现底部导航菜单栏. 一.主界面布局的实现: 先来张效果图: 介绍一下总体界面包括的内容:底部五个导航按钮,主界面包括一个FrameLayout用来放五个Fragment.点击底部按钮会对应跳转到指定的界面. 实现布局:activity_main.xml <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="h

  • Android 菜单栏DIY实现效果详解

    目录 前言 实现的效果和思路 1. 绘制底部布局 2. 添加子view 3. 处理事件分发 4. 做个动画 5. 小结 前言 个人打算开发个视频编辑的APP,然后把一些用上的技术总结一下,这次主要是APP的底部菜单栏用到了一个自定义View去绘制实现的,所以这次主要想讲讲自定义View的一些用到的点和自己如何去DIY一个不一样的自定义布局. 实现的效果和思路 可以先看看实现的效果 两个页面的内容还没做,当前就是一个Demo,可以看到底部的菜单栏是一个绘制出来的不规则的一个布局,那要如何实现呢.可

  • Android如何绘制发光效果详解

    前言 之前在看别人写自定义view作绘制的时候,看到别人家的view自带发光效果,看起来也是蛮炫酷的,于是自己也抽出时间来试用一下,这里做了一个模仿太阳的各种状态样式. 先上效果先上效果: 实现方式: public BlurMaskFilter(float radius, Blur style) { 实现是使用的Paint类的setMaskFilter()方法,传入BlurMaskFilter对象实现高斯模糊发光. float radius 设置模糊半径 Blur style 设置发光样式,包括

  • Android OpenGL仿自如APP裸眼3D效果详解

    目录 原理简介 & OpenGL 的优势 具体实现 1. 绘制静态图片 2. 让图片动起来 3. 几个反直觉的细节 4. 帕金森综合征? 源码 原理简介 & OpenGL 的优势 裸眼 3D 效果的本质是——将整个图片结构分为 3 层:上层.中层.以及底层.在手机左右上下旋转时,上层和底层的图片呈相反的方向进行移动,中层则不动,在视觉上给人一种 3D 的感觉: 也就是说效果是由以下三张图构成的: 接下来,如何感应手机的旋转状态,并将三层图片进行对应的移动呢?当然是使用设备自身提供各种各样优

  • Android Flutter实现五种酷炫文字动画效果详解

    目录 前言 波浪涌动效果 波浪线跳动文字组 彩虹动效 滚动广告牌效果 打字效果 其他效果 自定义效果 总结 前言 偶然逛国外博客,看到了一个介绍文字动画的库,进入 pub 一看,立马就爱上这个动画库了,几乎你能想到的文字动画效果它都有!现在正式给大家安利一下这个库:animated_text_kit.本篇我们介绍几个酷炫的效果,其他的效果大家可以自行查看官网文档使用. 波浪涌动效果 波浪涌动 上面的动画效果只需要下面几行代码,其中loadUntil用于控制波浪最终停留的高度,取值是0-1.0,如

  • Android通过自定义view实现刮刮乐效果详解

    前言 已经有两个月没有更新博客了,其实这篇文章我早在两个月前就写好了,一直保存在草稿箱里没有发布出来.原因是有一些原理性的东西还没了解清楚,最近抽时间研究了一下混合模式,终于也理解了刮刮乐是怎么实现的,所以想继续分享一下自己的一些心得,先上效果图. 效果图: 实现原理 其实刮刮乐实现原理也不算很复杂,最关键的还是需要了解Paint的混合模式.因为刮刮乐是由两个bitmap组成的,一个是源图另一个是目标图,我们需要把目标图的颜色改成灰色,在源图上面盖上了一张灰色的目标图.当手指滑动屏幕时paint

  • Android进阶NestedScroll嵌套滑动机制实现吸顶效果详解

    目录 引言 1 自定义滑动布局,实现吸顶效果 1.1 滑动容器实现 1.2 嵌套滑动机制完成交互优化 1.2.1 NestedScrollingParent接口和NestedScrollingChild接口 1.2.2 预滚动阶段实现 1.2.3 滚动阶段实现 1.2.4 滚动结束 引言 在上一篇文章Android进阶宝典 -- 事件冲突怎么解决?先从Android事件分发机制开始说起中,我们详细地介绍了Android事件分发机制,其实只要页面结构复杂,联动众多就会产生事件冲突,处理不得当就是b

  • Android 备忘录的实现流程详解

    目录 一.首先创建保存数据的表 二.主界面 2.1 activity_main.xml 三.:EditActivity.java 3.1 edit_layout.xml 四.对笔记进行增删查改 4.1 note_layout.xml 五.演示效果 5.1 登录界面 5.2笔记界面 5.3 增加记录 5.4删除记录 5.5 查找记录 5.6源码 总结 提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 一.首先创建保存数据的表 Notedatabase,java package co

  • Android ListView 滚动条的设置详解及实例代码

    Android ListView 滚动条的设置详解 1.滚动条的属性 android:scrollbarAlwaysDrawHorizontalTrack 设置是否始终显示水平滚动条.这里用ScrollView.ListView测试均没有效果. android:scrollbarAlwaysDrawVerticalTrack 设置是否始终显示垂直滚动条.这里用ScrollView.ListView测试均没有效果. android:scrollbarDefaultDelayBeforeFade 设

  • Android图片压缩的实例详解

    Android图片压缩的实例详解 在做微信分享的时候,由于分享的缩略图要求不得大于32K,否则不能调起微信,所以总结了一下Android图片的压缩问题,大部分资料都是来自网上各位的分享,自己只是完善或修改了一下,本着继续分享的精神,也方便自己记忆,于是总结如下. android图片压缩主要有两种方式:1.压缩图片分辨率 2.压缩图片质量 一.先看压缩图片分辨率,很好理解,如本来1280*768的图片压缩为640*384大小.废话不说,直接上代码: /** * 按比例压缩图片分辨率 * @para

  • Android中图片压缩方案详解及源码下载

    Android中图片压缩方案详解及源码下载 图片的展示可以说在我们任何一个应用中都避免不了,可是大量的图片就会出现很多的问题,比如加载大图片或者多图时的OOM问题,可以移步到Android高效加载大图及多图避免程序OOM.还有一个问题就是图片的上传下载问题,往往我们都喜欢图片既清楚又占的内存小,也就是尽可能少的耗费我们的流量,这就是我今天所要讲述的问题:图片的压缩方案的详解. 1.质量压缩法 设置bitmap options属性,降低图片的质量,像素不会减少 第一个参数为需要压缩的bitmap图

随机推荐