社区内容经常会有插入链接的需要,这时就产生了对链接的UI和点击交互的需求,我们在微博中也经常会在列表页面和详情页面看到。
Android仿微博正文链接交互
笑慢
2021年11月28日21:09
· 阅读1325
关注社区内容经常会有插入链接的需要,这时就产生了对链接的UI和点击交互的需求,我们在微博中也经常会在列表页面和详情页面看到。
下边我们就此功能分析一下具体实现。
一、链接的匹配和显示交互
首先我们先分析一下链接的组成部分,可以肯定的是需要一个显示的标题,我们可能会对这个标题在UI表现上做些处理(常见的是一个链接的标志和设置不同的颜色)来提示和吸引用户的注意,另外还需要点击时跳转的链接,这条链接可以是内部也可以是外部(这就属于业务的需求)。
关于链接的匹配方式可能会有不同的方案,我们这里选择了使用a标签的匹配方式,也就是接口会把链接的数据以a标签的形式给我们,客户端来进行匹配数据,下边我们简单的举一个例子,接口返回数据如下:
"你说的我是链接11111我也是链接好开心啊,哈哈哈哈"
复制代码
接下来我们对数据的处理:
/**
*匹配a标签直接插入链接
*/
suspendfuncomputeLenFilterLink(text:String,mContext:Context):SpannableStringBuilder=
withContext(Dispatchers.Default){
varstrings=SpannableStringBuilder(text)
valpattern="(.*?)"
valp=Pattern.compile(pattern)
valmatcher=p.matcher(strings)
while(matcher.find()){
valstr=matcher.group()
vallinkTitle=matcher.group(1)?:""
//a标签链接正则匹配
valpatternUrlString=
"\s*(?i)href\s*=\s*("([^"]*")|'[^']*'|([^'">\s]+))"
valpatternUrl=Pattern.compile(
patternUrlString,
Pattern.CASE_INSENSITIVE
)
//链接url
valmatcherUrL=patternUrl.matcher(strings)
varlinkUrl=""
while(matcherUrL.find()){
linkUrl=matcherUrL.group()
linkUrl=linkUrl.replace("href\s*=\s*(['|"]*)".toRegex(),"")
linkUrl=linkUrl.replace("['|"]".toRegex(),"")
linkUrl=linkUrl.trim{it<=''}
break
}
//设置颜色
valsb=SpannableString("#$linkTitle")
sb.setSpan(
ForegroundColorSpan(
ContextCompat.getColor(mContext,R.color.link_color)
),0,
sb.length,
Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
sb.setSpan(
object:MyLinkClickSpan(mContext){
overridefunonSpanClick(widget:View?){
//链接跳转
Toast.makeText(mContext,"点击链=$linkUrl",Toast.LENGTH_SHORT).show()
}
},
0,sb.length,Spanned.SPAN_INCLUSIVE_INCLUSIVE
)
valstart=strings.indexOf(str)
strings.delete(start,start+str.length)
//插入链接
strings.insert(start,sb)
}
return@withContextstrings
}
复制代码
这里我们是用正则匹配的方式取出链接的标题和跳转链接,然后设置链接的标志和颜色替换原来的a标签插入正文数据中。
接下来就是设置显示的数据:
valstring="你说的我是链接11111我也是链接好开心啊,哈哈哈哈"
lifecycleScope.launch{
valcontentString=LinkCheckHelper.computeLenFilterLink(string,this@MainActivity)
tvLink.text=contentString
}
复制代码
二、链接的点击交互
参考微博的交互效果我们会发现当我们触摸链接内容时其背景会由透明变为链接文字的颜色,手指抬起时又置回透明。
细心的你肯定发现我们在刚才链接数据的插入时设置了MyLinkClickSpan做了对链接点击的处理,那么背景颜色的改变就要在ClickSpan中的
updateDrawState(ds:TextPaint)方法中进行处理,代码如下:
classMyLinkClickSpan(privatevalcontext:Context):
ClickableSpan(),IPressedSpan{
privatevarisPressed=false
abstractfunonSpanClick(widget:View?)
overridefunonClick(widget:View){
if(ViewCompat.isAttachedToWindow(widget)){
onSpanClick(widget)
}
}
overridefunsetPressed(pressed:Boolean){
isPressed=pressed
}
overridefunupdateDrawState(ds:TextPaint){
if(isPressed){
ds.bgColor=ContextCompat.getColor(context,R.color.link_bg_color)
}else{
ds.bgColor=ContextCompat.getColor(context,android.R.color.transparent)
}
ds.isUnderlineText=false
}
}
复制代码
我们发现背景颜色的变化是需要对手指按下和抬起分别进行处理,所以这时不可避免的我们就要对触摸事件进行处理:
classLinkTextView(context:Context,attrs:AttributeSet?):
AppCompatTextView(context,attrs){
privatevarmPressedSpan:IPressedSpan?=null
init{
isFocusable=false
isLongClickable=false
//有链接点击需求不设置则点击无效
movementMethod=MyLinkMovementMethod.instance
highlightColor=Color.TRANSPARENT
}
overridefunonTouchEvent(event:MotionEvent):Boolean{
valtext=text
valspannable=Spannable.Factory.getInstance().newSpannable(text)
if(event.action==MotionEvent.ACTION_DOWN){
//按下时记下clickSpan
mPressedSpan=getPressedSpan(this,spannable,event)
}
returnif(mPressedSpan!=null){
//如果有clickSpan就走MyLinkMovementMethod的onTouchEvent
MyLinkMovementMethod.instance.onTouchEvent(this,getText()asSpannable,event)
}else{
super.onTouchEvent(event)
}
}
privatefungetPressedSpan(
textView:TextView,spannable:Spannable,
event:MotionEvent
):IPressedSpan?{
varmTouchSpan:IPressedSpan?=null
varx=event.x.toInt()
vary=event.y.toInt()
x-=textView.totalPaddingLeft
x+=textView.scrollX
y-=textView.totalPaddingTop
y+=textView.scrollY
vallayout=textView.layout
valline=layout.getLineForVertical(y)
try{
varoff=layout.getOffsetForHorizontal(line,x.toFloat())
if(xlayout.getLineRight(line)){
//实际上没点到任何内容
off=-1
}
vallinkSpans=
spannable.getSpans(off,off,IPressedSpan::class.java)
if(!linkSpans.isNullOrEmpty()){
mTouchSpan=linkSpans[0]
}
returnmTouchSpan
}catch(e:IndexOutOfBoundsException){
Log.d(this.toString(),"getPressedSpan",e)
}
returnnull
}
}
复制代码
classMyLinkMovementMethod:LinkMovementMethod(){
overridefunonTouchEvent(widget:TextView,buffer:Spannable,event:MotionEvent):Boolean{
return(sHelper.onTouchEvent(widget,buffer,event))
}
companionobject{
valinstance:MyLinkMovementMethod
get(){
if(sInstance==null){
sInstance=MyLinkMovementMethod()
}
returnsInstanceasMyLinkMovementMethod
}
privatevarsInstance:MyLinkMovementMethod?=null
privatevalsHelper=SpanClickHelper()
}
}
复制代码
classSpanClickHelper{
privatevarmPressedSpan:IPressedSpan?=null
funonTouchEvent(
textView:TextView,
spannable:Spannable,
event:MotionEvent
):Boolean{
returnwhen(event.action){
MotionEvent.ACTION_DOWN->{
mPressedSpan=getPressedSpan(textView,spannable,event)
if(mPressedSpan!=null){
//手指按下设置按下为true,修改对应的链接文字背景颜色
mPressedSpan!!.setPressed(true)
//设置选中区域
Selection.setSelection(
spannable,spannable.getSpanStart(mPressedSpan),
spannable.getSpanEnd(mPressedSpan)
)
}
mPressedSpan!=null
}
MotionEvent.ACTION_MOVE->{
valtouchedSpan=getPressedSpan(textView,spannable,event)
if(mPressedSpan!=null&&touchedSpan!=mPressedSpan){
//手指移动时设置按下为false,对应的链接文字背景颜色置回透明
mPressedSpan!!.setPressed(false)
mPressedSpan=null
//移除选中区域
Selection.removeSelection(spannable)
}
mPressedSpan!=null
}
MotionEvent.ACTION_UP->{
vartouchSpanHint=false
if(mPressedSpan!=null){
touchSpanHint=true
//手指抬起时设置按下为false,对应的链接文字背景颜色置回透明
mPressedSpan!!.setPressed(false)
//传递点击事件回调
mPressedSpan!!.onClick(textView)
}
mPressedSpan=null
Selection.removeSelection(spannable)
touchSpanHint
}
else->{
if(mPressedSpan!=null){
//其它收拾都设置按下为false,对应的链接文字背景颜色置回透明
mPressedSpan!!.setPressed(false)
}
//移除选中区域
Selection.removeSelection(spannable)
false
}
}
}
/**
*判断手指是否点击在链接上
*/
privatefungetPressedSpan(
textView:TextView,
spannable:Spannable,
event:MotionEvent
):IPressedSpan?{
varx=event.x.toInt()
vary=event.y.toInt()
x-=textView.totalPaddingLeft
y-=textView.totalPaddingTop
x+=textView.scrollX
y+=textView.scrollY
vallayout=textView.layout
valline=layout.getLineForVertical(y)
try{
varoff=layout.getOffsetForHorizontal(line,x.toFloat())
if(xlayout.getLineRight(line)){
//实际上没点到任何内容
off=-1
}
vallink=spannable.getSpans(
off,off,
IPressedSpan::class.java
)
vartouchedSpan:IPressedSpan?=null
if(link.isNotEmpty()){
touchedSpan=link[0]
}
returntouchedSpan
}catch(e:IndexOutOfBoundsException){
Log.d(this.toString(),"getPressedSpan",e)
}
returnnull
}
}
复制代码
代码比较简单,就是对点击区域判断是否为链接,然后根据手势的操作分别设置给ClickSpan是否按下,来改变链接的背景颜色。
另外需要注意一点的是必须要调用下边的代码:
movementMethod=MyLinkMovementMethod.instance
复制代码
此方法设置链接的点击。
另外,不同的机型上系统会有一个链接的高亮颜色,我们需要调用
highlightColor=Color.TRANSPARENT
复制代码
来取消掉。
这样,链接的显示和点击交互就完成了。
具体效果:
点击获取源码分类:Android标签:Android交互设计
安装掘金浏览器插件
多内容聚合浏览、多引擎快捷搜索、多工具便捷提效、多模式随心畅享,你想要的,这里都有!前往安装
关注
私信
获得点赞
149
文章被阅读
26,186下载稀土掘金APP一个帮助开发者成长的社区目录收藏成功!
已添加到「」,
点击更改微信微信扫码分享新浪微博QQ
沉浸阅读