如何使用 Android LayoutInflater Factory 注入和覆盖布局属性?

问题描述

有时我们希望将自定义属性添加认的 Android 组件或覆盖已设置的属性,例如文本字段。

一种解决方案是扩展认组件并为视图添加自定义样式属性。但该解决方案有一个主要缺点:您必须扩展此类视图的每个扩展组件。例如,如果您要扩展 TextView,它不会应用 EditView,除非您也扩展它。

解决方法

这个方案的优点是使用LayoutInflator Factory,布局不需要修改,可以应用于已经写好的代码。

//The LayoutInflater.Factory2 implementation
private class CustomLayoutFactory implements LayoutInflater.Factory2{
    private View createView(String name,Context context,AttributeSet attrs){
            View view = null;
            
            //For example if we need to filter the views only from the android.widget package
            //the rest of componans will be handled as ususal 
            
            String prefix = name.contains(".") ? null : "android.widget.";
            try {
                view = inflater.createView(name,prefix,attrs);
                handle(view);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }

            return view;
        }
    
    private static final class StyleableHelper{
        static int[] TextViewStyleable = new int[0];
        static int TextView_text = 0;
        static {
            try {
                if(Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
                    Class clazz = Class.forName("com.android.internal.R$styleable");
                    TextViewStyleable = (int[]) clazz.getField("TextView").get(null);
                    TextView_text = clazz.getField("TextView_text").getInt(null);
                }else{
                    //Pay attention if you are building apps for target SDK 30 and above the reflection for android internal objects are disabled
                    //In order to soleve this you have to lower the target SDK and extract styleable object and desired ids
                    TextViewStyleable = new int[]{16842766,16842804,16842901,16842902,16842903,16842904,16842905,16842906,16842907,16842923,16842927,16842928,16842929,16843039,16843040,16843071,16843072,16843086,16843087,16843088,16843089,16843090,16843091,16843092,16843093,16843094,16843095,16843096,16843097,16843098,16843099,16843100,16843101,16843102,16843103,16843104,16843105,16843106,16843107,16843108,16843109,16843110,16843111,16843112,16843113,16843114,16843115,16843116,16843117,16843118,16843119,16843120,16843121,16843287,16843288,16843293,16843296,16843299,16843300,16843364,16843365,16843366,16843461,16843462,16843463,16843540,16843541,16843542,16843614,16843615,16843618,16843636,16843660,16843666,16843667,16843692,16843869,16843958,16843959,16843990,16843991,16843997,16843998,16843999,16844085,16844086,16844087,16844088,16844102,16844135,16844144,16844155,16844157,16844158,16844159,16844165,16844178,17957193,17957194};
                    TextView_text = 18;
                }

            }catch (Exception e){
                Log.e(LOG_MODULE_TAG,"Styleable reflection failed",e);
            }
        }
    }   
        
    private void handle(View view){
         TypedArray typedArray = null;
            try {
                if(view instanceof TextView) {
                    typedArray = view.getContext().obtainStyledAttributes(attrs,StyleableHelper.TextViewStyleable);
                    CharSequence s = null;
                    try {
                        int textId = typedArray.getResourceId(StyleableHelper.TextView_text,-1);
                        if(textId > 0){
                            //Here we are finding a new resource for that text id that for exaple can be sent by server side
                            s = TextMapper.getInstance()
                                .getByResourceID(typedArray.getResourceId(StyleableHelper.TextView_text,-1));
                        }       
                    }catch (Resources.NotFoundException nfe) {
                        Log.e(LOG_TAG,"Resource for text: [" + typedArray.getString(StyleableHelper.TextView_text) + "] not found viewId: "
                                + Integer.toHexString(view.getId());
                    }catch (Exception e){
                        Log.e(LOG_TAG,"Unhandled exception on view id: " + Integer.toHexString(view.getId())
                                + " text: " + typedArray.getString(StyleableHelper.TextView_text));
                    }
                    if(s != null)
                        ((TextView) view).setText(s);
                }
            }finally {
                if(typedArray != null)
                    typedArray.recycle();
            }
            return view;
        }
    }
}

就是这样,现在您要做的就是用定制的充气机包裹原始充气机:

@Override
public void setContentView(@LayoutRes int layoutResID) {
        LayoutInflater inflator = inflater.cloneInContext(this);
        inflator.setFactory2(CustomLayoutFactory());
        setContentView(
                inflator.inflate(layoutResID,null));
} 
,

我建议为每种类型的小部件编写一个 BaseSuperClass。一开始是分配的,但这样您就可以完全控制每个小部件,并且可以将关注点分开。

相关问答

Selenium Web驱动程序和Java。元素在(x,y)点处不可单击。其...
Python-如何使用点“。” 访问字典成员?
Java 字符串是不可变的。到底是什么意思?
Java中的“ final”关键字如何工作?(我仍然可以修改对象。...
“loop:”在Java代码中。这是什么,为什么要编译?
java.lang.ClassNotFoundException:sun.jdbc.odbc.JdbcOdbc...