Android 如何布局文本以在图像周围流动

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2248759/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-20 05:09:16  来源:igfitidea点击:

How to layout text to flow around an image

androidandroid-layout

提问by silverburgh

Can you please tell me if there is a way to layout text around an image? Like this:

你能告诉我是否有办法在图像周围布置文本吗?像这样:

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

I have gotten a response from an android developer about this question. But I am not sure what he means by doing my own version of TextView? Thank for any tips.

我收到了一位 android 开发人员关于这个问题的回复。但我不确定他做我自己的 TextView 版本是什么意思?感谢您提供任何提示。

On Mon, Feb 8, 2010 at 11:05 PM, Romain Guy wrote:

2010 年 2 月 8 日星期一晚上 11:05,Romain Guy 写道:

Hi,

This is not possible using only the supplied widgets and layouts. You could write your own version of TextView to do this, it shouldn't be hard.

你好,

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

回答by vortexwolf

Now it is possible, but only for phones with version higher or equal 2.2 by using the android.text.style.LeadingMarginSpan.LeadingMarginSpan2interface which is available in API 8.

现在是可能的,但仅适用于版本高于或等于 2.2 的手机,使用android.text.style.LeadingMarginSpan.LeadingMarginSpan2API 8 中提供的接口。

Here is the article, not in English though, but you can download the source code of the example directly from here.

这是文章,虽然不是英文,但您可以直接从这里下载示例的源代码。

If you want to make your application compatible with older devices, you can display a different layout without a floating text. Here is an example:

如果您想让您的应用程序与旧设备兼容,您可以在没有浮动文本的情况下显示不同的布局。下面是一个例子:

Layout (default for older versions, will be changed programmatically for newer versions)

布局(旧版本的默认设置,新版本将通过编程方式更改)

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

The helper class

帮手类

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

The MyLeadingMarginSpan2 class (updated to support API 21)

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

Example of the usage

用法示例

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

This is how the application looks on the Android 2.2 device: Android 2.2 the text flows around the image

这是应用程序在 Android 2.2 设备上的外观: Android 2.2 文字围绕图像流动

And this is for the Android 2.1 device:

这是针对 Android 2.1 设备的:

Android 2.1 the text is situated near the image

Android 2.1 文本位于图像附近

回答by Nikita Shaposhnik

Nowadays you can use library: https://github.com/deano2390/FlowTextView. Like this:

现在你可以使用库: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>

回答by Ronen Yacobi

Here's an improvement for the FlowTextHelper (from vorrtex's reply). I added the possibility to add extra padding between the text and the image and improved the line calculation to also take padding into account. Enjoy!

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


}

回答by Victor

This question seems to same as my question How to fill the empty spaces with content below the Image in android

这个问题似乎与我的问题如何用android中图像下方的内容填充空白空间相同

I found the solution using flowtext library please find the first answer it might help you so far

我找到了使用流文本库的解决方案,请找到到目前为止可能对您有帮助的第一个答案

回答by johosher

Vorrtex and Ronen's answers are working for me except for one detail - After wrapping text around the image there was a weird "negative" margin below the image and on the opposite side. I figured out that when setting the span on the SpannableString I changed

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

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

to

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

which stopped the span after the image. Might not be necessary in all cases but thought I would share.

在图像之后停止了跨度。可能并非在所有情况下都需要,但我想我会分享。

回答by Peter vdL

"But I am not sure what he means by doing my own version of TextView?"

“但我不确定他做我自己的 TextView 版本是什么意思?”

He means that you can extend the class android.widget.TextView (or Canvas or some other renderable surface) and implement your own overriding version that allows embedded images with text flowing around them.

他的意思是你可以扩展类 android.widget.TextView(或 Canvas 或其他一些可渲染的表面)并实现你自己的覆盖版本,允许嵌入的图像和文本在它们周围流动。

This could be quite a lot of work depending on how general you make it.

这可能需要大量的工作,具体取决于您制作它的通用程度。

回答by Moses

vorrtex's answer didn't work for me but I took a lot from it and came up with my own solution. Here it is:

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

    }

}

and in my actual class where the span is used:

在我使用跨度的实际课程中:

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

I needed the global layout listener in order to get the right values for getWidth()and getHeight().

我所需要的全球布局监听器,以获得正确的值getWidth()getHeight()

Here is the result:

结果如下:

enter image description here

在此处输入图片说明

回答by Evgeny Karavashkin

I can offer more comfortable constructor for The MyLeadingMarginSpan2 class

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

回答by Gilbert Arafat

try this simple implementation using kotlin and androidx. first, create leading span helper class:

使用 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
}

Then create a layout using RelativeLayout:

然后使用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>

and finally setting up in your activityor fragmentlike:

最后在你的activityfragment喜欢的地方设置:

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

change number 5 in the Helpers.LeadingSpan(5, logoImage.intrinsicWidth + 10)according to your drawable height.

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