【问题标题】:How to layout text to flow around an image如何布局文本以围绕图像流动
【发布时间】:2011-01-15 23:11:19
【问题描述】:

你能告诉我是否有一种布局文本的方法 围绕图像? 像这样:

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

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

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

嗨,

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

【问题讨论】:

标签: android android-layout


【解决方案1】:

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

这里是article,虽然不是英文,但是你可以直接从here下载例子的源代码。

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

布局(旧版本的默认布局,新版本将以编程方式更改)

<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.1 设备的:

【讨论】:

  • 您可以使用一个简单的条件来代替 Class.forName 技巧:if (Build.VERSION.SDK_INT
  • 我也在使用这个。但是当带有 Html 标签的数据不支持 Html.fromHtml(html content) 请帮助我,我需要像上面一样显示带有 wrapText 适配器的列表
  • @Harsha Html.fromHtml 方法不适用于任何html,它只支持带有少量标签的简单html。
  • 伟大的工作,我几乎浪费了我的一天......非常感谢!
  • 这也增加了一个右边距,用于经过图像的线条(因此文本永远不会一直穿过)。知道如何修复这个错误吗?
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    这是 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
    • 您好,非常感谢您的信息。你知道如何通过将图像放在屏幕右侧来实现这一点吗?
    • @Ramona 看起来我也在寻找相同的解决方案。在我们的例子中,我们在屏幕右侧有一个图像。您是否遇到过任何有用的解决方案或线索?
    【解决方案4】:

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

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

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

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

    【讨论】:

      【解决方案5】:

      这个问题好像和我的问题How to fill the empty spaces with content below the Image in android一样

      我使用 flowtext 库找到了解决方案,请找到迄今为止可能对您有所帮助的第一个答案

      【讨论】:

        【解决方案6】:

        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() 的正确值。

        结果如下:

        【讨论】:

          【解决方案7】:

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

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

          这可能需要相当多的工作,具体取决于您的通用程度。

          【讨论】:

            【解决方案8】:

            我可以提供更舒适的构造函数 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; 
            }
            

            【讨论】:

            • 你好Evgeny,如何为屏幕右侧的图像设置text flow around image?非常感谢您的回答。
            【解决方案9】:

            使用 kotlin 和 androidx 尝试这个简单的实现。 首先,创建领先的 span 助手类:

            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>
            

            最后在您的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
            

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

            【讨论】:

              猜你喜欢
              • 2018-01-19
              • 2014-07-07
              • 2013-07-02
              • 2019-06-05
              • 2017-06-10
              • 2015-07-02
              • 1970-01-01
              • 1970-01-01
              • 2011-11-27
              相关资源
              最近更新 更多