首页 » 技术分享 » Android 自定义View聊天 气泡

Android 自定义View聊天 气泡

 

 https://github.com/xujiaji/HappyBubble

这里我只需要View

package com.as.happybubble;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.graphics.Region;
import android.os.Bundle;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.FrameLayout;


/**
 * 气泡布局
 * Created by JiajiXu on 17-12-1.
 */

public class BubbleLayout extends FrameLayout
{
    private Paint mPaint;
    private Path mPath;
    private Look mLook;
    private int mBubblePadding;
    private int mWidth, mHeight;
    private int mLeft, mTop, mRight, mBottom;
    private int mLookPosition, mLookWidth, mLookLength;
    private int mShadowColor, mShadowRadius, mShadowX, mShadowY;
    private int mBubbleRadius, mBubbleColor;
    private OnClickEdgeListener mListener;
    private Region mRegion = new Region();

    /**
     * 箭头指向
     */
    public enum Look
    {
        /**
         * 坐上右下
         */
        LEFT(1), TOP(2), RIGHT(3), BOTTOM(4);
        int value;

        Look(int v)
        {
            value = v;
        }

        public static Look getType(int value)
        {
            Look type = Look.BOTTOM;
            switch (value)
            {
                case 1:
                    type = Look.LEFT;
                    break;
                case 2:
                    type = Look.TOP;
                    break;
                case 3:
                    type = Look.RIGHT;
                    break;
                case 4:
                    type = Look.BOTTOM;
                    break;
            }

            return type;
        }
    }


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

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

    public BubbleLayout(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        setLayerType(LAYER_TYPE_SOFTWARE, null);
        setWillNotDraw(false);
        initAttr(context.obtainStyledAttributes(attrs, R.styleable.BubbleLayout, defStyleAttr, 0));
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
        mPaint.setStyle(Paint.Style.FILL);
        mPath = new Path();
        initPadding();
    }

    public void initPadding()
    {
        int p = mBubblePadding * 2;
        switch (mLook)
        {
            case BOTTOM:
                setPadding(p, p, p, mLookLength + p);
                break;
            case TOP:
                setPadding(p, p + mLookLength, p, p);
                break;
            case LEFT:
                setPadding(p + mLookLength, p, p, p);
                break;
            case RIGHT:
                setPadding(p, p, p + mLookLength, p);
                break;
        }
    }

    /**
     * 初始化参数
     */
    private void initAttr(TypedArray a)
    {
        mLook = Look.getType(a.getInt(R.styleable.BubbleLayout_lookAt, Look.BOTTOM.value));
        mLookPosition = a.getDimensionPixelOffset(R.styleable.BubbleLayout_lookPosition, 0);
        mLookWidth = a.getDimensionPixelOffset(R.styleable.BubbleLayout_lookWidth, dpToPx(getContext(), 17F));
        mLookLength = a.getDimensionPixelOffset(R.styleable.BubbleLayout_lookLength, dpToPx(getContext(), 17F));
        mShadowRadius = a.getDimensionPixelOffset(R.styleable.BubbleLayout_shadowRadius, dpToPx(getContext(), 3.3F));
        mShadowX = a.getDimensionPixelOffset(R.styleable.BubbleLayout_shadowX,dpToPx(getContext(), 1F));
        mShadowY = a.getDimensionPixelOffset(R.styleable.BubbleLayout_shadowY, dpToPx(getContext(), 1F));
        mBubbleRadius = a.getDimensionPixelOffset(R.styleable.BubbleLayout_bubbleRadius, dpToPx(getContext(), 7F));
        mBubblePadding = a.getDimensionPixelOffset(R.styleable.BubbleLayout_bubblePadding, dpToPx(getContext(), 8));
        mShadowColor = a.getColor(R.styleable.BubbleLayout_shadowColor, Color.GRAY);
        mBubbleColor = a.getColor(R.styleable.BubbleLayout_bubbleColor, Color.WHITE);
        a.recycle();
    }


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

    @Override
    public void invalidate()
    {
        initData();
        super.invalidate();
    }

    @Override
    public void postInvalidate()
    {
        initData();
        super.postInvalidate();
    }

    /**
     * 初始化数据
     */
    private void initData()
    {
        mPaint.setPathEffect(new CornerPathEffect(mBubbleRadius));
        mPaint.setShadowLayer(mShadowRadius, mShadowX, mShadowY, mShadowColor);

        mLeft = mBubblePadding + (mLook == Look.LEFT ? mLookLength : 0);
        mTop = mBubblePadding + (mLook == Look.TOP ? mLookLength : 0);
        mRight = mWidth - mBubblePadding - (mLook == Look.RIGHT ? mLookLength : 0);
        mBottom = mHeight - mBubblePadding - (mLook == Look.BOTTOM ? mLookLength : 0);
        mPaint.setColor(mBubbleColor);

        mPath.reset();

        int topOffset = (topOffset = mLookPosition) + mLookLength > mBottom ? mBottom - mLookWidth : topOffset;
        topOffset = topOffset > mBubblePadding ? topOffset : mBubblePadding;
        int leftOffset = (leftOffset = mLookPosition) + mLookLength > mRight ? mRight - mLookWidth : leftOffset;
        leftOffset = leftOffset > mBubblePadding ? leftOffset : mBubblePadding;

        switch (mLook)
        {
            case LEFT:
                mPath.moveTo(mLeft, topOffset);
                mPath.rLineTo(-mLookLength, mLookWidth / 2);
                mPath.rLineTo(mLookLength, mLookWidth / 2);
                mPath.lineTo(mLeft, mBottom);
                mPath.lineTo(mRight, mBottom);
                mPath.lineTo(mRight, mTop);
                mPath.lineTo(mLeft, mTop);
                break;
            case TOP:
                mPath.moveTo(leftOffset, mTop);
                mPath.rLineTo(mLookWidth / 2, -mLookLength);
                mPath.rLineTo(mLookWidth / 2, mLookLength);
                mPath.lineTo(mRight, mTop);
                mPath.lineTo(mRight, mBottom);
                mPath.lineTo(mLeft, mBottom);
                mPath.lineTo(mLeft, mTop);
                break;
            case RIGHT:
                mPath.moveTo(mRight, topOffset);
                mPath.rLineTo(mLookLength, mLookWidth / 2);
                mPath.rLineTo(-mLookLength, mLookWidth / 2);
                mPath.lineTo(mRight, mBottom);
                mPath.lineTo(mLeft, mBottom);
                mPath.lineTo(mLeft, mTop);
                mPath.lineTo(mRight, mTop);
                break;
            case BOTTOM:
                mPath.moveTo(leftOffset, mBottom);
                mPath.rLineTo(mLookWidth / 2, mLookLength);
                mPath.rLineTo(mLookWidth / 2, -mLookLength);
                mPath.lineTo(mRight, mBottom);
                mPath.lineTo(mRight, mTop);
                mPath.lineTo(mLeft, mTop);
                mPath.lineTo(mLeft, mBottom);
                break;
        }

        mPath.close();
    }

    @Override
    protected void onDraw(Canvas canvas)
    {
        super.onDraw(canvas);
        canvas.drawPath(mPath, mPaint);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        if (event.getAction() == MotionEvent.ACTION_DOWN)
        {
            RectF r = new RectF();
            mPath.computeBounds(r, true);
            mRegion.setPath(mPath, new Region((int) r.left, (int) r.top, (int) r.right, (int) r.bottom));
            if (!mRegion.contains((int) event.getX(), (int) event.getY()) && mListener != null)
            {
                mListener.edge();
            }
        }
        return super.onTouchEvent(event);
    }

    public Paint getPaint()
    {
        return mPaint;
    }

    public Path getPath()
    {
        return mPath;
    }

    public Look getLook()
    {
        return mLook;
    }

    public int getLookPosition()
    {
        return mLookPosition;
    }

    public int getLookWidth()
    {
        return mLookWidth;
    }

    public int getLookLength()
    {
        return mLookLength;
    }

    public int getShadowColor()
    {
        return mShadowColor;
    }

    public int getShadowRadius()
    {
        return mShadowRadius;
    }

    public int getShadowX()
    {
        return mShadowX;
    }

    public int getShadowY()
    {
        return mShadowY;
    }

    public int getBubbleRadius()
    {
        return mBubbleRadius;
    }

    public int getBubbleColor()
    {
        return mBubbleColor;
    }

    public void setBubbleColor(int mBubbleColor)
    {
        this.mBubbleColor = mBubbleColor;
    }

    public void setLook(Look mLook)
    {
        this.mLook = mLook;
        initPadding();
    }

    public void setLookPosition(int mLookPosition)
    {
        this.mLookPosition = mLookPosition;
    }

    public void setLookWidth(int mLookWidth)
    {
        this.mLookWidth = mLookWidth;
    }

    public void setLookLength(int mLookLength)
    {
        this.mLookLength = mLookLength;
        initPadding();
    }

    public void setShadowColor(int mShadowColor)
    {
        this.mShadowColor = mShadowColor;
    }

    public void setShadowRadius(int mShadowRadius)
    {
        this.mShadowRadius = mShadowRadius;
    }

    public void setShadowX(int mShadowX)
    {
        this.mShadowX = mShadowX;
    }

    public void setShadowY(int mShadowY)
    {
        this.mShadowY = mShadowY;
    }

    public void setBubbleRadius(int mBubbleRadius)
    {
        this.mBubbleRadius = mBubbleRadius;
    }


    public Parcelable onSaveInstanceState()
    {
        Bundle bundle = new Bundle();
        bundle.putParcelable("instanceState", super.onSaveInstanceState());
        bundle.putInt("mLookPosition", this.mLookPosition);
        bundle.putInt("mLookWidth", this.mLookWidth);
        bundle.putInt("mLookLength", this.mLookLength);
        bundle.putInt("mShadowColor", this.mShadowColor);
        bundle.putInt("mShadowRadius", this.mShadowRadius);
        bundle.putInt("mShadowX", this.mShadowX);
        bundle.putInt("mShadowY", this.mShadowY);
        bundle.putInt("mBubbleRadius", this.mBubbleRadius);
        bundle.putInt("mWidth", this.mWidth);
        bundle.putInt("mHeight", this.mHeight);
        bundle.putInt("mLeft", this.mLeft);
        bundle.putInt("mTop", this.mTop);
        bundle.putInt("mRight", this.mRight);
        bundle.putInt("mBottom", this.mBottom);
        return bundle;
    }

    //    private int mWidth, mHeight;
//    private int mLeft, mTop, mRight, mBottom;
    public void onRestoreInstanceState(Parcelable state)
    {
        if (state instanceof Bundle)
        {
            Bundle bundle = (Bundle) state;
            this.mLookPosition = bundle.getInt("mLookPosition");
            this.mLookWidth = bundle.getInt("mLookWidth");
            this.mLookLength = bundle.getInt("mLookLength");
            this.mShadowColor = bundle.getInt("mShadowColor");
            this.mShadowRadius = bundle.getInt("mShadowRadius");
            this.mShadowX = bundle.getInt("mShadowX");
            this.mShadowY = bundle.getInt("mShadowY");
            this.mBubbleRadius = bundle.getInt("mBubbleRadius");
            this.mWidth = bundle.getInt("mWidth");
            this.mHeight = bundle.getInt("mHeight");
            this.mLeft = bundle.getInt("mLeft");
            this.mTop = bundle.getInt("mTop");
            this.mRight = bundle.getInt("mRight");
            this.mBottom = bundle.getInt("mBottom");
            super.onRestoreInstanceState(bundle.getParcelable("instanceState"));
            return;
        }
        super.onRestoreInstanceState(state);
    }

    public void setOnClickEdgeListener(OnClickEdgeListener l)
    {
        this.mListener = l;
    }

    /**
     * 触摸到气泡的边缘
     */
    public interface OnClickEdgeListener
    {
        void edge();
    }

    public static int dpToPx(Context context, float dipValue)
    {
        float scale = context.getApplicationContext().getResources().getDisplayMetrics().density;
        return (int) (dipValue * scale + 0.5f);
    }
}

attr

<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
    <declare-styleable name="BubbleLayout" tools:ignore="MissingDefaultResource">
        <attr name="lookAt">
            <enum name="left"   value="1"/>
            <enum name="top"    value="2"/>
            <enum name="right"  value="3"/>
            <enum name="bottom" value="4"/>
        </attr>

        <attr name="lookPosition" format="dimension"/>
        <attr name="lookWidth"    format="dimension"/>
        <attr name="lookLength"   format="dimension"/>
        <attr name="bubbleColor"  format="color"/>
        <attr name="bubbleRadius" format="dimension"/>
        <attr name="bubblePadding" format="dimension"/>
        <attr name="shadowRadius" format="dimension"/>
        <attr name="shadowX"      format="dimension"/>
        <attr name="shadowY"      format="dimension"/>
        <attr name="shadowColor"  format="color"/>
    </declare-styleable>
</resources>

  <!--lookAt箭头指向-->
    <!--lookLength 箭头的长度-->
    <!--lookPosition  相对于上面左边 的 边距-->
    <!--lookWidth 箭头宽度-->
    <!--bubbleColor 气泡的颜色-->
    <!--bubbleRadius 气泡的圆角-->
    <!--bubblePadding 气泡边缘到BubbleLayout边缘的距离-->
    <!--shadowX阴影在x轴方向的偏移-->
    <!--app:shadowRadius="0dp" 无阴影-->
    <!--app:shadowColor 阴影颜色-->
    <com.as.happybubble.BubbleLayout
        android:id="@+id/bubbleLayout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:lookAt="left"
        app:lookLength="10dp"
        app:lookPosition="16dp"
        app:lookWidth="20dp"
        app:bubbleColor="#f0ff"
        app:bubbleRadius="20dp"
        app:bubblePadding="10dp"
        app:shadowX="0dp"
        app:shadowY="0dp"
        app:shadowColor="@color/colorPrimary"
        >
        <LinearLayout
            android:orientation="vertical"
            android:layout_width="200dp"
            android:layout_height="wrap_content">
            <TextView
                android:text="测试"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <TextView
                android:text="测试"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <TextView
                android:text="测试"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
            <TextView
                android:text="测试"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content" />
        </LinearLayout>



    </com.as.happybubble.BubbleLayout>

 

转载自原文链接, 如需删除请联系管理员。

原文链接:Android 自定义View聊天 气泡,转载请注明来源!

0