- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我想在android中将文本换成曲线外的贝塞尔曲线形状。
我的尝试:
Path path = new Path();
path.addCircle(x, y, radius, Path.Direction.CW);
myCanvas.drawTextOnPath(myText, path, offset, 0, myPaint);
我想要达到的目标:
但是这段代码在曲线上绘制文本..我不想在曲线上写文本..我想根据曲线换行并写在下一行。
要清楚了解请引用baconforme.com ..我想在不使用 webbrowser 的情况下在 android 中创建类似 jquery 的行为。
看到了这个链接On Android how do I wrapping text inside in a bezier path
问题:
最佳答案
我已经实现了一个基本 View ,它可以执行您正在尝试执行的操作。这里的想法是根据您请求的路径创建位图。路径外的每个像素都有一个 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;
// invalidate the path map.
mPathMap = null;
}
// This method converts the path into a bitmap which will be used to determine if a point is within the path
private void generatePathMap()
{
if (mPath != null)
{
// the path map bitmap can have poor quality, we're only checking for color or no color in each pixel.
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);
// draw the path.
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);
// make sure we have enough data to begin drawing text.
if (mPath == null || mText == null || getMeasuredWidth() == 0 || getMeasuredHeight() == 0)
return;
// if the path map hasn't been generated, generate it now.
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;
// maybe find a better way to limit this loop?
while (true)
{
// we don't have a rectangle. Get the next 1 in the list.
if (rect == null)
{
// no more rectangles to draw text on. Finish.
if (rectIndex >= writableRects.size())
return;
rect = new Rect(writableRects.get(rectIndex));
rectIndex++;
}
// we don't have text to print. Get the next word in the list.
if (textFragment == null)
{
// no more text to draw. Finish.
if (fragmentIndex >= textFragments.size())
return;
textFragment = textFragments.get(fragmentIndex);
fragmentIndex++;
}
// find how much width this text wants.
textWidth = mPaint.measureText(textFragment);
// if the rectangle doesn't have enough width, set it to null, indicating its "used up" and we need to next rect. Don't continue drawing text, find a new rect first.
if (textWidth > rect.width())
{
rect = null;
continue;
}
// draw the text.
canvas.drawText(textFragment, rect.left, rect.centerY(), mPaint);
// the word has been drawn. Set it null indicating a new 1 is needed in the next iteration.
textFragment = null;
// remove the used width from the rect and continue.
rect.left += textWidth;
// In word mode, account for the space that was removed.
if (mWrapMode == WrapMode.Words)
{
rect.left += mPaint.measureText(" ");
}
}
}
// get each String fragment as a list. For letters mode, each element will be a letter or char. For words mode, each element will be a word.
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;
// place y in the center of the text, jump in fontsize steps.
for (int y = (int)(mFontSize / 2); y < getMeasuredHeight(); y += mFontSize)
{
// place x at 0, jump with 5 px steps. This can be adjusted for better accuracy / performance.
for (int x = 0; x < getMeasuredWidth(); x += 5)
{
// Havent found a point within the polygon yet, but now I have!
if (!isInPolygon && mPathMap.getPixel(x, y) != 0)
{
isInPolygon = true;
rect = new Rect(x, y - (int)(mFontSize / 2), x, y + (int)(mFontSize / 2));
}
// We found where the polygon started in this row, and now we found where it ends.
else if (isInPolygon && mPathMap.getPixel(x, y ) == 0)
{
isInPolygon = false;
rect.right = x;
result.add(rect);
}
}
// If the edge is in the ploygon, limit the rect to the right side of the view.
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();
// sample of adding a path with a bezier curve element
path.moveTo(0, 0);
path.lineTo(500, 0);
path.cubicTo(700, 300, 400, 600, 800, 1000);
path.lineTo(0, 1000);
path.lineTo(0, 0);
// only needed when you don't close the path.
path.close();
mPolygonWrapView.setPath(path);
mPolygonWrapView.setFontSize(30);
mPolygonWrapView.setBackgroundColor(0xFFFFFFFF);
mPolygonWrapView.invalidate();
}
}
我在这里对其进行了测试,它似乎运行良好,至少足以让您入门。
您可能想要添加一些改进,例如确保整个行的高度在多边形内,而不仅仅是行的 centerY。
此外,您可能希望支持路径列表,而不仅仅是 1 个路径 - 这样您就可以控制文本在路径段之间的分布方式,而不是像我在这里所做的那样进行 x/y 框填充。
您可能还想通过调整分配给空格的像素数量来改进算法,以使所有文本行正确地固定在行尾。例如,如果一行没有足够的空间来容纳一个额外的单词,但没有该单词,该行明显在多边形结束之前结束,您可以增加每个单词之间的间距宽度以使该行的最后一个单词恰好在多边形边缘的位置结束。实现这一点需要更改我的算法以在绘制行之前对其进行预处理,但应该不会太难。
如果您还有其他问题,请告诉我。
编辑:我已经编辑了使用示例,以展示如何使用贝塞尔曲线实现此路径。 Here是有关如何创建带有路径的贝塞尔曲线以获取更多详细信息的引用。
关于android - 在android中将文本包裹在贝塞尔曲线之外,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29669960/
这个问题在这里已经有了答案: How to initialize var? (11 个答案) 关闭 8 年前。 我想给一个变量赋初值 null,并在下一个 if-else block 中赋值,但是编
我正在使用 TypeScript 3.8 编写 JS 和 TS 混合的代码。我写了以下行: export * as Easing from './easing'; 应该是 fair game在 Typ
我需要将 R 代码中的“/”更改为“\”。我有这样的事情: tmp <- paste(getwd(),"tmp.xls",sep="/") 所以我的 tmp是 c:/Study/tmp.xls 我希望
我有个问题。例如我有这个: id truth count 1 1 1 2 1 2 3 0 0 4 1 1 5 1 2 6 1
我正在尝试使用“IN”和“=”来查找一些 bean。我目前正在使用此代码: $ids = array(1,2,3,4); $user = 1; $things = R::find( 'thing'
是否可以在 Xcode 中部署到其他人的手机上?我没有 iPhone,但我想测试我在 friend 手机上制作的应用程序。在我支付 99 美元之前,我想确保这不会造成麻烦。 谢谢。 最佳答案 不会有任
我试图得到一个非常大的数字(超过 unsigned long long int )。所以我把它作为一个字符串,然后一个数字一个数字地转换成整数并使用它。 #include #include int
我在 Rust 中有 C 语言库的绑定(bind),但它们并不完整。 在 C 代码中,我定义了一个简化的宏,如下所示: #define MY_MACROS1(PTR) (((my_struct1
我正在努力解决这个问题。 http://jsfiddle.net/yhcqfy44/ 动画应该自动相对于 滚动到顶部每次出现滚动条时的高度。 我已经写了这个,但没有运气: var hheight =
我正在处理一个将数字作为字符串返回的 JSON API。例如 "12" ,但是,该字段值也可以是非数字的,例如:"-" . 我已将 JSON 数据解析为映射,我想将此字段提取为 elixir 中的整数
我正在尝试编写一个类,将.wav文件转换为.aiff文件作为项目的一部分。 我遇到了几个库Alvas.Audio(http://alvas.net/alvas.audio,overview.aspx)
我想在 Lucene 中将像“New York”这样的“复合词”索引为单个术语,而不是像“new”、“york”那样。这样,如果有人搜索“new place”,则包含“new york”的文档将不会匹
我希望这个解释能让我更好地了解使用宏的优点。 最佳答案 在函数中,所有参数在调用之前都会被评估。 这意味着 or 作为函数不能是惰性的,而宏可以将 or 重写为 if 语句,该语句仅在以下情况下计算分
我有一些看起来像这样的 XML foo ]]> (注意 > 登录 "> foo")和 XSLT 样式表 当我运行xsltproc stylesheet.xs
当我尝试将 Any 转换为 List 时,如下面的示例所示,我得到“Unchecked cast: Any!”到列表'警告。有没有解决此类问题的方法? val x: List = objectOfTy
我正在使用 Python 开发一个简单的爬虫。目的是创建一个 sitemap.xml。(你可以在这里找到真正的 alpha 版本:http://code.google.com/p/sitemappy/
我想知道在 VBScript 中是否可以在多行中中断 If 语句。喜欢: If (UCase(Trim(objSheet.Cells(i, a).Value)) = "YES") Or _ (UCas
for (String item : someList) { System.out.println(item); } 使用“do while”是否等效? 谢谢。 最佳答案 如果列表为空,f
这个问题已经有答案了: 已关闭10 年前。 Possible Duplicate: Split string with delimiters in C 在 C 中将“,”分隔的列表拆分为数组的最佳方法
我有一个如下所示的字符数组: [0, 10, 20, 30, 670] 如何将此字符串转换为整数数组? 这是我的数组 int i=0; size_t dim = 1; char* array = (c
我是一名优秀的程序员,十分优秀!