Merge pull request #352 from KryptKode/feature/shadow-drawables

implement shadow drawables programmatically
This commit is contained in:
Tibor Kaputa 2022-10-10 21:01:55 +02:00 committed by GitHub
commit e817bc3325
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 276 additions and 14 deletions

View File

@ -13,7 +13,6 @@ import android.os.Looper
import android.provider.MediaStore
import android.view.*
import android.widget.LinearLayout
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import androidx.core.view.*
import androidx.transition.*
@ -29,6 +28,7 @@ import com.simplemobiletools.camera.R
import com.simplemobiletools.camera.extensions.config
import com.simplemobiletools.camera.extensions.fadeIn
import com.simplemobiletools.camera.extensions.fadeOut
import com.simplemobiletools.camera.extensions.setShadowIcon
import com.simplemobiletools.camera.extensions.toFlashModeId
import com.simplemobiletools.camera.helpers.*
import com.simplemobiletools.camera.implementations.CameraXInitializer
@ -366,10 +366,19 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
last_photo_video_preview.setOnClickListener { showLastMediaPreview() }
toggle_flash.setOnClickListener { toggleFlash() }
shutter.setOnClickListener { shutterPressed() }
settings.setShadowIcon(R.drawable.ic_settings_vector)
settings.setOnClickListener { launchSettings() }
change_resolution.setOnClickListener { mPreview?.showChangeResolution() }
flash_on.setShadowIcon(R.drawable.ic_flash_on_vector)
flash_on.setOnClickListener { selectFlashMode(FLASH_ON) }
flash_off.setShadowIcon(R.drawable.ic_flash_off_vector)
flash_off.setOnClickListener { selectFlashMode(FLASH_OFF) }
flash_auto.setShadowIcon(R.drawable.ic_flash_auto_vector)
flash_auto.setOnClickListener { selectFlashMode(FLASH_AUTO) }
}
@ -394,7 +403,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private fun toggleFlash() {
if (checkCameraAvailable()) {
if (mIsInPhotoMode) {
showFlashOptions(mIsInPhotoMode)
showFlashOptions(true)
} else {
mPreview?.toggleFlashlight()
}
@ -408,7 +417,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
FLASH_ON -> R.drawable.ic_flash_on_vector
else -> R.drawable.ic_flash_auto_vector
}
toggle_flash.icon = AppCompatResources.getDrawable(this, flashDrawable)
toggle_flash.setShadowIcon(flashDrawable)
toggle_flash.transitionName = "${getString(R.string.toggle_flash)}$state"
}
@ -551,7 +560,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
private fun hasPhotoModePermissions(): Boolean {
return if (isTiramisuPlus()) {
hasPermission(PERMISSION_READ_MEDIA_IMAGES) && hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA)
hasPermission(PERMISSION_READ_MEDIA_IMAGES) && hasPermission(PERMISSION_READ_MEDIA_VIDEO) && hasPermission(PERMISSION_CAMERA)
} else {
hasPermission(PERMISSION_WRITE_STORAGE) && hasPermission(PERMISSION_CAMERA)
}
@ -622,7 +631,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
toggle_flash.beVisible()
} else {
toggle_flash.beInvisible()
toggle_flash.icon = AppCompatResources.getDrawable(this, R.drawable.ic_flash_off_vector)
toggle_flash.setShadowIcon(R.drawable.ic_flash_off_vector)
mPreview?.setFlashlightState(FLASH_OFF)
}
}
@ -741,7 +750,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
override fun displaySelectedResolution(resolutionOption: ResolutionOption) {
val imageRes = resolutionOption.imageDrawableResId
change_resolution.icon = AppCompatResources.getDrawable(this, imageRes)
change_resolution.setShadowIcon(imageRes)
change_resolution.transitionName = "${resolutionOption.buttonViewId}"
}
@ -765,9 +774,8 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
onSelect.invoke(index, selectedResolution.buttonViewId != clickedViewId)
}
resolutions.map {
createButton(it, onItemClick)
}.forEach { button ->
resolutions.forEach {
val button = createButton(it, onItemClick)
mediaSizeToggleGroup.addView(button)
}
@ -787,7 +795,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
}
return (layoutInflater.inflate(R.layout.layout_button, null) as MaterialButton).apply {
layoutParams = params
icon = AppCompatResources.getDrawable(context, resolutionOption.imageDrawableResId)
setShadowIcon(resolutionOption.imageDrawableResId)
id = resolutionOption.buttonViewId
transitionName = "${resolutionOption.buttonViewId}"
setOnClickListener {
@ -811,7 +819,7 @@ class MainActivity : SimpleActivity(), PhotoProcessor.MediaSavedListener, Camera
flash_toggle_group.check(config.flashlightState.toFlashModeId())
flash_toggle_group.beVisible()
flash_toggle_group.children.map { it as MaterialButton }.forEach(::setButtonColors)
flash_toggle_group.children.forEach { setButtonColors(it as MaterialButton) }
}
private fun setButtonColors(button: MaterialButton) {

View File

@ -0,0 +1,10 @@
package com.simplemobiletools.camera.extensions
import androidx.annotation.DrawableRes
import com.google.android.material.button.MaterialButton
import com.simplemobiletools.camera.R
import com.simplemobiletools.camera.views.ShadowDrawable
fun MaterialButton.setShadowIcon(@DrawableRes drawableResId: Int) {
icon = ShadowDrawable(context, drawableResId, R.style.TopIconShadow)
}

View File

@ -0,0 +1,228 @@
package com.simplemobiletools.camera.views
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.*
import android.graphics.drawable.Drawable
import androidx.annotation.DrawableRes
import androidx.annotation.StyleRes
import androidx.core.content.ContextCompat
import com.simplemobiletools.camera.R
import kotlin.math.ceil
import kotlin.math.roundToInt
class ShadowDrawable(context: Context, private val drawable: Drawable, @StyleRes styleResId: Int) : Drawable() {
companion object {
private const val SHARED_BITMAP_BUFFER_SIZE = 640
private val sharedDrawableBitmapBuffer = ThreadLocal<Bitmap>()
private val sharedDrawableCanvasBuffer = ThreadLocal<Canvas>()
}
private val bitmapPaint = Paint()
private val contentBounds = Rect()
private val destRect = Rect()
private val noRadiusShadowBounds = Rect()
private val shadowBounds = Rect()
private val shadowPaint = Paint()
private val srcRect = Rect()
private val unionBounds = Rect()
private var outputBuffer: Bitmap? = null
private var outputBufferCanvas: Canvas? = null
private var paddingBottom = 0
private var paddingEnd = 0
private var paddingStart = 0
private var paddingTop = 0
private var shadowColor = 0
private var shadowDx = 0
private var shadowDy = 0
private var shadowRadiusCeiling = 0
constructor(context: Context, @DrawableRes drawableRes: Int, @StyleRes styleResId: Int) : this(
context,
ContextCompat.getDrawable(
context,
drawableRes,
)!!,
styleResId
)
init {
drawable.callback = object : Callback {
override fun unscheduleDrawable(drawable2: Drawable, runnable: Runnable) {
unscheduleSelf(runnable)
}
override fun scheduleDrawable(drawable2: Drawable, runnable: Runnable, j: Long) {
scheduleSelf(runnable, j)
}
override fun invalidateDrawable(drawable2: Drawable) {
invalidateSelf()
}
}
if (styleResId != 0) {
val obtainStyledAttributes = context.obtainStyledAttributes(styleResId, R.styleable.ShadowDrawable)
shadowColor =
obtainStyledAttributes.getColor(R.styleable.ShadowDrawable_android_shadowColor, ContextCompat.getColor(context, R.color.md_grey_400_dark))
shadowDx = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowDx, 0f).toInt()
shadowDy = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowDy, 0f).toInt()
val shadowRadius = obtainStyledAttributes.getFloat(R.styleable.ShadowDrawable_android_shadowRadius, 0.0f).coerceAtLeast(0.0f)
shadowRadiusCeiling = ceil(shadowRadius).toInt()
val alpha = Color.alpha(shadowColor)
if (shadowRadius > 0.0f && alpha > 0) {
val blurMaskFilter = BlurMaskFilter(shadowRadius, BlurMaskFilter.Blur.NORMAL)
shadowPaint.alpha = alpha
shadowPaint.maskFilter = blurMaskFilter
} else {
shadowPaint.alpha = 0
}
obtainStyledAttributes.recycle()
}
shadowPaint.isAntiAlias = true
shadowPaint.isFilterBitmap = true
shadowPaint.isAntiAlias = true
shadowPaint.isFilterBitmap = true
}
override fun draw(canvas: Canvas) {
val createBitmap: Bitmap
val canvas2: Canvas
val bounds = bounds
contentBounds[bounds.left + paddingStart, bounds.top + paddingTop, bounds.right - paddingEnd] =
bounds.bottom - paddingBottom
if (contentBounds.width() <= 0 || contentBounds.height() <= 0) {
return
}
if (shadowPaint.alpha == 0) {
drawable.bounds = contentBounds
drawable.draw(canvas)
return
}
if (contentBounds.width() <= SHARED_BITMAP_BUFFER_SIZE && contentBounds.height() <= SHARED_BITMAP_BUFFER_SIZE) {
var sharedBitmap = sharedDrawableBitmapBuffer.get()
if (sharedBitmap != null) {
sharedBitmap.eraseColor(0)
} else {
sharedBitmap = Bitmap.createBitmap(SHARED_BITMAP_BUFFER_SIZE, SHARED_BITMAP_BUFFER_SIZE, Bitmap.Config.ARGB_8888)
sharedDrawableBitmapBuffer.set(sharedBitmap)
sharedDrawableCanvasBuffer.set(Canvas(sharedBitmap!!))
}
createBitmap = sharedBitmap
canvas2 = sharedDrawableCanvasBuffer.get()!!
} else {
createBitmap = Bitmap.createBitmap(
contentBounds.width(),
contentBounds.height(),
Bitmap.Config.ARGB_8888
)
canvas2 = Canvas(createBitmap)
}
drawable.setBounds(0, 0, contentBounds.width(), contentBounds.height())
drawable.draw(canvas2)
shadowBounds.set(contentBounds)
shadowBounds.offset(shadowDx, shadowDy)
noRadiusShadowBounds.set(shadowBounds)
val i = shadowRadiusCeiling
shadowBounds.inset(-i, -i)
unionBounds.set(bounds)
unionBounds.union(contentBounds)
unionBounds.union(shadowBounds)
var outputBuffer = this.outputBuffer
if (outputBuffer == null || unionBounds.width() > outputBuffer.width || unionBounds.height() > outputBuffer.height) {
outputBuffer = Bitmap.createBitmap(
unionBounds.width(),
unionBounds.height(),
Bitmap.Config.ARGB_8888
)
this.outputBuffer = outputBuffer
this.outputBufferCanvas = Canvas(outputBuffer)
} else {
outputBuffer.eraseColor(0)
}
srcRect[0, 0, contentBounds.width()] = contentBounds.height()
destRect.set(noRadiusShadowBounds)
destRect.offset(-unionBounds.left, -unionBounds.top)
outputBufferCanvas!!.drawBitmap(createBitmap.extractAlpha(), srcRect, destRect, shadowPaint)
destRect.set(contentBounds)
destRect.offset(-unionBounds.left, -unionBounds.top)
outputBufferCanvas!!.drawBitmap(createBitmap, srcRect, destRect, bitmapPaint)
srcRect[0, 0, unionBounds.width()] = unionBounds.height()
destRect.set(unionBounds)
canvas.drawBitmap(outputBuffer!!, srcRect, destRect, bitmapPaint)
}
@Deprecated("Deprecated in Java", ReplaceWith("PixelFormat.TRANSLUCENT", "android.graphics.PixelFormat"))
override fun getOpacity(): Int {
return drawable.opacity
}
override fun setTint(tintColor: Int) {
drawable.setTint(tintColor)
}
override fun setTintList(tint: ColorStateList?) {
drawable.setTintList(tint)
}
override fun setTintMode(tintMode: PorterDuff.Mode?) {
drawable.setTintMode(tintMode)
}
override fun getColorFilter(): ColorFilter? {
return drawable.colorFilter
}
override fun getState(): IntArray {
return drawable.state
}
override fun getIntrinsicHeight(): Int {
return drawable.intrinsicHeight + paddingTop + paddingBottom
}
override fun getIntrinsicWidth(): Int {
return drawable.intrinsicWidth + paddingStart + paddingEnd
}
override fun isStateful(): Boolean {
return drawable.isStateful
}
override fun onLevelChange(i: Int): Boolean {
return drawable.setLevel(i)
}
override fun getAlpha(): Int {
return drawable.alpha
}
override fun setAlpha(i: Int) {
drawable.alpha = i
shadowPaint.alpha = (Color.alpha(shadowColor) / 255.0f * i).roundToInt()
}
override fun setAutoMirrored(z: Boolean) {
drawable.isAutoMirrored = z
}
override fun setColorFilter(colorFilter: ColorFilter?) {
drawable.colorFilter = colorFilter
}
fun setPaddings(start: Int, top: Int, end: Int, bottom: Int) {
paddingStart = start
paddingTop = top
paddingEnd = end
paddingBottom = bottom
invalidateSelf()
}
override fun setState(iArr: IntArray): Boolean {
return drawable.setState(iArr)
}
}

View File

@ -11,7 +11,7 @@
tools:visibility="visible">
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/flash_off"
style="@style/Widget.App.Button.OutlineButton.IconOnly"
android:layout_width="0dp"
@ -19,7 +19,7 @@
android:layout_weight="1"
app:icon="@drawable/ic_flash_off_vector" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/flash_auto"
style="@style/Widget.App.Button.OutlineButton.IconOnly"
android:layout_width="0dp"
@ -27,7 +27,7 @@
android:layout_weight="1"
app:icon="@drawable/ic_flash_auto_vector" />
<Button
<com.google.android.material.button.MaterialButton
android:id="@+id/flash_on"
style="@style/Widget.App.Button.OutlineButton.IconOnly"
android:layout_width="0dp"

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ShadowDrawable">
<attr name="android:shadowColor" format="color" />
<attr name="android:shadowDx" format="float" />
<attr name="android:shadowDy" format="float" />
<attr name="android:shadowRadius" format="float" />
</declare-styleable>
</resources>

View File

@ -24,4 +24,11 @@
<item name="android:minHeight">@dimen/icon_size</item>
</style>
<style name="TopIconShadow">
<item name="android:shadowColor">@color/md_grey_200_dark</item>
<item name="android:shadowDx">0</item>
<item name="android:shadowDy">2</item>
<item name="android:shadowRadius">10</item>
</style>
</resources>