SimpleDraweeView实现聊天图片消息的尖嘴效果

项目中聊天模块要实现图片消息和视频消息带尖嘴的效果。

老的项目中实现方式是 重写ImageView的onDraw方法,通过BitMapShaper和Path路径实现圆角和尖角的绘制。

新的项目中大量的使用了fresco的simpleDraweeView,故需要一种新的实现方式。

image

实现圆角或尖角的原理:

欲实现圆角和尖嘴有两种实现技术:BitMapShaper 和PorterDuffXfermode。本文使用PorterDuffXfermode ,不过多介绍BitMapShaper。

PorterDuffXfermode 图形混合模式

该类有且只有一个含参的构造方法PorterDuffXfermode(PorterDuff.Mode mode),通过PorterDuff.Mode 可以实现两个图形的各种组合效果。

下图是PorterDuff.Mode取不同值时的组合效果:


image

在API中Android为我们提供了18种(比上图多了两种ADD和OVERLAY)模式:

ADD:饱和相加,对图像饱和度进行相加,不常用

CLEAR:清除图像

DARKEN:变暗,较深的颜色覆盖较浅的颜色,若两者深浅程度相同则混合

DST:只显示目标图像

DST_ATOP:在源图像和目标图像相交的地方绘制【目标图像】,在不相交的地方绘制【源图像】,相交处的效果受到源图像和目标图像alpha的影响

DST_IN:只在源图像和目标图像相交的地方绘制【目标图像】,绘制效果受到源图像对应地方透明度影响

DST_OUT:只在源图像和目标图像不相交的地方绘制【目标图像】,在相交的地方根据源图像的alpha进行过滤,源图像完全不透明则完全过滤,完全透明则不过滤

DST_OVER:将目标图像放在源图像上方

LIGHTEN:变亮,与DARKEN相反,DARKEN和LIGHTEN生成的图像结果与Android对颜色值深浅的定义有关

MULTIPLY:正片叠底,源图像素颜色值乘以目标图像素颜色值除以255得到混合后图像像素颜色值

OVERLAY:叠加

SCREEN:滤色,色调均和,保留两个图层中较白的部分,较暗的部分被遮盖

SRC:只显示源图像

SRC_ATOP:在源图像和目标图像相交的地方绘制【源图像】,在不相交的地方绘制【目标图像】,相交处的效果受到源图像和目标图像alpha的影响

SRC_IN:只在源图像和目标图像相交的地方绘制【源图像】

SRC_OUT:只在源图像和目标图像不相交的地方绘制【源图像】,相交的地方根据目标图像的对应地方的alpha进行过滤,目标图像完全不透明则完全过滤,完全透明则不过滤

SRC_OVER:将源图像放在目标图像上方

XOR:在源图像和目标图像相交的地方之外绘制它们,在相交的地方受到对应alpha和色值影响,如果完全不透明则相交处完全不绘制。

此处应该使用SRC_IN,取两个图形的交集

PorterDuffXfermode 实现圆形图片
/** 
     * 根据原图和边场绘制圆形图片 
     *  
     * @param source 
     * @param min 
     * @return 
     */  
    private Bitmap createCircleImage(Bitmap source, int min)  
    {  
        final Paint paint = new Paint();  
        paint.setAntiAlias(true);  
        Bitmap target = Bitmap.createBitmap(min, min, Config.ARGB_8888);  
        /** 
         * 产生一个同样大小的画布 
         */  
        Canvas canvas = new Canvas(target);  
        /** 
         * (1)首先绘制圆形 - 第一次绘制 
         */  
        canvas.drawCircle(min / 2, min / 2, min / 2, paint);  
        /** 
         * (2)paint使用SRC_IN,取两次绘制的交集
         */  
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));  
        /** 
         * (3)绘制图片 -第二次绘制
         */  
        canvas.drawBitmap(source, 0, 0, paint);  
        return target;  
    }  

首先创建一个Canvas,然后就是三步走:

  • 第一步 在canva上绘制一个圆形。
  • 第二步 paint.setXfermode() 指定PorterDuff.Mode.SRC_IN,取两次绘制的交集。
  • 第三步 将原bitmap绘制在Canvas上,进行第二次绘制。
    最终 原始的图片成了一个图形图片。
PorterDuffXfermode 实现尖嘴图片

原理同“圆形图片”,只不过将canvas.drawCircle 绘制圆形改成canvas.drawPath(path,paint) 绘制特定路径,其中path实现一个尖嘴路径。

绘制尖角路径的方法如下:

        /**
     * 绘制尖嘴在左侧的path
     *
     *         A
     *         * * * * * * * * * * * * *
     *       *                 *         *
     *     *                   *           *
     *     *  F                *     B      *
     *     *                   *            *
     *     * mArrowTop         * * * * * * **
     *   *                                  *
     *  *                                   *
     *   *                                  *
     *     *                                *
     *     *                                * C
     *     *                                *
     *     *                                *
     *     *  E                             *
     *     *                                *
     *     *                                *
     *     *                                *
     *     *                                *
     *     *                               *
     *      *                             *
     *        * * * * * * * * * * * * * *
     *                     D
     *
     * @param rect
     * @param path  如上图所示 从A点开始 顺时针绘制path路径.
     */
    public void leftPath(RectF rect, Path path) {
        path.moveTo(mCornerRadius + mArrowWidth, rect.top);//移动到A点
        path.lineTo(rect.width(), rect.top);//顶部横线
        path.arcTo(new RectF(rect.right - mCornerRadius * 2, rect.top, rect.right,
                mCornerRadius * 2 + rect.top), 270, 90);//绘制 右上角的90度的圆弧. (B对应的区域)
        path.lineTo(rect.right, rect.top);//绘制 右侧直线
        path.arcTo(new RectF(rect.right - mCornerRadius * 2, rect.bottom - mCornerRadius * 2,
                rect.right, rect.bottom), 0, 90);//右下角圆弧
        path.lineTo(rect.left + mArrowWidth, rect.bottom);//底部横线 (D对应的横线)
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mCornerRadius * 2,
                mCornerRadius * 2 + rect.left + mArrowWidth, rect.bottom), 90, 90);//左下角圆弧
        path.lineTo(rect.left + mArrowWidth, mArrowTop + mArrowHeight);//左侧偏下部竖线(E所示竖线)
        path.lineTo(rect.left, mArrowTop - mArrowOffset);//左侧凸起尖角 下半部分斜线
        path.lineTo(rect.left + mArrowWidth, mArrowTop); //左侧凸起尖角 上半部分斜线
        path.lineTo(rect.left + mArrowWidth, rect.top);//左侧片上部竖线(F所示竖线)
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.top, mCornerRadius * 2
                + rect.left + mArrowWidth, mCornerRadius * 2 + rect.top), 180, 90);//左上角圆弧

        path.close();
    }
PorterDuffXfermode 与SimpleDraweeView结合

我们知道fresco的SimpleDraweeView 中包含了多个图层,以显示placeholder、loading、加载失败等状态。所以不能简单的继承SimpleDraweeView重写onDraw方法。需要寻找新的方法。
查询fresco官网 发现fresco提供了一种叫做后处理器BasePostprocessor的工具,允许在bitmap下载完成后,对原始bitmap进行一些处理。

  • 尝试一 :利用BasePostprocessor,对下载完成的图片利用PorterDuffXfermode 生成尖嘴 再返回处理后的图片。
 public void showImage(final Uri uri, final int imageSuitableWidth, final int imageSuitableHeight){

        Postprocessor redMeshPostPorcessor = new BasePostprocessor() {
            @Override
            public void process(Bitmap destBitmap, Bitmap sourceBitmap) {

                Canvas canvas = new Canvas(destBitmap);
                int color = 0xff424242;// int color = 0xff424242;
                Paint paint = new Paint();
                paint.setColor(color);
                // 防止锯齿
                paint.setAntiAlias(true);

                Rect rect = new Rect(0,0,sourceBitmap.getWidth(),sourceBitmap.getHeight());

                RectF rectF = new RectF(rect);

                Path path = new Path();
                if(mArrowLocation == LOCATION_LEFT){
                    leftPath(rectF,path);
                }else {
                    rightPath(rectF,path);
                }
                canvas.drawARGB(0,0,0,0);
                canvas.drawPath(path,paint);
                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                canvas.drawBitmap(sourceBitmap,rect,rect,paint);

                LogUtils.d(TAG,"test size - redMeshPostPorcessor.width:"+sourceBitmap.getWidth()+",height:"+sourceBitmap.getHeight()+",destBitmap.width:"+destBitmap.getWidth()+",destBitMap.height:"+destBitmap.getHeight()+",imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight+",mArrowWidth:"+mArrowWidth+",location:"+mArrowLocation+",uri:"+uri);
            }
        };



        //LogUtils.d(TAG,"test size - imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight);
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setResizeOptions(new ResizeOptions(imageSuitableWidth, imageSuitableHeight))
                .setPostprocessor(redMeshPostPorcessor)
                .build();

        ViewGroup.LayoutParams params = getLayoutParams();
        params.width = imageSuitableWidth;
        params.height = imageSuitableHeight;
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(getController())
                .setImageRequest(request)
                .build();
        setController(controller);
        setLayoutParams(params);

    }

经测试基本实现了尖角效果,但是却存在一个问题:尖角效果是在原始Bitmap上重新绘制实现的,原始的bitmap 在SimpleDrawView上显示时,经常要经过一个放缩的处理,这个过程中绘制的尖角会被等比例的放大和缩小。导致的结果是 不同大小的图片 经过放缩处理后,在聊天列表页面显示时 尖嘴的大小也大小不一。

如何解决这个问题呢?查阅文档后发现fresco 后处理器 还有另一个方法,允许改变bitmap的大小

 public CloseableReference process(
                    Bitmap sourceBitmap,
                    PlatformBitmapFactory bitmapFactory) {
                    }
  • 尝试二 重写BasePostprocessor,处理原始图片时 首先把原始图片放缩到 最终要显示的大小,然后再添加尖角效果。问题解决。
public void showImage(final Uri uri, final int imageSuitableWidth, final int imageSuitableHeight){


        Postprocessor redMeshPostPorcessor = new BasePostprocessor() {
            @Override
            public CloseableReference process(
                    Bitmap sourceBitmap,
                    PlatformBitmapFactory bitmapFactory) {

                //创建一个安全的 新的bitmap
                CloseableReference bitmapRef = bitmapFactory.createBitmap(
                        imageSuitableWidth,
                        imageSuitableHeight);
                try {
                    Bitmap destBitmap = bitmapRef.get();

                    Canvas canvas = new Canvas(destBitmap);
                    int color = 0xff424242;// int color = 0xff424242;
                    Paint paint = new Paint();
                    paint.setColor(color);
                    // 防止锯齿
                    paint.setAntiAlias(true);

                    Rect rect = new Rect(0,0,imageSuitableWidth,imageSuitableHeight);

                    RectF rectF = new RectF(rect);

                    Path path = new Path();
                    if(mArrowLocation == LOCATION_LEFT){
                        leftPath(rectF,path);
                    }else {
                        rightPath(rectF,path);
                    }
                    canvas.drawARGB(0,0,0,0);
                    canvas.drawPath(path,paint);
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                    float scale = 1.0f;

                    float scaleWidth = (float) (imageSuitableWidth*1.0/sourceBitmap.getWidth());
                    float scaleHeight = (float) (imageSuitableHeight*1.0/sourceBitmap.getHeight());
                    scale = Math.max(scaleWidth,scaleHeight);
//                    canvas.drawBitmap(sourceBitmap,rect,rect,paint);
                    Matrix matrix = new Matrix();
                    matrix.postScale(scale,scale);
                    LogUtils.d(TAG,"scaleWidth:"+scaleWidth+",scaleHeight:"+scaleHeight+",scale:"+scale);
                    LogUtils.d(TAG,"test size - redMeshPostPorcessor.width:"+sourceBitmap.getWidth()+",height:"+sourceBitmap.getHeight()+",destBitmap.width:"+destBitmap.getWidth()+",destBitMap.height:"+destBitmap.getHeight()+",imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight+",mArrowWidth:"+mArrowWidth+",location:"+mArrowLocation+",uri:"+uri);
                    canvas.drawBitmap(sourceBitmap,matrix,paint);
                    return CloseableReference.cloneOrNull(bitmapRef);
                } finally {
                    CloseableReference.closeSafely(bitmapRef);
                }
            }
        };


        //LogUtils.d(TAG,"test size - imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight);
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setResizeOptions(new ResizeOptions(imageSuitableWidth, imageSuitableHeight))
                .setPostprocessor(redMeshPostPorcessor)
                .build();

        ViewGroup.LayoutParams params = getLayoutParams();
        params.width = imageSuitableWidth;
        params.height = imageSuitableHeight;
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(getController())
                .setImageRequest(request)
                .build();
        setController(controller);
        setLayoutParams(params);

    }

完整代码:

重写SimpleDraweView实现自定义ArrowSimpleDraweeView

package com.sogou.arrowsdview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;
import android.net.Uri;
import android.util.AttributeSet;
import android.view.ViewGroup;

import com.facebook.common.references.CloseableReference;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.bitmaps.PlatformBitmapFactory;
import com.facebook.imagepipeline.common.ResizeOptions;
import com.facebook.imagepipeline.request.BasePostprocessor;
import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.facebook.imagepipeline.request.Postprocessor;

/**
 * Created by baixuefei on 18/2/11.
 */

public class ArrowSimpleDraweeView extends SimpleDraweeView {

    private static final String TAG = ArrowSimpleDraweeView.class.getSimpleName();
    private float mCornerRadius ;
    private float mArrowTop ;//尖角纵向顶部到矩形登录的距离.
    private float mArrowWidth ;//尖角的横向宽度.
    private float mArrowHeight ;//尖角的纵向高度
    private float mArrowOffset ;

    private int mArrowLocation = 0;
    private static final int LOCATION_LEFT = 0;
    private static final int LOCATION_RIGHT = 1;


    private Context mContext;
    public ArrowSimpleDraweeView(Context context) {
        super(context);
        mContext = context;
        intiView(null);
    }

    public ArrowSimpleDraweeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
        intiView(attrs);
    }

    public ArrowSimpleDraweeView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mContext = context;
        intiView(attrs);
    }

    public void intiView(AttributeSet attrs){


        mCornerRadius = DensityUtils.dip2px(mContext,10);
        mArrowTop =DensityUtils.dip2px(mContext,40);//尖角纵向顶部到矩形登录的距离.
        mArrowWidth = DensityUtils.dip2px(mContext,10);//尖角的横向宽度.
        mArrowHeight = DensityUtils.dip2px(mContext,20);//尖角的纵向高度
        mArrowOffset = -DensityUtils.dip2px(mContext,10);

        if(attrs != null){
            TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ArrowSimpleDraweeView);
            mCornerRadius = a.getDimension(R.styleable.ArrowSimpleDraweeView_aCornerRadius,mCornerRadius);
            mArrowTop = a.getDimension(R.styleable.ArrowSimpleDraweeView_arrowTop,mArrowTop);
            float tmp = a.getDimension(R.styleable.ArrowSimpleDraweeView_arrowWidth,mArrowWidth);
            mArrowWidth = tmp;
            //LogUtils.d(TAG,"mArrowWidth:"+tmp+",DensityUtils.dip2px(10):"+DensityUtils.dip2px(10));
            mArrowHeight = a.getDimension(R.styleable.ArrowSimpleDraweeView_arrowHeihgt,mArrowHeight);
            mArrowOffset = a.getDimension(R.styleable.ArrowSimpleDraweeView_arrowOffsety,mArrowOffset);
            mArrowLocation = a.getInt(R.styleable.ArrowSimpleDraweeView_arrowLocation,mArrowLocation);
            a.recycle();
        }
    }

    public void showImage(final Uri uri, final int imageSuitableWidth, final int imageSuitableHeight){

//        Postprocessor redMeshPostPorcessor = new BasePostprocessor() {
//            @Override
//            public void process(Bitmap destBitmap, Bitmap sourceBitmap) {
//
//                Canvas canvas = new Canvas(destBitmap);
//                int color = 0xff424242;// int color = 0xff424242;
//                Paint paint = new Paint();
//                paint.setColor(color);
//                // 防止锯齿
//                paint.setAntiAlias(true);
//
//                Rect rect = new Rect(0,0,sourceBitmap.getWidth(),sourceBitmap.getHeight());
//
//                RectF rectF = new RectF(rect);
//
//                Path path = new Path();
//                if(mArrowLocation == LOCATION_LEFT){
//                    leftPath(rectF,path);
//                }else {
//                    rightPath(rectF,path);
//                }
//                canvas.drawARGB(0,0,0,0);
//                canvas.drawPath(path,paint);
//                paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
//                canvas.drawBitmap(sourceBitmap,rect,rect,paint);
//
//                LogUtils.d(TAG,"test size - redMeshPostPorcessor.width:"+sourceBitmap.getWidth()+",height:"+sourceBitmap.getHeight()+",destBitmap.width:"+destBitmap.getWidth()+",destBitMap.height:"+destBitmap.getHeight()+",imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight+",mArrowWidth:"+mArrowWidth+",location:"+mArrowLocation+",uri:"+uri);
//            }
//        };

        Postprocessor redMeshPostPorcessor = new BasePostprocessor() {
            @Override
            public CloseableReference process(
                    Bitmap sourceBitmap,
                    PlatformBitmapFactory bitmapFactory) {

                //创建一个安全的 新的bitmap
                CloseableReference bitmapRef = bitmapFactory.createBitmap(
                        imageSuitableWidth,
                        imageSuitableHeight);
                try {
                    Bitmap destBitmap = bitmapRef.get();

                    Canvas canvas = new Canvas(destBitmap);
                    int color = 0xff424242;// int color = 0xff424242;
                    Paint paint = new Paint();
                    paint.setColor(color);
                    // 防止锯齿
                    paint.setAntiAlias(true);

                    Rect rect = new Rect(0,0,imageSuitableWidth,imageSuitableHeight);

                    RectF rectF = new RectF(rect);

                    Path path = new Path();
                    if(mArrowLocation == LOCATION_LEFT){
                        leftPath(rectF,path);
                    }else {
                        rightPath(rectF,path);
                    }
                    canvas.drawARGB(0,0,0,0);
                    canvas.drawPath(path,paint);
                    paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
                    float scale = 1.0f;

                    float scaleWidth = (float) (imageSuitableWidth*1.0/sourceBitmap.getWidth());
                    float scaleHeight = (float) (imageSuitableHeight*1.0/sourceBitmap.getHeight());
                    scale = Math.max(scaleWidth,scaleHeight);
//                    canvas.drawBitmap(sourceBitmap,rect,rect,paint);
                    Matrix matrix = new Matrix();
                    matrix.postScale(scale,scale);
                    //LogUtils.d(TAG,"scaleWidth:"+scaleWidth+",scaleHeight:"+scaleHeight+",scale:"+scale);
                    //LogUtils.d(TAG,"test size - redMeshPostPorcessor.width:"+sourceBitmap.getWidth()+",height:"+sourceBitmap.getHeight()+",destBitmap.width:"+destBitmap.getWidth()+",destBitMap.height:"+destBitmap.getHeight()+",imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight+",mArrowWidth:"+mArrowWidth+",location:"+mArrowLocation+",uri:"+uri);
                    canvas.drawBitmap(sourceBitmap,matrix,paint);
                    return CloseableReference.cloneOrNull(bitmapRef);
                } finally {
                    CloseableReference.closeSafely(bitmapRef);
                }
            }
        };


        //LogUtils.d(TAG,"test size - imageSuitableWidth:"+imageSuitableWidth+",imageSuitableHeight:"+imageSuitableHeight);
        ImageRequest request = ImageRequestBuilder.newBuilderWithSource(uri)
                .setResizeOptions(new ResizeOptions(imageSuitableWidth, imageSuitableHeight))
                .setPostprocessor(redMeshPostPorcessor)
                .build();

        ViewGroup.LayoutParams params = getLayoutParams();
        params.width = imageSuitableWidth;
        params.height = imageSuitableHeight;
        DraweeController controller = Fresco.newDraweeControllerBuilder()
                .setOldController(getController())
                .setImageRequest(request)
                .build();
        setController(controller);
        setLayoutParams(params);

    }


    /**
     * 绘制尖角在左侧的path
     *
     *         A
     *         * * * * * * * * * * * * *
     *       *                 *         *
     *     *                   *           *
     *     *  F                *     B      *
     *     *                   *            *
     *     * mArrowTop         * * * * * * **
     *   *                                  *
     *  *                                   *
     *   *                                  *
     *     *                                *
     *     *                                * C
     *     *                                *
     *     *                                *
     *     *  E                             *
     *     *                                *
     *     *                                *
     *     *                                *
     *     *                                *
     *     *                               *
     *      *                             *
     *        * * * * * * * * * * * * * *
     *                     D
     *
     * @param rect
     * @param path  如上图所示 从A点开始 顺时针绘制path路径.
     */
    public void leftPath(RectF rect, Path path) {
        path.moveTo(mCornerRadius + mArrowWidth, rect.top);//移动到A点
        path.lineTo(rect.width(), rect.top);//顶部横线
        path.arcTo(new RectF(rect.right - mCornerRadius * 2, rect.top, rect.right,
                mCornerRadius * 2 + rect.top), 270, 90);//绘制 右上角的90度的圆弧. (B对应的区域)
        path.lineTo(rect.right, rect.top);//绘制 右侧直线
        path.arcTo(new RectF(rect.right - mCornerRadius * 2, rect.bottom - mCornerRadius * 2,
                rect.right, rect.bottom), 0, 90);//右下角圆弧
        path.lineTo(rect.left + mArrowWidth, rect.bottom);//底部横线 (D对应的横线)
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mCornerRadius * 2,
                mCornerRadius * 2 + rect.left + mArrowWidth, rect.bottom), 90, 90);//左下角圆弧
        path.lineTo(rect.left + mArrowWidth, mArrowTop + mArrowHeight);//左侧偏下部竖线(E所示竖线)
        path.lineTo(rect.left, mArrowTop - mArrowOffset);//左侧凸起尖角 下半部分斜线
        path.lineTo(rect.left + mArrowWidth, mArrowTop); //左侧凸起尖角 上半部分斜线
        path.lineTo(rect.left + mArrowWidth, rect.top);//左侧片上部竖线(F所示竖线)
        path.arcTo(new RectF(rect.left + mArrowWidth, rect.top, mCornerRadius * 2
                + rect.left + mArrowWidth, mCornerRadius * 2 + rect.top), 180, 90);//左上角圆弧

        path.close();
    }

    /**
     *  绘制 尖角在右侧 的path
     * @param rect
     * @param path
     */
    public void rightPath(RectF rect, Path path) {
        path.moveTo(mCornerRadius, rect.top);
        path.lineTo(rect.width(), rect.top);
        path.arcTo(new RectF(rect.right - mCornerRadius * 2 - mArrowWidth, rect.top,
                rect.right - mArrowWidth, mCornerRadius * 2 + rect.top), 270, 90);
        path.lineTo(rect.right - mArrowWidth, mArrowTop);
        path.lineTo(rect.right, mArrowTop - mArrowOffset);
        path.lineTo(rect.right - mArrowWidth, mArrowTop + mArrowHeight);
        path.lineTo(rect.right - mArrowWidth, rect.height() - mCornerRadius);
        path.arcTo(new RectF(rect.right - mCornerRadius * 2 - mArrowWidth, rect.bottom
                - mCornerRadius * 2, rect.right - mArrowWidth, rect.bottom), 0, 90);
        path.lineTo(rect.left, rect.bottom);
        path.arcTo(new RectF(rect.left, rect.bottom - mCornerRadius * 2, mCornerRadius * 2
                + rect.left, rect.bottom), 90, 90);
        path.lineTo(rect.left, rect.top);
        path.arcTo(new RectF(rect.left, rect.top, mCornerRadius * 2 + rect.left,
                mCornerRadius * 2 + rect.top), 180, 90);
        path.close();
    }


    public float getmCornerRadius() {
        return mCornerRadius;
    }

    public void setmCornerRadius(float mCornerRadius) {
        this.mCornerRadius = mCornerRadius;
    }

    public float getmArrowTop() {
        return mArrowTop;
    }

    public void setmArrowTop(float mArrowTop) {
        this.mArrowTop = mArrowTop;
    }

    public float getmArrowWidth() {
        return mArrowWidth;
    }

    public void setmArrowWidth(float mArrowWidth) {
        this.mArrowWidth = mArrowWidth;
    }

    public float getmArrowHeight() {
        return mArrowHeight;
    }

    public void setmArrowHeight(float mArrowHeight) {
        this.mArrowHeight = mArrowHeight;
    }

    public float getmArrowOffset() {
        return mArrowOffset;
    }

    public void setmArrowOffset(float mArrowOffset) {
        this.mArrowOffset = mArrowOffset;
    }

    public int getmArrowLocation() {
        return mArrowLocation;
    }

    public void setmArrowLocation(int mArrowLocation) {
        this.mArrowLocation = mArrowLocation;
    }
}


res->values->style.xml中 定义ArrowSimpleDraweeView 自定义属性

 
    
        
        
        
        
        
        
            
            
        
    

布局文件中声明 ArrowSimpleDraweeView


利用ArrowSimpleDraweeView加载尖嘴图片

 vh.leftImage.showImage(uri,imageSuitableWidth,imageSuitableHeight);

最终实现效果:


image

github地址:
https://github.com/feifei-123/ArrowSimpleDraweeView-master

你可能感兴趣的:(SimpleDraweeView实现聊天图片消息的尖嘴效果)