Skip to content

Instantly share code, notes, and snippets.

@grandstaish
Last active September 13, 2022 05:53
Show Gist options
  • Save grandstaish/8ef494348f4c3358031f to your computer and use it in GitHub Desktop.
Save grandstaish/8ef494348f4c3358031f to your computer and use it in GitHub Desktop.
LayoutInflaterFactory examples
package nz.bradcampbell.app.presentation;
import android.content.Context;
import android.support.v4.view.LayoutInflaterFactory;
import android.util.AttributeSet;
import android.view.View;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
/**
* This layout inflater inflates all custom views in the "nz.bradcampbell.app.presentation.customviews"
* package so that you don't need to use fully qualified classnames in the XML layout files.
*/
public class CustomViewsLayoutInflaterFactory implements LayoutInflaterFactory {
private static final String CUSTOM_VIEWS_PACKAGE = "nz.bradcampbell.app.presentation.customviews.";
private static final Set<String> namesVisited = new HashSet<>();
private static final Map<String, Constructor<? extends View>> constructorMap = new HashMap<>();
private static final Class<?>[] constructorSignature = new Class[] {
Context.class, AttributeSet.class };
private AppCompatActivity appCompatActivity;
public CustomViewsLayoutInflaterFactory(AppCompatActivity appCompatActivity) {
this.appCompatActivity = appCompatActivity;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View result = null;
if (!namesVisited.contains(name)) {
namesVisited.add(name);
try {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = context.getClassLoader()
.loadClass(CUSTOM_VIEWS_PACKAGE + name).asSubclass(View.class);
constructorMap.put(name, clazz.getConstructor(constructorSignature));
} catch (ClassNotFoundException e) {
// This will get picked up by the system later. No need to handle here.
} catch (NoSuchMethodException e) {
// This is expected and will happen for any custom view that doesn't have
// the custom constructor required
}
}
Constructor<? extends View> constructor = constructorMap.get(name);
if (constructor != null) {
try {
constructor.setAccessible(true);
result = constructor.newInstance(context, attrs);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
if (result == null) {
// Try to let the Activity handle it (inflating fragments from XML)
result = appCompatActivity.onCreateView(name, context, attrs);
}
if (result == null) {
// Get themed views from app compat
result = appCompatActivity.getDelegate().createView(parent, name, context, attrs);
}
return result;
}
}
/**
* An example of using cloneInContext
*/
public class MainActivityCloneInContext extends AppCompatActivity {
private LayoutInflater layoutInflater;
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
layoutInflater = getLayoutInflater().cloneInContext(this);
LayoutInflaterCompat.setFactory(layoutInflater, new CustomViewsLayoutInflaterFactory());
setContentView(R.layout.activity_main);
}
@NonNull @Override public LayoutInflater getLayoutInflater() {
return layoutInflater;
}
@Override public Object getSystemService(@NonNull String name) {
if (name.equals(LAYOUT_INFLATER_SERVICE)) {
if (layoutInflater == null) {
layoutInflater = (LayoutInflater) super.getSystemService(name);
}
return layoutInflater;
}
return super.getSystemService(name);
}
}
package nz.bradcampbell.app.presentation;
import android.content.Context;
import android.support.v4.view.LayoutInflaterFactory;
import android.util.AttributeSet;
import android.view.View;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static nz.bradcampbell.app.util.Strings.startsWith;
/**
* This layout inflater will inflate any view with the constructor signature as follows:
* <init>(Context, AttributeSet, MainActivityComponent)
*/
public class MainActivityComponentViewFactory implements LayoutInflaterFactory {
private static final String PRESENTATION_PACKAGE = "nz.bradcampbell.app.presentation";
private static final Set<String> namesVisited = new HashSet<>();
private static final Map<String, Constructor<? extends View>> constructorMap = new HashMap<>();
private static final Class<?>[] constructorSignature = new Class[] {
Context.class, AttributeSet.class, MainActivityComponent.class };
private MainActivityComponent component;
public MainActivityComponentViewFactory(MainActivityComponent component) {
this.component = component;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View result = null;
if (startsWith(name, PRESENTATION_PACKAGE)) {
if (!namesVisited.contains(name)) {
namesVisited.add(name);
try {
// Class not found in the cache, see if it's real, and try to add it
Class<? extends View> clazz = context.getClassLoader()
.loadClass(name).asSubclass(View.class);
constructorMap.put(name, clazz.getConstructor(constructorSignature));
} catch (ClassNotFoundException e) {
// This will get picked up by the system later. No need to handle here.
} catch (NoSuchMethodException e) {
// This is expected and will happen for any custom view that doesn't have
// the custom constructor required
}
}
Constructor<? extends View> constructor = constructorMap.get(name);
if (constructor != null) {
try {
constructor.setAccessible(true);
result = constructor.newInstance(context, attrs, component);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
if (result == null) {
// Try to let the Activity handle it (inflating fragments from XML)
result = component.getActivity().onCreateView(name, context, attrs);
}
if (result == null) {
// Get themed views from app compat
result = component.getAppCompatDelegate().createView(parent, name, context, attrs);
}
return result;
}
}
/**
* An example of using the Activity as the factory
*/
public class MainActivityNoFactory extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public View onCreateView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
View result = super.onCreateView(parent, name, context, attrs);
// Custom view inflation here
if (result == null) {
if (TextUtils.equals(name, "CustomView")) {
result = new CustomView(context, attrs);
}
}
return result;
}
}
/**
* This is just sample code that shows how you can avoid reflection for inflating certain views,
* perhaps in performance critical situations.
*/
public class MyLayoutInflaterFactory implements LayoutInflaterFactory {
private AppCompatActivity appCompatActivity;
public MyLayoutInflaterFactory(AppCompatActivity appCompatActivity) {
this.appCompatActivity = appCompatActivity;
}
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View result = null;
if (TextUtils.equals(name, "DebugDrawerLayout")) {
result = new DebugDrawerLayout(context, attrs);
} else if (TextUtils.equals(name, "ScrimInsetsFrameLayout") {
result = new ScrimInsetsFrameLayout(context, attrs);
}
// and so on...
if (result == null) {
// Try to let the Activity handle it (inflating fragments from XML)
result = appCompatActivity.onCreateView(name, context, attrs);
}
if (result == null) {
// Get themed views from app compat
result = appCompatActivity.getDelegate().createView(parent, name, context, attrs);
}
return result;
}
}
@BFridge
Copy link

BFridge commented Aug 23, 2017

good job! learn a lot, thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment