From 8f2e56eb5ecfc6b5ce2f1530ead6fe73f5bfe89c Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Thu, 29 Jun 2017 00:29:46 -0700 Subject: Completely rewrite about activity --- .../ca/allanwang/kau/about/AboutActivityBase.kt | 75 ++++---- .../kotlin/ca/allanwang/kau/about/LibraryItem.kt | 81 --------- .../kotlin/ca/allanwang/kau/iitems/CardIItem.kt | 116 +++++++++++++ .../kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt | 44 +++++ .../kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt | 81 +++++++++ .../ca/allanwang/kau/searchview/SearchItem.kt | 2 +- .../kotlin/ca/allanwang/kau/utils/ContextUtils.kt | 1 + .../main/kotlin/ca/allanwang/kau/utils/Utils.kt | 10 +- .../kotlin/ca/allanwang/kau/utils/ViewUtils.kt | 22 +++ .../ca/allanwang/kau/views/CutoutTextView.kt | 189 --------------------- .../kotlin/ca/allanwang/kau/views/CutoutView.kt | 183 ++++++++++++++++++++ 11 files changed, 491 insertions(+), 313 deletions(-) delete mode 100644 library/src/main/kotlin/ca/allanwang/kau/about/LibraryItem.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt delete mode 100644 library/src/main/kotlin/ca/allanwang/kau/views/CutoutTextView.kt create mode 100644 library/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt (limited to 'library/src/main/kotlin') diff --git a/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt b/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt index c518ca6..143e92a 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/about/AboutActivityBase.kt @@ -1,26 +1,24 @@ package ca.allanwang.kau.about +import android.graphics.drawable.Drawable import android.os.Bundle import android.support.v4.view.PagerAdapter import android.support.v4.view.ViewPager import android.support.v7.app.AppCompatActivity -import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.transition.TransitionInflater import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.FrameLayout -import android.widget.TextView import ca.allanwang.kau.R -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.dimenPixelSize -import ca.allanwang.kau.utils.string -import ca.allanwang.kau.views.CutoutTextView +import ca.allanwang.kau.iitems.CutoutIItem +import ca.allanwang.kau.iitems.LibraryIItem +import ca.allanwang.kau.utils.* import ca.allanwang.kau.widgets.ElasticDragDismissFrameLayout import ca.allanwang.kau.widgets.InkPageIndicator import com.mikepenz.aboutlibraries.Libs import com.mikepenz.aboutlibraries.entity.Library +import com.mikepenz.fastadapter.IItem import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import org.jetbrains.anko.doAsync import org.jetbrains.anko.uiThread @@ -66,7 +64,9 @@ abstract class AboutActivityBase(val rClass: Class<*>, val configBuilder: Config inner class Configs { var cutoutTextRes: Int = -1 - val cutoutText: String? = "KAU" //todo make null + var cutoutText: String? = null + var cutoutDrawableRes: Int = -1 + var cutoutDrawable: Drawable? = null var mainPageTitleRes: Int = -1 var mainPageTitle: String = "Kau test" var libPageTitleRes: Int = -1 @@ -78,6 +78,11 @@ abstract class AboutActivityBase(val rClass: Class<*>, val configBuilder: Config open val pageCount: Int = 2 + /** + * Gets the view associated with the given page position + * Keep in mind that when inflating, do NOT add the view to the viewgroup + * Use layoutInflater.inflate(id, parent, false) + */ open fun getPage(position: Int, layoutInflater: LayoutInflater, parent: ViewGroup): View { return when (position) { 0 -> inflateMainPage(layoutInflater, parent) @@ -86,43 +91,35 @@ abstract class AboutActivityBase(val rClass: Class<*>, val configBuilder: Config } } - fun inflateMainPage(layoutInflater: LayoutInflater, parent: ViewGroup): View { - val v = layoutInflater.inflate(R.layout.kau_about_section_main, parent, false) - postInflateMainPage( - v.findViewById(R.id.about_main_cutout), - v.findViewById(R.id.about_main_bottom_container), - v.findViewById(R.id.about_main_bottom_text) - ) - return v - } - - open fun postInflateMainPage(cutout: CutoutTextView, bottomContainer: FrameLayout, bottomText: TextView) { - with (configs) { - cutout.text = string(cutoutTextRes, cutoutText) - bottomText.text = string(mainPageTitleRes, mainPageTitle) + open fun inflateMainPage(layoutInflater: LayoutInflater, parent: ViewGroup): View { + val fastAdapter = FastItemAdapter>() + val recycler = fullLinearRecycler { + adapter = fastAdapter } + fastAdapter.add(CutoutIItem { + with(configs) { + text = string(cutoutTextRes, cutoutText) + drawable = drawable(cutoutDrawableRes, cutoutDrawable) + } + }) + return recycler } - fun inflateLibPage(layoutInflater: LayoutInflater, parent: ViewGroup): View { - val v = layoutInflater.inflate(R.layout.kau_about_section_libraries, parent, false) - postInflateLibPage( - v.findViewById(R.id.about_library_title), - v.findViewById(R.id.about_library_recycler) - ) - return v - } - open fun postInflateLibPage(title: TextView, recycler: RecyclerView) { - title.text = string(configs.libPageTitleRes, configs.libPageTitle) - val libAdapter = FastItemAdapter() - with(recycler) { - layoutManager = LinearLayoutManager(this@AboutActivityBase) - adapter = libAdapter - } + open fun inflateLibPage(layoutInflater: LayoutInflater, parent: ViewGroup): View { + val v = layoutInflater.inflate(R.layout.kau_recycler_detached_background, parent, false) + val fastAdapter = FastItemAdapter>() + val recycler = v.findViewById(R.id.kau_recycler_detached) + recycler.adapter = fastAdapter + val background = v.findViewById(R.id.kau_recycler_detached_background) doAsync { - val libs = getLibraries(Libs(this@AboutActivityBase, Libs.toStringArray(rClass.fields))).map { LibraryItem(it) } - uiThread { libAdapter.add(libs) } + val libs = getLibraries(Libs(this@AboutActivityBase, Libs.toStringArray(rClass.fields))).map { LibraryIItem(it) } + uiThread { + recycler.transitionDelayed(R.transition.kau_enter_slide_top) //TODO fix this + fastAdapter.add(libs) + } } + return v } inner class AboutPagerAdapter : PagerAdapter() { diff --git a/library/src/main/kotlin/ca/allanwang/kau/about/LibraryItem.kt b/library/src/main/kotlin/ca/allanwang/kau/about/LibraryItem.kt deleted file mode 100644 index c3bfb5a..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/about/LibraryItem.kt +++ /dev/null @@ -1,81 +0,0 @@ -package ca.allanwang.kau.about - -/** - * Created by Allan Wang on 2017-06-27. - */ - -import android.os.Build -import android.support.v7.widget.CardView -import android.support.v7.widget.RecyclerView -import android.text.Html -import android.view.View -import android.widget.LinearLayout -import android.widget.TextView -import ca.allanwang.kau.R -import ca.allanwang.kau.utils.bindView -import ca.allanwang.kau.utils.gone -import ca.allanwang.kau.utils.visible -import com.mikepenz.aboutlibraries.entity.Library -import com.mikepenz.fastadapter.items.AbstractItem - - -/** - * Created by mikepenz on 28.12.15. - */ -class LibraryItem(val lib: Library) : AbstractItem() { - - override fun getType(): Int = R.id.kau_item_about_library - - override fun getLayoutRes(): Int = R.layout.kau_about_item_library - - override fun isSelectable(): Boolean = false - - override fun bindView(holder: ViewHolder, payloads: MutableList?) { - super.bindView(holder, payloads) - with(holder) { - name.text = lib.libraryName - creator.text = lib.author - description.text = if (lib.libraryDescription.isBlank()) lib.libraryDescription - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - Html.fromHtml(lib.libraryDescription, Html.FROM_HTML_MODE_LEGACY) - else Html.fromHtml(lib.libraryDescription) - bottomDivider.gone() - bottomContainer.gone() - if (lib.libraryVersion?.isNotBlank() ?: false) { - bottomDivider.visible() - bottomContainer.visible() - version.text = lib.libraryVersion - } - if (lib.license?.licenseName?.isNotBlank() ?: false) { - bottomDivider.visible() - bottomContainer.visible() - license.text = lib.license?.licenseName - } - } - } - - override fun unbindView(holder: ViewHolder) { - super.unbindView(holder) - with (holder) { - name.text = null - creator.text = null - description.text = null - } - } - - override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) - - class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { - val card: CardView by bindView(R.id.lib_item_card) - val name: TextView by bindView(R.id.lib_item_name) - val creator: TextView by bindView(R.id.lib_item_author) - val description: TextView by bindView(R.id.lib_item_description) - val version: TextView by bindView(R.id.lib_item_version) - val license: TextView by bindView(R.id.lib_item_license) - val bottomContainer: LinearLayout by bindView(R.id.lib_item_bottom_container) - - val divider: View by bindView(R.id.lib_item_top_divider) - val bottomDivider: View by bindView(R.id.lib_item_bottom_divider) - } - -} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt new file mode 100644 index 0000000..36e7ef8 --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/iitems/CardIItem.kt @@ -0,0 +1,116 @@ +package ca.allanwang.kau.iitems + +import android.graphics.Color +import android.graphics.drawable.Drawable +import android.support.v7.widget.CardView +import android.support.v7.widget.RecyclerView +import android.view.View +import android.widget.Button +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import ca.allanwang.kau.R +import ca.allanwang.kau.utils.* +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.fastadapter.listeners.ClickEventHook +import com.mikepenz.iconics.typeface.IIcon + +/** + * Created by Allan Wang on 2017-06-28. + * + * Simple generic card item with an icon, title, description and button + * The icon and button are hidden by default unless values are given + */ +class CardIItem(val builder: Config.() -> Unit = {}) : AbstractItem() { + + + companion object { + fun bindClickEvents(fastAdapter: FastAdapter) { + fastAdapter.withEventHook(object : ClickEventHook() { + override fun onBindMany(viewHolder: RecyclerView.ViewHolder): List? { + return if (viewHolder is ViewHolder) listOf(viewHolder.card, viewHolder.button) else null + } + + override fun onClick(v: View, position: Int, adapter: FastAdapter, item: CardIItem) { + with(item.configs) { + when (v.id) { + R.id.kau_card_container -> cardClick?.onClick(v) + R.id.kau_card_button -> buttonClick?.onClick(v) + else -> { + } + } + } + } + }) + } + } + + val configs = Config().apply { builder() } + + class Config { + var title: String? = null + var titleRes: Int = -1 + var desc: String? = null + var descRes: Int = -1 + var button: String? = null + var buttonRes: Int = -1 + var buttonClick: View.OnClickListener? = null + var cardClick: View.OnClickListener? = null + var image: Drawable? = null + var imageIIcon: IIcon? = null + var imageIIconColor: Int = Color.WHITE + var imageRes: Int = -1 + } + + + override fun getType(): Int = R.id.kau_item_card + + override fun getLayoutRes(): Int = R.layout.kau_iitem_card + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder.itemView.context) context@ { + with(configs) { + holder.title.text = string(titleRes, title) + holder.description.text = string(descRes, desc) + val buttonText = string(buttonRes, button) + if (buttonText != null) { + holder.bottomRow.visible() + holder.button.text = buttonText + holder.button.setOnClickListener(buttonClick) + } + holder.icon.setImageDrawable( + if (imageRes > 0) drawable(imageRes) + else if (imageIIcon != null) imageIIcon!!.toDrawable(this@context, sizeDp = 40, color = imageIIconColor) + else image + ) + holder.card.setOnClickListener(cardClick) + } + } + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with(holder) { + icon.gone().setImageDrawable(null) + title.text = null + description.text = null + bottomRow.gone() + button.setOnClickListener(null) + card.setOnClickListener(null) + } + } + + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val card: CardView by bindView(R.id.kau_card_container) + val icon: ImageView by bindView(R.id.kau_card_image) + val title: TextView by bindView(R.id.kau_card_title) + val description: TextView by bindView(R.id.kau_card_description) + val bottomRow: LinearLayout by bindView(R.id.kau_card_bottom_row) + val button: Button by bindView(R.id.kau_card_button) + } + +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt new file mode 100644 index 0000000..d138917 --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/iitems/CutoutIItem.kt @@ -0,0 +1,44 @@ +package ca.allanwang.kau.iitems + +import android.support.v7.widget.RecyclerView +import android.view.View +import ca.allanwang.kau.R +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.views.CutoutView +import com.mikepenz.fastadapter.items.AbstractItem + +/** + * Created by Allan Wang on 2017-06-28. + * + * Just a cutout item with some defaults in [R.layout.kau_iitem_cutout] + */ +class CutoutIItem(val config: CutoutView.() -> Unit = {}) : AbstractItem() { + + override fun getType(): Int = R.id.kau_item_cutout + + override fun getLayoutRes(): Int = R.layout.kau_iitem_cutout + + override fun isSelectable(): Boolean = false + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder) { + cutout.config() + } + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with(holder) { + cutout.drawable = null + cutout.text = "Text" //back to default + } + } + + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val cutout: CutoutView by bindView(R.id.kau_cutout) + } + +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt b/library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt new file mode 100644 index 0000000..e617aaa --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/iitems/LibraryIItem.kt @@ -0,0 +1,81 @@ +package ca.allanwang.kau.iitems + +/** + * Created by Allan Wang on 2017-06-27. + */ + +import android.os.Build +import android.support.v7.widget.CardView +import android.support.v7.widget.RecyclerView +import android.text.Html +import android.view.View +import android.widget.LinearLayout +import android.widget.TextView +import ca.allanwang.kau.R +import ca.allanwang.kau.utils.bindView +import ca.allanwang.kau.utils.gone +import ca.allanwang.kau.utils.visible +import com.mikepenz.aboutlibraries.entity.Library +import com.mikepenz.fastadapter.items.AbstractItem + + +/** + * Created by mikepenz on 28.12.15. + */ +class LibraryIItem(val lib: Library) : AbstractItem() { + + override fun getType(): Int = R.id.kau_item_about_library + + override fun getLayoutRes(): Int = R.layout.kau_about_iitem_library + + override fun isSelectable(): Boolean = false + + override fun bindView(holder: ViewHolder, payloads: MutableList?) { + super.bindView(holder, payloads) + with(holder) { + name.text = lib.libraryName + creator.text = lib.author + description.text = if (lib.libraryDescription.isBlank()) lib.libraryDescription + else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + Html.fromHtml(lib.libraryDescription, Html.FROM_HTML_MODE_LEGACY) + else Html.fromHtml(lib.libraryDescription) + bottomDivider.gone() + bottomContainer.gone() + if (lib.libraryVersion?.isNotBlank() ?: false) { + bottomDivider.visible() + bottomContainer.visible() + version.text = lib.libraryVersion + } + if (lib.license?.licenseName?.isNotBlank() ?: false) { + bottomDivider.visible() + bottomContainer.visible() + license.text = lib.license?.licenseName + } + } + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + with (holder) { + name.text = null + creator.text = null + description.text = null + } + } + + override fun getViewHolder(v: View): ViewHolder = ViewHolder(v) + + class ViewHolder(v: View) : RecyclerView.ViewHolder(v) { + val card: CardView by bindView(R.id.lib_item_card) + val name: TextView by bindView(R.id.lib_item_name) + val creator: TextView by bindView(R.id.lib_item_author) + val description: TextView by bindView(R.id.lib_item_description) + val version: TextView by bindView(R.id.lib_item_version) + val license: TextView by bindView(R.id.lib_item_license) + val bottomContainer: LinearLayout by bindView(R.id.lib_item_bottom_container) + + val divider: View by bindView(R.id.lib_item_top_divider) + val bottomDivider: View by bindView(R.id.lib_item_bottom_divider) + } + +} \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt index a672e8a..60727a5 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchItem.kt @@ -47,7 +47,7 @@ class SearchItem(val key: String, styledContent!!.setSpan(StyleSpan(Typeface.BOLD), index, index + subText.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) } - override fun getLayoutRes(): Int = R.layout.kau_search_item + override fun getLayoutRes(): Int = R.layout.kau_search_iitem override fun getType(): Int = R.id.kau_item_search diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt index 876f634..2a929a2 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ContextUtils.kt @@ -82,6 +82,7 @@ fun Context.integer(@IntegerRes id: Int): Int = resources.getInteger(id) fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id) fun Context.dimenPixelSize(@DimenRes id: Int): Int = resources.getDimensionPixelSize(id) fun Context.drawable(@DrawableRes id: Int): Drawable = ContextCompat.getDrawable(this, id) +fun Context.drawable(@DrawableRes id: Int, fallback: Drawable?): Drawable? = if (id > 0) drawable(id) else fallback //Attr retrievers fun Context.resolveColor(@AttrRes attr: Int, fallback: Int = 0): Int { diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt index 6c1b6a1..55fb9ea 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/Utils.kt @@ -60,7 +60,11 @@ annotation class KauUtils return formatter.format(this) } -@KauUtils fun Drawable.toBitmap(scaling: Float = 1f): Bitmap { +/** + * Extracts the bitmap of a drawable, and applies a scale if given + * For solid colors, a 1 x 1 pixel will be generated + */ +@KauUtils fun Drawable.toBitmap(scaling: Float = 1f, config: Bitmap.Config = Bitmap.Config.ARGB_8888): Bitmap { if (this is BitmapDrawable && bitmap != null) { if (scaling == 1f) return bitmap val width = (bitmap.width * scaling).toInt() @@ -68,9 +72,9 @@ annotation class KauUtils return Bitmap.createScaledBitmap(bitmap, width, height, false) } val bitmap = if (intrinsicWidth <= 0 || intrinsicHeight <= 0) - Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888) // Single color bitmap will be created of 1x1 pixel + Bitmap.createBitmap(1, 1, config) else - Bitmap.createBitmap((intrinsicWidth * scaling).toInt(), (intrinsicHeight * scaling).toInt(), Bitmap.Config.ARGB_8888) + Bitmap.createBitmap((intrinsicWidth * scaling).toInt(), (intrinsicHeight * scaling).toInt(), config) val canvas = Canvas(bitmap) setBounds(0, 0, canvas.width, canvas.height) draw(canvas) diff --git a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt index 0f070d1..3fb8f27 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/utils/ViewUtils.kt @@ -6,9 +6,14 @@ import android.graphics.Outline import android.graphics.Rect import android.support.annotation.ColorInt import android.support.annotation.StringRes +import android.support.annotation.TransitionRes import android.support.design.widget.Snackbar import android.support.transition.AutoTransition +import android.support.transition.Transition +import android.support.transition.TransitionInflater import android.support.transition.TransitionManager +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView import android.view.View import android.view.ViewGroup import android.view.ViewOutlineProvider @@ -77,6 +82,12 @@ fun View.snackbar(@StringRes textId: Int, duration: Int = Snackbar.LENGTH_LONG, TransitionManager.beginDelayedTransition(this, transition) } +@KauUtils fun ViewGroup.transitionDelayed(@TransitionRes id: Int, builder: Transition.() -> Unit = {}) { + val transition = TransitionInflater.from(context).inflateTransition(id) + transition.builder() + TransitionManager.beginDelayedTransition(this, transition) +} + @KauUtils fun View.setRippleBackground(@ColorInt foregroundColor: Int, @ColorInt backgroundColor: Int) { background = createSimpleRippleDrawable(foregroundColor, backgroundColor) } @@ -99,4 +110,15 @@ val CIRCULAR_OUTLINE: ViewOutlineProvider = object : ViewOutlineProvider() { view.width - view.paddingRight, view.height - view.paddingBottom) } +} + +/** + * Generates a recycler view with match parent and a linearlayoutmanager, since it's so commonly used + */ +fun Context.fullLinearRecycler(configs: RecyclerView.() -> Unit = {}): RecyclerView { + return RecyclerView(this).apply { + layoutManager = LinearLayoutManager(this@fullLinearRecycler) + layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.MATCH_PARENT) + configs() + } } \ No newline at end of file diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/CutoutTextView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/CutoutTextView.kt deleted file mode 100644 index 83ed6dd..0000000 --- a/library/src/main/kotlin/ca/allanwang/kau/views/CutoutTextView.kt +++ /dev/null @@ -1,189 +0,0 @@ -/* - * 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.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 CutoutTextView @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 textPaint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG) - private val bitmapPaint: Paint = Paint(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 - private 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.CutoutTextView, 0, 0) - if (a.hasValue(R.styleable.CutoutTextView_font)) - textPaint.typeface = context.getFont(a.getString(R.styleable.CutoutTextView_font)) - foregroundColor = a.getColor(R.styleable.CutoutTextView_foregroundColor, foregroundColor) - text = a.getString(R.styleable.CutoutTextView_android_text) ?: text - minHeight = a.getDimension(R.styleable.CutoutTextView_android_minHeight, minHeight) - heightPercentage = a.getFloat(R.styleable.CutoutTextView_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!!, 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/ - cutoutX = (width - textPaint.measureText(text)) / 2 - val textBounds = Rect() - textPaint.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) - - 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) - - when (cutoutType) { - TYPE_TEXT -> { - // this is the magic – Clear mode punches out the bitmap - textPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) - cutoutCanvas.drawText(text, cutoutX, cutoutY, textPaint) - } - TYPE_DRAWABLE -> { - bitmapPaint.xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR) - cutoutCanvas.drawBitmap(drawable!!.toBitmap(bitmapScaling), cutoutX, cutoutY, bitmapPaint) - } - - } - } - - override fun onDraw(canvas: Canvas) { - canvas.drawBitmap(cutout!!, 0f, 0f, null) - } - - override fun hasOverlappingRendering(): Boolean = true - -} diff --git a/library/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt b/library/src/main/kotlin/ca/allanwang/kau/views/CutoutView.kt new file mode 100644 index 0000000..023bdb4 --- /dev/null +++ b/library/src/main/kotlin/ca/allanwang/kau/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.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 + +} -- cgit v1.2.3