2017 - 3/ Nov
Στο 5ο μάθημα θα προσθέσουμε μενού επιλογών έτσι ώστε να μπορεί ο χρήστης να αλλάξει πόλη πρόβλεψης.
Ας δούμε ένα παράδειγμα οθόνης ρυθμίσεων
Οι ρυθμίσεις μπορούν να χωριστούν σε κατηγορίες με την προσθήκη Headers. Επίσης κάθε ρύθμιση εκτός του ονοματός της, έχει απο κάτω και το preference summary το οποίο είναι η τιμή που έχει δώθεί στη συγκεκριμένη ρύθμιση. Οι πιο γνωστοί τύποι ρυθμίσεων είναι:
- CheckBoxPreference
- ListPreference
- EditTextPreference
Οι τιμές των ρυθμίσεων αποθηκεύονται σε ένα αρχείο SharedPreferences
με τη μορφή key-value
Συνεχίζουμε προσθέτοντας μια νέα κλάση με όνομα SettingsActivity
. Τον κώδικα για την κλάση μπορείτε να τον πάρετε απο εδώ:
https://gist.github.com/udacityandroid/41aca2eb9ff6942e769b
παρατηρήστε πως άλλαξε το Addroid Manifest αρχείο για να προστεθεί το νέο Settings Activity.
Η επιλογή για εμφάνιση του SettingsActivity
πρέπει να υπάρχει στο μενού τόσο του MainActivity
όσο και του DetailActivity
για να γίνει αυτό, τροποποιούμε (προσθέτουμε αν δεν υπάρχει) τη μέθοδο onOptionsItemSelected
στις κλάσεις MainActivity
και DetailActivity
ως εξής:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
Intent intent = new Intent(this, SettingsActivity.class);
startActivity(intent);
return true;
}
return super.onOptionsItemSelected(item);
}
Αυτή τη στιγμή η εφαρμογή έχει οθόνη ρυθμίσεων αλλά είναι κενή! Πρέπει να προσθέσουμε ρυθμίσεις για τον χρήστη!
- Δημιουργούμε ένα νέο φάκελο
xml
μέσα στοres
. (/res/xml). Δεξί κλικ στον res -> new -> Android Resource Directory - Δημιουργούμε ένα νέο αρχείο
pref_general.xml
μέσα στο folderxml
. Δεξί κλικ στον xml -> new -> XML Resource File. (Root Element =PreferenceScreen
) - Προσθέτουμε ένα Location preference, στο οποίο ο χρήστης θα εισάγει τον κωδικό πόλης, της οποίας τον καιρό θέλει να βλέπει:
<EditTextPreference
android:title="@string/pref_location_label"
android:key="@string/pref_location_key"
android:defaultValue="@string/pref_location_default"
android:inputType="text"
android:singleLine="true">
</EditTextPreference>
Το πεδίο android:title
είναι το κείμενο που θα φαίνεται στον χρήστη, π.χ. το Display Name ή το Ringtone στην παραπάνω εικόνα.
Το πεδίο android:key
είναι ουσιαστικά το κλειδί του map στο οποίο αποθηκεύονται οι ρυθμίσεις στο SharedPreferences file. Για παράδειγμα φανταστείτε αυτό το Map κάπως έτσι
key=value
location=734077
surname=Chaikalis
name=Theodore
Τα resources κειμένου για το το res/values/strings.xml
είναι :
<string name="pref_location_label">Location</string>
<string name="pref_location_key" translatable="false">location</string>
<string name="pref_location_default" translatable="false">734077</string>
Για να εμφανιστεί η νέα μας ρύθμιση θα πρέπει να προσθέσουμε κώδικα στην κλάση SettingsActivity
.
Μέθοδος onCreate(Bundle savedInstanceState)
:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.pref_general);
bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_location_key)));
}
Αφού έχουμε προσθέσει την κατάλληλη ρύθμιση, είμαστε πλέον σε θέση να διαβάζουμε την τιμή της ρύθμισης αυτής έτσι ώστε να καλούμε το Web Service του OpenWeatherMap API με τον επιθυμητό κωδικό πόλης και έτσι να βλέπουμε την επιθυμητή πρόβλεψη καιρού. Για να γίνει αυτό θα πρέπει να διορθώσουμε τον τρόπο κλήσης του Web Service. Θυμίζουμε ότι η υλοποίηση αυτή βρίσκεται στην κλάση ForecastFragment. Πρέπει να:
- Δημιουργήσουμε μια νέα μέθοδο με όνομα
updateWeather
- Να διορθώσουμε τη μέθοδο
onOptionsItemSelected(MenuItem item)
private void updateWeather() {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
String location = prefs.getString(getString(R.string.pref_location_key), getString(R.string.pref_location_default));
FetchWeatherTask weatherTask = new FetchWeatherTask();
weatherTask.execute(location);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
if(id == R.id.action_refresh){
updateWeather();
return true;
}
return super.onOptionsItemSelected(item);
}
Ας εκμεταλευτούμε την ευκαιρία της δημιουργίας της μεθόδου updateWeather για να προσθέσουμε την κλήση της κατά τη δημιουργία του Activity, έτσι ώστε να ξεκινάει η εφαρμογή με τα κανονικά και όχι με τα dummy δεδομένα. Στην ίδια κλάση (ForecastFragment) κάνουμε override τη μέθοδο onStart()
@Override
public void onStart() {
super.onStart();
updateWeather();
}
θα πρέπει επίσης να διορθώσουμε την δημιουργία του ArrayAdapter έτσι ώστε να μην αρχικοποιείται με Dummy Data, αλλά με ένα κενό ArrayList.
Προσθήκη μιας ακόμα επιλογής ρυθμίσεων για μετατροπή θερμοκρασίας απο Κελσίου (Weather API default), σε Fahrenheit.
Αρχικά προσθέτω τα κατάλληλα Strings:
<string name="pref_temp_units_label">Temperature Units</string>
<string name="pref_temp_units_key" translatable="false">tempunit</string>
<string name="pref_temp_units_celcius" translatable="false">C</string>
<string name="pref_temp_units_fahren" translatable="false">F</string>
<string-array name="pref_temp_units_entries">
<item>Celcius (Metric)</item>
<item>Fahrenheit (Imperial)</item>
</string-array>
<string-array name="pref_temp_units_entryValues">
<item>C</item>
<item>F</item>
</string-array>
Έπειτα προσθέτω μία ακόμα επιλογή τύπου List Preference
στo αρχείο pref_general.xml
<ListPreference
android:key="@string/pref_temp_units_key"
android:defaultValue="@string/pref_temp_units_celcius"
android:title="@string/pref_temp_units_label"
android:entries="@array/pref_temp_units_entries"
android:entryValues="@array/pref_temp_units_entryValues"
>
</ListPreference>
Τώρα θα πρέπει να κάνω τις κατάλληλες αλλαγές στη μέθοδο ForecastFragment.getWeatherDataFromJson() έτσι ώστε να "παίρνει" τη μονάδα μετατροπής θερμοκρασίας απο τις ρυθμίσεις:
//...
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());
String tempUnit = prefs.getString(getString(R.string.pref_temp_units_key),getString(R.string.pref_temp_units_celcius));
//....
//....
double high = temperatureObject.getDouble(OWM_MAX);
double low = temperatureObject.getDouble(OWM_MIN);
highAndLow = formatHighLows(high, low, tempUnit);
//....
ΕΤΟΙΜΟ!
Θα προσθέσουμε λειτουργία sharing στην εφαρμογή μας εκμεταλευόμενοι τα Implicit Intents
- Προσθέτουμε ένα νέο αρχείο
detailfragment
στον φάκελο /res/menu καθώς και τα κατάλληλα string resources. Το menu item αυτό θα εμφανίζεται σαν action στο επάνω taskbar και θα είναι ορατό συνεχώς.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" >
<item
android:id="@+id/action_share"
android:title="@string/action_share"
app:showAsAction="always"
app:actionProviderClass="android.support.v7.widget.ShareActionProvider" />
</menu>
-
Στη συνέχεια θα πρέπει να προσθέσουμε κώδικα στην inner κλάση
DetailFragment
(ίσως την έχετε ωςPlaceholderFragment
) της κλάσηςDetailActivity
. Αναλυτικά -
Προσθέτουμε απαραίτητες ιδιότητες που θα μας χρειαστούν
private static final String LOG_TAG = DetailFragment.class.getSimpleName();
private static final String FORECAST_SHARE_HASHTAG = " #SunshineApp";
private String forecastString;
- Δηλώνουμε ότι το fragment αυτό έχει δικό του μενού.
public DetailFragment() {
setHasOptionsMenu(true);
}
- Αλλάζουμε τον κώδικα δημιουργίας της μεταβλητής forecastString, καθώς τώρα είναι ιδιότητα ορατή σε όλη την κλάση και όχι τοπική μεταβλητή. Αυτό το κάνουμε γιατί θα χρειαστούμε την τιμή του String και σε άλλη μέθοδο. Είναι το String της πρόβλεψης που θα περάσουμε σαν μήνυμα στον ShareProvider. Είναι αυτό το κείμενο που τελικά θα γίνει share.
if(intent != null && intent.hasExtra(Intent.EXTRA_TEXT)){
forecastString = intent.getStringExtra(Intent.EXTRA_TEXT);
TextView detailText = (TextView)rootView.findViewById(R.id.detail_text);
detailText.setText(forecastString);
}
- Δημιουργούμε τη βοηθητική μέθοδο
createShareForacastIntent
, η οποία αναλαμβάνει να δημιουργήσει το Implicit Intent και να το γεμίσει με τα απαραίτητα δεδομένα.
private Intent createShareForacastIntent(){
Intent shareIntent = new Intent(Intent.ACTION_SEND);
//clear activity and not put it on the activity stack.
//If this flag is not present, then after sharing the screen will return to the sharing app
//and not in our app!!!
//FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET is DEPRECATED, FLAG_ACTIVITY_NEW_DOCUMENT should be used instead
// shareIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
shareIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_TEXT, forecastString + FORECAST_SHARE_HASHTAG);
Log.e(LOG_TAG, "share intent created");
Log.e(LOG_TAG, "string extra = "+shareIntent.getStringExtra(Intent.EXTRA_TEXT));
return shareIntent;
}
- Επικαλύπτουμε (Override) τη μέθοδο
onCreateOptionsMenu
έτσι ώστε να δηλώσουμε τι θέλουμε να γίνεται στη δημιουργία του menu επιλογών. Αυτό που δηλώνουμε είναι οτι θέλουμε στο πάτημα του SHARE εικονιδίου να ξεκινάει ο Share Action Provider του λειτουργικού στο οποίο έχουμε περάσει το Intent (.setShareIntent(createShareForacastIntent());
) με τις χρήσιμες πληροφορίες που θέλουμε να γίνουν Share.
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
//Inflate the menu, this adds items to the action bar if it is not present
inflater.inflate(R.menu.detailfragment, menu);
MenuItem menuItem = menu.findItem(R.id.action_share);
ShareActionProvider shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(menuItem);
if(shareActionProvider != null){
shareActionProvider.setShareIntent(createShareForacastIntent());
Log.e(LOG_TAG, "intent set to share action provider ");
}
else{
Log.e(LOG_TAG, "Share action provider is null?");
}
super.onCreateOptionsMenu(menu, inflater);
}