Android 以编程方式绘制气泡
我想在我的应用程序中有一个带有先兆值的气泡,我不能使用9个补丁,因为我希望它是可定制的,并且它的背景颜色应该是可更改的。 应该是这样的 我怎么做?这个气泡内部会有膨胀的视图,比如这个百分比或一些更大的布局。Android 以编程方式绘制气泡,android,android-canvas,Android,Android Canvas,我想在我的应用程序中有一个带有先兆值的气泡,我不能使用9个补丁,因为我希望它是可定制的,并且它的背景颜色应该是可更改的。 应该是这样的 我怎么做?这个气泡内部会有膨胀的视图,比如这个百分比或一些更大的布局。 另外,根据布局(手机或平板电脑),它的一面可能比另一面大(箭头不在中间),因此这也是我喜欢以编程方式进行操作的另一个原因。创建自定义绘图,并将其用作将文本或其他视图放入的任何容器的背景。 您需要修改背景的填充,以考虑气泡的指针。 下面的代码允许您将指针的对齐方式设置为左对齐、中对齐或右对齐
另外,根据布局(手机或平板电脑),它的一面可能比另一面大(箭头不在中间),因此这也是我喜欢以编程方式进行操作的另一个原因。创建自定义绘图,并将其用作将文本或其他视图放入的任何容器的背景。
您需要修改背景的填充,以考虑气泡的指针。
下面的代码允许您将指针的对齐方式设置为左对齐、中对齐或右对齐。
这只是给你一个想法的基本版本。您可以轻松地为气泡颜色添加设置器,或将笔划属性添加到“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);
}
}
活动\u main.xml
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);
}
}
<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>
显然,在应用程序中添加您不懂的代码从来都不是一个好主意,所以我不会用java代码为您写出一大堆公式。但是,如果您遵循并理解了下面的数学,那么在代码中使用所描述的方程并绘制圆弧将是一件相对简单的事情
要获得指针的圆形提示,您需要修改
updateInterpath()
目前,它只使用rLineTo()绘制三条形成三角形的线。
android
Path
类中还有另一个名为arcTo()
的方法,其形式如下:
arcTo(RectF oval, float startAngle, float sweepAngle)
您可以使用此方法在指针尖端绘制圆弧,但首先需要解决一些问题
您已经可以计算指针三角形三个角的坐标。这是updateInterpath()
已经做过的事情。现在看下图。要使用arcTo()
,您需要计算以下各项:
考虑到这一点:
- 一个圆有两个弧度
- 三角形中的三个角加起来就是弧度
- 直角为1/2弧度
通过计算从点C到点P的距离,然后减去半径r
现在: sin()=r/(从C到p的距离) 因此: 从C到p=r/sin()的距离 假设距离d是从点C到点p的距离减去半径r,我们得到: d=(r/sin())-r 这为您提供了计算边界矩形的左上角和右下角坐标所需的所有信息 现在剩下的就是求出点T的坐标 首先计算出从p到T的距离
鉴于: tan()=r/(从p到T的距离) 我们得到: 从p到T=r/tan()的距离 最后,在图表中再添加一点 我们可以看到: sin()=(从p到A的距离)/(从p到T的距离) 因此: 从p到A=(从p到T的距离)*sin() 同样地: cos()=(从T到A的距离)/(从p到T的距离) 因此: 从T到A=(从p到T的距离)*cos() 使用此信息,您现在可以计算点T的坐标 如果您理解了所有这些,那么从这里开始编码就很容易了。如果你有什么不确定的地方,那就问吧
下面给出了更新后的
updatePointPath()
的外观
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();
}
哇,我对这个答案的完整性感到惊讶,我在你的答案中学到了很多东西,非常感谢:D还有一件事,你能给我一个方法让箭头底部变圆而不是变尖吗?我现在可能自己能弄清楚,但我需要几个小时才能弄清楚如何使它与定位正确工作。是的,在指针顶端放一个弧度是相当直接的。你只需要用一点高中的三角法。这和你原来的问题有点离题,应该在一个单独的问题中提问,但为了把它们放在一起,我将把它放在第二个答案中