intents - appspot.combranko-cirovic.appspot.com/cp3490/listviews.pdf · intents activity_main.xml...
TRANSCRIPT
Intents
So far our apps had a single view.
Next example demonstrates how to create a navigation path through series of views.
Create new project Button Intent.
Intents
We’ll need some graphics (http://flaticons.net, Next-128.png saved as next.png)
Create folder drawable-xxhdpi relative to folder res, copy and paste downloaded image into newly created folder.
Intents
activity_main.xml is quite simple - linear vertical layout containing one button and text, horizontally centered with top margin of 64dp.
Intents
<LinearLayout android:id="@+id/next" android:orientation="vertical" android:layout_width="72dp" android:layout_height="96dp" android:layout_centerHorizontal="true" android:layout_marginTop="64dp">
Intents
<ImageButton android:layout_width="match_parent" android:layout_height="72dp" android:id="@+id/nextButton" android:gravity="center" android:background="@drawable/next" android:clickable="true"/>
Intents
<TextView android:layout_width="match_parent" android:layout_height="24dp" android:gravity="center_horizontal" android:layout_marginTop="6dp" android:text="Next"/> </LinearLayout>
We need another activity to transition to. Create layout next_activity.xml. Inside a relative layout we have a single Textview:
Intents
Intents<TextView android:id="@+id/nextTextView" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Next Activity" android:fontFamily="sans-serif-light" android:textSize="24dp" android:layout_marginLeft="12dp" android:layout_marginRight="12dp" android:gravity="center_horizontal" android:layout_centerVertical=“true"/>
NextActivity.java simply sets the content view to next_activity.xml
Intents
import android.support.v7.app.AppCompatActivity; import android.os.Bundle;
public class NextActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.next_activity); } }
In order to transition from MainActivity to NextActivity, we need to set onClickListener associated with the button:
Intents
ImageButton nextButton = (ImageButton)findViewById(R.id.nextButton); nextButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent nextScreen = new Intent(getApplicationContext(), NextActivity.class); startActivity(nextScreen); } });
NextActivity must be registered in AndroidManifest.xml:
Intents
<activity android:name=".NextActivity"> </activity>
Sometimes we need to pass data between activities. Say, we wanted to pass a message from MainActivity to the NextActivity. In MainActivity, just before we start intent:
Intents
nextScreen.putExtra("message", "Hello from MainActivity");
In NextActivity we have:
Intents
Bundle extras = getIntent().getExtras(); String message = extras.getString("message");
TextView nextText = (TextView) findViewById(R.id.nextTextView); nextText.setText(message);
An intent is an abstract description of an operation to be performed. It can be used with one Activity to launch another.
The primary pieces of information in an intent are:
Intents
1. action -- The general action to be performed, such as ACTION_VIEW.
2. data -- The data to operate on.
ListView is a view group that displays a list of scrollable items.
The list items are automatically inserted to the list using an Adapter.
ListView.1.zip
ListView
Adapters
An adapter manages the data model and adapts it to the individual rows in the list view. An adapter extends the BaseAdapter class.
Every line in the list view consists of a layout which can be as complex as you want.
In its simplest form it consists of a single TextView
activity_main.xml<?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http:// schemas.android.com/apk/ res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp" android:textSize="20sp"> </TextView>
Adapters
The adapter would inflate the layout for each row in its getView() method and assign the data to the individual views in the row.
The adapter is assigned to the ListView via the setAdapter method on the ListView object.
MainActivity.javapackage com.example.admin.listview1;
import android.app.ListActivity; import android.os.Bundle; 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 android.widget.AdapterView.OnItemClickListener;
public class MainActivity extends ListActivity {
MainActivity.javastatic final String[] PROGRAMS = new String[] { "Architectural", "Civil", "Geomatics", "Computing Systems", "Electronics (Biomedical)", "Electronics (Instrumentation)", "Electrical (Power)", "Telecommunications", "Chemical Processing", "Industrial", “Mechanical", "Mechanical (Manufacturing)", "Petroleum"};
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter(new ArrayAdapter<String>(this, R.layout.activity_main,PROGRAMS));
ListView listView = getListView(); listView.setTextFilterEnabled(true);
MainActivity.java
listView.setOnItemClickListener( new OnItemClickListener() { public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Toast.makeText(getApplicationContext(), ((TextView) view).getText(), Toast.LENGTH_SHORT).show(); } });
In many cases, single title inside list view’s cell is not sufficient ( e.g. Play Store )
Android allows us to define layout of cells in any way imaginable.
ListView.2.zip
CustomAdapters
Custom Adapters
Computing SystemsEngineering Technology
TextView
RelativeLayout
TextView
ImageView
In our case, cell might look like:
Custom Adapters
Create drawable-xxhdpi folder as before and paste into it right_arrow.png
Cell layout will be defined in rowlist.xml file: right-click on layout subdirectory and select New, XML, Layout XML file.
rowlist.xml<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/ res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp">
<TextView android:id="@+id/textid" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="text" android:textColor="#666" android:textSize="20sp"/>
rowlist.xml <TextView android:id="@+id/subtextid" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@id/textid" android:textColor="#aaa" android:textSize="12sp" android:text="subtext" android:layout_marginTop=“0dp"/>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/right_arrow" android:layout_alignParentRight="true" android:layout_centerVertical="true"/> </RelativeLayout>
activity_main.xml
<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height=“match_parent" android:background="#ffffff"> <ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/programListView" android:layout_gravity="center" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:clickable="true"/> </LinearLayout>
Contains a single ListView inside LinearLayout :
Custom Adapters
In order to create custom adapter, we have to create new class ProgramAdapter.java which extends ArrayAdapter.
Right-click on first subfolder relative to java folder, New, Java Class, ProgramAdapter.
ProgramAdapter.javapackage com.example.admin.listview2;
import android.widget.ArrayAdapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;
public class ProgramAdapter extends ArrayAdapter <String> { private Context context; private String[] items;
ProgramAdapter.javapublic ProgramAdapter(Context context, String[] items) {
super(context, R.layout.rowlist, R.id.textid, items); this.context = context; this.items = items; }
@Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context .getSystemService( Context.LAYOUT_INFLATER_SERVICE);
ProgramAdapter.java View rowView = inflater.inflate(R.layout.rowlist, parent, false); TextView textView = (TextView) rowView.findViewById(R.id.textid); TextView subTextView = (TextView) rowView.findViewById(R.id.subtextid); textView.setText(items[position]); subTextView.setText("Engineering Technology"); return rowView; } }
MainActivity.javapackage com.example.admin.listview2;
import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.widget.ListView; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView; import android.view.View; import android.widget.Toast;
public class MainActivity extends AppCompatActivity { static final String[] PROGRAMS = new String[] { "Architectural", "Civil", "Geomatics", "Computing Systems", "Biomedical", "Instrumentation", "Electrical (Power)", "Telecommunications", "Chemical Processing", "Industrial", “Mechanical", "Manufacturing", "Petroleum" };
MainActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
ListView programListView = (ListView) findViewById(R.id.programListView); programListView.setAdapter(new ProgramAdapter(this, PROGRAMS)); programListView.setTextFilterEnabled(true);
MainActivity.java programListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String selectedValue = (String) parent.getItemAtPosition(position); Toast.makeText(getBaseContext(), selectedValue, Toast.LENGTH_SHORT).show(); } }); } }
ListView Intents
ListView Intents
ListView.3.zip
Now that we have a list of programs, how would one create a list of courses based on a selected program?
In order to do so, we have to create an Intent. In general, for each new view we have to:
ListView Intents
1. Create layout ( xml ) in res/layout
2. Create activity ( java ) in java/com.example.listview or similar
3. Create Intent in activity
4. Register activity in AndroidManifest.xml
ListView Intents
Create new layout courses.xml.
Since both, programs and courses are lists, copy content of activity_main.xml, paste it into courses.xml
Replace id programListView with courseListView.
courses.xml<LinearLayout android:orientation="vertical" android:layout_width="match_parent" android:layout_height=“match_parent" android:background="#fff">
<ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/courseListView" android:layout_gravity="center" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:background="#fff" android:clickable="true"/> </LinearLayout>
Courses Adapter
Before we implement ComputingActivity ( shows courses in Computing Systems ) we have to create CourseAdapter.java
ProgramAdapter’s subtext was constant - Engineering Technology. Here, subtext is course’s number.
CourseAdapter.java
package com.example.admin.listview3;
import android.widget.ArrayAdapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;
public class CourseAdapter extends ArrayAdapter <String> { private Context context; private String[] items;
CourseAdapter.java
public CourseAdapter(Context context, String[] items) { super(context, R.layout.rowlist, R.id.textid, items); this.context = context; this.items = items; }
@Override public View getView(int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) context .getSystemService( Context.LAYOUT_INFLATER_SERVICE);
CourseAdapter.java
View rowView = inflater.inflate(R.layout.rowlist, parent, false); TextView textView = (TextView) rowView.findViewById(R.id.textid); TextView subTextView = (TextView) rowView.findViewById(R.id.subtextid); textView.setText(items[position]);
String s = items[position];
CourseAdapter.java if (s.equals("Active Circuit Applications")) subTextView.setText("AE3130"); else if (s.equals("Switching and L2 Security")) subTextView.setText("CE3370"); else if (s.equals("Software Engineering")) subTextView.setText("CP3490"); else if (s.equals("Databases")) subTextView.setText("CP3520"); else if (s.equals("Project Management")) subTextView.setText("PR3150"); else if (s.equals("Capstone Project")) subTextView.setText("PR2760");
return rowView; } }
Computing Activity
Create new file ComputingActivity.java in java subfolder folder, as you did before.
Computing Activity should simply display the list of courses when Computing Systems is selected ( onItemClick ) in MainActivity.
ComputingActivity.javapackage com.example.admin.listview3;
import android.os.Bundle; import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import android.widget.Toast;
public class ComputingActivity extends AppCompatActivity { static final String[] COURSES = new String[] { "Active Circuit Applications", "Switching and L2 Security", "Software Engineering", "Databases", "Project Management", "Capstone Project" };
ComputingActivity.java
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.courses);
ListView courseListView = (ListView) findViewById(R.id.courseListView); courseListView.setAdapter(new CourseAdapter(this, COURSES)); courseListView.setTextFilterEnabled(true);
ComputingActivity.java courseListView.setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String selectedValue = (String) parent.getItemAtPosition(position); Toast.makeText(getBaseContext(), selectedValue, Toast.LENGTH_SHORT).show(); } }); }
IntentNow, in order to link MainActivity to ComputingActivity, we have to create Intent in MainActivity ( instead of Toast ):
if(selectedValue.equals("Computing Systems")) { Intent computingScreen = newIntent(getApplicationContext(), ComputingActivity.class); startActivity(computingScreen); }
Intent
Before we can test our App, we have to register new activity ( ComputingActivity ) in Manifest file.
<activity android:name=".ComputingActivity" android:label="Courses" android:screenOrientation="portrait" />
Smooth Transitions
Android transitions between activities are quite simple - new view simply pops up. Animated transitions are accomplished through XML.
First of all, right-click on ListView/res and select New, Folder. Name it anim.
Copy slide_*xml files into anim folder.
Smooth Transitions
In MainActivity.java, after startActivity(computingScreen); add line:
overridePendingTransition( R.anim.slide_in_right, R.anim.slide_out_left);
Smooth Transitions
In Computing Activity.java, add method:
@Override public void onBackPressed() { super.onBackPressed(); overridePendingTransition( R.anim.slide_in_left, R.anim.slide_out_right ); }
ListView and XML
ListView and XMLSo far, our list views were populated by static data - array lists.
This is not normally the case - data is normally provided via XML files ( plain text files or output of database queries ).
Typically, data needs to be fetched from specified URL ( if network is available ), parsed and stored in a ArrayList. (ListView.4.zip)
ListView and XML
Networkavailable
class GetXML extends AsyncTask
doInBackground() { // fetch file }
onPostExecute() { // parse file }
T
Alert Error
F
Program’s XML structureEach program has associated XML file stored in Google cloud whose structure is:
<?xml version="1.0"?> <courses> <semester> <number>1</number> <course> <cid>CM1400</cid> <cname>Technical Report Writing I</cname> <credit>3</credit> <lect>3</lect> <lab>0</lab> </course> ... </semster> ... <courses>
NetworkingBefore we can request particular file, we have to construct file’s name based on selection ( Computing Systems - cs.xml etc. )
In MainActivity.java declare class variable : private String loc = new String();
Now, in onItemClick() method determine which file is to be fetched:
NetworkingStringBuffer sb = new StringBuffer(); sb.append("http://your-url.appspot.com/etcapp/programs/"); if(selectedValue.equals("Architectural")) sb.append("ae.xml"); else if(selectedValue.equals("Civil")) sb.append("ce.xml"); else if(selectedValue.equals("Geomatics")) sb.append("ge.xml"); else if(selectedValue.equals("Biomedical")) sb.append("eb.xml"); else if(selectedValue.equals("Computing Systems")) sb.append("cs.xml"); else if(selectedValue.equals("Electrical (Power)")) sb.append("ep.xml"); else if(selectedValue.equals("Instrumentation")) sb.append("ei.xml");
Networking
else if(selectedValue.equals("Telecommunications")) sb.append("te.xml"); else if(selectedValue.equals("Chemical Processing")) sb.append("cp.xml"); else if(selectedValue.equals("Industrial")) sb.append("in.xml"); else if(selectedValue.equals("Mechanical")) sb.append("me.xml"); else if(selectedValue.equals("Manufacturing")) sb.append("mm.xml"); else if(selectedValue.equals("Petroleum")) sb.append("pe.xml"); loc = sb.toString();
Networking
Before we can access network, we have to enable it in Manifest.xml ( before opening <application> tag ):
<uses-permission android:name="android.permission.INTERNET"/><uses-permission android:name="android.permission. ACCESS_NETWORK_STATE"/>
Networking
Now we can check the network status :
if(!isNetworkAvailable()) new AlertDialog.Builder(MainActivity.this). setTitle("Error").setMessage("No Network Connection"). setNeutralButton("Close", null).show(); else { // fetch and process data }
Networkingpublic boolean isNetworkAvailable() { ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo networkInfo = cm.getActiveNetworkInfo(); if (networkInfo != null && networkInfo.isConnected()) return true; else return false; }
Fetching data
To provide a good user experience all potentially slow running operations in an Android application should run asynchronously, e.g. via some way of concurrency.
This includes all potentially slow operations, like network, file and database access and complex calculations.
AsyncTask classThe AsyncTask class encapsulates the creation of a background process and the synchronization with the main thread.
To use AsyncTask we must subclass it. Parameters are generics ( type of arguments, progress value and result value ), e.g : AsyncTask <String, Void, String>
AsyncTask class
An AsyncTask is started via the execute() method.
The execute() method calls the doInBackGround() and the onPostExecute() method.
AsyncTask class
The doInBackGround() method contains the coding instruction which should be performed in a background thread.
This method runs automatically in a separate Thread.
AsyncTask class
The onPostExecute() method synchronizes itself again with the user interface thread and allows it to be updated.
This method is called by the framework once the doInBackGround() method finishes.
AsyncTask class
The else part calls execute("") method of the subclass GetXML:
if(!isNetworkAvailable()) new AlertDialog.Builder(MainActivity.this). setTitle("Error").setMessage("No Network Connection"). setNeutralButton("Close", null).show(); else { new GetXML().execute(""); }
AsyncTask classprivate class GetXML extends AsyncTask<String, Void, String> { String src = null; @Override protected String doInBackground(String... params) { try { URL url = new URL(loc); HttpURLConnection con = (HttpURLConnection) url.openConnection(); src = readStream(con.getInputStream()); } catch (Exception e) { e.printStackTrace(); } return src; }
AsyncTask class @Override protected void onPostExecute(String result) { if(src == null) new AlertDialog.Builder(MainActivity.this). setTitle("Error").setMessage("No Courses Found"). setNeutralButton("Close", null).show(); else { System.out.println(src); } }
@Override protected void onPreExecute() {}
@Override protected void onProgressUpdate(Void... values) {} }
readStream() Methodprivate String readStream(InputStream in) { BufferedReader reader = null; String line = null; StringBuffer sb = new StringBuffer(); try { reader = new BufferedReader(new InputStreamReader(in)); while ((line = reader.readLine()) != null { sb.append(line); } } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return sb.toString(); }
XML Parsing
Now that we fetched the file, we have to process it ( parse it ), in order to provide the data for courses list.
It is recommended to use the XmlPullParser ( simpler and faster than SAX and DOM Java parsers ). This parser is not available in standard Java.
XML ParsingThere are two key methods: next() and nextToken().
While next() provides access to high level parsing events, nextToken() allows access to lower level tokens.
The current event state of the parser can be determined by calling the getEventType() method. Initially, the parser is in the START_DOCUMENT state.
XML ParsingThe current event state of the parser can be determined by calling the getEventType() method.
Initially, the parser is in the START_DOCUMENT state.
The method next() advances the parser to the next event. Th following event types are seen by next():
XML Parsing1. START_TAG -- An XML start tag was read.
2. TEXT -- Text content was read; getText() can retrieve it.
3. END_TAG -- An end tag was read
4. END_DOCUMENT -- No more events are available
XML Parsing exampleimport java.io.IOException; import java.io.StringReader;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserFactory;
public class SimpleXmlPullApp { public static void main (String args[]) throws XmlPullParserException, IOException {
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser();
XML Parsing example xpp.setInput(new StringReader("<foo>Hello</foo>")); int eventType = xpp.getEventType(); while (eventType != XmlPullParser.END_DOCUMENT) { if(eventType == XmlPullParser.START_DOCUMENT) { System.out.println("Start document"); } else if(eventType == XmlPullParser.START_TAG) { System.out.println("Start tag "+xpp.getName()); } else if(eventType == XmlPullParser.END_TAG) { System.out.println("End tag "+xpp.getName()); } else if(eventType == XmlPullParser.TEXT) { System.out.println("Text "+xpp.getText()); } eventType = xpp.next(); } System.out.println("End document"); } }
XML Parsing
The example would generate the output:
Start document Start tag foo Text Hello End tag foo End document
XML Parsing
Where would we use XML parser?
We could use it in MainActivity, create ArrayList of courses and pass it to CoursesActivity, or
We could pass the String src to CoursesActivity and parse it there.
Courses Activity
We don’t need Computing Activity anymore.
Instead we will develop CoursesActivity which displays all the courses based on selected program in a ListView.
courses.xml
MainActivity will pass program’s name and source ( XML file ). Parsing will be done in CoursesActivity.
We start with courses.xml -- similar to existing one, except that list is below header displaying program’s name.
courses.xml<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#fff" xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="match_parent" android:layout_height="96dp" android:orientation="vertical" android:background="#055b8c" android:gravity="center" android:layout_marginBottom="0dp">
courses.xml <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/programTitle" android:gravity="center" android:text="Computing Systems" android:textColor="#fff" android:textSize="20sp"/> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:text="Engineering Technology" android:textColor="#fff" android:textSize="20sp"/>
courses.xml
<ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/courseListView" android:layout_gravity="center" android:layout_marginLeft="16dp" android:layout_marginRight="16dp" android:background="#fff" android:clickable="true"/> </LinearLayout>
Courses Activity
We have to pass program’s name and xml file (src ) to CoursesActivity. But how to do that since Activity’s constructor takes no arguments?
Android provides mechanism for that through method putExtra("name", variable) as in :coursesScreen.putExtra("source", src);
CoursesActivity.java
package com.example.listview;
import android.os.Bundle; import android.app.Activity; import android.widget.TextView;
public class CoursesActivity extends Activity { private String source = null; private String programTitle = null;
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.courses);
CoursesActivity.java Bundle extras = getIntent().getExtras(); source = extras.getString("source"); programTitle = extras.getString("programTitle"); TextView title = (TextView)findViewById(R.id.programTitle); title.setText(programTitle); System.out.println(source); }
@Override public void onBackPressed() { super.onBackPressed(); overridePendingTransition(R.anim.slide_in_left, R.anim.slide_out_right); } }
Main ActivityBack to MainActivity, onPostExecute() method, else part:
Intent coursesScreen = new Intent(getApplicationContext(), CoursesActivity.class); coursesScreen.putExtra("source", src); coursesScreen.putExtra("programTitle", title); startActivity(coursesScreen); overridePendingTransition(R.anim.slide_in_right, R.anim.slide_out_left);
Main Activity
There is an error, since variable title is not declared. Declare it under the declaration of variable loc:
private String title = new String();
Main Activity
Observe that title has the same value as selectedValue in onItemClick() method. So set it to:
title = selectedValue;
In Manifest, change ComputingActivity to CoursesActivity, cross fingers and run.
Parsing
Once we are done with parsing, for each semester we need ArrayList of courses, e.g ;
cname_1 cid_1credit lect lab
cname_2 cid_2credit lect lab
cname_2 cid_2credit lect lab
...
Parsing
Declare, therefore, array of ArrayLists of type Course. We’ll get an error, since we don’t have class Course.
private ArrayList<Course>[] courses;
Course.javapackage com.example.listview;
class Course { private String cname, cid; private String credit, lect, lab; public Course(String cid, String cname, String credit, String lect, String lab) { this.cid = cid; this.cname = cname; this.credit = credit; this.lect = lect; this.lab = lab; } public String getCid() { return cid; }
Course.java
public String getCname() { return cname; } public String getCredit() { return credit; } public String getLect() { return lect; } public String getLab() { return lab; } public String toString() { return cid + " " + cname + " " + credit + " " + lect + " " + lab; } }
Courses Activity
How many semesters are in a program?
Depends on the program. In order to create array courses we need to find out the number of semester:
Courses Activity
Where numberOfSections is class’ private variable
numberOfSections = 0; StringTokenizer st = new StringTokenizer(source); while(st.hasMoreTokens()) { String token = st.nextToken(); if(token.equals("<semester>")) numberOfSections++; } courses = new ArrayList[numberOfSections];parseXML(source);
parseXML(String)public void parseXML(String src) { try { StringReader sr = new StringReader(src); XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); factory.setNamespaceAware(true); XmlPullParser xpp = factory.newPullParser(); xpp.setInput(sr); String cid = new String(); String cname = new String(); String credit = new String(); String lect = new String(); String lab = new String(); int eventType = xpp.getEventType(); int semester = 0;
parseXML(String)while (eventType != XmlPullParser.END_DOCUMENT) { String name = null; switch(eventType) { case XmlPullParser.START_TAG : name = xpp.getName(); if(name.equals("semester")) { courses[semester] = new ArrayList<Course>(); semester++; } else if(name.equals("cid")) { cid = xpp.nextText(); } else if(name.equals("cname")) { cname = xpp.nextText();
parseXML(String) } else if(name.equals("credit")) { credit = xpp.nextText(); } else if(name.equals("lect")) { lect = xpp.nextText(); } else if(name.equals("lab")) { lab = xpp.nextText(); int index = semester - 1; Course c = new Course(cid, cname, credit, lect, lab); courses[index].add(c); } } eventType = xpp.next(); } }
parseXML(String)
catch (Exception e) { e.printStackTrace(); }
for(int i = 0; i < courses.length; i++) { System.out.println(i); for(int j = 0; j < courses[i].size(); j++) { System.out.println(courses[i].get(j)); } } }
Course AdapterNow that we have array of linked list, we have to somehow pass course’s name and id to adapter.
Single array of strings is not going to do it. Hash map will, where cid is the key and cname is the value. In Courses Activity, after parseXML(source) add:
List<Map<String, String>> data = new LinkedList<Map<String, String>>();
Course AdapterWe will also need a couple of fields and a method for adding tuples ( before onCreate ):
public final static String ITEM_TITLE = "cid"; public final static String ITEM_CAPTION = "cname"; public Map<String, String> createItem(String cid, String cname) { Map<String,String> item = new HashMap<String,String>(); item.put(ITEM_TITLE, cid); item.put(ITEM_CAPTION, cname); return item; }
Course Adapter
Now we can populate our Map with data and create list view:
for(int i = 0; i < courses.length; i++) { for(int j = 0; j < courses[i].size(); j++) { data.add(createItem(courses[i].get(j).getCid(), courses[i].get(j).getCname())); } }
Course Adapter
ListView coursesListView = (ListView) findViewById(R.id.coursesListView); coursesListView.setAdapter(new CourseAdapter( this, data, R.layout.coursesrowlist, new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.subtextid, R.id.textid })); coursesListView.setTextFilterEnabled(true);
Course Adapter
Observe the difference in Adapter’s constructor. Here we are extending SimpleAdapter, rather than ArrayAdapter.
SimpleAdapter is an adapter to map static data to views defined in an XML file. The Maps contain the data for each row.
Course AdapterConstructor takes context, data (map), resource (xml layout of each row), from (key and value) and to - text views in resource.
SimpleAdapter( this, data, R.layout.coursesrowlist, new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.subtextid, R.id.textid } )
courserowlist.xml<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android= "http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="10dp">
<TextView android:id="@+id/textid" android:layout_width="wrap_content" android:layout_height="wrap_content" android:textColor="#666" android:textSize="14sp" android:text="Text"/>
courserowlist.xml <TextView android:id="@+id/subtextid" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/textid" android:textColor="#aaa" android:textSize="12sp" android:layout_marginTop="0dp" android:text="subtext"/>
<ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:src="@drawable/right_arrow" android:layout_alignParentRight="true" android:layout_centerVertical="true"/> </RelativeLayout>
CourseAdapter.javapackage com.example.listview;
import java.util.List; import java.util.Map; import android.widget.SimpleAdapter; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView;
public class CourseAdapter extends SimpleAdapter { private Context context; private List<? extends Map<String, String>> items; private int resource;
CourseAdapter.java
public CourseAdapter(Context context, List<? extends Map<String, String>> items, int resource, String[] from, int[] to) { super(context, items, resource, from, to); this.context = context; this.items = items; this.resource = resource; }
CourseAdapter.java@Override public View getView(int position, View convertView, ViewGroup parent) {
LayoutInflater inflater = (LayoutInflater) context .getSystemService( Context.LAYOUT_INFLATER_SERVICE);
View rowView = inflater.inflate(resource, parent, false); Map <String, String> map = items.get(position); String title = map.get("cname"); String subtitle = map.get("cid");
CourseAdapter.java TextView textView = (TextView) rowView.findViewById(R.id.textid); textView.setText(title); TextView subTextView = (TextView) rowView.findViewById(R.id.subtextid); subTextView.setText(subtitle);
return rowView; } }
That’s it. Try to run it. (End of ListView.4.zip)
Dig deeper…
Detail Course View
ListView.5.zip
When user selects particular course from the list of courses, detailed view of course name, number, credit, lecture and lab hours should be displayed.
Detail Course View
We begin with the course.xml layout. Add it to res/layout folder.
Another file is required for round-corners background: listview_border.xml. Place it inside res/drawable-xxhdpi
course.xml
TextView
LinearLayout
TableLayout
Detail Course ViewIn Android we can use Shape Drawables to define background, borders and gradients for Views.
listview_border.xml is an example of drawable shape.
More details at : http://developer.android.com/reference/android/graphics/drawable/shapes/package-summary
Course ActivityIn Courses Activity, we have to set onItemClick listener and based on selected value ( cid ), find requested course in our array of linked lists.
Courses name, number of credit, lecture and lab hours should be extracted and Course Activity start as new Intent.
See CoursesActivity.java and CourseActivity.java for details.
SectionsListWithSections.zip
Unlike iOS, Android does not provide easy implementation of grouped lists.
Credit for adapter goes to Jeff Sharkey.
Separated List Adapter
SeparatedListAdapter class provides a single interface to multiple sections of other Adapters.
After using addSection() to construct the child sections, we can easily use ListView.setAdapter() to present the now-separated list to users.
LayoutsThree layouts are required:
1. activity_main.xml - LinearLayout with no children. ListView is programatically added in MainActivity.
2. rowlist.xml - defines regular rows in ListView
3. list_header.xml - defines header for each section
MainActivity
Sections are created in MainActivity:List<Map<String,?>> construction = new LinkedList<Map<String,?>>(); construction.add(createItem("Architectural", "Engineering Technology")); construction.add(createItem("Civil", "Engineering Technology")); construction.add(createItem("Geomatics", “Engineering Technology"));
MainActivity
Instance of SeparatedListAdapter is created:
SeparatedListAdapter adapter = new SeparatedListAdapter(this);
MainActivity
Items are added to sections:
adapter.addSection("CONSTRUCTION", new SimpleAdapter(this, construction, R.layout.rowlist, new String[] { ITEM_TITLE, ITEM_CAPTION }, new int[] { R.id.textid, R.id.subtextid }));
Separated List Adapter
"CONSTRUCTION" is section’s title
SimpleAdapter is an adapter to map static data to views defined in an XML file.
Constructor takes several arguments:
Separated List Adapter
context -- The context where the View associated with this SimpleAdapter is running
data -- A List of Maps. Each entry in the List corresponds to one row in the list. The Maps contain the data for each row, and should include all the entries specified in "from".
Separated List Adapter
resource -- Resource identifier of a view layout that defines the views for this list item. The layout file should include at least those named views defined in “to".
from -- A list of column names that will be added to the Map associated with each item.
Separated List Adapter
to -- The views that should display column in the "from" parameter. These should all be TextViews. The first N views in this list are given the values of the first N columns in the from parameter.
Separated List AdapterOnce the sections are created, we define ListView programatically and add it to the LinearLayout defined in activity_main.xml:
ListView list = new ListView(this); list.setAdapter(adapter); list.setBackgroundColor(Color.WHITE); LinearLayout ll = (LinearLayout) findViewById(R.id.mainlayout); ll.addView(list);
Lazy Image Loader
In many applications (Play store, News etc.) it is impractical to download all images before they are displayed.
Instead, such apps begin by loading the relevant text from RSS feed, and then download icons asynchronously so that user interface is more responsive.
Lazy News
LazyNews.1.zip
Lazy Image Loader
There is a number of helper classes that need to be imported into the project:
AsyncResponse.java, FileCache.javaFileDownloader.java, ImageLoader.javaLazyNewsAdapter.java, MemoryCache.javaUtils.java as well as placeholder.png
MainActivity
MainActivity is quite simple: it sets UI activity_main.xml displaying a progress bar and checks for network connectivity.
If there is no network, it displays error_layout.xml, otherwise it transitions into NewsActivity which asynchronously fetches and displays news.
MainActivity
networkavailable
error_layout.xml
NewsActivity (parse and display
list of news)
YES
NO
MainActivity
activity_main.xml<RelativeLayout xmlns:android="http://schemas.android.com/apk/res android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/loadingPanel" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity = "center_vertical|center_horizontal" tools:context=".MainActivity">
<ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity = "center_vertical|center_horizontal" android:indeterminate="true"/> </RelativeLayout>
error_layout.xml<RelativeLayout xmlns:android="http://schemas.android.com/apk/res android" android:layout_width="match_parent" android:layout_height="match_parent" android:background=“#eeeeee"> <TextView android:id="@+id/errorMessage" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Error" android:textSize="24dp" android:textColor="#888888" android:paddingLeft="24dp" android:paddingRight="24dp" android:layout_centerInParent="true" android:gravity="center"/> </RelativeLayout>
MainActivity.javapublic class MainActivity extends AppCompatActivity {
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main);
ConnectivityManager connManager = (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE); NetworkInfo mWifi = connManager.getNetworkInfo( ConnectivityManager.TYPE_WIFI); NetworkInfo mMobile = connManager.getNetworkInfo( ConnectivityManager.TYPE_MOBILE);
MainActivity.java
if (mWifi.isConnected() == false && mMobile.isConnected() == false) { showErrorView(); } else { System.out.println("Connected"); setContentView(R.layout.activity_main); findViewById(R.id.loadingPanel). setVisibility(View.VISIBLE); FileDownloader news = new FileDownloader("http://branko-cirovic.appspot.com/ etcapp/news/news.xml", MainActivity.this);
MainActivity.java news.setOnResultsListener(new AsyncResponse() { @Override public void processFinish(String output) { Intent newsScreen = new Intent(getApplicationContext(), NewsActivity.class); newsScreen.putExtra("xmlData", output); findViewById(R.id.loadingPanel). setVisibility(View.GONE); startActivity(newsScreen); } }); news.execute(); } }
MainActivity.java
private void showErrorView() { setContentView(R.layout.error_layout); TextView errorView = (TextView) findViewById(R.id.errorMessage); errorView.setText("App cannot connect to network. Check network settings and try again."); } }
NewsActivityNewsActivity will now extract xmlData downloaded and passed from MainActivity, parse it and store it in ArrayList newsData.
This list provides data to LazyNewsAdapter.
ntitle_1ndate_1 nimage_1 nurl_1 ndescription_1
ntitle_2ndate_2 nimage_2 nurl_2 ndescription_2
ntitle_nndate_n nimage_n nurl_n ndescription_n
…
NewsActivity.java
Required layouts are news_layout.xml (simple ListView) and news_cell.xml (image and textviews for headline and date.
We also need the News.java class for our ArrayList newsData.
NewsActivity.java
Make sure that Network permissions are set in Manifest.xml:
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
Register NewsActivity in Manifest and run. LazyNews.1
DetailNewsActivity.java
To go into a detailed news display we need DetailNewsActivity.java and detail_news_layout.xml
Transition is created in onItemClickListener in NewsActvity.
DetailNewsActivity must be registered in Manifest.
detail_news_layout.xmlScrollView
(top container)
RelativeView
ImageView
TextView
TextViewTextView
Lazy News
ViewPager
ViewPager
ViewPager is a layout manager that allows the user to flip left and right through pages of data.
Developer needs to supply an implementation of a PagerAdapter to generate the pages that the view shows.
Create new project ImageGallery.
activity_main.xmlInside RelativeLayout we have ViewPager with PagerTabStrip (indicator) underneath.
<android.support.v4.view.ViewPager android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/ViewPager">
<android.support.v4.view.PagerTabStrip android:id="@+id/pager_tab" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" android:background="#33b5e5" android:textColor="#fff"/> </android.support.v4.view.ViewPager>
page.xml
Page itself is described in page.xml - a single ImageView inside LinearLayout:
<ImageView android:id="@+id/img_view" android:layout_width="match_parent" android:layout_height="match_parent"/>
CustomPagerAdapterCustomPagerAdapter extends PagerAdapter. We must override the following methods at minimum:
1. instantiateItem(ViewGroup, int)Create the page for the given position. In our example it sets ImageView’s resource to the image at specified position and adds newly created view to our ViewPager (container)
CustomPagerAdapter
2. destroyItem(ViewGroup, int, Object)Remove a page for the given position.
3. getCount()Return the number of views available.
CustomPagerAdapter
4. isViewFromObject(View, Object)Determines whether a page View is associated with a specific key object as returned by instantiateItem(ViewGroup, int).This method is required for a PagerAdapter to function properly.
CustomPagerAdapter
In order to define titles for tabs we need method:
CharSequence getPageTitle(int)Which returns a character sequence for the tab at specified position.
MainActivityIn MainActivity, we simply instantiate ViewPager and assign PagerAdapter:
ViewPager viewPager = findViewById(R.id.ViewPager); CustomPagerAdapter adapter = new CustomPagerAdapter(this); viewPager.setAdapter(adapter);
Required images in drawable-xxhdpi: slide0.png, …, slide5.png
ImageGallery
TimetableAnother application of ViewPager
Instead of ImageView we’ll have ListView for each day (Mon - Fri)
activity_main.xmlInside RelativeLayout we have ViewPager with PagerTabStrip (indicator) on top.
<android.support.v4.view.ViewPager android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/ViewPager">
<android.support.v4.view.PagerTabStrip android:id="@+id/pager_tab" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity=“top" android:background="#3f51b5" android:textColor="#fff"/> </android.support.v4.view.ViewPager>
page.xmlPage itself is described in page.xml - a ListView inside LinearLayout:
<ListView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/timetable" android:layout_gravity="center" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:clickable="true" android:divider="@null" android:dividerHeight=“0dp"/>
rowlist.xmlIn order to describe cells in ListView we need rowlist.xml - two TextViews describing time and course:
<TextView android:id="@+id/time" android:layout_width="56dp" android:layout_height="wrap_content" android:text="noon" android:gravity="right" android:textColor=“#aaa" android:padding="8dp" android:textSize="16sp"/>
rowlist.xml
<TextView android:id="@+id/description" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_toRightOf="@id/time" android:padding="8dp" android:text="CP2530 Lecture ET328" android:textColor="#1870a2" android:textSize="18sp" android:layout_marginTop="0dp"/>
Adapters
We need two adapters:
1. TimeTableAdapter which extends SimpleAdapter and, as before, inflates Map<time,description> into rowlist.Required by the second adapter.
2. CustomPagerAdapter which extends PagerAdapter and creates ListView with data based on the position.
Adapters
MainActivity
In MainActivity, five HashMaps are created and populated to serve as data for five ListViews.
As before, ViewPager and CustomPagerAdapter are instantiated and associated.
Timetable
XML and Timetable
Rather than populating ListViews for each day using hard-coded data, we’ll fetch XML file, parse it and provide model for our ViewPager.
Project: TimeTable.2.zip
The structure of XML file modelling timetable of classes is as follows:
XML and Timetable<timetable> <program>CS</program> <semester>5</semester> <gid>M6</gid> <mon> <time>9</time> <cid></cid> <time>10</time> <cid></cid> <time>11</time> <cid></cid> <time>12</time> <cid>CP2530 Lecture ET328</cid> <time>13</time> <cid>CT2530 Lecture ET328</cid> ... </mon> ... <fri> ... </fri> </timetable>
XML and Timetable
The only change is in MainActivity - in onCreate method we check if we have network.
If so, fetch and parse file through GetXML object. Otherwise, alert user that there is no network connection.
XML and Timetable
File is asynchronously downloaded in doInBackground() method and stored in String class variable src.
Parsing is done in onPostExecute() method, if the XML source is not null (i.e., schedule exists).
XML and Timetable
Parsing populates ArrayList schedule, from which individual schedules (per day) are reconstructed and stored in days[][] array.
This array (days) is now used to create HashMaps for ListView adapters.
SpinnerFor some reason Google calls it Spinner, although nothing spins, unlike iOS’ UIPickerView (spinning wheel).
Anyway, Spinner provides a quick way to select one value from a set. It’s typically defined in XML:
<Spinner android:id="@+id/spinner_p" android:layout_width="match_parent" android:layout_height="wrap_content"/>
Spinner
Spinner
Spinner
Required files are:
1. listview_border.xml (spinners’ background in drawable-xxhdpi)
2. spinnerrow.xml which defines a layout for each entry in the Spinner
SpinnerWe need two spinners in main_activity.xml: one for programs and one for years.
There is also a button, which generates proper url for xml timetables when clicked.
Spinners are instantiated in MainActivity:
Spinner spinner_p = (Spinner) findViewById(R.id.spinner_p);
SpinnerIn order to populate spinner we need data - two arrays of strings - programs and years.
Adapters are created and set:
ArrayAdapter<String> arrayAdapter = new ArrayAdapter<String>( MainActivity.this, R.layout.spinnerrow, R.id.spinner_id, programs); spinner_p.setAdapter(arrayAdapter);
SpinnerWe set onItemSelectedListener for both spinners.
Program spinner listener sets the value of variable cid.
Year spinner listener sets the value of variable semester based on the selected year and current month.
Spinner
Button, when clicked, generates url where appropriate timetable is hosted (not all timetables are implemented).
There is an issue, not related to Spinner but to Button - its transparency when clicked.
Spinner
For some reason, change of transparency in Button / ImageButton when touched is not default behaviour.
That can be overridden in onTouchListener by changing the button’s alpha value.
Spinnerb.setOnTouchListener(new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if(v == b) { if(event.getAction() == MotionEvent.ACTION_DOWN) { v.setAlpha(.3f); } else { v.setAlpha(1f); } } return false; } });