在Fragment中使用自定义XML属性而无需自定义View

6

我正在尝试在现有的视图中添加自定义XML属性。

将它们添加到自定义视图中并不困难,但我不知道如何在“基本”视图(如TextView、LinearLayout、ImageView等)中访问这些属性。

当涉及到Fragment和/或Library项目时,这更加困难。

到目前为止,这是我的代码:

自定义属性定义和XML(attrs.xml和布局):

<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="StarWars">
    <attr name="jedi" format="string" />
    <attr name="rank" format="string" />
</declare-styleable>

<LinearLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:sw="http://schemas.android.com/apk/res-auto"
  android:layout_width="fill_parent"
  android:layout_height="fill_parent">

<TextView
    android:id="@+id/name"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="28dp"
    android:gravity="center_horizontal"
    sw:jedi="Obiwan" />

<TextView
    android:id="@+id/rank"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:textSize="28dp"
    android:gravity="center_horizontal"
    sw:rank="Master" />

由于我将其放在片段上膨胀(因此没有带有 attrs 参数的 onCreate!),尝试获取 2 个自定义 sw 属性的唯一方法是:

  1. Set a custom LayoutInflater.Factory to the Fragment LayoutInflater in onCreateView

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
      super.onCreateView(inflater, container, savedInstanceState);
    
      LayoutInflater layoutInflater = inflater.cloneInContext(inflater.getContext());
      layoutInflater.setFactory(new StarWarsLayoutFactory());
    
      View fragmentView = layoutInflater.inflate(R.layout.jedi, container, false);
      ((TextView) fragmentView.findViewById(android.R.id.jedi)).setText("Yep");
    
      return fragmentView;
    }
    
  2. Trying to get the custom attributes on the custom LayoutInflater.Factory :

    public class StarWarsLayoutFactory implements Factory {
      @Override
      public View onCreateView(String name, Context context, AttributeSet attrs) {
                  *** What to do here ? ***
    
          return null;
      }
    }
    

有人有过这样的疑问吗?

我在这里缺少什么?

提前感谢!

2个回答

2
我终于完成了这个:)
您需要创建一个新的LayoutInflater.Factory,就像我在OP上所做的那样,但是由于Factory用于所有充气布局视图,并且您必须在Factory.onCreateView中返回null(让Android处理充气),因此您必须将自定义XML属性缓存到某个地方。
所以这里是解决方案:
  • 您的布局XML视图必须有一个android:id

  • 创建类,它将保留您的自定义属性:

    public class AttributeParser {

    private AttributeParserFactory mFactory;
    private Map<Integer, HashMap<Integer, String>> mAttributeList;

    private class AttributeParserFactory implements LayoutInflater.Factory{
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            String id = attrs.getAttributeValue("http://schemas.android.com/apk/res/android", "id");

            if(id != null){
                // String with the reference character "@", so we strip it to keep only the reference
                id = id.replace("@", "");

                TypedArray libraryStyledAttributeList = context.obtainStyledAttributes(attrs, R.styleable.NewsHubLibrary);
                HashMap<Integer, String> libraryViewAttribute = new HashMap<Integer, String>();
                int i = 0;

                for(int attribute : R.styleable.NewsHubLibrary){
                    String attributeValue = libraryStyledAttributeList.getString(i);

                    if(attributeValue != null)
                        libraryViewAttribute.put(attribute, attributeValue);

                    i++;
                }

                if(!libraryViewAttribute.isEmpty())
                    mAttributeList.put(Integer.valueOf(id), libraryViewAttribute);

                libraryStyledAttributeList.recycle();
            }

            return null;
        }

    }

    public AttributeParser(){
        mAttributeList = new HashMap<Integer, HashMap<Integer, String>>();
        mFactory = new AttributeParserFactory();
    }

    public void clear() {
        mAttributeList.clear();
    }

    public LayoutInflater getLayoutInflater(LayoutInflater inflater) {
        clear();
        LayoutInflater layoutInflater = inflater.cloneInContext(inflater.getContext());
        layoutInflater.setFactory(mFactory);

        return layoutInflater;
    }

    public void setFactory(LayoutInflater inflater){
        inflater.cloneInContext(inflater.getContext()).setFactory(mFactory);
    }

    public void setViewAttribute(Activity activity) {
        for(Entry<Integer, HashMap<Integer, String>> attribute : mAttributeList.entrySet())
            if(activity.findViewById((Integer) attribute.getKey()) != null)
                activity.findViewById((Integer) attribute.getKey()).setTag(attribute.getValue());

    }

    public void setViewAttribute(View view) {
        for(Entry<Integer, HashMap<Integer, String>> attribute : mAttributeList.entrySet())
            if(view.findViewById((Integer) attribute.getKey()) != null)
                view.findViewById((Integer) attribute.getKey()).setTag(attribute.getValue());
    }

    public Map<Integer, HashMap<Integer, String>> getAttributeList() {
        return mAttributeList;
    }

    public void setAttributeList(Map<Integer, HashMap<Integer, String>> attributeList) {
        this.mAttributeList = attributeList;
    }
    }
  • 当您使用AttributeParser时,您自定义的属性将存储在每个View标记中:

    LayoutInflater layoutInflater = mAttributeParser.getLayoutInflater(inflater);
    View view = layoutInflater.inflate(R.layout.jedi, null);
    mAttributeParser.setViewAttribute(view);


你也可以分享一下你的attrs.xml文件吗? - Erik B
它在支持库v4:22.0.0下运行良好,但在更高版本(例如support-v4:23.0.1)中,onCreateView不会被调用,mAttributeList的大小为0。为什么? - Ragaisis
我真的不知道,这个解决方法很久以前就出现了。请检查一下Google是否改变了LayoutInflater的工作方式。 - Plumillon Forge
可能需要使用 Factory2。 - Jeremy

0

标准的TextView等控件无法访问这些属性。但您可以扩展它并提供包括读取这些自定义属性的功能。

TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.StarWars, 0, 0);

String jedi = a.getText(R.styleable.StarWars_jedi);
String rank = a.getText(R.styleable.StarWars_rank);

a.recycle();

记得在最后调用recycle()


是的,我知道可以使用扩展视图来实现,但我需要在所有视图中实现,无论是扩展视图还是普通视图。 - Plumillon Forge

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接