Android Tutorial - Development : Xml
Using xml resource
package com.commonsware.android.files; import android.app.Activity; import android.os.Bundle; import android.app.ListActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.io.InputStream; import java.util.ArrayList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; public class StaticFileDemo extends ListActivity { TextView selection; ArrayList<String> items=new ArrayList<String>(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); try { InputStream in=getResources().openRawResource(R.raw.words); DocumentBuilder builder=DocumentBuilderFactory .newInstance() .newDocumentBuilder(); Document doc=builder.parse(in, null); NodeList words=doc.getElementsByTagName("word"); for (int i=0;i<words.getLength();i++) { items.add(((Element)words.item(i)).getAttribute("value")); } in.close(); } catch (Throwable t) { Toast .makeText(this, "Exception: "+t.toString(), 2000) .show(); } setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items.get(position).toString()); } } //res\raw\words.xml <words> <word value="lorem" /> <word value="ipsum" /> <word value="dolor" /> <word value="sit" /> <word value="amet" /> <word value="consectetuer" /> <word value="adipiscing" /> <word value="elit" /> <word value="morbi" /> <word value="vel" /> <word value="ligula" /> <word value="vitae" /> <word value="arcu" /> <word value="aliquet" /> <word value="mollis" /> <word value="etiam" /> <word value="vel" /> <word value="erat" /> <word value="placerat" /> <word value="ante" /> <word value="porttitor" /> <word value="sodales" /> <word value="pellentesque" /> <word value="augue" /> <word value="purus" /> </words> //res\layout\main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>
XML Resource Demo
package com.commonsware.android.resources; import android.app.Activity; import android.os.Bundle; import android.app.ListActivity; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import java.io.InputStream; import java.util.ArrayList; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; public class XMLResourceDemo extends ListActivity { TextView selection; ArrayList<String> items=new ArrayList<String>(); @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setContentView(R.layout.main); selection=(TextView)findViewById(R.id.selection); try { XmlPullParser xpp=getResources().getXml(R.xml.words); while (xpp.getEventType()!=XmlPullParser.END_DOCUMENT) { if (xpp.getEventType()==XmlPullParser.START_TAG) { if (xpp.getName().equals("word")) { items.add(xpp.getAttributeValue(0)); } } xpp.next(); } } catch (Throwable t) { Toast .makeText(this, "Request failed: "+t.toString(), 4000) .show(); } setListAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, items)); } public void onListItemClick(ListView parent, View v, int position, long id) { selection.setText(items.get(position).toString()); } } //res\xml\words.xml <words> <word value="lorem" /> <word value="ipsum" /> <word value="dolor" /> <word value="sit" /> <word value="amet" /> <word value="consectetuer" /> <word value="adipiscing" /> <word value="elit" /> <word value="morbi" /> <word value="vel" /> <word value="ligula" /> <word value="vitae" /> <word value="arcu" /> <word value="aliquet" /> <word value="mollis" /> <word value="etiam" /> <word value="vel" /> <word value="erat" /> <word value="placerat" /> <word value="ante" /> <word value="porttitor" /> <word value="sodales" /> <word value="pellentesque" /> <word value="augue" /> <word value="purus" /> </words> //res\values\strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">XMLResourceDemo</string> </resources> //res\layout\main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:id="@+id/selection" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <ListView android:id="@android:id/list" android:layout_width="fill_parent" android:layout_height="fill_parent" android:drawSelectorOnTop="false" /> </LinearLayout>
Load style from styles.xml
package app.test; import android.app.Activity; import android.graphics.Color; import android.os.Bundle; import android.text.Spannable; import android.text.style.BackgroundColorSpan; import android.text.style.StyleSpan; import android.text.util.Linkify; import android.widget.EditText; import android.widget.TextView; public class Test extends Activity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); TextView tv =(TextView)this.findViewById(R.id.tv); tv.setAutoLinkMask(Linkify.ALL); tv.setText("asdf"); TextView tv3 =(TextView)this.findViewById(R.id.tv3); tv3.setText("asdf",TextView.BufferType.SPANNABLE); Spannable spn = (Spannable) tv3.getText(); spn.setSpan(new BackgroundColorSpan(Color.RED), 0, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spn.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC),0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); EditText et =(EditText)this.findViewById(R.id.et); et.setText("asdf"); Spannable spn2 = (Spannable) et.getText(); spn2.setSpan(new BackgroundColorSpan(Color.RED), 0, 7,Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); spn2.setSpan(new StyleSpan(android.graphics.Typeface.BOLD_ITALIC),0, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } //main.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" > <TextView android:layout_width="fill_parent" android:layout_height="wrap_content" android:autoLink="web|map" android:text="Please visit www.androidbook.com for more help on using Android." android:minLines="5" android:typeface="serif" /> <TextView android:id="@+id/tv" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <TextView android:id="@+id/tvStyled" android:layout_width="fill_parent" android:layout_height="wrap_content" android:text="@+string/styledText" /> <TextView android:id="@+id/tv3" android:layout_width="fill_parent" android:layout_height="wrap_content" /> <EditText android:id="@+id/et" android:layout_width="fill_parent" android:layout_height="wrap_content" android:inputType="text|textAutoCorrect|textAutoComplete|textMultiLine" /> <Button android:id="@+id/button1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/tv" /> <TextView android:id="@+id/errors" style="@style/ErrorText" android:text="There's trouble down at the mill." /> <TextView android:id="@+id/danger" style="@style/ErrorText.Danger" android:text="SERIOUSLY! THERE'S TROUBLE DOWN AT THE MILL!!" /> </LinearLayout> //strings.xml <?xml version="1.0" encoding="utf-8"?> <resources> <string name="hello">Hello World, ActivityMenu</string> <string name="app_name">HelloMenu</string> <string name="button1">button1</string> <string name="button2">button2</string> <string name="tv">I am a TextView</string> <string name="et">I am an EditText</string> <string name="actv">AutoComplete:</string> <string name="mactv">MultiAutoComplete:</string> </resources> //styles.xml <?xml version="1.0" encoding="utf-8"?> <resources> <style name="ErrorText"> <item name="android:layout_width">fill_parent</item> <item name="android:layout_height">wrap_content</item> <item name="android:textColor">#FF0000</item> <item name="android:typeface">monospace</item> </style> <style name="ErrorText.Danger" > <item name="android:textStyle">bold</item> </style> </resources>
Define PreferenceScreen in xml file
package app.test; import android.os.Bundle; import android.preference.PreferenceActivity; public class Test extends PreferenceActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.main); } } //xml/main.xml <?xml version="1.0" encoding="utf-8"?> <!-- This file is /res/xml/chkbox.xml --> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android" android:key="flight_columns_pref" android:title="Flight Search Preferences" android:summary="Set Columns for Search Results"> <CheckBoxPreference android:key="show_airline_column_pref" android:title="Airline" android:summary="Show Airline column" /> <CheckBoxPreference android:key="show_departure_column_pref" android:title="Departure" android:summary="Show Departure column" /> <CheckBoxPreference android:key="show_arrival_column_pref" android:title="Arrival" android:summary="Show Arrival column" /> <CheckBoxPreference android:key="show_total_travel_time_column_pref" android:title="Total Travel Time" android:summary="Show Total Travel Time column" /> <CheckBoxPreference android:key="show_price_column_pref" android:title="Price" android:summary="Show Price column" /> </PreferenceScreen>
Using XML Parser
package app.test; import java.io.ByteArrayInputStream; import java.io.IOException; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; import org.xml.sax.helpers.DefaultHandler; import android.app.Activity; import android.os.Bundle; import android.util.Log; public class Test extends Activity { XMLUser aUser; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); String xml = "<?xml version=\"1.0\"?>\n" + "<user>\n" + "<user-id>1</user-id>\n" + "<username>a</username>\n" + "<firstname>B</firstname>\n" + "<lastname>C</lastname>\n" + "</user>\n"; SAXParserFactory aSAXParserFactory = SAXParserFactory.newInstance(); try { SAXParser aSAXParser = aSAXParserFactory.newSAXParser(); XMLReader anXMLReader = aSAXParser.getXMLReader(); UserXMLHandler aUserXMLHandler = new UserXMLHandler(); anXMLReader.setContentHandler(aUserXMLHandler); anXMLReader.parse(new InputSource(new ByteArrayInputStream(xml.getBytes()))); } catch (Exception e) { e.printStackTrace(); } } class UserXMLHandler extends DefaultHandler { static final int NONE = 0; static final int ID = 1; static final int FIRSTNAME = 2; static final int LASTNAME = 3; int state = NONE; static final String ID_ELEMENT = "user-id"; static final String FIRSTNAME_ELEMENT = "firstname"; static final String LASTNAME_ELEMENT = "lsatname"; @Override public void startDocument() throws SAXException { Log.v("SimpleXMLParser", "startDocument"); aUser = new XMLUser(); } @Override public void endDocument() throws SAXException { Log.v("SimpleXMLParser", "endDocument"); Log.v("SimpleXMLParser", "User Info: " + aUser.user_id + " " + aUser.firstname + " " + aUser.lastname); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { Log.v("SimpleXMLParser", "startElement"); if (localName.equalsIgnoreCase(ID_ELEMENT)) { state = ID; } else if (localName.equalsIgnoreCase(FIRSTNAME_ELEMENT)) { state = FIRSTNAME; } else if (localName.equalsIgnoreCase(LASTNAME_ELEMENT)) { state = LASTNAME; } else { state = NONE; } } @Override public void endElement(String uri, String localName, String qName) throws SAXException { Log.v("SimpleXMLParser", "endElement"); } @Override public void characters(char[] ch, int start, int length) throws SAXException { String stringChars = new String(ch, start, length); if (state == ID) { aUser.user_id += stringChars.trim(); Log.v("SimpleXMLParser", "user_id:" + aUser.user_id); } else if (state == FIRSTNAME) { aUser.firstname += stringChars.trim(); Log.v("SimpleXMLParser", "firstname:" + aUser.firstname); } else if (state == LASTNAME) { aUser.lastname += stringChars.trim(); Log.v("SimpleXMLParser", "lastname:" + aUser.lastname); } } } } class XMLUser { String user_id; String firstname; String lastname; public XMLUser() { user_id = ""; firstname = ""; lastname = ""; } }
XML-defined adapters can be used to easily create adapters in your own application or to pass adapters to other processes.
package com.example.android.xmladapters; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.app.Activity; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; import android.database.Cursor; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.util.AttributeSet; import android.util.Xml; import android.view.View; import android.widget.BaseAdapter; import android.widget.CursorAdapter; import android.widget.ImageView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; import java.io.IOException; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.HashMap; /** * <p>This class can be used to load {@link android.widget.Adapter adapters} defined in * XML resources. XML-defined adapters can be used to easily create adapters in your * own application or to pass adapters to other processes.</p> * * <h2>Types of adapters</h2> * <p>Adapters defined using XML resources can only be one of the following supported * types. Arbitrary adapters are not supported to guarantee the safety of the loaded * code when adapters are loaded across packages.</p> * <ul> * <li><a href="#xml-cursor-adapter">Cursor adapter</a>: a cursor adapter can be used * to display the content of a cursor, most often coming from a content provider</li> * </ul> * <p>The complete XML format definition of each adapter type is available below.</p> * * <a name="xml-cursor-adapter"></a> * <h2>Cursor adapter</h2> * <p>A cursor adapter XML definition starts with the * <a href="#xml-cursor-adapter-tag"><code><cursor-adapter /></code></a> * tag and may contain one or more instances of the following tags:</p> * <ul> * <li><a href="#xml-cursor-adapter-select-tag"><code><select /></code></a></li> * <li><a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a></li> * </ul> * * <a name="xml-cursor-adapter-tag"></a> * <h3><cursor-adapter /></h3> * <p>The <code><cursor-adapter /></code> element defines the beginning of the * document and supports the following attributes:</p> * <ul> * <li><code>android:layout</code>: Reference to the XML layout to be inflated for * each item of the adapter. This attribute is mandatory.</li> * <li><code>android:selection</code>: Selection expression, used when the * <code>android:uri</code> attribute is defined or when the adapter is loaded with * {@link Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. * This attribute is optional.</li> * <li><code>android:sortOrder</code>: Sort expression, used when the * <code>android:uri</code> attribute is defined or when the adapter is loaded with * {@link Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. * This attribute is optional.</li> * <li><code>android:uri</code>: URI of the content provider to query to retrieve a cursor. * Specifying this attribute is equivalent to calling * {@link Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}. * If you call this method, the value of the XML attribute is ignored. This attribute is * optional.</li> * </ul> * <p>In addition, you can specify one or more instances of * <a href="#xml-cursor-adapter-select-tag"><code><select /></code></a> and * <a href="#xml-cursor-adapter-bind-tag"><code><bind /></code></a> tags as children * of <code><cursor-adapter /></code>.</p> * * <a name="xml-cursor-adapter-select-tag"></a> * <h3><select /></h3> * <p>The <code><select /></code> tag is used to select columns from the cursor * when doing the query. This can be very useful when using transformations in the * <code><bind /></code> elements. It can also be very useful if you are providing * your own <a href="#xml-cursor-adapter-bind-data-types">binder</a> or * <a href="#xml-cursor-adapter-bind-data-types">transformation</a> classes. * <code><select /></code> elements are ignored if you supply the cursor yourself.</p> * <p>The <code><select /></code> supports the following attributes:</p> * <ul> * <li><code>android:column</code>: Name of the column to select in the cursor during the * query operation</li> * </ul> * <p><strong>Note:</strong> The column named <code>_id</code> is always implicitly * selected.</p> * * <a name="xml-cursor-adapter-bind-tag"></a> * <h3><bind /></h3> * <p>The <code><bind /></code> tag is used to bind a column from the cursor to * a {@link android.view.View}. A column bound using this tag is automatically selected * during the query and a matching * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag is therefore * not required.</p> * * <p>Each binding is declared as a one to one matching but * custom binder classes or special * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> can * allow you to bind several columns to a single view. In this case you must use the * <a href="#xml-cursor-adapter-select-tag"><code><select /></code> tag to make * sure any required column is part of the query.</p> * * <p>The <code><bind /></code> tag supports the following attributes:</p> * <ul> * <li><code>android:from</code>: The name of the column to bind from. * This attribute is mandatory. Note that <code>@</code> which are not used to reference resources * should be backslash protected as in <code>\@</code>.</li> * <li><code>android:to</code>: The id of the view to bind to. This attribute is mandatory.</li> * <li><code>android:as</code>: The <a href="#xml-cursor-adapter-bind-data-types">data type</a> * of the binding. This attribute is mandatory.</li> * </ul> * * <p>In addition, a <code><bind /></code> can contain zero or more instances of * <a href="#xml-cursor-adapter-bind-data-transformation">data transformations</a> children * tags.</p> * * <a name="xml-cursor-adapter-bind-data-types"></a> * <h4>Binding data types</h4> * <p>For a binding to occur the data type of the bound column/view pair must be specified. * The following data types are currently supported:</p> * <ul> * <li><code>string</code>: The content of the column is interpreted as a string and must be * bound to a {@link android.widget.TextView}</li> * <li><code>image</code>: The content of the column is interpreted as a blob describing an * image and must be bound to an {@link android.widget.ImageView}</li> * <li><code>image-uri</code>: The content of the column is interpreted as a URI to an image * and must be bound to an {@link android.widget.ImageView}</li> * <li><code>drawable</code>: The content of the column is interpreted as a resource id to a * drawable and must be bound to an {@link android.widget.ImageView}</li> * <li><code>tag</code>: The content of the column is interpreted as a string and will be set as * the tag (using {@link View#setTag(Object)} of the associated View. This can be used to * associate meta-data to your view, that can be used for instance by a listener.</li> * <li>A fully qualified class name: The name of a class corresponding to an implementation of * {@link Adapters.CursorBinder}. Cursor binders can be used to provide * bindings not supported by default. Custom binders cannot be used with * {@link android.content.Context#isRestricted() restricted contexts}, for instance in an * application widget</li> * </ul> * * <a name="xml-cursor-adapter-bind-transformation"></a> * <h4>Binding transformations</h4> * <p>When defining a data binding you can specify an optional transformation by using one * of the following tags as a child of a <code><bind /></code> elements:</p> * <ul> * <li><code><map /></code>: Maps a constant string to a string or a resource. Use * one instance of this tag per value you want to map</li> * <li><code><transform /></code>: Transforms a column's value using an expression * or an instance of {@link Adapters.CursorTransformation}</li> * </ul> * <p>While several <code><map /></code> tags can be used at the same time, you cannot * mix <code><map /></code> and <code><transform /></code> tags. If several * <code><transform /></code> tags are specified, only the last one is retained.</p> * * <a name="xml-cursor-adapter-bind-transformation-map" /> * <p><strong><map /></strong></p> * <p>A map element simply specifies a value to match from and a value to match to. When * a column's value equals the value to match from, it is replaced with the value to match * to. The following attributes are supported:</p> * <ul> * <li><code>android:fromValue</code>: The value to match from. This attribute is mandatory</li> * <li><code>android:toValue</code>: The value to match to. This value can be either a string * or a resource identifier. This value is interpreted as a resource identifier when the * data binding is of type <code>drawable</code>. This attribute is mandatory</li> * </ul> * * <a name="xml-cursor-adapter-bind-transformation-transform"></a> * <p><strong><transform /></strong></p> * <p>A simple transform that occurs either by calling a specified class or by performing * simple text substitution. The following attributes are supported:</p> * <ul> * <li><code>android:withExpression</code>: The transformation expression. The expression is * a string containing column names surrounded with curly braces { and }. During the * transformation each column name is replaced by its value. All columns must have been * selected in the query. An example of expression is <code>"First name: {first_name}, * last name: {last_name}"</code>. This attribute is mandatory * if <code>android:withClass</code> is not specified and ignored if <code>android:withClass</code> * is specified</li> * <li><code>android:withClass</code>: A fully qualified class name corresponding to an * implementation of {@link Adapters.CursorTransformation}. Custom * transformations cannot be used with * {@link android.content.Context#isRestricted() restricted contexts}, for instance in * an app widget This attribute is mandatory if <code>android:withExpression</code> is * not specified</li> * </ul> * * <h3>Example</h3> * <p>The following example defines a cursor adapter that queries all the contacts with * a phone number using the contacts content provider. Each contact is displayed with * its display name, its favorite status and its photo. To display photos, a custom data * binder is declared:</p> * * <pre class="prettyprint"> * <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android" * android:uri="content://com.android.contacts/contacts" * android:selection="has_phone_number=1" * android:layout="@layout/contact_item"> * * <bind android:from="display_name" android:to="@id/name" android:as="string" /> * <bind android:from="starred" android:to="@id/star" android:as="drawable"> * <map android:fromValue="0" android:toValue="@android:drawable/star_big_off" /> * <map android:fromValue="1" android:toValue="@android:drawable/star_big_on" /> * </bind> * <bind android:from="_id" android:to="@id/name" * android:as="com.google.android.test.adapters.ContactPhotoBinder" /> * * </cursor-adapter> * </pre> * * <h3>Related APIs</h3> * <ul> * <li>{@link Adapters#loadAdapter(android.content.Context, int, Object[])}</li> * <li>{@link Adapters#loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[])}</li> * <li>{@link Adapters#loadCursorAdapter(android.content.Context, int, String, Object[])}</li> * <li>{@link Adapters.CursorBinder}</li> * <li>{@link Adapters.CursorTransformation}</li> * <li>{@link android.widget.CursorAdapter}</li> * </ul> * * @see android.widget.Adapter * @see android.content.ContentProvider * * attr ref android.R.styleable#CursorAdapter_layout * attr ref android.R.styleable#CursorAdapter_selection * attr ref android.R.styleable#CursorAdapter_sortOrder * attr ref android.R.styleable#CursorAdapter_uri * attr ref android.R.styleable#CursorAdapter_BindItem_as * attr ref android.R.styleable#CursorAdapter_BindItem_from * attr ref android.R.styleable#CursorAdapter_BindItem_to * attr ref android.R.styleable#CursorAdapter_MapItem_fromValue * attr ref android.R.styleable#CursorAdapter_MapItem_toValue * attr ref android.R.styleable#CursorAdapter_SelectItem_column * attr ref android.R.styleable#CursorAdapter_TransformItem_withClass * attr ref android.R.styleable#CursorAdapter_TransformItem_withExpression */ @SuppressWarnings({"JavadocReference"}) public class Adapters { private static final String ADAPTER_CURSOR = "cursor-adapter"; /** * <p>Interface used to bind a {@link android.database.Cursor} column to a View. This * interface can be used to provide bindings for data types not supported by the * standard implementation of {@link Adapters}.</p> * * <p>A binder is provided with a cursor transformation which may or may not be used * to transform the value retrieved from the cursor. The transformation is guaranteed * to never be null so it's always safe to apply the transformation.</p> * * <p>The binder is associated with a Context but can be re-used with multiple cursors. * As such, the implementation should make no assumption about the Cursor in use.</p> * * @see android.view.View * @see android.database.Cursor * @see Adapters.CursorTransformation */ public static abstract class CursorBinder { /** * <p>The context associated with this binder.</p> */ protected final Context mContext; /** * <p>The transformation associated with this binder. This transformation is never * null and may or may not be applied to the Cursor data during the * {@link #bind(android.view.View, android.database.Cursor, int)} operation.</p> * * @see #bind(android.view.View, android.database.Cursor, int) */ protected final CursorTransformation mTransformation; /** * <p>Creates a new Cursor binder.</p> * * @param context The context associated with this binder. * @param transformation The transformation associated with this binder. This * transformation may or may not be applied by the binder and is guaranteed * to not be null. */ public CursorBinder(Context context, CursorTransformation transformation) { mContext = context; mTransformation = transformation; } /** * <p>Binds the specified Cursor column to the supplied View. The binding operation * can query other Cursor columns as needed. During the binding operation, values * retrieved from the Cursor may or may not be transformed using this binder's * cursor transformation.</p> * * @param view The view to bind data to. * @param cursor The cursor to bind data from. * @param columnIndex The column index in the cursor where the data to bind resides. * * @see #mTransformation * * @return True if the column was successfully bound to the View, false otherwise. */ public abstract boolean bind(View view, Cursor cursor, int columnIndex); } /** * <p>Interface used to transform data coming out of a {@link android.database.Cursor} * before it is bound to a {@link android.view.View}.</p> * * <p>Transformations are used to transform text-based data (in the form of a String), * or to transform data into a resource identifier. A default implementation is provided * to generate resource identifiers.</p> * * @see android.database.Cursor * @see Adapters.CursorBinder */ public static abstract class CursorTransformation { /** * <p>The context associated with this transformation.</p> */ protected final Context mContext; /** * <p>Creates a new Cursor transformation.</p> * * @param context The context associated with this transformation. */ public CursorTransformation(Context context) { mContext = context; } /** * <p>Transforms the specified Cursor column into a String. The transformation * can simply return the content of the column as a String (this is known * as the identity transformation) or manipulate the content. For instance, * a transformation can perform text substitutions or concatenate other * columns with the specified column.</p> * * @param cursor The cursor that contains the data to transform. * @param columnIndex The index of the column to transform. * * @return A String containing the transformed value of the column. */ public abstract String transform(Cursor cursor, int columnIndex); /** * <p>Transforms the specified Cursor column into a resource identifier. * The default implementation simply interprets the content of the column * as an integer.</p> * * @param cursor The cursor that contains the data to transform. * @param columnIndex The index of the column to transform. * * @return A resource identifier. */ public int transformToResource(Cursor cursor, int columnIndex) { return cursor.getInt(columnIndex); } } /** * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified * XML resource. The content of the adapter is loaded from the content provider * identified by the supplied URI.</p> * * <p><strong>Note:</strong> If the supplied {@link android.content.Context} is * an {@link android.app.Activity}, the cursor returned by the content provider * will be automatically managed. Otherwise, you are responsible for managing the * cursor yourself.</p> * * <p>The format of the XML definition of the cursor adapter is documented at * the top of this page.</p> * * @param context The context to load the XML resource from. * @param id The identifier of the XML resource declaring the adapter. * @param uri The URI of the content provider. * @param parameters Optional parameters to pass to the CursorAdapter, used * to substitute values in the selection expression. * * @return A {@link android.widget.CursorAdapter} * * @throws IllegalArgumentException If the XML resource does not contain * a valid <cursor-adapter /> definition. * * @see android.content.ContentProvider * @see android.widget.CursorAdapter * @see #loadAdapter(android.content.Context, int, Object[]) */ public static CursorAdapter loadCursorAdapter(Context context, int id, String uri, Object... parameters) { XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR, parameters); if (uri != null) { adapter.setUri(uri); } adapter.load(); return adapter; } /** * <p>Loads the {@link android.widget.CursorAdapter} defined in the specified * XML resource. The content of the adapter is loaded from the specified cursor. * You are responsible for managing the supplied cursor.</p> * * <p>The format of the XML definition of the cursor adapter is documented at * the top of this page.</p> * * @param context The context to load the XML resource from. * @param id The identifier of the XML resource declaring the adapter. * @param cursor The cursor containing the data for the adapter. * @param parameters Optional parameters to pass to the CursorAdapter, used * to substitute values in the selection expression. * * @return A {@link android.widget.CursorAdapter} * * @throws IllegalArgumentException If the XML resource does not contain * a valid <cursor-adapter /> definition. * * @see android.content.ContentProvider * @see android.widget.CursorAdapter * @see android.database.Cursor * @see #loadAdapter(android.content.Context, int, Object[]) */ public static CursorAdapter loadCursorAdapter(Context context, int id, Cursor cursor, Object... parameters) { XmlCursorAdapter adapter = (XmlCursorAdapter) loadAdapter(context, id, ADAPTER_CURSOR, parameters); if (cursor != null) { adapter.changeCursor(cursor); } return adapter; } /** * <p>Loads the adapter defined in the specified XML resource. The XML definition of * the adapter must follow the format definition of one of the supported adapter * types described at the top of this page.</p> * * <p><strong>Note:</strong> If the loaded adapter is a {@link android.widget.CursorAdapter} * and the supplied {@link android.content.Context} is an {@link android.app.Activity}, * the cursor returned by the content provider will be automatically managed. Otherwise, * you are responsible for managing the cursor yourself.</p> * * @param context The context to load the XML resource from. * @param id The identifier of the XML resource declaring the adapter. * @param parameters Optional parameters to pass to the adapter. * * @return An adapter instance. * * @see #loadCursorAdapter(android.content.Context, int, android.database.Cursor, Object[]) * @see #loadCursorAdapter(android.content.Context, int, String, Object[]) */ public static BaseAdapter loadAdapter(Context context, int id, Object... parameters) { final BaseAdapter adapter = loadAdapter(context, id, null, parameters); if (adapter instanceof ManagedAdapter) { ((ManagedAdapter) adapter).load(); } return adapter; } /** * Loads an adapter from the specified XML resource. The optional assertName can * be used to exit early if the adapter defined in the XML resource is not of the * expected type. * * @param context The context to associate with the adapter. * @param id The resource id of the XML document defining the adapter. * @param assertName The mandatory name of the adapter in the XML document. * Ignored if null. * @param parameters Optional parameters passed to the adapter. * * @return An instance of {@link android.widget.BaseAdapter}. */ private static BaseAdapter loadAdapter(Context context, int id, String assertName, Object... parameters) { XmlResourceParser parser = null; try { parser = context.getResources().getXml(id); return createAdapterFromXml(context, parser, Xml.asAttributeSet(parser), id, parameters, assertName); } catch (XmlPullParserException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException( "Can't load adapter resource ID " + context.getResources().getResourceEntryName(id)); rnf.initCause(ex); throw rnf; } catch (IOException ex) { Resources.NotFoundException rnf = new Resources.NotFoundException( "Can't load adapter resource ID " + context.getResources().getResourceEntryName(id)); rnf.initCause(ex); throw rnf; } finally { if (parser != null) parser.close(); } } /** * Generates an adapter using the specified XML parser. This method is responsible * for choosing the type of the adapter to create based on the content of the * XML parser. * * This method will generate an {@link IllegalArgumentException} if * <code>assertName</code> is not null and does not match the root tag of the XML * document. */ private static BaseAdapter createAdapterFromXml(Context c, XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters, String assertName) throws XmlPullParserException, IOException { BaseAdapter adapter = null; // Make sure we are on a start tag. int type; int depth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (assertName != null && !assertName.equals(name)) { throw new IllegalArgumentException("The adapter defined in " + c.getResources().getResourceEntryName(id) + " must be a <" + assertName + " />"); } if (ADAPTER_CURSOR.equals(name)) { adapter = createCursorAdapter(c, parser, attrs, id, parameters); } else { throw new IllegalArgumentException("Unknown adapter name " + parser.getName() + " in " + c.getResources().getResourceEntryName(id)); } } return adapter; } /** * Creates an XmlCursorAdapter using an XmlCursorAdapterParser. */ private static XmlCursorAdapter createCursorAdapter(Context c, XmlPullParser parser, AttributeSet attrs, int id, Object[] parameters) throws IOException, XmlPullParserException { return new XmlCursorAdapterParser(c, parser, attrs, id).parse(parameters); } /** * Parser that can generate XmlCursorAdapter instances. This parser is responsible for * handling all the attributes and child nodes for a <cursor-adapter />. */ private static class XmlCursorAdapterParser { private static final String ADAPTER_CURSOR_BIND = "bind"; private static final String ADAPTER_CURSOR_SELECT = "select"; private static final String ADAPTER_CURSOR_AS_STRING = "string"; private static final String ADAPTER_CURSOR_AS_IMAGE = "image"; private static final String ADAPTER_CURSOR_AS_TAG = "tag"; private static final String ADAPTER_CURSOR_AS_IMAGE_URI = "image-uri"; private static final String ADAPTER_CURSOR_AS_DRAWABLE = "drawable"; private static final String ADAPTER_CURSOR_MAP = "map"; private static final String ADAPTER_CURSOR_TRANSFORM = "transform"; private final Context mContext; private final XmlPullParser mParser; private final AttributeSet mAttrs; private final int mId; private final HashMap<String, CursorBinder> mBinders; private final ArrayList<String> mFrom; private final ArrayList<Integer> mTo; private final CursorTransformation mIdentity; private final Resources mResources; public XmlCursorAdapterParser(Context c, XmlPullParser parser, AttributeSet attrs, int id) { mContext = c; mParser = parser; mAttrs = attrs; mId = id; mResources = mContext.getResources(); mBinders = new HashMap<String, CursorBinder>(); mFrom = new ArrayList<String>(); mTo = new ArrayList<Integer>(); mIdentity = new IdentityTransformation(mContext); } public XmlCursorAdapter parse(Object[] parameters) throws IOException, XmlPullParserException { Resources resources = mResources; TypedArray a = resources.obtainAttributes(mAttrs, R.styleable.CursorAdapter); String uri = a.getString(R.styleable.CursorAdapter_uri); String selection = a.getString(R.styleable.CursorAdapter_selection); String sortOrder = a.getString(R.styleable.CursorAdapter_sortOrder); int layout = a.getResourceId(R.styleable.CursorAdapter_layout, 0); if (layout == 0) { throw new IllegalArgumentException("The layout specified in " + resources.getResourceEntryName(mId) + " does not exist"); } a.recycle(); XmlPullParser parser = mParser; int type; int depth = parser.getDepth(); while (((type = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { if (type != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (ADAPTER_CURSOR_BIND.equals(name)) { parseBindTag(); } else if (ADAPTER_CURSOR_SELECT.equals(name)) { parseSelectTag(); } else { throw new RuntimeException("Unknown tag name " + parser.getName() + " in " + resources.getResourceEntryName(mId)); } } String[] fromArray = mFrom.toArray(new String[mFrom.size()]); int[] toArray = new int[mTo.size()]; for (int i = 0; i < toArray.length; i++) { toArray[i] = mTo.get(i); } String[] selectionArgs = null; if (parameters != null) { selectionArgs = new String[parameters.length]; for (int i = 0; i < selectionArgs.length; i++) { selectionArgs[i] = (String) parameters[i]; } } return new XmlCursorAdapter(mContext, layout, uri, fromArray, toArray, selection, selectionArgs, sortOrder, mBinders); } private void parseSelectTag() { TypedArray a = mResources.obtainAttributes(mAttrs, R.styleable.CursorAdapter_SelectItem); String fromName = a.getString(R.styleable.CursorAdapter_SelectItem_column); if (fromName == null) { throw new IllegalArgumentException("A select item in " + mResources.getResourceEntryName(mId) + " does not have a 'column' attribute"); } a.recycle(); mFrom.add(fromName); mTo.add(View.NO_ID); } private void parseBindTag() throws IOException, XmlPullParserException { Resources resources = mResources; TypedArray a = resources.obtainAttributes(mAttrs, R.styleable.CursorAdapter_BindItem); String fromName = a.getString(R.styleable.CursorAdapter_BindItem_from); if (fromName == null) { throw new IllegalArgumentException("A bind item in " + resources.getResourceEntryName(mId) + " does not have a 'from' attribute"); } int toName = a.getResourceId(R.styleable.CursorAdapter_BindItem_to, 0); if (toName == 0) { throw new IllegalArgumentException("A bind item in " + resources.getResourceEntryName(mId) + " does not have a 'to' attribute"); } String asType = a.getString(R.styleable.CursorAdapter_BindItem_as); if (asType == null) { throw new IllegalArgumentException("A bind item in " + resources.getResourceEntryName(mId) + " does not have an 'as' attribute"); } mFrom.add(fromName); mTo.add(toName); mBinders.put(fromName, findBinder(asType)); a.recycle(); } private CursorBinder findBinder(String type) throws IOException, XmlPullParserException { final XmlPullParser parser = mParser; final Context context = mContext; CursorTransformation transformation = mIdentity; int tagType; int depth = parser.getDepth(); final boolean isDrawable = ADAPTER_CURSOR_AS_DRAWABLE.equals(type); while (((tagType = parser.next()) != XmlPullParser.END_TAG || parser.getDepth() > depth) && tagType != XmlPullParser.END_DOCUMENT) { if (tagType != XmlPullParser.START_TAG) { continue; } String name = parser.getName(); if (ADAPTER_CURSOR_TRANSFORM.equals(name)) { transformation = findTransformation(); } else if (ADAPTER_CURSOR_MAP.equals(name)) { if (!(transformation instanceof MapTransformation)) { transformation = new MapTransformation(context); } findMap(((MapTransformation) transformation), isDrawable); } else { throw new RuntimeException("Unknown tag name " + parser.getName() + " in " + context.getResources().getResourceEntryName(mId)); } } if (ADAPTER_CURSOR_AS_STRING.equals(type)) { return new StringBinder(context, transformation); } else if (ADAPTER_CURSOR_AS_TAG.equals(type)) { return new TagBinder(context, transformation); } else if (ADAPTER_CURSOR_AS_IMAGE.equals(type)) { return new ImageBinder(context, transformation); } else if (ADAPTER_CURSOR_AS_IMAGE_URI.equals(type)) { return new ImageUriBinder(context, transformation); } else if (isDrawable) { return new DrawableBinder(context, transformation); } else { return createBinder(type, transformation); } } private CursorBinder createBinder(String type, CursorTransformation transformation) { if (mContext.isRestricted()) return null; try { final Class<?> klass = Class.forName(type, true, mContext.getClassLoader()); if (CursorBinder.class.isAssignableFrom(klass)) { final Constructor<?> c = klass.getDeclaredConstructor( Context.class, CursorTransformation.class); return (CursorBinder) c.newInstance(mContext, transformation); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instanciate binder type in " + mContext.getResources().getResourceEntryName(mId) + ": " + type, e); } return null; } private void findMap(MapTransformation transformation, boolean drawable) { Resources resources = mResources; TypedArray a = resources.obtainAttributes(mAttrs, R.styleable.CursorAdapter_MapItem); String from = a.getString(R.styleable.CursorAdapter_MapItem_fromValue); if (from == null) { throw new IllegalArgumentException("A map item in " + resources.getResourceEntryName(mId) + " does not have a 'fromValue' attribute"); } if (!drawable) { String to = a.getString(R.styleable.CursorAdapter_MapItem_toValue); if (to == null) { throw new IllegalArgumentException("A map item in " + resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute"); } transformation.addStringMapping(from, to); } else { int to = a.getResourceId(R.styleable.CursorAdapter_MapItem_toValue, 0); if (to == 0) { throw new IllegalArgumentException("A map item in " + resources.getResourceEntryName(mId) + " does not have a 'toValue' attribute"); } transformation.addResourceMapping(from, to); } a.recycle(); } private CursorTransformation findTransformation() { Resources resources = mResources; CursorTransformation transformation = null; TypedArray a = resources.obtainAttributes(mAttrs, R.styleable.CursorAdapter_TransformItem); String className = a.getString(R.styleable.CursorAdapter_TransformItem_withClass); if (className == null) { String expression = a.getString( R.styleable.CursorAdapter_TransformItem_withExpression); transformation = createExpressionTransformation(expression); } else if (!mContext.isRestricted()) { try { final Class<?> klas = Class.forName(className, true, mContext.getClassLoader()); if (CursorTransformation.class.isAssignableFrom(klas)) { final Constructor<?> c = klas.getDeclaredConstructor(Context.class); transformation = (CursorTransformation) c.newInstance(mContext); } } catch (ClassNotFoundException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (InstantiationException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Cannot instanciate transform type in " + mContext.getResources().getResourceEntryName(mId) + ": " + className, e); } } a.recycle(); if (transformation == null) { throw new IllegalArgumentException("A transform item in " + resources.getResourceEntryName(mId) + " must have a 'withClass' or " + "'withExpression' attribute"); } return transformation; } private CursorTransformation createExpressionTransformation(String expression) { return new ExpressionTransformation(mContext, expression); } } /** * Interface used by adapters that require to be loaded after creation. */ private static interface ManagedAdapter { /** * Loads the content of the adapter, asynchronously. */ void load(); } /** * Implementation of a Cursor adapter defined in XML. This class is a thin wrapper * of a SimpleCursorAdapter. The main difference is the ability to handle CursorBinders. */ private static class XmlCursorAdapter extends SimpleCursorAdapter implements ManagedAdapter { private String mUri; private final String mSelection; private final String[] mSelectionArgs; private final String mSortOrder; private final String[] mColumns; private final CursorBinder[] mBinders; private AsyncTask<Void,Void,Cursor> mLoadTask; XmlCursorAdapter(Context context, int layout, String uri, String[] from, int[] to, String selection, String[] selectionArgs, String sortOrder, HashMap<String, CursorBinder> binders) { super(context, layout, null, from, to); mContext = context; mUri = uri; mSelection = selection; mSelectionArgs = selectionArgs; mSortOrder = sortOrder; mColumns = new String[from.length + 1]; // This is mandatory in CursorAdapter mColumns[0] = "_id"; System.arraycopy(from, 0, mColumns, 1, from.length); CursorBinder basic = new StringBinder(context, new IdentityTransformation(context)); final int count = from.length; mBinders = new CursorBinder[count]; for (int i = 0; i < count; i++) { CursorBinder binder = binders.get(from[i]); if (binder == null) binder = basic; mBinders[i] = binder; } } @Override public void bindView(View view, Context context, Cursor cursor) { final int count = mTo.length; final int[] from = mFrom; final int[] to = mTo; final CursorBinder[] binders = mBinders; for (int i = 0; i < count; i++) { final View v = view.findViewById(to[i]); if (v != null) { binders[i].bind(v, cursor, from[i]); } } } public void load() { if (mUri != null) { mLoadTask = new QueryTask().execute(); } } void setUri(String uri) { mUri = uri; } @Override public void changeCursor(Cursor c) { if (mLoadTask != null && mLoadTask.getStatus() != QueryTask.Status.FINISHED) { mLoadTask.cancel(true); mLoadTask = null; } super.changeCursor(c); } class QueryTask extends AsyncTask<Void, Void, Cursor> { @Override protected Cursor doInBackground(Void... params) { if (mContext instanceof Activity) { return ((Activity) mContext).managedQuery( Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder); } else { return mContext.getContentResolver().query( Uri.parse(mUri), mColumns, mSelection, mSelectionArgs, mSortOrder); } } @Override protected void onPostExecute(Cursor cursor) { if (!isCancelled()) { XmlCursorAdapter.super.changeCursor(cursor); } } } } /** * Identity transformation, returns the content of the specified column as a String, * without performing any manipulation. This is used when no transformation is specified. */ private static class IdentityTransformation extends CursorTransformation { public IdentityTransformation(Context context) { super(context); } @Override public String transform(Cursor cursor, int columnIndex) { return cursor.getString(columnIndex); } } /** * An expression transformation is a simple template based replacement utility. * In an expression, each segment of the form <code>{([^}]+)}</code> is replaced * with the value of the column of name $1. */ private static class ExpressionTransformation extends CursorTransformation { private final ExpressionNode mFirstNode = new ConstantExpressionNode(""); private final StringBuilder mBuilder = new StringBuilder(); public ExpressionTransformation(Context context, String expression) { super(context); parse(expression); } private void parse(String expression) { ExpressionNode node = mFirstNode; int segmentStart; int count = expression.length(); for (int i = 0; i < count; i++) { char c = expression.charAt(i); // Start a column name segment segmentStart = i; if (c == '{') { while (i < count && (c = expression.charAt(i)) != '}') { i++; } // We've reached the end, but the expression didn't close if (c != '}') { throw new IllegalStateException("The transform expression contains a " + "non-closed column name: " + expression.substring(segmentStart + 1, i)); } node.next = new ColumnExpressionNode(expression.substring(segmentStart + 1, i)); } else { while (i < count && (c = expression.charAt(i)) != '{') { i++; } node.next = new ConstantExpressionNode(expression.substring(segmentStart, i)); // Rewind if we've reached a column expression if (c == '{') i--; } node = node.next; } } @Override public String transform(Cursor cursor, int columnIndex) { final StringBuilder builder = mBuilder; builder.delete(0, builder.length()); ExpressionNode node = mFirstNode; // Skip the first node while ((node = node.next) != null) { builder.append(node.asString(cursor)); } return builder.toString(); } static abstract class ExpressionNode { public ExpressionNode next; public abstract String asString(Cursor cursor); } static class ConstantExpressionNode extends ExpressionNode { private final String mConstant; ConstantExpressionNode(String constant) { mConstant = constant; } @Override public String asString(Cursor cursor) { return mConstant; } } static class ColumnExpressionNode extends ExpressionNode { private final String mColumnName; private Cursor mSignature; private int mColumnIndex = -1; ColumnExpressionNode(String columnName) { mColumnName = columnName; } @Override public String asString(Cursor cursor) { if (cursor != mSignature || mColumnIndex == -1) { mColumnIndex = cursor.getColumnIndex(mColumnName); mSignature = cursor; } return cursor.getString(mColumnIndex); } } } /** * A map transformation offers a simple mapping between specified String values * to Strings or integers. */ private static class MapTransformation extends CursorTransformation { private final HashMap<String, String> mStringMappings; private final HashMap<String, Integer> mResourceMappings; public MapTransformation(Context context) { super(context); mStringMappings = new HashMap<String, String>(); mResourceMappings = new HashMap<String, Integer>(); } void addStringMapping(String from, String to) { mStringMappings.put(from, to); } void addResourceMapping(String from, int to) { mResourceMappings.put(from, to); } @Override public String transform(Cursor cursor, int columnIndex) { final String value = cursor.getString(columnIndex); final String transformed = mStringMappings.get(value); return transformed == null ? value : transformed; } @Override public int transformToResource(Cursor cursor, int columnIndex) { final String value = cursor.getString(columnIndex); final Integer transformed = mResourceMappings.get(value); try { return transformed == null ? Integer.parseInt(value) : transformed; } catch (NumberFormatException e) { return 0; } } } /** * Binds a String to a TextView. */ private static class StringBinder extends CursorBinder { public StringBinder(Context context, CursorTransformation transformation) { super(context, transformation); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { if (view instanceof TextView) { final String text = mTransformation.transform(cursor, columnIndex); ((TextView) view).setText(text); return true; } return false; } } /** * Binds an image blob to an ImageView. */ private static class ImageBinder extends CursorBinder { public ImageBinder(Context context, CursorTransformation transformation) { super(context, transformation); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { if (view instanceof ImageView) { final byte[] data = cursor.getBlob(columnIndex); ((ImageView) view).setImageBitmap(BitmapFactory.decodeByteArray(data, 0, data.length)); return true; } return false; } } private static class TagBinder extends CursorBinder { public TagBinder(Context context, CursorTransformation transformation) { super(context, transformation); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { final String text = mTransformation.transform(cursor, columnIndex); view.setTag(text); return true; } } /** * Binds an image URI to an ImageView. */ private static class ImageUriBinder extends CursorBinder { public ImageUriBinder(Context context, CursorTransformation transformation) { super(context, transformation); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { if (view instanceof ImageView) { ((ImageView) view).setImageURI(Uri.parse( mTransformation.transform(cursor, columnIndex))); return true; } return false; } } /** * Binds a drawable resource identifier to an ImageView. */ private static class DrawableBinder extends CursorBinder { public DrawableBinder(Context context, CursorTransformation transformation) { super(context, transformation); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { if (view instanceof ImageView) { final int resource = mTransformation.transformToResource(cursor, columnIndex); if (resource == 0) return false; ((ImageView) view).setImageResource(resource); return true; } return false; } } } //src\com\example\android\xmladapters\ContactPhotoBinder.java package com.example.android.xmladapters; import android.content.ContentUris; import android.content.Context; import android.content.res.Resources; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.provider.ContactsContract; import android.view.View; import android.widget.TextView; import java.io.InputStream; import java.util.HashMap; /** * This custom cursor binder is used by the adapter defined in res/xml to * bind contacts photos to their respective list item. This binder simply * queries a contact's photo based on the contact's id and sets the * photo as a compound drawable on the TextView used to display the contact's * name. */ public class ContactPhotoBinder extends Adapters.CursorBinder { private static final int PHOTO_SIZE_DIP = 54; private final Drawable mDefault; private final HashMap<Long, Drawable> mCache; private final Resources mResources; private final int mPhotoSize; public ContactPhotoBinder(Context context, Adapters.CursorTransformation transformation) { super(context, transformation); mResources = mContext.getResources(); // Default picture used when a contact does not provide one mDefault = mResources.getDrawable(R.drawable.ic_contact_picture); // Cache used to avoid re-querying contacts photos every time mCache = new HashMap<Long, Drawable>(); // Compute the size of the photo based on the display's density mPhotoSize = (int) (PHOTO_SIZE_DIP * mResources.getDisplayMetrics().density + 0.5f); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { final long id = cursor.getLong(columnIndex); // First check whether we have already cached the contact's photo Drawable d = mCache.get(id); if (d == null) { // If the photo wasn't in the cache, ask the contacts provider for // an input stream we can use to load the photo Uri uri = ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, id); InputStream stream = ContactsContract.Contacts.openContactPhotoInputStream( mContext.getContentResolver(), uri); // Creates the drawable for the contact's photo or use our fallback drawable if (stream != null) { // decoding the bitmap could be done in a worker thread too. Bitmap bitmap = BitmapFactory.decodeStream(stream); d = new BitmapDrawable(mResources, bitmap); } else { d = mDefault; } d.setBounds(0, 0, mPhotoSize, mPhotoSize); ((TextView) view).setCompoundDrawables(d, null, null, null); // Remember the photo associated with this contact mCache.put(id, d); } else { ((TextView) view).setCompoundDrawables(d, null, null, null); } return true; } } //src\com\example\android\xmladapters\ContactsListActivity.java package com.example.android.xmladapters; import android.app.ListActivity; import android.os.Bundle; /** * This activity demonstrates how to create a complex UI using a ListView * and an adapter defined in XML. * * The following activity shows a list of contacts, their starred status * and their photos, using the adapter defined in res/xml. */ public class ContactsListActivity extends ListActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.contacts_list); setListAdapter(Adapters.loadAdapter(this, R.xml.contacts)); } } //src\com\example\android\xmladapters\ImageDownloader.java */package com.example.android.xmladapters; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.client.methods.HttpGet; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.http.AndroidHttpClient; import android.os.AsyncTask; import android.os.Handler; import android.util.Log; import android.widget.ImageView; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.concurrent.ConcurrentHashMap; /** * This helper class download images from the Internet and binds those with the provided ImageView. * * <p>It requires the INTERNET permission, which should be added to your application's manifest * file.</p> * * A local cache of downloaded images is maintained internally to improve performance. */ public class ImageDownloader { private static final String LOG_TAG = "ImageDownloader"; private static final int HARD_CACHE_CAPACITY = 40; private static final int DELAY_BEFORE_PURGE = 30 * 1000; // in milliseconds // Hard cache, with a fixed maximum capacity and a life duration private final HashMap<String, Bitmap> sHardBitmapCache = new LinkedHashMap<String, Bitmap>(HARD_CACHE_CAPACITY / 2, 0.75f, true) { @Override protected boolean removeEldestEntry(LinkedHashMap.Entry<String, Bitmap> eldest) { if (size() > HARD_CACHE_CAPACITY) { // Entries push-out of hard reference cache are transferred to soft reference cache sSoftBitmapCache.put(eldest.getKey(), new SoftReference<Bitmap>(eldest.getValue())); return true; } else return false; } }; // Soft cache for bitmap kicked out of hard cache private final static ConcurrentHashMap<String, SoftReference<Bitmap>> sSoftBitmapCache = new ConcurrentHashMap<String, SoftReference<Bitmap>>(HARD_CACHE_CAPACITY / 2); private final Handler purgeHandler = new Handler(); private final Runnable purger = new Runnable() { public void run() { clearCache(); } }; /** * Download the specified image from the Internet and binds it to the provided ImageView. The * binding is immediate if the image is found in the cache and will be done asynchronously * otherwise. A null bitmap will be associated to the ImageView if an error occurs. * * @param url The URL of the image to download. * @param imageView The ImageView to bind the downloaded image to. */ public void download(String url, ImageView imageView) { download(url, imageView, null); } /** * Same as {@link #download(String, ImageView)}, with the possibility to provide an additional * cookie that will be used when the image will be retrieved. * * @param url The URL of the image to download. * @param imageView The ImageView to bind the downloaded image to. * @param cookie A cookie String that will be used by the http connection. */ public void download(String url, ImageView imageView, String cookie) { resetPurgeTimer(); Bitmap bitmap = getBitmapFromCache(url); if (bitmap == null) { forceDownload(url, imageView, cookie); } else { cancelPotentialDownload(url, imageView); imageView.setImageBitmap(bitmap); } } /* * Same as download but the image is always downloaded and the cache is not used. * Kept private at the moment as its interest is not clear. private void forceDownload(String url, ImageView view) { forceDownload(url, view, null); } */ /** * Same as download but the image is always downloaded and the cache is not used. * Kept private at the moment as its interest is not clear. */ private void forceDownload(String url, ImageView imageView, String cookie) { // State sanity: url is guaranteed to never be null in DownloadedDrawable and cache keys. if (url == null) { imageView.setImageDrawable(null); return; } if (cancelPotentialDownload(url, imageView)) { BitmapDownloaderTask task = new BitmapDownloaderTask(imageView); DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task); imageView.setImageDrawable(downloadedDrawable); task.execute(url, cookie); } } /** * Clears the image cache used internally to improve performance. Note that for memory * efficiency reasons, the cache will automatically be cleared after a certain inactivity delay. */ public void clearCache() { sHardBitmapCache.clear(); sSoftBitmapCache.clear(); } private void resetPurgeTimer() { purgeHandler.removeCallbacks(purger); purgeHandler.postDelayed(purger, DELAY_BEFORE_PURGE); } /** * Returns true if the current download has been canceled or if there was no download in * progress on this image view. * Returns false if the download in progress deals with the same url. The download is not * stopped in that case. */ private static boolean cancelPotentialDownload(String url, ImageView imageView) { BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); if (bitmapDownloaderTask != null) { String bitmapUrl = bitmapDownloaderTask.url; if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) { bitmapDownloaderTask.cancel(true); } else { // The same URL is already being downloaded. return false; } } return true; } /** * @param imageView Any imageView * @return Retrieve the currently active download task (if any) associated with this imageView. * null if there is no such task. */ private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) { if (imageView != null) { Drawable drawable = imageView.getDrawable(); if (drawable instanceof DownloadedDrawable) { DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable; return downloadedDrawable.getBitmapDownloaderTask(); } } return null; } /** * @param url The URL of the image that will be retrieved from the cache. * @return The cached bitmap or null if it was not found. */ private Bitmap getBitmapFromCache(String url) { // First try the hard reference cache synchronized (sHardBitmapCache) { final Bitmap bitmap = sHardBitmapCache.get(url); if (bitmap != null) { // Bitmap found in hard cache // Move element to first position, so that it is removed last sHardBitmapCache.remove(url); sHardBitmapCache.put(url, bitmap); return bitmap; } } // Then try the soft reference cache SoftReference<Bitmap> bitmapReference = sSoftBitmapCache.get(url); if (bitmapReference != null) { final Bitmap bitmap = bitmapReference.get(); if (bitmap != null) { // Bitmap found in soft cache return bitmap; } else { // Soft reference has been Garbage Collected sSoftBitmapCache.remove(url); } } return null; } /** * The actual AsyncTask that will asynchronously download the image. */ class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> { private static final int IO_BUFFER_SIZE = 4 * 1024; private String url; private final WeakReference<ImageView> imageViewReference; public BitmapDownloaderTask(ImageView imageView) { imageViewReference = new WeakReference<ImageView>(imageView); } /** * Actual download method. */ @Override protected Bitmap doInBackground(String... params) { final AndroidHttpClient client = AndroidHttpClient.newInstance("Android"); url = params[0]; final HttpGet getRequest = new HttpGet(url); String cookie = params[1]; if (cookie != null) { getRequest.setHeader("cookie", cookie); } try { HttpResponse response = client.execute(getRequest); final int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != HttpStatus.SC_OK) { Log.w("ImageDownloader", "Error " + statusCode + " while retrieving bitmap from " + url); return null; } final HttpEntity entity = response.getEntity(); if (entity != null) { InputStream inputStream = null; OutputStream outputStream = null; try { inputStream = entity.getContent(); final ByteArrayOutputStream dataStream = new ByteArrayOutputStream(); outputStream = new BufferedOutputStream(dataStream, IO_BUFFER_SIZE); copy(inputStream, outputStream); outputStream.flush(); final byte[] data = dataStream.toByteArray(); final Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length); // FIXME : Should use BitmapFactory.decodeStream(inputStream) instead. //final Bitmap bitmap = BitmapFactory.decodeStream(inputStream); return bitmap; } finally { if (inputStream != null) { inputStream.close(); } if (outputStream != null) { outputStream.close(); } entity.consumeContent(); } } } catch (IOException e) { getRequest.abort(); Log.w(LOG_TAG, "I/O error while retrieving bitmap from " + url, e); } catch (IllegalStateException e) { getRequest.abort(); Log.w(LOG_TAG, "Incorrect URL: " + url); } catch (Exception e) { getRequest.abort(); Log.w(LOG_TAG, "Error while retrieving bitmap from " + url, e); } finally { if (client != null) { client.close(); } } return null; } /** * Once the image is downloaded, associates it to the imageView */ @Override protected void onPostExecute(Bitmap bitmap) { if (isCancelled()) { bitmap = null; } // Add bitmap to cache if (bitmap != null) { synchronized (sHardBitmapCache) { sHardBitmapCache.put(url, bitmap); } } if (imageViewReference != null) { ImageView imageView = imageViewReference.get(); BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView); // Change bitmap only if this process is still associated with it if (this == bitmapDownloaderTask) { imageView.setImageBitmap(bitmap); } } } public void copy(InputStream in, OutputStream out) throws IOException { byte[] b = new byte[IO_BUFFER_SIZE]; int read; while ((read = in.read(b)) != -1) { out.write(b, 0, read); } } } /** * A fake Drawable that will be attached to the imageView while the download is in progress. * * <p>Contains a reference to the actual download task, so that a download task can be stopped * if a new binding is required, and makes sure that only the last started download process can * bind its result, independently of the download finish order.</p> */ static class DownloadedDrawable extends ColorDrawable { private final WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference; public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) { super(Color.BLACK); bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask); } public BitmapDownloaderTask getBitmapDownloaderTask() { return bitmapDownloaderTaskReference.get(); } } } //src\com\example\android\xmladapters\PhotosListActivity.java package com.example.android.xmladapters; import android.app.ListActivity; import android.net.Uri; import android.os.Bundle; /** * This activity uses a custom cursor adapter which fetches a XML photo feed and parses the XML to * extract the images' URL and their title. */ public class PhotosListActivity extends ListActivity { private static final String PICASA_FEED_URL = "http://picasaweb.google.com/data/feed/api/featured?max-results=50&thumbsize=144c"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.photos_list); setListAdapter(Adapters.loadCursorAdapter(this, R.xml.photos, "content://xmldocument/?url=" + Uri.encode(PICASA_FEED_URL))); } } //src\com\example\android\xmladapters\RssReaderActivity.java package com.example.android.xmladapters; import android.app.ListActivity; import android.content.XmlDocumentProvider; import android.net.Uri; import android.os.Bundle; import android.widget.AdapterView.OnItemClickListener; /** * This example demonstrate the creation of a simple RSS feed reader using the XML adapter syntax. * The different elements of the feed are extracted using an {@link XmlDocumentProvider} and are * binded to the different views. An {@link OnItemClickListener} is also added, which will open a * browser on the associated news item page. */ public class RssReaderActivity extends ListActivity { private static final String FEED_URI = "http://feeds.nytimes.com/nyt/rss/HomePage"; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.rss_feeds_list); setListAdapter(Adapters.loadCursorAdapter(this, R.xml.rss_feed, "content://xmldocument/?url=" + Uri.encode(FEED_URI))); getListView().setOnItemClickListener(new UrlIntentListener()); } } //src\com\example\android\xmladapters\UrlImageBinder.java package com.example.android.xmladapters; import android.content.Context; import android.database.Cursor; import android.view.View; import android.widget.ImageView; /** * This CursorBinder binds the provided image URL to an ImageView by downloading the image from the * Internet. */ public class UrlImageBinder extends Adapters.CursorBinder { private final ImageDownloader imageDownloader; public UrlImageBinder(Context context, Adapters.CursorTransformation transformation) { super(context, transformation); imageDownloader = new ImageDownloader(); } @Override public boolean bind(View view, Cursor cursor, int columnIndex) { if (view instanceof ImageView) { final String url = mTransformation.transform(cursor, columnIndex); imageDownloader.download(url, (ImageView) view); return true; } return false; } } //src\com\example\android\xmladapters\UrlIntentListener.java package com.example.android.xmladapters; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; /** * A listener which expects a URL as a tag of the view it is associated with. It then opens the URL * in the browser application. */ public class UrlIntentListener implements OnItemClickListener { public void onItemClick(AdapterView<?> parent, View view, int position, long id) { final String url = view.getTag().toString(); final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); final Context context = parent.getContext(); context.startActivity(intent); } } // //res\layout\contact_item.xml <?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="wrap_content" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeight"> <TextView android:id="@+id/name" android:layout_width="0px" android:layout_weight="1.0" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceLarge" android:gravity="center_vertical" android:drawablePadding="6dip" android:paddingLeft="6dip" android:paddingRight="6dip" /> <ImageView android:id="@+id/star" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> //res\layout\contacts_list.xml <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@string/no_contacts" android:visibility="gone" /> </merge> //res\layout\photo_item.xml <?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="wrap_content" android:gravity="center_vertical" android:minHeight="?android:attr/listPreferredItemHeight"> <ImageView android:id="@+id/photo" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingLeft="6dip" android:paddingTop="4dip" android:paddingBottom="4dip" /> <TextView android:id="@+id/title" android:layout_width="0px" android:layout_weight="1.0" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:gravity="center_vertical" android:paddingLeft="6dip" android:paddingRight="6dip" /> </LinearLayout> //res\layout\photos_list.xml <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@string/no_photos" android:visibility="gone" /> </merge> //res\layout\rss_feed_item.xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/item_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" android:minHeight="?android:attr/listPreferredItemHeight"> <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_weight="1.0" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:gravity="left" android:paddingLeft="6dip" android:paddingRight="6dip" /> <TextView android:id="@+id/date" android:layout_width="fill_parent" android:layout_weight="1.0" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:gravity="left" android:paddingLeft="6dip" android:paddingRight="6dip" /> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:minHeight="?android:attr/listPreferredItemHeight"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="match_parent" android:gravity="center_vertical" android:paddingLeft="6dip" /> <TextView android:id="@+id/description" android:layout_width="fill_parent" android:layout_weight="1.0" android:layout_height="wrap_content" android:textAppearance="?android:attr/textAppearanceSmall" android:gravity="left" android:paddingLeft="6dip" android:paddingRight="6dip" /> </LinearLayout> </LinearLayout> //res\layout\rss_feeds_list.xml <?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <ListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" /> <TextView android:id="@android:id/empty" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" android:text="@string/no_rss_feed" android:visibility="gone" /> </merge> // //res\values\attrs.xml <?xml version="1.0" encoding="utf-8"?> <resources> <!-- Adapter used to bind cursors. --> <declare-styleable name="CursorAdapter"> <!-- URI to get the cursor from. Optional. --> <attr name="uri" format="string" /> <!-- Selection statement for the query. Optional. --> <attr name="selection" format="string" /> <!-- Sort order statement for the query. Optional. --> <attr name="sortOrder" format="string" /> <!-- Layout resource used to display each row from the cursor. Mandatory. --> <attr name="layout" format="reference" /> </declare-styleable> <!-- Attributes used in bind items for XML cursor adapters. --> <declare-styleable name="CursorAdapter_BindItem"> <!-- The name of the column to bind from. Mandatory. --> <attr name="from" format="string" /> <!-- The resource id of the view to bind to. Mandatory. --> <attr name="to" format="reference" /> <!-- The type of binding. If this value is not specified, the type will be inferred from the type of the "to" target view. Mandatory. The type can be one of: <ul> <li>string, The content of the column is interpreted as a string.</li> <li>image, The content of the column is interpreted as a blob describing an image.</li> <li>image-uri, The content of the column is interpreted as a URI to an image.</li> <li>drawable, The content of the column is interpreted as a resource id to a drawable.</li> <li>A fully qualified class name, corresponding to an implementation of android.widget.Adapters.CursorBinder.</li> </ul> --> <attr name="as" format="string" /> </declare-styleable> <!-- Attributes used in select items for XML cursor adapters.--> <declare-styleable name="CursorAdapter_SelectItem"> <!-- The name of the column to select. Mandatory. --> <attr name="column" format="string" /> </declare-styleable> <!-- Attributes used to map values to new values in XML cursor adapters' bind items. --> <declare-styleable name="CursorAdapter_MapItem"> <!-- The original value from the column. Mandatory. --> <attr name="fromValue" format="string" /> <!-- The new value from the column. Mandatory. --> <attr name="toValue" format="string" /> </declare-styleable> <!-- Attributes used to map values to new values in XML cursor adapters' bind items. --> <declare-styleable name="CursorAdapter_TransformItem"> <!-- The transformation expression. Mandatory if "withClass" is not specified. --> <attr name="withExpression" format="string" /> <!-- The transformation class, an implementation of android.widget.Adapters.CursorTransformation. Mandatory if "withExpression" is not specified. --> <attr name="withClass" format="string" /> </declare-styleable> </resources> //res\values\strings.xml <?xml version="1.0" encoding="utf-8"?> <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> <string name="app_name">Xml Adapters</string> <string name="contacts_list_activity">Xml Contacts Adapter</string> <string name="photos_list_activity">Xml Photos Adapter</string> <string name="rss_reader_activity">Xml RSS Reader</string> <string name="no_contacts">No contacts available</string> <string name="no_photos">Loading photos...</string> <string name="no_rss_feed">Loading RSS feed...</string> </resources> // //res\xml\contacts.xml <?xml version="1.0" encoding="utf-8"?> <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters" app:uri="content://com.android.contacts/contacts" app:selection="has_phone_number=1" app:layout="@layout/contact_item"> <bind app:from="display_name" app:to="@id/name" app:as="string" /> <bind app:from="starred" app:to="@id/star" app:as="drawable"> <map app:fromValue="0" app:toValue="@android:drawable/star_big_off" /> <map app:fromValue="1" app:toValue="@android:drawable/star_big_on" /> </bind> <bind app:from="_id" app:to="@id/name" app:as="com.example.android.xmladapters.ContactPhotoBinder" /> </cursor-adapter> //res\xml\photos.xml <?xml version="1.0" encoding="utf-8"?> <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters" app:selection="/feed/entry" app:layout="@layout/photo_item"> <bind app:from="/summary" app:to="@id/title" app:as="string" /> <bind app:from="/media:group/media:thumbnail\@url" app:to="@id/photo" app:as="com.example.android.xmladapters.UrlImageBinder" /> </cursor-adapter> //res\xml\rss_feed.xml <?xml version="1.0" encoding="utf-8"?> <cursor-adapter xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res/com.example.android.xmladapters" app:selection="/rss/channel/item" app:layout="@layout/rss_feed_item"> <bind app:from="/title" app:to="@id/title" app:as="string" /> <bind app:from="/media:content@url" app:to="@id/image" app:as="com.example.android.xmladapters.UrlImageBinder"/> <bind app:from="/media:description" app:to="@id/description" app:as="string" /> <bind app:from="/guid" app:to="@id/item_layout" app:as="tag" /> <bind app:from="/pubDate" app:to="@id/date" app:as="string"> <transform app:withExpression="Published on {/pubDate}." /> </bind> </cursor-adapter>
Xml Parse
//package com.helloandroid.acashboard.list; import java.util.ArrayList; import java.util.List; import android.sax.Element; import android.sax.EndElementListener; import android.sax.EndTextElementListener; import android.sax.RootElement; import android.util.Xml; public class XmlParseList { // TODO: do 1 function 2 folowing functon // parsing projects public List<String> parse(String XMLDOCUMENT,String root2,String itemname,String title,String title2) { // final Message currentMessage = new Message(); //TODO unused variable RootElement root = new RootElement(root2); final List<String> messages = new ArrayList<String>(); final List<String> id = new ArrayList<String>(); Element item = root.getChild(itemname); item.setEndElementListener(new EndElementListener() { public void end() { // documentd end listener! } }); item.getChild(title).setEndTextElementListener( new EndTextElementListener() { public void end(String body) { // TODO GET CONTENT messages.add(body); //get name } }); item.getChild(title2).setEndTextElementListener( new EndTextElementListener() { public void end(String body) { // TODO GET CONTENT messages.add(body); //get id } }); try { Xml.parse(XMLDOCUMENT, root.getContentHandler()); } catch (Exception e) { throw new RuntimeException(e); } return messages; } }
Xml Serializer Uri
/** * */ //package org.alldroid.forum.xml; import java.net.URL; import java.util.LinkedList; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; import android.net.Uri; import android.util.Log; /** * @author trr4rac * */ public class XmlSerializer { private static final String TAG = XmlSerializer.class.getSimpleName ( ); private DocumentBuilder builder; private Document document; /** * */ public XmlSerializer ( Uri url ) { try { builder = DocumentBuilderFactory.newInstance ( ).newDocumentBuilder ( ); document = builder.parse(new URL(url.toString ( )).openStream ( )); } catch ( Exception e ) { Log.e(TAG,e.toString ( )); } } public <T extends IXmlSerializable> T deserialize ( Class<T> cls ) throws SAXException { T result; if ( document == null ) { throw new SAXException("Document not available."); } Element ele = document.getDocumentElement ( ); try { result = cls.newInstance ( ); } catch ( Exception e ) { Log.e(TAG,e.toString ( )); return null; } result.deserialize ( ele ); return result; } } interface IXmlSerializable { public abstract void deserialize ( Element ele ); }
Get Text Content from Xml Node
import org.w3c.dom.Node; import org.w3c.dom.NodeList; class Dom { public static String getTextContent(Node n) { StringBuffer sb = new StringBuffer(); NodeList nl = n.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node child = nl.item(i); if (child.getNodeType() == Node.TEXT_NODE) sb.append(child.getNodeValue()); } return sb.toString(); } }
Escape un escape Xml
class Main{ public static String escapeXMLChars(String s) { return s.replaceAll("&", "&") .replaceAll("'", "'") .replaceAll("\"", """) .replaceAll("<", "<") .replaceAll(">", ">"); } public static String unescapeXMLChars(String s) { return s.replaceAll("&", "&") .replaceAll("'", "'") .replaceAll(""", "\"") .replaceAll("<", "<") .replaceAll(">", ">"); } }
Get Xml node value with substring
class Main{ public static String getNodeValue(String XML, String nodeName){ String tokenValue = null; int start = XML.indexOf("<"+nodeName+">") + ("<"+nodeName+">").length(); int stop = XML.indexOf("</"+nodeName+">"); if(start>stop || stop<0) return ""; try{ tokenValue = XML.substring(start, stop); }catch (Exception ex){ ex.printStackTrace(); } return tokenValue; } }
Get value from Element
import java.util.ArrayList; import org.w3c.dom.Element; import org.w3c.dom.NodeList; class Main { public static Long getLongElementValue(Element el, String child, long defaultValue) { try { String v = ((Element) el.getElementsByTagName(child).item(0)) .getChildNodes().item(0).getNodeValue(); return Long.parseLong(v); } catch (Exception ex) { return defaultValue; } } public static int getIntElementValue(Element el, String child, int defaultValue) { try { String v = ((Element) el.getElementsByTagName(child).item(0)) .getChildNodes().item(0).getNodeValue(); return Integer.parseInt(v); } catch (Exception ex) { return defaultValue; } } public static ArrayList<Element> getElements(Element parent, String children) { NodeList nodelist = parent.getElementsByTagName(children); ArrayList<Element> elements = new ArrayList<Element>(); int l = nodelist.getLength(); for (int i = 0; i < l; i++) { Element element = (Element) nodelist.item(i); elements.add(element); } return elements; } public static Element getElement(Element parent, String name) { NodeList nodelist = parent.getElementsByTagName(name); if (nodelist.getLength() > 0) { return (Element) nodelist.item(0); } return null; } }
Get attribute value
//package org.anddev.andengine.util; import org.xml.sax.Attributes; class SAXUtils { public static String getAttribute(final Attributes pAttributes, final String pAttributeName, final String pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? value : pDefaultValue; } public static String getAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { final String value = pAttributes.getValue("", pAttributeName); if(value != null) { return value; } else { throw new IllegalArgumentException("No value found for attribute: '" + pAttributeName + "'"); } } public static boolean getBooleanAttribute(final Attributes pAttributes, final String pAttributeName, final boolean pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Boolean.parseBoolean(value) : pDefaultValue; } public static boolean getBooleanAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Boolean.parseBoolean(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static byte getByteAttribute(final Attributes pAttributes, final String pAttributeName, final byte pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Byte.parseByte(value) : pDefaultValue; } public static byte getByteAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Byte.parseByte(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static short getShortAttribute(final Attributes pAttributes, final String pAttributeName, final short pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Short.parseShort(value) : pDefaultValue; } public static short getShortAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Short.parseShort(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static int getIntAttribute(final Attributes pAttributes, final String pAttributeName, final int pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Integer.parseInt(value) : pDefaultValue; } public static int getIntAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Integer.parseInt(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static long getLongAttribute(final Attributes pAttributes, final String pAttributeName, final long pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Long.parseLong(value) : pDefaultValue; } public static long getLongAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Long.parseLong(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static float getFloatAttribute(final Attributes pAttributes, final String pAttributeName, final float pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Float.parseFloat(value) : pDefaultValue; } public static float getFloatAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Float.parseFloat(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static double getDoubleAttribute(final Attributes pAttributes, final String pAttributeName, final double pDefaultValue) { final String value = pAttributes.getValue("", pAttributeName); return (value != null) ? Double.parseDouble(value) : pDefaultValue; } public static double getDoubleAttributeOrThrow(final Attributes pAttributes, final String pAttributeName) { return Double.parseDouble(SAXUtils.getAttributeOrThrow(pAttributes, pAttributeName)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final boolean pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final byte pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final short pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final int pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final long pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final float pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final double pValue) { SAXUtils.appendAttribute(pStringBuilder, pName, String.valueOf(pValue)); } public static void appendAttribute(final StringBuilder pStringBuilder, final String pName, final String pValue) { pStringBuilder.append(' ').append(pName).append('=').append('\"').append(pValue).append('\"'); } // // Inner and Anonymous Classes // }
Get field from NamedNodeMap
import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; public abstract class APIUtil { public static String getField( NamedNodeMap fields, String fieldName ) { Node field = fields.getNamedItem( fieldName ); if( field != null ) return field.getNodeValue(); else return new String(); } }
get Node Value With Attribute
class Main { public static String getNodeValueWithAttribute(String XML, String nodeName, String attribute) { String tokenValue = null; int start = XML.indexOf("<" + nodeName + " " + attribute + ">") + ("<" + nodeName + " " + attribute + ">").length(); int stop = XML.indexOf("</" + nodeName + ">"); try { tokenValue = XML.substring(start, stop); } catch (Exception ex) { } return tokenValue; } }
get Character Data From Element
import org.w3c.dom.CharacterData; import org.w3c.dom.Element; import org.w3c.dom.Node; class Main { public static String getCharacterDataFromElement(Element e) { Node child = e.getFirstChild(); if (child instanceof CharacterData) { CharacterData cd = (CharacterData) child; return cd.getData(); } return "?"; } }
update Xml
//package it.tava.andbudget.data.parser; import java.io.File; import java.io.IOException; import java.io.StringWriter; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.w3c.dom.Document; import org.xml.sax.SAXException; import org.xmlpull.v1.XmlSerializer; import android.util.Log; import android.util.Xml; public class XmlParser { private final static String TAG = "it.tava.andbudget.xml.XmlParser"; private static final String JAXP = "http://java.sun.com/xml/jaxp/properties/"; private static final String JAXP_SCHEMA_LANGUAGE = JAXP + "schemaLanguage"; private static final String JAXP_SCHEMA_SOURCE = JAXP + "schemaSource"; private static final String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; /** * Compila il file XML che viene passato come parametro e restituisce il Document DOM * associato, null altrimenti. * * @param xml il file XML di cui effettuare il parsing * @param xsd il file XSD schema associato all'XML in input * @param validation se effettuare o meno la validazione del file ottenuto * @return il Document DOM associato al file in ingresso o null altrimenti */ public static Document compileXml(File xml, File xsd, boolean validation) { Document document = null; try { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); factory.setValidating(validation); factory.setNamespaceAware(true); factory.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); factory.setAttribute(JAXP_SCHEMA_SOURCE, xsd); DocumentBuilder builder = factory.newDocumentBuilder(); document = builder.parse(xml); } catch (SAXException se) { Log.e(TAG, "Error: compilation failed"); } catch (IOException ioe) { Log.e(TAG, "Error: IOException"); } catch (ParserConfigurationException e) { Log.e(TAG, "Error: ParserConfigurationException"); } return document; } public static void updateXml(File xml, File xsd, Document document) { XmlSerializer serializer = Xml.newSerializer(); StringWriter writer = new StringWriter(); try { //Writer w = new FileWriter(new File("prova.xml")); serializer.setOutput(writer); serializer.startDocument("UTF-8", true); serializer.startTag("", "budget"); serializer.endTag("", "budget"); serializer.endDocument(); System.out.println(writer.toString()); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } }