Skip to content

Instantly share code, notes, and snippets.

@Hamza417
Created April 6, 2021 03:34
Show Gist options
  • Save Hamza417/5293017fe4dd31ed15a50c7ee3dfc28a to your computer and use it in GitHub Desktop.
Save Hamza417/5293017fe4dd31ed15a50c7ee3dfc28a to your computer and use it in GitHub Desktop.
plugins {
id 'com.android.library'
id 'kotlin-android'
}
android {
compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
package app.simple.switchview
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("app.simple.switchview.test", appContext.packageName)
}
}
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="app.simple.switchview">
</manifest>
package app.simple.switchview.callbacks;
public interface SwitchCallbacks {
void onCheckedChanged(boolean isChecked);
}
package app.simple.switchview.utils
import android.animation.ValueAnimator
import android.content.Context
import android.content.res.ColorStateList
import android.view.ViewGroup
import android.view.animation.DecelerateInterpolator
import android.widget.TextView
import androidx.annotation.AttrRes
import androidx.annotation.ColorInt
import androidx.appcompat.widget.AppCompatButton
import com.google.android.material.animation.ArgbEvaluatorCompat
object ColorUtils {
fun ViewGroup.animateColorChange(endColor: Int) {
val colorAnim = ValueAnimator.ofObject(ArgbEvaluatorCompat(), this.backgroundTintList?.defaultColor, endColor)
colorAnim.duration = 1000
colorAnim.interpolator = DecelerateInterpolator(1.5F)
colorAnim.addUpdateListener { animation -> this.backgroundTintList = ColorStateList.valueOf(animation.animatedValue as Int) }
colorAnim.start()
}
}
package app.simple.switchview.utils
import android.content.Context
import android.view.View
import android.view.WindowManager
import androidx.core.content.ContextCompat
import app.simple.switchview.R
object ViewUtils {
/**
* Dim the background when PopupWindow shows
* Should be called from showAsDropDown function
* because this is when container's parent is
* initialized
*/
fun dimBehind(contentView: View) {
val container = contentView.rootView
val windowManager = contentView.context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val layoutParams = container.layoutParams as WindowManager.LayoutParams
layoutParams.flags = layoutParams.flags or WindowManager.LayoutParams.FLAG_DIM_BEHIND
layoutParams.dimAmount = 0.3f
windowManager.updateViewLayout(container, layoutParams)
}
// @RequiresApi(28)
/**
* Adds outline shadows to the view using the accent color
* of the app
*
* @param contentView [View] that needs to be elevated with colored
* shadow
*/
fun addShadow(contentView: View) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {
contentView.outlineAmbientShadowColor = ContextCompat.getColor(contentView.context, R.color.switch_on)
contentView.outlineSpotShadowColor = ContextCompat.getColor(contentView.context, R.color.switch_on)
}
}
/**
* Makes the view go away
*/
fun View.makeGoAway() {
this.visibility = View.GONE
}
/**
* Makes the view come back
*/
fun View.makeVisible() {
this.visibility = View.VISIBLE
}
}
package app.simple.switchview.views
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Color
import android.util.AttributeSet
import android.widget.FrameLayout
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.MaterialShapeDrawable
import com.google.android.material.shape.ShapeAppearanceModel
open class SwitchFrameLayout @JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {
init {
backgroundTintList = ColorStateList.valueOf(Color.TRANSPARENT)
val shapeAppearanceModel = ShapeAppearanceModel()
.toBuilder()
.setAllCorners(CornerFamily.ROUNDED, 100F)
.build()
background = MaterialShapeDrawable(shapeAppearanceModel)
}
}
package app.simple.switchview.views
import android.animation.ValueAnimator
import android.annotation.SuppressLint
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.animation.DecelerateInterpolator
import android.view.animation.OvershootInterpolator
import android.widget.ImageView
import androidx.core.content.ContextCompat
import app.simple.switchview.R
import app.simple.switchview.callbacks.SwitchCallbacks
import app.simple.switchview.utils.ColorUtils.animateColorChange
import app.simple.switchview.utils.ViewUtils
@SuppressLint("ClickableViewAccessibility")
class SwitchView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0)
: SwitchFrameLayout(context, attrs, defStyleAttr) {
private var thumb: ImageView
private var track: SwitchFrameLayout
private var switchCallbacks: SwitchCallbacks? = null
var isCheckable = true
/**
*
*/
var isChecked: Boolean = false
set(value) {
if (value) {
animateChecked()
if (isCheckable) {
switchCallbacks?.onCheckedChanged(true)
}
} else {
animateUnchecked()
switchCallbacks?.onCheckedChanged(false)
}
field = value
}
init {
val view = LayoutInflater.from(context).inflate(R.layout.switch_view, this, true)
thumb = view.findViewById(R.id.switch_thumb)
track = view.findViewById(R.id.switch_track)
ViewUtils.addShadow(track)
view.setOnClickListener {
isChecked = !isChecked
}
}
override fun onTouchEvent(event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
thumb.animate()
.scaleY(1.5F)
.scaleX(1.5F)
.setInterpolator(DecelerateInterpolator(1.5F))
.setDuration(500L)
.start()
}
MotionEvent.ACTION_MOVE,
MotionEvent.ACTION_UP,
-> {
thumb.animate()
.scaleY(1.0F)
.scaleX(1.0F)
.setInterpolator(DecelerateInterpolator(1.5F))
.setDuration(500L)
.start()
}
}
return super.onTouchEvent(event)
}
private fun animateUnchecked() {
thumb.animate()
.translationX(0F)
.setInterpolator(OvershootInterpolator(3F))
.setDuration(500)
.start()
track.animateColorChange(ContextCompat.getColor(context, R.color.switch_off))
animateElevation(0F)
}
private fun animateChecked() {
if (!isCheckable) return
val w = context.resources.getDimensionPixelOffset(R.dimen.switch_width)
val p = context.resources.getDimensionPixelOffset(R.dimen.switch_padding)
val thumbWidth = context.resources.getDimensionPixelOffset(R.dimen.switch_thumb_dimensions)
thumb.animate()
.translationX((w - p * 2 - thumbWidth).toFloat())
.setInterpolator(OvershootInterpolator(3F))
.setDuration(500)
.start()
track.animateColorChange(ContextCompat.getColor(context, R.color.switch_on))
animateElevation(25F)
}
private fun animateElevation(elevation: Float) {
val valueAnimator = ValueAnimator.ofFloat(track.elevation, elevation)
valueAnimator.duration = 500L
valueAnimator.interpolator = DecelerateInterpolator(1.5F)
valueAnimator.addUpdateListener {
track.elevation = it.animatedValue as Float
}
valueAnimator.start()
}
fun setOnCheckedChangeListener(switchCallbacks: SwitchCallbacks) {
this.switchCallbacks = switchCallbacks
}
}
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval"
android:tint="@android:color/white" />
<?xml version="1.0" encoding="utf-8"?>
<app.simple.switchview.views.SwitchFrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/switch_track"
android:layout_width="@dimen/switch_width"
android:layout_height="wrap_content"
android:backgroundTint="@color/switch_off"
android:clipChildren="false"
android:clipToPadding="false"
android:elevation="10dp"
android:padding="@dimen/switch_padding">
<ImageView
android:id="@+id/switch_thumb"
android:layout_width="@dimen/switch_thumb_dimensions"
android:layout_height="@dimen/switch_thumb_dimensions"
android:layout_gravity="center_vertical"
android:src="@drawable/switch_thumb"
tools:ignore="ContentDescription" />
</app.simple.switchview.views.SwitchFrameLayout>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Switch -->
<color name="switch_on">#2196F3</color>
<color name="switch_off">#EAEAEA</color>
</resources>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Switch -->
<dimen name="switch_width">50dp</dimen>
<dimen name="switch_padding">5dp</dimen>
<dimen name="switch_thumb_dimensions">20dp</dimen>
</resources>
package app.simple.switchview
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment