Merge pull request #352 from KryptKode/feature/shadow-drawables
implement shadow drawables programmatically
This commit is contained in:
commit
e817bc3325
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue