mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Implemented ID parser
This commit is contained in:
Binary file not shown.
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/included_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<include layout="@layout/layout_include"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:id="@+id/inlined_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<aapt:attr name="android:background">
|
||||
<shape android:id="@+id/my_shape">
|
||||
<solid android:color="#f44336"/>
|
||||
</shape>
|
||||
</aapt:attr>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,21 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/included_parent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/included_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="This is an included layout"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/included_text_other"
|
||||
android:layout_below="@id/included_text"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"
|
||||
android:text="This is an included layout"/>
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -1,4 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<View xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/single_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
@@ -23,6 +23,7 @@ import android.graphics.drawable.ColorDrawable
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.android.aaptcompiler.XmlResource
|
||||
import com.itsaky.androidide.inflater.R
|
||||
import java.io.File
|
||||
|
||||
@@ -31,7 +32,7 @@ import java.io.File
|
||||
*
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
class ErrorView(file: File, context: Context, message: String) :
|
||||
class ErrorView(file: LayoutFile, context: Context, message: String) :
|
||||
ViewImpl(file = file, name = NAME, view = createErrView(context, message)) {
|
||||
companion object {
|
||||
private val NAME = View::class.java.name
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* 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.internal
|
||||
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* A data class to hold information about the file which was used to inflate and create a [ViewImpl]
|
||||
* instance.
|
||||
*
|
||||
* @property file The actual file.
|
||||
* @property resName The resource name.
|
||||
* @author Akash Yadav
|
||||
*/
|
||||
data class LayoutFile(val file: File, val resName: String)
|
||||
+32
-14
@@ -19,12 +19,12 @@ package com.itsaky.androidide.inflater.internal
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import com.itsaky.androidide.inflater.InflateException
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroup.LayoutParams
|
||||
import com.android.aapt.Resources.SourcePosition
|
||||
import com.android.aapt.Resources.XmlElement
|
||||
import com.android.aapt.Resources.XmlNode.NodeCase.ELEMENT
|
||||
import com.android.aaptcompiler.AaptResourceType.ID
|
||||
import com.android.aaptcompiler.AaptResourceType.LAYOUT
|
||||
import com.android.aaptcompiler.BlameLogger
|
||||
import com.android.aaptcompiler.ResourceFile
|
||||
@@ -36,8 +36,10 @@ import com.itsaky.androidide.inflater.IInflateEventsListener
|
||||
import com.itsaky.androidide.inflater.ILayoutInflater
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
import com.itsaky.androidide.inflater.InflateException
|
||||
import com.itsaky.androidide.inflater.InflationFinishEvent
|
||||
import com.itsaky.androidide.inflater.InflationStartEvent
|
||||
import com.itsaky.androidide.inflater.internal.utils.IDTable
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import com.itsaky.androidide.utils.ILogger
|
||||
@@ -55,17 +57,22 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
|
||||
override var inflationEventListener: IInflateEventsListener? = null
|
||||
private val log = ILogger.newInstance("LayoutInflaterImpl")
|
||||
private var inflatingFile: File? = null
|
||||
private var _primaryInflatingFile: File? = null
|
||||
private var _currentLayoutFile: LayoutFile? = null
|
||||
|
||||
protected val primaryInflatingFile: File
|
||||
get() = this._primaryInflatingFile!!
|
||||
|
||||
protected val file: File
|
||||
get() = this.inflatingFile!!
|
||||
protected val currentLayoutFile: LayoutFile
|
||||
get() = _currentLayoutFile!!
|
||||
|
||||
override fun inflate(file: File, parent: ViewGroup): IView? {
|
||||
this.inflatingFile = file
|
||||
this._primaryInflatingFile = file
|
||||
IDTable.newRound()
|
||||
inflationEventListener?.onEvent(InflationStartEvent())
|
||||
return doInflate(file, wrap(parent)).apply {
|
||||
inflationEventListener?.onEvent(InflationFinishEvent(this))
|
||||
inflatingFile = null
|
||||
_primaryInflatingFile = null
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,7 +97,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
|
||||
val processor = XmlProcessor(pathData.source, BlameLogger(IDELogger))
|
||||
processor.process(resFile, file.inputStream())
|
||||
|
||||
|
||||
return doInflate(processor, parent, module)
|
||||
}
|
||||
|
||||
@@ -101,18 +108,29 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
): IView? {
|
||||
// TODO(itsaky) : Add test for multiple view as root layout
|
||||
// The inflater should fail in such cases
|
||||
val (_, node) =
|
||||
val (file, node) =
|
||||
processor.xmlResources.find { it.file == processor.primaryFile }
|
||||
?: throw InflateException("Unable to find primary XML resource from XmlProcessor")
|
||||
|
||||
|
||||
this._currentLayoutFile = LayoutFile(this.primaryInflatingFile, file.name.entry!!)
|
||||
|
||||
if (node.nodeCase != ELEMENT) {
|
||||
throw InflateException(
|
||||
"Found ${node.nodeCase} but $ELEMENT was expected at ${node.source.lineCol()}"
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Store all IDs
|
||||
file.exportedSymbols.filter { it.name.type == ID }.forEach {
|
||||
IDTable.set(currentLayoutFile.resName, it.name.entry!!, View.generateViewId())
|
||||
}
|
||||
|
||||
val element = node.element
|
||||
return onCreateView(element, parent, module)
|
||||
val view = onCreateView(element, parent, module)
|
||||
|
||||
this._currentLayoutFile = null
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
protected open fun onCreateView(
|
||||
@@ -195,7 +213,7 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
): IView {
|
||||
return try {
|
||||
val v = createViewInstance(widget, parent)
|
||||
return ViewImpl(file, widget.qualifiedName, v)
|
||||
return ViewImpl(currentLayoutFile, widget.qualifiedName, v)
|
||||
} catch (err: Throwable) {
|
||||
onCreateUnsupportedView("Unable to create view for widget ${widget.qualifiedName}", parent)
|
||||
}
|
||||
@@ -233,11 +251,11 @@ open class LayoutInflaterImpl : ILayoutInflater() {
|
||||
}
|
||||
|
||||
private fun onCreateUnsupportedView(message: String, parent: ViewGroup): IView {
|
||||
return ErrorView(file, parent.context, message)
|
||||
return ErrorView(currentLayoutFile, parent.context, message)
|
||||
}
|
||||
|
||||
private fun wrap(parent: ViewGroup): IViewGroup {
|
||||
return ViewGroupImpl(file, parent.javaClass.name, parent)
|
||||
return ViewGroupImpl(currentLayoutFile, parent.javaClass.name, parent)
|
||||
}
|
||||
|
||||
private fun SourcePosition.lineCol(): String {
|
||||
|
||||
@@ -18,11 +18,12 @@
|
||||
package com.itsaky.androidide.inflater.internal
|
||||
|
||||
import android.view.ViewGroup
|
||||
import com.android.aaptcompiler.XmlResource
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
import java.io.File
|
||||
|
||||
open class ViewGroupImpl(file: File, name: String, view: ViewGroup) :
|
||||
open class ViewGroupImpl(file: LayoutFile, name: String, view: ViewGroup) :
|
||||
ViewImpl(file = file, name = name, view = view), IViewGroup {
|
||||
|
||||
protected val children = mutableListOf<IView>()
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package com.itsaky.androidide.inflater.internal
|
||||
|
||||
import android.view.View
|
||||
import com.android.aaptcompiler.XmlResource
|
||||
import com.itsaky.androidide.inflater.IAttribute
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.IViewGroup
|
||||
@@ -29,7 +30,7 @@ import java.io.File
|
||||
open class ViewImpl
|
||||
@JvmOverloads
|
||||
constructor(
|
||||
internal val file: File,
|
||||
internal val file: LayoutFile,
|
||||
override val name: String,
|
||||
override val view: View,
|
||||
override val simpleName: String = name.simpleName(),
|
||||
|
||||
+5
-4
@@ -29,13 +29,14 @@ import com.itsaky.androidide.inflater.IAttribute
|
||||
import com.itsaky.androidide.inflater.IAttributeAdapter
|
||||
import com.itsaky.androidide.inflater.INamespace
|
||||
import com.itsaky.androidide.inflater.IView
|
||||
import com.itsaky.androidide.inflater.internal.LayoutFile
|
||||
import com.itsaky.androidide.inflater.internal.ViewImpl
|
||||
import com.itsaky.androidide.inflater.internal.utils.endParse
|
||||
import com.itsaky.androidide.inflater.internal.utils.parseId
|
||||
import com.itsaky.androidide.inflater.internal.utils.parseString
|
||||
import com.itsaky.androidide.inflater.internal.utils.startParse
|
||||
import com.itsaky.androidide.projects.ProjectManager
|
||||
import com.itsaky.androidide.projects.api.AndroidModule
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Attribute adapter for [View].
|
||||
@@ -69,7 +70,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
|
||||
"foregroundGravity" -> foregroundGravity = parseGravity(value)
|
||||
"foregroundTint" -> foregroundTintList = parseColorStateList(context, value)
|
||||
"foregroundTintMode" -> foregroundTintMode = parsePorterDuffMode(value)
|
||||
"id" -> {}
|
||||
"id" -> parseId(file.resName, value)
|
||||
"minHeight" -> minimumWidth = parseDimension(context, value)
|
||||
"minWidth" -> minimumHeight = parseDimension(context, value)
|
||||
"padding" ->
|
||||
@@ -127,7 +128,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
|
||||
attribute: IAttribute,
|
||||
block:
|
||||
T.(
|
||||
file: File,
|
||||
file: LayoutFile,
|
||||
context: Context,
|
||||
layoutParams: ViewGroup.LayoutParams,
|
||||
namespace: INamespace,
|
||||
@@ -141,7 +142,7 @@ open class ViewAttrAdapter : IAttributeAdapter() {
|
||||
(view.view as T).apply {
|
||||
val file = (view as ViewImpl).file
|
||||
val module =
|
||||
ProjectManager.findModuleForFile(file) as? AndroidModule
|
||||
ProjectManager.findModuleForFile(file.file) as? AndroidModule
|
||||
?: throw IllegalStateException("Cannot find module for file: $file")
|
||||
startParse(module)
|
||||
block(file, context, layoutParams, attribute.namespace, attribute.name, attribute.value)
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.internal.utils
|
||||
|
||||
internal object IDTable {
|
||||
|
||||
private val ids = mutableMapOf<String, MutableMap<String, Int>>()
|
||||
|
||||
fun newRound() {
|
||||
ids.clear()
|
||||
}
|
||||
|
||||
fun get(file: String, name: String): Int? {
|
||||
return ids[file]?.get(name)
|
||||
}
|
||||
|
||||
fun set(file: String, name: String, id: Int) {
|
||||
ids.computeIfAbsent(file) { mutableMapOf() }[name] = id
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -440,7 +440,7 @@ fun <T> resolveResourceReference(
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseResourceReference(value: String): Triple<String?, AaptResourceType, String>? {
|
||||
internal fun parseResourceReference(value: String): Triple<String?, AaptResourceType, String>? {
|
||||
return tryParseReference(value)?.let {
|
||||
val pck = it.reference.name.pck
|
||||
val type = it.reference.name.type
|
||||
|
||||
+6
-2
@@ -19,9 +19,13 @@ package com.itsaky.androidide.inflater.internal.utils
|
||||
|
||||
import android.graphics.PorterDuff
|
||||
import android.view.View
|
||||
import com.android.aaptcompiler.AaptResourceType.ID
|
||||
import com.itsaky.androidide.inflater.internal.ViewImpl
|
||||
|
||||
fun parseId(value: String) : Int {
|
||||
return 0
|
||||
@JvmOverloads
|
||||
fun parseId(resName: String, value: String, def: Int = 0): Int {
|
||||
val name = parseResourceReference(value)?.third ?: return 0
|
||||
return IDTable.get(resName, name) ?: def
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
|
||||
@@ -53,6 +53,22 @@ class LayoutInflaterTest {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test inlined layout`() {
|
||||
inflaterTest {
|
||||
requiresActivity { activity ->
|
||||
val file = layoutFile("inlined")
|
||||
val parent = LinearLayout(activity)
|
||||
val inflater = ILayoutInflater.newInflater()
|
||||
val inflated = inflater.inflate(file, parent)
|
||||
inflated.apply {
|
||||
assertThat(this).isNotNull()
|
||||
assertThat(this).isInstanceOf(LinearLayout::class.java)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun layoutFile(name: String): File {
|
||||
val app = ProjectManager.app ?: throw IllegalStateException("Project is not initialized")
|
||||
|
||||
Reference in New Issue
Block a user