Reputation: 1726
Sometimes we would like to add custom properties to default Android components or override properties that are already set, for example, text fields.
One solution is to extend the default component and add custom styleable attributes for Views. But this solution has one major disadvantage: You will have to extend every extended component of such View. For example, if you are extending TextView it won't applicate EditView unless you will extend it too.
Upvotes: 1
Views: 906
Reputation: 1726
The advantage of this solution is that is uses LayoutInflator Factory, layout does not have to be modified and it can be applied to already written code.
//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;
}
}
}
That's it, now only what you have to do is wrap the original inflator with the customized one:
@Override
public void setContentView(@LayoutRes int layoutResID) {
LayoutInflater inflator = inflater.cloneInContext(this);
inflator.setFactory2(CustomLayoutFactory());
setContentView(
inflator.inflate(layoutResID,null));
}
Upvotes: 1