From b536b151f1012e730782f615dceed6be7e3a9652 Mon Sep 17 00:00:00 2001 From: Allan Wang Date: Sat, 24 Jun 2017 15:06:03 -0700 Subject: Properly align searchView clicks --- .../ca/allanwang/kau/searchview/SearchView.kt | 113 ++++++++++++--------- 1 file changed, 63 insertions(+), 50 deletions(-) (limited to 'library') diff --git a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt index 8ffc871..4449fe5 100644 --- a/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt +++ b/library/src/main/kotlin/ca/allanwang/kau/searchview/SearchView.kt @@ -8,10 +8,7 @@ import android.support.v7.widget.CardView import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.util.AttributeSet -import android.view.Menu -import android.view.MenuItem -import android.view.View -import android.view.ViewGroup +import android.view.* import android.widget.FrameLayout import android.widget.ImageView import android.widget.ProgressBar @@ -22,6 +19,7 @@ import com.mikepenz.fastadapter.commons.adapters.FastItemAdapter import com.mikepenz.google_material_typeface_library.GoogleMaterial import com.mikepenz.iconics.typeface.IIcon + /** * Created by Allan Wang on 2017-06-23. */ @@ -29,19 +27,9 @@ class SearchView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 ) : FrameLayout(context, attrs, defStyleAttr) { - companion object { - fun bind(parent: ViewGroup, menu: Menu, @IdRes id: Int, config: Configs.() -> Unit = {}) { - SearchView(parent.context).apply { - layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) - parent.addView(this) - this.bind(parent, menu, id, config) - } - } - } - //configs inner class Configs { - var foregroundColor: Int = 0xddffffff.toInt() + var foregroundColor: Int = 0xdd000000.toInt() set(value) { if (field == value) return field = value @@ -56,51 +44,52 @@ class SearchView @JvmOverloads constructor( var navIcon: IIcon? = GoogleMaterial.Icon.gmd_arrow_back set(value) { field = value - iconNav.setIcon(value) + iconNav.setSearchIcon(value) if (value == null) iconNav.gone() } var micIcon: IIcon? = GoogleMaterial.Icon.gmd_mic set(value) { field = value - iconMic.setIcon(value) + iconMic.setSearchIcon(value) if (value == null) iconMic.gone() } var clearIcon: IIcon? = GoogleMaterial.Icon.gmd_clear set(value) { field = value - iconClear.setIcon(value) + iconClear.setSearchIcon(value) if (value == null) iconClear.gone() } var revealDuration: Long = 300L var shouldClearOnOpen: Boolean = true - var openListener: (() -> Unit)? = null - var closeListener: (() -> Unit)? = null + var openListener: ((searchView: SearchView) -> Unit)? = null + var closeListener: ((searchView: SearchView) -> Unit)? = null } val configs = Configs() //views - private val shadow: View by bindView(R.id.search_shadow) - private val card: CardView by bindView(R.id.search_cardview) - private val iconNav: ImageView by bindView(R.id.search_nav) - private val editText: AppCompatEditText by bindView(R.id.search_edit_text) - private val progress: ProgressBar by bindView(R.id.search_progress) - private val iconMic: ImageView by bindView(R.id.search_mic) - private val iconClear: ImageView by bindView(R.id.search_clear) - private val recycler: RecyclerView by bindView(R.id.search_recycler) - private val adapter = FastItemAdapter() - private lateinit var parent: ViewGroup + val shadow: View by bindView(R.id.search_shadow) + val card: CardView by bindView(R.id.search_cardview) + val iconNav: ImageView by bindView(R.id.search_nav) + val editText: AppCompatEditText by bindView(R.id.search_edit_text) + val progress: ProgressBar by bindView(R.id.search_progress) + val iconMic: ImageView by bindView(R.id.search_mic) + val iconClear: ImageView by bindView(R.id.search_clear) + val recycler: RecyclerView by bindView(R.id.search_recycler) + val adapter = FastItemAdapter() + lateinit var parent: ViewGroup val isOpen: Boolean get() = card.isVisible() //menu view - private var revealX: Int = -1 - private var revealY: Int = -1 + var menuX: Int = -1 + var menuY: Int = -1 + var menuHalfHeight: Int = -1 init { View.inflate(context, R.layout.kau_search_view, this) - iconNav.setIcon(configs.navIcon) - iconMic.setIcon(configs.micIcon) - iconClear.setIcon(configs.clearIcon) + iconNav.setSearchIcon(configs.navIcon) + iconMic.setSearchIcon(configs.micIcon) + iconClear.setSearchIcon(configs.clearIcon) tintForeground(configs.foregroundColor) tintBackground(configs.backgroundColor) with(recycler) { @@ -115,15 +104,15 @@ class SearchView @JvmOverloads constructor( } } - internal fun ImageView.setSearchIcon(iicon: IIcon) { - setIcon(iicon, sizeDp = 20, color = configs.foregroundColor) + internal fun ImageView.setSearchIcon(iicon: IIcon?) { + setIcon(iicon, sizeDp = 18, color = configs.foregroundColor) } fun config(config: Configs.() -> Unit) { configs.config() } - fun bind(parent: ViewGroup, menu: Menu, @IdRes id: Int, config: Configs.() -> Unit = {}) { + fun bind(parent: ViewGroup, menu: Menu, @IdRes id: Int, config: Configs.() -> Unit = {}): SearchView { config(config) this.parent = parent val item = menu.findItem(id) @@ -131,19 +120,30 @@ class SearchView @JvmOverloads constructor( card.gone() item.setOnMenuItemClickListener { configureCoords(it); revealOpen(); true } shadow.setOnClickListener { revealClose() } + return this } fun configureCoords(item: MenuItem) { val view = parent.findViewById(item.itemId) ?: return val locations = IntArray(2) view.getLocationOnScreen(locations) - revealX = (locations[0] + view.width / 2) - revealY = (locations[1] + view.height / 2) - val topAlignment = revealY - card.height / 2 - val params = (card.layoutParams as MarginLayoutParams).apply { - topMargin = topAlignment - } - card.layoutParams = params + menuX = (locations[0] + view.width / 2) + menuHalfHeight = view.height / 2 + menuY = (locations[1] + menuHalfHeight) + card.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener { + override fun onPreDraw(): Boolean { + view.viewTreeObserver.removeOnPreDrawListener(this) + with(card) { + KL.e("S $width $measuredWidth $height $measuredHeight") + } + val topAlignment = menuY - card.height / 2 + val params = (card.layoutParams as MarginLayoutParams).apply { + topMargin = topAlignment + } + card.layoutParams = params + return false + } + }) } fun tintForeground(@ColorInt color: Int) { @@ -159,9 +159,14 @@ class SearchView @JvmOverloads constructor( fun revealOpen() { if (isOpen) return - card.circularReveal(revealX, revealY, duration = configs.revealDuration, + /** + * The y component is relative to the cardView, but it hasn't been drawn yet so its own height is 0 + * We therefore use half the menuItem height, which is a close approximation to our intended value + * The cardView matches the parent's width, so menuX is correct + */ + card.circularReveal(menuX, menuHalfHeight, duration = configs.revealDuration, onStart = { - configs.openListener?.invoke() + configs.openListener?.invoke(this) if (configs.shouldClearOnOpen) editText.text.clear() }, onFinish = { @@ -173,10 +178,18 @@ class SearchView @JvmOverloads constructor( fun revealClose() { if (!isOpen) return shadow.fadeOut() { - card.circularHide(revealX, revealY, duration = configs.revealDuration, + card.circularHide(menuX, menuHalfHeight, duration = configs.revealDuration, onFinish = { - configs.closeListener?.invoke() + configs.closeListener?.invoke(this) }) } } -} \ No newline at end of file +} + +fun ViewGroup.bindSearchView(menu: Menu, @IdRes id: Int, config: SearchView.Configs.() -> Unit = {}): SearchView { + val searchView = SearchView(context) + searchView.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT) + addView(searchView) + searchView.bind(this, menu, id, config) + return searchView +} -- cgit v1.2.3