Skip to content

Commit

Permalink
Merge pull request #28 from ericktijerou/feature/textview-length
Browse files Browse the repository at this point in the history
Add TextView length support
  • Loading branch information
ericktijerou authored Feb 2, 2021
2 parents f6bf2e8 + 4f42d95 commit acc5733
Show file tree
Hide file tree
Showing 18 changed files with 339 additions and 20 deletions.
15 changes: 12 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ You can [download](https://bintray.com/ericktijerou/maven/koleton/_latestVersion
```gradle
// In your module's `build.gradle.kts`
dependencies {
implementation("com.ericktijerou.koleton:koleton:0.8.2")
implementation("com.ericktijerou.koleton:koleton:0.8.5")
}
```

Expand All @@ -38,24 +38,33 @@ repositories {
To load the skeleton of a `View`, use the `loadSkeleton` extension function:

```kotlin
// Any View
// ConstraintLayout
constraintLayout.loadSkeleton()

// TextView
textView?.loadSkeleton(length = 20)

// RecyclerView
recyclerView.loadSkeleton(R.layout.item_example)
```

Skeletons can be configured with an optional trailing lambda:

```kotlin
// Any View
// ConstraintLayout
constraintLayout.loadSkeleton {
color(R.color.colorSkeleton)
cornerRadius(radiusInPixel)
shimmer(false)
lineSpacing(spacingInPixel)
}

// TextView
textView?.loadSkeleton(length = 20) {
color(R.color.colorSkeleton)
...
}

// RecyclerView
recyclerView.loadSkeleton(R.layout.item_example) {
itemCount(3)
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ compileSdk=29
groupId=com.ericktijerou.koleton
vcsUrl=https://github.com/ericktijerou/koleton
issueTrackerUrl=https://github.com/ericktijerou/koleton/issues
publishVersion=0.8.2
publishVersion=0.8.5
20 changes: 20 additions & 0 deletions koleton-base/src/main/kotlin/koleton/MainSkeletonLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import koleton.memory.DelegateService
import koleton.memory.SkeletonService
import koleton.skeleton.RecyclerViewSkeleton
import koleton.skeleton.Skeleton
import koleton.skeleton.TextViewSkeleton
import koleton.skeleton.ViewSkeleton
import koleton.target.RecyclerViewTarget
import koleton.target.SimpleViewTarget
import koleton.target.TextViewTarget
import koleton.target.ViewTarget
import koleton.util.*
import kotlinx.coroutines.*
Expand Down Expand Up @@ -90,6 +92,24 @@ internal class MainSkeletonLoader(
return when (skeleton) {
is RecyclerViewSkeleton -> generateRecyclerView(skeleton)
is ViewSkeleton -> generateSimpleView(skeleton)
is TextViewSkeleton -> generateTextView(skeleton)
}
}

private fun generateTextView(skeleton: TextViewSkeleton) = with(skeleton) {
return@with if (target is TextViewTarget) {
val attributes = TextViewAttributes(
view = target.view,
color = context.getColorCompat(colorResId ?: defaults.colorResId),
cornerRadius = cornerRadius ?: defaults.cornerRadius,
isShimmerEnabled = isShimmerEnabled ?: defaults.isShimmerEnabled,
shimmer = shimmer ?: defaults.shimmer,
lineSpacing = lineSpacing ?: defaults.lineSpacing,
length = length
)
target.view.generateTextKoletonView(attributes)
} else {
TextKoletonView(context)
}
}

Expand Down
13 changes: 12 additions & 1 deletion koleton-base/src/main/kotlin/koleton/custom/Attributes.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package koleton.custom

import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.annotation.LayoutRes
import androidx.annotation.Px
Expand Down Expand Up @@ -31,4 +32,14 @@ data class SimpleViewAttributes(
override val isShimmerEnabled: Boolean,
override val shimmer: Shimmer,
override val lineSpacing: Float
): Attributes()
): Attributes()

data class TextViewAttributes(
val view: TextView,
@ColorInt override val color: Int,
@Px override val cornerRadius: Float,
override val isShimmerEnabled: Boolean,
override val shimmer: Shimmer,
override val lineSpacing: Float,
val length: Int
): Attributes()
100 changes: 100 additions & 0 deletions koleton-base/src/main/kotlin/koleton/custom/TextKoletonView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package koleton.custom

import android.content.Context
import android.graphics.Canvas
import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import koleton.mask.KoletonMask
import koleton.util.*

internal class TextKoletonView @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : KoletonView(context, attrs, defStyleAttr) {

private var koletonMask: KoletonMask? = null
override var isSkeletonShown: Boolean = false
private var isMeasured: Boolean = false
private val viewList = arrayListOf<View>()

var attributes: TextViewAttributes? = null
set(value) {
field = value
originalText = value?.view?.text.toString()
value?.let { applyAttributes() }
}

private var originalText: String = EMPTY_STRING

private fun hideVisibleChildren(view: View) {
when (view) {
is ViewGroup -> view.children().forEach { hideVisibleChildren(it) }
else -> hideVisibleChild(view)
}
}

private fun hideVisibleChild(view: View) {
if (view.isVisible()) {
view.invisible()
viewList.add(view)
}
}

override fun showSkeleton() {
isSkeletonShown = true
setFakeText()
if (isMeasured && childCount > 0) {
hideVisibleChildren(this)
applyAttributes()
}
}

override fun hideSkeleton() {
isSkeletonShown = false
restoreOriginalText()
if (childCount > 0) {
viewList.forEach { it.visible() }
hideShimmer()
koletonMask = null
}
}

private fun setFakeText() {
attributes?.apply {
view.text = getRandomAlphaNumericString(length)
}
}

private fun restoreOriginalText() {
attributes?.apply {
view.text = originalText
}
}

override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
isMeasured = width > NUMBER_ZERO && height > NUMBER_ZERO
if (isSkeletonShown) {
showSkeleton()
}
}

override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.let { koletonMask?.draw(it) }
}

override fun applyAttributes() {
if (isMeasured) {
attributes?.let { attrs ->
if (!attrs.isShimmerEnabled) {
hideShimmer()
} else {
setShimmer(attrs.shimmer)
}
koletonMask = KoletonMask(this, attrs.color, attrs.cornerRadius, attrs.lineSpacing)
}
}
}

}
4 changes: 2 additions & 2 deletions koleton-base/src/main/kotlin/koleton/mask/KoletonMask.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package koleton.mask

import android.graphics.*
import android.graphics.text.LineBreaker
import android.os.Build
import android.text.Layout
import android.text.StaticLayout
import android.text.TextPaint
import android.view.View
Expand Down Expand Up @@ -78,7 +78,7 @@ internal class KoletonMask(
val spannable = spannable { background(color, cornerRadius, lineSpacingPerLine, view.text) }
val staticLayout = StaticLayout.Builder
.obtain(spannable, 0, spannable.length, textPaint.apply { color = Color.TRANSPARENT }, view.width)
.setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
.setBreakStrategy(LineBreaker.BREAK_STRATEGY_SIMPLE)
.setIncludePad(view.includeFontPadding)
.setMaxLines(view.lineCount)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import koleton.SkeletonLoader
import koleton.annotation.ExperimentalKoletonApi
import koleton.skeleton.RecyclerViewSkeleton
import koleton.skeleton.Skeleton
import koleton.skeleton.TextViewSkeleton
import koleton.skeleton.ViewSkeleton
import koleton.target.ViewTarget
import koleton.util.koletonManager
Expand Down Expand Up @@ -33,7 +34,7 @@ internal class DelegateService(
): SkeletonDelegate? {
val skeletonDelegate: SkeletonDelegate
when (skeleton) {
is ViewSkeleton, is RecyclerViewSkeleton -> when (val target = skeleton.target) {
is ViewSkeleton, is RecyclerViewSkeleton, is TextViewSkeleton -> when (val target = skeleton.target) {
is ViewTarget<*> -> {
skeletonDelegate = ViewTargetSkeletonDelegate(
imageLoader = imageLoader,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import koleton.lifecycle.GlobalLifecycle
import koleton.lifecycle.LifecycleCoroutineDispatcher
import koleton.skeleton.RecyclerViewSkeleton
import koleton.skeleton.Skeleton
import koleton.skeleton.TextViewSkeleton
import koleton.skeleton.ViewSkeleton
import koleton.target.Target
import koleton.target.ViewTarget
Expand All @@ -19,7 +20,7 @@ internal class SkeletonService {
@MainThread
fun lifecycleInfo(skeleton: Skeleton): LifecycleInfo {
when (skeleton) {
is ViewSkeleton, is RecyclerViewSkeleton -> {
is ViewSkeleton, is RecyclerViewSkeleton, is TextViewSkeleton -> {
val lifecycle = skeleton.getLifecycle()
return if (lifecycle != null) {
val mainDispatcher = LifecycleCoroutineDispatcher
Expand Down
87 changes: 87 additions & 0 deletions koleton-base/src/main/kotlin/koleton/skeleton/Skeleton.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package koleton.skeleton

import android.content.Context
import android.view.View
import android.widget.TextView
import androidx.annotation.ColorRes
import androidx.annotation.LayoutRes
import androidx.annotation.MainThread
Expand All @@ -15,6 +16,7 @@ import koleton.custom.KoletonView
import koleton.target.RecyclerViewTarget
import koleton.target.SimpleViewTarget
import koleton.target.Target
import koleton.target.TextViewTarget

/**
* The base class for a skeleton view.
Expand Down Expand Up @@ -243,4 +245,89 @@ class RecyclerViewSkeleton internal constructor(
)
}
}
}

class TextViewSkeleton internal constructor(
override val context: Context,
override val target: Target?,
override val lifecycle: Lifecycle?,
@ColorRes override val colorResId: Int?,
@Px override val cornerRadius: Float?,
override val isShimmerEnabled: Boolean?,
override val shimmer: Shimmer?,
override val lineSpacing: Float?,
internal val length: Int
) : Skeleton() {

/** Create a new [Builder] instance using this as a base. */
@JvmOverloads
fun newBuilder(context: Context = this.context) = Builder(this, context)

class Builder : SkeletonBuilder<Builder> {

private var target: Target?
private var lifecycle: Lifecycle?
private var length: Int

constructor(context: Context, length: Int) : super(context) {
target = null
lifecycle = null
this.length = length
}

@JvmOverloads
constructor(
skeleton: TextViewSkeleton,
context: Context = skeleton.context
) : super(skeleton, context) {
target = skeleton.target
lifecycle = skeleton.lifecycle
length = skeleton.length
}

/**
* Convenience function to set [view] as the [Target].
*/
fun target(view: TextView) = apply {
target(TextViewTarget(view))
}

/**
* Convenience function to create and set the [Target].
*/
inline fun target(
crossinline onStart: () -> Unit = {},
crossinline onError: () -> Unit = {},
crossinline onSuccess: (skeleton: KoletonView) -> Unit = {}
) = target(object : Target {
override fun onStart() = onStart()
override fun onError() = onError()
override fun onSuccess(skeleton: KoletonView) = onSuccess(skeleton)
})

fun target(target: Target?) = apply {
this.target = target
}

fun lifecycle(lifecycle: Lifecycle?) = apply {
this.lifecycle = lifecycle
}

/**
* Create a new [ViewSkeleton] instance.
*/
fun build(): TextViewSkeleton {
return TextViewSkeleton(
context,
target,
lifecycle,
colorResId,
cornerRadius,
isShimmerEnabled,
shimmer,
lineSpacing,
length
)
}
}
}
21 changes: 21 additions & 0 deletions koleton-base/src/main/kotlin/koleton/target/TextViewTarget.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package koleton.target

import android.widget.TextView
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import koleton.custom.KoletonView

/** A [Target] that handles setting skeleton on a [TextView]. */
open class TextViewTarget(override val view: TextView) : ViewTarget<TextView>, DefaultLifecycleObserver {

override fun onStart() = Unit

/** Show the [skeleton] view */
override fun onSuccess(skeleton: KoletonView) = skeleton.showSkeleton()

override fun onError() = Unit

override fun onStart(owner: LifecycleOwner) {}

override fun onStop(owner: LifecycleOwner) {}
}
Loading

0 comments on commit acc5733

Please sign in to comment.