Android NestedScrollView stopNestedScroll not working - android

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:

Related

Button is visiable but TextView / ImageView is not visiable in custom compound view

I am trying to create compound CardView using two CardView and extended Framelayout. If I place a button inside custom CardView as a child the button is visible but if i place TextView or ImageView as a child the widget (TextView or ImageView) is not visible.
Screenshot of Custom widget with TextView as child
Screenshot of Custom Widget with Button as child
kotlin code
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
class CompoundCardView : FrameLayout{
private var childView:CardView? = null
private var heading:String? = null
set(value) {
field = value
invalidate()
requestLayout()
}
get() {
return field
}
constructor(context: Context) : super(context) {
init(context, null)
}
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {
init(context, attrs)
}
constructor(context: Context,
attrs: AttributeSet,
defStyle: Int) : super(context, attrs, defStyle) {
init(context, attrs)
}
private fun init(context: Context, attrs: AttributeSet?) {
context.theme.obtainStyledAttributes(attrs,R.styleable.CompoundCardView,0,0).apply {
try {
heading = getString(R.styleable.CompoundCardView_heading)
}finally {
recycle()
}
}
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
inflater.inflate(R.layout.compaund_card_view,this,true)
childView = findViewById(R.id.holder)
val header:TextView = findViewById(R.id.header)
header.text = heading
}
override fun addView(child: View?) {
invalidate()
childView?.addView(child,0, child?.layoutParams)
requestLayout()
}
}
xml layout
<?xml version="1.0" encoding="utf-8"?>
<merge 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">
<com.google.android.material.card.MaterialCardView
android:id="#+id/holder"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"
android:background="#00FFFFFF"
app:cardCornerRadius="8dp"
app:strokeColor="#2196F3"
app:strokeWidth="1dp" />
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:backgroundTint="#FFC107"
app:cardCornerRadius="8dp"
app:contentPaddingBottom="4dp"
app:contentPaddingLeft="8dp"
app:contentPaddingRight="8dp"
app:contentPaddingTop="4dp"
app:strokeColor="#2196F3"
app:strokeWidth="1dp">
<TextView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="#string/heading"
android:textSize="20sp" />
</com.google.android.material.card.MaterialCardView>
</merge>

Databinding in custom ViewGroup give error

TLDR;
I receive an error says :
Unable to resume activity {...MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.widget.AppCompatTextView.setTag(java.lang.Object)' on a null object reference
How can I use databinding in Custom ViewGroup?
I created a custom ViewGroup with its layout like :
<?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">
<androidx.cardview.widget.CardView
android:id="#+id/card"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/margin_8dp"
android:minHeight="72dp"
android:orientation="vertical"
app:cardBackgroundColor="#color/white"
app:cardCornerRadius="#dimen/margin_8dp"
app:cardElevation="#dimen/margin_2dp"
app:strokeColor="#color/color_divider"
app:strokeWidth=".5dp">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/clRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="#dimen/margin_16dp">
<Some Views Here
...
...
/>
<LinearLayout
android:id="#+id/llContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/margin_16dp"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/tvHeader"
tools:background="#color/all_stock_back"
tools:layout_height="200dp" />
<Some Views Here
...
...
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</layout>
And View code like:
class CustomLayout : LinearLayout {
private lateinit var mBinding: CustomLayoutBinding
private lateinit var containerLayout: LinearLayout
constructor(context: Context) : super(context) {
initView(context)
}
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
initArgs(context, attrs)
initView(context)
}
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
initArgs(context, attrs)
initView(context)
}
private fun initView(context: Context) {
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
mBinding =
DataBindingUtil.inflate(inflater, R.layout.custom_layout, this, true)
containerLayout = mBinding.llContainer
}
private fun initArgs(context: Context, attrs: AttributeSet?) {
//set attrs
}
override fun onFinishInflate() {
super.onFinishInflate()
mBinding.run {
//Set texts and listeners
}
}
override fun addView(child: View?, index: Int, params: ViewGroup.LayoutParams?) {
val id = child?.id
if (id == # IDsOnLayout
) {
super.addView(child, index, params);
} else {
containerLayout.addView(child, index, params);
}
}
}
When I use this view in any activity/fragment and bind some text or anything to any of its child views like (for simplicity I removed attributes here):
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable name="vm"
type="SomeViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout>
<CustomLayout
android:id="#+id/customid"
app:collapsable="false">
<GridLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:columnCount="2" android:rowCount="4">
<androidx.appcompat.widget.AppCompatTextView
android:text="#{vm.headerText}" />
<androidx.appcompat.widget.AppCompatTextView />
</GridLayout>
</CustomLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I receive an error says :
Unable to resume activity {...MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void androidx.appcompat.widget.AppCompatTextView.setTag(java.lang.Object)' on a null object reference
In generated DBImpl textviews inside my customlayout are null. I guess there is some problem with addview and DBImpl generation.
How can I use databinding in Custom ViewGroup?

Sibling view prevents recyclerview from scrolling when made clickable

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
}
}
}

Android Custom View: How to set click listener on individual widgets

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

Custom view, onSaveInstanceState is not called

I created a custom view, and tried to restore the state automatically on screen rotation (just as EditText restores current input text automatically), but when I see the log, onSaveInstanceState is not called, and only onRestoreInstanceState is called. What is wrong?
class MyView:LinearLayout
{
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context, attrs, defStyleAttr)
init
{
isSaveEnabled=true
}
override fun onSaveInstanceState(): Parcelable
{
return super.onSaveInstanceState()
Log.d("ss", "save")
}
override fun onRestoreInstanceState(state: Parcelable?)
{
super.onRestoreInstanceState(state)
Log.d("ss", "restore")
}
}
Activity layout:
<?xml version="1.0" encoding="utf-8"?>
<AbsoluteLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/top"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.loser.mylayouttest.MyView
android:id="#+id/myView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</AbsoluteLayout>
Activity:
class MainActivity : AppCompatActivity()
{
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
The reason why you can't see Log.d("ss", "save") being called is simply that the code line is invoked after the return statement. The onSaveInstanceState() is actually called. To see the log move Log.d("ss", "save") above return super.onSaveInstanceState().

Categories

Resources