How can I set button positions from preference screen? - android

Hello StackOverflow users!
I'm trying to do someting complicated (for a noob like me XD) and I have some questions.
How can I set my main screen buttons position form a preference screen? Like, if I have 2 buttons to inverse their position...
I tried to do with the code below in kotlin (please don't judge me, already told u I'm noob af) but I wanna have an idea how to make this wotk. XD
findPreference<Preference>("buttonpositions")!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { preference: Preference, _: Any? ->
val checked = (preference as CheckBoxPreference).isChecked
if(!checked) {
val myBtn = R.id.cleanBtn as Button
myBtn.setPadding(0, 100, 0, 0)
}
true
}
and the buttons is something like this:
<com.google.android.material.button.MaterialButton
style="#style/Widget.Material3.Button.UnelevatedButton"
android:id="#+id/cleanBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_columnWeight="1"
android:minWidth="0dp"
app:layout_rowWeight="1"
android:tooltipText="#string/tooltip_clean_button"
app:iconGravity="top"
app:icon="#drawable/ic_broom"
app:iconTint="#android:color/system_accent1_400"
android:textColor="#android:color/system_accent1_400"
android:text="#string/clean"/>
<com.google.android.material.button.MaterialButton
style="#style/Widget.Material3.Button.UnelevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_columnWeight="1"
app:layout_rowWeight="1"
android:id="#+id/analyzeBtn"
app:iconGravity="top"
app:icon="#drawable/ic_search"
android:tooltipText="#string/analyze_tooltip"
app:iconTint="#android:color/system_accent1_700"
android:textColor="#android:color/system_accent1_700"
android:text="#string/analyze"
android:minWidth="0dp"
app:backgroundTint="#android:color/transparent"/>
First. I'm preety sure my "code" isn't right composed + idk if I need to add someting to buttons itself.
Thanks for your time :D

Why your code won't work:
R.id.cleanBtn is an Int ID, not a button. Trying to cast an Int into a Button will crash the app. (Casting is not the same thing as converting--it's promising the compiler that something is also something else already.) IDs are used to search existing view hierarchies for the actual view you want by using something like rootView.findViewById<Button>(R.id.cleanBtn), but this strategy isn't practical here since you don't have direct access to the view hierarchy.
Trying to change the button from the preference listener would only work when you change it. If the user rotates the screen or the exits the app and comes back, the main screen's view will be recreated back in the original position.
The right way to do this is not on the settings screen, but on the main screen. Preferences that are set on a preference screen by default are saved to the "default" shared preferences, and they are saved persistently, so they can be read from anywhere else in your app.
What I would do is read the current value of the preference in the onResume() function of the activity or fragment of your main screen. onResume() will get called every time that screen reappears, so it gives it an opportunity to check and apply the setting the first time the view appears and every time you return from the preferences screen, where the value may have changed.
//in main screen class
private var isButtonPositionFlipped = false
// Only need this function if in a Fragment
// Need to reset the property in case Fragment has recreated its view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//...
isButtonPositionFlipped = false
}
override fun onResume() {
super.onResume()
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this)
// use requireContext() instead of this if we're in a Fragment class
val shouldFlipButtonPosition = sharedPreferences.getBoolean("buttonpositions", false)
if (isButtonPositionFlipped != shouldFlipButtonPosition) {
isButtonPositionFlipped = shouldFlipButtonPosition
val cleanBtn = findViewById<Button>(R.id.cleanBtn)
val analyzeBtn = findViewById<Button>(R.id.analyzeBtn)
// use requireView().findViewByID if in a Fragment class
if (isButtonPositionFlipped) {
// swap the buttons here
} else {
// swap them back here
}
}
}
As for how to swap them, that depends on the type of layout they're in. If they're in a ConstraintLayout, I think it will be pretty complicated, but I haven't tried it before. You might try putting each of the two buttons inside FrameLayouts. Then you could get the FrameLayout views and swap the view children between them.
Edit: Actually, I think it would be easier in this case to just make your buttons more generically named. Instead of swapping them, just change their text and click listeners to the opposites.

Related

How to refer to a button that is in the list Kotlin

I am trying to create an app in android studio. I've only recently started to get interested in this and ran into a problem. As planned, for each move in the game, the player presses several buttons, I would like to put the id of these buttons in a separate list, and when necessary, use this list to change the color of all those buttons that are in this list
What i did:
I have the list->
val move_list: MutableList<Any> = mutableListOf()
When the player presses the button, I add its id to move_list
fun for_btn_buba2(view: View){
move_list.add(buba2.id)
In activity_main.xml my button seems like:
<Button
android:id="#+id/buba2"
android:text="Buba 2"
...
android:onClick="for_btn_buba2"/>
And on click of another button i wanted to insert code like this:
move_list[0].setBackgroundColor(Color.parseColor(colorString:"#FFFC9D45"))
move_list[0] means id for button buba2
In python it can be, but it isnt python)
How can I change the color of the button through the list index with the buttons?
Firstly I suggest you indicate the type of what is in the list, which is Int so like
val move_list: MutableList<Int> = mutableListOf()
Then you can probably do this in the onClick of the other button
findViewById<View>(move_list[0]).setBackgroundColor(Color.parseColor("#FFFC9D45"))
or to do it to all
move_list.forEach {
findViewById<View>(it).setBackgroundColor(Color.parseColor("#FFFC9D45"))
}
I also notice you directly refer to buba2 in it the for_btn_buba2. I have the feeling that you are writing this function for every buba. This is unnecessary. You can get the id from the view parameter because that is in fact the same id. So do
fun for_btn_buba(view: View){
move_list.add(view.id)
}
then you can give each buba the same for_btn_buba as android:onClick
Alternatively you don't even work with ids at all and make it a
val move_list: MutableList<View> = mutableListOf()
and then do
fun for_btn_buba(view: View){
move_list.add(view)
}
and then you can actually change the background like you wrote it:
move_list[0].setBackgroundColor(Color.parseColor("#FFFC9D45"))
or
move_list.forEach {
it.setBackgroundColor(Color.parseColor("#FFFC9D45"))
}

How to make view visible to only the item clicked in recyclerview android

I am building a music player app.
All songs are displayed in a recyclerview.
On click of any song, I want to:
Display a view (spectrum/wave) that indicates that the song is currently playing.
This should only be visible to current playing song.
RcvLayoutAllSongs.xml
<TextView
android:id="#+id/txt_artist_name"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="2dp"
android:textColor="#color/artist_name_color"
android:textSize="15sp"
app:layout_constraintBottom_toBottomOf="#+id/constraintLayout2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
android:layout_marginEnd="30dp"
app:layout_constraintStart_toStartOf="#+id/txt_song_name"
app:layout_constraintTop_toBottomOf="#+id/txt_song_name"
app:layout_constraintVertical_bias="0.0" />
<es.claucookie.miniequalizerlibrary.EqualizerView
android:id="#+id/equalizer_view"
android:layout_width="30dp"
android:layout_height="30dp"
app:animDuration="3500"
android:visibility="gone"
app:foregroundColor="#color/bottom_music_stroke"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/txt_song_name"
app:layout_constraintTop_toTopOf="parent" />
All I want to do is:-
Make equalizer_view visible to only the item clicked, and if possible change the color of textview of the item clicked
SongsAdapter
override fun onBindViewHolder(holder: AllSongsViewHolder, position: Int) {
val song = songs[position]
with(holder) {
with(songs[position]) {
binding.txtSongName.text = song.title
binding.txtArtistName.text = song.subtitle
//on click
binding.rootLyt.setOnClickListener {
onItemClickListener?.let { click ->
click(song)
//here I will handle click to display the view on only selected item
}
}
}
}
}
private var onItemClickListener: ((Songs) -> Unit)? = null
fun setOnItemClickListener(listener: (Songs) -> Unit) {
onItemClickListener = listener
}
override fun getItemCount() = songs.size
The problem with this approach is I also want to make the view invisible when the song is paused..And this is handled in the fragment...Not the adapter
SongFragment
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.pauseView.setOnClickListener {
//this is where I pause the song when the song is clicked from the adapter, I
also want to make the wave/view invisible from the adapter but don't know how
mainViewModel.pauseSong
}
}
In onBindViewHolder, check if that item is the currently playing song, and style it accordingly (font colours, wave image etc).
Make your click listener call some setCurrentSong() function that updates which song is the currently playing one, and make that call notifyDataSetChanged to make the RecyclerView update the items (which will call onBindViewHolder again).
The nice thing here is you can call this function from other places too, like when the fragment is first being set up, so you don't need a click to set the current song state. Just keep a reference to your Adapter and call adapter.setCurrentSong - plays nice with ViewModels etc! Keeps the logic out of the UI
You can take the same approach with your pausing - add a setPaused(Boolean) function on your adapter. When you pause or start playback, call that function, which sets a paused state and calls notifyDataSetChanged(). Make your onBindViewHolder check whether it's paused or not when deciding whether to display the image.
You could also use a property with a setter if you like:
var paused: Boolean = false
set(value) {
field = value
notifyDataSetChanged()
}
or a Delegates.observable - basically you just need to make sure the RecyclerView gets notified that it needs to update, whenever the value changes.
oh, if mainViewModel actually holds things like player state, and you have things like a LiveData that holds a paused state, one that holds a currentSongId or whatever, then you'd typically wire it up like this:
viewModel.currentSong -> observer -> calls adapter.setCurrentSong
recyclerView.item -> click -> calls viewModel.setCurrentSong
That way, the UI (the RecyclerView) responds to changes in the state, as described by the VM. When it pushes an event (like a song change) it just sets that on the view model because that's where the state is held. And when that state changes, the observer will run and update the UI. It's like a roundabout way of doing things, instead of the RecyclerView updating itself directly, but it can be cleaner and easier to reason about once you get your head around it! You don't need to worry about where the event is coming from, because it's always coming from the VM

Keep updating the same activity with different data on button click

I have an android application that asks users a few questions, kind of a survey. Each question has an YES and a NO Button. So when the user presses either YES or NO button, a new Button to go to the next question becomes visible over the YES and NO buttons. When the user clicks on the next question button it changes the textview for the question to the next question text as well as the imageView for that question changes.
I am making use of DataBinding along with LiveData.
I have created a list of images used and another list for the questions as follows in my ViewModel class.:
//list of images : Question 4 does not have an image
imgs= listOf(
R.drawable.imgQ1, // 1
R.drawable.imgQ2,// 2
R.drawable.imgQ3)//3
//list of questions
qs= listOf(
"What is .....?",//1
"Who is ....?",//2
"How to .....?",//3
"Why is .....?")//4
I created a ViewState to handle when to show which button and UI:
sealed class MyUIState{
data class UIState(
val policyText: Boolean, // determines if the policy textview should be visible or not
val nextQuestionButton: Boolean,// determines if the next question button should be visible or not
val nextQuestionNewImage: Int = R.drawable.imgQ1,//what image is used for the next question
val nextQuestion: String //what is the next question from the list
) : MyUIState()
}
I also have a BindingAdapter class which handles the visibility and text changes and image changes
(basically this - I have added these function to my layout file with data binding):
#BindingAdapter("btnNextQ")
fun Button.btnNextQ(myState: MyUIState?){
visibility = if (myState is MyUIState.UIState) {
if (state.nextQuestionButton)
View.VISIBLE
else
View.GONE
} else
visibility
}
#BindingAdapter("newImage")
fun ImageView.newImage(myState: MyUIState?){
if(myStateis MyUIState.UIState){
setImageResource(state.nextQuestionNewImage)
}
}
And then in my ViewModel class I have a function for the click event on either the YES or NO Button where I call the UIState like this:
//I have this declared at the top of my class (global)
private val myViewState = MutableLiveData<MyUIState>()
//this the button click for yes:
fun YesClick(){
myViewState.postValue(MyUIState.UIState(
policyText= true,
nextQuestionButton = true,
nextQuestion = qs[0])) //still keep the text for question1
}
Now, I want the image and question to change after the user clicked the next question button. So basically what happens is that after clicking next question, the next question buttons goes invisible as well as the other components keeping the YES and NO buttons and the updated question and image. This is my code for the click event on the next question button:
fun nextQuestion(){
myViewState .postValue(MyUIState.UIState(
nextQuestionNewImage = imgs[1],
nextQuestion = qs[1],
nextQuestionButton = false,
policyText= false))
}
So the state does move to the next question with the necessary items being visible and invisible, but I am struggling to keep it going to the next questions in the list. I can go from question1 to question2 but not sure how to keep doing that until the end of the list of questions.
So basically, I just want to keep updating the same activity with the new data.
Any help or advice will be highly appreciated.
Not sure if I understood your real problem. Maybe if you share more of your code we could help you better.
Anyway, I would recommend you to have two states for your view (activity in your case), one state for the question itself containing both yes and no buttons, question text and the right image. And another state for your "move to next" question which contains the question text, button and whatever you want.
Then your view needs to take care of each state. In the case, you need to deal with one state while dealing against the other state, for example showing one button and hiding the ones you don't need.
So whenever you click on the yes button, the VM will get the event, check everything and then emit an event back to the View with a new state, and so on.
It's quite the same thing you did, but using two states will help you to tackle each state and ease off your code

Mimic preference screen and displaying key-value attributes

How would I mimic the design of a preference screen or the "edit Contact" screen of android, without doing too much work?
I have to display ~12 attributes for an object and can't get my head around how to display them in a good way.
not for every attribute an obvious icon exists -> we want labels?
displaying textviews underneath other textviews that have an icon looks horrible, except if you want to manually add margins. (indenting)
PreferenceFragment has a kind of "Title" that can be used as Label and a Value, which would be even more perfect, as I have some interactive elements (item picker).
But using preferencefragment without SharedPreferences and instead to display arbitrary objects is obviously a (bad?) hack.
I've searched a lot and didn't come to a conclusion. Is there an obvious way I missed ?
You can actually create Settings Activity (with full functionality) without much effort.
Go to File > New > Activity > Settings Activity.
It will create the entire functionality - you only need to specify Switches, Lists in
res > xml > prefs_.xml
The values that you set via screen will be saved in SharedPreferences under the specified key.
To intercept the values that are configured via UI to the SharedPreferences you can add this to the SettingsActivity:
class SettingsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.pref_general, rootKey)
findPreference<ListPreference>("system_theme")?.setOnPreferenceClickListener {
val value = (it as? ListPreference)?.value
//send value to server
return#setOnPreferenceClickListener true
}
}
}

Android : Right place to keep logic for view visibility

I am implementing a bottom sheet menu. I am using viemodel that is used for databinding in the layout file. The viewmodel has a livedate of type int "selectedOption". I have 5 rows in the menu and each row has an Image. I want to have a logic that this image is visible for row 1 if selectedOption = 1.
What is the right way to implement this logic.
I can have in my layout file:
android:visibility="#{safeUnbox(vm.selectedOption) == 1 ? View.VISIBLE : View.GONE}"
In this case, which is the right place to define the constants 1, 2 etc. It is already defined in my source file. Can i access it in layout file
Or should I have a livedata for each of that in the viewmodel
like: isRow1Selected
or is there a better way to do it
There are going to be plenty of people with different opinions on what to do here. A big trend in modern mobile development is driving ui from state. So you certainly could keep this state of something being selected or not in the view model and have the view respond to changes by observing that state. That is personally what I would do. If you are worried about the 1 and 0 and having janky numbers in your code you could define an enum to hold it and that could by the type of your live data.
ex:
enum class Mode(val value: Int) {
SELECTED(1),
UNSELECTED(0)
}
viewModel.state.observe(viewLifeCycleOwner, Observer { newState ->
when(newState) {
Mode.SELECTED -> //Do thing
Mode.UNSELECTED -> //do thing
}
})

Categories

Resources