diff options
Diffstat (limited to 'library/src/main/kotlin/ca/allanwang/kau/views')
3 files changed, 148 insertions, 132 deletions
diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt index a07a118..60f5176 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/views/KauBoundedCardView.kt @@ -6,6 +6,7 @@ import android.support.v7.widget.CardView import android.util.AttributeSet import ca.allanwang.kau.R import ca.allanwang.kau.utils.parentViewGroup +import ca.allanwang.kau.utils.parentVisibleHeight /** @@ -19,13 +20,6 @@ class KauBoundedCardView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : CardView(context, attrs, defStyleAttr) { - val parentVisibleHeight: Int - get() { - val r = Rect() - parentViewGroup.getWindowVisibleDisplayFrame(r) - return r.height() - } - /** * Maximum height possible, defined in dp (will be converted to px) * Defaults to parent's visible height diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/KauCutoutTextView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/KauCutoutTextView.kt new file mode 100644 index 0000000..8df604a --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/views/KauCutoutTextView.kt @@ -0,0 +1,147 @@ +/* + * 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.views + +import android.content.Context +import android.graphics.* +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.logging.KL +import ca.allanwang.kau.utils.dimenPixelSize +import ca.allanwang.kau.utils.getFont +import ca.allanwang.kau.utils.parentVisibleHeight + +/** + * A view which punches out some text from an opaque color block, allowing you to see through it. + */ +class KauCutoutTextView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 +) : View(context, attrs, defStyleAttr) { + private val textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) + private var cutout: Bitmap? = null + var foregroundColor = Color.MAGENTA + var text: String? = "Text" + var overlayType: Int = 0 //todo add vector overlay options + private var textSize: Float = 0f + private var textY: Float = 0f + private var textX: Float = 0f + 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.KauCutoutTextView, 0, 0) + if (a.hasValue(R.styleable.KauCutoutTextView_kau_font)) + textPaint.typeface = context.getFont(a.getString(R.styleable.KauCutoutTextView_kau_font)) + foregroundColor = a.getColor(R.styleable.KauCutoutTextView_kau_foregroundColor, foregroundColor) + text = a.getString(R.styleable.KauCutoutTextView_android_text) ?: text + minHeight = a.getDimension(R.styleable.KauCutoutTextView_android_minHeight, minHeight) + heightPercentage = a.getFloat(R.styleable.KauCutoutTextView_kau_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) + calculateTextPosition() + createBitmap() + KL.d("Size changed") + } + + private fun calculateTextPosition() { + val targetWidth = width / PHI + textSize = getSingleLineTextSize(text!!, textPaint, targetWidth, 0f, maxTextSize, + 0.5f, resources.displayMetrics) + textPaint.textSize = textSize + + // measuring text is fun :] see: https://chris.banes.me/2014/03/27/measuring-text/ + textX = (width - textPaint.measureText(text)) / 2 + val textBounds = Rect() + textPaint.getTextBounds(text, 0, text!!.length, textBounds) + val textHeight = textBounds.height().toFloat() + textY = (height + textHeight) / 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) + + if (high - low < precision) { + return low + } else if (maxLineWidth > targetWidth) { + return getSingleLineTextSize(text, paint, targetWidth, low, mid, precision, metrics) + } else if (maxLineWidth < targetWidth) { + return getSingleLineTextSize(text, paint, targetWidth, mid, high, precision, metrics) + } else { + return 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) + + // this is the magic – Clear mode punches out the bitmap + textPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) + cutoutCanvas.drawText(text, textX, textY, textPaint) + } + + override fun onDraw(canvas: Canvas) { + canvas.drawBitmap(cutout!!, 0f, 0f, null) + } + + override fun hasOverlappingRendering(): Boolean = true + + companion object { + val PHI = 1.6182f + } +} diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/KauTextSlider.kt b/library/src/main/kotlin/ca/allanwang/kau/views/KauTextSlider.kt deleted file mode 100644 index 8294f9f..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/views/KauTextSlider.kt +++ /dev/null @@ -1,125 +0,0 @@ -package ca.allanwang.kau.views - -import android.content.Context -import android.graphics.Color -import android.support.v4.widget.TextViewCompat -import android.text.TextUtils -import android.util.AttributeSet -import android.view.Gravity -import android.view.animation.Animation -import android.view.animation.AnimationUtils -import android.widget.TextSwitcher -import android.widget.TextView -import ca.allanwang.kau.R -import java.util.* - -/** - * Created by Allan Wang on 2017-06-21. - * - * Text switcher with global text color and embedded sliding animations - * Also has a stack to keep track of title changes - */ -class KauTextSlider @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null -) : TextSwitcher(context, attrs) { - - val titleStack: Stack<CharSequence?> = Stack() - - /** - * Holds a mapping of animation types to their respective animations - */ - val animationMap = mapOf( - ANIMATION_NONE to null, - ANIMATION_SLIDE_HORIZONTAL to AnimationBundle( - R.anim.kau_slide_in_right, R.anim.kau_slide_out_left, - R.anim.kau_slide_in_left, R.anim.kau_slide_out_right), - ANIMATION_SLIDE_VERTICAL to AnimationBundle( - R.anim.kau_slide_in_bottom, R.anim.kau_slide_out_top, - R.anim.kau_slide_in_top, R.anim.kau_slide_out_bottom - ) - ) - - /** - * Holds lazy instances of the animations - */ - inner class AnimationBundle(private val nextIn: Int, private val nextOut: Int, private val prevIn: Int, private val prevOut: Int) { - val NEXT_IN: Animation by lazy { AnimationUtils.loadAnimation(context, nextIn) } - val NEXT_OUT: Animation by lazy { AnimationUtils.loadAnimation(context, nextOut) } - val PREV_IN: Animation by lazy { AnimationUtils.loadAnimation(context, prevIn) } - val PREV_OUT: Animation by lazy { AnimationUtils.loadAnimation(context, prevOut) } - } - - companion object { - const val ANIMATION_NONE = 1000 - const val ANIMATION_SLIDE_HORIZONTAL = 1001 - const val ANIMATION_SLIDE_VERTICAL = 1002 - } - - var animationType: Int = ANIMATION_SLIDE_HORIZONTAL - - var textColor: Int = Color.WHITE - get() = field - set(value) { - field = value - (getChildAt(0) as TextView).setTextColor(value) - (getChildAt(1) as TextView).setTextColor(value) - } - val isRoot: Boolean - get() = titleStack.size <= 1 - - init { - if (attrs != null) { - val styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.KauTextSlider) - animationType = styledAttrs.getInteger(R.styleable.KauTextSlider_kau_animation_type, ANIMATION_SLIDE_HORIZONTAL) - styledAttrs.recycle() - } - } - - override fun setText(text: CharSequence?) { - if ((currentView as TextView).text == text) return - super.setText(text) - } - - override fun setCurrentText(text: CharSequence?) { - if (titleStack.isNotEmpty()) titleStack.pop() - titleStack.push(text) - super.setCurrentText(text) - } - - fun setNextText(text: CharSequence?) { - if (titleStack.isEmpty()) { - setCurrentText(text) - return - } - titleStack.push(text) - val anim = animationMap[animationType] - inAnimation = anim?.NEXT_IN - outAnimation = anim?.NEXT_OUT - setText(text) - } - - /** - * Sets the text as the previous title - * No further checks are done, so be sure to verify with [isRoot] - */ - @Throws(EmptyStackException::class) - fun setPrevText() { - titleStack.pop() - val anim = animationMap[animationType] - inAnimation = anim?.PREV_IN - outAnimation = anim?.PREV_OUT - val text = titleStack.peek() - setText(text) - } - - init { - setFactory { - TextView(context).apply { - //replica of toolbar title - gravity = Gravity.START - setSingleLine() - ellipsize = TextUtils.TruncateAt.END - TextViewCompat.setTextAppearance(this, R.style.TextAppearance_AppCompat_Title) - } - } - } -}
\ No newline at end of file |