Customizing the layout of a PreferenceScreen - android

My requirements
Note: I need to support Android API 15 and onwards.
In my PreferenceFragment I am dynamically adding PreferenceScreen's to a PreferenceCategory.
PreferenceScreen as = mgr.createPreferenceScreen(context);
as.setTitle(name);
myCategory.addPreference(as);
When the settings Activity renders these PreferenceScreens are rendered with just a title in it's row, see the image below.
I want to customize what is shown in this row. Ideally I would like to have a title with a summary below, an icon to the left of the title/summary and on certain rows an icon on the right hand side. See the mockup image below.
PreferenceScreen.setWidgetLayoutResource(int widgetLayoutResId)
I know I can use "setWidgetLayoutResource" to add an icon to the right of the row layout (and a summary with "setSummary") using the following code in my settings activity
PreferenceScreen as = mgr.createPreferenceScreen(context);
as.setTitle(name);
as.setSummary(summary);
as.setWidgetLayoutResource(R.layout.as_widget);
myCategory.addPreference(as);
where "as_widget.xml" is
<?xml version="1.0" encoding="utf-8"?>
<ImageView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_action_favorite"/>
this will produce UI like the following
This gets me closer to what I would like but still not exactly what I would like (missing the icon at the beginning of the row).
PreferenceScreen.setLayoutResource(int layoutResId)
I believe if I use this, then I can control the rendering of the whole row. I have seen examples of this on stackoverflow, such as
Setting preference layout and changing the attribute in it
Creating a custom layout for preferences
From my understanding the layout file you specify has to have the following
its root View with the id "#android:id/widget_frame"
a view with the id android:id="#+android:id/title" (this is where the
string specified in PreferenceScreen.setTitle is placed)
a view with the id android:id="#+android:id/summary" (this is where
the string specified in PreferenceScreen.setSummary is placed)
However when I try and do this, Android Studio highlights "#+android:id/summary" with the error "Can not resolve symbol '#+android:id/summary'. When the application runs my rows are rendered completely blank.
My java is
PreferenceScreen as = mgr.createPreferenceScreen(context);
as.setTitle(name);
as.setSummary(summary);
as.setLayoutResource(R.layout.as_row_layout);
myCategory.addPreference(as);
And my layout is
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/widget_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize">
<ImageView
android:id="#+id/icon1"
android:src="#drawable/ic_action_email"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="15dip"
android:layout_marginRight="6dip"
android:layout_marginTop="6dip"
android:layout_marginBottom="6dip"
android:layout_weight="1">
<TextView android:id="#+android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee"
android:fadingEdge="horizontal" />
<TextView android:id="#+android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="#android:id/title"
android:layout_alignLeft="#android:id/title"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorSecondary"
android:maxLines="4" />
</RelativeLayout>
<ImageView
android:id="#+id/icon2"
android:src="#drawable/ic_action_favorite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
Extending PreferenceScreen
I looked at extended PreferenceScreen to overwrite how it renders, but it appears this class has now been made final so all examples on the internet that do it that way can not be used.

I have managed to get this working.
The Preference class uses com.android.internal.R.layout.preference as its layout. This contains an ImageView for an icon on the left hand side, then the title and summary textviews and finally a widget_frame Layout on the right hand side.
By calling "PreferenceScreen.setIcon(..)" you can set the drawable to place in the icon image view. By calling PreferenceScreen.setWidgetLayoutResource("...") you can set the layout to place in the widget_frame layout, in my case I put an ImageView layout containing my image.
Here is my Java code.
PreferenceScreen as = mgr.createPreferenceScreen(context);
as.setTitle(name);
as.setSummary(summary);
// add an icon to the PreferenceScreen,
// this is rendered on the left hand side of the row
accountScreen.setIcon(R.drawable.my_pref_icon);
// specify the layout to inflate into the widget area on the
// right hand side of the row, this layout is just my image
as.setWidgetLayoutResource(R.layout.as_widget);
myCategory.addPreference(as);
This produces a layout like the following
The problem with this layout is that the icons do not left align with the text of the preferences below which have no icons.
This can be resolved by specifying the layout for the PreferenceScreen as well. I copied Android's preference.xml into my project (renaming it appropriately for my usecase) and I changing the ImageView to have a left padding and margin of 0dp.
From
<ImageView
android:id="#+android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
/>
to
<ImageView
android:id="#+android:id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingLeft="0dp"
android:layout_marginLeft="0dp"/>
I then specified my copy of preference.xml for the PreferenceScreen's layout. So my java is now
PreferenceScreen as = mgr.createPreferenceScreen(context);
as.setTitle(name);
as.setSummary(summary);
// add an icon to the PreferenceScreen,
// this is rendered on the left hand side of the row
accountScreen.setIcon(R.drawable.my_pref_icon);
// specify the layout to inflate into the widget area on the
// right hand side of the row, this layout is just my image
as.setWidgetLayoutResource(R.layout.as_widget);
// specify the layout for the preference screen row when it is
// rendered as a row in a preference activity/fragment
as.setLayoutResource(R.layout.preference_row_layout);
myCategory.addPreference(as);
I believe the reason my original attempt at using PreferenceScreen.setLayoutResource was not working was because the layout I specified was incorrect. The incorrect layout had the whole layout with an id of #android:id/widget_frame, i.e.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/widget_frame" ...>
.....
</LinearLayout>
The correct layout does not need an id for the main layout, but needs to contain child views with ids of #+android:id/icon, #+android:id/title, #+android:id/summary, #+android:id/widget_frame, i.e.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
....>
<ImageView android:id="#+android:id/icon" ....>
....
<TextView android:id="#+android:id/title" ...>
....
<TextView android:id="#+android:id/summary" ...>
....
<LinearLayout android:id="#+android:id/widget_frame" ..>
....
</LinearLayout>

You can also customise the layout of a Preference by overriding Preference.onCreateView(parent). The example below uses an anonymous inner class to make red preferences.
screen.addPreference(
new Preference(context) {
#Override
protected View onCreateView(ViewGroup parent) {
View view = super.onCreateView(parent);
view.setBackgroundColor(Color.RED);
return view;
}
});

Related

View's Background Color Removed When Using RelativeLayout In ListView

While working with ListViews I ran into an interesting issue. So my goal was to write an xml layout for a custom ListView item design.
First of all, here is the problematic xml layout (problem described below):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:background="?android:attr/activatedBackgroundIndicator" >
<TextView
android:id="#+id/notebook_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceLarge"
android:layout_toRightOf="#+id/notebook_color_tag"
/>
<View
android:id="#+id/notebook_color_tag"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
/>
</RelativeLayout>
The layout is fairly basic, just a View, that's used to display a color, and a TextView.
I have a subclass of ListFragment which uses an instance of a subclass of CursorAdapter in its setListAdapter() method. In that custom CursorAdpater's bindView() I set the View's color and the TextView's text (that code is not the problem, you can ignore it, it's just here for referrence):
#Override
public void bindView(View view, Context context, Cursor cursor) {
TextView notebookName = (TextView) view.findViewById(R.id.notebook_title);
View colorTag = view.findViewById(R.id.notebook_color_tag);
String name = cursor.getString(NOTEBOOK_NAME_POS);
int color = cursor.getInt(NOTEBOOK_COLOR_POS);
notebookName.setText(name);
colorTag.setBackgroundColor(color);
}
Now, the problem is that if the View's android:layout_height is set to match_parent or wrap_content, the View simply does not appear in the ListView's items. It seems as it has some margin at the top. If a concrete width value is specified, for example: android:layout_height="40dp", everything works fine.
Here is how the bogus layout looks:
Here is how it should look:
As explained above, the blue rectangle does not seem to be inside the ListView's item container at all (but the element is there, it's accessible via findViewById() and it's color does get changed via the setBackgroundColor()call, the View simply seems not to be displayed).
The follwoing change to the layout fixes the problem:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"
android:orientation="horizontal"
android:background="?android:attr/activatedBackgroundIndicator" >
<View
android:id="#+id/notebook_color_tag"
android:layout_marginTop="0dp"
android:layout_width="20dp"
android:layout_height="match_parent"
/>
<TextView
android:id="#+id/notebook_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceLarge"
/>
</LinearLayout>
I understand that LinearLayout makes more sense here than the RelativeLayout, but I'm curious what was wrong in the RelativeLayout's case.
You have duplicate 'notebook_color_tag' ID's. You are adding it twice in the first layout XML (as a parameter in the TextView and also as a child of the layout).
Also, I would reverse the order, like so:
<View
android:id="#+id/notebook_color_tag"
android:layout_width="20dp"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
/>
<TextView
android:id="#+id/notebook_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:textAppearanceLarge"
android:layout_toRightOf="#id/notebook_color_tag"
/>
Since the RelativeLayout will create and place in the order of the XML, this is more efficient (it does not have to remeasure to place the views correctly.

RelativeLayout background stretches when inner view aligned to parent right

The following XML results in the below image.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/convoLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/right_bubble">
<TextView
android:id="#+id/convoBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true" // cause
android:padding="5dp"
android:text="test test test test"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
Is anyone aware of how to get it to look like this:
Note: I need the RelativeLayout as this is a dumbed down version of my XML :).
EDIT:
Let me also clarify. Without android:layout_alignParentRight="true" in the TextView, the layout looks like the image below (there is white padding on the right)
In essence, I need that padding on the left.
EDIT2:
To further clarify, #drawable/right_bubble is a 9-patch image that is meant to stretch to house the inner views dynamically and the RelativeLayout is to have 3 TextViews inside of it, in which the 9-patch image should encapsulate all 3.
EDIT3:
This is what I want the end result to look like
you are setting the background to relative layout... so, whole layout will have same background no matter what views it holds. Try setting "android:background="#drawable/right_bubble" to TextView instead.
EDIT (by OP after getting answer from comments):
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/convoLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="right" // added
android:background="#drawable/right_bubble" >
<TextView
android:id="#+id/convoBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content" // removed layout_alignParentRight
android:padding="5dp"
android:text="test test test test"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
J.Ajendra's answer got me most of the way there. His answer (that I edited in after understanding his comments) worked, but only if the RelativeLayout's container honors android:layout_gravity. (for example, if we were to Activity#setContentView(R.layout.convo_item)).
In my case, convo_item.xml was to be inflated into a ListView which overrides this behavior for RelativeLayouts (I'm not sure why, but it does) resulting in the chat bubble always being on the left. To solve this, I simply wrapped the RelativeLayout in a LinearLayout that took the parents entire width (android:layout_width="match_parent") and set the gravity of it's content to "right" (android:gravity="right"). The result, it works for all cases including ListViews and Activity#setContentView(...).
Here's the code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/convoOuterLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="right" >
<RelativeLayout
android:id="#+id/convoLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/right_bubble" >
<TextView
android:id="#+id/convoBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="test test test test"
android:textAppearance="?android:attr/textAppearanceMedium" />
</RelativeLayout>
</LinearLayout>

Android Button and drawable area

I am novice on Android programming.
My task is simple: I want to create a screen with just two objects:
an action Button;
a drawable area in wich to draw images, text, circles, and so on.
Is it possible so have a working example, or at least a guideline ?
I know how to subclass a view, and to draw int it, using:
MyView d = new MyView(this);
setContentView(d);
But this fills all the screen with MyView and the button is not visible.
Some suggestions ?
You need to define a layout file and specify relative position of button and the drawable area.
Make sure both of them are not specified as fill_parent in layout_width or layout-height.
Set the contentView to this layout file
You go through android Ui for detail understanding.
When you create an android project in eclipse you'll find res/layout/main.xml and this is where your default UI is defined and that is set using setContentView(R.layout.main); in onCreate method.
To put images you can use imageview and textview for Texts in xml. Like that many widgets are there for edit text, Button etc. A simple example including Imageview,textview and Button:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/ic_launcher" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="HELLO ANDROID"
android:layout_gravity="center"
/>
<Button
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="Click Me"
/>

Crashes whenever I try to use a custom view object in XML layout

I have made myself a custom LinearLayout by the name of com.theflyingnerd.DroidMe.DiscreteNumericalRangeSelectorWidget that hosts a couple of spinner widgets. This custom LinearLayout inflates the following XML layout (You might not need to look at this too carefully but it's here for completeness):
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content">
<!-- Min value Spinner -->
<Spinner
android:id="#+id/discrete_numerical_range_selector_min_value_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:layout_weight="1" />
<TextView
android:id="#+id/to_text"
android:text="to"
android:textSize="14sp"
android:paddingLeft="10sp"
android:paddingRight="10sp"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center"
android:layout_weight="0">
</TextView>
<!-- Max value Spinner -->
<Spinner
android:id="#+id/discrete_numerical_range_selector_max_value_spinner"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="right"
android:layout_weight="1" />
I have placed one such object in the layout for one of my activities like so:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<include layout="#layout/search_form_section_generic_top"/>
<include layout="#layout/search_form_section_car_specific"/>
<com.theflyingnerd.DroidMe.DiscreteNumericalRangeSelectorWidget/>
<include layout="#layout/search_form_section_advanced_options" />
</LinearLayout>
The problem is that my app force closes immediately upon startup. I've checked by putting breakpoints in my custom LinearLayout that none of my custom code is even being run yet. Furthermore, if I copy-paste the layout code for my compound widget in place everything works, which indicates to me that I probably haven't left any important XML attributes out. What could be going wrong?
I fixed it by making the LinearLayout XML element in the widget layout into a merge instead, and moved all of the layout parameters out of the widget XML file and into the activity XML itself, thus replacing
<com.theflyingnerd.DroidMe.DiscreteNumericalRangeSelectorWidget/>
with
<com.theflyingnerd.DroidMe.DiscreteNumericalRangeSelectorWidget
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
If someone could tell me why this worked, it might help me and others doing it again, and you can take credit.
because you must specify the width and height of every view you use in you xml?

View-tag dosen't work

I have some preoblems when it comes to designing a view, As you can see I have open my xml file with a view-tag, and it gives me error. why is that? if I change it to LinearLayout than it works.
<View xmlns:android="http://schemas.android.com/apk/res/android"
class="android.app.SearchDialog$SearchBar"
android:id="#+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:focusable="true"
android:descendantFocusability="afterDescendants">
<!-- Outer layout defines the entire search bar at the top of the screen -->
<TextView
android:id="#+id/search_badge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="2dip"
android:drawablePadding="0dip"
android:textAppearance="?android:attr/textAppearanceSmall"
android:textColor="?android:attr/textColorPrimaryInverse" />
<!-- Inner layout contains the app icon, button(s) and EditText -->
<LinearLayout
I don't think you can create a view like that in xml. The purpose of the xml files is to create a layout, which will be shown in your activity. Thus, the top level element needs to be one of the layout elements. See declaring layouts.

Categories

Resources