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);
Related
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'm trying to set a height peek from which the dialog must start, then the user if dragged should be able to expand it, the issue is that in any case the bottomsheet initial state get half of screen.
The BottomSheet looks like this:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/varianti_preferite_bottom_sheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:elevation="8dp"
app:behavior_peekHeight="200dp"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<ImageButton
android:id="#+id/closeButton"
style="#style/Widget.AppCompat.ImageButton"
android:minWidth="75dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#drawable/bottom_border_radius"
android:contentDescription="#string/bottom_sheet_close_button"
android:padding="10dp"
android:layout_marginBottom="16dp"
app:srcCompat="#drawable/ic_baseline_close" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/variantiRecycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="16dp"
android:clipToPadding="false"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="4"
tools:listitem="#layout/varianti_preferite">
</androidx.recyclerview.widget.RecyclerView>
</LinearLayout>
What i've tried:
I've tried to override onCreateDialog and set HalfExpanded ratio manually but nothing changed:
#NonNull
#Override
public Dialog onCreateDialog(#Nullable Bundle savedInstanceState) {
BottomSheetDialog dialog = (BottomSheetDialog) super.onCreateDialog(savedInstanceState);
dialog.setOnShowListener(dialog1 -> {
BottomSheetDialog d = (BottomSheetDialog) dialog1;
FrameLayout bottomSheet = (FrameLayout) d.findViewById(com.google.android.material.R.id.design_bottom_sheet);
BottomSheetBehavior<FrameLayout> bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet);
bottomSheetBehavior.setHalfExpandedRatio(0.2f);
bottomSheetBehavior.setFitToContents(false);
});
return dialog;
}
Kotlin answers are welcome too.
The way that successfully works for me:
Add viewTreeObserver.addOnGlobalLayoutListener in onCreate to setup your BottomSheet behavior:
binding.root.viewTreeObserver.addOnGlobalLayoutListener {
setupBottomSheetBehaviorForView(binding.bottomFragmentContainer)
}
Setup behavior:
private fun setupBottomSheetBehaviorForView(view: FragmentContainerView) {
val behavior = BottomSheetBehavior.from(view)
val screenHeight =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
windowManager.currentWindowMetrics.bounds.height()
} else {
val metrics = DisplayMetrics()
windowManager.defaultDisplay.getMetrics(metrics)
metrics.heightPixels
}
val toolbarLocation = IntArray(COORDINATES_ARRAY_SIZE) // was added to avoid overlapping the toolbar
binding.toolbar.getLocationOnScreen(toolbarLocation) // with the bottom sheet (in case of full screen activity)
behavior.apply {
peekHeight = (screenHeight * BOTTOM_SHEET_PEEK_PERCENT).toInt()
isFitToContents = false
halfExpandedRatio = BOTTOM_SHEET_PEEK_PERCENT
expandedOffset = toolbarLocation[1] // it's optional
}
}
where BOTTOM_SHEET_PEEK_PERCENT is Float const, for 40% initial peek height:
const val BOTTOM_SHEET_PEEK_PERCENT = 0.40f
I had struggled with a similar problem, my solution was to set layoutParams
Hope it works for you as well
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.apply {
bottomSheetConstraintLayout.layoutParams.height =
resources.displayMetrics.heightPixels
}
}
I've solved the issue by setting peek height in BottomSheet style.
So in style.xml
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<item name="bottomSheetDialogTheme">#style/AppBottomSheetDialogTheme</item>
</style>
<style name="AppBottomSheetDialogTheme" parent="Theme.Design.Light.BottomSheetDialog">
<item name="bottomSheetStyle">#style/AppModalStyle</item>
</style>
<style name="AppModalStyle" parent="Widget.Design.BottomSheet.Modal">
<item name="android:background">#drawable/bottom_sheet_rounded</item>
<item name="behavior_peekHeight">#dimen/peek_height</item>
</style>
Where in dimens.xml
<dimen name="peek_height">200dp</dimen>
But the issue is that it will set the peek_height to all BottomSheets of the application.
I'm trying to make my app show Dialogs, and so far it's working fine except for when I change themes (I'm using DayNight).
My Dialog is using a custom style which also has parent DayNight (Theme.AppCompat.DayNight.Dialog). When the theme changes (manually or automatically), and I open the dialog after the theme change, the app will crash.
The error I'm getting is a NullPointerException.
here's the logcat
2019-09-19 13:28:37.721 15733-15733/com.jerrwu.template E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.jerrwu.template, PID: 15733
kotlin.KotlinNullPointerException
at com.jerrwu.template.SettingsFragment$onPreferenceChangeListener$1.onSharedPreferenceChanged(SettingsFragment.kt:71)
at android.app.SharedPreferencesImpl$EditorImpl.notifyListeners(SharedPreferencesImpl.java:607)
at android.app.SharedPreferencesImpl$EditorImpl.apply(SharedPreferencesImpl.java:489)
at androidx.preference.Preference.tryCommit(Preference.java:1632)
at androidx.preference.Preference.persistBoolean(Preference.java:1931)
at androidx.preference.TwoStatePreference.setChecked(TwoStatePreference.java:92)
at androidx.preference.TwoStatePreference.onClick(TwoStatePreference.java:68)
at androidx.preference.Preference.performClick(Preference.java:1182)
at androidx.preference.Preference.performClick(Preference.java:1166)
at androidx.preference.SwitchPreference.performClick(SwitchPreference.java:195)
at androidx.preference.Preference$1.onClick(Preference.java:181)
at android.view.View.performClick(View.java:6599)
at android.view.View.performClickInternal(View.java:6576)
at android.view.View.access$3100(View.java:780)
at android.view.View$PerformClick.run(View.java:25926)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6718)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
from styles.xml
<style name="DialogTheme" parent="Theme.AppCompat.DayNight.Dialog">
<item name="android:windowNoTitle">true</item>
<item name="android:windowFrame">#null</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:backgroundTint">#color/colorMain</item>
<item name="android:windowBackground">#drawable/rounded_dialog_box</item>
<item name="android:layout_width">match_parent</item>
<item name="android:windowMinWidthMajor">85%</item>
<item name="android:windowMinWidthMinor">85%</item>
</style>
code to show the dialog
fun showDialog(title: String, textYes: String, textNo: String, activity: Activity) {
val dialog = Dialog(activity, R.style.DialogTheme)
dialog.requestWindowFeature(Window.FEATURE_NO_TITLE)
dialog.setCancelable(false)
dialog.setContentView(R.layout.custom_dialogue)
val body = dialog.findViewById(R.id.dialogueBody) as TextView
body.text = title
val yesBtn = dialog.findViewById(R.id.yesBtn) as Button
yesBtn.text = textYes
val noBtn = dialog.findViewById(R.id.noBtn) as Button
noBtn.text = textNo
if (textYes == "") { yesBtn.visibility = View.GONE }
yesBtn.setOnClickListener {
dialog.dismiss()
}
noBtn.setOnClickListener { dialog.dismiss() }
dialog.show()
}
custom_dialogue.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"
android:orientation="vertical"
android:layout_width="match_parent"
android:paddingTop="14dp"
android:paddingStart="24dp"
android:paddingEnd="24dp"
android:paddingBottom="4dp"
android:layout_height="wrap_content">
<TextView
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:id="#+id/dialogueBody"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="hie..."
android:textSize="16sp"
android:textColor="#color/colorFont"
/>
<Button
app:layout_constraintTop_toBottomOf="#id/dialogueBody"
android:layout_marginBottom="0dp"
android:layout_marginTop="12dp"
android:textAllCaps="false"
android:textSize="12sp"
android:minWidth="40dp"
android:minHeight="40dp"
android:id="#+id/yesBtn"
android:fontFamily="#font/productsansbold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:text="#string/button_yes"
android:textColor="#color/red"
android:background="?attr/selectableItemBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
app:layout_constraintTop_toBottomOf="#id/dialogueBody"
android:layout_marginBottom="0dp"
android:layout_marginTop="12dp"
app:layout_constraintEnd_toStartOf="#id/yesBtn"
app:layout_constraintBottom_toBottomOf="parent"
android:textSize="12sp"
android:textAllCaps="false"
android:minWidth="40dp"
android:minHeight="40dp"
android:id="#+id/noBtn"
android:layout_marginEnd="12dp"
android:fontFamily="#font/productsansbold"
android:text="#string/button_no"
android:textColor="#color/colorAccent"
android:background="?attr/selectableItemBackground"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</androidx.constraintlayout.widget.ConstraintLayout>
the fragment creating the dialog:
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.preferences, rootKey)
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(activity)
val darkToggle = sharedPreferences.getString("dark_toggle", "2")?.toInt()
val darkPreference = findPreference("dark_toggle") as ListPreference?
val versionPreference = findPreference("version") as Preference?
val namePreference = findPreference("name") as EditTextPreference?
var versionStr = "Error"
sharedPreferences.registerOnSharedPreferenceChangeListener(onPreferenceChangeListener)
preferenceScreen.removePreference(namePreference)
when (darkToggle) {
-1 -> darkPreference!!.summary = "Follow System"
0 -> darkPreference!!.summary = "Set by Battery Saver"
1 -> darkPreference!!.summary = "On"
2 -> darkPreference!!.summary = "Off"
}
try {
val pInfo = context?.packageManager?.getPackageInfo(activity?.packageName, 0)
versionStr = pInfo?.versionName.toString()
} catch (e: PackageManager.NameNotFoundException) {
e.printStackTrace()
}
versionPreference!!.summary = versionStr
}
var onPreferenceChangeListener: SharedPreferences.OnSharedPreferenceChangeListener =
SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
if (key == "dark_toggle") {
val darkPreference = findPreference(key) as ListPreference?
val darkToggle = sharedPreferences.getString(key, "2")?.toInt()
when (darkToggle) {
-1 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
darkPreference!!.summary = "Follow System"
}
0 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY)
darkPreference!!.summary = "Set by Battery Saver"
}
1 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES)
darkPreference!!.summary = "On"
}
2 -> {
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO)
darkPreference!!.summary = "Off"
}
}
}
else if (key == "bottomNavHide") {
InfoHelper.showDialog("An app restart is recommended after changing this setting.",
"", "OK", activity!!)
}
}
}
Thanks in advance to anyone trying to help!
I have created an extension of DialogFragment here:
class AlertDialogFragment(context: Context) : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = AlertDialog.Builder(activity).apply {
setStyle(STYLE_NO_FRAME, R.style.AlertDialogTheme)
setNeutralButton("Cancel", object : DialogInterface.OnClickListener{
override fun onClick(dialog: DialogInterface?, which: Int) {
}
})
setPositiveButton("Replace", object : DialogInterface.OnClickListener{
override fun onClick(dialog: DialogInterface?, which: Int) {
}
})
setNegativeButton("Delete", object : DialogInterface.OnClickListener{
override fun onClick(dialog: DialogInterface?, which: Int) {
}
})
}
return dialog.create()
}
}
As you can see above I have applied my AlertDialogTheme to the dialog:
<style name="AlertDialogTheme" parent="Theme.AppCompat.Light.Dialog.Alert">
<item name="android:background">#color/colorPrimary</item>
<item name="android:windowBackground">#color/colorPrimary</item>
</style>
However when my dialog is shown:
override fun onClick(v: View) {
if (v is AppCompatImageButton){
val dialog = AlertDialogFragment(this)
dialog.show(supportFragmentManager, "alertDialog")
}
}
The background of the dialog is black (~#333 I think). My #color/colorPrimary is white so that is what the background is meant to be.
Any idea what the problem is?
you can do it like that
class AlertDialogFragment : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container:ViewGroup?,savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_dialog, container, false)
view.delete.setOnClickListener { }
view.cancel.setOnClickListener { }
view.replace.setOnClickListener { }
return view
}
}
create xml layout fragment_dialog and design it as you want :
<?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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="200dp">
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:orientation="horizontal">
<Button
android:id="#+id/cancel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="cancel"
android:layout_weight="1"/>
<Button
android:id="#+id/replace"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="replace"
android:layout_weight="1"/>
<Button
android:id="#+id/delete"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="delete"
android:layout_weight="1"/>
</LinearLayout>
and finally show your dialog with your custom style just like that :
val dialog = AlertDialogFragment()
dialog.setStyle(DialogFragment.STYLE_NO_FRAME , R.style.AlertDialogTheme)
dialog.show(supportFragmentManager, "alertDialog")
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>