Mobile App Dev 2021W: Tutorial 8
This tutorial is still being developed.
Tasks
- Add another picture to the assets of PicViewer2A. How can you make sure this pic is shown by default?
- How do you change the code so your added pic will appear eventually when clicking on the current image?
- How many widgets do you have to move in order to get the input fields and their labels moved to the left side of the screen or centered? Why?
- What values do the fields take on when you delete all the text? Is it the same for every field?
- There is a lot of duplicated code in MainActivity.kt. What is the primary source of duplicated code?
- How can the PicWatcher class be used to greatly reduce the amount of duplicated code?
- How can you change the fields so they all take on a value of zero when all text is deleted?
Code
MainActivity.kt
package carleton.comp1601.picviewer2a
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.ImageView
val appName = "PicViewer2A"
class MainActivity : AppCompatActivity() {
private lateinit var p: ImageView
private var pic = R.drawable.kittens
private var r = 0F
private var scale = 1F
private var pX = 0F
private lateinit var rotationInput: EditText
private lateinit var scaleInput: EditText
private lateinit var pXInput: EditText
private lateinit var pYInput: EditText
val Y = PicWatcher("Y", 0F, ::updateImage)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState != null) {
// do something
}
p = findViewById(R.id.mypic)
rotationInput = findViewById(R.id.rotation)
rotationInput.setText(String.format("%.0f", r))
rotationInput.addTextChangedListener(rotationWatcher)
scaleInput = findViewById(R.id.scale)
scaleInput.addTextChangedListener(scaleWatcher)
pXInput = findViewById(R.id.x)
pXInput.addTextChangedListener(pXWatcher)
pYInput = findViewById(R.id.y)
pYInput.addTextChangedListener(Y)
updateImage()
Log.d(appName, "Activity Created")
}
fun updateImage() {
p.setImageResource(pic)
p.setX(pX)
p.setY(Y.value)
p.setRotation(r)
p.setScaleX(scale)
p.setScaleY(scale)
}
fun toggleImage(v: View) {
if (pic == R.drawable.roshi) {
pic = R.drawable.kittens
} else {
pic = R.drawable.roshi
}
updateImage()
}
private val rotationWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val new_rS = rotationInput.getText().toString()
val new_r = new_rS.toFloatOrNull()
if (new_r != null) {
r = new_r
Log.d(appName, "New rotation is ${new_r}")
} else {
r = 0F
Log.d(appName, "Rotation is ${r}")
}
updateImage()
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
private val scaleWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val new_scaleS = scaleInput.getText().toString()
val new_scale = new_scaleS.toFloatOrNull()
if (new_scale != null) {
if (new_scale > 10F) {
scale = 10F
} else if (new_scale < 0F) {
scale = 0F
} else {
scale = new_scale
}
Log.d(appName, "Set scale to ${new_scale}")
updateImage()
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
private val pXWatcher = object : TextWatcher {
override fun afterTextChanged(s: Editable) {
val new_xS = pXInput.getText().toString()
val new_x = new_xS.toFloatOrNull()
if (new_x != null) {
pX = new_x
Log.d(appName, "Set X to ${new_x}")
updateImage()
}
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
}
}
class PicWatcher: TextWatcher {
var value: Float
var name: String
var update: () -> Unit
override fun afterTextChanged(s: Editable) {
val new_value = s.toString().toFloatOrNull()
if (new_value != null) {
value = new_value
Log.d(appName, "Set ${name} to ${value} (wow).")
update()
}
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
constructor(n: String, v: Float, u: () -> Unit) {
value = v
name = n
update = u
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="@+id/mypic"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="0dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:layout_marginBottom="20dp"
android:onClick="toggleImage"
app:layout_constraintBottom_toTopOf="@+id/rotation"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/kittens" />
<EditText
android:id="@+id/rotation"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="5"
android:inputType="number"
app:layout_constraintBottom_toTopOf="@+id/scale"
app:layout_constraintStart_toStartOf="@+id/scale" />
<EditText
android:id="@+id/scale"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="5"
android:inputType="numberDecimal"
app:layout_constraintBottom_toTopOf="@+id/x"
app:layout_constraintStart_toStartOf="@+id/x" />
<EditText
android:id="@+id/x"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ems="5"
android:inputType="number"
app:layout_constraintBottom_toTopOf="@+id/y"
app:layout_constraintStart_toStartOf="@+id/y" />
<EditText
android:id="@+id/y"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:ems="5"
android:inputType="number"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/ylabel" />
<TextView
android:id="@+id/rotationlabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:text="rotation"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@+id/rotation"
app:layout_constraintEnd_toStartOf="@+id/rotation"
/>
<TextView
android:id="@+id/scalelabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:layout_marginBottom="11dp"
android:text="scale"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@+id/scale"
app:layout_constraintEnd_toStartOf="@+id/scale" />
<TextView
android:id="@+id/xlabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:text="X"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@+id/x"
app:layout_constraintEnd_toStartOf="@+id/x" />
<TextView
android:id="@+id/ylabel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp"
android:text="Y"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@+id/y"
app:layout_constraintEnd_toStartOf="@+id/y" />
</androidx.constraintlayout.widget.ConstraintLayout>