【安卓UI】TextView图文混排

一、为TextView设置链接的三种方式

1、为TextView设置带有<a>标签的的文本

设置带<a>标签的方式有两种:

  • 通过Html.formHtml()方法设置,示例代码如下:
<TextView android:id="@+id/textview"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"/>
var html="<a href='http://www.baidu.com'>百度一下</a>"
var cs:CharSequence = Html.fromHtml(html)
textView.text = cs
  • 通过引用strings.xml定义的带有<a>标签的文本,示例代码如下:
<resources>
    <string name="link_text"><a href="http://www.baidu.com">百度一下</a></string>
</resources>
<TextView android:id="@+id/textview"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:text="@string/link_text"/>

2、为TextView设置android:autoLink属性

android:autoLink属性有几个值:
- none:不匹配任何连接
- web:匹配网址
- email:匹配邮箱
- phone:匹配手机号
- map:匹配映射
- all:匹配网址、邮箱、手机号和映射

所有匹配上的网址、邮箱、手机号和映射都会产生链接。

示例代码如下:

<TextView android:id="@+id/textview"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:autoLink="all"/>
var link=StringBuilder()
link.append("百度URL:http://www.baidu.com\n")
    .append("邮箱:[email protected]\n")
    .append("手机:18802703417")
textView.text = link.toString()

3、通过SpannableString为TextView部分文字设置链接

示例代码如下:

var textView = findViewById(R.id.textview4)
var text = "显示Activity1"

//拆分字符串添加点击
var span=SpannableString(text)
span4.setSpan(object:ClickableSpan(){
     override fun onClick(widget: View?) {
         var intent = Intent(this@TextViewActivity,OneActivity::class.java)
         startActivity(intent)
     }},0,text.length,Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
textView.text = span

此例实现了点击文字跳转到别的Activity界面。

SpannableString类丰富文本的表现形式方面很强大,具体介绍可以看参考资料。

注意: 以上三种方式,只是给文本添加上了链接效果,点击链接并没有什么效果。必须调用textview.movementMethod = LinkMovementMethod.getInstance()激活链接。


二、为TextView实现图文混排的两种方式

1、Html实现图文混排

Spanned fromHtml(String source, int flags)
Spanned fromHtml (String source, int flags, Html.ImageGetter imageGetter, Html.TagHandler tagHandler)

扫描到source中每个标签都会调用Html.ImageGetter对象的’getDrawable(String source)’方法,并且把中src的值作为入参传递给这个方法。如果有无法解析的标签,就会调用Html.TagHandler对象的方法进行通知处理。

  • source:可能包含html标签的字符串;
  • imageGetter:Html.ImageGetter接口对象,需要自己实现其内部方法’public Drawable getDrawable(String source)’,形参source接收的是标签src属性的值;
  • tagHandler:如果有不能解析的标签,会调用这个对象的方法进行通知处理。

关于Html.ImageGetter对象的不完整实现示例代码如下:

         object:Html.ImageGetter{
            override fun getDrawable(source: String?): Drawable {
                //第一步:获取图片的drawable对象
                var drawable: Drawable = 
                //第二步:设置图片边界
                drawable.setBounds(0,0,drawable.intrinsicWidth,drawable.intrinsicHeight)
                return drawable
            }
         }

经过观察发现,fromHtml方法的关键是如何通过属性src的值获取指定图片的Drawable对象。
由于图片可能存储在项目drawable文件夹,也可能存储在安卓存储器上,也可能存储在服务器上,下面分三种情况来探讨如何获取对象的Drawable对象。

(1)、存储在项目drawable文件夹中

通常会设置src值为图片名称或R.drawable.图片名称(注意这里的图片名称不带扩展名)
如果设置的是图片名称,我们需要使用反射机制获取到R.drawable.图片名称的值。
然后调用Resources类对象的public Drawable getDrawable(@DrawableRes int id, @Nullable Theme theme)方法即可获取到这张图片的Drawable对象,其中R.drawable.图片名称的值要作为入参传递给方法形参id。
kotlin示例代码实现如下:

var drawable = resources.getDrawable([R.drawable.图片名称]的值,null)

注意:如何通过图片名称获取R.drawable.图片名称的值?

首先需要清楚:每当在drawable文件夹添加一张图片,系统都会在R类的内部类drawable中添加一个图片名称的属性,同时为这个属性生成一个值。也就是说这个属性值就指代这张图片资源,我们可以通过调用’resources.getDrawable([R.drawable.图片名称]的值)’获取这张图片的Drawable对象。
向drawable文件夹添加一张图片image1.jpg,R类大致情况如下:

    public final class R {
        public static final class drawable {  
           public static final int image1=0x7f060057;
        }
     }

使用反射机制通过image1名称获取到R.drawable.image1的值,kotlin实现代码如下:

fun getResourceId(name:String):Int{
          var field = R.drawable::class.java.getField(name)
          return field.get(null).toString().toInt()
      }

(2)、存储在安卓存储器上

图片存储在安卓存储器,通常属性src值为具体的图片路径,可以通过调用Drawable类的public static Drawable createFromPath(String pathName)方法获取图片资源。

(3)、存储在服务器上

图片存储在服务器,通常属性src值为一个服务器图片的网址。

首先需要异步方式从服务器下载这个图片,也就是从服务器获取图片流,从服务器获取图片的细节具体可看《http请求》,简化步骤如下:

var connection = URL("").openConnection() as HttpURLConnection
connection.connect()
inputStream = connection.inputStream

然后如果直接这样更新ui:imageView.setImageDrawable(Drawable.createFromStream(inputStream),image1),你会发现ui渲染不能展示完整的图片,甚至是一片黑。
具体内部实现不清楚,但是经过调试发现,’inputStream = connection.inputStream’并不能从服务器一次性获取全部的图片流,甚至完全获取不到图片流,这才导致更新
部分ui或者全黑,可能的原因是系统有什么的默认缓存的限制。知道这个,解决方案就有了,方案一:把接收的图片流输出为手机存储器的一张图片;方案二:把全部图片流转存到一片缓存空间。
有了完整的图片或者完整的图片流缓存,那么就容易获得这个图片的Drawable对象。

两种方案代码实现如下(kotlin):

import android.graphics.drawable.Drawable
import android.support.v7.app.AppCompatActivity
import android.os.Bundle
import android.os.Handler
import android.os.Message
import android.widget.ImageView
import com.mapc.demo.R
import java.io.*
import java.net.HttpURLConnection
import java.net.URL

class HttpActivity : AppCompatActivity() {

    private lateinit var ivURLImage:ImageView
    private var image1 = "1.jpg"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_http)
        this.title = ""

        ivURLImage = findViewById(R.id.iv_url_image)

        var inputStream: InputStream?=null
        var byteArray:ByteArray?=null

        var handler = object : Handler() {

            override fun handleMessage(msg: Message?) {
                super.handleMessage(msg)

                //获取网络图片Drawable对象的两种方式
                //方案1:图片方式渲染
                //var drawable = Drawable.createFromPath(File(Environment.getExternalStorageDirectory(),image1).path)
                //方案2:流方式渲染
                var drawable = Drawable.createFromStream(ByteArrayInputStream(byteArray),image1)

                ivURLImage.setImageDrawable(drawable)
            }
        }

        Thread(Runnable {
            var connection = URL("http://192.168.1.6:8080/android/api/image/"+image1).openConnection() as HttpURLConnection
            connection.requestMethod = "GET"
            connection.connectTimeout = 3000
            connection.doInput = true
            connection.doOutput = true
            connection.setRequestProperty("accept", "*/*")
            connection.setRequestProperty("Content-Type", "*/*")
            connection.connect()

            if (connection.responseCode == 200) {
                inputStream = connection.inputStream

                // 方式2:转存全部流,然后从流渲染ui
                var byteout=ByteArrayOutputStream()
                var byte = ByteArray(200)
                var len = inputStream?.read(byte)
                while (len !=null && len != -1) {
                    byteout.write(byte, 0, len)
                    len = inputStream?.read(byte)
                }
                byteArray=byteout.toByteArray()
                connection.disconnect()

                //方式1:流输出为图片,然后从图片渲染ui。
                //var file = File(Environment.getExternalStorageDirectory(),image1)
                //var out = FileOutputStream(file)
                //val buffer = ByteArray(1024)
                //var len: Int? = inputStream?.read(buffer)
                //while (len!=null && len != -1) {
                //    out.write(buffer, 0, len)
                //    len = inputStream?.read(buffer)
                //}
                //out.flush()
                //out.close()
                //connection.disconnect()

                handler.sendEmptyMessage(1)
            } else {}
        }).start()
    }
}

注意,以上是ImgeView控件显示网络图片的demo,可以在此用以说明本文本节问题。

(3)、SpannableString实现图文混排

示例代码如下:

var spannableString = SpannableString("在文本中添加表情(表情)")
var drawable = resources.getDrawable(R.mipmap.image1)
drawable.setBounds(0, 0, 42, 42);
var imageSpan = ImageSpan(drawable);
spannableString.setSpan(imageSpan, 6, 8, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
textView.setText(spannableString);

SpannableString很强大,是通过拆字实现部分文本的效果。


三、跑马灯效果设置

示例代码如下:

<TextView android:id="@+id/textview6"
          android:layout_width="match_parent"
          android:layout_height="wrap_content"
          android:singleLine="true"
          android:ellipsize="marquee"
          android:marqueeRepeatLimit="marquee_forever"
          android:focusable="true"
          android:focusableInTouchMode="true"/>

必须设置的几个属性如下:

  • android:singleLine,必须设置为单行。

  • android:ellipsize可选值:

start:省略号显示在开头,比如:…789
end:省略号显示在结尾,比如:123…
middle:省略号显示在中间,比如:12…89
marquee:以横向滚动方式显示,必须获得当前焦点
none:不做任何处理,会截断显示的文字

  • android:marqueeRepeatLimit

android:ellipsize设置为marquee时生效,指示滚动重复特点,滚动的必要条件之一

  • android:focusable,是否获得焦点,滚动的必要条件之一

android:focusableInTouchMode,抚摸模式下获得焦点

[完整demo]


参考资料:

1、老罗安卓开发-TextView控件

2、Html

3、Html类ImageGetter接口

4、使用Html.fromHtml()怎么加载Html中的图片

5、为TextView添加链接-setMovementMethod

6、TextView中的图文共存问题

7、Kotlin-35.反射(Reflection)

8、安卓开发中SpannableString之富文本显示效果

9、我赌5毛你没见过这样的SpannableString

10、R类源码

11、android.graphics.drawable.Drawable类源码

12、SpannableString

猜你喜欢

转载自blog.csdn.net/u012995888/article/details/80271867