Подтвердить что ты не робот

Как перемещать текст по изображению

Не могли бы вы рассказать мне, есть ли способ компоновки текста? вокруг изображения? Вот так:

------  text text text
|    |  text text text
-----   text text text
text text text text
text text text text

Я получил ответ от разработчика Android по этому вопросу. Но я не уверен, что он подразумевает, выполнив мою собственную версию TextView? Спасибо за любые советы.

В понедельник, 8 февраля 2010 года в 23:05, Ромен Гай написал:

Привет,

Это невозможно, используя только поставляемые виджеты и макеты. Вы может написать свою собственную версию TextView для этого, это не должно быть трудно.

4b9b3361

Ответ 1

Теперь это возможно, но только для телефонов с версией выше или равной 2.2 с помощью интерфейса android.text.style.LeadingMarginSpan.LeadingMarginSpan2, который доступен в API 8.

Вот статья, но не на английском, но вы можете скачать исходный код примера непосредственно из здесь.

Если вы хотите, чтобы ваше приложение совместимо со старыми устройствами, вы можете отобразить другой макет без плавающего текста. Вот пример:

Макет (по умолчанию для старых версий будет изменен программно для более новых версий)

<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
    <ImageView 
            android:id="@+id/thumbnail_view"
            android:src="@drawable/icon"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

    <TextView android:id="@+id/message_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/thumbnail_view"
            android:textSize="18sp"
            android:text="@string/text" />
</RelativeLayout>

Вспомогательный класс

class FlowTextHelper {

    private static boolean mNewClassAvailable;

    static {
        if (Integer.parseInt(Build.VERSION.SDK) >= 8) { // Froyo 2.2, API level 8
           mNewClassAvailable = true;
        }
    }

    public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display){
        // There is nothing I can do for older versions, so just return
        if(!mNewClassAvailable) return;

        // Get height and width of the image and height of the text line
        thumbnailView.measure(display.getWidth(), display.getHeight());
        int height = thumbnailView.getMeasuredHeight();
        int width = thumbnailView.getMeasuredWidth();
        float textLineHeight = messageView.getPaint().getTextSize();

        // Set the span according to the number of lines and width of the image
        int lines = (int)FloatMath.ceil(height / textLineHeight);
        //For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
        messageView.setText(ss);

        // Align the text with the image by removing the rule that the text is to the right of the image
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
        int[]rules = params.getRules();
        rules[RelativeLayout.RIGHT_OF] = 0;
    }
}

Класс MyLeadingMarginSpan2 (обновлен для поддержки API 21)

public class MyLeadingMarginSpan2 implements LeadingMarginSpan2 {
    private int margin;
    private int lines;
    private boolean wasDrawCalled = false;
    private int drawLineCount = 0;

    public MyLeadingMarginSpan2(int lines, int margin) {
        this.margin = margin;
        this.lines = lines;
    }

    @Override
    public int getLeadingMargin(boolean first) {
        boolean isFirstMargin = first;
        // a different algorithm for api 21+
        if (Build.VERSION.SDK_INT >= 21) {
            this.drawLineCount = this.wasDrawCalled ? this.drawLineCount + 1 : 0;
            this.wasDrawCalled = false;
            isFirstMargin = this.drawLineCount <= this.lines;
        }

        return isFirstMargin ? this.margin : 0;
    }

    @Override
    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom, CharSequence text, int start, int end, boolean first, Layout layout) {
        this.wasDrawCalled = true;
    }

    @Override
    public int getLeadingMarginLineCount() {
        return this.lines;
    }
}

Пример использования

ImageView thumbnailView = (ImageView) findViewById(R.id.thumbnail_view);
TextView messageView = (TextView) findViewById(R.id.message_view);
String text = getString(R.string.text);

Display display = getWindowManager().getDefaultDisplay();
FlowTextHelper.tryFlowText(text, thumbnailView, messageView, display);

Так выглядит приложение на устройстве Android 2.2: Android 2.2 the text flows around the image

И это для устройства Android 2.1:

Android 2.1 the text is situated near the image

Ответ 2

Здесь улучшается FlowTextHelper (из ответа vorrtex). Я добавил возможность добавить дополнительное дополнение между текстом и изображением и улучшить расчет строки, чтобы также учитывать отступы. Наслаждайтесь!

public class FlowTextHelper {
   private static boolean mNewClassAvailable;

   /* class initialization fails when this throws an exception */
   static {
       try {
           Class.forName("android.text.style.LeadingMarginSpan$LeadingMarginSpan2");
           mNewClassAvailable = true;
       } catch (Exception ex) {
           mNewClassAvailable = false;
       }
   }

   public static void tryFlowText(String text, View thumbnailView, TextView messageView, Display display, int addPadding){
       // There is nothing I can do for older versions, so just return
       if(!mNewClassAvailable) return;



       // Get height and width of the image and height of the text line
        thumbnailView.measure(display.getWidth(), display.getHeight());
        int height = thumbnailView.getMeasuredHeight();
        int width = thumbnailView.getMeasuredWidth() + addPadding;
        messageView.measure(width, height); //to allow getTotalPaddingTop
        int padding = messageView.getTotalPaddingTop();
        float textLineHeight = messageView.getPaint().getTextSize();

        // Set the span according to the number of lines and width of the image
        int lines =  (int)Math.round((height - padding) / textLineHeight);
        SpannableString ss = new SpannableString(text);
        //For an html text you can use this line: SpannableStringBuilder ss = (SpannableStringBuilder)Html.fromHtml(text);
        ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);
        messageView.setText(ss);

        // Align the text with the image by removing the rule that the text is to the right of the image
        RelativeLayout.LayoutParams params = (RelativeLayout.LayoutParams)messageView.getLayoutParams();
        int[]rules = params.getRules();
        rules[RelativeLayout.RIGHT_OF] = 0;
   }


}

Ответ 3

В настоящее время вы можете использовать библиотеку: https://github.com/deano2390/FlowTextView. Как это:

<uk.co.deanwild.flowtextview.FlowTextView
    android:id="@+id/ftv"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_alignParentTop="true"
            android:padding="10dip"
            android:src="@drawable/android"/>
</uk.co.deanwild.flowtextview.FlowTextView>

Ответ 5

Ответы Vorrtex и Ronen работают на меня, за исключением одной детали. После обертывания текста вокруг изображения был странный "отрицательный" край под изображением и на противоположной стороне. Я понял, что при установке диапазона на SpannableString я изменил

ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, ss.length(), 0);

к

ss.setSpan(new MyLeadingMarginSpan2(lines, width), 0, lines, 0);

который остановил интервал после изображения. Может быть, не нужно во всех случаях, но думал, что я поделюсь.

Ответ 6

"Но я не уверен, что он подразумевает, выполнив мою собственную версию TextView?"

Он означает, что вы можете расширить класс android.widget.TextView(или Canvas или какую-либо другую отображаемую поверхность) и реализовать свою собственную переопределяющую версию, которая позволяет встроенным изображениям с текстом, проходящим вокруг них.

Это может быть довольно много работы в зависимости от того, насколько вы это делаете.

Ответ 7

Я могу предложить более удобный конструктор для Класс MyLeadingMarginSpan2

    MyLeadingMarginSpan2(Context cc,int textSize,int height,int width) {                
    int pixelsInLine=(int) (textSize*cc.getResources().getDisplayMetrics().scaledDensity);              
    if (pixelsInLine>0 && height>0) {
        this.lines=height/pixelsInLine;          
    } else  {
        this.lines=0;
    }
    this.margin=width; 
}

Ответ 8

Ответ vorrtex не сработал для меня, но я многое взял из него и придумал собственное решение. Вот:

package ie.moses.keepitlocal.util;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.support.annotation.IntRange;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
import android.view.View;
import android.widget.TextView;

import ie.moses.keepitlocal.util.MeasurementUtils;
import ie.moses.keepitlocal.util.TextUtils;

import static com.google.common.base.Preconditions.checkArgument;

public class WrapViewSpan implements LeadingMarginSpan.LeadingMarginSpan2 {

    private final Context _context;
    private final int _lineCount;
    private int _leadingMargin;
    private int _padding;

    public WrapViewSpan(View wrapeeView, TextView wrappingView) {
        this(wrapeeView, wrappingView, 0);
    }

    /**
     * @param padding Padding in DIP.
     */
    public WrapViewSpan(View wrapeeView, TextView wrappingView, @IntRange(from = 0) int padding) {
        _context = wrapeeView.getContext();
        setPadding(padding);

        int wrapeeHeight = wrapeeView.getHeight();
        float lineHeight = TextUtils.getLineHeight(wrappingView);

        int lineCnt = 0;
        float linesHeight = 0F;
        while ((linesHeight += lineHeight) <= wrapeeHeight) {
            lineCnt++;
        }

        _lineCount = lineCnt;
        _leadingMargin = wrapeeView.getWidth();
    }

    public void setPadding(@IntRange(from = 0) int paddingDp) {
        checkArgument(paddingDp >= 0, "padding cannot be negative");
        _padding = (int) MeasurementUtils.dpiToPixels(_context, paddingDp);
    }

    @Override
    public int getLeadingMarginLineCount() {
        return _lineCount;
    }

    @Override
    public int getLeadingMargin(boolean first) {
        if (first) {
            return _leadingMargin + _padding;
        } else {
            return _padding;
        }
    }

    @Override
    public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline,
                                  int bottom, CharSequence text, int start, int end,
                                  boolean first, Layout layout) {

    }

}

и в моем реальном классе, где используется диапазон:

ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
headerViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
    @Override
    public void onGlobalLayout() {
        String descriptionText = _descriptionView.getText().toString();
        SpannableString spannableDescriptionText = new SpannableString(descriptionText);
        LeadingMarginSpan wrapHeaderSpan = new WrapViewSpan(_headerView, _descriptionView, 12);
        spannableDescriptionText.setSpan(
                wrapHeaderSpan,
                0,
                spannableDescriptionText.length(),
                Spanned.SPAN_EXCLUSIVE_EXCLUSIVE
        );
        _descriptionView.setText(spannableDescriptionText);
        ViewTreeObserver headerViewTreeObserver = _headerView.getViewTreeObserver();
        headerViewTreeObserver.removeOnGlobalLayoutListener(this);
    }
});

Мне нужен глобальный слушатель макета, чтобы получить правильные значения для getWidth() и getHeight().

Вот результат:

enter image description here

Ответ 9

Попробуйте эту простую реализацию, используя kotlin и androidx. во-первых, создайте вспомогательный класс ведущего диапазона:

class LeadingSpan(private val line: Int, private val margin: Int) : LeadingMarginSpan.LeadingMarginSpan2 {

    override fun drawLeadingMargin(canvas: Canvas?, paint: Paint?, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence?, start: Int, end: Int, first: Boolean, layout: Layout?) {}

    override fun getLeadingMargin(first: Boolean): Int =  if (first) margin else 0

    override fun getLeadingMarginLineCount(): Int = line
}

Затем создайте макет, используя RelativeLayout:

<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/about_desc"
        android:text="@string/about_desc"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <androidx.appcompat.widget.AppCompatImageView
        android:id="@+id/logo"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

</RelativeLayout>

и, наконец, настройка в вашей activity или fragment как:

val about = view.findViewById<TextView>(R.id.about_desc)
val logoImage = ContextCompat.getDrawable(view.context, R.mipmap.ic_launcher) as Drawable
@Suppress("DEPRECATION")
view.findViewById<AppCompatImageView>(R.id.logo).setBackgroundDrawable(logoImage)
val spannableString = SpannableString(about.text)
spannableString.setSpan(Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10), 0, spannableString.length, 0)
about.text = spannableString

измените число 5 в Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10) соответствии с вашей высотой рисования.