mirror of
https://gitee.com/zhangsken/AndroidIDE.git
synced 2026-10-16 07:49:44 +08:00
Fix: Invalid XML code generated by UI Designer
This commit is contained in:
@@ -21,6 +21,10 @@ package com.itsaky.widgets;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import com.itsaky.widgets.models.Widget;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
@@ -28,26 +32,39 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Collection;
|
||||
import java.util.SortedMap;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class WidgetInfo {
|
||||
|
||||
private final SortedMap<String, Widget> widgets;
|
||||
|
||||
public WidgetInfo (final Context ctx) throws IOException {
|
||||
public WidgetInfo (@NonNull final Context ctx) throws IOException {
|
||||
this.widgets = new TreeMap<>();
|
||||
readWidgets(ctx.getResources());
|
||||
}
|
||||
|
||||
public Widget getWidgetBySimpleName (String simpleName) {
|
||||
return this.widgets.getOrDefault(simpleName, null);
|
||||
public Widget getWidget (String name) {
|
||||
return this.widgets.getOrDefault(name, null);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Widget getWidgetBySimpleName (@NonNull final String name) {
|
||||
for (final var entry : this.widgets.entrySet ()) {
|
||||
final var key = entry.getKey ();
|
||||
final var simple = key.substring (key.lastIndexOf (".") + 1);
|
||||
if (simple.equals (name)) {
|
||||
return entry.getValue ();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<Widget> getWidgets() {
|
||||
return this.widgets.values();
|
||||
}
|
||||
|
||||
private void readWidgets(final Resources resources) throws IOException {
|
||||
private void readWidgets(@NonNull final Resources resources) throws IOException {
|
||||
final InputStream in = resources.openRawResource(com.itsaky.sdkinfo.R.raw.widgets);
|
||||
if(in == null) return;
|
||||
|
||||
@@ -60,17 +77,12 @@ public class WidgetInfo {
|
||||
final char code = name.charAt(0);
|
||||
|
||||
final String viewName = name.substring(1);
|
||||
final String simpleViewName = simpleName(viewName);
|
||||
final boolean isViewGroup = code == 'L'; // L -> Layout, W -> Widget, P -> LayoutParam
|
||||
|
||||
// Don't add layout params
|
||||
if(code != 'P') {
|
||||
widgets.put(simpleViewName, new Widget(viewName, simpleViewName, isViewGroup));
|
||||
widgets.put(viewName, new Widget(viewName, isViewGroup));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String simpleName(String name) {
|
||||
return name.substring(name.lastIndexOf(".") + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,24 +19,26 @@
|
||||
**************************************************************************************/
|
||||
package com.itsaky.widgets.models;
|
||||
|
||||
public class Widget implements Comparable {
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
public class Widget implements Comparable <Widget> {
|
||||
|
||||
public String name;
|
||||
public String simpleName;
|
||||
public boolean isViewGroup;
|
||||
|
||||
public Widget(String name, String simpleName, boolean isViewGroup) {
|
||||
public Widget(@NonNull String name, boolean isViewGroup) {
|
||||
this.name = name;
|
||||
this.simpleName = simpleName;
|
||||
this.simpleName = name.substring (name.lastIndexOf (".") + 1);
|
||||
this.isViewGroup = isViewGroup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Object p1) {
|
||||
if(p1 instanceof Widget) {
|
||||
Widget that = (Widget) p1;
|
||||
public int compareTo(Widget that) {
|
||||
if(that != null) {
|
||||
return this.simpleName.compareTo(that.simpleName);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
@@ -34,6 +34,7 @@ import com.itsaky.attrinfo.AttrInfo;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewAttrAdapter;
|
||||
import com.itsaky.inflater.adapters.android.view.ViewGroupAttrAdapter;
|
||||
import com.itsaky.inflater.impl.BaseView;
|
||||
import com.itsaky.inflater.impl.ErrorUiView;
|
||||
import com.itsaky.inflater.impl.UiAttribute;
|
||||
import com.itsaky.inflater.impl.UiView;
|
||||
import com.itsaky.inflater.impl.UiViewGroup;
|
||||
@@ -43,6 +44,7 @@ import com.itsaky.widgets.models.Widget;
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@@ -104,7 +106,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
IView root = onCreateView (doc.child (0), parent);
|
||||
|
||||
if (root == null) {
|
||||
root = onCreateErrorView (TextView.class.getName (), "No views added");
|
||||
root = onCreateErrorView (TextView.class.getName (), "No views added", parent);
|
||||
}
|
||||
|
||||
// Notify
|
||||
@@ -267,7 +269,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
final Widget widget = widgetInfo.getWidgetBySimpleName (name);
|
||||
if (widget == null) {
|
||||
LOG.error ("Unable to inflate view. widget == null");
|
||||
return onCreateErrorView (name, getString (com.itsaky.inflater.R.string.msg_cannot_create_view, name));
|
||||
return onCreateErrorView (name, getString (com.itsaky.inflater.R.string.msg_cannot_create_view, name), parent);
|
||||
}
|
||||
|
||||
return createFromQualifiedName (widget.name, parent, style);
|
||||
@@ -278,6 +280,13 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
|
||||
// TODO Try to load classes directly from .class files if possible
|
||||
try {
|
||||
|
||||
final var androidView = this.widgetInfo.getWidget (name);
|
||||
if (androidView == null) {
|
||||
// If this is not an Android view, do not bother to create one
|
||||
throw new InflateException (String.format ("%s is not supported yet.", name));
|
||||
}
|
||||
|
||||
final View created = createAndroidViewForName (name);
|
||||
final BaseView view =
|
||||
created instanceof ViewGroup
|
||||
@@ -287,7 +296,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
} catch (Throwable th) {
|
||||
final var msg = getString (R.string.msg_cannot_create_view, name);
|
||||
LOG.error (msg, th);
|
||||
return onCreateErrorView (name, msg);
|
||||
return onCreateErrorView (name, msg, parent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -295,8 +304,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
protected View createAndroidViewForName (String name) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, java.lang.reflect.InvocationTargetException {
|
||||
final Class<? extends View> loaded = Class.forName (name).asSubclass (View.class);
|
||||
final Constructor<? extends View> constructor = loaded.getConstructor (Context.class /*, AttributeSet.class, int.class*/);
|
||||
final View created = constructor.newInstance (contextProvider.getContext ()/*, null, style*/);
|
||||
return created;
|
||||
return constructor.newInstance (contextProvider.getContext ()/*, null, style*/);
|
||||
}
|
||||
|
||||
protected IView applyLayoutParams (@NonNull IView view, ViewGroup parent) {
|
||||
@@ -305,18 +313,9 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
return view;
|
||||
}
|
||||
|
||||
protected IView onCreateErrorView (String name, String msg) throws InflateException {
|
||||
|
||||
final Context ctx = contextProvider.getContext ();
|
||||
assertNotnull (ctx, "Context is null!");
|
||||
|
||||
final TextView error = new TextView (ctx);
|
||||
error.setText (msg);
|
||||
error.setLayoutParams (new ViewGroup.LayoutParams (-2, -2));
|
||||
error.setTextColor (ContextCompat.getColor (ctx, android.R.color.black));
|
||||
error.setBackgroundColor (ContextCompat.getColor (ctx, android.R.color.white));
|
||||
|
||||
return new UiView (name, error, true);
|
||||
protected IView onCreateErrorView (String name, String msg, final ViewGroup parent) throws InflateException {
|
||||
final var view = ErrorUiView.create (Objects.requireNonNull (contextProvider.getContext (), "Context is null."), name, msg);
|
||||
return applyLayoutParams (view, parent);
|
||||
}
|
||||
|
||||
private String getString (@StringRes int id, Object... format) throws InflateException {
|
||||
@@ -329,6 +328,7 @@ class XMLLayoutInflater extends BaseLayoutInflater {
|
||||
try {
|
||||
final Class<?> clazz = parent.getClass ();
|
||||
Method method = null;
|
||||
|
||||
try {
|
||||
method = clazz.getDeclaredMethod ("generateDefaultLayoutParams");
|
||||
} catch (NoSuchMethodException | SecurityException e) {
|
||||
|
||||
-1
@@ -72,7 +72,6 @@ public class ViewAttrAdapter extends CommonParseUtils implements IAttributeAdapt
|
||||
final String name = attribute.getAttributeName();
|
||||
final String value = attribute.getValue();
|
||||
final ViewGroup.LayoutParams params = view.getLayoutParams();
|
||||
final Resources res = view.getResources();
|
||||
final Context ctx = view.getContext();
|
||||
|
||||
if (!canHandleNamespace(namespace)) {
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* 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.inflater.impl;
|
||||
|
||||
import android.content.Context;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
import com.itsaky.inflater.IView;
|
||||
|
||||
import org.jetbrains.annotations.Contract;
|
||||
|
||||
/**
|
||||
* A view which is used to show views that cannot be inflated.
|
||||
* This view will preserver the attributes and the children
|
||||
* if the view is a view group;
|
||||
*/
|
||||
public class ErrorUiView extends UiViewGroup {
|
||||
|
||||
private ErrorUiView (String qualifiedName, ViewGroup view) {
|
||||
super (qualifiedName, view);
|
||||
}
|
||||
|
||||
private ErrorUiView (String qualifiedName, ViewGroup view, boolean isPlaceholder) {
|
||||
super (qualifiedName, view, isPlaceholder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new error view for the given qualified name of the view
|
||||
* that was not inflated correctly.
|
||||
*
|
||||
* We add a text directly in this view. Any other views that will
|
||||
* be added to this group will have visibility {@link View#GONE}
|
||||
* by default.
|
||||
*
|
||||
* @param context The context that will be used to create the view.
|
||||
* @param name The qualified name of the view that was not inflated.
|
||||
* @param message The message that should be shown to the user.
|
||||
* @return A newly created instance of the error view.
|
||||
*/
|
||||
@NonNull
|
||||
@Contract("_, _, _ -> new")
|
||||
public static IView create (@NonNull final Context context, @NonNull final String name, @NonNull final String message) {
|
||||
final var container = new LinearLayout (context);
|
||||
final var error = new TextView (context);
|
||||
error.setText (message);
|
||||
error.setLayoutParams (new ViewGroup.LayoutParams (-2, -2));
|
||||
error.setTextColor (ContextCompat.getColor (context, android.R.color.black));
|
||||
error.setBackgroundColor (ContextCompat.getColor (context, android.R.color.white));
|
||||
|
||||
container.setOrientation (LinearLayout.HORIZONTAL);
|
||||
container.addView (error, new LinearLayout.LayoutParams (ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
|
||||
return new ErrorUiView (name, container, true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addView (IView view, int index) {
|
||||
// Adding the view at the given index will result in an error
|
||||
// This is because the children list is different in this error
|
||||
// view. Because of TextView added in ErrorView.create (...);
|
||||
// So we simply add the view at index 0 and make it invisible
|
||||
super.addView (view, 0);
|
||||
|
||||
view.asView ().setVisibility (View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getXmlTag () {
|
||||
// This might be a custom view or something
|
||||
// So return the qualified name of the view
|
||||
return this.qualifiedName;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public String generateCode (int indentCount) {
|
||||
return super.generateCode (indentCount);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user