Path 实现点九图效果 (聊天背景)

2,726 阅读11分钟

今天是我第一次在掘金发表文章,作为一个菜鸟,经常找UI哥.为啥呢?因为有些效果需要点九图啊,虽然SDK带有工具,但是首先你得有那个原图,才能在四周加线啊!所以今儿我们就来实现一个利用Path绘制一个类似点九图的效果背景!

首先,还是来个图说明哈,无图无真相!

看官感兴趣就往下看!在这里感谢 github.com/lguipeng/Bu…,
我们是站在了巨人的肩膀上,才能看得更远!下面我就参考大神的样例,加上我自己的一些体会,讲讲具体实现过程!

在这里我们需要使用 Path,所以先说下我们需要使用和 Path 相关的几个API

  1. moveTo(float x, float y),将画笔移动到某一个点,画笔的原始位置在屏幕的左上方,即(0,0)位置.
  2. lineTo(float x, float y),从画笔所在位置划线到(float x,float y).
  3. arcTo(RectF oval, float startAngle, float sweepAngle),其中oval 确定了圆弧的起点和终点,startAngle是圆弧的起始角度,sweepAngle是圆弧角度,也就是圆心和起点的连线与圆心和终点的连线的夹角角度.

接下来我们将效果从简单到复杂三步走, 第一步 我们画一个矩形.


package com.jooyer.bubbleview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * 使用 Path 实现点九图效果
 * Created by Jooyer on 2017/3/11
 */

public class JooyerBubbleView extends TextView {
    private Paint mPaint;
    private Path mPath;
    // 画笔描边默认宽度
    private static final float DEFAULT_STROKE_WIDTH = 10;

    public JooyerBubbleView(Context context) {
        this(context, null);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 画笔颜色
        mPaint.setColor(Color.GREEN);
        // 画笔的填充模式
        mPaint.setStyle(Paint.Style.STROKE);
        // 画笔描边宽度
        mPaint.setStrokeWidth(DEFAULT_STROKE_WIDTH);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRect(canvas);
    }

    private void drawRect(Canvas canvas) {
        mPath.moveTo(100,100);
        mPath.lineTo(500,100);
        mPath.lineTo(500,300);
        mPath.lineTo(100,300);
        mPath.close();

        canvas.drawPath(mPath,mPaint);
    }
}


这里贴个布局代码,方便大家直接拷贝过去运行,就看到效果啦!

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.jooyer.bubbleview.MainActivity">

    <com.jooyer.bubbleview.JooyerBubbleView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />

</RelativeLayout>

得到的效果如图

有的朋友可能会问了,画个矩形,直接 canvas.drawRect()不就好了嘛!
是的,条条大路通罗马.不过我们这里因为要用到四个角,对其进行圆弧处理,所以用 path 更方便处理了!
废话那么多,我自己都不好意思了,那么接下来实现 第二步 四个圆角部分.


package com.jooyer.bubbleview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * 使用 Path 实现点九图效果
 * Created by Jooyer on 2017/3/11
 */

public class JooyerBubbleView extends TextView {
    private Paint mPaint;
    private Path mPath;
    // 默认圆角半径
    private static final float DEFAULT_RADIUS = 50;

    // 画笔描边默认宽度
    private static final float DEFAULT_STROKE_WIDTH = 10;

    public JooyerBubbleView(Context context) {
        this(context, null);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 画笔颜色
        mPaint.setColor(Color.GREEN);
        // 画笔的填充模式
        mPaint.setStyle(Paint.Style.STROKE);
        // 画笔描边宽度
        mPaint.setStrokeWidth(DEFAULT_STROKE_WIDTH);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRect(canvas);
    }

    private void drawRect(Canvas canvas) {
        // 这个地方因为圆弧的关系,起点需要有(100,100)改变为(100 + DEFAULT_RADIUS,100)
        mPath.moveTo(100 + DEFAULT_RADIUS, 100);
        // 因为绘制了一段圆弧,所以这里的终点是圆弧的起点,所以这里的值不能是(500,100)
        mPath.lineTo(500 - DEFAULT_RADIUS, 100);
        //绘制右上第一个圆角
        mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), 100, 500, (100 + DEFAULT_RADIUS)), 270, 90);
        //同理这里的终点则是下一段圆弧的起点
        mPath.lineTo(500, 300 - DEFAULT_RADIUS);
        mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), (300 - DEFAULT_RADIUS), 500, 300), 0, 90);
        mPath.lineTo(100 + DEFAULT_RADIUS, 300);
        mPath.arcTo(new RectF(100, (300 - DEFAULT_RADIUS), (100 + DEFAULT_RADIUS), 300), 90, 90);
        mPath.lineTo(100, 300 - DEFAULT_RADIUS);
        mPath.arcTo(new RectF(100, 100, (100 + DEFAULT_RADIUS), (100 + DEFAULT_RADIUS)), 180, 90);
        mPath.close();

        canvas.drawPath(mPath, mPaint);

    }
}


得到的效果如图:

估计大家有个疑问,第一个圆弧那里:
mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), 100, 500, (100 + DEFAULT_RADIUS)), 270, 90); 为何是270°呢?
开始我也没搞明白,后来才知道,Android里面绘图角度是顺时针转动的,朝右为正,朝下为正.请看下图:

注释比较清楚了,其他的如果有问题就给我发邮件吧?Jooyer@outlook.com,注意第一个字符大写哦!

好了,终于到了我们最后阶段了.


package com.jooyer.bubbleview;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * 使用 Path 实现点九图效果
 * Created by Jooyer on 2017/3/11
 */

public class JooyerBubbleView extends TextView {
    private Paint mPaint;
    private Path mPath;
    // 默认圆角半径
    private static final float DEFAULT_RADIUS = 50;

    // 画笔描边默认宽度
    private static final float DEFAULT_STROKE_WIDTH = 10;

    public JooyerBubbleView(Context context) {
        this(context, null);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public JooyerBubbleView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        mPaint = new Paint();
        // 抗锯齿
        mPaint.setAntiAlias(true);
        // 画笔颜色
        mPaint.setColor(Color.GREEN);
        // 画笔的填充模式
        mPaint.setStyle(Paint.Style.STROKE);
        // 画笔描边宽度
        mPaint.setStrokeWidth(DEFAULT_STROKE_WIDTH);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        drawRect(canvas);
    }

    private void drawRect(Canvas canvas) {
        // 这个地方因为圆弧的关系,起点需要有(100,100)改变为(100 + DEFAULT_RADIUS,100)
        mPath.moveTo(100 + DEFAULT_RADIUS, 100);
        // 因为绘制了一段圆弧,所以这里的终点是圆弧的起点,所以这里的值不能是(500,100)
        mPath.lineTo(500 - DEFAULT_RADIUS, 100);
        //绘制右上第一个圆角
        mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), 100, 500, (100 + DEFAULT_RADIUS)), 270, 90);
        //同理这里的终点则是下一段圆弧的起点
        mPath.lineTo(500, 300 - DEFAULT_RADIUS);
        mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), (300 - DEFAULT_RADIUS), 500, 300), 0, 90);
        mPath.lineTo(100 + DEFAULT_RADIUS, 300);
        mPath.arcTo(new RectF(100, (300 - DEFAULT_RADIUS), (100 + DEFAULT_RADIUS), 300), 90, 90);

        // 假设我们想凸起部分在左侧,其实我们知道凸起部分就是2条线段而已,所以处理就简单了
        // 我们假设凸起部分的高度为 50 ,宽度也为50,也就是三角的底边长度50,底边高度也是50

//        mPath.lineTo(100, 100 + DEFAULT_RADIUS);
        // 那么上面的这个线则不能移动到 (100, 100 + DEFAULT_RADIUS),必须加上一个三角形底边长度
        mPath.lineTo(100, 100 + DEFAULT_RADIUS + 50);
        // 等腰三角形,所以其定点高度值为底边一半
        mPath.lineTo(100 - 50, 100 + DEFAULT_RADIUS + 25);
        mPath.lineTo(100,100 + DEFAULT_RADIUS);

        mPath.arcTo(new RectF(100, 100, (100 + DEFAULT_RADIUS), (100 + DEFAULT_RADIUS)), 180, 90);
        mPath.close();
        canvas.drawPath(mPath, mPaint);

    }
}



得到的效果如图:

是不是觉得三角很丑呢,这个你可以根据需要调整其大小,就好看咯!
三部曲完成了,那么接下来就是我们的实际使用时间了.上面只是演示 Path 具体用法,但是如果想把上面的效果当作点九图来使用,也为了以后的复用,我们将画笔操作单独提出来,放在一个自定义的drawble中,这个 drawable仅仅使用了以上画笔的功能,也就是把画笔的这部分功能封装在drawable 里面了而已!

首先我们看看 JooyerBubbleDrawable 这个类


package com.jooyer.bubbleview;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PixelFormat;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.util.Log;

/**
 * Created by Jooyer on 2017/3/11
 */

public class JooyerBubbleDrawable extends Drawable {
    private static final String TAG = JooyerBubbleDrawable.class.getSimpleName();

    /**
     * 保存坐标(自定义控件的大小)
     */
    private RectF mRect;

    /**
     * 气泡的路径
     */
    private Path mPath = new Path();


    private Paint mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    /**
     * 箭头宽度
     */
    private float mArrowWidth;

    /**
     * 箭头宽度
     */
    private float mArrowHeight;

    /**
     * 圆弧半径
     */
    private float mRadius;
    /**
     * 箭头所在位置偏移量
     */
    private float mArrowOffset;

    /**
     * 气泡背景色
     */
    private int mBubbleColor;


    /**
     * 三角箭头所在位置
     */
    private ArrowDirection mArrowDirection;


    /**
     * 箭头是否居中
     */
    private boolean mArrowCenter;

    /**
     *  重写此方法,在这里实现和 自定义控件中 onDraw 类似的功能
     */
    @Override
    public void draw(Canvas canvas) {
        mPaint.setColor(mBubbleColor);
        setUpPath(mArrowDirection, mPath);
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public void setAlpha(int alpha) {
        mPaint.setAlpha(alpha);
    }

    @Override
    public void setColorFilter(ColorFilter colorFilter) {
        mPaint.setColorFilter(colorFilter);
    }

    @Override
    public int getOpacity() {
        return PixelFormat.TRANSLUCENT; //窗口透明化
    }


    private void setUpPath(ArrowDirection arrowDirection, Path path) {
        switch (arrowDirection) {
            case LEFT:
                setUpLeftPath(mRect, path);
                break;
            case TOP:
                setUpTopPath(mRect, path);
                break;
            case RIGHT:
                setUpRightPath(mRect, path);
                break;
            case BOTTOM:
                setUpBottomPath(mRect,path);
                break;
        }
    }

    /**
     * 箭头朝左
     */
    private void setUpLeftPath(RectF rect, Path path) {
        if (mArrowCenter)
            mArrowOffset = (rect.bottom - rect.top - mArrowWidth) / 2;

        path.moveTo(rect.left + mArrowWidth + mRadius, rect.top);
        path.lineTo(rect.width() - mRadius, rect.top); // 这里的rect.width() 是可以使用rect.right
        Log.i(TAG, "====setUpLeftPath========" + (rect.width() - mRadius) + "======= : " + (rect.right - mRadius));

        path.arcTo(new RectF(rect.right - mRadius, rect.top, rect.right, rect.top + mRadius), 270, 90);
        path.lineTo(rect.right, rect.bottom - mRadius);

        path.arcTo(new RectF(rect.right - mRadius, mRect.bottom - mRadius, rect.right, rect.bottom), 0, 90);
        path.lineTo(rect.left + mArrowWidth + mRadius, rect.bottom);

        path.arcTo(new RectF(rect.left + mArrowWidth, rect.bottom - mRadius, rect.left + mArrowWidth + mRadius, rect.bottom), 90, 90);
        path.lineTo(rect.left + mArrowWidth, mArrowHeight + mArrowOffset);
        path.lineTo(rect.left, mArrowOffset + mArrowHeight / 2);
        path.lineTo(rect.left + mArrowWidth, mArrowOffset);
        path.lineTo(rect.left + mArrowWidth, rect.top + mRadius);

        path.arcTo(new RectF(rect.left + mArrowWidth, mRect.top, rect.left + mArrowWidth + mRadius, rect.top + mRadius), 180, 90);
        path.close();

    }

    /**
     * 箭头朝上
     */
    private void setUpTopPath(RectF rect, Path path) {
        if (mArrowCenter)
            mArrowOffset = (rect.right - rect.left - mArrowWidth) / 2;

        path.moveTo(rect.left + Math.min(mRadius, mArrowOffset), rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowOffset, rect.top + mArrowHeight);
        path.lineTo(rect.left + mArrowOffset + mArrowWidth / 2, rect.top);
        path.lineTo(rect.left + mArrowOffset + mArrowWidth, rect.top + mArrowHeight);
        path.lineTo(rect.right - mRadius, rect.top + mArrowHeight);

        path.arcTo(new RectF(rect.right - mRadius, rect.top + mArrowHeight, rect.right, rect.top + mArrowHeight + mRadius), 270, 90);
        path.lineTo(rect.right, rect.bottom - mRadius);

        path.arcTo(new RectF(rect.right - mRadius, rect.bottom - mRadius, rect.right, rect.bottom), 0, 90);
        path.lineTo(rect.left + mRadius, rect.bottom);

        path.arcTo(new RectF(rect.left, rect.bottom - mRadius, rect.left + mRadius, rect.bottom), 90, 90);
        path.lineTo(rect.left, rect.top + mArrowHeight + mRadius);

        path.arcTo(new RectF(rect.left, rect.top + mArrowHeight, rect.left + mRadius, rect.top + mArrowHeight + mRadius), 180, 90);
        path.close();
    }

    /**
     * 箭头朝右
     */
    private void setUpRightPath(RectF rect, Path path) {
        if (mArrowCenter)
            mArrowOffset = (rect.bottom - rect.top - mArrowWidth) / 2;

        path.moveTo(rect.left + mRadius, rect.top);
        path.lineTo(rect.right - mRadius - mArrowWidth, rect.top);

        path.arcTo(new RectF(rect.right - mArrowWidth - mRadius, rect.top, rect.right - mArrowWidth, rect.top + mRadius), 270, 90);
        path.lineTo(rect.right - mArrowWidth, rect.top + mArrowOffset);
        path.lineTo(rect.right, rect.top + mArrowOffset + mArrowHeight / 2);
        path.lineTo(rect.right - mArrowWidth, rect.top + mArrowOffset + mArrowHeight);
        path.lineTo(rect.right - mArrowWidth, rect.bottom - mRadius);

        path.arcTo(new RectF(rect.right - mArrowWidth - mRadius, rect.bottom - mRadius, rect.right - mArrowWidth, rect.bottom), 0, 90);
        path.lineTo(rect.right - mArrowWidth - mRadius, rect.bottom);

        path.arcTo(new RectF(rect.left, rect.bottom - mRadius, rect.left + mRadius, rect.bottom), 90, 90);
        path.lineTo(rect.left, rect.top + mRadius);

        path.arcTo(new RectF(rect.left, rect.top, rect.left + mRadius, rect.top + mRadius), 180, 90);
        path.close();
    }

    /**
     * 箭头朝下
     */
    private void setUpBottomPath(RectF rect, Path path) {
        if (mArrowCenter)
            mArrowOffset = (rect.right - rect.left - mArrowWidth) / 2;

        path.moveTo(rect.left + mRadius, rect.top);
        path.lineTo(rect.right - mRadius, rect.top);

        path.arcTo(new RectF(rect.right - mRadius, rect.top, rect.right, rect.top + mRadius), 270, 90);
        path.lineTo(rect.right, rect.bottom - mArrowHeight - mRadius);

        path.arcTo(new RectF(rect.right - mRadius, rect.bottom - mArrowHeight - mRadius, rect.right, rect.bottom - mArrowHeight), 0, 90);
        path.lineTo(rect.left + mArrowOffset + mArrowWidth, rect.bottom - mArrowHeight);
        path.lineTo(rect.left + mArrowOffset + mArrowWidth / 2, rect.bottom);
        path.lineTo(rect.left + mArrowOffset, rect.bottom - mArrowHeight);
        path.lineTo(rect.left + mRadius, rect.bottom - mArrowHeight);

        path.arcTo(new RectF(rect.left, rect.bottom - mArrowHeight - mRadius, rect.left + mRadius, rect.bottom - mArrowHeight), 90, 90);
        path.lineTo(rect.left, rect.top + mRadius);

        path.arcTo(new RectF(rect.left, rect.top,rect.left + mRadius,rect.top + mRadius),180,90);
        path.close();
    }

    private JooyerBubbleDrawable(Builder builder) {
        this.mRect = builder.mRectF;
        this.mRadius = builder.mRadius;
        this.mArrowWidth = builder.mArrowWidth;
        this.mArrowHeight = builder.mArrowHeight;
        this.mArrowOffset = builder.mArrowOffset;
        this.mBubbleColor = builder.mBubbleColor;
        this.mArrowDirection = builder.mArrowDirection;
        this.mArrowCenter = builder.mArrowCenter;
    }


    /**
     * 建造者模式
     */
    public static class Builder {
        /**
         * 箭头默认宽度
         */
        public static float DEFAULT_ARROW_WIDTH = 25;
        /**
         * 箭头默认高度
         */
        public static float DEFAULT_ARROW_HEIGHT = 25;
        /**
         * 默认圆角半径
         */
        public static float DEFAULT_RADIUS = 20;
        /**
         * 默认箭头偏移量
         */
        public static float DEFAULT_ARROW_OFFSET = 50;
        /**
         * 气泡默认背景颜色
         */
        public static int DEFAULT_BUBBLE_COLOR = Color.RED;

        private RectF mRectF;
        private float mArrowWidth = DEFAULT_ARROW_WIDTH;
        private float mArrowHeight = DEFAULT_ARROW_HEIGHT;
        private float mRadius = DEFAULT_RADIUS;
        private float mArrowOffset = DEFAULT_ARROW_OFFSET;

        private int mBubbleColor = DEFAULT_BUBBLE_COLOR;
        private ArrowDirection mArrowDirection = ArrowDirection.LEFT;
        private boolean mArrowCenter;

        public Builder rect(RectF rect) {
            this.mRectF = rect;
            return this;
        }

        public Builder arrowWidth(float width) {
            this.mArrowWidth = width;
            return this;
        }

        public Builder arrowHeight(float height) {
            this.mArrowHeight = height;
            return this;
        }

        public Builder radius(float angle) {
            this.mRadius = angle; //TODO
            return this;
        }

        public Builder arrowOffset(float position) {
            this.mArrowOffset = position;
            return this;
        }

        public Builder bubbleColor(int color) {
            this.mBubbleColor = color;
            return this;
        }


        public Builder arrowDirection(ArrowDirection direction) {
            this.mArrowDirection = direction;
            return this;
        }

        public Builder arrowCenter(boolean arrowCenter) {
            this.mArrowCenter = arrowCenter;
            return this;
        }

        public JooyerBubbleDrawable build() {
            if (null == mRectF) {
                throw new IllegalArgumentException("BubbleDrawable RectF can not be null");
            }
            return new JooyerBubbleDrawable(this);
        }
    }




    /**
     * 箭头位置
     */
    public enum ArrowDirection {
        LEFT(0x00),
        TOP(0x01),
        RIGHT(0x02),
        BOTTOM(0x03);

        private int mValue;

        ArrowDirection(int value) {
            mValue = value;
        }

        private int getIntValue() {
            return mValue;
        }

        public static ArrowDirection getDefault() {
            return LEFT;
        }

        public static ArrowDirection mapIntToValue(int stateInt) {
            for (ArrowDirection value : ArrowDirection.values()) {
                if (stateInt == value.getIntValue()) {
                    return value;
                }
            }
            return getDefault();
        }
    }


}



然后看看 JooyerTextView 这个类:


package com.jooyer.bubbleview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.widget.TextView;

/**
 * Created by Jooyer on 2017/3/11
 */

public class JooyerTextView extends TextView {

    private static  String TAG = JooyerTextView.class.getSimpleName();
    private JooyerBubbleDrawable mBubbleDrawable;

    private float mArrowWidth;
    private float mArrowHeight;
    private float mRadius;
    private float mArrowOffset;
    private int mBubbleColor;
    private JooyerBubbleDrawable.ArrowDirection mArrowDirection;
    private boolean mArrowCenter;

    public JooyerTextView(Context context) {
        this(context,null);
    }

    public JooyerTextView(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public JooyerTextView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.JooyerBubble);
        mArrowWidth = array.getDimension(R.styleable.JooyerBubble_jooyer_bubble_arrow_width,
                JooyerBubbleDrawable.Builder.DEFAULT_ARROW_WIDTH);
        mArrowHeight = array.getDimension(R.styleable.JooyerBubble_jooyer_bubble_arrow_width,
                JooyerBubbleDrawable.Builder.DEFAULT_ARROW_HEIGHT);
        mArrowOffset = array.getDimension(R.styleable.JooyerBubble_jooyer_bubble_arrow_width,
                JooyerBubbleDrawable.Builder.DEFAULT_ARROW_OFFSET);
        mRadius = array.getDimension(R.styleable.JooyerBubble_jooyer_bubble_arrow_width,
                JooyerBubbleDrawable.Builder.DEFAULT_RADIUS);
        mBubbleColor = array.getColor(R.styleable.JooyerBubble_jooyer_bubble_arrow_color,
                JooyerBubbleDrawable.Builder.DEFAULT_BUBBLE_COLOR);
        mArrowCenter = array.getBoolean(R.styleable.JooyerBubble_jooyer_bubble_arrow_center,
                false);
        int direction = array.getInt(R.styleable.JooyerBubble_jooyer_bubble_arrow_direction,
               0);
        mArrowDirection = JooyerBubbleDrawable.ArrowDirection.mapIntToValue(direction);
        array.recycle();
        setPadding();
    }

    /**
     *  由于箭头的问题,当有 padding 时我们需要再加三角箭头的尺寸
     */
    private void setPadding() {
        int left = getPaddingLeft();
        int top = getPaddingTop();
        int right = getPaddingRight();
        int bottom = getPaddingBottom();
        switch (mArrowDirection){
            case LEFT:
                left += mArrowWidth;
                break;
            case TOP:
                top += mArrowHeight;
                break;
            case RIGHT:
                right += mArrowWidth;
                break;
            case BOTTOM:
                    bottom += mArrowHeight;
                break;

        }
        setPadding(left,top,right,bottom);
    }

    /**
     *  当大小发生改变时,我们需要重绘
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w > 0 && h > 0) {
            reset(w, h);
        }
    }
    /**
     *  当位置发生改变时,我们需要重绘
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        reset(getWidth(),getHeight());
    }

    @Override
    protected void onDraw(Canvas canvas) {
        // 绘制背景
        if (null != mBubbleDrawable){
            mBubbleDrawable.draw(canvas);
        }
        super.onDraw(canvas);
    }

    private void reset(int width, int height) {
        reset(0,0,width,height);
    }

    private void reset(int left, int top, int right, int bottom) {
        RectF rectF = new RectF(left,top,right,bottom);
        mBubbleDrawable = new JooyerBubbleDrawable.Builder()
                .rect(rectF)
                .arrowWidth(mArrowWidth)
                .arrowHeight(mArrowHeight)
                .radius(mRadius)
                .arrowOffset(mArrowOffset)
                .arrowDirection(mArrowDirection)
                .arrowCenter(mArrowCenter)
                .bubbleColor(mBubbleColor)
                .build();

    }
}



接着看下 jooyer_bobbleview_attrs 这个文件:

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <declare-styleable name="JooyerBubble">
        <!-- 三角箭头宽度 -->
        <attr name="jooyer_bubble_arrow_width" format="dimension"/>
        <!-- 三角箭头高度 -->
        <attr name="jooyer_bubble_arrow_height" format="dimension"/>
        <!-- 三角箭头位置(相对偏移量) -->
        <attr name="jooyer_bubble_arrow_offset" format="dimension"/>
        <!-- 气泡圆角半径 -->
        <attr name="jooyer_bubble_arrow_radius" format="dimension"/>
        <!-- 气泡背景颜色 -->
        <attr name="jooyer_bubble_arrow_color" format="color"/>
        <!-- 三角箭头是否居中 -->
        <attr name="jooyer_bubble_arrow_center" format="boolean"/>
        <!-- 三角箭头方向朝向 -->
        <attr name="jooyer_bubble_arrow_direction" format="enum">
            <enum name="jooyer_bubble_arrow_direction_left" value="0x00"/>
            <enum name="jooyer_bubble_arrow_direction_top" value="0x01"/>
            <enum name="jooyer_bubble_arrow_direction_right" value="0x02"/>
            <enum name="jooyer_bubble_arrow_direction_bottom" value="0x03"/>
        </attr>
    </declare-styleable>

</resources>

还有布局文件:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:orientation="vertical"
    tools:context="com.jooyer.bubbleview.MainActivity">

    <com.jooyer.bubbleview.JooyerTextView
        android:text="@string/test"
        android:textSize="20sp"
        android:textColor="@color/color_test"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="15dp"
        app:jooyer_bubble_arrow_width="20dp"
        app:jooyer_bubble_arrow_height="20dp"
        app:jooyer_bubble_arrow_offset="20dp"
        app:jooyer_bubble_arrow_radius="20dp"
        app:jooyer_bubble_arrow_color="#e363e134"
        app:jooyer_bubble_arrow_center="false"
        app:jooyer_bubble_arrow_direction="jooyer_bubble_arrow_direction_left"
        />

</LinearLayout>

最后来看下运行的效果图:

是不是觉得不错呢,哈哈!如果喜欢记得点赞收藏关注哦哦!下一章咱们就理由今天的学习实现常见toolbar点击弹出菜单效果,欢喜前来踢场!