I have my own style for buttons defined as themes but I also use my own class to handle buttons (because of own fonts). Is it possible to call my button with a pretty name such as
<MyButton>
instead of
<com.wehavelongdomainname.android.ui.MyButton>
So the answer, surprisingly, is "yes". I learned about this recently, and it's actually something you can do to make your custom view inflation more efficient. IntelliJ still warns you that its invalid (although it will compile and run successfully) -- I'm not sure whether Eclipse warns you or not.
Anyway, so what you'll need to do is define your own subclass of LayoutInflater.Factory:
public class CustomViewFactory implements LayoutInflater.Factory {
private static CustomViewFactory mInstance;
public static CustomViewFactory getInstance () {
if (mInstance == null) {
mInstance = new CustomViewFactory();
}
return mInstance;
}
private CustomViewFactory () {}
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
//Check if it's one of our custom classes, if so, return one using
//the Context/AttributeSet constructor
if (MyCustomView.class.getSimpleName().equals(name)) {
return new MyCustomView(context, attrs);
}
//Not one of ours; let the system handle it
return null;
}
}
Then, in whatever activity or context in which you're inflating a layout that contains these custom views, you'll need to assign your factory to the LayoutInflater for that context:
public class CustomViewActivity extends Activity {
public void onCreate (Bundle savedInstanceState) {
//Get the LayoutInflater for this Activity context
//and set the Factory to be our custom view factory
LayoutInflater.from(this).setFactory(CustomViewFactory.getInstance());
super.onCreate(savedInstanceState);
setContentView(R.layout.layout_with_custom_view);
}
}
You can then use the simple class name in your XML:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<MyCustomView
android:id="#+id/my_view"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="center_vertical" />
</FrameLayout>
Defining your own subclass of LayoutInflater.Factory seems a lot of work me.
Simply override the Activity's onCreateView() with some generic code:
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view;
// No need wasting microseconds getting the inflater every time.
// This method gets called a great many times.
// Better still define these instance variables in onCreate()
if (mInflator == null){
mInflator = LayoutInflater.from(context);
mPrefix = ((Activity) context).getComponentName().getClassName();
// Take off the package name including the last period
// and look for custom views in the same directory.
mPrefix = mPrefix.substring(0, mPrefix.lastIndexOf(".")+1);
}
// Don't bother if 'a path' is already specified.
if (name.indexOf('.') > -1) return null;
try{
view = mInflator.createView(name, mPrefix, attrs);
} catch (ClassNotFoundException e) {
view = null;
} catch (InflateException e) {
view = null;
}
// Returning null is no big deal. The super class will continue the inflation.
return view;
}
Note the custom views must reside in the same package (i.e. in the same directory) as this activity, but then it's just a generic piece of code you can slap in any activity (or even better, inherit from a custom parent activity class).
You're not worried about looking out for a particular class as specified in the solution offered by kcoppock:
if (MyCustomView.class.getSimpleName().equals(name)) {....
You're certainly not creating a whole new class.
The real magic is in the core library class, LayoutInflator.java. See the call, mPrivateFactory.onCreateView(), below?:
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, mContext, attrs);
}
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
}
You see, if the so called, mPrivateFactory, returns null (mPrivateFactory happens to be your activity class by the way), the LayoutInflator just carries on with it's other alternative approach and continues the inflation:
if (view == null) {
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
}
It's a good idea to 'walk through' the library classes with your IDE debugger and really see how Android works. :)
Notice the code,if (-1 == name.indexOf('.')) {, is for you guys who still insist on putting in the full path with your custom views, <com.wehavelongdomainname.android.ui.MyButton> If there is a 'dot' in the name, then the creatview() is called with the prefix (the second parameter) as null: view = createView(name, null, attrs);
Why I use this approach is because I have found there were times when the package name is moved (i.e. changed) during initial development. However, unlike package name changes performed within the java code itself, the compiler does not catch such changes and discrepancies now present in any XML files. Using this approach, now it doesn't have to.
Cheers.
You can also do this:
<view
class="com.wehavelongdomainname.android.ui.MyButton"
... />
cf. http://developer.android.com/guide/topics/ui/custom-components.html#modifying
No. You need to to give "full path" to your class, otherwise framework will not be able to inflate your layout.
Related
I have a view that inherits from ConstraintLayout. Inside this layout I place the children by use of a ConstraintSet.
This works as long as use a given AttributeSet form outside in the constructor:
public AnswerView(Context context, AttributeSet attrs) {
super(context, attrs);
It does not work, if I try to assign the layout otherwise, when I don't have attrs available.
public AnswerView(Context context) {
super(context);
setLayoutParams(new LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT));
In the first case the children get a pretty distribution like defined by the ConstraintSet, in the second case the all line up at the left border.
In both cases the layout stretches full width, as I can prove by setting a background color.
What is missing in my code?
This is not a direct answer to the question. A direct answer is still missing. This answer is of the type, "take a better approach!".
When there are few or no answers to a question, I am usually on a track, that few people go. Then there are reasons why they don't.
My approach to programatically nest views within other views is cool, but it turns out to be difficult to do this without the use of layouts. It's too expensive to set up all the benefits of the configurations programatically, that can easily done within a layout. The API of Android is not well prepared for this.
So I turned back to the approach, to create the view classes based on layouts. This means, I create the view with the two parameter constructor for layouts.
In the enclosing view class I can't create the nested view directly any more, as there is no constructor for this. Instead I read the configured view from a layout.
I created a small class to assist extracting configured subparts of a layout:
public class Cloner {
LayoutInflater inflater;
int layoutId;
public Cloner of(Context context) {
inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
return this;
}
public Cloner from(#LayoutRes Integer layoutId) {
this.layoutId = layoutId;
return this;
}
public View clone(#IdRes int id) {
assert inflater != null;
View layout = inflater.inflate(layoutId, null);
View view = layout.findViewById(id);
((ViewManager) view.getParent()).removeView(view);
return view;
}
}
It is used like this:
MultipleChoiceAnswerView mcav =
(MultipleChoiceAnswerView) new Cloner().of(getContext())
.from(layoutId).clone(R.id.multipleChoiceAnswerView);
mcav.plugModel(challenge.getAnswer());
This already shows, how I connect the model in a second step, because I can't feed it into by the constructor any more.
In the constructor I first evaluate the given attributes. Then I set up the view by inflating an accompanying second layout file, that I don't show here. So there are two layouts involved, one to configure the input to the constructor, one for the internal layout.
When the call to plugModel happens, the inflated internal layout is used and extended by objects matching the given model. Again I don't create this objects programatically, but read them from a third (or the second) layout file as templates. Again done with the assistance of the Cloner given above.
private Button getButton(final Integer index, String choice) {
Button button = (Button) new Cloner().of(getContext()).from(layoutId).clone(R.id.button);
button.setId(generateViewId());
button.setText(choice);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
answer.choiceByIndex(index);
}
});
return button;
}
In practice I put this object templates (like a button) as children into the second layout file. So I can style it as a whole in Android Studio Designer.
Programatically I remove the templates before I actually fill the layout with the cloned objects. By this approach I only need to maintain one layout file per view class. (The configuration of the constructor happens in the layout file of the enclosing view.)
I have tried to change the background color of options menu in my android app. I am using ActionBarSherlock library. I have tried this code for changing the background color of options menu
https://stackoverflow.com/a/8475357/584095
But I ended up with an exception "java.lang.illegalstateexception: a factory has already been set on this layoutinflater" at line
LayoutInflater.setFactory();
I don't know what is wrong in this code. Can anyone help me in resolving this issue?
There been a change in support library since version 22.1.0.
You will get an IllegalStateException if you try to call getLayoutInflater().setFactory()
You should use the new api
LayoutInflaterCompat.setFactory()
AppCompatActivity instead of deprecated ActionBarActivity
Or simply use the old version
com.android.support:appcompat-v7:22.0.0
com.android.support:appcompat-v4:22.0.0
This happens because you are using compatibility library. It sets own Factory to deal with platform specific layouts.
You may try to set own factory in onCreate() method before calling super.onCreate(). This will disallow compatibility library to override factory and you will be unable to inflate fragments from xml files, but styling should work.
To keep compatibility library working and avoid "java.lang.illegalstateexception: a factory has already been set on this layoutinflater", you need to get a final reference to the already set Factory and call its onCreateView within your own Factory.onCreateView. Before that an introspection trick must be use to allow you to set one more time a Factory to the LayoutInflater :
LayoutInflater layoutInflater = getLayoutInflater();
final Factory existingFactory = layoutInflater.getFactory();
// use introspection to allow a new Factory to be set
try {
Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
field.setAccessible(true);
field.setBoolean(layoutInflater, false);
getLayoutInflater().setFactory(new Factory() {
#Override
public View onCreateView(String name, final Context context, AttributeSet attrs) {
View view = null;
// if a factory was already set, we use the returned view
if (existingFactory != null) {
view = existingFactory.onCreateView(name, context, attrs);
}
// do whatever you want with the null or non-null view
// such as expanding 'IconMenuItemView' and changing its style
// or anything else...
// and return the view
return view;
}
});
} catch (NoSuchFieldException e) {
// ...
} catch (IllegalArgumentException e) {
// ...
} catch (IllegalAccessException e) {
// ...
}
This works for me:
LayoutInflater inflater = LayoutInflater.from(context);
if (inflater.getFactory() != null) {
inflater = inflater.cloneInContext(context);
}
inflater.setFactory(factory);
I faced the same issue but no answer helped me.
In my case:
I'm extending AppCompatActivity
Project targeting API 27
Implementation support lib 27.1.1
Solution:
Apparently, now there is no need of setting the Factory to the LayoutInflater, it would crash or be ignored. Just override both methods onCreateView(...) in your AppCompatActivity (from android.support.v4.app.BaseFragmentActivityApi14)
public class myActivity extends AppCompatActivity {
...
#Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
if (name.compareTo("EditText") == 0) {
CustomEdit newEdit = new CustomEdit(this,attrs);
return newEdit;
}
return super.onCreateView(parent, name, context, attrs);
}
#Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}
...
}
I try my hardest to develop a clever way to clean out piles of Blah blah = (Blah) this.findViewById(R.id.blah) that otherwise pollute the field and the onCreate() method of my little Activity, and to do so, I feel I should not use setContentView() but getViewInflate().inflate() for every View defined in XMLs.
Is Activity.setContentView() is sorta a syntax sugar and it's virtually repeating getViewInflate().inflate() for every View on XML? I read something saying as if they were the same.
If I can get an answer by looking into the code, please tell so. I checked Activity.class, but only comments were found.
The setContentView on your Activity actually calls the setContentView on the Window used by the activity, which itself does a lot more than just inflating the layout.
What you could do is to map the views to the class field using reflexion. You can download a utility class on Github that does this.
It will parse all the views declared in the layout, then try to find the name corresponding to the id in your R.id class. Then it will try to find a field with the same name in the target object and set it with the corresponding view.
For example, if you have a layout like this
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
it will automatically map it to a textView1 field in your activity.
I'm posting my poor research. In summary, Activity.setContentView() delegates PhoneWindow.setContentView() (the only concrete class of Window ) within which LayoutInflater.inflate() is called, so saying "setContentView() == ViewInflate().inflate()" is not so overly off, I guess.
public class Activity extends ContextThemeWrapper{
private Window mWindow;
public void setContentView(int layoutResID) {
getWindow().setContentView(layoutResID);
initActionBar();
}
public Window getWindow() {
return mWindow;
}
}
public class PhoneWindow extends Window {
private LayoutInflater mLayoutInflater;
#Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else {
mContentParent.removeAllViews();
}
**mLayoutInflater.inflate(layoutResID, mContentParent);**
final Callback cb = getCallback();
if (cb != null) {
cb.onContentChanged();
}
}
}
Actually you're right, there's two ways to achieve the same thing:
1) setContentView(R.layout.layout);
2)
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = inflater.inflate(R.layout.layout, null);
setContentView(v);
You decide what is more appropriate for you. Hope this helps.
I am customizing an Android control. This control uses a merge layout containing an edit field (fluff left out):
<merge xmlns:android="http://schemas.android.com/apk/res/android">
...
<EditText android:id="#+id/numberpicker_input">
...
/>
...
</merge>
This works fine for a single control. However, if I put multiple instances of this control in a layout, it results in weird behaviour when rotating a device from portrait to landscape. All controls get the same value restored and callbacks get attached to the wrong control instance.
Upon examination, it emerged that the embedded edit controls have the exact same id value.
The relevant java source looks like this:
public class NumberPicker extends LinearLayout implements OnClickListener,
OnFocusChangeListener, OnLongClickListener, OnEditorActionListener {
private final EditText mText;
....
public NumberPicker(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
Log.d(TAG, "Numberpicker create, have id: " + getId() );
setOrientation(VERTICAL);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.number_picker, this, true);
...
mText = (EditText) findViewById(R.id.numberpicker_input);
...
}
}
Now, consider the value of R.id.numberpicker_input, when you have three separate instances of a NumberPicker in a layout. In each instance, the edittext field will have the same id.
To get the custom control to work properly, I need the id's of the embedded edit controls to be unique. How can I fix this?
Here's another option, with onSave/RestoreInstanceState(). Doing this the usual way doesn't work, because the framework will restore the EditText value right after the call to onRestoreInstanceState(), overwriting anything you had restored.
To avoid this, clear the id of the embedded control in the constructor of the compound control, eg.:
mText.setId( NO_ID );
Layout elements with id set are ignored while saving/restoring.
Now, save and restore the embedded control value in the compound control state:
protected Parcelable onSaveInstanceState() {
Parcelable p = super.onSaveInstanceState();
Bundle bundle = new Bundle();
bundle.putString( "EDIT_TEXT", mText.getText().toString() );
bundle.putParcelable("SUPER", p);
return bundle;
}
protected void onRestoreInstanceState(Parcelable parcel) {
Bundle bundle = (Bundle) parcel;
mText.setText( bundle.getString("EDIT_TEXT") );
super.onRestoreInstanceState(bundle.getParcelable("SUPER"));
}
The restored state is specific to a given instance of the compound object, therefore there will be no interference between multiple instances in the same view.
The advantage of this wrt. previous answer is that no fiddling with id's is necessary.
you may create unique IDs in xml resources as below:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="firstButton"/>
<item type="id" name="secondButton"/>
<item type="id" name="thirdButton"/>
</resources>
and in code, design your EditTexts programmatically and set their ids like:
txt1.setId(R.id.firstButton);
txt2.setId(R.id.secondButton);
txt3.setId(R.id.thirdButton);
for more info, read this
Update.....
another approach would be to iterate through all the views and set them unique ids yourself and keep track of those assigned ids:
first of all, set an id to your parent layout in XML file. It will help us to retrieve the entire layout as ViewGroup. In this example I've set my LinearLayout the id parentLayout
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.test_activity);
ViewGroup parent = (ViewGroup)findViewById(R.id.parentLayout);
for(int i=0; i<parent.getChildCount(); i++){
//at this point i dont know the Id of edittext yet
View v = parent.getChildAt(i);
//if the view is an instance of EditText then lets set to it a new Id
if(v instanceof EditText){
EditText et = (EditText) v;
et.setId(123456);
Toast.makeText(this,"EditText's id: " + et.getId(),
Toast.LENGTH_SHORT).show();
}
}
}
I guess using this technique, you may even assign some random ids to all your views in order to avoid any id collision.
I've been looking at the code for saving instance state in the Android libs. What I noticed is that the control/layout id's are used as keys in the saved state. I think here lies the problem: the id's of the embedded elements in the multiple instances of the compound objects are the same. So on save, the state of just one embedded element is saved, the rest is discarded.
I think the solution lies in making the id's of the embedded elements unique. My solution is this:
Define an attribute which passes an id for the embedded control:
<resources>
<declare-styleable name="compoundcontrol">
<attr name="idEmbedded" format="reference" />
</declare-styleable>
</resources>
Pass this id as parameter to the compound control in the layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:np="http://schemas.android.com/apk/res/org.example.compoundcontrol"
...
/>
...
<org.example.compoundcontrol.CompoundControl
android:id = "#+id/compound_id"
np:idEmbedded="#+id/embedded_id"
/>
...
In the constructor of the compound object, read in this attribute value and use it to set the id of the embedded control.
public CompoundControl(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes( attrs, R.styleable.compoundcontrol );
int new_id = a.getResourceId( R.styleable.compoundcontrol_idEmbedded, DEFAULT_VALUE );
a.recycle();
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.compoundcontrol, this, true);
View mEmbedded = findViewById(R.id.embedded_control);
mEmbedded.setId( new_id );
}
It is unfortunate that the id of the embedded control has to be set so explicitly, but it does the trick.
Another possibility to get new resource ids is this little hack.
public class MyBaseDialogFragment extends DialogFragment implements
ResourceIdGetter {
Random mRandom;
#Override
public int getNewResourceId() {
if(mRandom == null)
mRandom = new Random(123456789); // make them predictable
int i;
while (true) {
i = mRandom.nextInt();
if(i <= 0)
continue;
try {
getResources().getResourceName(i);
} catch (NotFoundException e) {
break;
}
}
return i;
}
}
After that, you can reassign new unique resource id's to controls with ids:
protected mNewResorceId;
public void addEditViewToLinearLayout(LinearLayout pContainer,
LayoutInflater pInflater, ResourceIdGetter pRes) {
View a_multiple_inflated_layout = pInflater.inflate(
R.layout.a_multiple_inflated_layout, pContainer, false);
mNewResorceId = pRes.getNewResourceId()
a_multiple_inflated_layout.findViewById(R.id.child_view_with_id).setId(
mNewResorceId);
pContainer.addView(a_multiple_inflated_layout, new LayoutParams(
LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT));
}
After that, you have to use findViewById(mNewResorceId) to get the view.
Because of the given seed for Random, the assigned numbers are always the same. That means, even if the activity is destroyed and recreated, things like cursor position are restored.
I have created a Custom preference which has the following constructor
public CoordinatesPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
setLayoutResource(R.layout.coordinates_preference);
}
And I have Overriden onCreateView() so it writes to the log like this:
#Override
protected View onCreateView(ViewGroup parent)
{
Log.d("test", "Creating Preference view");
return super.onCreateView(parent);
}
and my log is full of "Creating Preference view" messages, this creates a laggy feel to scrolling and I believe convert view is supposed to solve this, I had a look at the preference source code and if convert view is null then onCreateView() is called.
for testing purposes I added this method:
#Override
public View getView(View convertView, ViewGroup parent)
{
if (convertView == null)
{
return super.getView(convertView, parent);
}
return super.getView(convertView, parent);
}
and set a break point. I have found that almost always my convert view is null. and therefore it must create a new view, why is this? and how can I improve this to avoid a laggy preference screen?
EDIT: Changed the way the onCreate is called, now its all android I just use setLayoutResource. but this does not solve the problem...
EDIT2: I have used Debug.StartMethodTracing() and have found as I suspected that 55% of the time spend (when I'm just scrolling up and down) is spend on the Inflation of the preference from the method onCreateView() which is called from getView() when convertView is null.
Thanks, Jason
I don't know what have you implemented in this custom preference, but maybe the super class doesn't know how create a proper view to your preference?
From the documentation:
protected View onCreateView (ViewGroup
parent)
Since:
API Level 1 Creates the View to
be shown for this Preference in the
PreferenceActivity. The default
behavior is to inflate the main layout
of this Preference (see
setLayoutResource(int). If changing
this behavior, please specify a
ViewGroup with ID widget_frame. Make
sure to call through to the
superclass's implementation.
http://developer.android.com/reference/android/preference/Preference.html
I guess you have set that id on the horizontal layout.
Now that I'm talking about it, why don't you include this horizontal layout in the layout that you are inflating?
I am not sure if the code you are using is an accurate test. I have a custom preference and I only override 5 methods and three of them are constructors.
public ImageButtonPreference(Context context)
{
this(context, null);
}
public ImageButtonPreference(Context context, AttributeSet attrs)
{
this(context, attrs, 0);
}
public ImageButtonPreference(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
mInflater = LayoutInflater.from(context);
// This is where I pull all of the styleable info from the attrs
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ImageButtonPreference, defStyle, 0);
for(int i = a.getIndexCount(); i >= 0; i--)
{
int attr = a.getIndex(i);
switch (attr)
{
case R.styleable.ImageButtonPreference_logo:
mImageResource = a.getResourceId(attr, mImageResource);
break;
case R.styleable.ImageButtonPreference_button_text:
mButtonText = a.getString(attr);
break;
}
}
}
#Override
protected View onCreateView(ViewGroup parent)
{
View view = mInflater.inflate(R.layout.image_button_preference, parent, false);
return view;
}
#Override
protected void onBindView(View view)
{
super.onBindView(view);
ImageView image = (ImageView)view.findViewById(R.id.Logo);
if(image != null && mImageResource != 0) image.setImageResource(mImageResource);
Button button = (Button)view.findViewById(R.id.ConnectButton);
if(button != null)
{
button.setText(mButtonText);
button.setOnClickListener(mButtonListener);
}
}
I have pulled this code, almost verbatim from the Android Source, so it should be just as fast as any other preference.
I was running into this problem and I tracked it down to the fact that I had the layout set both in the preferences.xml file and in my Preference subclass onCreateView() method. When I removed the layout from preferences.xml, onCreateView() stopped getting called multiple times.
As a more general answer, not specifically relating to custom preferences:
It's hard to see with the code you posted, but if your needs to pull a preference every time it creates a view, it will be VERY slow and laggy as you describe. Even if the view does exist, you still need to set the value, and it sounds like that needs to come from the preference. Android preference reads are incredibly slow, so they can't be associated with UI creation if you want a good fast experience.
I think you should store the preferences in the app (perhaps in the Activity, or subclass the Application and store them in there), in order to implement some simple caching. i.e. the first time you need a preference, request it from your app's store, if it's not there, pull it out of the preferences. If the preference is stored in the activity/application already, use that value without reaching out to the prefs. Then, when you write prefs out, write to the store AND the preference. By doing that it doesn't matter how often getView() needs to create new views, as the preference can be accessed quickly using the copy in the activity/application object, but also stored durably in the preferences for the future.
I don't know whether the preferences framework has some caching in it somewhere, but my experience of loading prefs is that if a few need loading the user WILL notice a lag, so caching is essential.