How to set BottomSheetDialogFragment to fullScreen? - android

I'm trying to make my BottomSheetDialogFragment to be fullscreen when it's opened, the issue is that in any case the Dialog is shown half of screen height.
I've tried to set the peekHeight as the following:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
dialog?.setOnShowListener { dialog ->
val bottomSheetBehavior: BottomSheetBehavior<*> = (dialog as BottomSheetDialog).behavior
bottomSheetBehavior.peekHeight = Resources.getSystem().displayMetrics.heightPixels
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
But the Dialog is shown as same as without the peekHeight.
Then i've tried to add android:theme="#android:style/Theme.Material.Light.NoActionBar.Fullscreen"
In my BottomSheet layout but still had the same result.

Use this
fun setupRatio(context: Context, bottomSheetDialog: BottomSheetDialog, percetage: Int) {
//id = com.google.android.material.R.id.design_bottom_sheet for Material Components
//id = android.support.design.R.id.design_bottom_sheet for support librares
val bottomSheet =
bottomSheetDialog.findViewById<View>(R.id.design_bottom_sheet) as FrameLayout
val behavior: BottomSheetBehavior<*> = BottomSheetBehavior.from(bottomSheet)
val layoutParams = bottomSheet.layoutParams
layoutParams.height = getBottomSheetDialogDefaultHeight(context, percetage)
bottomSheet.layoutParams = layoutParams
behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
call this into onStart in your dialog
override fun onStart() {
super.onStart()
setupRatio(requireContext(),dialog as BottomSheetDialog,100)
}
private fun getBottomSheetDialogDefaultHeight(context: Context, percetage: Int): Int {
return getWindowHeight(context) * percetage / 100
}
private fun getWindowHeight(context: Context): Int {
// Calculate window height for fullscreen use
val displayMetrics = DisplayMetrics()
(context as Activity?)!!.windowManager.defaultDisplay.getMetrics(displayMetrics)
return displayMetrics.heightPixels
}

Related

MotionLayout not recyclable as a child of RecyclerView

i try to implement programmatically version of MotionLayout by extending it. And i have a base activity ayout using RecyclerView.
However, when i add my motion layout as an item of the RecyclerView, the view is not recycled when i try to scrolling up and down.
And it works well when i use as a normal view (act as single view).
Here is the preview:
class SimpleMotionLayout #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : MotionLayout(context, attrs, defStyleAttr) {
private val motionScene = MotionScene(this)
private var _simpleTransition: MotionScene.Transition? = null
private lateinit var squareView: View
init {
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
initDefaultConstraint(this)
setMotion()
}
fun setMotion() {
_simpleTransition = createPlaceholderTransition(motionScene)
setDebugMode(DEBUG_SHOW_PATH)
/**
* The order matters here.
* [MotionScene.addTransition] adds the transition to the scene while
* [MotionScene.setTransition] sets the transition to be the current transition.
*/
motionScene.addTransition(_simpleTransition)
motionScene.setTransition(_simpleTransition)
scene = motionScene
setTransition(_simpleTransition!!.id)
animateView()
}
fun setSquareColor(color: Int) {
squareView.setBackgroundColor(color)
}
fun initDefaultConstraint(motionLayout: ConstraintLayout) {
// View
squareView = View(context).apply {
id = R.id.default_button
setBackgroundColor(Color.BLACK)
}
motionLayout.addView(
squareView,
LayoutParams(
fromDp(context, 52),
fromDp(context, 52)
)
)
val set = ConstraintSet()
set.clone(motionLayout)
// Setup constraint set to TOP, LEFT to the Parent
set.connect(
squareView.id,
TOP,
PARENT_ID,
TOP
)
set.connect(
squareView.id,
START,
PARENT_ID,
START
)
set.applyTo(motionLayout)
}
private fun setToEnd() {
val endSet = getConstraintSet(_simpleTransition?.endConstraintSetId ?: return)
endSet.clear(R.id.default_button, START)
endSet.connect(
R.id.default_button,
END,
PARENT_ID,
END
)
}
fun animateView() {
setToEnd()
_simpleTransition?.setOnSwipe(
OnSwipe().apply {
dragDirection = DRAG_END
touchAnchorId = R.id.default_button
touchAnchorSide = SIDE_START
onTouchUp = ON_UP_AUTOCOMPLETE_TO_START
setMaxAcceleration(500)
}
)
setTransition(_simpleTransition!!.id)
}
// Placeholder transition??
fun createPlaceholderTransition(motionScene: MotionScene): MotionScene.Transition? {
val startSetId = View.generateViewId()
val startSet = ConstraintSet()
startSet.clone(this)
val endSetId = View.generateViewId()
val endSet = ConstraintSet()
endSet.clone(this)
val transitionId = View.generateViewId()
return TransitionBuilder.buildTransition(
motionScene,
transitionId,
startSetId, startSet,
endSetId, endSet
)
}
/**
* Get px from dp
*/
private fun fromDp(context: Context, inDp: Int): Int {
val scale = context.resources.displayMetrics.density
return (inDp * scale).toInt()
}
}
Below is my adapter:
class SimpleMotionLayoutAdapter : RecyclerView.Adapter<SimpleMotionLayoutAdapter.ViewHolder>() {
val items = mutableListOf<Int>() // colors
class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
fun setColor(color: Int) {
(view as SimpleMotionLayout).setSquareColor(color)
}
}
override fun getItemId(position: Int): Long {
return position.toLong()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = SimpleMotionLayout(parent.context)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.setColor(items[position])
}
override fun getItemCount(): Int = items.size
companion object {
const val TYPE_NORMAL = 0
const val TYPE_EXCEPTIONAL = 1
}
}
Am i missing implementation?
Thank you
In general you need to cache and restore the state of the MotionLayout when it gets Recycled.
Right now in onBindViewHolder you only set the Color.
Remember RecyclerView keeps a only a screens worth (+ about 3) of ViewHolders and reuses them using onBindViewHolder
At minimum you need to set the Progress of the MotionLayout.
Due to differences in timing you may need to set the progress in an onPost

Android DialogFragment not dismissing on taping outside of it

So I have a DialogFragment which I want it to be cancelable, the thing is that I need to apply match_parent as width and height to see the dialog in the screen well formated , but when clicking outside it does not dismiss the dialog
class InformationDialogFragment : DialogFragment() {
lateinit var viewState: InformationDialogViewState
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(STYLE_NORMAL, R.style.ModalTheme)
}
override fun onStart() {
super.onStart()
val dialog = dialog
if (dialog != null) {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.MATCH_PARENT
dialog.window!!.setLayout(width, height)
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = Dialog(requireContext(), R.style.MyDialog)
dialog.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
dialog.window?.requestFeature(Window.FEATURE_NO_TITLE)
dialog.window?.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)
return dialog
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
val binding: InformationDialogBinding = DataBindingUtil.inflate(inflater, R.layout.account_information_dialog, container, false)
binding.viewState = viewState
return binding.root
}
}
I don't need to put isCancelable, setCancelableOnTouch, setCancelable because the dialog itself is cancealable as default, and I have tried it all
when I remove the onStart code, the touch outside works and dismisses the dialog, but the dialog is all shown wrong and vertically
Changing height to wrap content let me dismiss the dialog clicking outside of it
override fun onStart() {
super.onStart()
val dialog = dialog
if (dialog != null) {
val width = ViewGroup.LayoutParams.MATCH_PARENT
val height = ViewGroup.LayoutParams.WRAP_CONTENT
dialog.window!!.setLayout(width, height)
}
}

Android kotlin bottomSheetBehavior COLLAPSED after clik button

Hi i want tu COLLAPSED my bottomSheetBehavior after click button I do this :
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
But it doesn't work , what I do wrong
this is my bottomSheetBehavior :
bottomSheetBehavior.setBottomSheetCallback(object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
Log.e("a","a")
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
val upperState = 0.66
val lowerState = 0.33
if (bottomSheetBehavior.state == BottomSheetBehavior.STATE_SETTLING ) {
if(slideOffset >= upperState){
bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED
}
if(slideOffset > lowerState && slideOffset < upperState){
bottomSheetBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED
}
if(slideOffset <= lowerState){
bottomSheetBehavior.state = BottomSheetBehavior.STATE_COLLAPSED
}
}
changeSize()
}
})
private fun changeSizer() {
val screenHeight = getScreenHeight(this)
bottomSheetBehavior.peekHeight = (screenHeight * 0.2).toInt()
val params: ViewGroup.LayoutParams = llBottomSheet.layoutParams
params.height = (screenHeight * 0.8).toInt()
llBottomSheet.layoutParams = params
}
Set fitToContents
bottomSheetBehavior.isFitToContents = false
Sets whether the height of the expanded sheet is determined by the height of its contents, or
if it is expanded in two stages (half the height of the parent container, full height of parent
container).
Default value is true. We have to set it to false to have option set height "manually".
Set peekHeight
Sets the height of the bottom sheet when it is collapsed.
Second parameter (true) is responsible for animating between the
old height and the new height.
bottomSheetBehavior.setPeekHeight(peekHeight, true)
Full working example
class MainActivity : AppCompatActivity() {
private lateinit var bottomSheetBehavior: BottomSheetBehavior<LinearLayout>
private val bottomSheetHeight: Int by lazy {
bottom_sheet.height
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
bottomSheetBehavior = BottomSheetBehavior.from(bottom_sheet)
bottomSheetBehavior.isFitToContents = false
setInitValue()
}
/**
* Use listener, because at the beginning The UI has not been sized and laid out on the screen yet.
*/
private fun setInitValue() {
bottom_sheet.run {
viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
if (bottom_sheet.isShown) {
// Show only 25%
updatePeekHeight(0.25f)
viewTreeObserver.removeOnGlobalLayoutListener(this)
}
}
})
}
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)
return super.onCreateOptionsMenu(menu)
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
val factor: Float = when (item.itemId) {
R.id.option_full -> 1f
R.id.option_3_4 -> 0.75f
R.id.option_2_4 -> 0.50f
R.id.option_1_4 -> 0.25f
R.id.option_hide -> 0f
else -> 1f
}
updatePeekHeight(factor)
return true
}
private fun updatePeekHeight(factor: Float) {
val peekHeight: Int = (bottomSheetHeight * factor).toInt()
bottomSheetBehavior.setPeekHeight(peekHeight, true)
}
}
Demo

Pass touch event from dialog fragment to View right below (inside the parent activity)

As you can see in the image , red border rectangle is the parent activity . Blue one is dialog fragment . The circle is indicating a view and rectangle below is the description. I want the click on circle to be passed down to the button. So far i have tried
1. overriding onTouchEvent in Circle View and return false
2. setOntouchListener on circle view and call activity.dispatchTouchListener and return false
3. mark dialog frgament and circle view clickable/focusable false.
None of the above seems to be calling onCLickListener of the button underneath. I can see touch event being received in Activity's onIterceptTouch() though. Please help
Activity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.main_activity)
val viewTreeObserver = button2.viewTreeObserver
viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {MainFragment.newInstance(button2).show(supportFragmentManager, "dialog")
button1.viewTreeObserver.removeOnGlobalLayoutListener(this)
}
})
container.setOnClickListener {
Log.d("Test","Activity clicked")
}
button.setOnClickListener {
Log.d("Test","Button Clicked")
}
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
Log.d("Test","Activity onTouchEvent")
return super.onTouchEvent(event)
}
}
Dialog Fragment
class MainFragment : DialogFragment() {
companion object {
fun newInstance(view: View?) : MainFragment {
val args = Bundle()
val point = getRippleLocation(getViewCenterLocation(view))
args.putInt("X", point.x)
args.putInt("Y", point.y)
val frag = MainFragment()
frag.arguments = args
return frag
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View {
return inflater.inflate(R.layout.main_fragment, container, false)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
dialog.window!!.setGravity(Gravity.START or Gravity.TOP)
point.x = arguments!!.getInt("X")
point.y = arguments!!.getInt("Y")
params.x = point.x
params.y = (point.y - 50)
dialog.window!!.attributes = params
return dialog
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
addRippleView(main, 0, 0)
}
private fun addCircleView(rootView: ViewGroup, leftMargin: Int, topMargin: Int) {
val rippleView = BaseCircleView(context, null)
rippleView.isClickable = false
rippleView.isFocusable = false
rippleView.setOnTouchListener(object : View.OnTouchListener{
override fun onTouch(p0: View?, p1: MotionEvent?): Boolean {
activity!!.dispatchTouchEvent(p1)
return false
}
})
configureRipple(rippleView)
context?.resources?.let { resources ->
rippleView.id = R.id.gather_ripple_view_id
val params = RelativeLayout.LayoutParams(resources.getDimensionPixelSize(R.dimen.gather_on_boarding_ripple_container_width),
resources.getDimensionPixelSize(R.dimen.gather_on_boarding_ripple_container_height))
params.leftMargin = leftMargin
params.topMargin = topMargin
rootView.addView(rippleView, params)
}
}
}
If someone is still struggling with this one, this is how is solved it.
dialog?.window?.decorView?.setOnTouchListener { v, event ->
activity?.dispatchTouchEvent(event)
false
}
this will pass touch event through
setting the FLAG_NOT_TOUCH_MODAL worked for me.
Window window = dialog.getWindow();
window.setFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL);

RecycleView list items not appearing

I've been trying to make RecyclerView work in my app where the list items are ImageViews, and the images get downloaded and put inside (asynchronously) in the onBindViewHolder method. I'm not facing any errors in my code, but for some reason
only the list items which will be visible (even partially) to the user
when the activity loads, have images loaded into them.
Though I can't see the images, I observed that the height and width of these items have been allocated correctly. And since the images get downloaded first, and then the ImageView's dimensions are determined I figure that the problem has got something to do with RecyclerView itself? If someone can shed some light on this, it would be great. Thanks.
I would also like to add, that if the Activity is paused and then resumed (by clicking on the "square" navigation button and then resuming it), the images of all the list items load correctly.
Pic #1
Pic #2
Here's my code:
onCreate method:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
recyclerView {
id = ViewID.ID_LIST
}
val imgList = ArrayList<ImageView>()
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
imgList.add(ImageView(ctx))
val lv = findViewById(ViewID.ID_LIST) as RecyclerView
lv.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
lv.adapter = ImageRecyclerAdapter(ctx, imgList)
}
The RecyclerView.Adapter class:
private class ImageRecyclerAdapter(val context: Context, val imageList: ArrayList<ImageView>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onViewRecycled(holder: RecyclerView.ViewHolder?) {
super.onViewRecycled(holder)
if (holder != null) {
val v = holder.itemView as ImageView
v.setImageBitmap(null)
}
}
override fun onBindViewHolder(p: RecyclerView.ViewHolder, position: Int) {
val v = p.itemView as ImageView
Ion.with(v)
.load("https://pbs.twimg.com/profile_images/616076655547682816/6gMRtQyY.jpg")
.setCallback({ exception, t ->
if (t != null) {
val dm = Point()
context.windowManager.defaultDisplay.getRealSize(dm)
val w = t.maxWidth
val h = t.maxHeight
val params = t.layoutParams
if (params != null) {
params.width = dm.x
params.height = (dm.x * (h.toDouble() / w.toDouble())).toInt()
t.layoutParams = params
t.requestLayout()
}
}
})
}
override fun getItemCount(): Int {
return imageList.size
}
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
val v = ImageView(context)
return object : RecyclerView.ViewHolder(v) {}
}
}
It worked after I made the binding of data into a Synchronous request, and I moved the ImageView manipulation (changing LayoutParams) into the onViewAttachedToWindow overridden method of my adapter.
onViewAttachedToWindow:
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder?) {
super.onViewAttachedToWindow(holder)
val t = holder?.itemView as ImageView
val dm = Point()
context.windowManager.defaultDisplay.getRealSize(dm)
val w = t.maxWidth
val h = t.maxHeight
val params = t.layoutParams
if (params != null) {
params.width = dm.x
params.height = (dm.x * (h.toDouble() / w.toDouble())).toInt()
t.layoutParams = params
t.requestLayout()
}
}
onBindViewHolder:
override fun onBindViewHolder(p: RecyclerView.ViewHolder, position: Int) {
val v = p.itemView as ImageView
Ion.with(v)
.load(imageList[position].toString())
.tryGet()
}

Categories

Resources