aboutsummaryrefslogtreecommitdiff
path: root/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views
diff options
context:
space:
mode:
authorAllan Wang <me@allanwang.ca>2017-07-08 20:25:23 -0700
committerAllan Wang <me@allanwang.ca>2017-07-08 20:25:23 -0700
commit81996038462de1be86643e95d262933c4b96c551 (patch)
tree39423b28217251e7051f86e9e4f80c47bcba20b2 /core-ui/src/main/kotlin/ca/allanwang/kau/ui/views
parent880d433e475e5be4e5d91afac145b490f9a959b7 (diff)
downloadkau-81996038462de1be86643e95d262933c4b96c551.tar.gz
kau-81996038462de1be86643e95d262933c4b96c551.tar.bz2
kau-81996038462de1be86643e95d262933c4b96c551.zip
Move components to separate modules
Diffstat (limited to 'core-ui/src/main/kotlin/ca/allanwang/kau/ui/views')
-rw-r--r--core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/BoundedCardView.kt47
-rw-r--r--core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt183
2 files changed, 230 insertions, 0 deletions
diff --git a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/BoundedCardView.kt b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/BoundedCardView.kt
new file mode 100644
index 0000000..554f71f
--- /dev/null
+++ b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/BoundedCardView.kt
@@ -0,0 +1,47 @@
+package ca.allanwang.kau.ui.views
+
+import android.content.Context
+import android.support.v7.widget.CardView
+import android.util.AttributeSet
+import ca.allanwang.kau.R
+
+
+/**
+ * Created by Allan Wang on 2017-06-26.
+ *
+ * CardView with a limited height
+ * This view should be used with wrap_content as its height
+ * Defaults to at most the parent's visible height
+ */
+class BoundedCardView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : CardView(context, attrs, defStyleAttr) {
+
+ /**
+ * Maximum height possible, defined in dp (will be converted to px)
+ * Defaults to parent's visible height
+ */
+ var maxHeight: Int = -1
+ /**
+ * Percentage of resulting max height to fill
+ * Negative value = fill all of maxHeight
+ */
+ var maxHeightPercent: Float = -1.0f
+
+ init {
+ if (attrs != null) {
+ val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.BoundedCardView)
+ maxHeight = styledAttrs.getDimensionPixelSize(R.styleable.BoundedCardView_maxHeight, -1)
+ maxHeightPercent = styledAttrs.getFloat(R.styleable.BoundedCardView_maxHeightPercent, -1.0f)
+ styledAttrs.recycle()
+ }
+ }
+
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ var maxMeasureHeight = if (maxHeight > 0) maxHeight else parentVisibleHeight
+ if (maxHeightPercent > 0f) maxMeasureHeight = (maxMeasureHeight * maxHeightPercent).toInt()
+ val trueHeightMeasureSpec = MeasureSpec.makeMeasureSpec(maxMeasureHeight, MeasureSpec.AT_MOST)
+ super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec)
+ }
+
+} \ No newline at end of file
diff --git a/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt
new file mode 100644
index 0000000..abd96ed
--- /dev/null
+++ b/core-ui/src/main/kotlin/ca/allanwang/kau/ui/views/CutoutView.kt
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2015 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package ca.allanwang.kau.ui.views
+
+import android.content.Context
+import android.graphics.*
+import android.graphics.drawable.Drawable
+import android.text.TextPaint
+import android.util.AttributeSet
+import android.util.DisplayMetrics
+import android.util.TypedValue
+import android.view.View
+import ca.allanwang.kau.R
+import ca.allanwang.kau.utils.dimenPixelSize
+import ca.allanwang.kau.utils.getFont
+import ca.allanwang.kau.utils.parentVisibleHeight
+import ca.allanwang.kau.utils.toBitmap
+
+/**
+ * A view which punches out some text from an opaque color block, allowing you to see through it.
+ */
+class CutoutView @JvmOverloads constructor(
+ context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
+) : View(context, attrs, defStyleAttr) {
+
+ companion object {
+ const val PHI = 1.6182f
+ const val TYPE_TEXT = 100
+ const val TYPE_DRAWABLE = 101
+ }
+
+ private val paint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
+ private var bitmapScaling: Float = 1f
+ private var cutout: Bitmap? = null
+ var foregroundColor = Color.MAGENTA
+ var text: String? = "Text"
+ set(value) {
+ field = value
+ if (value != null) cutoutType = TYPE_TEXT
+ else if (drawable != null) cutoutType = TYPE_DRAWABLE
+ }
+ var cutoutType: Int = TYPE_TEXT
+ private var textSize: Float = 0f
+ private var cutoutY: Float = 0f
+ private var cutoutX: Float = 0f
+ var drawable: Drawable? = null
+ set(value) {
+ field = value
+ if (value != null) cutoutType = TYPE_DRAWABLE
+ else if (text != null) cutoutType = TYPE_TEXT
+ }
+ private var heightPercentage: Float = 0f
+ private var minHeight: Float = 0f
+ private val maxTextSize: Float
+
+ init {
+ if (attrs != null) {
+ val a = context.obtainStyledAttributes(attrs, R.styleable.CutoutView, 0, 0)
+ if (a.hasValue(R.styleable.CutoutView_font))
+ paint.typeface = context.getFont(a.getString(R.styleable.CutoutView_font))
+ foregroundColor = a.getColor(R.styleable.CutoutView_foregroundColor, foregroundColor)
+ text = a.getString(R.styleable.CutoutView_android_text) ?: text
+ minHeight = a.getDimension(R.styleable.CutoutView_android_minHeight, minHeight)
+ heightPercentage = a.getFloat(R.styleable.CutoutView_heightPercentageToScreen, heightPercentage)
+ a.recycle()
+ }
+ maxTextSize = context.dimenPixelSize(R.dimen.kau_display_4_text_size).toFloat()
+ }
+
+ override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
+ super.onSizeChanged(w, h, oldw, oldh)
+ calculatePosition()
+ createBitmap()
+ }
+
+ private fun calculatePosition() {
+ when (cutoutType) {
+ TYPE_TEXT -> calculateTextPosition()
+ TYPE_DRAWABLE -> calculateImagePosition()
+ }
+ }
+
+ private fun calculateTextPosition() {
+ val targetWidth = width / PHI
+ textSize = getSingleLineTextSize(text!!, paint, targetWidth, 0f, maxTextSize,
+ 0.5f, resources.displayMetrics)
+ paint.textSize = textSize
+
+ // measuring text is fun :] see: https://chris.banes.me/2014/03/27/measuring-text/
+ cutoutX = (width - paint.measureText(text)) / 2
+ val textBounds = Rect()
+ paint.getTextBounds(text, 0, text!!.length, textBounds)
+ val textHeight = textBounds.height().toFloat()
+ cutoutY = (height + textHeight) / 2
+ }
+
+ private fun calculateImagePosition() {
+ if (drawable!!.intrinsicHeight <= 0 || drawable!!.intrinsicWidth <= 0) throw IllegalArgumentException("Drawable's intrinsic size cannot be less than 0")
+ val targetWidth = width / PHI
+ val targetHeight = height / PHI
+ bitmapScaling = Math.min(targetHeight / drawable!!.intrinsicHeight, targetWidth / drawable!!.intrinsicWidth)
+ cutoutX = (width - drawable!!.intrinsicWidth * bitmapScaling) / 2
+ cutoutY = (height - drawable!!.intrinsicHeight * bitmapScaling) / 2
+ }
+
+ /**
+ * If height percent is specified, ensure it is met
+ */
+ override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+ val minHeight = Math.max(minHeight, heightPercentage * parentVisibleHeight)
+ val trueHeightMeasureSpec = if (minHeight > 0)
+ MeasureSpec.makeMeasureSpec(Math.max(minHeight.toInt(), measuredHeight), MeasureSpec.EXACTLY)
+ else heightMeasureSpec
+ super.onMeasure(widthMeasureSpec, trueHeightMeasureSpec)
+ }
+
+ /**
+ * Recursive binary search to find the best size for the text.
+
+ * Adapted from https://github.com/grantland/android-autofittextview
+ */
+ fun getSingleLineTextSize(text: String,
+ paint: TextPaint,
+ targetWidth: Float,
+ low: Float,
+ high: Float,
+ precision: Float,
+ metrics: DisplayMetrics): Float {
+ val mid = (low + high) / 2.0f
+
+ paint.textSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid, metrics)
+ val maxLineWidth = paint.measureText(text)
+
+ return if (high - low < precision) low
+ else if (maxLineWidth > targetWidth) getSingleLineTextSize(text, paint, targetWidth, low, mid, precision, metrics)
+ else if (maxLineWidth < targetWidth) getSingleLineTextSize(text, paint, targetWidth, mid, high, precision, metrics)
+ else mid
+ }
+
+ private fun createBitmap() {
+ if (!(cutout?.isRecycled ?: true))
+ cutout?.recycle()
+ if (width == 0 || height == 0) return
+ cutout = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
+ cutout!!.setHasAlpha(true)
+ val cutoutCanvas = Canvas(cutout!!)
+ cutoutCanvas.drawColor(foregroundColor)
+ paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
+
+ when (cutoutType) {
+ TYPE_TEXT -> {
+ // this is the magic – Clear mode punches out the bitmap
+ paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
+ cutoutCanvas.drawText(text, cutoutX, cutoutY, paint)
+ }
+ TYPE_DRAWABLE -> {
+ cutoutCanvas.drawBitmap(drawable!!.toBitmap(bitmapScaling, Bitmap.Config.ALPHA_8), cutoutX, cutoutY, paint)
+ }
+
+ }
+ }
+
+ override fun onDraw(canvas: Canvas) {
+ canvas.drawBitmap(cutout!!, 0f, 0f, null)
+ }
+
+ override fun hasOverlappingRendering(): Boolean = true
+
+}