如何安排文字在图像周围流动


74

您能告诉我是否可以在图像周围布置文本吗?像这样:

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

我已经收到一个Android开发人员对此问题的回应。但是我不确定他做我自己的TextView版本意味着什么?感谢您的任何提示。

在2010年2月8日(星期一)晚上11:05,罗曼·盖伊(Romain Guy)写道:

你好

仅使用提供的窗口小部件和布局是不可能的。您可以编写自己的TextView版本来执行此操作,这并不难。


silverburgh:您是否找到了可以分享的解决方案?
2010年


在网上如此简单。我现在暂时跳过此功能。
danny117

您可以使用ImageSpan。看看这个链接
2016年

Answers:


65

现在有可能,但仅适用于版本高于或等于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文字围绕图像流动

这是针对Android 2.1设备的:

Android 2.1文字位于图片附近


2
可以使用一个简单的条件代替Class.forName技巧:if(Build.VERSION.SDK_INT <Build.VERSION_CODES.FROYO){...
WojciechGórski

我也在使用this.But,但是当Html.fromHtml(html content)不支持带有Html标签的数据时,请帮助我,我需要显示带有上述wrapText适配器的列表
Harsha 2014年

@Harsha该方法Html.fromHtml不适用于任何html,它仅支持带有少量标签的简单html。
vortexwolf 2014年

伟大的工作,我几乎浪费了我的一天。。。
Palak Darji'3

1
但是,这也为图像之后的行添加了一个右边界(因此文本永远不会一直贯穿整个边界)。任何想法如何解决此错误?
派克

8

如今,您可以使用库: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>

6

这是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;
   }


}

嗨,罗嫩。我在理解“文字环绕图像”问题的整个想法时遇到了很大的麻烦。您能否告诉我在哪里可以获得有关如何编写此类代码的信息?我想学习如何自己编写代码,而不仅仅是复制代码。
拉莫纳

嗨@Ramona也许看一下这个库:github.com/deano2390/FlowTextView
Ronen Yacobi

您好,非常感谢您提供信息。您知道如何通过放置在屏幕右侧的图像来实现此目的吗?
拉蒙娜


3

Vorrtex和Ronen的答案对我有用,除了一个细节-将文字环绕在图片周围之后,图片下方和另一侧出现了怪异的“负”边距。我发现当在SpannableString上设置跨度时,我改变了

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

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

在图像之后停止了跨度。不一定在所有情况下都是必要的,但我想我会分享。


1

“但是我不确定他使用我自己的TextView版本意味着什么吗?”

他的意思是,您可以扩展android.widget.TextView类(或Canvas或其他可渲染表面),并实现自己的替代版本,该版本允许嵌入的图像周围带有文本。

这取决于您的一般性,可能需要做很多工作。


0

我可以为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; 
}

你好,叶夫根尼,如何设置text flow around image放在屏幕右侧的图像?回答高度赞赏。
拉莫纳

0

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

结果如下:

在此处输入图片说明


-1

尝试使用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

Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10)根据您的可绘制高度更改数字5 。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.