I have a BottomSheet which houses a product detail card. The problem is, when I click on the + or - button on the product detail while the bottom sheet is in it's Expanded state, it jumps down.
When it is down and I click on the buttons it doesn't jump, it only happens when it is in it's Expanded (completely up) state
I have attached a GIF to show what is exactly happening
Here is the code
scan_sheet.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:id="#+id/bottom_sheet"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:animateLayoutChanges="false"
android:background="#drawable/bottom_sheet_dialog_fragment"
android:orientation="vertical"
app:behavior_hideable="true"
app:behavior_peekHeight="100dp"
app:layout_behavior="studio.monotype.storedemo.BottomSheetBehavior">
<include
layout="#layout/hero_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/divider_view"
android:layout_width="match_parent"
android:layout_height="4dp"
android:layout_marginStart="24dp"
android:layout_marginTop="44dp"
android:layout_marginEnd="24dp"
android:background="#color/colorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/hero_item" />
<include
layout="#layout/related_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider_view"
tools:layout_editor_absoluteX="0dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
ScanActivity.kt (simplified to show only what is essential)
class ScanActivity : AppCompatActivity() {
private lateinit var bottomSheet: BottomSheetBehavior<*>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_scan)
setupBottomSheet()
showSheet()
}
private fun setupBottomSheet() {
bottomSheet = BottomSheetBehavior.from(bottom_sheet)
bottomSheet.isHideable = true
bottomSheet.skipCollapsed= true
bottomSheet.isDraggable = true
bottomSheet.state = BottomSheetBehavior.STATE_HIDDEN
bottomSheet.addBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback {
override fun onSlide(bottomSheet: View, slideOffset: Float) {
}
#SuppressLint("SwitchIntDef")
override fun onStateChanged(sheet: View, newState: Int) {
when (newState) {
BottomSheetBehavior.STATE_HIDDEN -> {
codeScanner.startPreview()
}
}
}
})
plus_btn.setOnClickListener {
var qty= qty_tv.text.toString().toInt()
qty++
qty_tv.text =qty.toString()
}
minus_btn.setOnClickListener {
var qty= qty_tv.text.toString().toInt()
if(qty!=0)
{
qty--
}
qty_tv.text =qty.toString()
}
}
private fun showSheet() {
bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
}
}
it seems that google engineer gave correct answer
Seems like something is going on because you are setting
android:layout_gravity="bottom" on the view with the
BottomSheetBehavior. You should remove that line.
It helped on my case
Looks to me like that could be a bug in the BottomSheetBehavior? Seems like the height of the sheet isn't getting saved or restored correctly. After the button is pressed, a layout happens again which changes the height. Could you file a bug at https://issuetracker.google.com/issues/new?component=439535
Related
everyone. I have a BottomnNavigationsBar and a FrameLayout inside my activity_main.xml file. In the frame I display a ViewPager within a Fragment. The navigation contains 3 elements. If I click on one, the clicked status does not change. The respective fragment is displayed correctly, but the icon does not change in color and size.
What am I doing wrong or what do I have to change?
Here is my MainActivity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewBinding = ActivityMainBinding.inflate(layoutInflater)
setContentView(viewBinding.root)
setFragementTo(HomeFragment.newInstance("",""))
viewBinding.bottomNavigationView.setOnItemSelectedListener{
when(it.itemId)
{
R.id.home ->{
setFragementTo(HomeFragment.newInstance("",""))
}
R.id.neu ->{
setFragementTo(NewFragment.newInstance("",""))
}
R.id.settings ->{
setFragementTo(FilterFragment.newInstance("",""))
}
}
false
}
}
private fun setFragementTo(fragment: Fragment) {
supportFragmentManager.beginTransaction().replace(R.id.myFrameInMain,fragment).commit()
}
activity_main.xml:
<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">
<FrameLayout
android:id="#+id/myFrameInMain"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/bottomNavigationView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"></FrameLayout>
<com.google.android.material.bottomnavigation.BottomNavigationView
android:background="#drawable/bottom_nav_style"
app:itemRippleColor="#color/white"
app:itemTextColor="#color/item_bottom_nav"
app:itemIconTint="#color/item_bottom_nav"
android:id="#+id/bottomNavigationView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_nav"/>
</androidx.constraintlayout.widget.ConstraintLayout>
Here I click on the middle icon in the navigation. The fragment appears correctly, but the icon in the navigation does not
And here the filter icon:
I think I got it. I had to change the parameter from false to true in the setOnItemSelectedListener method
I have a problem:
I have a TextView whose content is constantly changing via LiveData. This looks all right when the animation isn't executing, but when the MotionLayout starts executing, my text gets blocked a little bit.
And the constraints of my TextView and Button are packed.
activity:
class ErrorActivity : AppCompatActivity() {
private val mViewModel by viewModels<ErrorViewModel>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DataBindingUtil.setContentView<ActivityErrorBinding>(this, R.layout.activity_error).apply {
lifecycleOwner = this#ErrorActivity
viewModel = mViewModel
}
}
fun startStopAnim(v: View) {
mViewModel.anim()
}
}
activity layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:binding="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.test.test.ErrorViewModel" />
</data>
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="#xml/scene"
binding:anim="#{viewModel.mAnim}">
<TextView
android:id="#+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{#string/test(viewModel.mCountDown)}"
android:textSize="32sp"
app:layout_constraintEnd_toStartOf="#+id/btn"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="#string/test" />
<Button
android:id="#+id/btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/tv"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/anim"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="startStopAnim"
android:text="startOrStop"
android:textAllCaps="false"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</layout>
viewModel:
class ErrorViewModel : ViewModel() {
private val _countDown: MutableLiveData<String> = MutableLiveData()
val mCountDown: LiveData<String> = _countDown
private val _anim: MutableLiveData<Boolean> = MutableLiveData()
val mAnim: LiveData<Boolean> = _anim
private var count = 0
private var state = false
init {
viewModelScope.launch(Dispatchers.IO) {
while (true) {
delay(1000)
_countDown.postValue(count++.toString())
}
}
}
fun anim() {
state = !state
_anim.value = state
}
}
#BindingAdapter("anim")
fun anim(layout: MotionLayout, play: Boolean?) {
play?.let {
if (it) {
layout.transitionToEnd()
} else {
layout.transitionToStart()
}
}
}
motionScene:
For simplicity, Scene has only one duration
<?xml version="1.0" encoding="utf-8"?>
<MotionScene xmlns:app="http://schemas.android.com/apk/res-auto">
<Transition
app:constraintSetEnd="#+id/end"
app:constraintSetStart="#+id/start"
app:duration="2000" />
</MotionScene>
gif
As you can see in the gif, when there is no click, everything works fine for the numbers, but when I click the button, a 2 second animation starts, during which time my text doesn't display properly.
Of course, this is just a demo example. In a real scene, not only the text is not displayed completely, but even the TextView And ImageView are misplaced, and once the animation is executed, it cannot be recovered.
Can someone help me...
Actually in a real scene, the parent layout (B) of the problematic TextView (A) would perform a displacement animation. As long as this animation is executed once, the constraint relationship of TextView A will definitely have problems and cannot be restored (onResume can be restored after onPause is currently found)
By design during animation MotionLayout does not honor requestLayout.
Because in the majority of applications it is not needed and would have a significant impact on performance.
To enable it in the transition add
<Transition ... motion:layoutDuringTransition="honorRequest" \>
I created a small practice app which changes the image on-screen when the buttonNext is clicked. However, neither the image nor button does anything. Everything in my MainActivity looks normal for an onClickListener setup and I followed various guides to create a layout which works. But, I'm not sure why it looks so weird and does not work in the emulator.
Here is the MainActivity code I wrote so far:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setListener()
}
private fun changeImage(view: View) {
when (view.id) {
R.id.goodMorning -> view.setBackgroundResource(R.drawable.gm1)
R.id.goodMorning -> view.setBackgroundResource(R.drawable.gm2)
else -> view.setBackgroundColor(Color.LTGRAY)
}
}
private fun setListener() {
val goodMorning = findViewById<ImageView>(R.id.goodMorning)
val buttonNext = findViewById<TextView>(R.id.buttonNext)
fun setListener(){
val clickableViews: List<View> =
listOf(goodMorning, buttonNext)
for (item in clickableViews){
item.setOnClickListener() { changeImage(it) }
}
}
}
}
This is the layout xml:
<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">
<ImageView
android:id="#+id/goodMorning"
android:layout_width="404dp"
android:layout_height="562dp"
android:contentDescription="#android:string/no"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.428"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.365"
tools:srcCompat="#drawable/gm1" />
<Button
android:id="#+id/buttonNext"
style="#style/twoButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="#string/buttonNext"
android:textSize="#dimen/box_text_size"
app:layout_constraintBaseline_toBaselineOf="#+id/buttonBack"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/buttonBack" />
<Button
android:id="#+id/buttonDownload"
style="#style/twoButtons"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:layout_marginEnd="16dp"
android:layout_marginRight="16dp"
android:text="#string/buttonDownload"
android:textSize="#dimen/box_text_size"
app:layout_constraintBaseline_toBaselineOf="#+id/buttonBack"
app:layout_constraintEnd_toStartOf="#+id/buttonBack"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/buttonBack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:text="#string/buttonBack"
android:textColor="#android:color/black"
android:textSize="#dimen/box_text_size"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/goodMorning"
app:layout_constraintVertical_bias="0.718" />
This is what the app looks like in Pixel 2:
Any help is greatly appreciated!
change the code like this
private fun changeImage(view: View) {
val goodMorning = findViewById<ImageView>(R.id.goodMorning)
when (view.id) {
R.id.buttonBack -> view.setBackgroundResource(R.drawable.gm1)
R.id.buttonNext -> view.setBackgroundResource(R.drawable.gm2)
else -> goodMorning.setBackgroundColor(Color.LTGRAY)
}
}
private fun setListener() {
val buttonNext = findViewById<TextView>(R.id.buttonNext)
val buttonBack = findViewById<TextView>(R.id.buttonBack)
val clickableViews: List<View> =
listOf(buttonBack, buttonNext)
for (item in clickableViews){
item.setOnClickListener() { changeImage(it) }
}
}
I found out how to fix the issue:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val nextButton: Button = findViewById(R.id.buttonNext)
nextButton.setOnClickListener() { changeImage() }
}
private fun changeImage() {
// Shift through all available images
val imageOptions: ImageView = findViewById(R.id.morningImages)
val drawableResources = when (imageOptions) {
buttonNext -> R.drawable.gm1
else -> R.drawable.gm2
}
imageOptions.setImageResource(drawableResources)
// Repeat function needed
}
}
You are better off creating the clickListener at the beginning with onCreate because you will not have to create another private function which can make this issue more confusing than it really is.
Create a new variable within the changeImage function to find the ImageView. After that, make another variable called to create a when block to shift through the images available and end it with an else statement to avoid creating an error.
Set the imageResource to drawableResources so the images appear on-screen when requested.
Note: a repeat function is needed to shift through the images again once the user goes through all of them.
I'm using the latest Nav Component (2.2.0-alpha01) in my project, and am presented with an issue I can't seem to resolve.
I have a splash animation - nothing serious, a custom background stretching to the whole screen, and a logo in the very middle, using ConstraintLayout. During the initial sync, I animate a custom animated VectorDrawable (let's call it #drawable/logo_animated), which uses the common #drawable/logo as a source, and applies animations to its groups.
To time the animation right, I created the following helper function:
fun ImageView.setRepeatingAnimatedVector(
#DrawableRes animationRes: Int,
delayMs: Long = 0,
startDelayMs: Long = 0,
shouldRunOptional: () -> Boolean = { false },
optionalRunnable: () -> Unit = {}
) {
val anim = AnimatedVectorDrawableCompat.create(context, animationRes)?.apply {
registerAnimationCallback(object : Animatable2Compat.AnimationCallback() {
override fun onAnimationEnd(drawable: Drawable?) {
this#setRepeatingAnimatedVector.postDelayed({ if (shouldRunOptional()) optionalRunnable() else start() }, delayMs)
}
})
}
setImageDrawable(anim)
postDelayed({ anim?.start() }, startDelayMs)
}
It takes an AnimatedVectorDrawable as an input, and applies it to the ImageView. Upon finishing the animation cycle, a check in the form of a lambda (shouldRunOptional) is ran. If it returns true, the optionalRunnable lambda is launched, otherwise it repeats the animation.
With this, I can wait for the ViewModel to finish syncing, and then await the end of the animation to move between fragments without any weirdness. The animation itself is short (~900ms), so at most the user will be delayed by a second.
I also use a custom NavigationManager composition for navigation. The Manager itself is an interface (INavigationManager) of generic calls (such as splashToLanding() or openDetail(id: UUID)) that gets injected into ViewModels, with an extra interface taking care of the NavComponent specific bits:
IFragmentNavigator.kt
interface IFragmentNavigator {
val command: SingleLiveEvent<NavigationCommand>
var splashLandingExtras: Navigator.Extras?
fun setSplashLandingTransition(extras: Navigator.Extras) {
splashLandingExtras = extras
}
fun back() {
navigate(NavigationCommand.Back)
}
fun navigate(direction: NavDirections) {
navigate(NavigationCommand.To(direction))
}
fun navigate(navCommand: NavigationCommand) {
command.postValue(navCommand)
}
}
The implementation just takes care of property initializations, and then later on used in the following manner:
class FragmentNavigationManager:
INavigationManager, IFragmentNavigator by FragmentNavigator() { [...] }
The command property of this interface is then later used in the Fragments, via Observers:
open val navigationObserver = Observer<NavigationCommand> {
when(it) {
is NavigationCommand.To -> findNavController().navigate(it.directions)
is NavigationCommand.Back -> findNavController().popBackStack()
is NavigationCommand.BackTo -> findNavController().popBackStack(it.destinationId, false)
is NavigationCommand.ToRoot -> TODO()
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
navigator.command.observe(this, navigationObserver)
}
The Directions instance created in the FragmentNavigationManager is used by the NavController directly. I made sure to add the FragmentNavigator's Extras field to the actual navigation call:
override fun splashToLanding() {
navigate(
NavigationCommand.To(
SplashFragmentDirections.actionSplashFragmentToLandingFragment(),
null, null, splashLandingExtras
)
)
}
And of course in the SplashFragment, I assign the appropriate view to the transition name for splashLandingExtras:
navigator.splashLandingExtras = FragmentNavigatorExtras(binding.logo to "logo")
In the LandingFragment's onCreate method, I do set up enter and exit animations:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
TransitionInflater.from(context).inflateTransition(android.R.transition.move).let {transition ->
sharedElementEnterTransition = transition
sharedElementReturnTransition = transition
}
}
The layouts are the following:
splash.xml
<layout 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">
<data>
<variable
name="viewModel"
type="com.felcana.app.viewmodel.SplashViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="#layout/background" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/logo"
style="?attr/logoStyle"
android:transitionName="logo"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
landing.xml
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<variable
name="viewModel"
type="com.my.app.viewmodel.LandingViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<include layout="#layout/background" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<ImageView
android:id="#+id/logo"
style="?attr/logoStyle"
android:layout_marginBottom="24dp"
android:transitionName="logo"
app:layout_constraintBottom_toTopOf="#id/button_register"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="#+id/button_register"
style="?attr/flatWhiteButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="8dp"
android:onClick="#{() -> viewModel.goToRegister()}"
android:text="#string/button_register"
app:layout_constraintBottom_toTopOf="#id/button_login"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.google.android.material.button.MaterialButton
android:id="#+id/button_login"
style="?attr/borderlessWhiteButtonStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:onClick="#{() -> viewModel.goToLogin()}"
android:text="#string/button_login"
app:layout_constraintBottom_toTopOf="#id/disclaimer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<TextView
android:id="#+id/disclaimer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="32dp"
android:maxLines="2"
android:textAlignment="center"
android:textColor="#color/app_white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:text="#tools:sample/lorem/random" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
For some reason, the animation does not play at all - the ImageView just jumps without any sort of transition to the new position.
What's going wrong here? According to the documentation, this should be working. I did try going back to more stable versions of the NavComponent library, to no avail.
Have you tried postponing the enter transition inside LandingFragment::onViewCreated and manually setting the transition name to the view?
Something like this:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// Pause the enter transition
postponeEnterTransition()
// Manually apply the transitionName
logo.transitionName = "logo"
// Resume the transition
startPostponedEnterTransition()
super.onViewCreated(view, savedInstanceState)
}
I'm trying to create an DSL-like OnClick-listener (in kotlin) for a custom view that I'm using several places in the Android project I'm currently working on. The view has an ImageView, a primary textview and a secondary textview. I'm trying to create a listener-helper that allows you to only override specific methods of an interface instead of all of them (inspired by this article). But I can't get it working. Actually it doesn't even work using regular OnClick listeners.
Here's my ErrorMessageView:
class ErrorMessageView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : FrameLayout(context, attrs, defStyle, defStyleRes) {
private var mOnClickListener: OnErrorMessageViewClickListener? = null
init {
LayoutInflater.from(context).inflate(
R.layout.custom_errorview,
this,
true)
ButterKnife.bind(this)
}
interface OnErrorMessageViewClickListener {
fun onImageClick()
fun onPrimaryTextClick()
fun onSecondaryTextClick()
}
... left out for brevity ...
fun setOnErrorMessageViewClickListener(
onViewClickListener: OnErrorMessageViewClickListener?) {
this.mOnClickListener = onViewClickListener
}
fun setOnErrorMessageViewClickListener(init: ErrorMessageViewClickListenerHelper.() -> Unit) {
val listener = ErrorMessageViewClickListenerHelper()
listener.init()
mOnClickListener = listener
}
#OnClick(R.id.image_container)
internal fun onImageViewClick() {
Timber.d("Clicked image view")
mOnClickListener?.onImageClick()
}
#OnClick(R.id.primary_text_container)
internal fun onPrimaryTextViewClick() {
Timber.d("Clicked primary textview")
mOnClickListener?.onPrimaryTextClick()
}
#OnClick(R.id.secondary_text_container)
internal fun onSecondaryTextViewClick() {
Timber.d("Clicked secondary textview")
mOnClickListener?.onSecondaryTextClick()
}
}
And here's my helper class:
private typealias ErrorViewClickListener = () -> Unit
open class ErrorMessageViewClickListenerHelper : ErrorMessageView.OnErrorMessageViewClickListener {
private var mImageClick: ErrorViewClickListener? = null
fun onImageClick(onImageClick: ErrorViewClickListener?) {
mImageClick = onImageClick
}
override fun onImageClick() {
mImageClick?..invoke()
}
private var mPrimaryTextClick: ErrorViewClickListener? = null
fun onPrimaryTextClick(onPrimaryTextClick: ErrorViewClickListener?) {
mPrimaryTextClick = onPrimaryTextClick
}
override fun onPrimaryTextClick() {
mPrimaryTextClick?.invoke()
}
private var mSecondaryTextClick: ErrorViewClickListener? = null
fun onSecondaryTextClick(onSecondaryTextClick: ErrorViewClickListener?) {
mSecondaryTextClick = onSecondaryTextClick
}
override fun onSecondaryTextClick() {
mSecondaryTextClick?.invoke()
}
}
My layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp">
<android.support.v7.widget.AppCompatImageView
android:id="#+id/image_container"
android:layout_width="#dimen/dialog_worklist_image_size"
android:layout_height="#dimen/dialog_worklist_image_size"
android:layout_centerVertical="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
tools:ignore="ContentDescription" />
<TextView
android:id="#+id/primary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="8dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="#+id/image_container"
app:layout_constraintStart_toStartOf="#+id/image_container"
app:layout_constraintTop_toBottomOf="#id/image_container" />
<TextView
android:id="#+id/secondary_text_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:textSize="16sp"
android:clickable="true"
android:focusable="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/primary_text_container" />
</android.support.constraint.ConstraintLayout>
I'm using the helper in my code like this, but nothing gets logged (It's almost as if no clicklistener can be attached):
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setSecondaryTextClickListener(View.OnClickListener { Timber.d("Test secondary click") })
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
// . this#WorklistDialog
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}
Does anyone have a suggestion to what is wrong with my code?
Btw. the above code is used in a DialogFragment - that is why you might notice the mView?. as a bit awkward. But that is the way to get a handle on the views in a dialog.
Was just about to start crying when I finally found the solution - OMG.
The problem was the SwipeRefreshLayout in the layout file for the dialogfragment, which can be seen here:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<android.support.v7.widget.Toolbar
android:id="#+id/dialog_worklist_toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:background="#color/colorPrimary"
android:elevation="4dp"
android:minHeight="?android:attr/actionBarSize"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<com.conhea.smartgfr.ui.examination.layouts.ErrorMessageView
android:id="#+id/dialog_worklist_errorview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/dialog_worklist_toolbar" />
<ProgressBar
android:id="#+id/dialog_worklist_progressbar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/dialog_worklist_toolbar" />
<android.support.v4.widget.SwipeRefreshLayout
android:id="#+id/dialog_worklist_swiperefreshlayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
app:layout_constraintTop_toBottomOf="#id/dialog_worklist_toolbar">
<android.support.v7.widget.RecyclerView
android:id="#+id/dialog_worklist_recyclerview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#android:color/transparent" />
</android.support.v4.widget.SwipeRefreshLayout>
</android.support.constraint.ConstraintLayout>
The SwipeRefreshLayout was blocking the custom ErrorMessageView from being clickable.
After adding mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false to the below in my DialogFragment everything started working:
override fun setWorklistNotEnabledMessage(showMessage: Boolean) {
if (showMessage) {
mView?.dialog_worklist_recyclerview?.visibility = View.GONE
mView?.dialog_worklist_swiperefreshlayout?.isEnabled = false
mView?.dialog_worklist_errorview?.apply {
visibility = View.VISIBLE
setErrorDrawable(R.drawable.ic_worklist_disabled_black_24dp)
setPrimaryText(R.string.global_worklist_disabled_error)
setSecondaryText(R.string.dialog_worklist_worklist_disabled_error_secondary_text)
setOnErrorMessageViewClickListener {
onSecondaryTextClick { Timber.d("Test secondary click") }
onPrimaryTextClick { Timber.d("Test primary click") }
onImageClick { Timber.d("Test image click") }
}
}
} else {
mView?.dialog_worklist_errorview?.apply {
visibility = View.GONE
setErrorDrawable(null)
setPrimaryText("")
setSecondaryText("")
setOnErrorMessageViewClickListener(null)
}
}
}