以编程方式绘制气泡

Dou*_*P90 8 android android-canvas

我希望在我的应用程序中有一个具有百分比值的气泡,我不能使用9个补丁,因为我希望它可以自定义并且它的背景颜色应该是可变的.看起来应该是这样的 在此输入图像描述

我该怎么做?这个泡泡会在其内部膨胀视图,如此百分比或一些较大的布局.也取决于布局(手机或平板电脑),它可能有一侧比另一侧大(箭头不在中心)所以这是我更喜欢以编程方式进行的另一个原因

Sou*_*ion 15

创建一个自定义Drawable并将其用于放置文本或其他视图的任何容器的背景.
您需要修改背景的填充以考虑气泡的指针.
下面的代码允许您将指针的对齐方式设置为LEFT,CENTER或RIGHT.
这只是一个给你一个想法的基本版本.您可以轻松地为气泡颜色添加设置器,或者为'mPaint'添加描边属性以获得额外的灵活性.

public class BubbleDrawable extends Drawable {

    // Public Class Constants
    ////////////////////////////////////////////////////////////

    public static final int LEFT = 0;
    public static final int CENTER = 1;
    public static final int RIGHT = 2;

    // Private Instance Variables
    ////////////////////////////////////////////////////////////

    private Paint mPaint;
    private int mColor;

    private RectF mBoxRect;
    private int mBoxWidth;
    private int mBoxHeight;
    private float mCornerRad;
    private Rect mBoxPadding = new Rect();

    private Path mPointer;
    private int mPointerWidth;
    private int mPointerHeight;
    private int mPointerAlignment;

    // Constructors
    ////////////////////////////////////////////////////////////

    public BubbleDrawable(int pointerAlignment) {
        setPointerAlignment(pointerAlignment);
        initBubble();
    }

    // Setters
    ////////////////////////////////////////////////////////////

    public void setPadding(int left, int top, int right, int bottom) {
        mBoxPadding.left = left;
        mBoxPadding.top = top;
        mBoxPadding.right = right;
        mBoxPadding.bottom = bottom;
    }

    public void setCornerRadius(float cornerRad) {
        mCornerRad = cornerRad;
    }

    public void setPointerAlignment(int pointerAlignment) {
        if (pointerAlignment < 0 || pointerAlignment > 3) {
            Log.e("BubbleDrawable", "Invalid pointerAlignment argument");
        } else {
            mPointerAlignment = pointerAlignment;
        }
    }

    public void setPointerWidth(int pointerWidth) {
        mPointerWidth = pointerWidth;
    }

    public void setPointerHeight(int pointerHeight) {
        mPointerHeight = pointerHeight;
    }

    // Private Methods
    ////////////////////////////////////////////////////////////

    private void initBubble() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mColor = Color.RED;
        mPaint.setColor(mColor);
        mCornerRad = 0;
        setPointerWidth(40);
        setPointerHeight(40);
    }

    private void updatePointerPath() {
        mPointer = new Path();
        mPointer.setFillType(Path.FillType.EVEN_ODD);

        // Set the starting point
        mPointer.moveTo(pointerHorizontalStart(), mBoxHeight);

        // Define the lines
        mPointer.rLineTo(mPointerWidth, 0);
        mPointer.rLineTo(-(mPointerWidth / 2), mPointerHeight);
        mPointer.rLineTo(-(mPointerWidth / 2), -mPointerHeight);
        mPointer.close();
    }

    private float pointerHorizontalStart() {
        float x = 0;
        switch (mPointerAlignment) {
        case LEFT:
            x = mCornerRad;
            break;
        case CENTER:
            x = (mBoxWidth / 2) - (mPointerWidth / 2);
            break;
        case RIGHT:
            x = mBoxWidth - mCornerRad - mPointerWidth;
        }
        return x;
    }

    // Superclass Override Methods
    ////////////////////////////////////////////////////////////

    @Override
    public void draw(Canvas canvas) {
        mBoxRect = new RectF(0.0f, 0.0f, mBoxWidth, mBoxHeight);
        canvas.drawRoundRect(mBoxRect, mCornerRad, mCornerRad, mPaint);
        updatePointerPath();
        canvas.drawPath(mPointer, mPaint);
    }

    @Override
    public int getOpacity() {
        return 255;
    }

    @Override
    public void setAlpha(int alpha) {
        // TODO Auto-generated method stub

    }

    @Override
    public void setColorFilter(ColorFilter cf) {
        // TODO Auto-generated method stub

    }

    @Override
    public boolean getPadding(Rect padding) {
        padding.set(mBoxPadding);

        // Adjust the padding to include the height of the pointer
        padding.bottom += mPointerHeight;
        return true;
    }

    @Override
    protected void onBoundsChange(Rect bounds) {
        mBoxWidth = bounds.width();
        mBoxHeight = getBounds().height() - mPointerHeight;
        super.onBoundsChange(bounds);
    }
}
Run Code Online (Sandbox Code Playgroud)

用法
下面的示例显示了如何使用BubbleDrawable.

MainActivity.java

public class MainActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        LinearLayout linearLayout = (LinearLayout)findViewById(R.id.myLayout);
        BubbleDrawable myBubble = new BubbleDrawable(BubbleDrawable.CENTER);
        myBubble.setCornerRadius(20);
        myBubble.setPointerAlignment(BubbleDrawable.RIGHT);
        myBubble.setPadding(25, 25, 25, 25);
        linearLayout.setBackgroundDrawable(myBubble);
    }
}
Run Code Online (Sandbox Code Playgroud)

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    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=".MainActivity" >

    <LinearLayout
        android:id="@+id/myLayout"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Some Text"
            android:textAppearance="?android:attr/textAppearanceLarge" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="Some Other Text"
            android:textAppearance="?android:attr/textAppearanceLarge" />
        </LinearLayout>

</RelativeLayout>
Run Code Online (Sandbox Code Playgroud)


Sou*_*ion 13

显然,在您的应用程序中使用您不理解的代码永远不是一个好主意,所以我不会只为您编写java代码中的一堆方程式.但是,如果您遵循并理解下面的数学,那么在代码中使用所描述的方程并绘制圆弧将是一个相对简单的事情.


要在指针上实现圆形尖端,您需要修改updatePointerPath().
目前它只使用rLineTo()绘制三条线形成一个三角形.
在android Path类中有另一种方法,arcTo()它采用以下形式:

    arcTo(RectF oval, float startAngle, float sweepAngle)
Run Code Online (Sandbox Code Playgroud)

您可以使用此方法在指针的尖端绘制弧,但是您需要先完成一些事情.

您已经可以计算指针三角形三个角的坐标.这就是updatePointerPath()已经存在的.现在看一下下图.要使用arcTo(),您需要计算以下内容:

  1. T的坐标,它是弧开始的位置.
  2. 边界RectF的左上角和右下角的坐标
  3. 你的起始角度(Α)
  4. 你的扫掠角(2*披)

图2

起始角度 Α 可以很容易地找到基本触发,如下图所示.

图1

注意:最好是坚持使用Radians而不是Degrees来处理所有角度,因为这是android'Math'类中所有trig函数所需要的.
考虑到这一点:

  • 有2个皮 圆圈中的弧度
  • 三角形中的三个角加起来 皮 弧度
  • 一个直角是 皮/ 2弧度

因此,在由点C,TP形成的三角形中添加三个角度,您将获得:

Α + 披 + 皮/ 2 = 皮

因此

披 = 皮/ 2 - Α

所以我们现在计算了 Α披.

接下来,d是点P和边界框底部之间的距离.
您可以通过计算从C点到P点的距离,然后减去半径r来得到它.
现在:

罪(Α)= r /(从CP的距离)

因此:

CP = r/sin的距离(Α)

因此,假设距离d是从点C到点P的距离减去半径r,我们得到:

d =(r/sin(Α)) - r

这为您提供了计算边界RectF左上角和右下角坐标所需的所有信息.

现在剩下的就是计算出点T的坐标.

首先计算从PT的距离.
鉴于:

棕褐色(Α)= r /(从PT的距离)

我们得到:

PT = r/tan的距离(Α)

最后,在图表中再加一点......

图3

我们可以看到:

罪(Α)=(从PA的距离)/(从PT的距离)

所以:

PA的距离=(从PT的距离)*sin(Α)

同理:

COS(Α)=(从TA的距离)/(从PT的距离)

所以:

TA的距离=(从PT的距离)*cos(Α)

有了这些信息,您现在可以计算点T的坐标!

如果你理解了这一切,那么这里的编码很容易.如果你不确定什么,那就问问吧.


以下介绍了更新后的updatePointerPath()外观.

private void updatePointerPath() {
    float xDistance;
    float yDistance;
    mPointer = new Path();
    mPointer.setFillType(Path.FillType.EVEN_ODD);

    // Set the starting point (top left corner of the pointer)
    mPointer.moveTo(pointerHorizontalStart(), mBoxHeight);

    // Define the lines

    // First draw a line to the top right corner
    xDistance= mPointerWidth;
    yDistance= 0;
    mPointer.rLineTo(xDistance, yDistance);

    // Next draw a line down to point T
    xDistance= (mPointerWidth / 2) - distancePtoA;
    yDistance= mPointerHeight - distanceTtoA;
    mPointer.rLineTo(-xDistance, yDistance); //Note: Negative sign because we are moving back to the left

    // Next draw the arc
    // Note: The RectF used in arcTo() is defined in absolute screen coordinates
    float boundingBoxLeft = pointerHorizontalStart() + (mPointerWidth / 2) - (2 * radius);
    float boundingBoxTop = mBoxHeight - distanceD - (2 * radius);
    float boundingBoxRight = boundingBoxLeft + (2 * radius);
    float boundingBoxBottom = boundingBoxTop + (2 * radius);

    RectF boundingBox = new RectF(boundingBoxLeft, boundingBoxTop, boundingBoxRight, boundingBoxBottom);

    // Note: While the methods in the android Math class like sin() and asin() all use Radians,
    // for some reason it was decided that arcTo() in the Path class would use Degrees,
    // so we have to convert the angles
    float startAngleInDegrees = angleAlpha * (180 / Math.PI);
    float sweepAngleInDegrees = 2 * anglePhi * (180 / Math.PI);

    mPointer.arcTo(boundingBox, startAngleInDegrees, sweepAngleInDegrees);

    // Finally draw the line from the end of the arc back up to the top left corner
    // Note: The distances are the same as from the top right corner to point T,
    // just the direction is different.
    mPointer.rLineTo(-xDistance, -yDistance); // Note: Negative in front of yDistance because we are moving up

    // Close off the path  
    mPointer.close();
}
Run Code Online (Sandbox Code Playgroud)