Skip to content

Commit

Permalink
feat: add group view to sync multiple SkelletonView class
Browse files Browse the repository at this point in the history
  • Loading branch information
iagormoraes committed Apr 18, 2021
1 parent d0b10c0 commit 74844fd
Show file tree
Hide file tree
Showing 12 changed files with 334 additions and 139 deletions.
51 changes: 48 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ A skelleton solution done in native view, with it you can wrap any view componen

## Installation

##### NPM
```sh
npm install react-native-skelleton
```

##### Yarn
```sh
yarn add react-native-skelleton
```

## Usage

```typescript jsx
Expand All @@ -32,6 +38,35 @@ render() {
}
```

### With SkelletonGroupView

```typescript jsx
import SkelletonView, { SkelletonGroupView } from 'react-native-skelleton';

render() {
return (
<SkelletonGroupView
duration={1000}
interpolator="AccelerateDecelerateInterpolator"
style={style}
>
<SkelletonView
color="#888888"
repeatCount={-1}
repeatMode={2}
style={{
...style,
backgroundColor: '#C3C3C3',
}}
/>
</SkelletonGroupView>
)
}
```
This component accepts any children component class, it will search recursively for the SkelletonView class and animate them.

⚠️ Use this if you to animate a group of skeletons in sync, without it they will not be in sync as the timer is not controlled by the AnimatorSet.

## Props and types

```typescript
Expand All @@ -52,9 +87,18 @@ export type SkelletonInterpolator =
| 'LinearInterpolator'
| 'OvershootInterpolator';

export type SkelletonGroupProps = {
children?: React.ReactElement | React.ReactElement[];
duration?: number;
startDelay?: number;
interpolator?: SkelletonInterpolator;
style?: ViewStyle;
};

export type SkelletonProps = {
children?: React.ReactElement;
color: string;
children?: React.ReactElement | React.ReactElement[];
color?: string;
autoStart?: boolean;
duration?: number;
startDelay?: number;
repeatDelay?: number;
Expand All @@ -67,7 +111,8 @@ export type SkelletonProps = {

## Known Issues

- animation between views are not 100% yet due to current synchronous render of react components.
- animation between views that are not wrapped with SkelletonGroupView is not 100% in sync.
- repeatDelay only works without using SkelletonGroupView.

## Contributing

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.reactnativeskelleton

import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.os.Handler
import android.util.Log
import android.view.View
import android.view.ViewGroup
import android.view.animation.*
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup
import kotlin.collections.ArrayList


@SuppressLint("ViewConstructor")
class SkelletonGroupView(reactContext: ThemedReactContext): ReactViewGroup(reactContext) {
private val animatorSet = AnimatorSet()
private val objectAnimatorList = ArrayList<ObjectAnimator>()

private fun buildAnimatorSet() {
Handler().post {
objectAnimatorList.clear()

getSkelletonViewAnimator(this)

animatorSet.playTogether(objectAnimatorList.toList())

animatorSet.start()
}
}

private fun getSkelletonViewAnimator(view: View) {
if (view !is ViewGroup) { return }

for (i in 0 until view.childCount) {
val child = view.getChildAt(i)

getSkelletonViewAnimator(child)
}

if(view is SkelletonView) {
objectAnimatorList.add(view.objectAnimator)
}
}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)

buildAnimatorSet()
}

fun setDuration(duration: Int) {
animatorSet.duration = duration.toLong()
}

fun setStartDelay(delay: Int) {
animatorSet.startDelay = delay.toLong()
}

fun setInterpolator(interpolator: String) {
animatorSet.interpolator = when(interpolator) {
"AccelerateDecelerateInterpolator" -> AccelerateDecelerateInterpolator()
"AccelerateInterpolator" -> AccelerateInterpolator()
"AnticipateInterpolator" -> AnticipateInterpolator()
"AnticipateOvershootInterpolator" -> AnticipateOvershootInterpolator()
"BounceInterpolator" -> BounceInterpolator()
"CycleInterpolator" -> CycleInterpolator(1f)
"DecelerateInterpolator" -> DecelerateInterpolator()
"LinearInterpolator" -> LinearInterpolator()
"OvershootInterpolator" -> OvershootInterpolator()

else -> AccelerateDecelerateInterpolator()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,6 @@ class SkelletonPackage : ReactPackage {
}

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
return listOf(SkelletonViewManager())
return listOf(SkelletonViewManager(), SkelletonGroupViewManager())
}
}
106 changes: 106 additions & 0 deletions android/src/main/java/com/reactnativeskelleton/SkelletonView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package com.reactnativeskelleton

import android.animation.Animator
import android.animation.ObjectAnimator
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.os.Handler
import android.view.animation.*
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.view.ReactViewGroup

@SuppressLint("ViewConstructor")
class SkelletonView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
private val startInitialPosition = -9999f
private val endInitialPosition = 0f
private var repeatDelay: Long = 0
private var measured = false

var objectAnimator: ObjectAnimator = ObjectAnimator.ofFloat(this, "translationX", startInitialPosition, endInitialPosition)

init {
// set initial position to maintain the user experience
translationX = startInitialPosition

objectAnimator.addListener(SkelletonAnimatorListener())
}

override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {}

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)

objectAnimator.setFloatValues(-(measuredWidth + 1).toFloat(), (measuredWidth + 1).toFloat())

measured = true
}

fun setDuration(duration: Int) {
objectAnimator.duration = duration.toLong()
}

fun setStartDelay(delay: Int) {
objectAnimator.startDelay = delay.toLong()
}

fun setRepeatDelay(delay: Int) {
repeatDelay = delay.toLong()
}

fun setRepeatCount(count: Int) {
objectAnimator.repeatCount = count
}

fun setRepeatMode(mode: Int) {
val repeatMode = when(mode) {
ValueAnimator.RESTART -> ValueAnimator.RESTART
else -> ValueAnimator.REVERSE
}

objectAnimator.repeatMode = repeatMode
}

fun setInterpolator(interpolator: String) {
objectAnimator.interpolator = when(interpolator) {
"AccelerateDecelerateInterpolator" -> AccelerateDecelerateInterpolator()
"AccelerateInterpolator" -> AccelerateInterpolator()
"AnticipateInterpolator" -> AnticipateInterpolator()
"AnticipateOvershootInterpolator" -> AnticipateOvershootInterpolator()
"BounceInterpolator" -> BounceInterpolator()
"CycleInterpolator" -> CycleInterpolator(1f)
"DecelerateInterpolator" -> DecelerateInterpolator()
"LinearInterpolator" -> LinearInterpolator()
"OvershootInterpolator" -> OvershootInterpolator()

else -> AccelerateDecelerateInterpolator()
}
}

fun setAutoStart(autoStart: Boolean) {
if(measured) return

if(autoStart) {
objectAnimator.cancel()
objectAnimator.start()
}
}

private inner class SkelletonAnimatorListener: Animator.AnimatorListener {
override fun onAnimationRepeat(animator: Animator?) {
animator?.let {
if(repeatDelay > 0) {
it.pause()

Handler().postDelayed(it::resume, repeatDelay)
}
}
}

override fun onAnimationEnd(animator: Animator?) {}

override fun onAnimationCancel(animator: Animator?) {}

override fun onAnimationStart(animator: Animator?) {}

}
}
Loading

0 comments on commit 74844fd

Please sign in to comment.