テキスト エディター アプリ
2024年4月29日10時頃、ScrollViewとConstraintLayoutとListViewの組み合わせでは動作不良でテキスト ファイルの内容を全てスクロールして閲覧する事ができなかったので、LinearLayoutに変更する修正をしました。
ちなみに、デフォルトではScrollView内のListViewは、ScrollViewがタッチ イベントをListViewに渡してくれないため、ListViewのスクロールが動作しないので、アクティビティなどでScrollViewに対してrequestDisallowInterceptTouchEvent(true)を実行してタッチ イベントを渡すように変更する必要が有ります。
2024年4月24日22時頃、contentResolver.openOutputStream()で"wt"モードを指定していなかったため、書き込む予定のテキストよりも、書き込む前のファイルの内容のテキストの文字数のほうが大きい場合、書き込む前のファイルの内容のテキストの先頭の一部を置換するような形に成ってしまい、書き込む前のファイルの内容のテキストの末尾が切り捨てられずに残ってしまう不具合を修正しました。
Githubでテキスト エディター アプリの.apkファイルなどをパブリック ドメインで公開しております。
マイクロソフトのBing検索エンジンで「github android-simple-text-editor」などで検索してみてください。
残念ながらグーグル検索エンジンでは検索できません。
広告無しのテキスト エディター アプリを自作しました。
文字コードを指定してテキスト ファイルを読み込めます。
文字コードと改行コードを指定してテキスト ファイルを保存できます。
戻す、アン ドゥ、Un-doとやり直す、リ ドゥ、Re-doできます。
文字パターン「正規表現」で検索できます。
文字パターン「正規表現」で置換、全置換できます。
AQUOS sense3で動作を確認できました。
※下記のXMLファイルやKotlinのプログラムなどのコードをコピペする場合は、2文字の全角空白を4文字の半角空白に置換してください。
また、Android StudioにJavaやKotlinなどのプログラムのコードをコピペして、「import android.R」が自動で追加されてしまったら、削除してください。
「android.R」は、「R.layout.activity_main」や「R.id.◯◯◯」の「R」とは違います。
そのため、「import android.R」が有ると、コンパイル エラーが発生してしまいます。
Android StudioにJavaやKotlinなどのプログラムのコードをコピペすると、変数の名前が半角バッククォート記号(`)で囲まれる事が有ります。
Kotlinでは変数の名前を半角バッククォート記号(`)で囲むと予約語(inやnullなど)や半角空白記号( )などを変数の名前にできるそうです。
可能であれば、半角バッククォート記号(`)で囲まれた変数の名前は、半角バッククォート記号(`)で囲まずに済む名前に変更したほうが良いのでは、と個人的に思っております。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/AndroidManifest.xml
――――――――――――――――――――
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/Theme.SimpleTextEditor"
tools:targetApi="31"
>
<activity
android:name=".MainActivity"
android:exported="true"
>
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".Edit"
android:parentActivityName=".MainActivity"
>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity"
/>
</activity>
</application>
</manifest>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/res/values/strings.xml
――――――――――――――――――――
<resources>
<string name="app_name">SimpleTextEditor</string>
<string name="read">Read</string>
<string name="edit">Edit</string>
<string name="copy_all">Copy All</string>
<string name="character_code_name_default">UTF-8</string>
<string name="line_break_default">\\n</string>
<string name="view">View</string>
<string name="save">Save</string>
<string name="undo">Undo</string>
<string name="redo">Redo</string>
<string name="search">Search</string>
<string name="replace">Replace</string>
<string name="replace_all">Replace All</string>
<string name="editing_file_name_prefix">*</string>
<string name="empty_search_pattern">The "Regular Expression" of search pattern is empty. Could you input?</string>
<string name="wrong_search_pattern">The "Regular Expression" of search pattern is wrong. </string>
<string name="search_result_zero">Search Result: Zero Record</string>
</resources>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/res/values-ja/strings.xml
――――――――――――――――――――
<resources>
<string name="app_name">SimpleTextEditor</string>
<string name="read">テキスト ファイル読み込み</string>
<string name="edit">編集</string>
<string name="copy_all">全コピー</string>
<string name="character_code_name_default">UTF-8</string>
<string name="line_break_default">\\n</string>
<string name="view">ビュー</string>
<string name="save">保存</string>
<string name="undo">戻す</string>
<string name="redo">やり直す</string>
<string name="search">検索</string>
<string name="replace">置換</string>
<string name="replace_all">全置換</string>
<string name="editing_file_name_prefix">※</string>
<string name="empty_search_pattern">文字パターン「正規表現」を検索条件に入力してください。</string>
<string name="wrong_search_pattern">検索条件の文字パターン「正規表現」が誤っているので直してください。</string>
<string name="search_result_zero">検索結果: ゼロ件</string>
</resources>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/res/layout/activity_main.xml
――――――――――――――――――――
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/viewFileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
/>
<EditText
android:id="@+id/viewCharacterCodeName"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@string/character_code_name_default"
/>
<EditText
android:id="@+id/viewLineBreak"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text="@string/line_break_default"
/>
<Button
android:id="@+id/viewRead"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/read"
android:layout_gravity="center"
/>
<Button
android:id="@+id/viewEdit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/edit"
android:layout_gravity="center"
/>
<Button
android:id="@+id/viewCopyAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/copy_all"
android:layout_gravity="center"
/>
<ListView
android:id="@+id/viewLineList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
/>
</LinearLayout>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/res/layout/view_line.xml
――――――――――――――――――――
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/viewLine"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left"
android:layout_gravity="left"
/>
</LinearLayout>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/res/layout/edit.xml
――――――――――――――――――――
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<TextView
android:id="@+id/editFileName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=""
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<EditText
android:id="@+id/editCharacterCodeName"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text=""
app:layout_constraintTop_toBottomOf="@id/editFileName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<EditText
android:id="@+id/editLineBreak"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text=""
app:layout_constraintTop_toBottomOf="@id/editCharacterCodeName"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Button
android:id="@+id/editView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/view"
app:layout_constraintTop_toBottomOf="@id/editLineBreak"
app:layout_constraintStart_toStartOf="parent"
/>
<Button
android:id="@+id/editSave"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/save"
app:layout_constraintTop_toBottomOf="@id/editLineBreak"
app:layout_constraintStart_toEndOf="@id/editView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
/>
<Button
android:id="@+id/editUndo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/undo"
app:layout_constraintTop_toBottomOf="@id/editView"
app:layout_constraintStart_toStartOf="parent"
/>
<Button
android:id="@+id/editRedo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/redo"
app:layout_constraintTop_toBottomOf="@id/editView"
app:layout_constraintStart_toEndOf="@id/editUndo"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
/>
<EditText
android:id="@+id/editSearchPattern"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text=""
app:layout_constraintTop_toBottomOf="@id/editUndo"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Button
android:id="@+id/editSearch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/search"
app:layout_constraintTop_toBottomOf="@id/editSearchPattern"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<EditText
android:id="@+id/editReplaceText"
android:inputType="text"
android:layout_width="match_parent"
android:layout_height="50dp"
android:text=""
app:layout_constraintTop_toBottomOf="@id/editSearch"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
/>
<Button
android:id="@+id/editReplace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/replace"
app:layout_constraintTop_toBottomOf="@id/editReplaceText"
app:layout_constraintStart_toStartOf="parent"
/>
<Button
android:id="@+id/editReplaceAll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/replace_all"
app:layout_constraintTop_toBottomOf="@id/editReplaceText"
app:layout_constraintStart_toEndOf="@id/editReplace"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
/>
<EditText
android:id="@+id/editText"
android:inputType="textMultiLine"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/editReplaceAll"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
>
<requestFocus/>
</EditText>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</LinearLayout>
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/java/eliphas1810/simpletexteditor/MainActivity.kt
――――――――――――――――――――
package eliphas1810.simpletexteditor
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.documentfile.provider.DocumentFile
import java.io.InputStreamReader
import java.nio.charset.Charset
private class Adapter(context: Context, lineList: List<String>, ): ArrayAdapter<String>(context, R.layout.view_line) {
var maxLineNumberSize = 1
override fun getView(position: Int, view: View?, viewGroup: ViewGroup): View {
var view: View? = view
try {
if (view == null) {
view = LayoutInflater.from(context).inflate(R.layout.view_line, viewGroup, false)
}
var line = getItem(position)
var lineNumber = "" + (position + 1)
view?.findViewById<TextView>(R.id.viewLine)?.text = lineNumber + " " + line
} catch (exception: Exception) {
Toast.makeText(view?.context?.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
return view!!
}
}
class MainActivity : AppCompatActivity() {
companion object {
const val FILE_NAME_KEY = "eliphas1810.simpletexteditor.FILE_NAME"
const val CHARACTER_CODE_KEY = "eliphas1810.simpletexteditor.CHARACTER_CODE"
const val LINE_BREAK_KEY = "eliphas1810.simpletexteditor.LINE_BREAK"
const val TEXT_KEY = "eliphas1810.simpletexteditor.TEXT"
const val CURSOR_TEXT_INDEX_KEY = "eliphas1810.simpletexteditor.CURSOR_TEXT_INDEX"
}
var fileNameTextView: TextView? = null
var characterCodeEditText: EditText? = null
var lineBreakEditText: EditText? = null
var viewLineListView: ListView? = null
private var text: String? = ""
private var readActivityResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(StartActivityForResult()) { activityResult: ActivityResult ->
if (activityResult.resultCode == RESULT_OK) {
val intent = activityResult.data
val uri: Uri? = intent?.data
val documentFile = DocumentFile.fromSingleUri(applicationContext, uri!!)
fileNameTextView?.text = documentFile?.name
var characterCode = characterCodeEditText?.text?.toString()!!
if (characterCode.isEmpty()) {
characterCodeEditText?.setText(getString(R.string.character_code_name_default))
characterCode = getString(R.string.character_code_name_default)
}
try {
Charset.forName(characterCode)
} catch (exception: Exception) {
characterCodeEditText?.setText(getString(R.string.character_code_name_default))
characterCode = getString(R.string.character_code_name_default)
}
InputStreamReader(contentResolver.openInputStream(uri!!), characterCode).use {
text = it.readText()
}
if (text?.contains("\r\n") == true) {
lineBreakEditText?.setText("\\r\\n")
} else if (text?.contains("\r") == true) {
lineBreakEditText?.setText("\\r")
} else {
lineBreakEditText?.setText("\\n")
}
text = text?.replace("\r\n", "\n")
text = text?.replace("\r", "\n")
var lineList: List<String> = text!!.split("\n")
if (lineList.isEmpty()) {
lineList = listOf("")
}
val adapter = viewLineListView?.adapter as Adapter
adapter.clear()
adapter.addAll(lineList)
adapter.maxLineNumberSize = lineList.size
adapter.notifyDataSetChanged()
}
}
private var editActivityResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(StartActivityForResult()) { activityResult: ActivityResult ->
if (activityResult.resultCode == RESULT_OK) {
val intent = activityResult.data
fileNameTextView?.text = intent?.getStringExtra(FILE_NAME_KEY)
characterCodeEditText?.setText(intent?.getStringExtra(CHARACTER_CODE_KEY))
lineBreakEditText?.setText(intent?.getStringExtra(LINE_BREAK_KEY))
text = intent?.getStringExtra(TEXT_KEY)
text = text?.replace("\r\n", "\n")
text = text?.replace("\r", "\n")
var lineList: List<String> = text!!.split("\n")
if (lineList.isEmpty()) {
lineList = listOf("")
}
val adapter = viewLineListView?.adapter as Adapter
adapter.clear()
adapter.addAll(lineList)
adapter.maxLineNumberSize = lineList.size
adapter.notifyDataSetChanged()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
try {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
fileNameTextView = findViewById(R.id.viewFileName)
characterCodeEditText = findViewById(R.id.viewCharacterCodeName)
lineBreakEditText = findViewById(R.id.viewLineBreak)
viewLineListView = findViewById(R.id.viewLineList)
val adapter = Adapter(this, listOf(""))
adapter.maxLineNumberSize = 1
viewLineListView?.adapter = adapter
viewLineListView?.onItemClickListener = AdapterView.OnItemClickListener { adapterView, view, position, id ->
try {
text = text?.replace("\r\n", "\n")
text = text?.replace("\r", "\n")
var lineList: List<String> = text!!.split("\n")
if (lineList.isEmpty()) {
lineList = listOf("")
}
var cursorTextIndex = 0
if (1 <= position) {
cursorTextIndex = lineList.slice(0..(position - 1)).joinToString("\n").length + 1
}
val intent = Intent(this, Edit::class.java)
intent.putExtra(FILE_NAME_KEY, fileNameTextView?.text)
intent.putExtra(CHARACTER_CODE_KEY, characterCodeEditText?.text?.toString())
intent.putExtra(LINE_BREAK_KEY, lineBreakEditText?.text?.toString())
intent.putExtra(TEXT_KEY, text)
intent.putExtra(CURSOR_TEXT_INDEX_KEY, cursorTextIndex)
startActivity(intent)
} catch (exception: Exception) {
Toast.makeText(view.context.applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.viewRead).setOnClickListener{ view ->
try {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
readActivityResultLauncher?.launch(intent)
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.viewEdit).setOnClickListener{ view ->
try {
val intent = Intent(this, Edit::class.java)
intent.putExtra(FILE_NAME_KEY, fileNameTextView?.text)
intent.putExtra(CHARACTER_CODE_KEY, characterCodeEditText?.text?.toString())
intent.putExtra(LINE_BREAK_KEY, lineBreakEditText?.text?.toString())
intent.putExtra(TEXT_KEY, text)
intent.putExtra(CURSOR_TEXT_INDEX_KEY, text?.length!!)
editActivityResultLauncher?.launch(intent)
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.viewCopyAll).setOnClickListener{ view ->
try {
val clipboardManager = applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
clipboardManager.setPrimaryClip(ClipData.newPlainText("", text))
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
override fun onDestroy() {
try {
fileNameTextView = null
characterCodeEditText = null
lineBreakEditText = null
viewLineListView = null
text = null
readActivityResultLauncher?.unregister()
readActivityResultLauncher = null
editActivityResultLauncher?.unregister()
editActivityResultLauncher = null
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
} finally {
super.onDestroy()
}
}
}
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
eliphas1810/simpletexteditorは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。
eliphas1810.simpletexteditorは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。
/home/◯◯◯/AndroidStudioProjects/SimpleTextEditor/app/src/main/java/eliphas1810/simpletexteditor/Edit.kt
――――――――――――――――――――
package eliphas1810.simpletexteditor
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.widget.*
import androidx.activity.OnBackPressedCallback
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts.StartActivityForResult
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import java.io.BufferedWriter
import java.io.OutputStreamWriter
import java.nio.charset.Charset
//テキスト編集履歴
data class History (
var text: String = "", //テキスト編集履歴を保持した時点のテキスト
var cursorIndex: Int = 0 //テキスト編集履歴を保持した時点のテキスト内のカーソルのインデックス番号
) {
}
class Edit : AppCompatActivity(), TextWatcher {
companion object {
const val FILE_NAME_KEY = "eliphas1810.simpletexteditor.FILE_NAME"
const val CHARACTER_CODE_KEY = "eliphas1810.simpletexteditor.CHARACTER_CODE"
const val LINE_BREAK_KEY = "eliphas1810.simpletexteditor.LINE_BREAK"
const val TEXT_KEY = "eliphas1810.simpletexteditor.TEXT"
const val CURSOR_TEXT_INDEX_KEY = "eliphas1810.simpletexteditor.CURSOR_TEXT_INDEX"
}
var fileNameTextView: TextView? = null
var characterCodeEditText: EditText? = null
var lineBreakEditText: EditText? = null
var editText: EditText? = null
var fileName: String? = null
var editingFileName: String? = null //テキスト編集中を表すためのファイル名
private var historyList: MutableList<History>? = mutableListOf() //テキスト編集履歴の一覧
private var historyIndex: Int? = -1
private var saveActivityResultLauncher: ActivityResultLauncher<Intent>? = registerForActivityResult(StartActivityForResult()) { activityResult: ActivityResult ->
if (activityResult.resultCode == RESULT_OK) {
val intent = activityResult.data
val uri: Uri? = intent?.data
val documentFile = DocumentFile.fromSingleUri(applicationContext, uri!!)
fileName = documentFile?.name
editingFileName = getString(R.string.editing_file_name_prefix) + fileName
fileNameTextView?.text = fileName
editText = findViewById<EditText>(R.id.editText)
var text = editText?.text?.toString()
var lineBreak = "\n"
if (lineBreakEditText?.text?.toString() == "\\n") {
lineBreak = "\n"
} else if (lineBreakEditText?.text?.toString() == "\\r\\n") {
lineBreak = "\r\n"
} else if (lineBreakEditText?.text?.toString() == "\\r") {
lineBreak = "\r"
} else {
lineBreak = "\n"
}
if (lineBreak == "\n") {
lineBreakEditText?.setText("\\n")
} else if (lineBreak == "\r\n") {
lineBreakEditText?.setText("\\r\\n")
} else if (lineBreak == "\r") {
lineBreakEditText?.setText("\\r")
}
text = text?.replace("\r\n", "\n")
text = text?.replace("\r", "\n")
text = text?.replace("\n", lineBreak)
var characterCode = characterCodeEditText?.text?.toString()!!
if (characterCode.isEmpty()) {
characterCodeEditText?.setText(getString(R.string.character_code_name_default))
characterCode = getString(R.string.character_code_name_default)
}
try {
Charset.forName(characterCode)
} catch (exception: Exception) {
characterCodeEditText?.setText(getString(R.string.character_code_name_default))
characterCode = getString(R.string.character_code_name_default)
}
//contentResolver.openOutputStream()で"wt"モードを指定しないと、書き込み前のテキストの文字数が大きい場合、書き込み前のテキストの先頭の一部を置換するような形に成ってしまいます。
OutputStreamWriter(contentResolver.openOutputStream(uri!!, "wt"), characterCode).use {
BufferedWriter(it).use {
it.write(text)
}
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
try {
super.onCreate(savedInstanceState)
setContentView(R.layout.edit)
//戻るボタン、戻るジェスチャーを無効化
onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {}
})
fileNameTextView = findViewById(R.id.editFileName)
characterCodeEditText = findViewById(R.id.editCharacterCodeName)
lineBreakEditText = findViewById(R.id.editLineBreak)
editText = findViewById<EditText>(R.id.editText)
fileName = getIntent()?.getStringExtra(FILE_NAME_KEY)
editingFileName = getString(R.string.editing_file_name_prefix) + fileName
fileNameTextView?.text = fileName
characterCodeEditText?.setText(getIntent()?.getStringExtra(CHARACTER_CODE_KEY))
lineBreakEditText?.setText(getIntent()?.getStringExtra(LINE_BREAK_KEY))
editText?.setText(getIntent()?.getStringExtra(TEXT_KEY))
editText?.setSelection(getIntent()?.getIntExtra(CURSOR_TEXT_INDEX_KEY, 0)!!)
historyList?.clear()
historyList?.add(History(editText?.text?.toString()!!, editText?.text?.toString()?.length!!))
historyIndex = historyList?.size!! - 1
findViewById<Button>(R.id.editUndo).isEnabled = false
findViewById<Button>(R.id.editRedo).isEnabled = false
findViewById<Button>(R.id.editView).setOnClickListener{ view ->
try {
historyList?.clear()
historyIndex = -1
findViewById<Button>(R.id.editUndo).isEnabled = false
findViewById<Button>(R.id.editRedo).isEnabled = false
val intent = Intent(this, MainActivity::class.java)
intent.putExtra(FILE_NAME_KEY, fileName)
intent.putExtra(CHARACTER_CODE_KEY, characterCodeEditText?.text?.toString())
intent.putExtra(LINE_BREAK_KEY, lineBreakEditText?.text?.toString())
intent.putExtra(TEXT_KEY, editText?.text.toString())
setResult(RESULT_OK, intent)
fileName = null
editingFileName = null
finish()
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editSave).setOnClickListener{ view ->
try {
if (historyIndex!! <= -1) {
return@setOnClickListener
}
while (historyIndex!! < historyList?.size!! - 1) {
historyList?.removeLast()
}
historyList?.add(History(editText?.text?.toString()!!, editText?.selectionEnd ?: editText?.text?.toString()?.length!!))
historyIndex = historyList?.size!! - 1
findViewById<Button>(R.id.editUndo).isEnabled = true
findViewById<Button>(R.id.editRedo).isEnabled = false
val intent = Intent(Intent.ACTION_CREATE_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "*/*"
saveActivityResultLauncher?.launch(intent)
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editUndo).setOnClickListener{ view ->
try {
if (historyIndex!! <= 0) {
return@setOnClickListener
}
historyIndex = historyIndex!! - 1
editText?.setText(historyList?.get(historyIndex!!)?.text!!)
editText?.setSelection(historyList?.get(historyIndex!!)?.cursorIndex!!)
findViewById<Button>(R.id.editRedo).isEnabled = true
if (historyIndex == 0) {
findViewById<Button>(R.id.editUndo).isEnabled = false
fileNameTextView?.text = fileName
} else {
fileNameTextView?.text = editingFileName
}
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editRedo).setOnClickListener{ view ->
try {
if (historyIndex!! <= -1) {
return@setOnClickListener
}
if (historyList?.size!! - 1 <= historyIndex!!) {
return@setOnClickListener
}
historyIndex = historyIndex!! + 1
editText?.setText(historyList?.get(historyIndex!!)?.text!!)
editText?.setSelection(historyList?.get(historyIndex!!)?.cursorIndex!!)
findViewById<Button>(R.id.editUndo).isEnabled = true
fileNameTextView?.text = editingFileName
if (historyList?.size!! - 1 <= historyIndex!!) {
findViewById<Button>(R.id.editRedo).isEnabled = false
}
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editSearch).setOnClickListener{ view ->
try {
val searchPattern = findViewById<EditText>(R.id.editSearchPattern)?.text?.toString()!!
if (searchPattern.isEmpty()) {
Toast.makeText(applicationContext, getString(R.string.empty_search_pattern), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
var searchRegex: Regex? = null
try {
searchRegex = Regex(searchPattern)
} catch (exception: Exception) {
Toast.makeText(applicationContext, getString(R.string.wrong_search_pattern) + exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
if (editText?.hasFocus() == false) {
editText?.requestFocus()
editText?.setSelection(0)
}
val text = editText?.text?.toString()!!
val searchStartIndex = editText?.selectionEnd!!
val matchResult = searchRegex?.find(text, searchStartIndex)
if (matchResult == null) {
Toast.makeText(applicationContext, getString(R.string.search_result_zero), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
editText?.setSelection(matchResult?.range?.start!!, matchResult?.range?.endInclusive!! + 1)
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editReplace).setOnClickListener{ view ->
try {
val searchPattern = findViewById<EditText>(R.id.editSearchPattern)?.text?.toString()!!
val replaceText = findViewById<EditText>(R.id.editReplaceText)?.text?.toString()!!
if (searchPattern.isEmpty()) {
Toast.makeText(applicationContext, getString(R.string.empty_search_pattern), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
var searchRegex: Regex? = null
try {
searchRegex = Regex(searchPattern)
} catch (exception: Exception) {
Toast.makeText(applicationContext, getString(R.string.wrong_search_pattern) + exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
if (editText?.hasFocus() == false) {
editText?.requestFocus()
editText?.setSelection(0)
}
var text = editText?.text?.toString()!!
val searchStartIndex = editText?.selectionStart!!
val matchResult = searchRegex?.find(text, searchStartIndex)
if (matchResult == null) {
Toast.makeText(applicationContext, getString(R.string.search_result_zero), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
val startIndex = matchResult?.range?.start!!
val endIndex = matchResult?.range?.endInclusive!!
val replacedText = text.substring(startIndex, endIndex + 1).replace(Regex(searchPattern), replaceText)
text = text.substring(0, startIndex) + replacedText + text.substring(endIndex + 1)
while (historyIndex!! < historyList?.size!! - 1) {
historyList?.removeLast()
}
historyList?.add(History(text, startIndex + replaceText.length))
historyIndex = historyList?.size!! - 1
editText?.setText(text)
editText?.setSelection(startIndex + replaceText.length)
fileNameTextView?.text = editingFileName
findViewById<Button>(R.id.editUndo).isEnabled = true
findViewById<Button>(R.id.editRedo).isEnabled = false
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
findViewById<Button>(R.id.editReplaceAll).setOnClickListener{ view ->
try {
val searchPattern = findViewById<EditText>(R.id.editSearchPattern)?.text?.toString()!!
val replaceText = findViewById<EditText>(R.id.editReplaceText)?.text?.toString()!!
if (searchPattern.isEmpty()) {
Toast.makeText(applicationContext, getString(R.string.empty_search_pattern), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
var searchRegex: Regex? = null
try {
searchRegex = Regex(searchPattern)
} catch (exception: Exception) {
Toast.makeText(applicationContext, getString(R.string.wrong_search_pattern) + exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
var text = editText?.text?.toString()!!
if (searchRegex?.find(text) == null) {
Toast.makeText(applicationContext, getString(R.string.search_result_zero), Toast.LENGTH_LONG).show()
return@setOnClickListener
}
text = text.replace(Regex(searchPattern), replaceText)
while (historyIndex!! < historyList?.size!! - 1) {
historyList?.removeLast()
}
var cursorIndex = editText?.selectionEnd ?: 0
if (text?.length!! < cursorIndex) {
cursorIndex = text?.length!!
}
historyList?.add(History(text, cursorIndex))
historyIndex = historyList?.size!! - 1
editText?.setText(text)
editText?.setSelection(cursorIndex)
fileNameTextView?.text = editingFileName
findViewById<Button>(R.id.editUndo).isEnabled = true
findViewById<Button>(R.id.editRedo).isEnabled = false
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
editText?.addTextChangedListener(this)
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
override fun onDestroy() {
try {
fileNameTextView = null
characterCodeEditText = null
lineBreakEditText = null
editText = null
fileName = null
editingFileName = null
historyList?.clear()
historyList = null
historyIndex = null
saveActivityResultLauncher?.unregister()
saveActivityResultLauncher = null
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
} finally {
super.onDestroy()
}
}
//EditTextのtextが変更されている時に1度だけ、変更前のtextなどを渡されて起動されます。
override fun beforeTextChanged(text: CharSequence?, index: Int, changedSize: Int, addedSize: Int) {
}
//EditTextのtextが変更されている時に変更のたびに、変更中のtextなどを渡されて起動されます。
override fun onTextChanged(text: CharSequence?, index: Int, deletedSize: Int, addedSize: Int) {
}
//EditTextのtextが変更された時に、変更後のtextを渡されて起動されます。
override fun afterTextChanged(text: Editable?) {
try {
if (historyIndex!! <= -1) {
return
}
val lastText = historyList?.get(historyIndex!!)?.text!!
if (lastText == text?.toString()) {
return
}
while (historyIndex!! < historyList?.size!! - 1) {
historyList?.removeLast()
}
historyList?.add(History(text?.toString() ?: "", editText?.selectionEnd ?: text?.toString()?.length ?: 0))
historyIndex = historyList?.size!! - 1
fileNameTextView?.text = editingFileName
findViewById<Button>(R.id.editUndo).isEnabled = true
findViewById<Button>(R.id.editRedo).isEnabled = false
} catch (exception: Exception) {
Toast.makeText(applicationContext, exception.toString(), Toast.LENGTH_LONG).show()
throw exception
}
}
}
――――――――――――――――――――
◯◯◯はLinux Mintのユーザー名です。
SimpleTextEditorは著者が付けたAndroid Studioのプロジェクトの名前です。
eliphas1810/simpletexteditorは著者が付けたJavaやKotlinのプログラムのパッケージのディレクトリの相対パスです。
eliphas1810.simpletexteditorは著者が付けたJavaやKotlinのプログラムのパッケージの名前です。
新規登録で充実の読書を
- マイページ
- 読書の状況から作品を自動で分類して簡単に管理できる
- 小説の未読話数がひと目でわかり前回の続きから読める
- フォローしたユーザーの活動を追える
- 通知
- 小説の更新や作者の新作の情報を受け取れる
- 閲覧履歴
- 以前読んだ小説が一覧で見つけやすい
アカウントをお持ちの方はログイン
ビューワー設定
文字サイズ
背景色
フォント
組み方向
機能をオンにすると、画面の下部をタップする度に自動的にスクロールして読み進められます。
応援すると応援コメントも書けます