我已经实现了一个基本视图,可以执行您要做的操作。这里的想法是根据您请求的路径创建位图。路径外的每个像素都将具有0值,路径内的每个像素都将具有其他值。
通过这样做,您可以知道某一点是否在多边形内部。现在,我们需要确定文本的绘制位置。
我将通过遍历生成的位图来生成矩形列表。每个矩形将定义在多边形内部开始和结束的行。
对于每个矩形,我开始填充文本,直到该矩形无法容纳更多文本,在这种情况下,我移动到下一个矩形。一旦没有更多的矩形,或者我用完了文本,我就停止绘制。
在此实现中,我添加了一些自定义选项,例如字体大小、文本颜色和换行模式。
这就是代码:
PolygonWrapView.java
public class PolygonWrapView extends View
{
public enum WrapMode
{
Letters,
Words
}
private Path mPath;
private String mText;
private float mFontSize;
private int mTextColor;
private Paint mPaint;
private Bitmap mPathMap;
private WrapMode mWrapMode = WrapMode.Words;
public PolygonWrapView(Context context)
{
super(context);
init();
}
public PolygonWrapView(Context context, AttributeSet attrs)
{
super(context, attrs);
init();
}
public PolygonWrapView(Context context, AttributeSet attrs, int defStyleAttr)
{
super(context, attrs, defStyleAttr);
init();
}
private void init()
{
mPaint = new Paint();
mFontSize = 20;
mTextColor = 0xFF000000;
}
public void setPath(Path path)
{
mPath = path;
mPathMap = null;
}
private void generatePathMap()
{
if (mPath != null)
{
mPathMap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), Bitmap.Config.ARGB_4444);
Canvas canvas = new Canvas(mPathMap);
Paint pathPaint = new Paint();
pathPaint.setStyle(Paint.Style.FILL);
pathPaint.setColor(0xFFFFFFFF);
canvas.drawPath(mPath, pathPaint);
}
}
public void setText(String text)
{
mText = text;
}
public void setFontSize(float fontSize)
{
mFontSize = fontSize;
}
public void setTextColor(int textColor)
{
mTextColor = textColor;
}
public void setWrapMode(WrapMode wrapMode)
{
mWrapMode = wrapMode;
}
@Override
protected void onDraw(Canvas canvas)
{
super.onDraw(canvas);
if (mPath == null || mText == null || getMeasuredWidth() == 0 || getMeasuredHeight() == 0)
return;
if (mPathMap == null)
generatePathMap();
final List<Rect> writableRects = getTextRects();
final List<String> textFragments = getTextFragments();
mPaint.setColor(mTextColor);
mPaint.setTextSize(mFontSize);
int rectIndex = 0;
int fragmentIndex = 0;
Rect rect = null;
String textFragment = null;
float textWidth;
while (true)
{
if (rect == null)
{
if (rectIndex >= writableRects.size())
return;
rect = new Rect(writableRects.get(rectIndex));
rectIndex++;
}
if (textFragment == null)
{
if (fragmentIndex >= textFragments.size())
return;
textFragment = textFragments.get(fragmentIndex);
fragmentIndex++;
}
textWidth = mPaint.measureText(textFragment);
if (textWidth > rect.width())
{
rect = null;
continue;
}
canvas.drawText(textFragment, rect.left, rect.centerY(), mPaint);
textFragment = null;
rect.left += textWidth;
if (mWrapMode == WrapMode.Words)
{
rect.left += mPaint.measureText(" ");
}
}
}
private List<String> getTextFragments()
{
List<String> result = new ArrayList<String>();
if (mWrapMode == WrapMode.Letters)
{
for (int i = 0; i < mText.length(); i++)
{
result.add("" + mText.charAt(i));
}
}
else if (mWrapMode == WrapMode.Words)
{
String[] words = mText.split("\\s+");
for (String word : words)
result.add(word);
}
return result;
}
private List<Rect> getTextRects()
{
final List<Rect> result = new ArrayList<Rect>();
boolean isInPolygon = false;
Rect rect = null;
for (int y = (int)(mFontSize / 2); y < getMeasuredHeight(); y += mFontSize)
{
for (int x = 0; x < getMeasuredWidth(); x += 5)
{
if (!isInPolygon && mPathMap.getPixel(x, y) != 0)
{
isInPolygon = true;
rect = new Rect(x, y - (int)(mFontSize / 2), x, y + (int)(mFontSize / 2));
}
else if (isInPolygon && mPathMap.getPixel(x, y ) == 0)
{
isInPolygon = false;
rect.right = x;
result.add(rect);
}
}
if (isInPolygon)
{
rect.right = getMeasuredWidth();
result.add(rect);
}
}
return result;
}
}
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">
<com.gil.polygonwrap.PolygonWrapView
android:id="@+id/polygonWrap"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</RelativeLayout>
MainActivity.java:(使用示例)
public class MainActivity extends ActionBarActivity
{
private PolygonWrapView mPolygonWrapView;
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mPolygonWrapView = (PolygonWrapView)findViewById(R.id.polygonWrap);
final String text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.";
mPolygonWrapView.setText(text);
Path path = new Path();
path.moveTo(0, 0);
path.lineTo(500, 0);
path.cubicTo(700, 300, 400, 600, 800, 1000);
path.lineTo(0, 1000);
path.lineTo(0, 0);
path.close();
mPolygonWrapView.setPath(path);
mPolygonWrapView.setFontSize(30);
mPolygonWrapView.setBackgroundColor(0xFFFFFFFF);
mPolygonWrapView.invalidate();
}
}
我在这里进行了测试,似乎可以很好地工作,至少足以让您入门。
您可能想添加一些改进,例如确保整个行的高度都在多边形内,而不仅仅是行的中心Y坐标。
此外,您可能希望支持路径列表,而不仅仅是一个路径 —— 这样您就可以控制文本在路径段之间的分布方式,而不像我在这里所做的那样进行一个x/y盒子填充。
您还可以改进算法,通过调整为空格分配的像素数量,使所有文本行正确地夹紧到行尾。例如,如果一行没有足够的空间来容纳另一个单词,但是没有那个单词,该行会明显在多边形的末端之前结束,您可以增加每个单词之间的间距宽度,使该行的最后一个单词恰好在多边形边缘结束。实现这个需要改变我的算法,在绘制之前预处理行,但不应该太难。
如果您有任何进一步的问题,请告诉我。
编辑:我已编辑使用示例,以显示如何使用bezier曲线实现此路径。这里提供了有关如何使用路径创建bezier曲线的参考资料,以获取更多详细信息。