mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
feat(layout-inflater,uidesigner): move child view index computation logic to viewgroup adapters
This commit is contained in:
@@ -17,13 +17,9 @@
|
||||
|
||||
package com.itsaky.androidide.uidesigner.models
|
||||
|
||||
import android.graphics.RectF
|
||||
import android.view.ViewGroup
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroupAdapter
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.HORIZONTAL
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.TOP_LEFT
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.VERTICAL
|
||||
import com.itsaky.androidide.inflater.internal.LayoutFile
|
||||
import com.itsaky.androidide.inflater.internal.ViewGroupImpl
|
||||
import com.itsaky.androidide.inflater.viewAdapter
|
||||
@@ -53,74 +49,17 @@ internal class UiViewGroup(file: LayoutFile, name: String, view: ViewGroup) :
|
||||
return childCount
|
||||
}
|
||||
|
||||
return when (adapter.getLayoutBehavior(this)) {
|
||||
TOP_LEFT -> childCount
|
||||
VERTICAL -> computeViewIndexVertically(x, y)
|
||||
HORIZONTAL -> computeViewIndexHorizontally(x, y)
|
||||
}
|
||||
return adapter.computeChildIndex(this, x, y)
|
||||
}
|
||||
|
||||
private fun computeViewIndexHorizontally(x: Float, y: Float): Int {
|
||||
get(0).apply {
|
||||
val rect = getViewRect(this)
|
||||
if (x < rect.left) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
get(childCount - 1).apply {
|
||||
val rect = getViewRect(this)
|
||||
if (x > rect.right) {
|
||||
return childCount
|
||||
}
|
||||
}
|
||||
val (child, index) = findNearestChild(x, y, false) ?: return childCount
|
||||
val rect = getViewRect(child)
|
||||
val mid = rect.left + (rect.width() / 2)
|
||||
val left = RectF(rect).apply { this.right = mid - 10 }
|
||||
val right = RectF(rect).apply { this.left = mid + 10 }
|
||||
if (x > left.left && x < left.right) {
|
||||
return index - 1
|
||||
}
|
||||
if (x > right.left && x < right.right) {
|
||||
return index + 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun computeViewIndexVertically(x: Float, y: Float): Int {
|
||||
get(0).apply {
|
||||
val rect = getViewRect(this)
|
||||
if (y < rect.top) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
get(childCount - 1).apply {
|
||||
val rect = getViewRect(this)
|
||||
if (y > rect.bottom) {
|
||||
return childCount
|
||||
}
|
||||
}
|
||||
val (child, index) = findNearestChild(x, y, true) ?: return childCount
|
||||
val rect = getViewRect(child)
|
||||
val mid = rect.top + (rect.height() / 2)
|
||||
val top = RectF(rect).apply { this.bottom = mid - 10 }
|
||||
val bottom = RectF(rect).apply { this.top = mid + 10 }
|
||||
if (y > top.top && y < top.bottom) {
|
||||
return index - 1
|
||||
}
|
||||
if (y > bottom.top && y < bottom.bottom) {
|
||||
return index + 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private fun findNearestChild(x: Float, y: Float, vertical: Boolean = true): Pair<IView, Int>? {
|
||||
override fun findNearestChild(x: Float, y: Float, vertical: Boolean
|
||||
): Pair<IView, Int>? {
|
||||
for (i in 0 until childCount) {
|
||||
val child = get(i)
|
||||
if (child is CommonUiView && !child.includeInIndexComputation) {
|
||||
continue
|
||||
}
|
||||
val rect = getViewRect(child)
|
||||
val rect = child.getViewRect()
|
||||
if (vertical && (y > rect.top && y < rect.bottom)) {
|
||||
return child to i
|
||||
}
|
||||
@@ -132,14 +71,4 @@ internal class UiViewGroup(file: LayoutFile, name: String, view: ViewGroup) :
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getViewRect(view: IView): RectF {
|
||||
val v = view.view
|
||||
val rect = RectF()
|
||||
rect.left = v.left.toFloat()
|
||||
rect.top = v.top.toFloat()
|
||||
rect.right = rect.left + v.width
|
||||
rect.bottom = rect.top + v.height
|
||||
return rect
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
import android.graphics.RectF
|
||||
import android.view.View
|
||||
|
||||
/**
|
||||
@@ -108,6 +109,27 @@ interface IView {
|
||||
*/
|
||||
fun onHighlightStateUpdated(highlight: Boolean)
|
||||
|
||||
/**
|
||||
* Get the bounds of the view.
|
||||
*
|
||||
* @return The bounds of the view.
|
||||
*/
|
||||
fun getViewRect(): RectF {
|
||||
return RectF().apply { getViewRect(this) }
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the bounds of the view.
|
||||
*
|
||||
* @return The [RectF] to store the bounds of the view.
|
||||
*/
|
||||
fun getViewRect(out: RectF) {
|
||||
out.left = view.left.toFloat()
|
||||
out.top = view.top.toFloat()
|
||||
out.right = out.left + view.width
|
||||
out.bottom = out.top + view.height
|
||||
}
|
||||
|
||||
/** Removes this view from its parent view. */
|
||||
fun removeFromParent() {
|
||||
parent?.removeChild(this)
|
||||
@@ -159,8 +181,12 @@ interface IView {
|
||||
}
|
||||
|
||||
open class SingleAttributeChangeListener : AttributeChangeListener {
|
||||
|
||||
override fun onAttributeAdded(view: IView, attribute: IAttribute) {}
|
||||
override fun onAttributeRemoved(view: IView, attribute: IAttribute) {}
|
||||
override fun onAttributeUpdated(view: IView, attribute: IAttribute, oldValue: String) {}
|
||||
override fun onAttributeUpdated(view: IView, attribute: IAttribute,
|
||||
oldValue: String
|
||||
) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,8 +51,8 @@ interface IViewGroup : IView, Iterable<IView> {
|
||||
* view's [IViewAdapter.canAcceptChild] returns `true`.
|
||||
*/
|
||||
fun canAcceptChild(name: String): Boolean {
|
||||
return canModifyChildViews() &&
|
||||
(this.viewAdapter as? IViewGroupAdapter)?.canAcceptChild(this, name) == true
|
||||
return canModifyChildViews() && (this.viewAdapter as? IViewGroupAdapter)?.canAcceptChild(
|
||||
this, name) == true
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -115,6 +115,17 @@ interface IViewGroup : IView, Iterable<IView> {
|
||||
return -1
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the child nearest to the given coordinates.
|
||||
*
|
||||
* @param x The X coordinate.
|
||||
* @param y The Y coordinate.
|
||||
* @param vertical Whether to compare the vertical or horizontal bounds of
|
||||
* existing child views with the given coordinates.
|
||||
*/
|
||||
fun findNearestChild(x: Float, y: Float, vertical: Boolean = true
|
||||
): Pair<IView, Int>?
|
||||
|
||||
/**
|
||||
* Adds the given hierarchy change listener.
|
||||
*
|
||||
@@ -167,6 +178,7 @@ interface IViewGroup : IView, Iterable<IView> {
|
||||
|
||||
/** Allows overriding a single method in [OnHierarchyChangeListener]. */
|
||||
open class SingleOnHierarchyChangeListener : OnHierarchyChangeListener {
|
||||
|
||||
override fun onViewAdded(group: IViewGroup, view: IView, index: Int) {}
|
||||
override fun onViewRemoved(group: IViewGroup, view: IView, index: Int) {}
|
||||
}
|
||||
|
||||
@@ -25,14 +25,26 @@ package com.itsaky.androidide.inflater
|
||||
interface IViewGroupAdapter {
|
||||
|
||||
/**
|
||||
* Get the layout behavior for the given view group and the view. Implementations must take the
|
||||
* Get the layout strategy for the given view group and the view. Implementations must take the
|
||||
* attributes of the view group into account.
|
||||
*
|
||||
* @param group The view group whose layout behavior should be returned.
|
||||
* @return The layout behavior.
|
||||
* @param group The view group whose layout strategy should be returned.
|
||||
* @return The layout strategy.
|
||||
*/
|
||||
fun getLayoutBehavior(group: IViewGroup): LayoutBehavior
|
||||
|
||||
fun getLayoutStrategy(group: IViewGroup): LayoutStrategy
|
||||
|
||||
/**
|
||||
* Compute the index of the child based on the given [x] and [y] coordinates
|
||||
* of the drag event.
|
||||
*
|
||||
* @param x The X coordinate of the drag event.
|
||||
* @param y The Y coordinate of the drag event.
|
||||
* @return The index at which the new child must be added
|
||||
*/
|
||||
fun computeChildIndex(parent: IViewGroup, x: Float, y: Float): Int {
|
||||
return getLayoutStrategy(parent).computeChildIndex(parent, x, y)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by the UI Designer to check if the given view group (which this adpater handles) can
|
||||
* accept the child view with the given qualified name.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
/**
|
||||
* Represents how an [IViewGroup] lays its children. This is used to determine the position of the
|
||||
* view that the user drags in the UI Designer's workspace.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
enum class LayoutBehavior {
|
||||
|
||||
/** The child views are laid out vertically. */
|
||||
VERTICAL,
|
||||
|
||||
/** The child views are laid out horizontally. */
|
||||
HORIZONTAL,
|
||||
|
||||
/**
|
||||
* The child views are placed on the top left corner of the view group. This is the default value.
|
||||
*/
|
||||
TOP_LEFT
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* This file is part of AndroidIDE.
|
||||
*
|
||||
* AndroidIDE is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* AndroidIDE is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with AndroidIDE. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.itsaky.androidide.inflater
|
||||
|
||||
import android.graphics.RectF
|
||||
|
||||
/**
|
||||
* Represents how an [IViewGroup] lays its children. This is used to determine the position of the
|
||||
* view that the user drags in the UI Designer's workspace.
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
interface LayoutStrategy {
|
||||
|
||||
companion object {
|
||||
|
||||
/** The child views are laid out vertically. */
|
||||
@JvmStatic
|
||||
val VERTICAL = VerticalLayoutStrategy()
|
||||
|
||||
/** The child views are laid out horizontally. */
|
||||
@JvmStatic
|
||||
val HORIZONTAL = HorizontalLayoutStrategy()
|
||||
|
||||
/**
|
||||
* The child views are placed on the top left corner of the view group. This is the default value.
|
||||
*/
|
||||
@JvmStatic
|
||||
val TOP_LEFT = TopLeftLayoutStrategy()
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the index of the child based on the given [x] and [y] coordinates
|
||||
* of the drag event.
|
||||
*
|
||||
* @param x The X coordinate of the drag event.
|
||||
* @param y The Y coordinate of the drag event.
|
||||
* @return The index at which the new child must be added
|
||||
*/
|
||||
fun computeChildIndex(parent: IViewGroup, x: Float, y: Float) : Int
|
||||
|
||||
class TopLeftLayoutStrategy : LayoutStrategy {
|
||||
override fun computeChildIndex(parent: IViewGroup, x: Float, y: Float): Int {
|
||||
return parent.childCount
|
||||
}
|
||||
}
|
||||
|
||||
class VerticalLayoutStrategy : LayoutStrategy {
|
||||
|
||||
override fun computeChildIndex(parent: IViewGroup, x: Float, y: Float
|
||||
): Int {
|
||||
return parent.run {
|
||||
get(0).apply {
|
||||
val rect = getViewRect()
|
||||
if (y < rect.top) {
|
||||
return@run 0
|
||||
}
|
||||
}
|
||||
|
||||
get(childCount - 1).apply {
|
||||
val rect = getViewRect()
|
||||
if (y > rect.bottom) {
|
||||
return@run childCount
|
||||
}
|
||||
}
|
||||
|
||||
val (child, index) = findNearestChild(x, y, true) ?: return@run childCount
|
||||
val rect = child.getViewRect()
|
||||
val mid = rect.top + (rect.height() / 2)
|
||||
val top = RectF(rect).apply { this.bottom = mid - 10 }
|
||||
val bottom = RectF(rect).apply { this.top = mid + 10 }
|
||||
|
||||
if (y > top.top && y < top.bottom) {
|
||||
return@run index - 1
|
||||
}
|
||||
|
||||
if (y > bottom.top && y < bottom.bottom) {
|
||||
return@run index + 1
|
||||
}
|
||||
|
||||
return@run index
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HorizontalLayoutStrategy : LayoutStrategy {
|
||||
|
||||
override fun computeChildIndex(parent: IViewGroup, x: Float, y: Float): Int {
|
||||
return parent.run {
|
||||
get(0).apply {
|
||||
val rect = getViewRect()
|
||||
if (x < rect.left) {
|
||||
return@run 0
|
||||
}
|
||||
}
|
||||
get(childCount - 1).apply {
|
||||
val rect = getViewRect()
|
||||
if (x > rect.right) {
|
||||
return@run childCount
|
||||
}
|
||||
}
|
||||
val (child, index) = findNearestChild(x, y, false) ?: return@run childCount
|
||||
val rect = child.getViewRect()
|
||||
val mid = rect.left + (rect.width() / 2)
|
||||
val left = RectF(rect).apply { this.right = mid - 10 }
|
||||
val right = RectF(rect).apply { this.left = mid + 10 }
|
||||
if (x > left.left && x < left.right) {
|
||||
return@run index - 1
|
||||
}
|
||||
if (x > right.left && x < right.right) {
|
||||
return@run index + 1
|
||||
}
|
||||
|
||||
return@run index
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
-25
@@ -29,9 +29,7 @@ import com.itsaky.androidide.inflater.AttributeHandlerScope
|
||||
import com.itsaky.androidide.inflater.INamespace
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.HORIZONTAL
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.VERTICAL
|
||||
import com.itsaky.androidide.inflater.LayoutStrategy
|
||||
import com.itsaky.androidide.inflater.internal.LayoutFile
|
||||
import com.itsaky.androidide.inflater.models.UiWidget
|
||||
import com.itsaky.androidide.inflater.utils.newAttribute
|
||||
@@ -47,7 +45,9 @@ import com.itsaky.androidide.resources.R.string
|
||||
@IncludeInDesigner(group = LAYOUTS)
|
||||
open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
|
||||
|
||||
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
|
||||
override fun createAttrHandlers(
|
||||
create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit
|
||||
) {
|
||||
super.createAttrHandlers(create)
|
||||
|
||||
create("baselineAligned") { view.isBaselineAligned = parseBoolean(value) }
|
||||
@@ -64,23 +64,16 @@ open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
|
||||
|
||||
override fun createUiWidgets(): List<UiWidget> {
|
||||
val horizontal =
|
||||
LinearLayoutWidget(
|
||||
title = string.widget_linear_layout_horz,
|
||||
icon = drawable.ic_widget_linear_layout_horz,
|
||||
isVertical = false
|
||||
)
|
||||
val vertical =
|
||||
LinearLayoutWidget(
|
||||
title = string.widget_linear_layout_vert,
|
||||
icon = drawable.ic_widget_linear_layout_vert,
|
||||
isVertical = true
|
||||
)
|
||||
LinearLayoutWidget(title = string.widget_linear_layout_horz,
|
||||
icon = drawable.ic_widget_linear_layout_horz, isVertical = false)
|
||||
val vertical = LinearLayoutWidget(title = string.widget_linear_layout_vert,
|
||||
icon = drawable.ic_widget_linear_layout_vert, isVertical = true)
|
||||
return listOf(horizontal, vertical)
|
||||
}
|
||||
|
||||
override fun getLayoutBehavior(group: IViewGroup): LayoutBehavior {
|
||||
override fun getLayoutStrategy(group: IViewGroup): LayoutStrategy {
|
||||
val orientation = group.findAttribute("orientation", INamespace.ANDROID.uri)
|
||||
return if (orientation?.value == "vertical") VERTICAL else HORIZONTAL
|
||||
return if (orientation?.value == "vertical") LayoutStrategy.VERTICAL else LayoutStrategy.HORIZONTAL
|
||||
}
|
||||
|
||||
protected open fun parseOrientation(value: String): Int {
|
||||
@@ -91,22 +84,23 @@ open class LinearLayoutAdapter<T : LinearLayout> : ViewGroupAdapter<T>() {
|
||||
}
|
||||
}
|
||||
|
||||
private class LinearLayoutWidget(
|
||||
@StringRes title: Int,
|
||||
@DrawableRes icon: Int,
|
||||
private val isVertical: Boolean
|
||||
private class LinearLayoutWidget(@StringRes title: Int,
|
||||
@DrawableRes icon: Int,
|
||||
private val isVertical: Boolean
|
||||
) : UiWidget(LinearLayout::class.java, title, icon) {
|
||||
|
||||
companion object {
|
||||
|
||||
private const val HORIZONTAL = "horizontal"
|
||||
private const val VERTICAL = "vertical"
|
||||
}
|
||||
|
||||
override fun createView(context: Context, parent: ViewGroup, layoutFile: LayoutFile): IView {
|
||||
override fun createView(context: Context, parent: ViewGroup,
|
||||
layoutFile: LayoutFile
|
||||
): IView {
|
||||
return super.createView(context, parent, layoutFile).apply {
|
||||
addAttribute(
|
||||
newAttribute(this, name = "orientation", value = if (isVertical) VERTICAL else HORIZONTAL)
|
||||
)
|
||||
addAttribute(newAttribute(this, name = "orientation",
|
||||
value = if (isVertical) VERTICAL else HORIZONTAL))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -22,19 +22,24 @@ import android.view.ViewGroup
|
||||
import com.itsaky.androidide.inflater.AttributeHandlerScope
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
import com.itsaky.androidide.inflater.IViewGroupAdapter
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior
|
||||
import com.itsaky.androidide.inflater.LayoutBehavior.TOP_LEFT
|
||||
import com.itsaky.androidide.inflater.LayoutStrategy
|
||||
import com.itsaky.androidide.inflater.LayoutStrategy.Companion.TOP_LEFT
|
||||
|
||||
/**
|
||||
* Base attribute adapter for all [ViewGroup].
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
abstract class ViewGroupAdapter<T : ViewGroup> : ViewAdapter<T>(), IViewGroupAdapter {
|
||||
abstract class ViewGroupAdapter<T : ViewGroup> : ViewAdapter<T>(),
|
||||
IViewGroupAdapter {
|
||||
|
||||
override fun createAttrHandlers(create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit) {
|
||||
override fun createAttrHandlers(
|
||||
create: (String, AttributeHandlerScope<T>.() -> Unit) -> Unit
|
||||
) {
|
||||
super.createAttrHandlers(create)
|
||||
create("animateLayoutChanges") { view.layoutTransition = LayoutTransition() }
|
||||
create("animateLayoutChanges") {
|
||||
view.layoutTransition = LayoutTransition()
|
||||
}
|
||||
create("clipChildren") { view.clipChildren = parseBoolean(value) }
|
||||
create("clipToPadding") { view.clipToPadding = parseBoolean(value) }
|
||||
create("descendantFocusability") {
|
||||
@@ -43,7 +48,7 @@ abstract class ViewGroupAdapter<T : ViewGroup> : ViewAdapter<T>(), IViewGroupAda
|
||||
create("layoutMode") { view.layoutMode = parseLayoutMode(value) }
|
||||
}
|
||||
|
||||
override fun getLayoutBehavior(group: IViewGroup): LayoutBehavior {
|
||||
override fun getLayoutStrategy(group: IViewGroup): LayoutStrategy {
|
||||
return TOP_LEFT
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user