2015-04-16 18 views
16

Voglio avvolgere il testo sotto forma di curva di Bezier fuori dalla curva in Android.Avvolge il testo all'esterno della curva di Bezier in Android

Quello che ho cercato:

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

Quello che sto cercando di realizzare:

Ma questo codice disegna testo su curve..I non vogliono scrivere il testo su curva. . Voglio avvolgere il testo in base alla curva e scriverlo sulla riga successiva.

Per comprenderlo chiaramente, fare riferimento a baconforme.com .. Desidero creare questo comportamento simile a jQuery in Android senza utilizzare il browser.

e ho visto questo link On Android how do I wrapping text inside in a bezier path

Domanda:

  1. E 'possibile raggiungere questo obiettivo?
  2. Se sì, quindi per favore guidami.

risposta

9

Ho implementato una vista di base che esegue ciò che si sta tentando di fare. L'idea qui è di creare una bitmap fuori dal percorso che hai richiesto. Ogni pixel al di fuori del percorso avrà un valore 0 e ogni pixel all'interno del percorso avrà un altro valore.

Con questo, è possibile sapere quando un punto si trova all'interno del poligono oppure no. Ora dobbiamo determinare dove disegniamo il testo.

Genererò un elenco di rettangoli attraversando la bitmap generata. Ogni rettangolo definirà le righe che iniziano e terminano all'interno del poligono.

Con ciascun rettangolo, inizio a popolare il testo finché il rettangolo non può più contenere più testo, nel qual caso mi sposto nel rettangolo successivo. Una volta che non ci sono più rettangoli, o non ho più testo, smetto di disegnare.

In questa implementazione, ho aggiunto alcune personalizzazioni come la dimensione del carattere, il colore del testo e la modalità di avvolgimento.

Eccolo:

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: (esempio di utilizzo)

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(); 
    } 
} 

L'ho provato qui e sembra funzionare bene, almeno quanto basta per iniziare.

È possibile aggiungere alcuni miglioramenti, ad esempio assicurandosi che l'intera altezza della riga si trovi all'interno del poligono, non solo il centroY della riga.

Inoltre, è possibile supportare un elenco di percorsi, anziché solo 1 percorso, in questo modo è possibile controllare il modo in cui il testo viene distribuito tra i segmenti del percorso, piuttosto che riempire una casella x/y come sto facendo qui .

Si potrebbe anche voler migliorare l'algoritmo per rendere tutte le stringhe di righe di testo alla fine della riga correttamente regolando la quantità di pixel che si assegnano per gli spazi. Ad esempio, se una riga non ha spazio sufficiente per contenere una parola aggiuntiva, ma senza quella parola la riga termina notevolmente prima della fine del poligono, è possibile aumentare la larghezza dello spazio tra ogni parola per fare l'ultima parola della riga termina esattamente dove si trova il bordo del poligono. L'implementazione di questo richiede la modifica del mio algoritmo per il preprocesso della riga prima del disegno, ma non dovrebbe essere troppo difficile.

Fatemi sapere se avete ulteriori domande.

Modifica: Ho modificato l'esempio di utilizzo per mostrare come implementare questo percorso con una curva di Bézier. Here è un riferimento a come creare una curva di Bezier con un percorso per maggiori dettagli.

+0

grazie per aver dedicato del tempo per rispondere a questo .. ti proverò n ti fa sapere – Shruti

+0

ha fatto questa risposta a risolvere il tuo problema? –

+0

Non sono ancora chiaro su come posso aggiungere una curva di Bezier al percorso, per favore suggeriscimi qualcosa anche su questo – Shruti

Problemi correlati