I'll get right to the question: Is there a known issue or code that leads to the drawable of checkboxes (or other views) to not be refreshed/updated properly according to their state? If so, is there a solution?
Setup: I have an activity which adds a fragment containing checkboxes. The checkboxes behave peachy, responding on click events and I can handle them inside an onCheckedChangeListener. When the user has ticked three checkboxes the fragment is saved in the backstack and replaced by the subsequent fragment.
Expected Behaviour: If the user were to return to the previous "checkbox"-fragment, correct boxes should be ticked. CheckState of those said boxes should therefore be true so checkbox.isChecked = true is called in onViewCreated.
Behaviour: The boxes are not shown as ticked but behave in that manner when moving back to the previous fragment. The drawable shown is representing checked = false + pressed false
When I press one of the said boxes it shows the drawable representing checked = true + pressed = true and then returns to drawable representing checked = false + pressed = false on the first press, leading me to suspect that they in fact are checked as expected but shown as checked = false.
If I click on the same box again it gets checked, which supports the suspicion.
The "isChecked" inside the onCheckedChangeListener supports this suspicion as well being false # #1 and true # #2
What I've done: I've tried to isolate the setup in another application and everything works as expected there. I can call ".isChecked = true" in onViewCreated and those checkboxes will appear with drawable representing checked = true. One would think that the root of the problem would be easy to find when I've stripped the fragment down to the bare minimum but still, no luck.
Feel free to ask clarifying questions. I've been stuck on this for hours so any help is appreciated!
Fragment:
import android.app.Activity
import android.content.Context
import android.os.Bundle
import android.support.v7.widget.AppCompatCheckBox
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import co.joyship.android.R
import co.joyship.android.base.BaseFragment
import co.joyship.android.co.joyship.android.constants.Screen
import co.joyship.android.data.enums.Interest
import co.joyship.android.data.enums.Interest.*
import kotlinx.android.synthetic.main.fragment_top_interests.*
import java.util.*
class InterestFragment : BaseFragment(), View.OnClickListener {
private var listOfInterests = ArrayList<String>()
private var mListener: PreferencesRegistrationInterface? = null
private var checkAccumulator: Int = 0
private var interestViewMap = mapOf<Interest, AppCompatCheckBox>()
private var viewInterestMap = mapOf<Int, Interest>()
private lateinit var mOnCheckedListener: CompoundButton.OnCheckedChangeListener
override fun onResume() {
super.onResume()
checkBoxesInitially()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_top_interests, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mOnCheckedListener = instantiateOnCheckedListener()
generateViewMap()
generateInterestMap()
btnTopInterestsBack?.setOnClickListener(this)
btnTopInterestsForward?.setOnClickListener(this)
btnTopInterestsForward?.isEnabled = false
}
private fun generateViewMap() {
interestViewMap = mapOf<Interest, AppCompatCheckBox>(
Pair(MUSIC, cbMusic),
Pair(TV_SERIES, cbTv),
Pair(MOVIES, cbMovies),
Pair(GAMES, cbGames),
Pair(READING, cbReading),
Pair(TRAVEL, cbTravel),
Pair(SPORTS, cbSports),
Pair(EXERCISE, cbExercise),
Pair(PARTY, cbParty),
Pair(FOOD_BEVERAGE, cbFood),
Pair(FASHION, cbFashion),
Pair(POLITICS, cbPolitics),
Pair(BUSINESS, cbBusiness),
Pair(TECHNOLOGY, cbTechnology),
Pair(SCIENCE, cbScience))
}
private fun generateInterestMap() {
viewInterestMap = mapOf(
Pair(cbMusic.id, MUSIC),
Pair(cbTv.id, TV_SERIES),
Pair(cbMovies.id, MOVIES),
Pair(cbGames.id, GAMES),
Pair(cbReading.id, READING),
Pair(cbTravel.id, TRAVEL),
Pair(cbSports.id, SPORTS),
Pair(cbExercise.id, EXERCISE),
Pair(cbParty.id, PARTY),
Pair(cbFood.id, FOOD_BEVERAGE),
Pair(cbFashion.id, FASHION),
Pair(cbPolitics.id, POLITICS),
Pair(cbBusiness.id, BUSINESS),
Pair(cbTechnology.id, TECHNOLOGY),
Pair(cbScience.id, SCIENCE))
}
override fun onClick(view: View) {
if (!clickThrottled())
when (view.id) {
R.id.btnTopInterestsForward -> saveInterestsAndMoveForward()
R.id.btnTopInterestsBack ->
mListener?.onInterestsSelectionFragmentInteraction(
null,
null,
null,
view)
}
}
override fun onAttach(activityContext: Context?) {
super.onAttach(activityContext)
if (activityContext is PreferencesRegistrationInterface) {
mListener = activityContext
} else {
throw RuntimeException(activityContext!!.toString() + " must implement PreferencesRegistrationInterface")
}
}
override fun onDetach() {
mListener = null
super.onDetach()
}
private fun countCheck(isChecked: Boolean) {
checkAccumulator += if (isChecked) 1 else -1
}
private fun checkBoxesInitially() {
for (i in listOfInterests.indices) {
interestViewMap
.filterKeys { it.toString() == listOfInterests[i] }
.values
.first().isChecked = true
}
checkAccumulator = listOfInterests.size
interestViewMap.values.forEach { it.setOnCheckedChangeListener(mOnCheckedListener) }
}
private fun instantiateOnCheckedListener() =
CompoundButton.OnCheckedChangeListener { buttonView, isChecked ->
val boxId = buttonView.id
if (!clickThrottled()) {
countCheck(isChecked)
if (checkAccumulator <= 3) {
btnTopInterestsForward?.isEnabled = false
if (isChecked)
listOfInterests.add(viewInterestMap[boxId].toString())
else listOfInterests.remove(viewInterestMap[boxId].toString())
}
if (checkAccumulator == 3 && isChecked) {
saveInterestsAndMoveForward()
} else if (checkAccumulator > 3) {
btnTopInterestsForward!!.isEnabled = true
buttonView.isChecked = !isChecked
checkAccumulator--
}
btnTopInterestsForward!!.isEnabled = checkAccumulator == 3
} else {
(buttonView as AppCompatCheckBox).apply {
setOnCheckedChangeListener(null)
setChecked(!isChecked)
setOnCheckedChangeListener(mOnCheckedListener)
}
}
}
private fun saveInterestsAndMoveForward() {
val interest1 = listOfInterests[0]
val interest2 = listOfInterests[1]
val interest3 = listOfInterests[2]
mListener?.onInterestsSelectionFragmentInteraction(interest1, interest2, interest3, null)
}
companion object {
fun newInstance(): InterestFragment {
return InterestFragment().apply { type = "Interest fragment" }
}
}
}
XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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/svInterests"
style="#style/joyship_page.scroll"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="#color/joyship_background"
android:fadeScrollbars="true"
android:orientation="vertical"
android:paddingBottom="#dimen/marginMainMinusShadow"
android:scrollbarThumbVertical="#color/colorBrand">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.AppCompatImageButton
android:id="#+id/btnTopInterestsBack"
style="#style/joyship_clickableIconPadded"
android:layout_width="#dimen/iconArrowNarrowEnlarged"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ib_arrow_backward" />
<android.support.v7.widget.AppCompatImageButton
android:id="#+id/btnTopInterestsForward"
style="#style/joyship_clickableIconPadded"
android:layout_width="#dimen/iconArrowNarrowEnlarged"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="#drawable/ib_arrow_forward" />
<TextView
android:id="#+id/tvHeaderTopActivities"
style="#style/joyship_text.header"
android:layout_marginTop="#dimen/halfMainMargin"
android:fontFamily="#font/josefin_sans_regular"
android:text="#string/headerInterestSelection"
android:textSize="#dimen/fontSizeXl"
app:layout_constraintTop_toBottomOf="#id/btnTopInterestsBack"
tools:ignore="SpUsage" />
<android.support.v7.widget.LinearLayoutCompat
style="#style/joyship_viewgroup.card"
android:layout_marginTop="#dimen/marginBeneathOnboardingHeaderShadow"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="vertical"
app:layout_constraintTop_toBottomOf="#id/tvHeaderTopActivities">
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbMusic"
style="#style/joyship_control.checkbox"
android:layout_marginTop="#dimen/halfMainMargin"
android:text="#string/interest_music" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbTv"
style="#style/joyship_control.checkbox"
android:text="#string/interest_tv" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbMovies"
style="#style/joyship_control.checkbox"
android:text="#string/interest_movies" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbGames"
style="#style/joyship_control.checkbox"
android:text="#string/interest_games" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbReading"
style="#style/joyship_control.checkbox"
android:text="#string/interest_reading" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbTravel"
style="#style/joyship_control.checkbox"
android:text="#string/interest_travel" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbSports"
style="#style/joyship_control.checkbox"
android:text="#string/interest_sports" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbExercise"
style="#style/joyship_control.checkbox"
android:text="#string/interest_exercise" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbParty"
style="#style/joyship_control.checkbox"
android:text="#string/interest_party" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbFood"
style="#style/joyship_control.checkbox"
android:text="#string/interest_foodAndBeverage" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbFashion"
style="#style/joyship_control.checkbox"
android:text="#string/interest_fashion" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbPolitics"
style="#style/joyship_control.checkbox"
android:text="#string/interest_politics" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbBusiness"
style="#style/joyship_control.checkbox"
android:text="#string/interest_business" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbTechnology"
style="#style/joyship_control.checkbox"
android:text="#string/interest_technology" />
<android.support.v7.widget.AppCompatCheckBox
android:id="#+id/cbScience"
style="#style/joyship_control.checkbox"
android:layout_marginBottom="#dimen/halfMainMargin"
android:text="#string/interest_science" />
</android.support.v7.widget.LinearLayoutCompat>
</android.support.constraint.ConstraintLayout>
<style name="joyship_control">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_marginStart">#dimen/marginMainPlusShadow</item>
<item name="android:layout_marginEnd">#dimen/marginMainPlusShadow</item>
<item name="android:fontFamily">#font/josefin_sans_regular</item>
<item name="android:gravity">center_vertical</item>
<item name="android:textColor">#color/colorText</item>
<item name="android:textSize">#dimen/fontSizeM</item>
</style>
<style name="joyship_control.checkbox" parent="joyship_control">
<item name="android:layout_height">#dimen/controllerHeight</item>
<item name="android:background">#null</item>
<item name="android:button">#null</item>
<item name="android:drawableEnd">#drawable/cb_joyship</item>
</style>
Related
I am pretty new to Android development, so please go easy on me, but I have hit a dead-end trying to figure out why I can't add an event listener to any buttons within a fragment. What I am trying to do is I have a Fragment that will serve as the general landing page for my app that has a series of buttons the user should be able to click on that will navigate them to the other pages of the app. All the buttons are there, but I can't get them to do anything. The println statement that is supposed to print out the the id of the current button does work, so the code is entering the switch case, but the setOnClickListener isn't behaving the way I would expect. Here is what I have currently for the code of the Fragment class I am working on (please note there is a closing bracket to end the class I just couldn't get it in the code styling for some reason):
class HomeFragment : Fragment() {
private val buttonFragments = listOf("recipes", "budget", "inventory", "customers",
"reports")
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
for(button in buttonFragments) {
var currentHomeButton: View
when(button) {
"recipes" -> {
currentHomeButton = view.findViewById(R.id.button_recipes)
println(currentHomeButton.id)
currentHomeButton.setOnClickListener { println("Hello") }
}
}
}
}
Here is what I have tried:
The implementation you currently are seeing
Implementing View.OnClickListener and overriding the onClick function
trying to override the onClick function from within the setOnClickListener call
I hope this enough to go off of, but I can share more if needed.
UPDATE
After doing some more exploration, I think I better understand why my issue is occurring, but still can't find how to fix it. The HomeFragment is comprised of a series of custom buttons I have created called HomeButton. HomeButton is made up of a MaterialButton with a bunch of specific styling done to it. When I attach the setOnClick to the id of the material button, it works, but when trying to attach it to my custom component, it doesn't.
So after a good deal more research and trying out different solutions, I have figured out what the problem is and fixed it. The problem is that I am trying to attach a setOnClickListener on a custom view component I made. Because of this, the setOnClickListener gets attached to nothing (or at least nothing useful), because the function doesn't know how to work with a custom view component. To solve this, I implemented the following code in the HomeButton.kt custom view file:
private var listener: OnClickListener? = null
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
if (event.action == MotionEvent.ACTION_UP) {
if (listener != null) listener!!.onClick(this)
}
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)) {
if (listener != null) listener!!.onClick(this)
}
return super.dispatchKeyEvent(event)
}
override fun setOnClickListener(listener: OnClickListener?) {
this.listener = listener
}
This allows for the setOnClickListener to be used on the HomeButton custom view component. Then back in the HomeFragment.kt fragment file, this was the code I implemented and it worked with no problems:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
for(button in buttonFragments) {
var currentHomeButton: HomeButton
when(button) {
"recipes" -> {
currentHomeButton = view.findViewById(R.id.button_recipes)
currentHomeButton.setOnClickListener { utils.replaceFragment(
RecipesListFragment(), currentHomeButton.getText()) }
}
"budget" -> {
currentHomeButton = view.findViewById(R.id.button_budget)
currentHomeButton.setOnClickListener { utils.replaceFragment(
BudgetListFragment(), currentHomeButton.getText()) }
}
...
}
}
Hope this can help someone in the future
You're using standard output (println(...) function), which is not being used in Android apps.
Meaning, your click listener works but the code inside is useless unless you execute it manually on your development machine (with main function).
Learn how to use write logs using LogCat
Suppose we have a Fragment called (TestFragment)
Its layout :
fragment_testfragment.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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".presintation.TestFragment">
<TextView
android:id="#+id/txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="xxxxxxxxx"
android:textSize="30sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.257" />
<Button
android:id="#+id/b1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="b1"
app:layout_constraintBottom_toTopOf="#+id/b2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/b2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="b2"
app:layout_constraintBottom_toTopOf="#+id/b3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/b3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="b3"
app:layout_constraintBottom_toTopOf="#+id/b4"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent" />
<Button
android:id="#+id/b4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="228dp"
android:text="b4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.501"
app:layout_constraintStart_toStartOf="parent"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
to init 4 buttons (b1,b2,b3,b4) and change the text with the name of the clicked button
the code in the fragment class :
TestFragment.kt:
package com.mostafan3ma.android.hilttesting.presintation
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import com.mostafan3ma.android.hilttesting.R
import kotlin.math.log
class TestFragment : Fragment() {
private val TAG = "TestFragment"
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_testfragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.findViewById<Button>(R.id.b1).setOnClickListener {
Log.d(TAG, "onViewCreated: b1 clicked")
Toast.makeText(context, "b1 clicked", Toast.LENGTH_SHORT).show()
view.findViewById<TextView>(R.id.txt).text = "b1"
}
view.findViewById<Button>(R.id.b2).setOnClickListener {
Log.d(TAG, "onViewCreated: b2 clicked")
Toast.makeText(context, "b2 clicked", Toast.LENGTH_SHORT).show()
view.findViewById<TextView>(R.id.txt).text = "b2"
}
view.findViewById<Button>(R.id.b3).setOnClickListener {
Log.d(TAG, "onViewCreated: b3 clicked")
Toast.makeText(context, "b3 clicked", Toast.LENGTH_SHORT).show()
view.findViewById<TextView>(R.id.txt).text = "b3"
}
view.findViewById<Button>(R.id.b4).setOnClickListener {
Log.d(TAG, "onViewCreated: b4 clicked")
Toast.makeText(context, "b4 clicked", Toast.LENGTH_SHORT).show()
view.findViewById<TextView>(R.id.txt).text = "b4"
}
}
}
Hey I am working on search bar in android. I get the lead from this post. Now I want to try something more. Above post explanation in short :- I have searchview in the middle of screen. When we focus to on searchview we animate to go to top of screen and after remove focus goes to original position of search view. Now I want to show back arrow with initial screen load, look like this
Image 1
When we focus I need to show screen like this
Image 2
I tried some piece of code, but I am not succeed
ExploreConsultationsLayoutBinding.xml
<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:focusable="true">
<com.google.android.material.appbar.AppBarLayout
android:id="#+id/appBar"
android:layout_width="match_parent"
android:gravity="bottom"
android:backgroundTint="#color/red_primary_80"
android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
android:fitsSystemWindows="true">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="#color/black">
<androidx.appcompat.widget.SearchView
android:id="#+id/searchView"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:layout_marginStart="16dp"
app:iconifiedByDefault="false"
android:layout_marginEnd="16dp"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
/>
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<!-- Scrollable content -->
</androidx.coordinatorlayout.widget.CoordinatorLayout>
ExploreConsultationsActivity.kt
package com.example.app.consultation
import android.content.Context
import android.graphics.Rect
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.InputMethodManager
import androidx.appcompat.widget.SearchView
import com.example.app.common.BaseActivity
import com.example.app.databinding.ExploreConsultationsLayoutBinding
import org.koin.androidx.viewmodel.ext.android.viewModel
import org.koin.core.parameter.parametersOf
class ExploreConsultationsActivity : BaseActivity() {
companion object {
const val CONSULTATION_LIST_KEY = "consultation_list"
}
private val binding by lazy { ExploreConsultationsLayoutBinding.inflate(layoutInflater) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
setupView()
}
fun setupView() {
hideActionBar()
setupSearchView()
}
fun hideActionBar() {
supportActionBar?.let { actionBar ->
actionBar.hide()
}
}
fun setupSearchView() {
binding.consultationSearchView.apply {
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?) = false
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null) {
viewModel.queryText = newText
}
return true
}
})
setOnQueryTextFocusChangeListener { view, hasFocus ->
binding.appBar.setExpanded(!hasFocus)
if (hasFocus) {
binding.toolbar.apply {
setSupportActionBar(this)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
} else {
supportActionBar?.setDisplayHomeAsUpEnabled(false)
}
isSelected = hasFocus
}
}
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
if (ev?.action == MotionEvent.ACTION_DOWN) {
val view: View? = currentFocus
if (view is SearchView.SearchAutoComplete) {
val outRect = Rect()
view.getGlobalVisibleRect(outRect);
if (!outRect.contains(ev.rawX.toInt(), ev.rawY.toInt())) {
view.clearFocus()
val inputMethodManager = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
}
return super.dispatchTouchEvent(ev)
}
}
Mainfest.xml
<activity android:name="ExploreConsultationsActivity"
android:screenOrientation="portrait"
android:theme="#style/NoActionBar"/>
Style.xml
<style name="NoActionBar" parent="#style/Theme.MaterialComponents.Light.NoActionBar">
<item name="colorPrimary">#color/abc</item>
<item name="colorPrimaryDark">#color/xyz</item>
<item name="colorAccent">#color/abc</item>
<item name="android:theme">#style/AppTheme</item>
<item name="android:colorBackground">#color/white</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:statusBarColor">#color/status_bar</item>
<item name="android:windowLightStatusBar" tools:ignore="NewApi">true</item>
</style>
Actual Output
Expected Output
Image 1 and Image 2 please look top image of question.
Github Project
UPDATE
my search view is very close to status bar so how can I give top margin or padding?
You could change the start margin of the SearchView when it got the focus; and return it to the original margin when it loses the focus:
var originalMargin = 0
fun setupSearchView() {
binding.consultationSearchView.apply {
setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String?) = false
override fun onQueryTextChange(newText: String?): Boolean {
if (newText != null) {
}
return true
}
})
val params =
binding.consultationSearchView.layoutParams as CollapsingToolbarLayout.LayoutParams
originalMargin = params.marginStart
setOnQueryTextFocusChangeListener { view, hasFocus ->
binding.appBar.setExpanded(!hasFocus)
isSelected = hasFocus
if (hasFocus)
params.marginStart = originalMargin + 150 // arbitrary constant
else
params.marginStart = originalMargin
view.layoutParams = params
}
}
}
I try to handle insets by setting ViewCompat.setOnApplyWindowInsetsListener on attached view, but listener is not called.
I'm confused, because when i apply insets listener to decorView, it works:
ViewCompat.setOnApplyWindowInsetsListener(window.decorView) { v, insets ->
Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
insets
}
But when i apply insets listener to activity root view (i do it in same place), it's not working:
ViewCompat.setOnApplyWindowInsetsListener(main_root) { v, insets ->
Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
insets
}
I have a single activity app, and i set both this listeners when onCreate() method called.
I know i can get insets from first example and cache them, but now i need second code works, because i need this library works:
https://github.com/chrisbanes/insetter
It's have api like:
Insetter.builder()
.applySystemWindowInsetsToPadding(Side.LEFT)
.applyToView(main_bottom_nav)
It provide easy way to apply insets to your views. But it did't work.
I debug it and i saw that under the hood this library set listener to view like i try to do in a second code example, so when i get the reason why my listeners are not trigger, i think i can make this library work.
My minds are:
I don't rly understand what's happening. It's very strange for me that decorView has insets, but activity root is not. By logic, it's possible only when someone between decorView and activity view handle and consume insets, but is sounds strange: activity root view must to know about insets.
What i tried to do(see code), and it's not help:
Change root ViewGroup from ConstraintLayout ro Frame/Linear/RelativeLayout.
Add/remove fitsSystemWindows attribute to activity root ViewGroup. In fact i planning to remove insets at all to make a fullscreen application, so i don't think i need this flag enabled.
Change windowSoftInputMode of activity: it's adjustResize now.
Call requestApplyInsets() on target view before and after listener applied.
Downgrade androidx.core:core-ktx and androidx.activity:activity-ktx to stable versions.
Code:
activity.xml:
https://github.com/KirstenLy/AdviceCollector/blob/master/app/src/main/res/layout/activity_main.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/main_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/color_main_content">
<ImageView
android:id="#+id/main_icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/wise"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="#+id/main_bottom_nav"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/color_main_content"
android:elevation="#dimen/elevation_4"
android:translationZ="#dimen/elevation_4"
android:visibility="gone"
app:labelVisibilityMode="unlabeled"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:menu="#menu/bottom_navigation_menu" />
<!-- Helper view to show snackBar: need because of transparent navigation bar -->
<View
android:id="#+id/main_snackbar_anchor"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<!-- No internet error section -->
<ImageView
android:id="#+id/main_error_internet_icon"
android:layout_width="#dimen/image_size_96"
android:layout_height="#dimen/image_size_96"
app:layout_constraintBottom_toTopOf="#+id/main_error_internet_text"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed"
app:srcCompat="#drawable/ic_wifi" />
<TextView
android:id="#+id/main_error_internet_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center"
android:lines="3"
android:padding="#dimen/padding_16"
android:textAlignment="center"
app:layout_constraintBottom_toTopOf="#+id/main_error_internet_retry_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/main_error_internet_icon"
tools:text="#string/main_activity_error_loading" />
<com.google.android.material.button.MaterialButton
android:id="#+id/main_error_internet_retry_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:backgroundTint="#color/dark_blue"
android:padding="#dimen/padding_16"
android:text="#string/default_retry"
app:icon="#drawable/ic_autorenew"
app:iconGravity="textEnd"
app:iconPadding="#dimen/padding_4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/main_error_internet_text" />
<androidx.constraintlayout.widget.Group
android:id="#+id/main_error_group"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="main_error_internet_icon,main_error_internet_retry_button,main_error_internet_text" />
<FrameLayout
android:id="#+id/main_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/main_bottom_nav"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MainActivity.kt:
https://github.com/KirstenLy/AdviceCollector/blob/master/app/src/main/java/com/kirstenly/advice_collector/ui/activity_main/MainActivity.kt
package com.kirstenly.advice_collector.ui.activity_main
import android.os.Bundle
import android.widget.Toast
import androidx.core.view.ViewCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import com.google.android.play.core.review.ReviewManagerFactory
import com.kirstenly.advice_collector.R
import com.kirstenly.advice_collector.Screens
import com.kirstenly.advice_collector.contracts.NavigationEventConsumer
import com.kirstenly.advice_collector.other.helpers.DayNightThemeHelper
import com.kirstenly.advice_collector.other.helpers.InsetHelper
import com.kirstenly.advice_collector.ui.activity_main.router.MainActivityRouter
import com.kirstenly.sdk.extensions.*
import com.kirstenly.sdk.other.navigation.BaseScreen
import dagger.android.support.DaggerAppCompatActivity
import kotlinx.android.synthetic.main.activity_main.*
import javax.inject.Inject
/** Host activity for application */
class MainActivity : DaggerAppCompatActivity(R.layout.activity_main), NavigationEventConsumer {
#Inject lateinit var router: MainActivityRouter
#Inject lateinit var viewModel: MainActivityViewModel
#Inject lateinit var insetHelper: InsetHelper
#Inject lateinit var dayNightThemeHelper: DayNightThemeHelper
override fun onNavigationEvent(screen: BaseScreen) {
router.navigateTo(screen)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setTheme(R.style.AppTheme)
initViews()
initObservers()
}
private fun initViews() {
ViewCompat.setOnApplyWindowInsetsListener(window.decorView){ v, insets ->
Toast.makeText(this, "INSETS HANDLED", Toast.LENGTH_LONG).show()
insets
}
// main_bottom_nav.applySystemWindowInsetsToMargin(bottom = true, top = true, left = true, consume = true)
// root_root.requestApplyInsetsWhenAttached()
// Insetter.builder()
// .applySystemWindowInsetsToPadding(Side.LEFT)
// .applyToView(main_bottom_nav)
//
// setWindowTransparency { statusBarSize, navigationBarSize ->
// insetHelper.topInset = statusBarSize
// insetHelper.bottomInset = navigationBarSize
// main_bottom_nav.updatePaddingFromPx(bottom = navigationBarSize)
// main_snackbar_anchor.layoutParams.height = navigationBarSize
// }
with(main_bottom_nav) {
setOnNavigationItemSelectedListener {
when (it.itemId) {
R.id.bottom_home -> router.navigateTo(Screens.HomeScreen)
R.id.bottom_favorite -> router.navigateTo(Screens.FavoriteAdvicesScreen)
R.id.bottom_login -> router.navigateToLoginOrUserAdvices()
R.id.bottom_new_advice -> router.navigateToNewAdvice()
R.id.bottom_day_night -> dayNightThemeHelper.setDayOrNightMode()
}
true
}
removeItemIconTintList()
setIconOnItem(R.id.bottom_day_night, dayNightThemeHelper.getDayOrNightModeIcon())
}
main_error_internet_retry_button.setOnClickListener {
viewModel.retryInternetConnection()
}
}
private fun initObservers() {
viewModel.errorStateLiveData.observe(this, { errorState ->
main_error_group.isVisible = errorState != ErrorState.NONE
main_icon.isVisible = errorState == ErrorState.NONE
main_error_internet_retry_button.isEnabled = errorState != ErrorState.RETRYING
val errorText = when (errorState) {
ErrorState.RETRYING -> getString(R.string.main_activity_error_retrying)
ErrorState.NO_INTERNET_ERROR -> getString(R.string.main_activity_error_loading)
ErrorState.UPDATE_DATA_ERROR -> getString(R.string.main_activity_error_update)
ErrorState.NONE -> null
}
main_error_internet_text.text = errorText
})
viewModel.navigationEventLiveData.observe(this, router::navigateTo)
viewModel.isDataPreparedLiveData.observe(this, { isDataPrepared ->
main_icon.isGone = isDataPrepared
})
}
// TODO: Поддержать inAppReview, будет возможно только после залива в GP
private fun initAppReview() {
val manager = ReviewManagerFactory.create(this)
val request = manager.requestReviewFlow()
request.addOnCompleteListener { request ->
if (request.isSuccessful) {
// We got the ReviewInfo object
val reviewInfo = request.result
val flow = manager.launchReviewFlow(this, reviewInfo)
flow.addOnCompleteListener { a ->
a.isComplete
// The flow has finished. The API does not indicate whether the user
// reviewed or not, or even whether the review dialog was shown. Thus, no
// matter the result, we continue our app flow.
}
} else {
// There was some problem, continue regardless of the result.
}
}
}
override fun onBackPressed() {
if (!router.handleOnBackPressedByCurrentFragment()) {
if (!supportFragmentManager.isBackStackEmpty()) {
super.onBackPressed()
} else {
finish()
}
}
}
}
In order for the insets listener to be called when using:
ViewCompat.setOnApplyWindowInsetsListener(main_root) { v, insets ->
...
insets
}
the main_root view in the layout.xml needs to have the android:fitsSystemWindows="true" defined, or you need to have the following in your app-theme:
android:fitsSystemWindows="true"
Ok, fixed when i add:
true
in my application theme.
If i understand it correctly, without this attribute decorView consume insets.
¯_(ツ)_/¯
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.
There are more fields below the keyboard. This happened when i updated the support library. I know it's Kotlin but it looks almost the same as java. How do I fix this issue?
This is what it looks like:
My code:
class ProjectsEditBottomSheetFragment(val privateID: String,
val publicID: String) : BottomSheetDialogFragment() {
private val mBottomSheetBehaviorCallback = object : BottomSheetBehavior.BottomSheetCallback() {
override fun onStateChanged(bottomSheet: View, newState: Int) {
if (newState == BottomSheetBehavior.STATE_HIDDEN) {
dismiss()
}
}
override fun onSlide(bottomSheet: View, slideOffset: Float) {
if (slideOffset < -0.15f) {
dismiss()
}
}
}
override fun setupDialog(dialog: Dialog, style: Int) {
super.setupDialog(dialog, style)
val view = View.inflate(context, R.layout.projects_edit_sheet, null)
dialog.setContentView(view)
dialog.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
val params = (view.parent as View).layoutParams as CoordinatorLayout.LayoutParams
val behavior = params.behavior
if (behavior != null && behavior is BottomSheetBehavior<*>) {
behavior.setBottomSheetCallback(mBottomSheetBehaviorCallback)
}
// Get and set values
val realm = Realm.getDefaultInstance()
val realmObject = realm.where(ProjectsRealmObject::class.java)
.equalTo("privateID", privateID)
.findFirst()
realm.beginTransaction()
view.title_input.text = SpannableStringBuilder(realmObject.title)
view.description_input.text = SpannableStringBuilder(realmObject.description)
view.public_checkbox.isChecked = realmObject.isPublic
realm.commitTransaction()
// Keyboard
view.title_input.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(view.title_input, InputMethodManager.SHOW_FORCED)
} else {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.title_input.windowToken, 0)
}
}
view.description_input.onFocusChangeListener = View.OnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).showSoftInput(view.description_input, InputMethodManager.SHOW_FORCED)
} else {
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(view.description_input.windowToken, 0)
}
}
// Click listners
view.public_layout.setOnClickListener { view.public_checkbox.toggle() }
view.cancel.setOnClickListener {
view?.hideKeyboard()
dismiss()
}
view.save.setOnClickListener {
view?.hideKeyboard()
// Save to realm
realm.beginTransaction()
realmObject.title = if (view.title_input.text.toString() == "") getString(R.string.unnamed) else view.title_input.text.toString()
realmObject.description = view.description_input.text.toString()
realmObject.isPublic = view.public_checkbox.isChecked
realmObject.synced = false
realmObject.updatedRealm = TimeUnit.MILLISECONDS.toSeconds(System.currentTimeMillis()).toString() + ""
realm.commitTransaction()
ProjectsSync(context)
toast("Sparat")
dismiss()
}
}
}
xml:
<ScrollView
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:background="#color/white"
app:layout_collapseMode="none"
app:behavior_hideable="false"
app:behavior_peekHeight="100dp"
app:layout_behavior="android.support.design.widget.BottomSheetBehavior"
style="#style/Widget.Design.BottomSheet.Modal">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/content">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="16dp"
android:paddingLeft="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="8dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/edit_info_placeholder_title"
android:id="#+id/title_input"/>
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingRight="16dp"
android:paddingLeft="16dp">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/edit_info_placeholder_description"
android:id="#+id/description_input"/>
</android.support.design.widget.TextInputLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:clickable="true"
android:background="#drawable/click"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:id="#+id/public_layout">
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:id="#+id/public_checkbox"
android:layout_marginRight="8dp"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/edit_info_placeholder_is_public"
android:layout_gravity="center_vertical"
style="#style/textMedium"/>
</LinearLayout>
<!-- Buttons -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="right"
android:paddingBottom="8dp">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/edit_info_button_cancel"
android:id="#+id/cancel"
style="#style/Widget.AppCompat.Button.Borderless.Colored"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/edit_info_button_save"
android:id="#+id/save"
style="#style/Widget.AppCompat.Button.Borderless.Colored"/>
</LinearLayout>
</LinearLayout>
</FrameLayout>
I found the solution for 27 api. So the reason why keyboard hides view even with SOFT_INPUT_ADJUST_RESIZE is that the windowIsFloating is set for Dialogs.
The most convenient way that I found to change this is by creating style:
<style name="DialogStyle" parent="Theme.Design.Light.BottomSheetDialog">
<item name="android:windowIsFloating">false</item>
<item name="android:statusBarColor">#android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustResize</item>
</style>
And set this in onCreate method of your BottomSheetDialogFragment:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setStyle(DialogFragment.STYLE_NORMAL, R.style.DialogStyle)
}
This is how it looks on my device:
I tried all of answers in this topic but nothing helped. I looked through many sites and found only one solution that working for me.
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState)
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
dialog.setOnShowListener {
Handler().post {
val bottomSheet = (dialog as? BottomSheetDialog)?.findViewById<View>(R.id.design_bottom_sheet) as? FrameLayout
bottomSheet?.let {
BottomSheetBehavior.from(it).state = BottomSheetBehavior.STATE_EXPANDED
}
}
}
return dialog
}
Original solution
You can use the next class:
import android.graphics.Rect;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.BottomSheetBehavior;
import android.support.design.widget.BottomSheetDialog;
import android.support.design.widget.BottomSheetDialogFragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
public class TestBottomSheetDialog extends BottomSheetDialogFragment {
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View fragmentView = LayoutInflater.from(getContext()).inflate(R.layout.fragment_bottom_sheet, container, false);
if (getDialog().getWindow() != null) {
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
}
if (getActivity() != null) {
View decorView = getActivity().getWindow().getDecorView();
decorView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
Rect displayFrame = new Rect();
decorView.getWindowVisibleDisplayFrame(displayFrame);
int height = decorView.getContext().getResources().getDisplayMetrics().heightPixels;
int heightDifference = height - displayFrame.bottom;
if (heightDifference != 0) {
if (fragmentView.getPaddingBottom() != heightDifference) {
fragmentView.setPadding(0, 0, 0, heightDifference);
}
} else {
if (fragmentView.getPaddingBottom() != 0) {
fragmentView.setPadding(0, 0, 0, 0);
}
}
});
}
getDialog().setOnShowListener(dialog -> {
BottomSheetDialog d = (BottomSheetDialog) dialog;
View bottomSheetInternal = d.findViewById(android.support.design.R.id.design_bottom_sheet);
if (bottomSheetInternal == null) return;
BottomSheetBehavior.from(bottomSheetInternal).setState(BottomSheetBehavior.STATE_EXPANDED);
});
return fragmentView;
}
}
This is working for me
public class CustomBottomSheetDialogFragment extends BottomSheetDialogFragment
{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle
savedInstanceState) {
View v = inflater.inflate(R.layout.content_dialog_bottom_sheet, container, false);
getDialog().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
return v;
}
This solution worked for me after spending 5 hours without luck:
Step #1:
Add this code to your styles.xml (located in res\values folder)
<style name="CustomizedBottomDialogStyle">
<item name="android:windowBackground">#android:color/transparent</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:backgroundDimAmount">0.7</item>
<item name="android:windowIsFloating">false</item>
<item name="android:windowAnimationStyle">#android:style/Animation.Dialog</item>
<item name="android:statusBarColor">#android:color/transparent</item>
<item name="android:windowSoftInputMode">adjustResize</item>
<item name="android:background">#android:color/transparent</item>
</style>
The key here is to set android:windowIsFloating -> false, if it is true your code will not work! Therefor i used rather android:backgroundDimEnabled and android:backgroundDimAmount to make background looks transparent with beautiful overlay.
Step #2:
Write this function to adjust it programmatically (note it is not optional, you need to perform both steps #1 and #2):
private fun showDialog() {
BottomSheetDialog(requireActivity(), R.style.CustomizedBottomDialogStyle).apply {
window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE)
setOnShowListener {
Handler().post {
val bottomSheet = findViewById<View>(R.id.design_bottom_sheet) as? FrameLayout
bottomSheet?.let {
BottomSheetBehavior.from(it).state = STATE_EXPANDED
}
}
}
setContentView(R.layout.dialog_layout)
// Your code goes here....
show()
}
}
The answer to the highest score is partly right. In my case, 90% of the view is visible after the same style is set. Finally, I made it completely visible through the following solution:
editText.setOnFocusChangeListener { v, hasFocus ->
if (hasFocus) {
(this#****BottomSheetDialogFragment.dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED
}
}
add this to your styles
<style name="DialogStyle">
<item name="android:windowBackground">#android:color/transparent</item>
<item name="colorPrimaryDark">#android:color/transparent</item>
</style>
then in your's bottom sheet dialog's onCreate() add
setStyle(DialogFragment.STYLE_NO_FRAME, R.style.DialogStyle);
also don't forget to add to dialog's setupDialog() method
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);