I try to implement a simple scenario: click on a button to show/hide the view(dependency). When the view(dependency) is shown, move the other view down. But onDependentViewChanged is only called once... Please help me to solve this issue.
<View
android:id="#+id/child"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|top"
app:layout_behavior=".behavior.MoveDownBehavior">
<CustomizedView
android:id="#+id/dependency"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top|center_horizontal"
android:visibility="gone" />
class MoveDownBehavior<V : View>(context: Context, attrs: AttributeSet) :
CoordinatorLayout.Behavior<V>(context, attrs) {
// Rest of the code is the same
override fun layoutDependsOn(parent: CoordinatorLayout, child: V, dependency: View): Boolean {
return dependency is CustomizedView
}
override fun onDependentViewChanged(
parent: CoordinatorLayout,
child: V,
dependency: View
): Boolean {
Timber.e("dependency.visibility = ${dependency.visibility}")
// TODO according dependency.visibility to move down or up the child view
return super.onDependentViewChanged(parent, child, dependency)
}
}
What's wrong with it?
Related
My screen consists of a ConstraintLayout with two children. The one child is a RecyclerView which takes up the entire screen. The other child is another ConstraintLayout that contains a TextView (the screen is actually more complex than this, but I'm reducing it to something here to make it easier to comprehend). The child ConstraintLayout is positioned over the RecyclerView and scrolls up whenever you scroll the RecyclerView. This is done using a scroll listener on the RecyclerView and re-adjusting the Y position of the child ConstraintLayout. The child ConstraintLayout needs to be clickable.
The problem I am having is that if you try scrolling the RecyclerView by first touching down on the child ConstraintLayout, the scrolling will not work. The click event handler for the child ConstraintLayout will work, however.
From my understanding of how Android handles touch events, if a view returns false in its onTouchEvent handler, that view will not get notified of any further motion events. So theoretically, if my child ConstraintLayout returns false in its onTouchEvent handler, the RecyclerView should still get the scroll motion events.
Here is my layout xml:
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
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:fitsSystemWindows="true"
tools:background="#55338855">
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="#+id/dashboardConstraintLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
app:layoutDescription="#xml/dashboard_initial_animation_start"
app:showPaths="false">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/contentRecyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:clipChildren="false"
android:fitsSystemWindows="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/guideWelcome"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_default="percent"
app:layout_constraintWidth_percent="1.0" />
<ReservationCard
android:id="#+id/reservationConstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:background="#color/dashboard_reservation_card_background_color"
android:padding="16dp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/txtSearch">
<TextView
android:id="#+id/txtReservationHeader"
style="#style/AppTheme.Label"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#string/dashboard_screen_reservation_card_headline"
android:textColor="#color/dashboard_reservation_card_text_color" />
</com.sinnerschrader.motelone.screens.dashboard.ReservationCard>
</androidx.constraintlayout.motion.widget.MotionLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
And here is my custom ConstraintLayout (ReservationCard):
class ReservationCard : androidx.constraintlayout.widget.ConstraintLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
private val gestureDetector: GestureDetector
init {
gestureDetector = GestureDetector(GestureListener())
}
override fun onTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
gestureDetector.onTouchEvent(ev)
return true
} else if (ev?.action == MotionEvent.ACTION_UP) {
gestureDetector.onTouchEvent(ev)
return false
} else {
super.onTouchEvent(ev)
return false
}
}
private inner class GestureListener : GestureDetector.OnGestureListener {
override fun onFling(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onScroll(p0: MotionEvent?, p1: MotionEvent?, p2: Float, p3: Float): Boolean {
return false
}
override fun onLongPress(p0: MotionEvent?) {
}
override fun onDown(p0: MotionEvent?): Boolean {
return false
}
override fun onShowPress(p0: MotionEvent?) {
}
override fun onSingleTapUp(p0: MotionEvent?): Boolean {
return false
}
}
}
I have 2 fragments inside the NestedScrollView and one fragment contains RecyclerView and another fragment contains GoogleMap and each fragment contains a text view as a title on top of it.
When I long tap on recycler view I am showing recycler view in full-screen view by changing its height to 100%.
Now I want to stop scrolling the title of RecyclerView's fragment but it is still scrolling the title when recycler view reached its last item.
This is the layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<com.myapp.common.CustomNestedScrollView
android:id="#+id/nestedScrollView"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true" />
</com.myapp.common.CustomNestedScrollView>
</LinearLayout>
It looks like:
I tried to stop the scrolling by extending the CustomNestedScrollView with NestedScrollView like:
class CustomNestedScrollView : NestedScrollView {
var isEnableScrolling = true
constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(context, attrs, defStyle) {}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {}
constructor(context: Context) : super(context) {}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
return if (isEnableScrolling) {
super.onInterceptTouchEvent(ev)
} else {
false
}
}
override fun onTouchEvent(ev: MotionEvent): Boolean {
return if (isEnableScrolling) {
super.onTouchEvent(ev)
} else {
false
}
}
}
And setting the value from the activity when height is 100% like:
nestedScrollView.isEnableScrolling = !isFull
Also tried by stopping Scroll of NestedScrollView like:
if (isFull) {
nestedScrollView.stopNestedScroll(ViewCompat.TYPE_NON_TOUCH)
} else {
nestedScrollView.startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL, ViewCompat.TYPE_TOUCH)
}
Expected:
I am using CoordinatorLayout with custom behavior and NestedScrollView in my project. It works well except one issue. When I am call nestedScrollView.smoothScrollTo(0,0) I get wrong Y coordinated in MyCustomBehavior. When I scroll it by finger I receive correct coordinates. How to get correct coordinates in behavior class when it scrolling by smoothScrollTo() method?
Here is my code:
<?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="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:layout_width="300dp"
android:layout_height="150dp"
android:background="#f00"
android:layout_gravity="center_horizontal"
app:layout_behavior="com.mysite.app.MyCustomBehavior" />
<com.mysite.app.CustomScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="Very long text here" />
</com.mysite.app.CustomScrollView>
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginTop="24dp"
android:elevation="20dp" />
</android.support.design.widget.CoordinatorLayout>
</android.support.constraint.ConstraintLayout>
CustomScrollView:
class CustomScrollView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : NestedScrollView(context, attrs, defStyleAttr) {
private val runnable = Runnable {
if (scrollY in 1..299) {
smoothScrollTo(0,0)
}
}
override fun stopNestedScroll(type: Int) {
super.stopNestedScroll(type)
removeCallbacks(runnable)
handler.postDelayed(runnable, 200)
}
}
Behavior:
class CollapsingImageBehavior(private val context: Context, attrs: AttributeSet?) : CoordinatorLayout.Behavior<CardView>(context, attrs) {
private var scroll = 0
override fun layoutDependsOn(parent: CoordinatorLayout, child: CardView, dependency: View): Boolean {
return dependency is NestedScrollView
}
override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: CardView, directTargetChild: View, target: View, axes: Int, type: Int): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type)
}
override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: CardView, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, type: Int) {
super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type)
scroll += dyConsumed
Log.d("tag", "onNestedScroll " + scroll)
}
override fun onDependentViewChanged(parent: CoordinatorLayout, child: CardView, dependency: View): Boolean {
return true
}
}
I have created an app with BottomNavigationView but I want when user scroll the list view then BottomNavigationView should hide. I tried a lot but I am not able to do that. Can anyone help me?
Layout code:
<android.support.design.widget.BottomNavigationView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/navigation"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?android:attr/windowBackground"
app:menu="#menu/bottom_nav_menu"/>
you must create a class to handle the behavior of the BottomNavigationView.
Below I show a code made in Kotlin.
class BottomNavigationBehavior constructor(
context: Context,
attrs: AttributeSet? = null
) : CoordinatorLayout.Behavior<BottomNavigationView>(context, attrs) {
override fun layoutDependsOn(
parent: CoordinatorLayout,
child: BottomNavigationView,
dependency: View
): Boolean {
return dependency is FrameLayout
}
override fun onStartNestedScroll(
coordinatorLayout: CoordinatorLayout,
child: BottomNavigationView,
directTargetChild: View,
target: View,
axes: Int,
type: Int
): Boolean {
return axes == ViewCompat.SCROLL_AXIS_VERTICAL
}
/*this is where bottomnavigation is shown or hidden*/
override fun onNestedPreScroll(coordinatorLayout: CoordinatorLayout, child: BottomNavigationView,
target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (dy < 0) {
showBottomNavigationView(child)
} else if (dy > 0) {
hideBottomNavigationView(child)
}
}
private fun hideBottomNavigationView(view: BottomNavigationView) {
view.animate().translationY(view.height.toFloat())
}
private fun showBottomNavigationView(view: BottomNavigationView) {
view.animate().translationY(0f)
}
}
After creating the class, we need to tell the BottomNavigationView which is the class that will control its behavior.
val layoutParams: CoordinatorLayout.LayoutParams = mBottomNavigation.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.behavior = BottomNavigationBehavior(this)
I hope it helps you.
This is my layout file for the custom view
<LinearLayout 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="wrap_content"
android:orientation="vertical">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="?actionBarSize">
<ImageView
android:id="#+id/ivMenu"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/ripple"
android:clickable="true"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_menu_gray" />
<ImageView
android:id="#+id/ivDrawer"
android:layout_width="48dp"
android:layout_height="48dp"
android:layout_marginBottom="8dp"
android:layout_marginTop="8dp"
android:background="#drawable/ripple"
android:clickable="true"
android:padding="12dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ic_drawer_gray" />
</android.support.constraint.ConstraintLayout>
</LinearLayout>
There are two ImageViews that I need to set click listeners on. I have created a class extending LinearLayout, since it is the root element in my layout.
I don't know how to set click listeners on individual items in the layout.
here is my class file for the layout
class WolfBottomAppBar : LinearLayout {
constructor(context: Context) : super(context) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
init(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init(context)
}
private fun init(context: Context) {
((context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) as LayoutInflater).inflate(R.layout.wolf_bottom_app_bar, this)
}
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP) {
/*
* I can set click listener for the whole view here, but not on individual items
*/
}
return super.dispatchTouchEvent(event)
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
if (event.action == KeyEvent.ACTION_UP && (event.keyCode == KeyEvent.KEYCODE_DPAD_CENTER || event.keyCode == KeyEvent.KEYCODE_ENTER)) {
}
return super.dispatchKeyEvent(event)
}
}
put
fun setMenuListener(listener : View.OnClickListener) {
findViewById<ImageView>(R.id.ivMenu).setOnClickListener(listener)
}
fun setDrawerListener(listener : View.OnClickListener) {
findViewById<ImageView>(R.id.ivDrawer).setOnClickListener(listener)
}
into you custom view class.
You can just access the ImageViews with their ids. You need to add the inflated view to your root (=WolfBottomAppBar); e.g. in the init method:
private fun init(context: Context) {
((context.getSystemService(Context.LAYOUT_INFLATER_SERVICE)) as LayoutInflater).inflate(R.layout.wolf_bottom_app_bar, this, true)
ivMenu.setOnClickListener { Log.d(TAG, "Image ivMenu clicked!") }
}
Don't forget to import the synthetic Kotlin package for the layout file and to add android:focusable="true" to the two ImageViews xml