I'm building an MultiStateToggle Button.
To highlight the selected state, I need to draw a rectangle which is exactly the size of a button. This rectangle slides to the current state. Therefore I need to draw, but whatever I do, I do not see any change even though onDraw was called.
The superclass ToggleButton simply holds some constants. It extends LinearLayout!
Does anybody know, why drawing does not work in this LinearLayout sub-class?
class MultiStateToggleButton : ToggleButton {
//region Variables
private val mHighlightPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
#DrawableRes
private val iSelectableItemBackgroundDrawableResId: Int
private val mButtons = mutableListOf<Button>()
//endregion
constructor(context: Context) : this(context, Any() as AttributeSet)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
val mTypedValue = TypedValue()
context.theme.resolveAttribute(R.attr.colorPrimary, mTypedValue, true)
val iPrimaryColor = mTypedValue.data
context.theme.resolveAttribute(R.attr.colorPrimary, mTypedValue, true)
val iPrimaryDarkColor = mTypedValue.data
setBackgroundColor(iPrimaryDarkColor)
context.theme.resolveAttribute(R.attr.colorAccent, mTypedValue, true)
val iAccentColor = mTypedValue.data
val mTypedArray = context.obtainStyledAttributes(attrs, R.styleable.MultiStateToggleButton)
foregroundColor = mTypedArray.getColor(R.styleable.MultiStateToggleButton_foregroundColor, iPrimaryColor)
highlightColor = mTypedArray.getColor(R.styleable.MultiStateToggleButton_highlightColor, iAccentColor)
val mTexts = mTypedArray.getTextArray(R.styleable.MultiStateToggleButton_elements)
if (mTexts != null) {
val iSelectedElement = mTypedArray.getInt(R.styleable.MultiStateToggleButton_selectedElement, 0)
setElements(Array(mTexts.size) { i ->
mTexts[i].toString()
}, iSelectedElement)
}
mTypedArray.recycle()
}
init {
val mTypedValue = TypedValue()
context.theme.resolveAttribute(android.R.attr.selectableItemBackground, mTypedValue, true)
iSelectableItemBackgroundDrawableResId = mTypedValue.resourceId
mHighlightPaint.apply {
color = highlightColor
style = Paint.Style.FILL
}
}
//region Public Variables
override var selectedElement: Int = 0
set(value) {
selectButton(value)
field = value
super.selectedElement = value
}
//endregion
//region Methods
fun setElements(texts: Array<String>) {
removeAllViews()
mButtons.clear()
texts.forEachIndexed { i, text ->
val mButton = Button(context).apply {
layoutParams = LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, LAYOUT_WEIGHT_EQUAL)
setBackgroundResource(iSelectableItemBackgroundDrawableResId)
setText(text)
setTextColor(textColor)
setOnClickListener { selectedElement = i }
}
mButtons.add(mButton)
addView(mButton)
}
}
fun setElements(texts: Array<String>, selectedItem: Int) {
setElements(texts)
selectButton(selectedItem)
}
override fun setEnabled(enabled: Boolean) {
for (i in 0 until childCount) {
getChildAt(i).isEnabled = enabled
}
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.drawRect(0f, 0f, 30f, 30f, mHighlightPaint)
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
setWillNotDraw(false)
}
override fun onSaveInstanceState(): Parcelable {
return Bundle().apply {
putParcelable(KEY_INSTANCE_STATE, super.onSaveInstanceState())
putInt(KEY_SELECTED_BUTTON, selectedElement)
}
}
override fun onRestoreInstanceState(state: Parcelable?) {
var mRestoredState: Parcelable? = state
if (state is Bundle) {
selectButton(state.getInt(KEY_SELECTED_BUTTON))
mRestoredState = state.getParcelable(KEY_INSTANCE_STATE)
}
super.onRestoreInstanceState(mRestoredState)
}
private fun selectButton(position: Int) {
if (mButtons.isNotEmpty()) {
if (position >= mButtons.size) {
throw IndexOutOfBoundsException("Position was $position but there are only ${mButtons.size} Buttons.")
}
getChildAt(selectedElement).isEnabled = true
getChildAt(position).isEnabled = false
invalidate()
}
}
//endregion
companion object {
private const val KEY_SELECTED_BUTTON = "mtb_selected_button"
private const val KEY_INSTANCE_STATE = "mtb_instance_state"
}
}
As Kotlins' init block is called on Primary Constructor call, my Paint was not initialized.
Related
I'm creating a custom SwipeButton extend RelativeLayout in android which contains an image that I can swipe to right.
On the other hand, I have a Fragment that includes this SwipeButton. My goal is that when swipe has completed (the image has been scrolled to the end) execute a function in fragment
include some code:
1. Fragment
class FirstStep() : Fragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_first_step, container, false)
val swipeButton : SwipeButton = view.findViewById(R.id.swipe_btn)
// I TRIED THIS: ------------------------------
swipeButton.setOnTouchListener { view, motionEvent ->
when(motionEvent.action){
MotionEvent.ACTION_UP -> {
if(swipeButton.completed){
//function that i need to run in firebase
}
return#setOnTouchListener true
}
else -> {return#setOnTouchListener true}
}
}
// I TRIED THIS: ------------------------------
// NOT WORKS *explanation below
return view
}
}
*Didn't work because apparently I think it conflicts with the same on touch listener that is inside the SwipeButton Class and from then the Button not works
2. fragment_first_step.xml
... some layouts
<com.app.SwipeButton
android:id="#+id/swipe_btn"
android:layout_width="wrap_content"
android:layout_weight="1"
android:layout_height="70dp"
/>
3. SwipeButton Class
class SwipeButton : RelativeLayout {
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
constructor(
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
defStyleRes: Int
) : super(context, attrs, defStyleAttr, defStyleRes) {
init(context, attrs)
}
private var slidingButton: ImageView? = null
private var initialX : Float = 0f
private var active = false
var completed = false
private var initialButtonWidth : Int = 0
private var centerText: TextView? = null
private var disabledDrawable: Drawable? = null
private var enabledDrawable: Drawable? = null
private fun init(context: Context, attrs: AttributeSet?) {
// add Relative Layout
val background = RelativeLayout(context)
val layoutParamsView = LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
layoutParamsView.addRule(CENTER_IN_PARENT, TRUE)
background.background = ContextCompat.getDrawable(context, R.drawable.shape_rounded)
addView(background, layoutParamsView)
// add text to button
val centerText:TextView = TextView(context)
this.centerText = centerText
val layoutParams = LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
layoutParams.addRule(CENTER_IN_PARENT, TRUE)
centerText.text = "SIGUIENTE PASO" //add any text you need
centerText.setTextColor(Color.WHITE)
centerText.setPadding(35, 35, 35, 35)
background.addView(centerText, layoutParams)
// add image to swipe
val swipeButton = ImageView(context)
slidingButton = swipeButton
disabledDrawable =
ContextCompat.getDrawable(getContext(), R.drawable.ic_home)
enabledDrawable =
ContextCompat.getDrawable(getContext(), R.drawable.ic_close_black)
slidingButton!!.setImageDrawable(disabledDrawable)
slidingButton!!.setPadding(40, 40, 40, 40)
val layoutParamsButton = LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT,
)
layoutParamsButton.addRule(ALIGN_PARENT_LEFT, TRUE)
layoutParamsButton.addRule(CENTER_VERTICAL, TRUE)
swipeButton.background = ContextCompat.getDrawable(context, R.drawable.shape_button)
swipeButton.setImageDrawable(disabledDrawable)
addView(swipeButton, layoutParamsButton)
// LISTEN TO TOUCH CHANGES
setOnTouchListener(getButtonTouchListener())
this.onFinishInflate()
}
#SuppressLint("ClickableViewAccessibility")
private fun getButtonTouchListener(): OnTouchListener? {
return OnTouchListener { _, event ->
when (event.action) {
MotionEvent.ACTION_DOWN -> return#OnTouchListener true
MotionEvent.ACTION_MOVE -> {
if (initialX.toInt() == 0) {
initialX = slidingButton!!.x;
}
if ((event.x > initialX + slidingButton!!.width.toFloat() / 2) && (event.x + slidingButton!!.width / 2 < width)) {
slidingButton!!.setX(event.x - slidingButton!!.width / 2);
centerText?.setAlpha(1 - 1.3f * (slidingButton!!.x + slidingButton!!.width) / width);
}
if (event.x + slidingButton!!.width / 2 > width && slidingButton!!.x + slidingButton!!.width / 2 < width) {
slidingButton!!.setX((width - slidingButton!!.width).toFloat());
}
if (event.x < slidingButton!!.width / 2 && slidingButton!!.x > 0) {
slidingButton!!.setX(0F)
}
}
MotionEvent.ACTION_UP -> {
if (active) {
collapseButton();
} else {
initialButtonWidth = slidingButton!!.width
if (slidingButton!!.x + slidingButton!!.width > width * 0.85) {
expandButton();
completed = true
// HERE SWIPE IS COMPLETED
} else {
moveButtonBack();
}
}
}
}
false
}
}
private fun expandButton() {
val positionAnimator = ValueAnimator.ofFloat(slidingButton!!.x, 0f)
positionAnimator.addUpdateListener {
val x = positionAnimator.animatedValue as Float
slidingButton!!.x = x
}
val widthAnimator = ValueAnimator.ofInt(
slidingButton!!.width,
width
)
widthAnimator.addUpdateListener {
val params = slidingButton!!.layoutParams
params.width = (widthAnimator.animatedValue as Int)
slidingButton!!.layoutParams = params
}
val animatorSet = AnimatorSet()
animatorSet.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
active = true
slidingButton!!.setImageDrawable(enabledDrawable)
}
})
animatorSet.playTogether(positionAnimator, widthAnimator)
animatorSet.start()
}
private fun collapseButton() {
val widthAnimator = ValueAnimator.ofInt(
slidingButton!!.width,
initialButtonWidth
)
widthAnimator.addUpdateListener {
val params = slidingButton!!.layoutParams
params.width = (widthAnimator.animatedValue as Int)
slidingButton!!.layoutParams = params
}
widthAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
super.onAnimationEnd(animation)
active = false
slidingButton!!.setImageDrawable(disabledDrawable)
}
})
val objectAnimator = ObjectAnimator.ofFloat(
centerText!!, "alpha", 1f
)
val animatorSet = AnimatorSet()
animatorSet.playTogether(objectAnimator, widthAnimator)
animatorSet.start()
}
private fun moveButtonBack() {
val positionAnimator = ValueAnimator.ofFloat(slidingButton!!.x, 0f)
positionAnimator.interpolator = AccelerateDecelerateInterpolator()
positionAnimator.addUpdateListener {
val x = positionAnimator.animatedValue as Float
slidingButton!!.x = x
}
val objectAnimator = ObjectAnimator.ofFloat(
centerText!!, "alpha", 1f
)
positionAnimator.duration = 200
val animatorSet = AnimatorSet()
animatorSet.playTogether(objectAnimator, positionAnimator)
animatorSet.start()
}
}
any help would be greatly appreciated
Thanks!!
I use MaterialAutoCompleteTextView, when i add new item / items and call notifyDataSetChanged(), dropdown automatically closes
How to fix this
VIDEO:
https://drive.google.com/file/d/1AYWW-_HcIpefrn0RcPMx2EsLLoyB2TtY/view?usp=sharing
CODE:
class MainActivity : AppCompatActivity() {
private lateinit var materialAutoComplete: MaterialAutoCompleteTextView
private lateinit var adapter: CustomArrayAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
materialAutoComplete = findViewById(R.id.materialAutoComplete_main)
setupMaterialAutoComplete()
}
private var number = 0
private fun setupMaterialAutoComplete() {
val items = mutableListOf("Shop", "Shop", "Shop", "Shop")
adapter = CustomArrayAdapter(
this,
R.layout.item_goods_material_auto_complete,
items
)
materialAutoComplete.setAdapter(adapter)
object : CountDownTimer(3000, 1000) {
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
number += 1
adapter.addAll(mutableListOf("$number", "${number + 1}"))
}
}.start()
}
}
Just override this methods
class MaterialAutoCompleteTextViewWithoutAutoCloseDropdown : MaterialAutoCompleteTextView {
constructor(context: Context) : super(context)
constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)
constructor(
context: Context, attributeSet: AttributeSet?, defStyleAttr: Int,
) : super(context, attributeSet, defStyleAttr)
override fun dismissDropDown() {
}
override fun onKeyPreIme(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing) {
if (event.action == KeyEvent.ACTION_DOWN && event.repeatCount == 0) {
val state = keyDispatcherState
state?.startTracking(event, this)
return true
} else if (event.action == KeyEvent.ACTION_UP) {
val state = keyDispatcherState
state?.handleUpEvent(event)
if (event.isTracking && !event.isCanceled) {
super.dismissDropDown()
return true
}
}
}
return super.onKeyPreIme(keyCode, event)
}
override fun replaceText(text: CharSequence?) {
clearComposingText()
setText(text)
val spannable = getText()
Selection.setSelection(spannable, spannable.length)
super.dismissDropDown()
}
}
I am getting a runtime error: No Value passed for parameter 'soundToInit'in my SecondAdapter class.
My SecondAdapter is an Adapter to a RecyclerView declared in SecondActivity.
SecondAdapter class:
class SecondAdapter(val content:Array<String>) : RecyclerView.Adapter<SecondCustomViewGolder>(){
//var lessons = arrayOf("Satu", "Dua", "Tiga", "Empat", "Lima", "Enam", "Tujuh",
// "Lapan", "Sembilan")
var soundList = arrayOf(R.raw.sound1, R.raw.sound2,R.raw.sound1, R.raw.sound2,
R.raw.sound1, R.raw.sound2,R.raw.sound1, R.raw.sound2,
R.raw.sound1)
override fun getItemCount(): Int {
return content.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SecondCustomViewGolder {
var layoutInflater = LayoutInflater.from(parent.context)
var cellForRow = layoutInflater.inflate(R.layout.lesson_row, parent, false)
return SecondCustomViewGolder(cellForRow)
}
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
var soundToLoad = SoundEngine()
holder.soundToInit = soundToLoad.load(SecondViewActivity(), soundList.get(position), 1)
}
}
class SecondCustomViewGolder(var viewTwo : View, var soundToInit:Int) : RecyclerView.ViewHolder(viewTwo){
init {
var soundToPlay = SoundEngine()
soundToPlay.play(soundToInit, 1F, 1F,1,0, 1F)
}
}
SoundEngine:
class SoundEngine {
private var soundPool: SoundPool
init {
soundPool = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
val audioAttributes = AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
.setUsage(AudioAttributes.USAGE_GAME)
.build()
SoundPool.Builder()
.setMaxStreams(1)
.setAudioAttributes(audioAttributes)
.build()
} else {
SoundPool(1, AudioManager.STREAM_MUSIC, 0)
}
}
fun load(context: Context, rawId: Int, priority: Int):Int {
return soundPool.load(context, rawId, priority)
}
fun play(soundID: Int, leftVolume: Float, rightVolume: Float, priority: Int, loop: Int, rate: Float) {
soundPool.play(soundID, leftVolume, rightVolume, priority, loop, rate)
}
}
Please give me some pointers... Thank in advance...
Change your Holder like below
class SecondCustomViewGolder(var viewTwo : View) : RecyclerView.ViewHolder(viewTwo) {
private var soundEngine = SoundEngine()
fun loadAndPlaySound(soundIdToPlay:Int, priority: Int) {
val soundToPlay = soundEngine.load(viewTwo.context, soundIdToPlay, priority)
soundEngine.play(soundToPlay, 1F, 1F, 1, 0, 1F)
}
}
And call it like below:
override fun onBindViewHolder(holder: SecondCustomViewGolder, position: Int) {
holder.loadAndPlaySound(soundList.get(position), 1)
}
I have a custom EditText providig PinEntry functionality looking like this:
class PinEntryView : EditText {
private var mSpace = 15f
private var mCharSize = 0f
private var mNumChars = 4f
private var mLineSpacing = 8f
private val XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"
private var mClickListener: View.OnClickListener? = null
private var mLineStroke = 1f
private var mLinesPaint: Paint? = null
private var mOnPinEnteredListener: OnPinEnteredListener? = null
private fun updateColorForLines(next: Boolean) {
if (isFocused) {
mLinesPaint!!.color = getColor(context, android.R.color.transparent)
if (next) {
mLinesPaint!!.color = getColor(context, R.color.edit_text_background)
}
} else {
mLinesPaint!!.color = getColor(context, android.R.color.transparent)
}
}
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(
context: Context, attrs: AttributeSet,
defStyleAttr: Int
) : super(context, attrs, defStyleAttr) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet) {
val multi = context.resources.displayMetrics.density
mLineStroke *= multi
mLinesPaint = Paint(paint)
mLinesPaint!!.strokeWidth = mLineStroke
mSpace *= multi
mLineSpacing *= multi
val mMaxLength = attrs.getAttributeIntValue(
XML_NAMESPACE_ANDROID,
"maxLength",
6
)
paint.color = getColor(context, android.R.color.white)
mNumChars = mMaxLength.toFloat()
super.setCustomSelectionActionModeCallback(
object : ActionMode.Callback {
override fun onPrepareActionMode(
mode: ActionMode,
menu: Menu
): Boolean {
return false
}
override fun onDestroyActionMode(mode: ActionMode) {}
override fun onCreateActionMode(
mode: ActionMode,
menu: Menu
): Boolean {
return false
}
override fun onActionItemClicked(
mode: ActionMode,
item: MenuItem
): Boolean {
return false
}
})
super.setOnClickListener { v ->
setSelection(text.length)
if (mClickListener != null) {
mClickListener!!.onClick(v)
}
}
}
override fun setOnClickListener(l: OnClickListener) {
mClickListener = l
}
override fun onDraw(canvas: Canvas) {
val availableWidth = width - paddingRight - paddingLeft
mCharSize = if (mSpace < 0) {
(availableWidth / (mNumChars * 2 - 1))
} else {
(availableWidth - mSpace * (mNumChars - 1)) / mNumChars
}
var startX = paddingLeft.toFloat()
val bottom = height.toFloat() - paddingBottom.toFloat()
val text = text
val textLength = text.length
val textWidths = FloatArray(textLength)
paint.getTextWidths(getText(), 0, textLength, textWidths)
for (i in 0 until mNumChars.toInt()) {
updateColorForLines(i == textLength)
canvas.drawRoundRect(
startX, 0f, startX + mCharSize, height.toFloat(), 5f, 5f, mLinesPaint
)
if (text.length > i) {
val middle = startX + mCharSize / 2
canvas.drawText(
"******",
i,
i + 1,
middle - textWidths[0] / 2,
bottom - mLineSpacing,
paint
)
}
startX += if (mSpace < 0) {
mCharSize * 2
} else {
mCharSize + mSpace
}
}
}
override fun onTextChanged(text: CharSequence, start: Int, lengthBefore: Int, lengthAfter: Int) {
if (mOnPinEnteredListener != null && text.length.toFloat() == mNumChars) {
mOnPinEnteredListener!!.onPinEntered(text)
}
}
fun setOnPinEnteredListener(l: OnPinEnteredListener) {
mOnPinEnteredListener = l
}
interface OnPinEnteredListener {
fun onPinEntered(str: CharSequence)
}
}
I followed this tutorial: https://medium.com/#ali.muzaffar/building-a-pinentryedittext-in-android-5f2eddcae5d3
And I am getting behavior showed on picture:
But I would like to have a behavior as shown here:
How can I achieve that? How can I add those default dots/symbols? I tried adding it as a hint or default text, but it does not work because of my onDraw function
Hopefully you found already your solution. But here is how I would've done it:
The PinEntryEditText has an attribute you can set in the .xml file.
Adding this to the element will do the trick: app:pinSingleCharHint="•"
For more customisation check out the sample xml from this page
This question already has answers here:
All Editexts be replace by same value after pop a Fragment
(4 answers)
Closed 4 years ago.
I have a custom view with an edittext and an imageview. I use this view multiple times in the same layout. It's behaving very strange though. Whenever I rotate the device the value from the last declared view in the xml is put into all the other views in the layout and I just can't figure out where things go wrong.
My view:
class InputView(context: Context, attrs: AttributeSet?, #AttrRes defStyleAttr: Int) : ConstraintLayout(context, attrs, defStyleAttr) {
private var textInputLayout: TextInputLayout
private var textView: TextInputEditText
private var imageView: ImageView
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
init {
val view = LayoutInflater.from(context).inflate(R.layout.custom_inputview, this, true)
textInputLayout = view.custom_inputview_text_input_layout
textView = view.custom_inputview_text_input_edit_text
imageView = view.custom_inputview_image_view
attrs.let {
context.theme.obtainStyledAttributes(
it,
R.styleable.InputView,
defStyleAttr, 0).apply {
try {
textView.textSize = getDimension(R.styleable.InputView_android_textSize, 16.0f)
textView.text = SpannableStringBuilder(getString(R.styleable.InputView_android_text) ?: "")
textInputLayout.hint = getText(R.styleable.InputView_android_hint)
if (getDrawable(R.styleable.InputView_android_src) == null) {
imageView.isVisible = false
} else {
imageView.setImageDrawable(getDrawable(R.styleable.InputView_android_src))
imageView.setColorFilter(getColorOrThrow(R.styleable.InputView_android_tint))
}
textView.maxLines = getInteger(R.styleable.InputView_android_maxLines, 1)
textView.minLines = getInteger(R.styleable.InputView_android_minLines, 0)
textView.setLines(getInteger(R.styleable.InputView_android_lines, 1))
textView.inputType = getInteger(R.styleable.InputView_android_inputType, EditorInfo.IME_NULL)
textView.setCompoundDrawablesWithIntrinsicBounds(
getDrawable(R.styleable.InputView_android_drawableStart),
getDrawable(R.styleable.InputView_android_drawableTop),
getDrawable(R.styleable.InputView_android_drawableEnd),
getDrawable(R.styleable.InputView_android_drawableBottom))
} finally {
recycle()
}
}
}
}
override fun onSaveInstanceState(): Parcelable {
// 1
val bundle = Bundle()
// 2
bundle.putString("text", textView.text.toString())
// 3
Log.d("InputView", "Saving state text: ${textView.text.toString()}")
bundle.putParcelable("superState", super.onSaveInstanceState())
return bundle
}
override fun onRestoreInstanceState(state: Parcelable) {
// 4
var viewState = state
if (viewState is Bundle) {
// 5
Log.d("InputView", "Textview text: ${viewState.getString("text")}")
textView.text = SpannableStringBuilder(viewState.getString("text"))
// 6
viewState = viewState.getParcelable("superState")
}
super.onRestoreInstanceState(viewState)
}
}
I've tried overriding onSaveIntanceState() and onRestoreInstanceState(), but it doesn't change anything.
Screenshot before rotation:
Screenshot after rotation:
Added this to my custom view and the problem was gone:
class InputView(context: Context, attrs: AttributeSet?, #AttrRes defStyleAttr: Int) : ConstraintLayout(context, attrs, defStyleAttr) {
...
#Suppress("UNCHECKED_CAST")
public override fun onSaveInstanceState(): Parcelable? {
val superState = super.onSaveInstanceState()
val ss = SavedState(superState)
ss.childrenStates = SparseArray()
for (i in 0 until childCount) {
getChildAt(i).saveHierarchyState(ss.childrenStates as SparseArray<Parcelable>)
}
return ss
}
#Suppress("UNCHECKED_CAST")
public override fun onRestoreInstanceState(state: Parcelable) {
val ss = state as SavedState
super.onRestoreInstanceState(ss.superState)
for (i in 0 until childCount) {
getChildAt(i).restoreHierarchyState(ss.childrenStates as SparseArray<Parcelable>)
}
}
override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
dispatchFreezeSelfOnly(container)
}
override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
dispatchThawSelfOnly(container)
}
class SavedState(superState: Parcelable?) : BaseSavedState(superState) {
var childrenStates: SparseArray<Any>? = null
override fun writeToParcel(out: Parcel, flags: Int) {
super.writeToParcel(out, flags)
childrenStates?.let {
out.writeSparseArray(it)
}
}
}
}