2015-04-16 23 views
16

我想在android中的曲线外部将贝塞尔曲线的形状换成文本。在android中将贝塞尔曲线外部的文本换行

我已经试过

Path path = new Path(); 
path.addCircle(x, y, radius, Path.Direction.CW); 
myCanvas.drawTextOnPath(myText, path, offset, 0, myPaint); 

我所试图实现

但这个代码利用了curve..I文字不想写曲线文本。 。我想根据曲线包装文字并将其写在下一行。

为了清楚地理解它,请参阅baconforme.com ..我想在android中创建这个jquery类似的行为,而不使用webbrowser。

,看到这个链接On Android how do I wrapping text inside in a bezier path

问题

  1. 是否有可能实现这一目标?
  2. 如果是,请指导我。

回答

9

我已经实现了一个基本视图,它执行你正在尝试做的事情。这里的想法是从你请求的路径中创建一个位图。路径外的每个像素将具有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(); 
    } 
} 

我在这里测试过它,它似乎工作得很好,至少足以让你开始。

您可能想要添加一些改进,例如确保整行的高度位于多边形内,而不仅仅是行的中心Y.

此外,您可能希望支持路径列表,而不仅仅是一个路径 - 这样您就可以控制文本在路径段之间如何分布,而不是像我在这里做的那样执行x/y框填充。

您可能还想改进算法,通过调整分配给空间的像素量,使所有文本行正确地夹到行的末尾。例如,如果一行没有足够的空间来容纳一个额外的单词,但没有这个单词,那么该行在多边形的末尾明显结束,那么可以增加每个单词之间的空白宽度以使该行的最后一个单词完全结束多边形边缘的位置。实现这一点需要改变我的算法,在绘制之前对行进行预处理,但不应该太困难。

让我知道你是否还有其他问题。

编辑:我编辑了使用示例以显示如何使用贝塞尔曲线实现此路径。 Here是如何创建具有路径的贝塞尔曲线以获取更多细节的参考。

+0

感谢您花时间回答这个问题。我会尽力让您知道 – Shruti

+0

这个答案解决了您的问题吗? –

+0

我仍然不清楚如何添加贝塞尔曲线的路径,请给我建议这个 – Shruti