From 720d20e1be987fc263c56ab7105605679f61c9e1 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Fri, 9 Oct 2020 15:20:46 +0300 Subject: [PATCH] Disable animator in edit mode (#947) --- CHANGELOG.md | 14 + .../anytypeio/anytype/ui/page/PageFragment.kt | 7 +- .../editor/holders/interface/TextHolder.kt | 11 +- sample/src/main/AndroidManifest.xml | 14 +- .../sample/DisabledAnimationActivity.kt | 266 ++++++++++++++++++ .../anytype/sample/adapter/AbstractAdapter.kt | 16 ++ .../anytype/sample/adapter/AbstractHolder.kt | 8 + .../layout/activity_disabled_animation.xml | 54 ++++ sample/src/main/res/layout/item_editable.xml | 12 + sample/src/main/res/values/styles.xml | 2 +- 10 files changed, 388 insertions(+), 16 deletions(-) create mode 100644 sample/src/main/java/com/anytypeio/anytype/sample/DisabledAnimationActivity.kt create mode 100644 sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractAdapter.kt create mode 100644 sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractHolder.kt create mode 100644 sample/src/main/res/layout/activity_disabled_animation.xml create mode 100644 sample/src/main/res/layout/item_editable.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a654706c6..ed499e3616 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,19 @@ # Change log for Android @Anytype app. +## Version 0.1.1 (WIP) + +### New features & enhancements 🚀 + +* Disable animation for edit-mode in order to increase editor performance (#884) + +### Design & UX 🔳 + +* + +### Fixes & tech 🚒 + +* + ## Version 0.1.0 ### New features 🚀 diff --git a/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt b/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt index 3abf557661..ce459a03d1 100644 --- a/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt +++ b/app/src/main/java/com/anytypeio/anytype/ui/page/PageFragment.kt @@ -29,6 +29,7 @@ import androidx.core.view.updateLayoutParams import androidx.core.view.updatePadding import androidx.lifecycle.ViewModelProviders import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.DefaultItemAnimator import androidx.recyclerview.widget.LinearLayoutManager import androidx.transition.ChangeBounds import androidx.transition.Fade @@ -370,7 +371,7 @@ open class PageFragment : recycler.apply { layoutManager = LinearLayoutManager(requireContext()) setHasFixedSize(true) - //itemAnimator = null + itemAnimator = null adapter = pageAdapter addOnScrollListener(titleVisibilityDetector) } @@ -824,7 +825,7 @@ open class PageFragment : } bottomMenu.update(count) if (!bottomMenu.isShowing) { - //recycler.apply { itemAnimator = DefaultItemAnimator() } + recycler.apply { itemAnimator = DefaultItemAnimator() } hideSoftInput() Timber.d("Hiding top menu") topToolbar.invisible() @@ -835,7 +836,7 @@ open class PageFragment : } } } else { - //recycler.apply { itemAnimator = null } + recycler.apply { itemAnimator = null } bottomMenu.hideWithAnimation() hideSelectButton() } diff --git a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/interface/TextHolder.kt b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/interface/TextHolder.kt index 9efeb5badc..8352ddb790 100644 --- a/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/interface/TextHolder.kt +++ b/core-ui/src/main/java/com/anytypeio/anytype/core_ui/features/editor/holders/interface/TextHolder.kt @@ -1,6 +1,5 @@ package com.anytypeio.anytype.core_ui.features.editor.holders.`interface` -import android.text.Editable import android.view.Gravity import android.view.View import android.view.inputmethod.InputMethodManager @@ -74,10 +73,11 @@ interface TextHolder { } fun setFocus(item: Focusable) { - if (item.isFocused) + if (item.isFocused) { focus() - else + } else { content.clearFocus() + } } fun focus() { @@ -86,12 +86,13 @@ interface TextHolder { post { if (!hasFocus()) { if (requestFocus()) { - context.imm().showSoftInput(this, InputMethodManager.SHOW_IMPLICIT) + context.imm().showSoftInput(this, InputMethodManager.SHOW_FORCED) } else { Timber.d("Couldn't gain focus") } - } else + } else { Timber.d("Already had focus") + } } } } diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml index ea2657e825..f83febf038 100644 --- a/sample/src/main/AndroidManifest.xml +++ b/sample/src/main/AndroidManifest.xml @@ -9,27 +9,27 @@ - - + - - + + + diff --git a/sample/src/main/java/com/anytypeio/anytype/sample/DisabledAnimationActivity.kt b/sample/src/main/java/com/anytypeio/anytype/sample/DisabledAnimationActivity.kt new file mode 100644 index 0000000000..892750881e --- /dev/null +++ b/sample/src/main/java/com/anytypeio/anytype/sample/DisabledAnimationActivity.kt @@ -0,0 +1,266 @@ +package com.anytypeio.anytype.sample + +import android.content.Context +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager +import android.widget.EditText +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.anytypeio.anytype.core_utils.ext.imm +import com.anytypeio.anytype.core_utils.ui.ViewType +import com.anytypeio.anytype.sample.adapter.AbstractAdapter +import com.anytypeio.anytype.sample.adapter.AbstractHolder +import kotlinx.android.synthetic.main.activity_disabled_animation.* +import kotlinx.android.synthetic.main.item_editable.view.* +import timber.log.Timber + +class DisabledAnimationActivity : AppCompatActivity(R.layout.activity_disabled_animation) { + + private val start: List + get() = mutableListOf( + Mock( + id = 0, + text = "TITLE", + type = 0, + isFocused = false + ), + Mock( + id = 0, + text = "BULLETED", + type = 0, + isFocused = true + ), + Mock( + id = 1, + text = "PARAGRAPH 2", + type = 0, + isFocused = false + ) + ) + + private val end: List + get() = listOf( + Mock( + id = 0, + text = "TITLE", + type = 0, + isFocused = false + ), + Mock( + id = 0, + text = "PARAGRAPH", + type = 1, + isFocused = true + ), + Mock( + id = 1, + text = "PARAGRAPH 2", + type = 0, + isFocused = false + ) + ) + + private val mockAdapter = MockAdapter( + items = start.toMutableList() + ) + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + recycler.apply { + layoutManager = CustomLinearLayoutManager(context) + adapter = mockAdapter + itemAnimator = null + } + startButton.setOnClickListener { + Timber.d("Start button clicked") + mockAdapter.update( + update = end + ) + } + + endButton.setOnClickListener { + Timber.d("End button clicked") + mockAdapter.update( + update = start + ) + } + } + + class MockAdapter(val items: MutableList) : AbstractAdapter(items) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractHolder { + return when (viewType) { + 0 -> { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.item_editable, parent, false) + MockHolder(view) + } + 1 -> { + val inflater = LayoutInflater.from(parent.context) + val view = inflater.inflate(R.layout.item_editable, parent, false) + MockHolder2(view) + } + else -> throw IllegalStateException() + } + } + + override fun getItemViewType(position: Int): Int { + return items[position].getViewType() + } + + override fun update(update: List) { + val old = ArrayList(items) + val cb = Differ(old = old, new = update) + val result = DiffUtil.calculateDiff(cb) + items.clear() + items.addAll(update) + result.dispatchUpdatesTo(this) + } + } + + class MockHolder(view: View) : AbstractHolder(view) { + + override fun bind(item: Mock) { + Timber.d("Binding item: $item") + itemView.input.setText(item.text) + if (item.isFocused) + focus() + else + itemView.input.clearFocus() + } + + private fun focus() { + itemView.input.apply { + post { + if (!hasFocus()) { + if (requestFocus()) { + context.imm().showSoftInput(this, InputMethodManager.SHOW_FORCED) + } else { + Timber.d("Couldn't gain focus") + } + } else + Timber.d("Already had focus") + } + } + } + } + + class MockHolder2(view: View) : AbstractHolder(view) { + + override fun bind(item: Mock) { + Timber.d("Binding item: $item") + itemView.input.setText(item.text) + itemView.input.setTextColor(Color.GREEN) + if (item.isFocused) focus() + } + + private fun focus() { + itemView.input.apply { + post { + Timber.d("Focusing!") + if (!hasFocus()) { + if (requestFocus()) { + context.imm().showSoftInput(this, InputMethodManager.SHOW_FORCED) + } else { + Timber.d("Couldn't gain focus") + } + } else + Timber.d("Already had focus") + } + } + } + } + + data class Mock( + val id: Int, + val text: String, + val isFocused: Boolean, + val type: Int + ) : ViewType { + override fun getViewType(): Int = type + } + + class Differ( + private val old: List, + private val new: List + ) : DiffUtil.Callback() { + + override fun getOldListSize(): Int = old.size + override fun getNewListSize(): Int = new.size + + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = old[oldItemPosition] + val newItem = new[newItemPosition] + Timber.d("areItemsTheSame for: $oldItem \n and \n $newItem") + return oldItem.id == newItem.id + } + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { + val oldItem = old[oldItemPosition] + val newItem = new[newItemPosition] + Timber.d("areContentsTheSame for: $oldItem \n and \n $newItem") + return oldItem == newItem + } + } + + class CustomLinearLayoutManager( + context: Context, + //focus: View + ) : LinearLayoutManager(context) { + + override fun onInterceptFocusSearch(focused: View, direction: Int): View? { + Timber.d("OnInterceptFocusSearch view with text: ${(focused as EditText).text}") + when (direction) { + View.FOCUS_UP -> { + Timber.d("OnInterceptFocusSearch direction: FOCUS_UP") + } + View.FOCUS_DOWN -> { + Timber.d("OnInterceptFocusSearch direction: FOCUS_DOWN") + } + } + //return super.onInterceptFocusSearch(focused, direction) + + //val v = getChildAt(2)?.findViewById(R.id.input) + + //v?.requestFocus() + + //Timber.d("At position 2 there is a view with text: ${(v as EditText).text}") + + return null + } + + override fun removeView(child: View?) { + Timber.d("On remove view") + super.removeView(child) + } + + override fun detachView(child: View) { + Timber.d("On detach view") + super.detachView(child) + } + + override fun onFocusSearchFailed( + focused: View, + focusDirection: Int, + recycler: RecyclerView.Recycler, + state: RecyclerView.State + ): View? { + Timber.d("onFocusSearchFailed for view with text: ${(focused as EditText).text}") + when (focusDirection) { + View.FOCUS_UP -> { + Timber.d("onFocusSearchFailed direction: FOCUS_UP") + } + View.FOCUS_DOWN -> { + Timber.d("onFocusSearchFailed direction: FOCUS_DOWN") + } + } + return super.onFocusSearchFailed(focused, focusDirection, recycler, state) + } + } +} diff --git a/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractAdapter.kt b/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractAdapter.kt new file mode 100644 index 0000000000..136b5da446 --- /dev/null +++ b/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractAdapter.kt @@ -0,0 +1,16 @@ +package com.anytypeio.anytype.sample.adapter + +import androidx.recyclerview.widget.RecyclerView + +abstract class AbstractAdapter( + private var items: List +) : RecyclerView.Adapter>() { + + override fun getItemCount(): Int = items.size + + override fun onBindViewHolder(holder: AbstractHolder, position: Int) { + holder.bind(items[position]) + } + + abstract fun update(update: List) +} \ No newline at end of file diff --git a/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractHolder.kt b/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractHolder.kt new file mode 100644 index 0000000000..711bbe37de --- /dev/null +++ b/sample/src/main/java/com/anytypeio/anytype/sample/adapter/AbstractHolder.kt @@ -0,0 +1,8 @@ +package com.anytypeio.anytype.sample.adapter + +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +abstract class AbstractHolder(view: View) : RecyclerView.ViewHolder(view) { + abstract fun bind(item: T) +} \ No newline at end of file diff --git a/sample/src/main/res/layout/activity_disabled_animation.xml b/sample/src/main/res/layout/activity_disabled_animation.xml new file mode 100644 index 0000000000..e7b0b87b91 --- /dev/null +++ b/sample/src/main/res/layout/activity_disabled_animation.xml @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/layout/item_editable.xml b/sample/src/main/res/layout/item_editable.xml new file mode 100644 index 0000000000..40eabe1498 --- /dev/null +++ b/sample/src/main/res/layout/item_editable.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml index 2215fe243b..cfa9b62f4a 100644 --- a/sample/src/main/res/values/styles.xml +++ b/sample/src/main/res/values/styles.xml @@ -5,7 +5,7 @@ @color/orange @color/anytype_text_teal - @color/anytype_text_blue + @color/orange @style/DialogTheme