2015-04-16 24 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