{"id":18561051,"url":"https://github.com/2dxgujun/kpan","last_synced_at":"2025-07-11T02:34:19.296Z","repository":{"id":83626026,"uuid":"115491161","full_name":"2dxgujun/Kpan","owner":"2dxgujun","description":":four_leaf_clover:Kotlin wrapper around SpannableStringBuilder to bring modern API","archived":false,"fork":false,"pushed_at":"2018-10-23T03:19:31.000Z","size":370,"stargazers_count":105,"open_issues_count":7,"forks_count":9,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-04-10T18:13:11.832Z","etag":null,"topics":["span","spannable","spannablestring","spannablestringbuilder","text","textstyle","textview"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/2dxgujun.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-12-27T06:49:38.000Z","updated_at":"2023-11-24T15:48:45.000Z","dependencies_parsed_at":null,"dependency_job_id":"f56ea7bf-58cd-4f2c-a9b5-0b247916a212","html_url":"https://github.com/2dxgujun/Kpan","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/2dxgujun/Kpan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2dxgujun%2FKpan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2dxgujun%2FKpan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2dxgujun%2FKpan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2dxgujun%2FKpan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/2dxgujun","download_url":"https://codeload.github.com/2dxgujun/Kpan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/2dxgujun%2FKpan/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":264713080,"owners_count":23652716,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["span","spannable","spannablestring","spannablestringbuilder","text","textstyle","textview"],"created_at":"2024-11-06T22:05:36.496Z","updated_at":"2025-07-11T02:34:19.277Z","avatar_url":"https://github.com/2dxgujun.png","language":"Kotlin","readme":"# Android Spannable in kotlin\n\n\u003e Kotlin wrappers around SpannableStringBuilder. Inspired by binaryfork's [Spanny](https://github.com/binaryfork/Spanny).\n\n[SpannableStringBuilder](https://developer.android.com/reference/android/text/SpannableStringBuilder.html)\nAPI is attractive, it's a nice way to styling text without split your text into multiple views, \nbut the usage of the raw API is awful.\n\nLet's say a simple example: Spans **are ~~hard~~**?\n\n- The raw `SpannableStringBuilder` API:\n\n```kotlin\nval builder = SpannableStringBuilder()\nbuilder.append(\"Spans \")\nvar str = \"are \"\nbuilder.append(str)\nbuilder.setSpan(StyleSpan(Typeface.BOLD), builder.length - str.length, builder.length,\n    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\nstr = \"hard\"\nbuilder.append(str)\nbuilder.setSpan(StyleSpan(Typeface.BOLD), builder.length - str.length, builder.length,\n    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\nbuilder.setSpan(StrikethroughSpan(), builder.length - str.length, builder.length,\n    Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)\nbuilder.append(\"?\")\n```\n\n- The [Spanny](https://github.com/binaryfork/Spanny) helper API:\n```kotlin\nval spanny = Spanny(\"Spans \")\n    .append(\"are \", StyleSpan(Typeface.BOLD))\n    .append(\"hard\", StyleSpan(Typeface.BOLD), StrikethroughSpan())\n    .append(\"?\")\n```\n\nThat is cool in Java, but I was using kotlin, and it's not cool enough, it's verbose and limited. Kotlin allows to [*type-check* builders](https://kotlinlang.org/docs/reference/type-safe-builders.html), \nwhich is the best way to build spans, so I make it happen:\n\n```kotlin\nval text = span {\n  +\"Spans \"\n  span {\n    textStyle = \"bold\"\n    +\"are \"\n    span(\"hard\") {\n      textDecorationLine = \"line-through\"\n    }\n  }\n  +\"?\"\n}\n```\n\n![sample](/screenshots/sample.png)\n\n### Download\n\nYou can grab it via Gradle:\n\n```\nimplementation 'me.gujun.android:span:1.7'\n```\n\n### Usage\n\n```kotlin\nval text = span {\n  textColor = Color.BLACK\n  backgroundColor = Color.WHITE\n  textSize = dp(13)\n  fontFamily = \"monospace\"\n  typeface = getFont(R.font.pacifico)\n  textStyle = \"bold\"\n  alignment = \"normal\"\n  textDecorationLine = \"underline\"\n  lineSpacing = dp(3)\n  verticalPadding = dp(5)\n  paddingTop = dp(5)\n  paddingBottom = dp(5)\n  onClick = {\n  }\n  addSpan(TextAppearanceSpan(context, R.style.TextAppearance))\n  \n  span {\n    text = \"Hi\"\n  }\n  \n  span(\"Hi\")\n  \n  +\"Hi\"\n  \n  link(\"http://google.com\", \"Text\")\n  \n  quote(Color.RED, \"Text\")\n  \n  subscript(\"Text\")\n  \n  superscript(\"Text\")\n  \n  image(getDrawable(R.drawable.ic_fun))\n}\n```\n\n**Caution: Avoid nested text in spans**\n\nThe following code will cause *RuntimeException: Can't nest \"World\" in spans*. \nThis is because you try to nest span with text \"World\" in its parent span with text \"Hello\", \nit does not make any sense.\n\n```kotlin\nval text = span {\n  text = \"Hello\"\n  span(\"World\")\n}\n```\n\n#### Overwrite styles\n\nStyles can be overwrite in nested spans, inner span overwrite the outer span.\n\n```kotlin\nval text = span {\n  textColor = Color.BLUE\n  textSize = dp(20)\n  +\"Simple text\"\n  +\"\\n\"\n  span(\"Overwrite foreground\") {\n    textColor = Color.RED\n  }\n  +\"\\n\"\n  span(\"Overwrite text size\") {\n    textSize = dp(10)\n  }\n}\n```\n\n#### Reusable style\n\nYou can create reusable styles and use them in multiple spans.\n\n```kotlin\nval headerStyle = style {\n  textColor = Color.BLACK\n  textStyle = \"bold\"\n  textSize = dp(20)\n  verticalPadding = dp(3)\n}\nval content = span {\n  span(\"Header1\") {\n    style = headerStyle\n  }\n  span {\n    text = \"\\n...\\n\"\n  }\n  span(\"Header2\") {\n    style = headerStyle\n  }\n}\n```\n\nor you can declare an extension function and apply styles as you wish:\n\n```kotlin\nfun Span.header(text: CharSequence): Span = span(text) {\n  textColor = Color.BLACK\n  textStyle = \"bold\"\n  textSize = dp(20)\n  verticalPadding = dp(3)\n}\n\nval content = span {\n  header(\"Header1\\n\")\n  span {\n    text = \"...\"\n  }\n}\n```\n\n#### Global style\n\nYou can set global style by create a *style* and pass it to the companion object `Span.globalStyle`.\n\nFor example, if your project using a custom typeface, you can apply styles with custom typeface, \nthen you will no need to specify typeface each time you using spans. \n\n```kotlin\nSpan.globalStyle = style {\n  typeface = getFont(R.font.pacifico)\n}\n```\n\n### Reference\n\n#### Supported attributes\n\n- text\n\n- textColor\n\n- backgroundColor\n\n- textSize\n\n- typeface\n\n- lineSpacing\n\n- verticalPadding\n\n- paddingTop\n\n- paddingBottom\n\n- onClick\n\n- fontFamily: include \"monospace\", \"serif\", and \"sans-serif\"\n\n- textStyle: include \"normal\", \"bold\", \"italic\", and \"bold_italic\"\n\n- alignment: include \"normal\", \"opposite\", and \"center\"\n\n- textDecorationLine: include \"none\", \"underline\", \"line-through\", and \"underline line-through\"\n\n\n#### Extended spans\n\n- link: URLSpan\n\n- quote: QuoteSpan\n\n- superscript: SuperscriptSpan\n\n- subscript: SubscriptSpan\n\n- image: ImageSpan, include \"bottom\" and \"baseline\" alignment\n\n- addSpan: Add custom span\n\n\n### License\n\n```\nCopyright 2018 Jun Gu\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2dxgujun%2Fkpan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2F2dxgujun%2Fkpan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2F2dxgujun%2Fkpan/lists"}