Path 實現點九圖效果 (聊天背景)

Jooyer發表於2017-03-12

今天是我第一次在掘金髮表文章,作為一個菜鳥,經常找UI哥.為啥呢?因為有些效果需要點九圖啊,雖然SDK帶有工具,但是首先你得有那個原圖,才能在四周加線啊!所以今兒我們就來實現一個利用Path繪製一個類似點九圖的效果背景!

首先,還是來個圖說明哈,無圖無真相!

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>複製程式碼

得到的效果如圖

Path 實現點九圖效果 (聊天背景)

有的朋友可能會問了,畫個矩形,直接 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);

    }
}
複製程式碼


得到的效果如圖:

Path 實現點九圖效果 (聊天背景)

估計大家有個疑問,第一個圓弧那裡:
mPath.arcTo(new RectF((500 - DEFAULT_RADIUS), 100, 500, (100 + DEFAULT_RADIUS)), 270, 90); 為何是270°呢?
開始我也沒搞明白,後來才知道,Android裡面繪圖角度是順時針轉動的,朝右為正,朝下為正.請看下圖:

Path 實現點九圖效果 (聊天背景)

註釋比較清楚了,其他的如果有問題就給我發郵件吧?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 實現點九圖效果 (聊天背景)

是不是覺得三角很醜呢,這個你可以根據需要調整其大小,就好看咯!
三部曲完成了,那麼接下來就是我們的實際使用時間了.上面只是演示 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>複製程式碼

最後來看下執行的效果圖:

Path 實現點九圖效果 (聊天背景)

是不是覺得不錯呢,哈哈!如果喜歡記得點贊收藏關注哦哦!下一章我們們就理由今天的學習實現常見toolbar點選彈出選單效果,歡喜前來踢場!

相關文章