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<Bitmap> 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<Bitmap> process(
                    Bitmap sourceBitmap,
                    PlatformBitmapFactory bitmapFactory) {

                //创建一个安全的 新的bitmap
                CloseableReference<Bitmap> 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<Bitmap> process(
                    Bitmap sourceBitmap,
                    PlatformBitmapFactory bitmapFactory) {

                //创建一个安全的 新的bitmap
                CloseableReference<Bitmap> 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 自定义属性

 <!-- 带尖角的simpleDrawView的属性 -->
    <declare-styleable name="ArrowSimpleDraweeView">
        <attr name="aCornerRadius" format="dimension"/>
        <attr name="arrowTop" format="dimension"/>
        <attr name="arrowWidth" format="dimension"/>
        <attr name="arrowHeihgt" format="dimension"/>
        <attr name="arrowOffsety" format="dimension"/>
        <attr name="arrowLocation" format="enum">
            <enum name="left" value="0"/>
            <enum name="right" value="1"/>
        </attr>
    </declare-styleable>

布局文件中声明 ArrowSimpleDraweeView

<com.sogou.arrowsdview.ArrowSimpleDraweeView
                xmlns:fresco="http://schemas.android.com/apk/res-auto"
                android:id="@+id/image_left"
                android:layout_width="200dp"
                android:layout_height="200dp"
                fresco:placeholderImage="@drawable/default_photo"
                fresco:roundedCornerRadius="5dp"
                fresco:arrowHeihgt="10dp"
                fresco:arrowTop="10dp"
                fresco:arrowOffsety="-5dp"
                fresco:arrowWidth="5dp"
                fresco:aCornerRadius="5dp"
                fresco:arrowLocation="left"
                android:layout_marginLeft="6dp"
                android:layout_marginRight="115dp"/>

利用ArrowSimpleDraweeView加载尖嘴图片

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

最终实现效果:


image

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,546评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,224评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,911评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,737评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,753评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,598评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,338评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,249评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,696评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,888评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,013评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,731评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,348评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,929评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,048评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,203评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,960评论 2 355

推荐阅读更多精彩内容