Open8
Kotlin+Android業務アプリで何度も使いまわしている記法まとめ
Android開発でよく使いまわしているコードの備忘録
毎回忘れるよね
※ほぼ自分用なので、ソースコードを殴り書いているだけです
ツールメニューバー
ツールバーがあるレイアウト
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">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:title="@string/app_title"
app:titleTextColor="@android:color/primary_text_dark" />
</androidx.constraintlayout.widget.ConstraintLayout>
ツールバー自体のレイアウト
drawableにVector Assetsでアイコンをダウンロードしておく。
res/menu/menu.xml
<?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"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.gologius.infoclip.MainActivity">
<item
android:id="@+id/action_settings"
android:title="settings"
android:icon="@drawable/ic_baseline_settings_24"
app:showAsAction="ifRoom"/>
</menu>
ツールバーを表示させるプログラム
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// ツールバーの設定
val toolbar: Toolbar = findViewById(R.id.toolbar)
setSupportActionBar(toolbar)
}
/**
* @override
* オプションメニューのビューをセットする
*/
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
val inflater: MenuInflater = menuInflater
inflater.inflate(R.menu.menu, menu)
// 戻る(←)ボタンを表示する
// supportActionBar?.setDisplayHomeAsUpEnabled(true)
// supportActionBar?.title = "在庫移動"
return true
}
/**
* @override
* オプションメニューが押下されたときの動作
*/
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
// 設定マークが押されたら設定アクティビティに移動する処理 などを書く
R.id.action_settings -> {
//val intent = Intent(application, SettingActivity::class.java)
//startActivity(intent)
}
// 戻る(←)ボタンを表示しているときは、ActivityをFinishする
android.R.id.home -> {
//finish()
}
}
return super.onOptionsItemSelected(item)
}
}
SharedPreference
アプリケーションの設定項目などを保存しておくエリア
ユーザー名、ディレクトリ名、など好きな項目をまとめておけて便利。
Preference.kt
object Preference {
/**
* Preferenceの初期値を設定する
*/
fun init(activity: Activity) {
val sharedPref = activity.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE)
// 既に初期化されているときは、初期化しない
if (sharedPref.getBoolean("init", false)) {
return
}
println("Preference has been initialized")
sharedPref.edit()
.putString(KEY.USER.key, "レモン")
.putString(KEY.TERMINAL.key, "ANDROID-01")
.putBoolean("init", true)
.apply()
}
fun clear(activity: Activity) {
val sharedPref = activity.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE)
sharedPref.edit().putBoolean("init", false).apply()
Preference.init(activity)
}
/**
* Preferenceに保存されるデータの一覧
*/
enum class KEY(val key: String) {
USER("user"),
TERMINAL("terminal"),
// ... 以降も、増やしたい設定項目を記述する
}
/**
* Preferenceの値を設定する
*/
fun setValue(activity: Activity, key: KEY, value: String) {
val sharedPref = activity.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE)
sharedPref.edit().putString(key.key, value).apply()
}
/**
* Preferenceの値を取得する
*/
fun getValue(activity: Activity, key: KEY): String {
val sharedPref = activity.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE)
return sharedPref.getString(key.key, "") ?: ""
}
}
使い方のサンプル
SampleActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sample)
// Preferenceの初期化
Preference.init(this)
// Preference項目の取得
var user: String = Preference.getValue(this, Preference.KEY.USER)
Log.d("Pref", user)
// -> レモン
// Preference項目の設定
Preference.setValue(this, Preference.KEY.USER, "aiueo")
user = Preference.getValue(this, Preference.KEY.USER)
Log.d("Pref", user)
// -> aiueo
}
バイブレーションエフェクト
業務アプリではバイブレーションは割と大事です。
ハンディターミナルでQRを読み込んだ時にブブッって震えたりすると。
FX.kt
object FxVibration {
/**
* 短いバイブレーションを鳴らす
*/
fun play(ctx: Context) {
if (!ctx.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE).getBoolean("vibration", true)) {
return
}
val vibrator = ctx.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect =
VibrationEffect.createOneShot (
120,
VibrationEffect.DEFAULT_AMPLITUDE
)
vibrator.vibrate(vibrationEffect)
}
}
/**
* 長いバイブレーション(アラート)を鳴らす
*/
fun playAlert(ctx: Context) {
if (!ctx.getSharedPreferences("setting", AppCompatActivity.MODE_PRIVATE).getBoolean("vibration", true)) {
return
}
val vibrator = ctx.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val vibrationEffect =
VibrationEffect.createOneShot(
480,
VibrationEffect.DEFAULT_AMPLITUDE
)
vibrator.vibrate(vibrationEffect)
}
}
}
使い方のサンプル
Sample.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sample)
findViewById(R.id.Button).setOnClickListener {
FxVibration.play(this)
}
findViewById(R.id.Button2).setOnClickListener {
FxVibration.playAlert(this)
}
}
サウンドエフェクト
絶対にもっといい作り方がありそう。
でも動くからいいか~。
res/rawに、効果音ファイルを入れること
FX.kt
/**
* サウンド再生用のシングルトン
*/
class FxSound constructor(context: Context) {
private var soundPool: SoundPool? = null
companion object {
var SOUND_CLICK = 0
var SOUND_BUZZER = 0
var SOUND_SEND = 0
var SOUND_CLEAR = 0
var INSTANCE: FxSound? = null
fun instance(context: Context) =
INSTANCE ?: FxSound(context).also {
INSTANCE = it
}
}
init {
createSoundPool()
loadSoundIDs(context)
}
private fun createSoundPool() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val attributes = AudioAttributes.Builder().apply {
setUsage(AudioAttributes.USAGE_GAME)
setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
}.build()
soundPool = SoundPool.Builder().apply {
setMaxStreams(2)
setAudioAttributes(attributes)
}.build()
}
else {
@Suppress("DEPRECATION")
soundPool = SoundPool(2, AudioManager.STREAM_MUSIC, 0)
}
}
private fun loadSoundIDs(context: Context) {
soundPool?.let {
SOUND_CLICK = it.load(context, R.raw.scan,1)
SOUND_BUZZER = it.load(context, R.raw.buzzer,1)
SOUND_SEND = it.load(context, R.raw.send2,1)
SOUND_CLEAR = it.load(context, R.raw.clear,1)
}
}
fun playSound(soundID: Int) {
soundPool?.let {
it.play(soundID, 1.0f,1.0f,1,0,1.0f)
}
}
fun close() {
soundPool?.release()
soundPool = null
INSTANCE = null
}
}
}
使い方のサンプル
Sample.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sample)
findViewById(R.id.Button).setOnClickListener {
FxSound.instance(this).playSound(FxSound.SOUND_BUZZER)
}
}
RecyclerView
めっちゃむずい。Androidぜんぜん分からん。の要因の一つ、RecyclerViewくん。
4つのモノが必要です
- RecyclerViewで表示する一要素のビュー(fragment_recycler.xml)
- RecyclerViewを含んだレイアウト(activity_data.xml)
- RecyclerViewのAdapterを作成するクラス(ItemListAdapter.kt)
- RecyclerViewのAdapterを接続するアクティビティ(DataActivity.kt)
RecyclerViewのフォームを作成
fragment_recycler.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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="70dp"
android:background="@drawable/border_1"
android:orientation="vertical">
<View
android:id="@+id/divider10"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
<LinearLayout
android:id="@+id/list_Header"
android:layout_width="match_parent"
android:layout_height="24dp"
android:background="@color/purple_200"
android:orientation="horizontal">
<TextView
android:id="@+id/list_Head"
android:layout_width="80dp"
android:layout_height="match_parent"
android:drawableStart="@drawable/ic_baseline_location_on_16"
android:gravity="center_vertical"
android:text="A-123456"
android:textSize="14sp" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<TextView
android:id="@+id/list_Body"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:text="0123456789"
android:textSize="18sp" />
</LinearLayout>
<View
android:id="@+id/divider5"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="?android:attr/listDivider" />
</LinearLayout>
RecyclerViewを含んだレイアウトの作成
activity_data.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">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
RecyclerViewのアダプターを作成するクラス
むずかしい!boundsEffectは、RecyclerViewをフリックしたときのアニメーションを表示するだけなので、不要な場合は不要です。
ItemListAdapter.kt
/**
* アイテムリスト
*/
class ItemListAdapter(private val dataSet: Array<ItemList>) :
RecyclerView.Adapter<ItemListAdapter.ViewHolder>() {
lateinit var _listener: OnItemClickListener
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val head: TextView = view.findViewById(R.id.list_Head)
val body: TextView = view.findViewById(R.id.list_Body)
val header: LinearLayout = view.findViewById(R.id.list_Header)
val springAnimY: SpringAnimation = SpringAnimation(itemView, SpringAnimation.TRANSLATION_Y)
.setSpring(SpringForce().apply {
finalPosition = 0f
dampingRatio = SpringForce.DAMPING_RATIO_LOW_BOUNCY
stiffness = SpringForce.STIFFNESS_VERY_LOW
})
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_historylist, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.body.text = dataSet[position].body
holder.head.text = dataSet[position].head
holder.itemView.setOnClickListener {
_listener.onItemClickListener(it, position, dataSet[position])
}
}
interface OnItemClickListener {
fun onItemClickListener(view: View, position: Int, clickedValue: ItemList)
}
fun setOnItemClickListener(listener: OnItemClickListener) {
_listener = listener
}
override fun getItemCount() = dataSet.size
/**
* @summary リサイクルビューをスクロールアニメーションさせる(画面端でばうんどする)
*/
val boundsEffect = object : RecyclerView.EdgeEffectFactory() {
override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {
return object : EdgeEffect(view.context) {
val OVER_SCROLL_COEF = 0.2f // リスト端にスクロールしたときにどのくらいまでリスト外までスクロールさせるかを決める比率係数
val OVER_FLICK_COEF = 0.8f // リスト端にフリックしたときに程度リスト外までスクロールさせるかを決める比率係数
// リストの端に行ったときに呼び出される
override fun onPull(deltaDistance: Float, displacement: Float) {
super.onPull(deltaDistance, displacement)
// deltaDistance 0~1f 前回からの変化した割合
// displacement 0~1f タップした位置の画面上の相対位置
val sign = if (direction == DIRECTION_BOTTOM) -1 else 1
val deltaY = sign * view.height * deltaDistance * OVER_SCROLL_COEF
// view holderのアニメーションを移動距離に合わせて更新
for (i in 0 until view.childCount) {
view.apply {
val holder =
getChildViewHolder(getChildAt(i)) as ItemListAdapter.ViewHolder
holder.springAnimY.cancel()
holder.itemView.translationY += deltaY
}
}
}
// 指を離したとき
override fun onRelease() {
super.onRelease()
// アニメーションスタート
for (i in 0 until view.childCount) {
view.apply {
val holder =
getChildViewHolder(getChildAt(i)) as ItemListAdapter.ViewHolder
holder.springAnimY.start()
}
}
}
// リストをフリックして画面端に行ったとき
override fun onAbsorb(velocity: Int) {
super.onAbsorb(velocity)
val sign = if (direction == DIRECTION_BOTTOM) -1 else 1
val translationVelocity = sign * velocity * OVER_FLICK_COEF
for (i in 0 until view.childCount) {
view.apply {
val holder =
getChildViewHolder(getChildAt(i)) as ItemListAdapter.ViewHolder
holder.springAnimY
.setStartVelocity(translationVelocity)
.start()
}
}
}
}
}
}
}
/**
* RecyclerViewで使用するアイテムリスト
*/
data class ItemList(
val head: String,
val body: String,
)
DataActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_history)
val itemList: RecyclerView = findViewById(R.id.recyclerView)
val rLayoutManager: RecyclerView.LayoutManager = LinearLayoutManager(this)
itemList.layoutManager = rLayoutManager
val items = arrayOf<ItemList>(ItemList("Head1", "Body1"), ItemList("Head2", "Body2"))
val adapter = ItemListAdapter(items)
adapter.setOnItemClickListener(listClick)
itemList.adapter = adapter
itemList.edgeEffectFactory = adapter.boundsEffect
}
ダイアログ
Android開発で面食らう最初の関門
AlertDialog.builderを使ってもいいけど、画面回転時のメモリリークなどもあってこちらを使うほうがいい?
AndroidDialog.kt
/**
* OK、もしくはYes、Noだけを展開するAlertDialog
* @param title ダイアログのタイトル
* @param message ダイアログのメッセージ
* @param type ダイアログタイプ(OK, OK Cancel)
* @param onPressOK OKボタンを押したときの動作
*/
class SimpleAlertDialog(title: String, message: String, type: TYPE = TYPE.OK, onPressOK: () -> Unit = {}): DialogFragment() {
val _title = title
val _message = message
val _onPressOK = onPressOK
val _type = type
enum class TYPE() {
OK,
OK_CANCEL,
YES_NO
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder: AlertDialog.Builder = AlertDialog.Builder(activity)
builder.setTitle(_title)
.setMessage(_message)
when (_type) {
TYPE.OK -> {
builder.setPositiveButton("OK") { _, _ ->
_onPressOK()
}
}
TYPE.OK_CANCEL -> {
builder.setPositiveButton("OK") { _, _ ->
_onPressOK()
}.setNegativeButton("Cancel", null)
}
else -> {
}
}
return builder.create()
}
/**
* 画面回転時のメモリリーク対策用
*/
override fun onPause() {
super.onPause()
dismiss()
}
}
使い方
MainActivity.kt
SimpleAlertDialog("確認", "送りますか?", SimpleAlertDialog.TYPE.OK_CANCEL) {
Toast.makeText(this, "送りました", Toast.LENGTH_SHORT).show()
}.show(supportFragmentManager, "msg_confirm")
リストビュー(simple_list_item_2)
いちいちRecyclerView使ってListAdapter用意して……ってのが面倒くさいときにパッと使えると便利
simple_list_item_2は簡易的なタイトルとボディがつきます
タイトルだけのもっと簡素なsimple_list_item_1もあります
MainActivity.kt
lateinit var listView: ListView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_sample)
listView = findViewById(R.id.listView)
// タイトル・ボディ付の2種リストを用意する
val titles = listOf("title1", "title2", "title3")
val bodies = listOf("body1", "body2", "body3")
val items = titles.zip(bodies).map {
mapOf("Title" to it.first, "Body" to it.second)
}
/// こちらの書き方でも可能
// val items2 = listOf(
// mapOf("Title" to "title 1", "Body" to "body 1"),
// mapOf("Title" to "title 2", "Body" to "body 2"),
// mapOf("Title" to "title 3", "Body" to "body 3")
// )
// アダプターの作成
val adapter = SimpleAdapter(
this,
items,
android.R.layout.simple_list_item_2,
arrayOf("Title", "Body"),
intArrayOf(android.R.id.text1, android.R.id.text2)
)
listView.adapter = adapter
}