Last active
November 19, 2021 01:53
-
-
Save slaviboy/3ff8c71bd39a20e1a95b7275018fc29b to your computer and use it in GitHub Desktop.
Stackoverflow https://stackoverflow.com/questions/70011311/android-put-apostrophe-over-cyrillic-characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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" | |
android:focusable="true" | |
android:focusableInTouchMode="true" | |
android:imeOptions="actionDone" | |
tools:context=".MainActivity"> | |
<com.slaviboy.universaldictionarybg.ApostropheTextView | |
android:layout_width="350dp" | |
android:layout_height="700dp" | |
android:background="#1B1B1B" | |
android:gravity="center" | |
android:includeFontPadding="false" | |
android:letterSpacing="0.6" | |
android:lineSpacingExtra="30dp" | |
android:text="`а `б `в `г `д `е\n`ж `з `и `й `к `л\n`м `н `о `п `р `с\n`т `у `ф `х `ц `ч\n`ш `щ `ъ `ь `ю `я\n`А `Б `В `Г `Д `Е\n`Ж `З `И `Й `К `Л\n`М `Н `О `П `Р `С\n`Т `У `Ф `Х `Ц `Ч\n`Ш `Щ `Ъ `Ь `Ю `Я" | |
android:textColor="#6AE349" | |
android:textSize="22sp" | |
app:layout_constraintStart_toStartOf="parent" | |
app:layout_constraintTop_toTopOf="parent" /> | |
</androidx.constraintlayout.widget.ConstraintLayout> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package com.slaviboy.universaldictionarybg | |
import android.content.Context | |
import android.graphics.* | |
import android.util.AttributeSet | |
import android.view.Gravity | |
import android.widget.TextView | |
import androidx.appcompat.widget.AppCompatTextView | |
class ApostropheTextView : AppCompatTextView { | |
constructor(context: Context) : super(context) | |
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) | |
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) | |
val textPaint: Paint = Paint().apply { | |
isAntiAlias = true | |
style = Paint.Style.STROKE | |
strokeWidth = 2f | |
} | |
lateinit var charsMiddle: ArrayList<ArrayList<Float>> | |
lateinit var charsHeight: ArrayList<ArrayList<Float>> | |
lateinit var replaceChar: String | |
lateinit var linesLeft: ArrayList<Float> | |
lateinit var linesBottom: ArrayList<Float> | |
lateinit var originalText: String | |
lateinit var actualText: String | |
lateinit var apostropheIndicesPosition: ArrayList<ArrayList<Int>> | |
val apostrophePath: Path = Path() | |
override fun setText(text: CharSequence?, type: BufferType?) { | |
replaceChar = " " | |
// remove all apostrophes and get the bounds | |
originalText = text.toString() | |
actualText = originalText.replace("`", "").replace(" ", replaceChar) | |
super.setText(actualText, type) | |
} | |
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { | |
super.onMeasure(widthMeasureSpec, heightMeasureSpec) | |
generate(originalText) | |
getBound(actualText) | |
} | |
/** | |
* For example if we have the text value "h`appy", it first finds the | |
* positions of all "`" chars and puts them in arraylist [1], then it | |
* returns the actual string "happy". | |
*/ | |
fun generate(string: String = this.text.toString()) { | |
val lines = string.replace(" ", replaceChar).split("\n") | |
charsMiddle = arrayListOf() | |
charsHeight = arrayListOf() | |
apostropheIndicesPosition = arrayListOf() | |
lines.forEachIndexed { i, str -> | |
apostropheIndicesPosition.add(arrayListOf()) | |
charsMiddle.add(arrayListOf()) | |
charsHeight.add(arrayListOf()) | |
val p = apostropheIndicesPosition.last() | |
val m = charsMiddle.last() | |
val h = charsHeight.last() | |
if (str.contains('`')) { | |
str.forEachIndexed { j, char -> | |
if (char == '`') { | |
p.add(j - p.size) | |
} | |
} | |
val str2 = str.replace("`", "") | |
p.forEach { | |
val w1 = paint.measureText(str2, 0, it + 1) | |
val w2 = paint.measureText(str2, 0, it) | |
val bounds3 = RectF() | |
val path = Path() | |
paint.getTextPath(str2, it, it + 1, 0f, 0f, path) | |
path.computeBounds(bounds3, true) | |
val height = Math.abs(bounds3.top) // the height from the baseline to the top of the character | |
val w3 = bounds3.width() / 2f | |
m.add(w2 + w3) | |
h.add(height) | |
} | |
} | |
} | |
} | |
/** | |
* Get the bound rectangles, that are indicate the size of each line, and later are used | |
* as indication of the position where the apostrophes should be placed | |
*/ | |
fun getBound(string: String = this.text.toString()) { | |
val lines = string.replace(" ", replaceChar).split("\n") | |
var offset = 0 | |
val boundVertical = Rect() | |
getLineBounds(0, boundVertical) | |
val firstTop = boundVertical.top | |
linesLeft = arrayListOf() | |
linesBottom = arrayListOf() | |
lines.forEachIndexed { i, str -> | |
// replace all tabs with _ char for measuring | |
val s = str.replace('\t', '_') | |
// get horizontal bound for each line | |
val boundHorizontal = Rect() | |
paint.getTextBounds(s, 0, s.length, boundHorizontal) | |
boundHorizontal.offset( | |
paddingStart + (layout?.getPrimaryHorizontal(offset)?.toInt() ?: 0), | |
0 | |
) | |
val left = boundHorizontal.left | |
val bottom = firstTop + (layout?.getLineBaseline(i) ?: 0) | |
linesLeft.add(left.toFloat()) | |
linesBottom.add(bottom.toFloat()) | |
offset += (s.length + 1) | |
} | |
} | |
override fun onDraw(canvas: Canvas) { | |
textPaint.apply { | |
style = Paint.Style.FILL | |
color = currentTextColor | |
} | |
val size = width * textSize * 0.00005f | |
charsMiddle.forEachIndexed { i, m -> | |
m.forEachIndexed { j, it -> | |
val x = linesLeft[i] + it | |
val y = linesBottom[i] - charsHeight[i][j] - (size * 2) | |
apostrophePath.apply { | |
rewind() | |
moveTo(x, y) | |
lineTo(x - size, y) | |
lineTo(x - size * 2, y - size * 3) | |
lineTo(x - size, y - size * 3) | |
} | |
canvas.drawPath(apostrophePath, textPaint) | |
} | |
} | |
super.onDraw(canvas) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment