Android-自定义气泡View

实践背景

在即时通讯类应用里,很常见各种气泡布局包裹消息,通常我们采用.9图实现。但是使用气泡图片面临着间距不可控,如果是图片消息,此方法就无法实现气泡。本文将介绍如何更加用优雅的方式去实现自定义气泡布局。

PS前置知识: 如何自定义view、XFermode混合图层、path概念以及贝赛尔曲线。

惯例,我们先看下最终要实现的效果图,如下图,总共有5种类型,基本满足日常需要,可以根据需要再进行扩展。

自定义气泡View思路分析

1.图形基本分析

以上四种常见气泡,从外形上看是圆角带犄角,文字内容在气泡的矩形内,图片被裁剪部分。 图片类型的气泡上,犄角部分是带有图片的一部分,而且在图片的左右下角有一个提示类型的图片(特殊UI需要),上图中未体现该效果。文字类型气泡特殊一些,在单个字的时候,文字是居中的,然后左右内间距和多文字下的间距不一样(UI要求),但是从整体上也符合气泡的通用裁剪规则。容器类型气泡,内部子view可随意布置,但是最终显示区域只有气泡部分,这样可扩展度搞。

2.实现思路分析

那么我们如何去形成这种View呢?最开始我接触到的代码是用drawable加载.9图的方式,但是新UI效果图片类型气泡就无法下手了。可供采用的方案有2种,canvas的clipPath和XFermode图层混合。

PS:目前任何布局类型都是四边形的,而那些各种形状的布局,其实只是四边形布局只显示其中部分区域而已。

  • clipPath: 通过对canvas的裁剪形成气泡布局,先用描绘出一个气泡模样的path,然后按照这个path把画布裁剪,然后再这个画布上绘制内容。具体操作下面会介绍。
  • XFermode图层混合:XFermode可以通过多个图层进行叠加按照一定规则保留部分图形区域。利用这个特性,我们先绘制原始图形,然后在这个执行之后,依然先描绘出气泡path,我们可以给paint画笔设置PorterDuffXfermode,然后用画笔带上DST_IN模式进行图层叠加。

具体代码实践分析

1.简单介绍下本文所需的自定义View知识

首先我们要知道自定义View需要做哪些代码准备,一般来说,onDraw是必然需要的。根据需要onMeasure,onSizeChange,onLayout,dispatchDraw等方法有时候也需要。本文是实现一个气泡view,有单一显示视图的自定义view以及容器类型的气泡。所以大家需要了解onDraw、onSizeChange、dispatchDraw等需要重写的方法。此外,了解invalidate和postInvalidate等刷新View视图方法。

onDraw

这个方法是核心,主要是用来描绘出你展示的视图界面。比如你想画一个花,那么这个地方进行最终的描绘工作。在部分情况下,这里会在继承某个View情况下,增加一些绘制,此时会继续调用super.onDraw(canvas); ,这样保留父view的图形。

onSizeChange

在进行ondraw之前会有若干次调用onSizeChange,这里可以用来提前获取当前view的最新高宽,比如下面代码。

 @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        mHeight = h;
        mWidth = w;

    }

这个方法在本次自定义view中主要是给容器布局使用,调用顺序在ondraw之后,重写这个用来在分发绘制内部子view时,绘制需要的背景色以及进行图层裁剪形成气泡。

2.ClipPath方式实现策略

以文本气泡View为例,我们先思考气泡的图形path如何形成。首先我们先继承TextView,因为我们要在这个基础上实现文字气泡。图形大致上拆分为一个圆角矩形,然后再左侧或者右侧画一个犄角。犄角是带有一定弧度的,这个和UI沟通过,我当初是通过px一点点调整最终给到UI满意的弧度。圆角矩形的绘制就不多说了,看API。犄角,是通过2条二阶贝塞尔曲线形成的,上面一条,下面一条(关于path的介绍很多,看上面的预学习链接,或者自行搜索)。

    protected void onDraw(Canvas canvas) {
        canvas.setDrawFilter(mPaintFlagsDrawFilter);
//        LogUtil.i(TAG, getText() + "  getPaddingLeft" + getPaddingLeft() + "  getPaddingRight" + getPaddingRight());
        mSrcPath.reset();
        if (mIsRightPop) {
            mRoundRect.set(0, 0, mWidth - mWidthDiff, mHeight);
            mSrcPath.addRoundRect(mRoundRect, mRoundRadius, mRoundRadius, Path.Direction.CW);
            //给path增加右侧的犄角,形成气泡效果
            mSrcPath.moveTo(mWidth - mWidthDiff, mRoundRadius);
            mSrcPath.quadTo(mTopControl.x, mTopControl.y, mWidth, mRoundRadius - mDefaultCornerPadding);
            mSrcPath.quadTo(mBottomControl.x, mBottomControl.y, mWidth - mWidthDiff,
                    mRoundRadius + mWidthDiff);
        } else {
            mRoundRect.set(mWidthDiff, 0, mWidth, mHeight);
            mSrcPath.addRoundRect(mRoundRect, mRoundRadius, mRoundRadius, Path.Direction.CW);
            //给path增加右侧的犄角,形成气泡效果
            mSrcPath.moveTo(mWidthDiff, mRoundRadius);
            mSrcPath.quadTo(mTopControl.x, mTopControl.y, 0, mRoundRadius - mDefaultCornerPadding);
            mSrcPath.quadTo(mBottomControl.x, mBottomControl.y, mWidthDiff, mRoundRadius + mWidthDiff);
        }
     
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值