Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/Devlight/CornerCutLinearLayout

Linear Layout that allow corner (parent and children) cuts, complex shadow and divider.
https://github.com/Devlight/CornerCutLinearLayout

android android-library android-ui kotlin kotlin-android kotlin-library library ui

Last synced: about 2 months ago
JSON representation

Linear Layout that allow corner (parent and children) cuts, complex shadow and divider.

Awesome Lists containing this project

README

        

# CornerCutLinearLayout

[![](https://jitpack.io/v/Devlight/CornerCutLinearLayout.svg)](https://jitpack.io/#Devlight/CornerCutLinearLayout) [![Android Arsenal]( https://img.shields.io/badge/Android%20Arsenal-CornerCutLinearLayout-green.svg?style=flat )]( https://android-arsenal.com/details/1/8111 )

`CornerCutLinearLayout` extends [`LinearLayout`](https://developer.android.com/reference/android/widget/LinearLayout). It allows cutting parent corners with different shapes and build proper shadow for complex shapes.\
It also allows cutting each child's corners.

Developed by [Devlight](https://www.devlight.io/) company.

Additionally, using available properties and custom providers, those cuts may be turned into cutouts of different shapes, sizes, etc.\
Widget's sole purpose is to use with children with no transformations (like rotation, scale, matrix transformations).

Amongst additional features:
- RTL support
- child layout parameters that allow overriding default parent parameters
- custom shadow
- custom dividers & providers
- custom cutouts & providers
- custom view visible area provider

# Installation

**Step 1.** Add the JitPack repository to your project's `build.gradle` file:

```groovy
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
```

or

```groovy
subprojects {
repositories {
...
maven {
...
url = "https://jitpack.io"
}
}
}
```

**Step 2.** Add the following dependency to your target module's `build.gradle` file:

```groovy
dependencies {
implementation 'com.github.Devlight:CornerCutLinearLayout:1.0.5'
}
```

# Usage

For simple quick usage that covers most use cases, see **Basics** section below.\
For more complex usage section **Advanced** might be useful.

# Basics

#### Declaration in XML

All widget attributes start with `ccll_` prefix. Children's layout attributes starts with `layout_ccll_` prefix, respectively. There are plenty of attributes. In order to facilitate their usage, they separated into few categories with start prefix:
- `ccll_` or `ccll_corner_cut` - global widget attributes
- `ccll_child_` - global child cut attributes
- `ccll_custom_shadow` - custom shadow attributes
- `ccll_custom_divider` - custom divider attributes

```xml

```

#### Declaration in Code (Kotlin)

All XML attributes correspond to `CornerCutLinearLayout`'s property or function.

```kotlin
with(ccll_kotlin_synthetic_view) {
val density = resources.displayMetrics.density
cornerCutFlag = combineFlags(CornerCutFlag.START_TOP, CornerCutFlag.END_BOTTOM)
setCornerCutSize(density * 24)
setCornerCutType(CornerCutType.OVAL)
setChildCornerCutType(CornerCutType.OVAL_INVERSE)
customShadowColor = Color.parseColor("#FEB545")
customShadowRadius = density * 16
}
```
Visual result would be follow:

### Corner Cut Anatomy

By default `CornerCutType.OVAL` is used for both parent corner and child cuts. Corner cut are bounded to its personal dimensions - **depth** and **length**.

**Depth** - relative to orientation width of the cutout bounds.\
**Length** - relative to orientation height of the cutout bounds.

Each of 4 parent corner cuts dimensions could be specified individually.\
Children's corner cuts could have separate dimensions for `ChildSideCutFlag.START` and `ChildSideCutFlag.END` sides. Children's corner cuts could also be rotated. The rotation angle could optionally be mirrored.

As you may notice, parent corner cuts are purely bounded to corners, but child corner cut bounds are "mirrored". Indeed, each child corner cut forms a mirrored path. This strategy was chosen in order for children could separately override the contact part of the corner cut.

Also, note that **depth** and **length** depends on widget's layout direction (`LinearLayout.LAYOUT_DIRECTION_LTR` or `LinearLayout.LAYOUT_DIRECTION_RTL`) and orientation (`LinearLayout.VERTICAL` or `LinearLayout.HORIZONTAL`).

### Corner Cut Types

There are 5 default corner cut types for parent and children corners\*.

1) Oval.

2) Oval Inverse.

3) Rectangle.\*\*

4) Rectangle Inverse.\*\*

5) Bevel.

>\* - Each child corner type, in fact, is mirrored and combined into a path from the respective corner cut types of contact children.\
\*\* - Rectangle types support an internal corner radius. There are respective attributes and view properties for both parent and child cuts.

### Layout Parameters

Each child can override parent defined properties and attributes related to the corner cuts.

In the examples below, parent `CornerCutLinearLayout` has `ccll_child_corner_cut_type` = `oval_inverse` and the middle children override each corner (different types)

```xml

...

...

```

#### Edge Child
There also special layout params for the first and last child. For example in a vertical orientation, when the top and bottom children are not aligned to parent top and bottom respectively they can override **contact cut\*** with the parent.

```xml


...

```

In case edge child is aligned to the respective side, they could optionally override parent corner cut type\*\*.

```xml


...

```

>\* - By default specified `ccll_child_corner_cut_type` is used for child-parent contact if not overridden by edge child.\
\*\* - Note that only type of parent corner cut type is overridden, while other properties (depth, length, etc.) are stay preserved.

### Extra Child Corner Cut Properties

There are next extra child corner cut properties:
- Depth & Length Offset
- Corner Cut Rotation

#### Depth & Length Offset

Each side of child corner cuts could have different depth and length offset (see anatomy above).

**Example 1 - Depth Offset**

```xml

...

...

```

**Example 2 - Depth & Length Offset**
```xml


```

#### Rotation
Each side could have its corner cuts rotated be specified degree.
Corresponding attributes are:
- `ccll_child_start_side_corner_cut_rotation_degree`
- `ccll_child_end_side_corner_cut_rotation_degree`

Also, it might be necessary to keep the same mirrored corner cut angle for the both sides. For such purposes attribute `ccll_is_child_corner_cut_end_rotation_mirrored_from_start_rotation` might be helpful.

Each attribute has its corresponding `CornerCutLinearLayout`'s property and/or convenience function.

```xml

...

```

### Shadow
One of the main problem of [Android's shadow](https://developer.android.com/training/material/shadows-clipping) is that path must be [convex](https://developer.android.com/reference/android/graphics/Path#isConvex()).

>\* - A path is convex if it has a single contour, and only ever curves in a single direction.

This widget allows bypass this limitation by automatically building complex shadow (event with cutouts). Of course, shadow is custom and has its pros and cons.

**Pros**:
- Shadow has custom properties, such as offset & color (ARGB).
- Supports complex non convex path.

**Cons**:
- Shadow is artificial compared to native elevation shadow's nature. Thus, you cannot rely on global source an light position and elevation parameter.
- Shadow uses view's area (padding), which you should keep in mind during layout process or dynamic change of shadow radius.
- Shadow does NOT depend on view's or children's background and their transparencies, thus cannot be a composite shadow with the overlays of different levels of transparency (opacity).

By default shadow are build upon parent padded area combined with all cutouts data. It means that shadow does NOT depend on view's background, child presence or child's background. But this behavior could be changed by `CustomViewAreaProvider` (see **Advanced** section).

**Shadow Padding**.\
You could also enable custom shadow auto padding (`ccll_is_custom_shadow_auto_padding_enabled`), allow or prevent custom shadow over user defined padding. (`ccll_could_draw_custom_shadow_over_user_defined_padding`). Last attribute works only in conjunction with enabled first attribute.

Examples:

### Custom Divider
Custom Divider has similar anatomy and properties as default's LinearLayout divider.
Custom dividers does not change view's dimension unlike default's LinearLayout divider did (last adds space at specified by flag position equal to divider's width or height). Custom diviers are drown over view and default dividers. You can combine default and custom divider.

Custom dividers have several advantages though:
- additional show flags
- line caps (ROUND, BUTT, SQUARE)
- seperate start an end padding
- dashed line divider (width and gap)
- gravity of dashed divider (`CustomDividerGravity`: `START`, `CENTER`, `END`)
- custom divider provider (see **Advanced** section)

**Show Flags** (`CustomDividerShowFlag`):
- `container_beginning` - at view beginning
- `beginning` - between contact of first view's margin and parent.
- `middle` - between children contact margins
- `end` - between contact of last view's margin and parent.
- `container_end` - at view end

>By default custom dividers are not taken into consideration when shadow are build.

Custom Divider attributes:

```xml

...

```

Examples:

# Advanced
Sometimes you might want to have even more complex visible area, divider, cutouts, etc.
For such purposes there are custom providers for aforementioned subjects. All of them could be specified programatically. For your convenience, there are also a Kotlin lambda-style functions for the most of the providers.

### Corner Cut Provider
`CornerCutProvider` allows to override each of 4 widget's corners.\
Let's look at an examples.

**Example 1**.

1. As usual define our view at xml (this scenario) or create & setup it programatically.
```xml

```
2. Set a `CornerCutProvider`.
```kotlin
ccll_corner_cut_provider.setCornerCutProvider { view, cutout, cutCorner, rectF ->
when (cutCorner) {
CornerCutFlag.START_TOP -> {
rectF.inset(inset, inset) // inset - globally defined property
cutout.moveTo(rectF.left, rectF.top)
cutout.lineTo(rectF.right, rectF.top)
cutout.lineTo(rectF.left, rectF.bottom)
true // accept left top corner
}

CornerCutFlag.END_BOTTOM -> {
// complex pacman path
...
true // accept right bottom corner
}

else -> false // skip the rest of the corners and treat them by default settings
}
}
```

Here, we simply override left top and right bottom corner cuts with the custom ones. They are accepted by returning `true` as a last statement, otherwise respective corner would be handled by its default settings (if any).

When is necessary to compose `cutout` path with nested cutout pathes, use either `Path.addPath()` function (with different `fillType`) or `Path.op()` function with different `Path.Op` modes.

The result would be as follow:

**Example 2**.
In some scenario you might need transform your cutout path (scale, rotate, skew, etc). For this purposes there is also optional function `getTransformationMatrix()` of `CornerCutProvider` interface.\
In this example, there are 3 simple views with different background color below `CornerCutLinearLayout` with `CornerCutProvider`.

Also, as you can see from the image below, you could achieve animated effects by calling public function `invalidateCornerCutPath()` whenever it is necessary to update your view after changing your custom cutout relative values.

Aforementioned views become visible through cutout. Moreover shadow is also properly drawn around cutout. And this cutout exceeds its recommended by `rectF` bounds. For such purposes better use `CustomCutoutProvider`.

```kotlin
ccll_corner_cut_example_2.setCornerCutProvider(
{ view, _, _, _ ->
val matrix = Matrix()
val pb = view.paddedBounds
matrix.postRotate(currentRotationAngle, pb.centerX(), pb.centerY())
matrix // returns matrix that will be applied to cutout path. Null by default
},

{ view, cutout, cutCorner, rectF ->
when (cutCorner) {
CornerCutLinearLayout.CornerCutFlag.START_TOP -> {
with(view.paddedBounds) {
cutout.addRect(
centerX() - rectF.width() / 2.0F,
centerY() - rectF.height() / 2.0F,
centerX() + rectF.width() / 2.0F,
centerY() + rectF.height() / 2.0F,
Path.Direction.CW
)
}
true
}

else -> false
}
}
)
```

### Child Corner Cut Provider
`ChildCornerCutProvider` is almost the same as `CornerCutProvider`. Instead of `cutCorner` it has `cutEdge` and additionally possible contact children - `relativeCutTopChild` & `relativeCutBottomChild`.

**Example 1**.

```xml

...

```

```kotlin
ccll_child_cut_provider_example_1.setChildCornerCutProvider { view, cutout, _, rectF, _, _ ->
with(cutout) {
moveTo(rectF.centerX(), rectF.top)
arcTo(...)
lineTo(rectF.centerX() + rectF.width(), rectF.bottom)
arcTo(...)
lineTo(rectF.centerX(), rectF.top)
val halfChordWidth = rectF.height() / 2.0F
addCircle(...)
moveTo(rectF.centerX() + rectF.width(), rectF.top)
lineTo(view.paddedBounds.right - rectF.width() / 2.0F, rectF.centerY())
lineTo(rectF.centerX() + rectF.width(), rectF.bottom)
lineTo(rectF.centerX() + rectF.width(), rectF.top)
}
true // accept custom cutout
}
```

The result would be as follow:

> Note that in this example only left side corner cuts are build with custom cutout provider. This is because providers is only called for `cutSide`'s & `cutCorner`'s specified by `ccll_child_side_cut_flag` & `ccll_corner_cut_flag`, respectively.

**Example 2**.
```xml

...

```
```kotlin
ccll_showcase_custom_child_cut_provider_mixed.setChildCornerCutProvider(
{ _, _, cutSide, rectF, _, _ ->
val matrix = Matrix()
when (cutSide) {
CornerCutLinearLayout.ChildSideCutFlag.START -> {
matrix.postSkew(-0.25F, 0.0F, rectF.centerX(), rectF.centerY())
}
CornerCutLinearLayout.ChildSideCutFlag.END -> {
matrix.postRotate(-10.0F, rectF.centerX(), rectF.centerY())
matrix.postTranslate(-rectF.width() / 2.0F, 0.0F)
}
}
matrix // return transformation matrix that will be applied over previously defined cutouts
},

{ view, cutout, cutSide, rectF, relativeCutTopChild, _ ->
when (cutSide) {
CornerCutLinearLayout.ChildSideCutFlag.START -> {
if (view.indexOfChild(relativeCutTopChild ?: return@setChildCornerCutProvider false) != 1) return@setChildCornerCutProvider false
// cutout star path
true // accept path for only 2 (index 1) start side cutout
}
CornerCutLinearLayout.ChildSideCutFlag.END -> {
if (view.indexOfChild(relativeCutTopChild ?: return@setChildCornerCutProvider false) != 0) return@setChildCornerCutProvider false
// cutout star path
true // accept path for only 1 (index 0) end side cutout
}
else -> false
}
}
)
```

The result would be as follow:

### Custom Cutout Provider
This type of provider (`CustomCutoutProvider`) is similar to previous cut providers. The only difference is that you could add many cutout providers and the `rectF` parameter in a both interface's functions return view's padded bounds.

### Custom View Area Provider
`CustomViewAreaProvider` might be useful in case you need to show custom area of view. Posibilities are only limited by your imagination and Android hardware. May require little knowledge of path composition, path op modes, fill types, etc.

**Example 1**.
```xml

```
```kotlin
ccll_custom_view_area_provider_example_1.setCustomViewAreaProvider { view, path, rectF ->
// properties
val offset = view[0].marginEnd
val cornerRadius = rectF.height() / 4.0F
val tailCircleRadius = cornerRadius / 2.0F
val innerTailCircleRadius = tailCircleRadius / 2.0F
val smallCornerRadius = cornerRadius / 4.0F

// left part: round rect
path.addRoundRect(...)

// right part: tail
path.moveTo(rectF.right - offset, rectF.top + cornerRadius)
path.arcTo(...)
path.lineTo(rectF.right - tailCircleRadius, rectF.centerY() - innerTailCircleRadius)
path.lineTo(rectF.right - offset + innerTailCircleRadius, rectF.centerY() - innerTailCircleRadius)
path.arcTo(...)
path.lineTo(rectF.right - tailCircleRadius, rectF.centerY() + innerTailCircleRadius)
path.arcTo(...)
path.lineTo(rectF.right - offset, rectF.top + cornerRadius)
path.addCircle(rectF.right - tailCircleRadius, rectF.centerY(), tailCircleRadius, Path.Direction.CW)
path.addCircle(rectF.right - tailCircleRadius, rectF.centerY(), innerTailCircleRadius, Path.Direction.CCW)
}
```

The result would be as follow:

**Example 2**.
```xml


...

```

In this example `CornerCutLinearLayout` has nested `CornerCutLinearLayout` (first two children) and simple view (3rd child).\
**First child** is transformed (constantly running animation): rotated around x, y & z sequentially.\
**Second child** is continuously animated (translation Y) as well. It also has its own `CustomCutoutProvider` in a form of a star.\
**Third child** is a regular view with semi-transparent background.

Also parent `CustomCutoutProvider` has `CustomCutoutProvider` set programatically (curved lines path). In this examples we want to build shadow upon only children visible area also modifying some of them (virtual corner cuts for 3rd child).

```kotlin
// 1. Add Custom Cutout Provider
val waveLineCutWidth = resources.getDimension(R.dimen.offset_12)
val waveLineHeight = resources.getDimension(R.dimen.offset_48)
val halfWaveLineHeight = waveLineHeight / 2.0F
val halfWaveLineCutWidth = waveLineCutWidth / 2.0F
ccll_custom_view_area_provider_example_2.addCustomCutoutProvider { _, cutout, rectF ->
cutout.moveTo(rectF.left, rectF.centerY() - halfWaveLineCutWidth)
cutout.lineTo(rectF.left + rectF.width() / 4.0F, rectF.centerY() - halfWaveLineCutWidth - halfWaveLineHeight)
cutout.lineTo(rectF.right - rectF.width() / 4.0F, rectF.centerY() - halfWaveLineCutWidth + halfWaveLineHeight)
cutout.lineTo(rectF.right, rectF.centerY() - halfWaveLineCutWidth)
cutout.lineTo(rectF.right, rectF.centerY() + halfWaveLineCutWidth)
cutout.lineTo(rectF.right - rectF.width() / 4.0F, rectF.centerY() + halfWaveLineCutWidth + halfWaveLineHeight)
cutout.lineTo(rectF.left + rectF.width() / 4.0F, rectF.centerY() + halfWaveLineCutWidth - halfWaveLineHeight)
cutout.lineTo(rectF.left, rectF.centerY() + halfWaveLineCutWidth)
cutout.lineTo(rectF.left, rectF.centerY() - halfWaveLineCutWidth)
}

// 2. Set Custom View Area Provider
ccll_showcase_custom_view_area_provider_example_2.setCustomViewAreaProvider { view, path, _ ->
view.forEach {
tempPath.rewind()
if (it is CornerCutLinearLayout) {
tempPath.offset(-it.left.toFloat(), -it.top.toFloat())
tempPath.addPath(it.viewAreaPath) // nested ccll visible area path
tempPath.transform(it.matrix)
tempPath.offset(it.left.toFloat(), it.top.toFloat())
} else {
tempRectF.set(it.left.toFloat(), it.top.toFloat(), it.right.toFloat(), it.bottom.toFloat())
val childCornerRadius = min(tempRectF.width(), tempRectF.height()) / 6.0F
tempPath.addRoundRect(tempRectF, childCornerRadius, childCornerRadius, Path.Direction.CW)
tempPath.offset(-it.left.toFloat(), -it.top.toFloat())
tempPath.transform(it.matrix)
tempPath.offset(it.left.toFloat(), it.top.toFloat())
}
path.op(tempPath, Path.Op.UNION)
}
}
```

Note that 3rd child is clipped virtually. So when its bound overlay another visible area bounds corners of 3rd child become visible.

As you see custom shadow are build correctly upon custom visible view area (including children cutouts and transformations) & global custom cutouts.

The result would be as follow:

When you nest `CornerCutLinearLayout` in another `CornerCutLinearLayout` and work with `CustomViewAreaProvider` it might be necessary to get current visible view area path. The copy of it could be obtained via `CornerCutLinearLayout.viewAreaPath`\*. In similar manner widget's padded bounds could be obtained (`CornerCutLinearLayout.paddedBounds`).

>\* - custom shadow are build upon `viewAreaPath`.

### Custom Divider Provider
Sometimes you might want to have some not trivial different dividers at different positions mixed with default dividers. In a such scenario, `CustomDividerProvider` might come handy.

**Example 1**.
```xml

...

```

Then we define globally divider paint:

```kotlin
ccll_custom_divider_provider_example_1.doOnNonNullSizeLayout {
val pb = it.paddedBounds
it.customDividerProviderPaint.shader = RadialGradient(
pb.centerX(), pb.centerY(),
hypot(pb.width() / 2.0F, pb.height() / 2.0F) * 0.8F,
Color.BLACK, Color.WHITE,
Shader.TileMode.CLAMP
)
}
```

Lastly, we add `CustomDividerProvider`:

```kotlin
ccll_custom_divider_provider_example_1.setCustomDividerProvider { _, dividerPath, dividerPaint, showDividerFlag, dividerTypeIndex, rectF ->
when (showDividerFlag) {
CornerCutLinearLayout.CustomDividerShowFlag.CONTAINER_BEGINNING -> {
dividerPaint.style = Paint.Style.STROKE
dividerPaint.strokeWidth = triangleHeight
dividerPaint.pathEffect = PathDashPathEffect(topDividerTrianglePath, triangleBaseWidth, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.top)
dividerPath.lineTo(rectF.right, rectF.top)
}

CornerCutLinearLayout.CustomDividerShowFlag.MIDDLE -> {
dividerPaint.style = Paint.Style.STROKE
if (dividerTypeIndex == 0) {
dividerPaint.strokeWidth = circleRadius
dividerPaint.pathEffect = PathDashPathEffect(circleDotDividerPath, triangleBaseWidth, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.centerY())
dividerPath.lineTo(rectF.right + triangleBaseWidth, rectF.centerY())
} else {
dividerPaint.strokeWidth = circleRadius
dividerPaint.pathEffect = PathDashPathEffect(diamondDotDividerPath, triangleBaseWidth, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.centerY())
dividerPath.lineTo(rectF.right + triangleBaseWidth, rectF.centerY())
}
}

CornerCutLinearLayout.CustomDividerShowFlag.CONTAINER_END -> {
dividerPaint.style = Paint.Style.STROKE
dividerPaint.strokeWidth = triangleHeight
dividerPaint.pathEffect = PathDashPathEffect(bottomDividerTrianglePath, triangleBaseWidth, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.top)
dividerPath.lineTo(rectF.right, rectF.top)
}
}
true // accept divider path
}
```

The result would be as follow:

**Example 2**.
This example shows the combination of custom and default's dividers.

```xml

...

```
```kotlin
ccll_custom_divider_provider_example_2.setCustomDividerProvider { _, dividerPath, dividerPaint, showDividerFlag, dividerTypeIndex, rectF ->
when (showDividerFlag) {
CornerCutLinearLayout.CustomDividerShowFlag.MIDDLE -> {
dividerPaint.style = Paint.Style.STROKE
when (dividerTypeIndex) {
0 -> {
dividerPaint.shader = RadialGradient(
rectF.centerX(), rectF.centerY(),
rectF.width() / 2.0F, Color.GREEN, Color.RED,
Shader.TileMode.MIRROR
)
dividerPaint.strokeWidth = circleRadius
dividerPaint.pathEffect = PathDashPathEffect(diamondDotDividerPath, triangleBaseWidth, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.centerY())
dividerPath.lineTo(rectF.right + triangleBaseWidth, rectF.centerY())
return@setCustomDividerProvider true // accept divider and draw it
}

2 -> {
dividerPaint.shader = LinearGradient(
rectF.centerX(), rectF.centerY() - halfWaveHeight,
rectF.centerX(), rectF.centerY() + halfWaveHeight,
Color.BLUE, Color.YELLOW, Shader.TileMode.CLAMP
)
dividerPaint.strokeWidth = halfWaveHeight * 2.0F
dividerPaint.pathEffect = PathDashPathEffect( wavePath, halfWaveWidth * 2.0F, 0.0F, PathDashPathEffect.Style.TRANSLATE)
dividerPath.moveTo(rectF.left, rectF.centerY())
dividerPath.lineTo(rectF.right, rectF.centerY())
return@setCustomDividerProvider true // accept divider and draw it
}

else -> return@setCustomDividerProvider false // skip divider and draw default
}
}
}
false // skip divider and draw default
}
```

The result would be as follow:

## Sample App
In order to take closer look over examples provided and library in general, please run the sample app.

## Author
Created by [Mykola Melnyk](https://github.com/XtreeIvI) - [@xtreeivi](mailto:[email protected])

### Donation
I would appreciate if you "toss the coin to your faithful servant".\
Details: **5432 5912 5316 4615** (MasterCard)

## Company
[![Facebook](https://drive.google.com/uc?export=download&id=0BxPO_UeS7wScaGhGVFNKU0VxMnc)](https://www.facebook.com/devlightagency)     [![Twitter](https://drive.google.com/uc?export=download&id=0BxPO_UeS7wScZ1ExQWh5cHF5cVE)](https://twitter.com/DevLightIO)     [![LinkedIn](https://drive.google.com/uc?export=download&id=0BxPO_UeS7wSccGZINzEycE1nVFE)](https://www.linkedin.com/company/devlight)

## Created by [Mykola Melnyk](https://github.com/XtreeIvI) - [@xtreeivi](mailto:[email protected])
[Here](https://github.com/DevLight) you can see open source works developed by Devlight LLC.
This and another works is an exclusive property of Devlight LLC.

If you want to use this library in applications which will be available on Google Play, please report us or author of the library about it.

Whether you're searching for a new partner or trusted team for creating your new great product we are always ready to start work with you.

You can contact us: [email protected] or [email protected].
Thanks in advance.

Devlight LLC, 2020
[devlight.io](http://devlight.io)

## License
Please see [LICENSE](/LICENSE.txt)