Android Span进阶之路——ClickableSpan

Android 点击跳转ClickableSpan使用心得总结 在Android日常开发中,经常遇到需要实现点击某个view或者文字跳转到其他页面或者超链接,通常我们会考虑使用ClickableSpan 具体使用方法见链接https://blog.youkuaiyun.com/yuanpengs/article/details/26342195 例如,需要在"了解更多"添加超链接,这里使用URLSpan 这里注意,URLSpan是继承CkickableSpa... 阅读详情
AI摘要

本文深入探讨 Android 中 ClickableSpan 的使用方法,包括自定义 ClickableSpan 实现点击效果及字体颜色变化,并提供实现代码。

一、前言

    在Android中,可以使用强大的标记(Span)对象来实现富文本展示,相比 HTML 而言更高效实用。关于 Android Span 的入门篇可以阅读 Android中强大的标记对象-Span。本文将对 ClickableSpan (可点击的Span)展开深入的学习。

二、基本使用

    查看Android Doc 文档可以知道,ClickableSpan 是一个抽象类,它有两个子类,分别是 URLSpan 和 TextLinks.TextLinkSpan(从 API Level 28 开始支持),对于这两个类的使用,这里不做详细讲解,我们主要讲解下如何通过继承 ClickableSpan 实现可点击的标记。

2.1 ClicableSpan 源码剖析

    首先,我们先来看看 ClickableSpan 抽象类的源码:

public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance {
    private static int sIdCounter = 0;

    private int mId = sIdCounter++;

    /**
     * Performs the click action associated with this span.
     */
    public abstract void onClick(@NonNull View widget);

    /**
     * Makes the text underlined and in the link color.
     */
    @Override
    public void updateDrawState(@NonNull TextPaint ds) {
        ds.setColor(ds.linkColor);
        ds.setUnderlineText(true);
    }

    /**
     * Get the unique ID for this span.
     *
     * @return The unique ID.
     * @hide
     */
    public int getId() {
        return mId;
    }
}

    从上面的源码来看,ClickableSpan 抽象类非常简单,继承该类需要重写的方法也是比较少,其中抽象方法 onClick() 是必须实现,下面讲解重写方法所能实现的效果:

  • public abstract void onClick(@NonNull View widget):抽象方法,必须实现。用以相应可点击标记被点击时的事件相应处理。
  • public void updateDrawState(@NonNull TextPaint ds):配置绘制参数,可以用来更改绘制样式,比如文字颜色、背景颜色、链接颜色、是否包含下划线等等。如果不重载此方法,将会使用默认的绘制样式。

2.2 自定义 ClickableSpan

    从前面的源码我们了解到 ClickableSpan 的成员方法,实现自己的自定义 ClickableSpan 就非常容易了:

/**
 * 自定义 ClickableSpan
 * @param textColor 可点击标记文字颜色
 * @param clickListener 点击时间监听
 */
class CSClickableSpan (@param:ColorInt private val textColor: Int,
                       private val clickListener: View.OnClickListener?) : ClickableSpan() {
    override fun onClick(widget: View) {
        clickListener?.onClick(widget)
    }

    override fun updateDrawState(ds: TextPaint) {
        super.updateDrawState(ds)

        ds.color = textColor // 字体颜色(前景色)
        ds.bgColor = Color.TRANSPARENT  // 背景颜色
        ds.linkColor = textColor // 链接颜色
        ds.isUnderlineText = false // 是否显示下划线
        // 这里还可以配置其他绘制样式,比如下划线的粗细(如果启用下划线)、字体等等
    }
}

2.3 使用自定义的 ClickableSpan

    接下来就可以在 SpannableString 或者 SpannableStringBuilder 中使用自定义的 CSClickableSpan 类。

val tvNormal = findViewById<TextView>(R.id.tv_normal_clickable_span)
// 必须设置 TextView 的 movementMethod 为 LinkMovementMethod,否则标记无法响应点击事件
tvNormal.movementMethod = LinkMovementMethod.getInstance()
tvNormal.setText(SpannableString("我是普通的ClickableSpan").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvNormal.text, Toast.LENGTH_SHORT).show()
    }), 5, 18, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})

注意事项:在 TextView 中使用 ClickableSpan 时,必须要设置 TextView 对象的 movementMethod 属性为 LinkMovementMethod,否则 ClickableSpan 标记不会响应点击事件。

    运行之后,可以看看效果。

  • 点击前
    自定义ClickableSpan-点击前
  • 点击后
    自定义ClickableS-点击后
        根据上面的例子,我们会发现标记点击后,会有一个背景色,其实这个背景色是 TextView 的高亮颜色,因为 LinkMovementMethod 在标记点击后,会选中标记部分文本。解决这个问题也很简单,只要将 TextView 的 highlightColor 设置为透明即可,如下示例:
val tvNormalNoSelection = findViewById<TextView>(R.id.tv_normal_clickable_span_no_selection)
// 将 TextView 的高两色设置为透明,可去除点击后的选择高亮色
tvNormalNoSelection.highlightColor = Color.TRANSPARENT
// 必须设置 TextView 的 movementMethod 为 LinkMovementMethod,否则标记无法响应点击事件
tvNormalNoSelection.movementMethod = LinkMovementMethod.getInstance()
tvNormalNoSelection.setText(SpannableString("我是普通的ClickableSpan(无选中背景)").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvNormalNoSelection.text, Toast.LENGTH_SHORT).show()
    }), 5, 18, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})

    运行之后看效果,点击标记之后选中高亮色为透明,看起来就是没有高两色的效果,如下图:
ClickableSpan去除点击后的高亮选中颜色

三、高手进阶

3.1 在 ClickableSpan 中实现点击效果

    在前面篇幅中,虽然可以去掉标记选中高亮色,但是这样也并不完美,没有点击效果,用户体验还是有所欠缺。我们首先会想到用TextView 的高亮色,然而高亮色只能设置整型的颜色值,并不能设置ColorList。于是就猜想通过 TextView 的高亮色结合自定义的 CSClickableSpan 实现,笔者刚开始也是从这个角度着手,预想将高亮色设置成按下状态颜色,然后再将高亮色设置为透明色,后来发现这样无法实现,因为 ClickableSpan 这个过程中,会在 onClick() 方法调用之前,前后均会调用 updateDrawState() 更新绘制文本,在如此的调用逻辑下,这种方案是不可行的。既然无法从 TextView 下手,在示例代码中,我们唯一能寄予希望的就是 TextView 的 movementMethod 属性了(也就是 LinkMovementMethod)。

  • LinkMovementMethod类源码剖析
package android.text.method;

import android.annotation.UnsupportedAppUsage;
import android.os.Build;
import android.text.Layout;
import android.text.NoCopySpan;
import android.text.Selection;
import android.text.Spannable;
import android.text.style.ClickableSpan;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.textclassifier.TextLinks.TextLinkSpan;
import android.widget.TextView;

/**
 * A movement method that traverses links in the text buffer and scrolls if necessary.
 * Supports clicking on links with DPad Center or Enter.
 */
public class LinkMovementMethod extends ScrollingMovementMethod {
    private static final int CLICK = 1;
    private static final int UP = 2;
    private static final int DOWN = 3;

    private static final int HIDE_FLOATING_TOOLBAR_DELAY_MS = 200;

    @Override
    public boolean canSelectArbitrarily() {
        return true;
    }

    @Override
    protected boolean handleMovementKey(TextView widget, Spannable buffer, int keyCode,
            int movementMetaState, KeyEvent event) {
        switch (keyCode) {
            case KeyEvent.KEYCODE_DPAD_CENTER:
            case KeyEvent.KEYCODE_ENTER:
                if (KeyEvent.metaStateHasNoModifiers(movementMetaState)) {
                    if (event.getAction() == KeyEvent.ACTION_DOWN &&
                            event.getRepeatCount() == 0 && action(CLICK, widget, buffer)) {
                        return true;
                    }
                }
                break;
        }
        return super.handleMovementKey(widget, buffer, keyCode, movementMetaState, event);
    }

    @Override
    protected boolean up(TextView widget, Spannable buffer) {
        if (action(UP, widget, buffer)) {
            return true;
        }

        return super.up(widget, buffer);
    }

    @Override
    protected boolean down(TextView widget, Spannable buffer) {
        if (action(DOWN, widget, buffer)) {
            return true;
        }

        return super.down(widget, buffer);
    }

    @Override
    protected boolean left(TextView widget, Spannable buffer) {
        if (action(UP, widget, buffer)) {
            return true;
        }

        return super.left(widget, buffer);
    }

    @Override
    protected boolean right(TextView widget, Spannable buffer) {
        if (action(DOWN, widget, buffer)) {
            return true;
        }

        return super.right(widget, buffer);
    }

    private boolean action(int what, TextView widget, Spannable buffer) {
        Layout layout = widget.getLayout();

        int padding = widget.getTotalPaddingTop() +
                      widget.getTotalPaddingBottom();
        int areaTop = widget.getScrollY();
        int areaBot = areaTop + widget.getHeight() - padding;

        int lineTop = layout.getLineForVertical(areaTop);
        int lineBot = layout.getLineForVertical(areaBot);

        int first = layout.getLineStart(lineTop);
        int last = layout.getLineEnd(lineBot);

        ClickableSpan[] candidates = buffer.getSpans(first, last, ClickableSpan.class);

        int a = Selection.getSelectionStart(buffer);
        int b = Selection.getSelectionEnd(buffer);

        int selStart = Math.min(a, b);
        int selEnd = Math.max(a, b);

        if (selStart < 0) {
            if (buffer.getSpanStart(FROM_BELOW) >= 0) {
                selStart = selEnd = buffer.length();
            }
        }

        if (selStart > last)
            selStart = selEnd = Integer.MAX_VALUE;
        if (selEnd < first)
            selStart = selEnd = -1;

        switch (what) {
            case CLICK:
                if (selStart == selEnd) {
                    return false;
                }

                ClickableSpan[] links = buffer.getSpans(selStart, selEnd, ClickableSpan.class);

                if (links.length != 1) {
                    return false;
                }

                ClickableSpan link = links[0];
                if (link instanceof TextLinkSpan) {
                    ((TextLinkSpan) link).onClick(widget, TextLinkSpan.INVOCATION_METHOD_KEYBOARD);
                } else {
                    link.onClick(widget);
                }
                break;

            case UP:
                int bestStart, bestEnd;

                bestStart = -1;
                bestEnd = -1;

                for (int i = 0; i < candidates.length; i++) {
                    int end = buffer.getSpanEnd(candidates[i]);

                    if (end < selEnd || selStart == selEnd) {
                        if (end > bestEnd) {
                            bestStart = buffer.getSpanStart(candidates[i]);
                            bestEnd = end;
                        }
                    }
                }

                if (bestStart >= 0) {
                    Selection.setSelection(buffer, bestEnd, bestStart);
                    return true;
                }

                break;

            case DOWN:
                bestStart = Integer.MAX_VALUE;
                bestEnd = Integer.MAX_VALUE;

                for (int i = 0; i < candidates.length; i++) {
                    int start = buffer.getSpanStart(candidates[i]);

                    if (start > selStart || selStart == selEnd) {
                        if (start < bestStart) {
                            bestStart = start;
                            bestEnd = buffer.getSpanEnd(candidates[i]);
                        }
                    }
                }

                if (bestEnd < Integer.MAX_VALUE) {
                    Selection.setSelection(buffer, bestStart, bestEnd);
                    return true;
                }

                break;
        }

        return false;
    }

    @Override
    public boolean onTouchEvent(TextView widget, Spannable buffer,
                                MotionEvent event) {
        int action = event.getAction();

        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            int x = (int) event.getX();
            int y = (int) event.getY();

            x -= widget.getTotalPaddingLeft();
            y -= widget.getTotalPaddingTop();

            x += widget.getScrollX();
            y += widget.getScrollY();

            Layout layout = widget.getLayout();
            int line = layout.getLineForVertical(y);
            int off = layout.getOffsetForHorizontal(line, x);

            ClickableSpan[] links = buffer.getSpans(off, off, ClickableSpan.class);

            if (links.length != 0) {
                ClickableSpan link = links[0];
                if (action == MotionEvent.ACTION_UP) {
                    if (link instanceof TextLinkSpan) {
                        ((TextLinkSpan) link).onClick(
                                widget, TextLinkSpan.INVOCATION_METHOD_TOUCH);
                    } else {
                        link.onClick(widget);
                    }
                } else if (action == MotionEvent.ACTION_DOWN) {
                    if (widget.getContext().getApplicationInfo().targetSdkVersion
                            >= Build.VERSION_CODES.P) {
                        // Selection change will reposition the toolbar. Hide it for a few ms for a
                        // smoother transition.
                        widget.hideFloatingToolbar(HIDE_FLOATING_TOOLBAR_DELAY_MS);
                    }
                    Selection.setSelection(buffer,
                            buffer.getSpanStart(link),
                            buffer.getSpanEnd(link));
                }
                return true;
            } else {
                Selection.removeSelection(buffer);
            }
        }

        return super.onTouchEvent(widget, buffer, event);
    }

    @Override
    public void initialize(TextView widget, Spannable text) {
        Selection.removeSelection(text);
        text.removeSpan(FROM_BELOW);
    }

    @Override
    public void onTakeFocus(TextView view, Spannable text, int dir) {
        Selection.removeSelection(text);

        if ((dir & View.FOCUS_BACKWARD) != 0) {
            text.setSpan(FROM_BELOW, 0, 0, Spannable.SPAN_POINT_POINT);
        } else {
            text.removeSpan(FROM_BELOW);
        }
    }

    public static MovementMethod getInstance() {
        if (sInstance == null)
            sInstance = new LinkMovementMethod();

        return sInstance;
    }

    @UnsupportedAppUsage
    private static LinkMovementMethod sInstance;
    private static Object FROM_BELOW = new NoCopySpan.Concrete();
}

    源码有点多,但是我们的目标是实现点击效果,那么肯定跟触摸事件相关,所以,我们需要处理的也就是 onTouchEvent() 方法。接下来,我们通过继承 LinkMovementMethod 来自定义一个MovementMethod 类,在onTouchEvent() 方法中的 MotionEvent.ACTION_DOWN 和 MotionEvent.ACTION_UP 事件中添加处理逻辑。

/**
 * 可点击标记 MovementMethod
 * @param clickedBgColor 按下背景颜色
 */
class ClickableSpanMovementMethod(@ColorInt val clickedBgColor: Int) : LinkMovementMethod() {
    override fun onTouchEvent(widget: TextView?, buffer: Spannable?,  event: MotionEvent?): Boolean {
        if(null == event || null == widget || null == buffer) {
            return false
        }
        val action = event.action
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val links = buffer!!.getSpans(off, off, ClickableSpan::class.java)
            if (links.isNotEmpty()) {
                val link = links[0]
                if (action == MotionEvent.ACTION_UP) {
                    // ACTION_UP 移除选中
                    Selection.removeSelection(buffer)
                    link.onClick(widget)
                } else if (action == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 设置高亮色为点击色,并选中标记
                    widget.highlightColor = clickedBgColor
                    Selection.setSelection(
                        buffer,
                        buffer.getSpanStart(link),
                        buffer.getSpanEnd(link)
                    )
                }
                return true
            } else {
                Selection.removeSelection(buffer)
            }
        }
        return super.onTouchEvent(widget, buffer, event)
    }
}

    然后将 TextView 的 movementMethod 属性值设置为自定义的 MovementMethod 实例对象即可:

val tvStyle = findViewById<TextView>(R.id.tv_clickstyle_clickable_span)
tvStyle.movementMethod = ClickableSpanMovementMethod(Color.argb(0x20, 0x33, 0x33, 0x33))
tvStyle.setText(SpannableString("我是带点击效果的ClickableSpan").apply {
    setSpan(CSClickableSpan(Color.BLUE, View.OnClickListener {
        Toast.makeText(this@ClickableSpanActivity, tvStyle.text, Toast.LENGTH_SHORT).show()
    }), 8, 21, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
})
  • 实现效果
    ClickableSpan点击效果
        至此,已经完美实现 ClickableSpan 点击效果。上面的示例是通过改变选中高亮色来实现的,下面是通过给 ClickableSpan 重叠一个 BackgroundColorSpan 的实现方案,效果完全一致,代码如下所示:
/**
 * 可点击标记 MovementMethod
 * @param clickedBgColor 按下背景颜色
 */
class ClickableSpanMovementMethod(@ColorInt val clickedBgColor: Int) : LinkMovementMethod() {
    override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?): Boolean {
        if(null == event || null == widget || null == buffer) {
            return false
        }
        val action = event.action
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val links = buffer!!.getSpans(off, off, ClickableSpan::class.java)
            if (links.isNotEmpty()) {
                val link = links[0]
                if (action == MotionEvent.ACTION_UP) {
                    // ACTION_UP 给当前标记添加一个透明色的背景Span
                    buffer.setSpan(
                        BackgroundColorSpan(Color.TRANSPARENT), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                    link.onClick(widget)
                } else if (action == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 给当前标记添加一个点击色的背景Span
                    buffer.setSpan(BackgroundColorSpan(clickedBgColor), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                }
                return true
            } else {
                Selection.removeSelection(buffer)
            }
        }
//            return false
        return super.onTouchEvent(widget, buffer, event)
    }
}

3.2 在ClickableSpan中实现点击改变标记字体颜色

    我们知道,在实例化 Span 对象时,我们只能传 int 类型的颜色值,无法传入 ColorList 类型,因此点击时改变标记字体颜色,也必须通过自定义才能实现效果。有了前车之鉴,实现点击时改变字体颜色就很容易了。在上一章节中提到的实现点击效果,可以通过给文字一个叠加的背景色标记来实现,那么改变文字颜色,就可以通过叠加一个前景色标记来实现。下面直接上代码:

/**
 * 可点击标记 MovementMethod
 * @param clickedBgColor 按下背景颜色
 * @param normalTextColor 普通模式下文字颜色
 * @param clickedTextColor 按下文字颜色
 */
class ClickableSpanMovementMethod(@ColorInt val clickedBgColor: Int, @ColorInt val normalTextColor : Int,
                                  @ColorInt val clickedTextColor: Int) : LinkMovementMethod() {
    override fun onTouchEvent(widget: TextView?, buffer: Spannable?, event: MotionEvent?): Boolean {
        if(null == event || null == widget || null == buffer) {
            return false
        }
        val action = event.action
        if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
            var x = event.x.toInt()
            var y = event.y.toInt()
            x -= widget.totalPaddingLeft
            y -= widget.totalPaddingTop
            x += widget.scrollX
            y += widget.scrollY
            val layout = widget.layout
            val line = layout.getLineForVertical(y)
            val off = layout.getOffsetForHorizontal(line, x.toFloat())
            val links = buffer!!.getSpans(off, off, ClickableSpan::class.java)
            if (links.isNotEmpty()) {
                val link = links[0]
                if (action == MotionEvent.ACTION_UP) {
                    // ACTION_UP 给当前标记添加一个透明色的背景Span
                    buffer.setSpan(
                        BackgroundColorSpan(Color.TRANSPARENT), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // ACTION_UP 恢复普通字体颜色
                    buffer.setSpan(ForegroundColorSpan(normalTextColor), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)

                    // ACTION_UP 移除选中
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                    link.onClick(widget)
                } else if (action == MotionEvent.ACTION_DOWN) {
                    // ACTION_DOWN 给当前标记添加一个点击色的背景Span
                    buffer.setSpan(BackgroundColorSpan(clickedBgColor), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // ACTION_DOWN 给当前标记添加一个点击色的前景Span
                    buffer.setSpan(ForegroundColorSpan(clickedTextColor), buffer.getSpanStart(link),
                        buffer.getSpanEnd(link), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
                    // 移除选中(如果将TextView高亮色设置为透明,可忽略此行代码)
                    Selection.removeSelection(buffer)
                }
                return true
            } else {
                Selection.removeSelection(buffer)
            }
        }
        return super.onTouchEvent(widget, buffer, event)
    }
}
  • 实现效果
  • ClickableSpan-点击改变文字颜色
Android TextView ClickSpan与onClick事件冲突问题 0 本章内容 TextView自定义Span实现 多Span融合 点击事件冲突处理方法 1 TextView自定义Span实现 1.1 产品需求 产品要求实现一个类似于下图的功能 其中红框部分需要实现点击,且左右有Padding。 并且整体的文本需要有点击事件 和 长按事件,蓝字处要实现另一个点击事件 1.2 Android中的Span 这个不再赘述了,Android内本身支... 阅读详情

相关推荐

android实现clickspan点击的效果(类似@好友,当action_up时,效果消失)

android实现clickspan点击的效果(类似@好友,当action_up时,效果消失)

TextView ClickableSpan 事件分发的坑

TextView 的 ClickableSpan 的坑

u013083465的专栏 1375

UE5入门第二周总结:基于蓝图的国际象棋的实现

在C++的学习中,这是一个入门级别的项目。我们的目标是灵活运用蓝图的特性和强大的可视化功能,做出这一入门级项目的同时,思考如何优化并改进算法,并尽可能的用面向对象的方法实现,用蓝图中的继承和多态简化编码过程。

YoungeYang的博客 1258

ClickableSpan空白区域也能点击的问题

ClickableSpan是安卓开发过程中经常使用的一个类,只要有超链我们就能看到他的身影,使用方式也很简单,这里就不做介绍了,效果就是下面这个样子 通常情况下我们这么使用并不会有什么问题 ,但是测试却对我报了个bug,说,当@xxx之后,点击空白区域也会响应点击事件,这个是我没有想到的,所以特此记录一下。 我复现了一下应用场景: 当布局是这种方式时候,可以看到点击空白区域的时候,也会响应事件: 可以看到,我们在点击外部空白区域的时候确实有事件响应了,这个不是我们需要的.需要修改一下。 问题的原因就出现

wzlyd1的专栏 1518

SAP BAPI调用避坑指南:处理MD61/MD62计划独立需求时,这些参数和返回值你必须检查

本文详细解析了SAP BAPI调用中处理MD61/MD62计划独立需求的关键参数和返回值检查要点,包括版本控制、日期类型和删除标记等核心参数设置,以及常见错误排查和高级调试技巧,帮助开发者高效管理需求计划数据。

weixin_30316097的博客 610

Revit模型转点云全流程:从OBJ导出到CloudCompare点云生成(2020版)

本文详细介绍了将Revit模型转换为点云数据的完整流程,特别针对Revit 2020用户。通过使用OBJ导出插件和开源软件CloudCompare,实现从模型导出、网格采样到生成结构化PCD点云的全过程,并提供了关键参数设置与常见问题解决方案,为BIM与三维数字孪生应用提供技术支持。

oo7890的博客 154

AndroidUI SpannableString基本使用

SpannableString`可以在字符序列基础上对指定的字符进行修饰,在日常开发过程中可以对TextView进行修饰,如一段完整文字中,对当中的词或句添加,粗体、点击事件、颜色、下划线等等。

bugyinyin的博客 1832

dify离线部署

以bge-m3为例,本地部署bge-m3首先在一台能连网的linux服务器上(部署有ollama)创建想要保存模型的文件夹编辑ollama.service,配置ollama模型的保存路径添加模型存储地址将models目录设置属主,必须做,否则ollama无法启动重新加载配置文件并重启ollama测试ollama 运行是否正常最后拉取 bge-m3可在/opt/LLMModels/bge_m3/models目录下看到拉取得模型使用tar命令压缩文件夹。

weixin_42538734的博客 3064

Android TextView的各种Span

首先Span是什么 Span是功能强大的标记对象,可用于在字符或段落级别的文本设置样式.我们可以将该标记对象添加到文本上,从而可以改变文本的颜色,使文本可点击,缩放文本大小等等. 创建Span 创建一个Span,可以使用以下三个类SpannedString 使用该类创建的Span对象不可以改变文本,不可以改变标记.很少使用到.SpannableString 使用该类创建的Span对象不可改变文本,可以改变标记,一般在不修改文本内容,只修改文本标记的情况下(比如修改文本的颜色等)应该使用该类.Spann..

xujian197的博客 1680

基于价值的深度强化学习:DQN

本文主要是介绍DQN原理和算法伪代码和代码实现,算法实现包括pytorch版本和stable-baselines3版本

溯源的专栏 1082

中国高精度年度平均气温栅格数据集(1901-2024)

本文基于中国月度平均气温栅格数据集,计算生成1901-2024年1km分辨率的年度平均气温数据集,并按中国省市行政区划进行裁剪。该数据集融合CRU全球数据和WorldClim高分辨率数据,经496个站点验证,具有高时空精度和长时间序列特点,覆盖全国(含港澳台)逾百年气温记录,为气候变化研究、生态水文分析及农业生产评估等提供重要数据支撑。数据格式为Tif,单位为0.1℃,可获取全国、各省及各市三个尺度的气温栅格数据。

PPdata_Academy的博客 1241

安卓 使用 clickablespan 时点击无效问题

安卓Textview 使用SpannableString 设置ClickableSpan 出现点击事件无效。 注意以下几点: 1. 要注意 文字要设置在 textview setText之前 2. 设置文字之后再设置 setSpan 3. textView 设置 setMovementMethod(LinkMovementMethod.getInstance()) 4. 然后会有点击事件效果 ,但是字体点击时会有一个背景点击的选中效果,默认主题色, 如果不想要的话,设置 setHighligh...

Mr_Liangxiaobai的博客 6580

Windows 11 WSL2环境下OpenClaw部署的兼容性问题分析与解决方案研究

本文研究了OpenClaw在Windows 11 WSL2环境下的部署方案。WSL2通过无缝集成Linux与Windows系统,为OpenClaw提供了良好的运行环境,支持GPU加速和跨平台开发。文章详细分析了硬件配置要求(推荐8核CPU/16GB内存/NVMe SSD)、系统选择(Windows 11 Pro+Ubuntu 22.04)以及部署步骤,包括WSL2启用、Linux发行版安装、Python/Node.js/Redis等依赖配置。通过优化.wslconfig文件参数(如内存分

博客 2278

android 字体各种设置方式 Span的使用

一:首先说一下 Span的各种功能 BackgroundColorSpan 背景色 ClickableSpan 文本可点击,有点击事件 ForegroundColorSpan 文本颜色(前景色) DrawableMarginSpan  文本插入图片 MaskFilterSpan 修饰效果,如模糊(BlurMaskFilter)、浮雕(EmbossMaskFilter) MetricAff...

林清溯 2124

RK3588网卡出现丢包或者带宽不足的问题验证排查

注意除了有最大的TCP数据接收缓冲,还有/proc/sys/net/core/wmem_max — 最大的TCP数据发送缓冲可以调整,这里暂时没用到,针对具体需要可适当调整。比如把ethx接口中断从默认的cpu0迁移到其它cpu,例如将eth0中断234移到cpu7 (10是16进制, cpu0 =3588的cpu有8个核,4个A55(小核)+4个A76(大核),可以找到对应中断 将中断从默认cpu0核切到cpu7。tcp_rmem:单位是字节,接收缓存区大小,缓存从对端接收的数据,后续会被应用程序读取。

Android/Linux驱动开发传帮带学习,让驱动入门门槛更低! 2026

TMS_WebGMaps_1.9.8.0_Full_Source

TMS_WebGMaps_1.9.8.0_Full_Source 全源码 delphi cbuilder

android如何设置多个span_android – 在TextView中处理多个ClickableSpan

试试这种方式String s="Cat link 1 Cat link 2 Cat link 3";SpannableString ss = new SpannableString(s);String first ="Cat link 1";String second ="Cat link 2";String third ="Cat link 3";int firstIndex = s.toStr...

weixin_36489344的博客 852

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

【Android】Span富文本简介

android.text包下span体系类,主要指Spanned、Spannable、ParagraphStyle、CharacterStyle实现类。Android通过Span体系,搭建了富文本API,其中Spanned、Spannable实现了CharSequence接口,旨在映射段落start~end之间Span装饰对象。

qq_40900926的博客 956

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

android span的用法,Android Span的使用

Android中TextView作为最常用的组件, 常常有很多特殊的需求,例如:嵌入图标、部分文字可点击,点击链接等,这些需求都可以通过Span来实现。上面就是系统提供的所有可用的Span类。下面就来解决上面提出的两个问题。1.文字中添加图片这种方式的实现主要是利用ImageSpanval spannableString = SpannableString("img这是一张图片")getDrawa...

weixin_35671171的博客 514

Android Span富文本设置点击事件/超链接 - URLSpan/ClickableSpan

######为文字实现很丰富的特殊效果,下面来实现 点击事件/超链接 操作 so... 直接上效果: ##前提条件: textView.movementMethod = LinkMovementMethod.getInstance() cc:TextView必须设置此方法的前提下,才能选择受影响的文本范围,不然点击会没有任何效果 #1设置点击事件:ClickableSpan //ClickableSpan不能直接setSpan class MClickableSpan(...

最孤单的人的博客 4392

Android 使用 Span 打造丰富多彩的文本

code小生一个专注大前端领域的技术平台公众号回复Android加入安卓技术群本文来自于:QiShare1.引言在开发过程中经常需要使用文本,有时候需要对一段文字中的部分文字进行特殊的处...

weixin_38754349的博客 413

Android TextView 中,使用ClickableSpan实现点击效果和修改文字颜色

ClickableSpan 可以支持在Textiew中,实现点击处理。但是文字颜色是固定的,无法修改。 这里通过查看ClickableSpan的定义和参考其它文档,实现了修改颜色的功能。 如下是我具体使用的例子,在updateDrawState中,重新设定链接文字的颜色。 public abstract class QuestionClickSpan extends ClickableSp...

Vigi的专栏 3175

Android SpannableString 中 ClickableSpan的使用注意事项

bdCommentEmptyView.setText("开头");//直接先设置前段文字 SpannableString spannableString = new SpannableString("要点击的内容"); spannableString.setSpan(new ForegroundColorSpan(getResources().getColor(R.color.theme_default)), 0, 6, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);//修改字体颜色

weixin_43942430的博客 1266

Android中各种Span的用法

Android中各种Span的用法 Android中各种Span的用法 前言SpannableStringBuilderURLSpanUnderlineSpanTypefaceSpanTextAppearanceSpanTabStopSpanStandardSuperscriptSpanSubscriptSpanStrikethroughSpanScaleXSpanStyl

kisso143的博客 1288

Android的TextView部分文字点击切换颜色(ClickableSpan)

Activity关键代码: tv.setText(getClickableSpan(1)); //设置点击背景色透明 ((TextView) tv).setHighlightColor(getResources().getColor(android.R.color.transparent)); tv.setMovementMethod(LinkMovementMethod.getInstance()); } private Spannable

苦涩精灵的博客 1528

android clickablespan listview,解决TextView中ClickableSpan点击事件

需求背景:在TextView中包含局部可点击的链接,且改链接其他地方也是有相应的点击事件其实需求比较合理,实现也应该不难,于是简单的demo如下:TextView content = (TextView) findViewById(R.id.comment_item_detail_content);String string = "我是和常常大声点发大水发送到发送到发";SpannableStri...

weixin_34429137的博客 663

android clickablespan超链接 点击时改变背景色,android中TextView 添加ClickableSpan后点击选中文字背景变色问题...

public class MainActivity extendsActionBarActivity {@Overrideprotected voidonCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);if (savedInstanceState == null) {getSupportFragmentMa...

weixin_42387319的博客 929
上一篇: Gradle 构建环境变量配置
下一篇: Gradle基础——在构建脚本定义变量
精装机械师
博客等级 码龄17年 109粉丝 95原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值