glaukon
Chapter 3: Java - Part 24
Okay, it's been a while since the last time we stopped to compared code with each other. Here is what I have for MainActivity. You'll notice copious comments, as well as some very minor reordering and restructuring here and there to make the code more readable.

package glaukon.tutorials.tipcalc;


// Import statements let us use code that's not in our project's package.

import java.util.ArrayList;

import android.app.Activity;

import android.os.Bundle;

import android.view.Menu;

import android.view.View;

import android.view.View.OnClickListener;

import android.view.View.OnFocusChangeListener;

import android.widget.EditText;

import android.widget.ImageButton;

import android.widget.SeekBar;

import android.widget.SeekBar.OnSeekBarChangeListener;

import android.widget.TableLayout;

import android.widget.TableRow;

import android.widget.TextView;


// MainActivity inherits from the Activity class, but also from the OnClickListener, OnFocusChangeListener, and OnSeekBarChangeListener interfaces.

public class MainActivity extends Activity implements OnClickListener, OnFocusChangeListener, OnSeekBarChangeListener {


// Variable declarations go here.

// totalTip is calculated as tipPercentValue * grandTotal.

private double totalTip;

private double tipPercentValue;

// tax is an absolute number, and taxPercentValue is calculated as tax / grandTotal.

private double tax;

private double taxPercentValue;

// grandTotal does not include tip or tax, but is simply the sum of all the Diners' totals.

private double grandTotal;

// Next, we have the UI elements we made in the layout XML file.

private TableLayout mainTable;

private ImageButton addDinerButton;

// firstCustomer, amount1of1, addButton1, textSplit1, and textSplit1Dollar are all attributes of the first Diner.

private EditText firstCustomer;

private EditText amount1of1;

private ImageButton addButton1;

private TextView textSplit1;

private TextView textSplit1Dollar;

private EditText tipPercent;

private TextView tipDollar;

private SeekBar tipSlider;

private EditText taxDollar;

private TextView textGTotal;

// dinerList will be used to organize all Diners in one outer-object.

private ArrayList<Diner> dinerList;


// Replace the onCreate method we inherited from Activity with our own code using @Override.

@Override

protected void onCreate(Bundle savedInstanceState) {

// First, run Activity's version of onCreate.

super.onCreate(savedInstanceState);

// Inflate the UI using activity_mail.xml in the layout folder.

setContentView(R.layout.activity_main);

// Set all doubles to 0, except tipPercent, which defaults to 15%.

// Note that we use 0.0 instead of 0. Doesn't really matter, just reminds us they're doubles, not ints.

totalTip = 0.0;

tipPercentValue = 0.15;

tax = 0.0;

taxPercentValue = 0.0;

grandTotal = 0.0;

// Retrieve all the Views we need from our UI using findViewById.

// Then assign them to variables we declared earlier.

mainTable = (TableLayout) findViewById(R.id.mainTable);

addDinerButton = (ImageButton) findViewById(R.id.addDinerButton);

firstCustomer = (EditText) findViewById(R.id.firstCustomer);

amount1of1 = (EditText) findViewById(R.id.amount1of1);

addButton1 = (ImageButton) findViewById(R.id.addButton1);

textSplit1 = (TextView) findViewById(R.id.textSplit1);

textSplit1Dollar = (TextView) findViewById(R.id.textSplit1Dollar);

tipDollar = (TextView) findViewById(R.id.tipDollar);

tipPercent = (EditText) findViewById(R.id.tipPercent);

tipSlider = (SeekBar) findViewById(R.id.tipSlider);

taxDollar = (EditText) findViewById(R.id.taxDollar);

textGTotal = (TextView) findViewById(R.id.textGTotal);

// Make a new ArrayList called dinerList, then make the first Diner using the Views from our UI.

// Finally, add that Diner to dinerList.

dinerList = new ArrayList<Diner>();

Diner diner = new Diner(firstCustomer, amount1of1, addButton1, textSplit1, textSplit1Dollar);

dinerList.add(diner);

// Set up listeners.

firstCustomer.setOnFocusChangeListener(this);

amount1of1.setOnFocusChangeListener(this);

tipPercent.setOnFocusChangeListener(this);

taxDollar.setOnFocusChangeListener(this);

addDinerButton.setOnClickListener(this);

addButton1.setOnClickListener(this);

tipSlider.setOnSeekBarChangeListener(this);

}


// Replace the onCreateOptiosnMenu method we inherited from Activity with our own code using @Override.

@Override

public boolean onCreateOptionsMenu(Menu menu) {

// Inflate the options menu using activity_main.xml in the menu folder.

getMenuInflater().inflate(R.menu.activity_main, menu);

return true;

}


// Implement the onClick method of OnClickListener.

@Override

public void onClick(View v) {

// If it's addDinerButton that was clicked, do this.

if (v == addDinerButton) {

// Create a 2 TableRows, 2 EditTexts, 1 ImageButton, and 2 TextViews.

// Set their attributes based on inflated Views from the UI.

TableRow row1 = new TableRow(this);

EditText et1 = new EditText(this);

et1.setText("Customer");

et1.setSelectAllOnFocus(true);

et1.setInputType(firstCustomer.getInputType());

et1.setGravity(firstCustomer.getGravity());

et1.setLayoutParams(firstCustomer.getLayoutParams());

et1.setWidth(firstCustomer.getWidth());

et1.setOnFocusChangeListener(this);

EditText et2 = new EditText(this);

et2.setText("$0.00");

et2.setSelectAllOnFocus(true);

et2.setInputType(amount1of1.getInputType());

et2.setGravity(amount1of1.getGravity());

et2.setLayoutParams(amount1of1.getLayoutParams());

et2.setWidth(amount1of1.getWidth());

et2.setOnFocusChangeListener(this);

// Make sure the order EditText gets selected automatically.

et2.requestFocus();

ImageButton ib = new ImageButton(this);

ib.setImageResource(R.drawable.additem);

// Remember to set the listener.

ib.setOnClickListener(this);

// Add Views to rows, then add rows to mainTable.

// Use a for loop to get the right row numbers to insert rows at.

// Make sure to account for every order of every Diner.

row1.addView(et1);

row1.addView(et2);

row1.addView(ib);

int rowIndex = 1;

for (int i = 0; i < dinerList.size(); i++) {

rowIndex += dinerList.get(i).orderList.size();

}

mainTable.addView(row1, rowIndex);

TableRow row2 = new TableRow(this);

TextView tv1 = new TextView(this);

tv1.setText(et1.getText().toString());

tv1.setGravity(textSplit1.getGravity());

TextView tv2 = new TextView(this);

tv2.setText(et2.getText().toString());

tv2.setGravity(textSplit1Dollar.getGravity());

row2.addView(tv1);

row2.addView(tv2);

// Add 11 static rows to rowIndex, as well as one row for each Diner in the bill splitting portion of app.

mainTable.addView(row2, rowIndex + 11 + dinerList.size());

// Make a new Diner and add it to dinerList.

Diner diner = new Diner(et1, et2, ib, tv1, tv2);

dinerList.add(diner);

// If it's not addDinerButton, do this.

} else {

// Use a for loop to run though all of dinerList.

// If any one of their ibAddOrder ImageButtons were clicked, do this.

for (int i = 0; i < dinerList.size(); i++) {

if (v == dinerList.get(i).ibAddOrder) {

// Create a row, then 2 EditTexts.

// The first is a dummy one used to push the second one into the second column.

// Again, grab attributes from Views inflated by XML.

TableRow row3 = new TableRow(this);

EditText emptyEditText = new EditText(this);

emptyEditText.setVisibility(4);

EditText newOrder = new EditText(this);

newOrder.setText("$0.00");

newOrder.setSelectAllOnFocus(true);

newOrder.setInputType(amount1of1.getInputType());

newOrder.setGravity(amount1of1.getGravity());

newOrder.setLayoutParams(amount1of1.getLayoutParams());

newOrder.setWidth(amount1of1.getWidth());

newOrder.setOnFocusChangeListener(this);

newOrder.requestFocus();

row3.addView(emptyEditText);

row3.addView(newOrder);

// For loop again to get the right row number to insert the row at.

int rowIndex2 = 1;

for (int j = 0; j <= i; j++) {

rowIndex2 += dinerList.get(j).orderList.size();

}

mainTable.addView(row3, rowIndex2);

// Add the order to the correct Diner's orderList.

dinerList.get(i).newOrder(newOrder);

}

}

}

}


// Implement the onFocusChange method of OnFocusChangeListener.

@Override

public void onFocusChange(View v, boolean hasFocus) {

// If tipPercent lost focus, do this.

if (v == tipPercent && hasFocus == false) {

// Get the tip from tipPercent, making sure to adjust it so that Double.parseDouble can handle it.

tipPercentValue = Double.parseDouble(((EditText) v).getText().toString().replace("%", ""));

// If the user set the tip higher than 50, bring it back down to 50.

if (tipPercentValue > 50) {

tipPercentValue = 50;

}

// Use setProgress to trigger the code in onProgressChanged.

// Note we have to cast to int because tipPercentValue is a double.

tipSlider.setProgress((int) tipPercentValue);

// If the user didn't change the tip, then setProgress won't actually move tipSlider.

// Thus, onProgressChange won't get triggered, and tipPercent won't get divided by 100.

// To make sure tipPercent is a percentage, and not a whole number, we check if it's greater than 0.5.

// If it is, we need to change it back to a percentage here, since onProgressChanged was never triggered.

if (tipPercentValue > .5) {

tipPercentValue = tipPercentValue / 100;

}

// We don't need to do anything else in this block, since onProgressChanged should get triggered.

// The rest of the stuff that needs to happen will happen in onProgressChanged.

// And if it was not triggered, then that means nothing changed and nothing needs to happen anyway.

// If tipPercent didn't trigger onFocusChange, check if taxDollar lost focus. If so, do this.

} else if (v == taxDollar && hasFocus == false) {

// Set tax to what's inside taxDollar. Then set taxDollar to tax.

// We need to set taxDollar to back to tax for the sake of formatting.

tax = Double.parseDouble(((EditText) v).getText().toString().replace("$", "").replace(",", ""));

((EditText) v).setText("$" + String.format("%,.2f", tax));

// Then we run calcGrandTotal to update everything.

calcGrandTotal();

// Finally, if it wasn't tipPercent or taxDollar, do this stuff.

} else {

// Use a for loop to run though all of dinerList.

for (int i = 0; i < dinerList.size(); i++) {

// If any one of their etName EditTexts lost focus, do this.

if (v == dinerList.get(i).etName && hasFocus == false) {

// Set the tvName TextView using the Diner's setName method.

dinerList.get(i).setName();

} else {

// Otherwise, run through the orderList for each Diner.

for (int j = 0; j < dinerList.get(i).orderList.size(); j++) {

// If any EditTexts in the orderList lost focus, do this.

if (v == dinerList.get(i).orderList.get(j) && hasFocus == false) {

// Run the updateTotal method to calculate the Diner's new total.

// Then use calcGrandTotal to update everthing that needs updating.

dinerList.get(i).updateTotal((EditText) v);

calcGrandTotal();

}

}

}

}

}

}


// Implement the onProgressChanged method of OnSeekBarChangeListener.

@Override

public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {

// Check to make sure the correct seekBar was changed (technically not necessary since we only have one).

if (seekBar == tipSlider) {

// Set tipPercentValue to the progress of tipSlider, then set the tipPercent EditText accordingly.

// Make sure tipPercentValue goes back to being a percent value, not a whole number, at the end.

tipPercentValue = progress;

tipPercent.setText(String.format("%.0f", tipPercentValue) + "%");

tipPercentValue = tipPercentValue / 100;

// Finally, update everything else with calcGrandTotal.

calcGrandTotal();

}

}


@Override

public void onStartTrackingTouch(SeekBar seekBar) {

// Not needed.

}


@Override

public void onStopTrackingTouch(SeekBar seekBar) {

// Not needed.

}


// This is the final method that gets run every time something changes.

// It does all the major updating that the user sees.

public void calcGrandTotal() {

// Before calculating grandTotal, remember to reset it back to 0 first.

grandTotal = 0.0;

// Go through dinerList and add each Diner's total to grandTotal.

for (int i = 0; i < dinerList.size(); i++) {

grandTotal += dinerList.get(i).total;

}

// Calculate totalTip, then set tipDollar accordingly.

totalTip = grandTotal * tipPercentValue;

tipDollar.setText("$" + String.format("%,.2f", totalTip));

// Then calculate taxPercentValue.

taxPercentValue = tax / grandTotal;

// Use tipPercentValue and taxPercentValue to call every Diner's setTotalText method.

for (int i = 0; i < dinerList.size(); i++) {

dinerList.get(i).setTotalText(tipPercentValue, taxPercentValue);

}

// To finish, set textGTotal to grandTotal + totalTip + tax.

textGTotal.setText("$" + String.format("%,.2f", grandTotal + totalTip + tax));

}

}

And here's my Diner class:

package glaukon.tutorials.tipcalc;


// Import statements let us use code that's not in our project's package.

import java.util.ArrayList;

import android.widget.EditText;

import android.widget.ImageButton;

import android.widget.TextView;


public class Diner {

// Declare the variables we'll need.

// total does not include tip or tax.

public double total;

public ArrayList<EditText> orderList;

public EditText etName;

public EditText etFirstOrder;

public ImageButton ibAddOrder;

public TextView tvName;

public TextView tvSplitBill;


// This is the constructor. It runs when you instantiate a Diner.

// It brings together all the Views that are associated with a new Diner object.

public Diner(EditText et1, EditText et2, ImageButton ib, TextView tv1, TextView tv2) {

// total starts off as 0, and is used to keep track of the sum of a Diner's orders.

total = 0.0;

// We also need an ArrayList to keep track of each Diner's orders.

orderList = new ArrayList<EditText>();

etName = et1;

etFirstOrder = et2;

ibAddOrder = ib;

tvName = tv1;

tvSplitBill = tv2;

// Make sure to add the first order to orderList.

orderList.add(etFirstOrder);

}


// This method sets the Text property of tvName to whatever the user changed the Text property of etName to.

public void setName() {

// We need to use toString because getText does not return a string.

tvName.setText(etName.getText().toString());

}


// This method adds a new order to a Diner's orderList.

public void newOrder(EditText newOrder) {

orderList.add(newOrder);

}


// This method updates the Diner's total based its orderList.

// It is run when the user changes focus from an EditText in orderList.

public void updateTotal(EditText toBeUpdated) {

// First, reset total

total = 0.0;

// Then, format the EditText that was just changed

toBeUpdated.setText("$" + String.format("%,.2f", editTextToDouble(toBeUpdated)));

// Finally, go through each order and add it to total. Use editTextToDouble to help.

for (int i = 0; i < orderList.size(); i++) {

total += editTextToDouble(orderList.get(i));

}

}


// We used to have an overloaded updateTotal method here, but we no longer need it.

// public void updateTotal() {

// updateTotal(etFirstOrder);

// }


// This method converts what's inside an EditText to a double, and returns it.

public double editTextToDouble(EditText et) {

double db = 0.0;

db = Double.parseDouble(et.getText().toString().replace("$", "").replace(",", ""));

return db;

}


// This method uses the tip and tax MainActivity gets it to set the proper value for tvSplitBill.

public void setTotalText(double tip, double tax) {

tvSplitBill.setText("$" + String.format("%,.2f", total * (1 + tip) + total * tax));

}

}