├── .gitignore
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle
├── keystore.jks
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── glailton
│ │ └── expandabletextview
│ │ └── demo
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── glailton
│ │ │ └── expandabletextview
│ │ │ └── demo
│ │ │ ├── MainActivity.kt
│ │ │ └── ui
│ │ │ ├── ExamplesFragment.kt
│ │ │ ├── ExpandableCardAdapter.kt
│ │ │ ├── ListViewFragment.kt
│ │ │ └── RecyclerViewFragment.kt
│ └── res
│ │ ├── drawable-v24
│ │ └── ic_launcher_foreground.xml
│ │ ├── drawable
│ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── fragment_examples.xml
│ │ ├── fragment_list_view.xml
│ │ ├── fragment_recycler_view.xml
│ │ ├── item.xml
│ │ ├── item_expandable_card.xml
│ │ └── nav_header.xml
│ │ ├── menu
│ │ └── nav_menu.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ └── ic_launcher_round.webp
│ │ ├── navigation
│ │ └── nav_graph.xml
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── io
│ └── github
│ └── glailton
│ └── expandabletextview
│ └── demo
│ └── ExampleUnitTest.kt
├── build.gradle
├── expandable-textview
├── .gitignore
├── build.gradle
├── gradle.properties
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── io
│ │ └── github
│ │ └── glailton
│ │ └── expandabletextview
│ │ └── ExampleInstrumentedTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── io
│ │ │ └── github
│ │ │ └── glailton
│ │ │ └── expandabletextview
│ │ │ ├── Constants.kt
│ │ │ └── ExpandableTextView.kt
│ └── res
│ │ ├── values-night
│ │ └── themes.xml
│ │ └── values
│ │ ├── attrs.xml
│ │ ├── colors.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ └── test
│ └── java
│ └── io
│ └── github
│ └── glailton
│ └── expandabletextview
│ └── ExampleUnitTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── publish-mavencentral.gradle
├── resources
└── gif.gif
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.aar
4 | *.ap_
5 | *.aab
6 |
7 | # Files for the ART/Dalvik VM
8 | *.dex
9 |
10 | # Java class files
11 | *.class
12 |
13 | # Generated files
14 | bin/
15 | gen/
16 | out/
17 | # Uncomment the following line in case you need and you don't have the release build type files in your app
18 | # release/
19 |
20 | # Gradle files
21 | .gradle/
22 | build/
23 |
24 | # Local configuration file (sdk path, etc)
25 | local.properties
26 |
27 | # Proguard folder generated by Eclipse
28 | proguard/
29 |
30 | # Log Files
31 | *.log
32 |
33 | # Android Studio Navigation editor temp files
34 | .navigation/
35 |
36 | # Android Studio captures folder
37 | captures/
38 |
39 | # IntelliJ
40 | *.iml
41 | .idea/*
42 | .idea/*.xml
43 | .idea/workspace.xml
44 | .idea/tasks.xml
45 | .idea/gradle.xml
46 | .idea/assetWizardSettings.xml
47 | .idea/dictionaries
48 | .idea/libraries
49 | # Android Studio 3 in .gitignore file.
50 | .idea/caches
51 | .idea/modules.xml
52 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you
53 | .idea/navEditor.xml
54 | .idea/codeStyles/
55 | .idea/misc.xml
56 | .idea/compiler.xml
57 |
58 | # Keystore files
59 | # Uncomment the following lines if you do not want to check your keystore files in.
60 | #*.jks
61 | #*.keystore
62 |
63 | # External native build folder generated in Android Studio 2.2 and later
64 | .externalNativeBuild
65 | .cxx/
66 |
67 | # Google Services (e.g. APIs or Firebase)
68 | # google-services.json
69 |
70 | # Freeline
71 | freeline.py
72 | freeline/
73 | freeline_project_description.json
74 |
75 | # fastlane
76 | fastlane/report.xml
77 | fastlane/Preview.html
78 | fastlane/screenshots
79 | fastlane/test_output
80 | fastlane/readme.md
81 |
82 | # Version control
83 | vcs.xml
84 |
85 | # lint
86 | lint/intermediates/
87 | lint/generated/
88 | lint/outputs/
89 | lint/tmp/
90 | # lint/reports/
91 |
92 | *.gpg
93 |
94 | # txt
95 | *.txt
96 |
97 | # mp4
98 | *.mp4
99 |
100 | app/release
101 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Glailton Costa
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ExpandableTextView
2 |
3 | [](https://central.sonatype.com/artifact/io.github.glailton/expandabletextview)
4 | [](https://appetize.io/app/b_tebmxa2vwvqnbyxetlafkh725e)
5 | [](https://opensource.org/licenses/MIT)
6 |
7 | An Expandable TextView for Android written in
8 | [Kotlin](https://kotlinlang.org/). The main ideia was study how create a library in Android and deploy
9 | on [Maven Central Repository](https://s01.oss.sonatype.org).
10 |
11 |
12 | ## Table of Contents
13 |
14 | - [Demo project](#demo-project)
15 | - [Getting started](#getting-started)
16 | - [Features](#features)
17 | - [Usage](#usage)
18 | - [XML](#in-xml)
19 | - [Programmatically](#programmatically)
20 | - [Contributing](#contributing)
21 | - [License](#license)
22 | - [Acknowledgments](#acknowledgments)
23 |
24 | ## Demo Project
25 |
26 | `ExpandableTextView` is a custom `TextView` that allows long text to be collapsed or expanded, either inline within the layout or via a popup dialog.
27 | Take a look at the [demo project](app).
28 |
29 | 
30 |
31 | ## Getting Started
32 |
33 | The library is included in Maven Central Repository, so just add this dependency to your module level `gradle.build`:
34 |
35 | ```kotlin
36 | dependencies {
37 | implementation 'io.github.glailton.expandabletextview:expandabletextview:$LatestVersion'
38 | }
39 | ```
40 | Current latest version is: [](https://search.maven.org/artifact/io.github.glailton.expandabletextview/expandabletextview)
41 |
42 | ## Features
43 |
44 | - Collapsible/expandable text with animation.
45 | - Two expansion modes: layout-based and popup.
46 | - Custom "read more" / "read less" labels.
47 | - Ellipsized text with custom color and underline.
48 | - Optional fade animation on expand/collapse.
49 | - Supports usage in XML, programmatically, or with data binding.
50 |
51 | ---
52 |
53 | ## XML Attributes
54 |
55 | | Attribute | Type | Description |
56 | |------------------------|---------|-----------------------------------------------------------------------------|
57 | | `collapsedLines` | Integer | Number of lines to show when collapsed. Default is 3. |
58 | | `isExpanded` | Boolean | Initial expansion state. Default is `false`. |
59 | | `animDuration` | Integer | Animation duration in milliseconds. Default is 300. |
60 | | `readMoreText` | String | Text shown to expand the view. Default is `"Read more"`. |
61 | | `readLessText` | String | Text shown to collapse the view. Default is `"Read less"`. |
62 | | `expandType` | Enum | Either `layout` or `popup`. Default is `layout`. |
63 | | `ellipsizeTextColor` | Color | Color of the ellipsis and expand label. Default is `Color.BLUE`. |
64 | | `isUnderlined` | Boolean | Whether the expand/collapse label should be underlined. Default is `false`. |
65 | | `fadeAnimationEnabled` | Boolean | Enable or disable fade animation. Default is `true`. |
66 |
67 | ---
68 |
69 | ## Usage
70 |
71 | ### In XML
72 |
73 | Xml snippet:
74 | ```xml
75 |
88 | ```
89 | ### Programmatically
90 | ```
91 | binding.expandTvProg
92 | .setAnimationDuration(500)
93 | .setFadeAnimationEnabled(true)
94 | .setReadMoreText("View More")
95 | .setReadLessText("View Less")
96 | .setCollapsedLines(3)
97 | .setIsExpanded(true)
98 | .setIsUnderlined(true)
99 | .setExpandType(EXPAND_TYPE_POPUP)
100 | .setEllipsizedTextColor(ContextCompat.getColor(this, R.color.purple_200))
101 |
102 | binding.expandTvProg.text =
103 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
104 | "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +
105 | "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
106 | "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
107 |
108 | binding.expandTvProg.setOnClickListener {
109 | binding.expandTvProg.toggle()
110 | if (binding.expandTvVeryLong.isExpanded && binding.expandTvVeryLong.expandType == EXPAND_TYPE_LAYOUT)
111 | binding.expandTvVeryLong.toggle()
112 | }
113 | ```
114 |
115 | ## Contributing
116 |
117 | If you wish to send a pull request, please make sure to checkout from `main` branch and merge with `main` branch as well.
118 |
119 | ## License
120 |
121 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
122 |
123 | ## Acknowledgments
124 |
125 | This library was based on: [viewmore-textview](https://github.com/mike5v/viewmore-textview).
126 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'kotlin-android'
4 | id 'kotlin-kapt'
5 | }
6 |
7 | android {
8 | compileSdk 35
9 |
10 | defaultConfig {
11 | applicationId "br.com.grsoft.expandabletextview.demo"
12 | minSdkVersion 23
13 | targetSdkVersion 35
14 | versionCode 1
15 | versionName "1.0.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_21
28 | targetCompatibility JavaVersion.VERSION_21
29 | }
30 | kotlinOptions {
31 | jvmTarget = '21'
32 | }
33 | buildFeatures {
34 | viewBinding true
35 | }
36 | namespace 'io.github.glailton.expandabletextview.demo'
37 | }
38 |
39 | dependencies {
40 |
41 | implementation 'androidx.core:core-ktx:1.3.2'
42 | implementation 'androidx.appcompat:appcompat:1.2.0'
43 | implementation 'com.google.android.material:material:1.3.0'
44 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
45 | implementation 'androidx.drawerlayout:drawerlayout:1.2.0'
46 | implementation 'androidx.navigation:navigation-fragment-ktx:2.8.9'
47 | implementation 'androidx.navigation:navigation-ui-ktx:2.8.9'
48 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:2.1.0"
49 |
50 | implementation project(':expandable-textview')
51 | //implementation("io.github.glailton:expandabletextview:1.0.6")
52 | testImplementation 'junit:junit:4.+'
53 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
54 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
55 | }
--------------------------------------------------------------------------------
/app/keystore.jks:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/keystore.jks
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/app/src/androidTest/java/io/github/glailton/expandabletextview/demo/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("br.com.grsoft.expandabletextview.demo", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/glailton/expandabletextview/demo/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.appcompat.widget.Toolbar
6 | import androidx.drawerlayout.widget.DrawerLayout
7 | import androidx.navigation.NavController
8 | import androidx.navigation.fragment.NavHostFragment
9 | import androidx.navigation.ui.AppBarConfiguration
10 | import androidx.navigation.ui.navigateUp
11 | import androidx.navigation.ui.setupActionBarWithNavController
12 | import androidx.navigation.ui.setupWithNavController
13 | import com.google.android.material.navigation.NavigationView
14 |
15 | class MainActivity : AppCompatActivity() {
16 |
17 | private lateinit var drawerLayout: DrawerLayout
18 | private lateinit var navController: NavController
19 | private lateinit var appBarConfiguration: AppBarConfiguration
20 |
21 | override fun onCreate(savedInstanceState: Bundle?) {
22 | super.onCreate(savedInstanceState)
23 | setContentView(R.layout.activity_main)
24 |
25 | drawerLayout = findViewById(R.id.drawer_layout)
26 | val navView = findViewById(R.id.navigationView)
27 | val toolbar = findViewById(R.id.toolbar)
28 | setSupportActionBar(toolbar)
29 |
30 | val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
31 | navController = navHostFragment.navController
32 | appBarConfiguration = AppBarConfiguration(
33 | setOf(R.id.examplesFragment, R.id.listViewFragment), drawerLayout
34 | )
35 |
36 | setupActionBarWithNavController(navController, appBarConfiguration)
37 | navView.setupWithNavController(navController)
38 | }
39 |
40 | override fun onSupportNavigateUp(): Boolean {
41 | return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/glailton/expandabletextview/demo/ui/ExamplesFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo.ui
2 |
3 | import android.os.Bundle
4 | import android.text.SpannableString
5 | import androidx.fragment.app.Fragment
6 | import android.view.LayoutInflater
7 | import android.view.View
8 | import android.view.ViewGroup
9 | import android.widget.TextView
10 | import androidx.core.content.ContextCompat
11 | import io.github.glailton.expandabletextview.EXPAND_TYPE_LAYOUT
12 | import io.github.glailton.expandabletextview.EXPAND_TYPE_POPUP
13 | import io.github.glailton.expandabletextview.demo.R
14 | import io.github.glailton.expandabletextview.demo.databinding.FragmentExamplesBinding
15 |
16 | class ExamplesFragment : Fragment() {
17 |
18 | private var _binding: FragmentExamplesBinding? = null
19 | private val binding get() = _binding!!
20 |
21 | override fun onCreateView(
22 | inflater: LayoutInflater, container: ViewGroup?,
23 | savedInstanceState: Bundle?,
24 | ): View {
25 | _binding = FragmentExamplesBinding.inflate(inflater, container, false)
26 | return _binding!!.root
27 | }
28 |
29 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
30 | super.onViewCreated(view, savedInstanceState)
31 |
32 | binding.expandTvProg
33 | .setAnimationDuration(500)
34 | .setReadMoreText("View More")
35 | .setReadLessText("View Less")
36 | .setCollapsedLines(3)
37 | .setIsExpanded(true)
38 | .setIsUnderlined(true)
39 | .setExpandType(EXPAND_TYPE_POPUP)
40 | .setEllipsizedTextColor(ContextCompat.getColor(requireActivity(), R.color.purple_200))
41 |
42 | binding.expandTvProg.text =
43 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." +
44 | "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." +
45 | "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur." +
46 | "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
47 |
48 | binding.expandTvProg.setOnClickListener {
49 | binding.expandTvProg.toggle()
50 | if (binding.expandTvVeryLong.isExpanded && binding.expandTvVeryLong.expandType == EXPAND_TYPE_LAYOUT)
51 | binding.expandTvVeryLong.toggle()
52 | }
53 |
54 | binding.expandTvSpannable
55 | .setAnimationDuration(500)
56 | .setFadeAnimationEnabled(true)
57 | .setReadMoreText("View More")
58 | .setReadLessText("View Less")
59 | .setCollapsedLines(3)
60 | .setIsExpanded(false)
61 | .setIsUnderlined(true)
62 | .setExpandType(EXPAND_TYPE_LAYOUT)
63 | .setEllipsizedTextColor(ContextCompat.getColor(requireActivity(), R.color.purple_200))
64 |
65 | binding.expandTvSpannable.setText(SpannableString("quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."), TextView.BufferType.SPANNABLE)
66 |
67 | }
68 |
69 | override fun onDestroyView() {
70 | super.onDestroyView()
71 | _binding = null
72 | }
73 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/glailton/expandabletextview/demo/ui/ExpandableCardAdapter.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo.ui
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import androidx.recyclerview.widget.RecyclerView
7 | import io.github.glailton.expandabletextview.ExpandableTextView
8 | import io.github.glailton.expandabletextview.demo.R
9 |
10 | class ExpandableCardAdapter(private val items: List) :
11 | RecyclerView.Adapter() {
12 |
13 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExpandableCardViewHolder {
14 | val view = LayoutInflater.from(parent.context)
15 | .inflate(R.layout.item_expandable_card, parent, false)
16 | return ExpandableCardViewHolder(view)
17 | }
18 |
19 | override fun onBindViewHolder(holder: ExpandableCardViewHolder, position: Int) {
20 | holder.bind(items[position])
21 | }
22 |
23 | override fun getItemCount() = items.size
24 |
25 | class ExpandableCardViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
26 | private val expandableTextView =
27 | itemView.findViewById(R.id.expandableTextView)
28 |
29 | fun bind(text: String) {
30 | expandableTextView.text = text
31 | expandableTextView
32 | .setReadMoreText(itemView.context.getString(R.string.view_more))
33 | .setReadLessText(itemView.context.getString(R.string.view_less))
34 | .setCollapsedLines(3)
35 | .setIsExpanded(false)
36 | .setIsUnderlined(true)
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/io/github/glailton/expandabletextview/demo/ui/ListViewFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo.ui
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import android.widget.ArrayAdapter
9 | import android.widget.ListView
10 | import androidx.core.content.ContextCompat
11 | import io.github.glailton.expandabletextview.EXPAND_TYPE_DEFAULT
12 | import io.github.glailton.expandabletextview.ExpandableTextView
13 | import io.github.glailton.expandabletextview.demo.R
14 | import io.github.glailton.expandabletextview.demo.databinding.FragmentListViewBinding
15 |
16 | class ListViewFragment : Fragment() {
17 |
18 | private var _binding: FragmentListViewBinding? = null
19 | val binding get() = _binding
20 |
21 | private val dummyTexts = listOf(
22 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
23 | "Short text.",
24 | "Another very very very very very very very very very very long text that will probably need to be collapsed when displayed! dshgsdfgdfgkjdgçsdjfglpgçsodifghçdlkjb´sdopigj´wpothjw´srpofbjsd~çlbm ´sjb ´pofbj ´dspofb s´pfobgj[sapfogj´dsaopfigjádiogh´-qeir9jgádpofbgj´~dsofpbjdóibj´sdoifbjdosfibjsdfbn´sfioghj´sdfophjsdfplhgsj~sdfpjge"
25 | )
26 |
27 | override fun onCreateView(
28 | inflater: LayoutInflater, container: ViewGroup?,
29 | savedInstanceState: Bundle?,
30 | ): View {
31 | // Inflate the layout for this fragment
32 | _binding = FragmentListViewBinding.inflate(inflater, container, false)
33 | return _binding!!.root
34 | }
35 |
36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
37 | super.onViewCreated(view, savedInstanceState)
38 |
39 | val listView = view.findViewById(R.id.listView)
40 | listView.adapter = object : ArrayAdapter(requireActivity(), R.layout.item, R.id.listview_review_record_content, dummyTexts) {
41 | override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
42 | val rowView = super.getView(position, convertView, parent)
43 |
44 | val reviewContent = rowView.findViewById(R.id.listview_review_record_content)
45 | reviewContent.text = getItem(position)
46 |
47 | reviewContent
48 | .setReadMoreText(getString(R.string.view_more))
49 | .setReadLessText(getString(R.string.view_less))
50 | .setCollapsedLines(3)
51 | .setIsExpanded(false)
52 | .setIsUnderlined(true)
53 | .setExpandType(EXPAND_TYPE_DEFAULT)
54 | .setEllipsizedTextColor(ContextCompat.getColor(parent.context, R.color.purple_700))
55 |
56 | return rowView
57 | }
58 | }
59 | }
60 |
61 | override fun onDestroyView() {
62 | super.onDestroyView()
63 | _binding = null
64 | }
65 | }
--------------------------------------------------------------------------------
/app/src/main/java/io/github/glailton/expandabletextview/demo/ui/RecyclerViewFragment.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo.ui
2 |
3 | import android.os.Bundle
4 | import androidx.fragment.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import androidx.recyclerview.widget.LinearLayoutManager
9 | import androidx.recyclerview.widget.RecyclerView
10 | import io.github.glailton.expandabletextview.demo.R
11 |
12 | class RecyclerViewFragment : Fragment() {
13 | private val dummyTexts = listOf(
14 | "Texto curto.",
15 | "Este é um texto um pouco maior que deve ser truncado ao exibir no card.",
16 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin a justo in sapien laoreet accumsan. Sed fringilla risus non massa ultrices. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin a justo in sapien laoreet accumsan. Sed fringilla risus non massa ultrices"
17 | )
18 |
19 | override fun onCreateView(
20 | inflater: LayoutInflater, container: ViewGroup?,
21 | savedInstanceState: Bundle?,
22 | ): View? {
23 | // Inflate the layout for this fragment
24 | return inflater.inflate(R.layout.fragment_recycler_view, container, false)
25 | }
26 |
27 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
28 | super.onViewCreated(view, savedInstanceState)
29 |
30 | val recyclerView = view.findViewById(R.id.recyclerView)
31 | recyclerView.layoutManager = LinearLayoutManager(requireContext())
32 | recyclerView.adapter = ExpandableCardAdapter(dummyTexts)
33 | }
34 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
9 |
10 |
13 |
14 |
25 |
26 |
34 |
35 |
36 |
43 |
44 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_examples.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
15 |
16 |
34 |
35 |
53 |
54 |
71 |
72 |
86 |
87 |
99 |
100 |
111 |
112 |
113 |
114 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_list_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_recycler_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_expandable_card.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/nav_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/navigation/nav_graph.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
14 |
15 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExpandableTextView Demo
3 | Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
4 | "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean ornare lacinia magna, sed eleifend dui aliquet sed."
5 | Lorem Ipsum dolor sit amet
6 |
7 | Eu inicio com esse texto que parece com o que eu quero setar mas nao eh Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac orci quis tortor imperdiet venenatis.
8 | Duis elementum auctor accumsan. Aliquam in felis sit amet augue.
9 |
10 | View More
11 | View Less
12 |
13 | Hello blank fragment
14 | ExpandableTextView
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/app/src/test/java/io/github/glailton/expandabletextview/demo/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview.demo
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:8.10.0'
9 | classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.0'
10 |
11 | classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.10.2'
12 | }
13 | }
14 |
15 | task clean(type: Delete) {
16 | delete rootProject.buildDir
17 | }
--------------------------------------------------------------------------------
/expandable-textview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/expandable-textview/build.gradle:
--------------------------------------------------------------------------------
1 | import com.vanniktech.maven.publish.SonatypeHost
2 |
3 | plugins {
4 | id 'com.android.library'
5 | id 'org.jetbrains.kotlin.android'
6 | id 'kotlin-kapt'
7 | id 'com.vanniktech.maven.publish' version '0.30.0'
8 | }
9 |
10 | android {
11 | compileSdk 35
12 |
13 | defaultConfig {
14 | minSdkVersion 23
15 | targetSdkVersion 35
16 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
17 | }
18 |
19 | buildTypes {
20 | release {
21 | minifyEnabled false
22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
23 | }
24 | }
25 |
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_11
28 | targetCompatibility JavaVersion.VERSION_11
29 | }
30 |
31 | kotlinOptions {
32 | jvmTarget = '11'
33 | }
34 |
35 | buildFeatures {
36 | dataBinding true
37 | }
38 |
39 | namespace 'io.github.glailton.expandabletextview'
40 | }
41 |
42 | dependencies {
43 | implementation 'androidx.core:core-ktx:1.3.2'
44 | implementation 'androidx.appcompat:appcompat:1.2.0'
45 | implementation 'com.google.android.material:material:1.3.0'
46 | testImplementation 'junit:junit:4.+'
47 | androidTestImplementation 'androidx.test.ext:junit:1.1.2'
48 | androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
49 | }
50 |
51 | // Configuração do Maven Publish
52 | mavenPublishing {
53 | // Publicação para Maven Central via OSSRH moderno
54 | publishToMavenCentral(SonatypeHost.CENTRAL_PORTAL, true) // usa https://central.sonatype.com
55 |
56 | signAllPublications()
57 |
58 | coordinates("io.github.glailton", "expandabletextview", "1.0.7")
59 |
60 | pom {
61 | name = "ExpandableTextView"
62 | description = "An expandable Android TextView written in Kotlin."
63 | inceptionYear = "2024"
64 | url = "https://github.com/glailton/ExpandableTextView"
65 |
66 | licenses {
67 | license {
68 | name = "MIT License"
69 | url = "https://opensource.org/licenses/MIT"
70 | }
71 | }
72 |
73 | developers {
74 | developer {
75 | id = "glailton"
76 | name = "Glailton Costa"
77 | email = "glailton.rc@gmail.com"
78 | }
79 | }
80 |
81 | scm {
82 | connection = "scm:git:https://github.com/glailton/ExpandableTextView.git"
83 | developerConnection = "scm:git:https://github.com/glailton/ExpandableTextView.git"
84 | url = "https://github.com/glailton/ExpandableTextView"
85 | }
86 | }
87 | }
--------------------------------------------------------------------------------
/expandable-textview/gradle.properties:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/expandable-textview/gradle.properties
--------------------------------------------------------------------------------
/expandable-textview/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/expandable-textview/src/androidTest/java/io/github/glailton/expandabletextview/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("br.com.grsoft.expandabletextview", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/expandable-textview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/java/io/github/glailton/expandabletextview/Constants.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview
2 |
3 | class Constants {
4 | companion object {
5 | const val COLLAPSED_MAX_LINES = Int.MAX_VALUE
6 | const val DEFAULT_ANIM_DURATION = 450
7 | const val READ_MORE = "Read more"
8 | const val READ_LESS = "Read less"
9 | const val DEFAULT_ELLIPSIZED_TEXT = "… "
10 | const val EMPTY_SPACE = " "
11 | }
12 | }
--------------------------------------------------------------------------------
/expandable-textview/src/main/java/io/github/glailton/expandabletextview/ExpandableTextView.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview
2 |
3 | import android.animation.Animator
4 | import android.animation.ValueAnimator
5 | import android.app.AlertDialog
6 | import android.content.Context
7 | import android.graphics.Color
8 | import android.graphics.Rect
9 | import android.graphics.drawable.GradientDrawable
10 | import android.graphics.drawable.GradientDrawable.Orientation.BOTTOM_TOP
11 | import android.text.Spannable
12 | import android.text.SpannableString
13 | import android.text.SpannableStringBuilder
14 | import android.text.style.ForegroundColorSpan
15 | import android.text.style.UnderlineSpan
16 | import android.util.AttributeSet
17 | import android.view.View
18 | import android.view.ViewGroup
19 | import android.view.animation.DecelerateInterpolator
20 | import androidx.appcompat.widget.AppCompatTextView
21 | import androidx.databinding.BindingAdapter
22 | import io.github.glailton.expandabletextview.Constants.Companion.COLLAPSED_MAX_LINES
23 | import io.github.glailton.expandabletextview.Constants.Companion.DEFAULT_ANIM_DURATION
24 | import io.github.glailton.expandabletextview.Constants.Companion.DEFAULT_ELLIPSIZED_TEXT
25 | import io.github.glailton.expandabletextview.Constants.Companion.EMPTY_SPACE
26 | import io.github.glailton.expandabletextview.Constants.Companion.READ_LESS
27 | import io.github.glailton.expandabletextview.Constants.Companion.READ_MORE
28 | import java.lang.Integer.min
29 | import kotlin.math.max
30 | import kotlin.math.roundToInt
31 |
32 | /**
33 | * Expand the text within layout
34 | */
35 | const val EXPAND_TYPE_LAYOUT = 0
36 |
37 | /**
38 | * Expand the text as a popup
39 | */
40 | const val EXPAND_TYPE_POPUP = 1
41 |
42 | /**
43 | * Default expand type which will layout
44 | */
45 | const val EXPAND_TYPE_DEFAULT = EXPAND_TYPE_LAYOUT
46 |
47 | /**
48 | * This value define how much space should be consumed by ellipsize text to represent itself.
49 | */
50 | const val ELLIPSIZE_TEXT_LENGTH_MULTIPLIER = 2.0
51 |
52 | class ExpandableTextView @JvmOverloads constructor(
53 | context: Context,
54 | attrs: AttributeSet,
55 | defStyleAttr: Int = R.attr.expandableTextView) : AppCompatTextView(context, attrs, defStyleAttr),
56 | View.OnClickListener {
57 |
58 | private var mOriginalText: CharSequence? = ""
59 | private var mCollapsedLines = 0
60 | private var mReadMoreText: CharSequence = READ_MORE
61 | private var mReadLessText: CharSequence = READ_LESS
62 | var isExpanded: Boolean = false
63 | private set
64 | private var mAnimationDuration: Int? = 0
65 | private var foregroundColor: Int? = 0
66 | private var initialText = ""
67 | private var isUnderlined: Boolean? = false
68 | private var mEllipsizeTextColor: Int? = 0
69 | var expandType = EXPAND_TYPE_DEFAULT
70 | private set
71 | private var fadeAnimationEnabled = true
72 | private lateinit var collapsedVisibleText: String
73 |
74 | override fun onClick(v: View?) {
75 | toggleExpandableTextView()
76 | }
77 |
78 | override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
79 | super.onLayout(changed, left, top, right, bottom)
80 | if (initialText.isBlank()) {
81 | initialText = text.toString()
82 | post {
83 | collapsedVisibleText = collapsedVisibleText()
84 | //Override expand property in specific scenarios
85 | isExpanded = if (collapsedVisibleText.isAllTextVisible()) {
86 | true
87 | } else when (expandType) {
88 | EXPAND_TYPE_POPUP -> false
89 | else -> isExpanded
90 | }
91 | configureMaxLines()
92 | setEllipsizedText(isExpanded)
93 | setForeground(isExpanded)
94 | }
95 | }
96 | }
97 |
98 | private fun toggleExpandableTextView() {
99 | if (collapsedVisibleText.isAllTextVisible()) return
100 |
101 | when (expandType) {
102 | EXPAND_TYPE_LAYOUT -> animateHeightTransition()
103 | EXPAND_TYPE_POPUP -> showPopupText()
104 | else -> throw UnsupportedOperationException("No toggle operation provided for expand type[$expandType]")
105 | }
106 | }
107 |
108 |
109 | private fun configureMaxLines(){
110 | if (mCollapsedLines < COLLAPSED_MAX_LINES){
111 | maxLines = when(expandType) {
112 | EXPAND_TYPE_LAYOUT -> if (isExpanded) COLLAPSED_MAX_LINES else mCollapsedLines
113 | EXPAND_TYPE_POPUP -> mCollapsedLines
114 | else -> maxLines
115 | }
116 | }
117 | }
118 |
119 | private fun animateHeightTransition() {
120 | val expanding = !isExpanded
121 | val startHeight = height
122 |
123 | isExpanded = expanding
124 | configureMaxLines()
125 | setEllipsizedText(expanding)
126 |
127 | measure(
128 | MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
129 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
130 | )
131 | val endHeight = measuredHeight
132 |
133 | if (startHeight == endHeight) return //
134 |
135 | val duration = mAnimationDuration?.toLong() ?: DEFAULT_ANIM_DURATION.toLong()
136 |
137 | val animator = ValueAnimator.ofInt(startHeight, endHeight).apply {
138 | this.duration = duration
139 | interpolator = DecelerateInterpolator()
140 |
141 | addUpdateListener { animation ->
142 | val value = animation.animatedValue as Int
143 | layoutParams.height = value
144 | requestLayout()
145 |
146 | clipBounds = Rect(0, 0, width, value)
147 | }
148 |
149 | addListener(object : Animator.AnimatorListener {
150 | override fun onAnimationStart(animation: Animator) {
151 | visibility = View.VISIBLE
152 | if (fadeAnimationEnabled) {
153 | alpha = 0f
154 | animate().alpha(1f).setDuration(duration / 2).start()
155 | } else {
156 | alpha = 1f
157 | }
158 | }
159 |
160 | override fun onAnimationEnd(animation: Animator) {
161 | layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
162 | requestLayout()
163 | clipBounds = null
164 | }
165 |
166 | override fun onAnimationCancel(animation: Animator) {
167 | clipBounds = null
168 | }
169 |
170 | override fun onAnimationRepeat(animation: Animator) {}
171 | })
172 | }
173 |
174 | animator.start()
175 | }
176 |
177 | private fun showPopupText() {
178 | AlertDialog.Builder(context)
179 | .setTitle("")
180 | .setMessage(initialText)
181 | .setNegativeButton(android.R.string.ok, null)
182 | .show()
183 | }
184 |
185 | override fun setText(text: CharSequence?, type: BufferType?) {
186 | mOriginalText = text
187 | super.setText(text, type)
188 | }
189 |
190 | fun setReadMoreText(readMore: String): ExpandableTextView {
191 | mReadMoreText = readMore
192 | return this
193 | }
194 |
195 | fun setReadLessText(readLess: String): ExpandableTextView {
196 | mReadLessText = readLess
197 | return this
198 | }
199 |
200 | fun setCollapsedLines(collapsedLines: Int): ExpandableTextView {
201 | mCollapsedLines = collapsedLines
202 | return this
203 | }
204 |
205 | fun setIsExpanded(isExpanded: Boolean): ExpandableTextView {
206 | this.isExpanded = isExpanded
207 | return this
208 | }
209 |
210 | fun setAnimationDuration(animationDuration: Int): ExpandableTextView {
211 | mAnimationDuration = animationDuration
212 | return this
213 | }
214 |
215 | fun setIsUnderlined(isUnderlined: Boolean): ExpandableTextView {
216 | this.isUnderlined = isUnderlined
217 | return this
218 | }
219 |
220 | fun setEllipsizedTextColor(ellipsizeTextColor: Int): ExpandableTextView {
221 | mEllipsizeTextColor = ellipsizeTextColor
222 | return this
223 | }
224 |
225 | fun setForegroundColor(foregroundColor: Int): ExpandableTextView {
226 | this.foregroundColor = foregroundColor
227 | return this
228 | }
229 |
230 | fun setExpandType(expandType: Int): ExpandableTextView {
231 | this.expandType = expandType
232 | return this
233 | }
234 |
235 | fun setFadeAnimationEnabled(fadeAnimationEnabled: Boolean): ExpandableTextView {
236 | this.fadeAnimationEnabled = fadeAnimationEnabled
237 | return this
238 | }
239 |
240 | fun toggle() {
241 | toggleExpandableTextView()
242 | }
243 |
244 | init {
245 | context.obtainStyledAttributes(attrs, R.styleable.ExpandableTextView).apply {
246 | try {
247 | mCollapsedLines = getInt(R.styleable.ExpandableTextView_collapsedLines, COLLAPSED_MAX_LINES)
248 | mAnimationDuration = getInt(R.styleable.ExpandableTextView_animDuration, DEFAULT_ANIM_DURATION)
249 | mReadMoreText = getString(R.styleable.ExpandableTextView_readMoreText) ?: READ_MORE
250 | mReadLessText = getString(R.styleable.ExpandableTextView_readLessText) ?: READ_LESS
251 | foregroundColor = getColor(R.styleable.ExpandableTextView_foregroundColor, Color.TRANSPARENT)
252 | isUnderlined = getBoolean(R.styleable.ExpandableTextView_isUnderlined, false)
253 | isExpanded = getBoolean(R.styleable.ExpandableTextView_isExpanded, false)
254 | mEllipsizeTextColor = getColor(R.styleable.ExpandableTextView_ellipsizeTextColor, Color.BLUE)
255 | expandType = getInt(R.styleable.ExpandableTextView_expandType, EXPAND_TYPE_DEFAULT)
256 | fadeAnimationEnabled = getBoolean(R.styleable.ExpandableTextView_fadeAnimationEnabled, true)
257 |
258 | } finally {
259 | this.recycle()
260 | }
261 | }
262 |
263 | configureMaxLines()
264 | setOnClickListener(this)
265 | }
266 |
267 | private fun setEllipsizedText(isExpanded: Boolean) {
268 | if (initialText.isBlank())
269 | return
270 |
271 | text = if (collapsedVisibleText.isAllTextVisible()){
272 | initialText
273 | } else{
274 | when(expandType){
275 | EXPAND_TYPE_POPUP -> getCollapseText()
276 | EXPAND_TYPE_LAYOUT -> if (isExpanded) getExpandText() else getCollapseText()
277 | else -> throw UnsupportedOperationException("No supported expand mechanism provided for expand type[$expandType]")
278 | }
279 | }
280 | }
281 |
282 | private fun getExpandText(): SpannableStringBuilder {
283 | return SpannableStringBuilder(initialText)
284 | .append(EMPTY_SPACE)
285 | .append(mReadLessText.toString().span())
286 | }
287 |
288 | private fun getCollapseText(): SpannableStringBuilder {
289 |
290 | val ellipseTextLength = ((mReadMoreText.length + DEFAULT_ELLIPSIZED_TEXT.length) * ELLIPSIZE_TEXT_LENGTH_MULTIPLIER).roundToInt()
291 | val textAvailableLength = max(0, collapsedVisibleText.length - ellipseTextLength)
292 | val ellipsizeAvailableLength = min(collapsedVisibleText.length, DEFAULT_ELLIPSIZED_TEXT.length)
293 | val readMoreAvailableLength = min(collapsedVisibleText.length - ellipsizeAvailableLength, mReadMoreText.length)
294 |
295 | return SpannableStringBuilder(collapsedVisibleText.substring(0, textAvailableLength))
296 | .append(DEFAULT_ELLIPSIZED_TEXT.substring(0, ellipsizeAvailableLength))
297 | .append(mReadMoreText.substring(0, readMoreAvailableLength).span())
298 | }
299 |
300 | private fun collapsedVisibleText(): String {
301 | try {
302 | var finalTextOffset = 0
303 | if (mCollapsedLines < COLLAPSED_MAX_LINES) {
304 | for (i in 0 until mCollapsedLines) {
305 | val textOffset = layout.getLineEnd(i)
306 | if (textOffset == initialText.length)
307 | return initialText
308 | else
309 | finalTextOffset = textOffset
310 | }
311 | return initialText.substring(0, finalTextOffset)
312 | }else {
313 | return initialText
314 | }
315 | }catch (e: Exception){
316 | e.printStackTrace()
317 | return initialText
318 | }
319 | }
320 |
321 | private fun setForeground(isExpanded: Boolean) {
322 | foreground = GradientDrawable(BOTTOM_TOP, intArrayOf(foregroundColor!!, Color.TRANSPARENT))
323 | foreground.alpha = if (isExpanded) {
324 | MIN_VALUE_ALPHA
325 | } else {
326 | MAX_VALUE_ALPHA
327 | }
328 | }
329 |
330 | private fun String.isAllTextVisible(): Boolean = this == text.toString()
331 |
332 | private fun String.span(): SpannableString =
333 | SpannableString(this).apply {
334 | setSpan(
335 | ForegroundColorSpan(mEllipsizeTextColor!!),
336 | 0,
337 | this.length,
338 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
339 | )
340 | if (isUnderlined!!)
341 | setSpan(
342 | UnderlineSpan(),
343 | 0,
344 | this.length,
345 | Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
346 | )
347 | }
348 |
349 | companion object {
350 | const val MAX_VALUE_ALPHA = 255
351 | const val MIN_VALUE_ALPHA = 0
352 | }
353 | }
354 |
355 | @BindingAdapter("readMoreText")
356 | fun ExpandableTextView.readMoreText(readMore: String) {
357 | this.setReadMoreText(readMore)
358 | }
359 |
360 | @BindingAdapter("readLessText")
361 | fun ExpandableTextView.readLessText(readLess: String) {
362 | this.setReadLessText(readLess)
363 | }
364 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/res/values/attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFBB86FC
4 | #FF6200EE
5 | #FF3700B3
6 | #FF03DAC5
7 | #FF018786
8 | #FF000000
9 | #FFFFFFFF
10 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | ExpandableTextView
3 |
--------------------------------------------------------------------------------
/expandable-textview/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/expandable-textview/src/test/java/io/github/glailton/expandabletextview/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package io.github.glailton.expandabletextview
2 |
3 | import org.junit.Test
4 |
5 | import org.junit.Assert.*
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * See [testing documentation](http://d.android.com/tools/testing).
11 | */
12 | class ExampleUnitTest {
13 | @Test
14 | fun addition_isCorrect() {
15 | assertEquals(4, 2 + 2)
16 | }
17 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app"s APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Automatically convert third-party libraries to use AndroidX
19 | android.enableJetifier=true
20 | # Kotlin code style for this project: "official" or "obsolete":
21 | kotlin.code.style=official
22 | android.defaults.buildfeatures.buildconfig=true
23 | android.nonTransitiveRClass=false
24 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Mar 22 22:49:52 BRT 2021
2 | distributionBase=GRADLE_USER_HOME
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
4 | distributionPath=wrapper/dists
5 | zipStorePath=wrapper/dists
6 | zipStoreBase=GRADLE_USER_HOME
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/publish-mavencentral.gradle:
--------------------------------------------------------------------------------
1 | mavenPublishing {
2 | publishToMavenCentral(com.vanniktech.maven.publish.SonatypeHost.CENTRAL_PORTAL)
3 |
4 | coordinates("io.github.glailton", "expandabletextview", "1.0.5")
5 |
6 | pom {
7 | name = "ExpandableTextView"
8 | description = "An expandable Android TextView written in Kotlin."
9 | inceptionYear = "2024"
10 | url = "https://github.com/glailton/ExpandableTextView"
11 |
12 | licenses {
13 | license {
14 | name = "MIT License"
15 | url = "https://opensource.org/licenses/MIT"
16 | }
17 | }
18 |
19 | developers {
20 | developer {
21 | id = "glailton"
22 | name = "Glailton Costa"
23 | email = "glailton.rc@gmail.com"
24 | }
25 | }
26 |
27 | scm {
28 | connection = "scm:git:https://github.com/glailton/ExpandableTextView.git"
29 | developerConnection = "scm:git:https://github.com/glailton/ExpandableTextView.git"
30 | url = "https://github.com/glailton/ExpandableTextView"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/resources/gif.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/glailton/ExpandableTextView/a742011793521cd9af650adf30e3b72308336577/resources/gif.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | plugins {
8 | id "com.vanniktech.maven.publish" version "0.25.3"
9 | id 'org.jetbrains.kotlin.android' version '1.9.20'
10 | }
11 | }
12 | dependencyResolutionManagement {
13 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
14 | repositories {
15 | google()
16 | mavenCentral()
17 | }
18 | }
19 | rootProject.name = "ExpandableTextView Demo"
20 | include ':app'
21 | include ':expandable-textview'
22 |
--------------------------------------------------------------------------------