Android - Update view outside a custom control (from inside custom control) - android

I have a custom control with buttons (image) that should update an activity (fragment) control (big 0 string).
May be it is too simple but I don't know how to do it because onClickListener buttons are inside the custom control class. Which is the best approach?
Listener inside custom control class is:
binding.counterSelectorViewPrevious.setOnClickListener(OnClickListener {
decreaseValue()}
fun decreaseValue() {
if (mSelectedIndex > 0) {
val newSelectedIndex = mSelectedIndex - 1
setSelectedIndex(newSelectedIndex)
}
}
Activity control is just a TextView outside of custom control class.
<TextView
android:id="#+id/workout_length"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="0"
android:textAlignment="textEnd"
android:textAppearance="#style/TextAppearance.AppCompat.Display2"
tools:text="99:99:99" />
Thanks in advance.

You need to accept a listener in your custom view what get's some callback from your activity/fragment
something like this
private OnValueChangeListener onValueChangeListener;
public void addOnValueChangeListener(OnValueChangeListener onValueChangeListener)
{
this.onValueChangeListener = onValueChangeListener;
}
public void decreaseValue() {
onValueChangeListener.onValueChange(newValue);
}
public interface OnValueChangeListener {
void onValueChange(int newValue);
}
then you need to add invoke this somewhere in your activity/fragment (like in onCreate/onCreateView)
void setupListener() {
addOnValueChangeListener(newValue -> {
findViewById(R.id.workout_length).setText("" + newValue);
});
}

Related

Click Listeners for onStartIconClickListeners not working in TextInputLayout

I am implementing onstartIconClickletener using a custom attribute and Binder method
My Layout element is
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/text.layout.email.and.phone"
style="#style/Widget.MaterialComponents.TextInputLayout.FilledBox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="#dimen/_16sdp"
android:hint="#string/txt_email_phone"
android:textColorHint="#color/bg_input_text"
app:boxStrokeColor="#color/bg_input_text"
app:hintTextColor="#color/bg_input_text"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="#id/toolbar"
app:prefixTextColor="#color/text_color_white_1"
app:startIconDrawable="#drawable/ic_down_arrow"
app:startIconTint="#color/colorPrimaryDark"
app:onStartIconClicked = "#{(view)->viewModel.onStartIconClicked(view)}"
app:prefixText="#{viewModel.prefixText}">
I created an interface with one single method
public interface StartInconOnClickListener {
void onStartIconClicked(View view);
}
In my BindingAdapter
#BindingAdapter({"app:onStartIconClicked"})
public static void setonStartIconClickedListener(TextInputLayout textInputLayout, StartInconOnClickListener newListener) {
View.OnClickListener onClickListener = new View.OnClickListener() {
#Override public void onClick(View v) {
newListener.onStartIconClicked(v);
}
};
textInputLayout.setStartIconOnClickListener(onClickListener);
}
But clicking the startIcon it is invoking onClick in the BindingAdapter, but it is not calling my lambda function in the ViewModel
My Lamda function in the view model is
public void onStartIconClicked(View view) {
prefixText.set("+91");
Event event = new Event(Boolean.valueOf(true));
_showCountryCodeDialog.setValue(event);
}
I spent the whole day. now I am looking for help
Please dont respond to this question, it is mistake since the viewmodel object in the layout was null.
How we can see these issues when a null pointer exception is happening in the layout file

Text Watcher Binding Adapter with ViewModel

I'm still new to all this MVVM and Android Architecture Components. I have a few screens (Login and Register) that have inputs for email, password, name etc and a 'continue' button that's only enabled when the required fields are filled out correctly. There's also text views for messages such as "password must be..." and "not a valid email...". I'm trying to use a binding adapter and viewmodel to pull some of this validation logic out of my MainActivity and away from my business logic, but I'm struggling. I'm not even sure if this is the best way to do it. My thought with the ViewModel is that it would hold it's state on rotation/activity changes.
RegisterActivity is an Activity that simply holds a ViewPager with 3 fragments (email/password, first/last name, validation code).
Binding Adapter
#BindingAdapter("app:onTextChanged")
public static void onTextChanged(TextInputEditText view, TextViewBindingAdapter.OnTextChanged listener) {
}
Layout
<TextView
android:id="#+id/tv_error_message"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:text="#string/register_email_requirement"
android:textColor="#color/moenPrimaryError"
android:textSize="18sp"
android:visibility="#{viewModel.emailValidationVisible ? View.VISIBLE: View.GONE}"
app:layout_constraintBottom_toTopOf="#id/input_layout_password"
app:layout_constraintEnd_toEndOf="#+id/input_layout_email_address"
app:layout_constraintStart_toStartOf="#+id/input_layout_email_address"
app:layout_constraintTop_toBottomOf="#+id/input_layout_email_address"
app:layout_goneMarginTop="16dp" />
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_email_address"
style="#style/MoenTextInputEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textEmailAddress"
app:onTextChanged="#{viewModel.onTextChanged}"
app:onFocusChange="#{inputLayoutEmailAddress}">
</com.google.android.material.textfield.TextInputEditText>
ViewModel
public class RegisterFragmentViewModel extends BaseObservable {
private boolean emailValidationVisible = false;
#Bindable
public boolean getEmailValidationVisible() {
return this.emailValidationVisible;
}
public void toggle() {
this.emailValidationVisible = !this.emailValidationVisible;
notifyPropertyChanged(BR.viewModel);
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
Log.w("tag", "onTextChanged " + s);
this.toggle();
}
}
This is just a test I have. My thought was that I could bind the TextView visibility to a boolean that I can toggle/manipulate with a onTextChanged listener, but I don't know how to wire up the Binding Adapter. Am I on the right path? Is there a better/easier way to do this?
Am I on the right path? Is there a better/easier way to do this?
it is a way of doing it. I would remove the ternary operator from this line
android:visibility="#{viewModel.emailValidationVisible ? View.VISIBLE: View.GONE}"
and create a simple function in your VM that returns it. EG:
#Bindable
public int getVisibility() {
return emailValidationVisible ? View.VISIBLE: View.GONE
}
and in toggle(), you will have something like
public void toggle() {
this.emailValidationVisible = !this.emailValidationVisible;
notifyPropertyChanged(BR.visibility);
}
which will call the getter for you. Your xml will have to be changed like follow
android:visibility="#{viewModel.getVisibility()}"
Alternately you could create a BindingAdapter that takes a Boolean and change the visibility accordingly to it

How to set click listener to this Button

I cant get this to work I want the sign out Button on this preferences screen to have a ClickListener
This is how it looks like:
Here´s the code and the buttonView is always NULL:
class PreferencesFragment : PreferenceFragmentCompat() {
lateinit var activity: Context
private var prefs: SharedPreferences = BleApplication.getInstance().getDefaultSharedPreferences()
override fun onAttach(context: Context?) {
super.onAttach(context)
activity = requireActivity()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val buttonView = view.findViewById<View>(R.id.btn_sign_out)
if (buttonView != null) {
buttonView.setOnClickListener {
Toast.makeText(getActivity(), "You clicked me.", Toast.LENGTH_SHORT).show()
}
}
// Hide the divider
/* setDivider(ColorDrawable(Color.TRANSPARENT))
setDividerHeight(0)*/
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.app_prefs)
}
}
I also tried the kotlinx.android.synthetic but same problem there
Here´s the xml
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_remote_battery_title">
<SwitchPreferenceCompat
android:key="#string/pref_key_category_remote_battery_switch"
android:title="#string/pref_category_remote_battery_switch_title"
android:summary="#string/pref_category_remote_battery_switch_summ"/>
</PreferenceCategory>
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_sign_out_title">
<Preference
android:key="#string/pref_key_category_signed_out"
android:widgetLayout="#layout/pref_category_sign_out_button"
android:title="#string/pref_category_sign_out_button_title"
android:summary="#string/pref_category_sign_out_buttom_summ"/>
</PreferenceCategory>
</PreferenceScreen>
Here is the "#layout/pref_category_sign_out_button" layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<Button
android:id="#+id/btn_sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/buttonshape"
android:text="#string/pref_category_sign_out_title" />
</LinearLayout>
Since your Fragment extends from PreferenceFragmentCompat, you should not try to set a View.OnClickListener but override PreferenceFragmentCompat.onPreferenceTreeClick() instead. According to the documentation, this method is ...
Called when a preference in the tree rooted at this PreferenceScreen has been clicked.
Code example in Java:
#Override
onPreferenceTreeClick(Preference preference){
if(preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
return true;
}
return false;
}
Code example in Kotlin (I hope I can trust Android Studio :P)
override fun onPreferenceTreeClick(preferenceScreen: PreferenceScreen, preference: Preference): Boolean {
return if (preference.key == context.getString(R.string.pref_key_category_signed_out)) {
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
true
} else false
}
This will enable you to catch click events for the Preference but not for the Button.
In order to do that as well, one can use a custom Preference and override onBindViewHolder(PreferenceViewHolder). Since PreferenceViewHolder - similar to RecyclerView.ViewHolder - has a field itemView which contains the inflated layout, here is a good opportunity to set our own View.OnClickListener.
SignOutPreference extends from TwoStatePreference (in the com.android.support:preference-v7 library) because replacing the CheckBox widget with your custom Button requires only to set the android:widgetLayout attribute, just like you do in your code snippet:
<PreferenceCategory
android:layout="#layout/pref_category_text"
android:title="#string/pref_category_sign_out_title">
<your.package.name.SignOutPreference
android:key="#string/pref_key_category_signed_out"
android:widgetLayout="#layout/pref_category_sign_out_button"
android:title="#string/pref_category_sign_out_button_title"
android:summary="#string/pref_category_sign_out_buttom_summ"/>
</PreferenceCategory>
SignOutPreference.java
public class SignOutPreference extends TwoStatePreference {
public SignOutPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public SignOutPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public SignOutPreference(Context context) {
super(context);
}
#Override
public void onBindViewHolder(final PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
Button button = holder.itemView.findViewById(R.id.btn_sign_out);
if(button != null){
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(holder.itemView.getContext(), "CLICKED!", Toast.LENGTH_SHORT).show();
}
});
}
}
}
As #0X0nosugar mentioned you can use the onPreferenceTreeClicked method to handle all clicks in a convenient way like this:
#Override
onPreferenceTreeClick(Preference preference){
if ((preference.getKey().equals(getContext().getString(R.string.pref_key_category_signed_out))){
// user clicked signout "button"
// take appropriate actions
// return "true" to indicate you handled the click
return true;
}
return false;
}
The problem when using a custom button via widgetLayout is that when the button click is not a Preference click, so the handler doesn't catch it. One way to circumvent this problem is to just disable the built-in click for the button, like this:
<Button
android:id="#+id/btn_sign_out"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/buttonshape"
android:text="#string/pref_category_sign_out_title"
android:clickable="false"/>
See the last line. This way you don't need to create an extra class just for the button and you can easily access whatever methods and variables you have in your PreferencesFragment class.
I'm sure there's a better way to somehow trigger the Preference click from the button click, but at least this works as pretty well.
I was looking for a simple answer and found a way. Just set the onClick attribute for the button in the xml file, and implement the method in the parent activity of the preference fragment. (It's important to implement it in the Activity, not in the Preference Fragment. Or else it will give you crashes)
I wanted to make my onClick method to work only when the button is touched(clicked), and not respond to clicks in the area outside of the button. So far this is the only way that works just like I wanted it to.
My code is in Kotlin, but the logic is simple so it won't be hard to write it in java.
This is my button.xml used for the preference's widgetLayout. Look at how I set the android:onClick= attribute.
<?xml version="1.0" encoding="utf-8"?>
<Button
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/btn"
android:text="reset"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="#style/rippleEffect" //my custom ripple effect
android:onClick="onClickMethod">
</Button>
Then I implemented the onClickMethod in the parent activity of the preference fragment. This callback method should be public, and have View as input parameter.
(For more info read this -> How exactly does the android:onClick XML attribute differ from setOnClickListener? )
fun onClickMethod(view: View) {
//do something
}
Below is my preference.xml.
<Preference
android:key="pref_key"
android:title="Reset"
android:summary="summary.."
app:iconSpaceReserved="false"
android:widgetLayout="#layout/button"/>
Also I tried to set the ClickListener programmatically, but the only way that worked without errors was when I set the button's ClickListener inside the PreferenceClickListener. This only worked half way, since I need to touch(click) the preference item first to init the button's ClickListener.
val view = findPreference<androidx.preference.Preference>("pref_key")
var isFirst = true
view?.setOnPreferenceClickListener {
if (isFirst) {
btn.setOnClickListener {
Toast.makeText(requireContext(), "button clicked!", Toast.LENGTH_SHORT).show()
}
isFirst = false
it.summary = "unlocked"
} else {
isFirst = true
it.summary = "locked - tap to unlock"
}
true
}
Anyway this answer is working well for me, but I'm still looking for a way to use my preference key since this method does not fully uses the preference attribute, but just as a layout. But for now I hope this is helpful for those who want to use buttons in android preference.

android databinding: how to avoid onCheckedChanged triggered by programmatically

I am now trying to use android data-binding in my project, and encounter this kind of issue, for example: I have 3 checkbox as a checkbox group, if first checkbox is checked, then a variable type is 1. the second makes type to 2, the 3rd makes type to 3. so I implement the code in this way.
// layout.xml
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="50dp"
android:checked="#{userInfoViewModel.type == 1}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 1)}"
/>
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="55dp"
android:checked="#{userInfoViewModel.type == 2}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 2)}"
/>
<android.support.v7.widget.AppCompatCheckBox
android:layout_width="50dp"
android:layout_height="55dp"
android:checked="#{userInfoViewModel.type == 3}"
android:onCheckedChanged="#{(compoundButton, checked) -> userInfoViewModel.onTypeChecked(checked, 3)}"
/>
// viewModel
public void onTypeChecked(boolean checked, int i) {
if (checked) {
// if it is a check. set the type
type.set(i);
} else {
// if it is a uncheck. set type to unknown
type.set(0);
}
}
Now the problem is that, if I have checked 1st checkbox, then I check the 2nd. type should be set to 2, and the UI should update correctly. But the reality is that uncheck event also occur on the 1st checkbox, after type is set to 2, then type.set(0) is triggered, so no checkbox is checked.
In fact, this issue is same to onCheckedChanged called automatically. What I need is a solution for data-binding.
In non-data-binding project, I think the best solution is using setCheckedSilent(answer by #Emanuel Andrada).
public void setCheckedSilent(boolean checked) {
super.setOnCheckedChangeListener(null);
super.setChecked(checked);
super.setOnCheckedChangeListener(listener);
}
But in data-binding, I can not do this. So is there any expert can help me out?
According to #Arpan Sharma's answer, listen to onClick instead of onCheckedChanged. This solution works currently, But I am worried about the value of checked, is it always right?
public void onTypeChecked(View view, int i) {
Boolean checked = ((CheckBox) view).isChecked();
if (checked) {
type.set(i);
} else {
type.set(0);
}
}
Expose an ObservableBoolean from the ViewModel, then use two-way databinding over that boolean.
Then you can use the ObservableBoolean's values to decide what you want to do, rather than encode it in the XML.
android:checked="#={vm.checkedValue}"
This is very simple with data binding
In xml checkbox component
<CheckBox
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onCheckedChanged="#{(compoundButton, checked) ->
changeKeyboardVM.onCheckedChange(compoundButton, checked)}" />
In ViewModel or Activity
public void onCheckedChange(CompoundButton button, boolean check) {
Log.d("Z1D1", "onCheckedChange: " + check);
}
now Boolean check true on checked
and false on unchecked
I faced the same problem and i used onCLick listener instead onCHeck
listener .That way the listener wont change the check state when it is set programatically.
In your problem you should try setting different check change listeners to your check boxes.
I came across this question for first time and I think it's better to be implemented using binding adapter.
Here is the code for binding adapter
interface OnUserCheckedChangeListener {
fun onUserCheckChange(view:CompoundButton, isChecked:Boolean)
}
#BindingAdapter("onUserCheckedChange")
fun setUserCheckedChangeListener(view:CompoundButton, listener: OnUserCheckedChangeListener?){
if(listener == null){
view.setOnClickListener(null)
}else{
view.setOnClickListener {
listener.onUserCheckChange(view, view.isChecked)
}
}
}
And we can use it on any compound button
<CheckBox
android:id="#+id/finish_check"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
android:checked="#{your_condition}"
onUserCheckedChange="#{(view, checked) -> vm.onItemChecked(todo, checked)}"
/>
Using onClick instead of onCheckedChanged to prevent 2-ways binding.
From item_order_view.xml:
<data>
<variable
name="viewModel"
type="com.package.name.OrderItemViewModel" />
</data>
<CheckBox
android:id="#+id/cb_selected"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="8dp"
android:buttonTint="#color/white"
android:checked="#{viewModel.isSelect}"
android:onClick="#{() -> viewModel.onClickedCheckBox()}"
android:textColor="#color/white" />
From OrderItemViewModel.java
public class OrderItemViewModel {
public final ObservableField<Boolean> isSelect;
public final OrderItemViewModelListener mListener;
private final Order mOrder;
public OrderItemViewModel(Order order, OrderItemViewModelListener listener) {
this.mListener = listener;
this.mOrder = order;
isSelect = new ObservableField<>(mOrder != null ? mOrder.isSelect() : false);
}
/**
* Using onClick instead of onCheckedChanged
* to prevent 2-ways binding issue.
*/
public void onClickedCheckBox() {
mListener.onCheckChanged();
}
public interface OrderItemViewModelListener {
void onCheckChanged();
}
}
See https://stackoverflow.com/a/52606437/2914140:
Write inside OnCheckedChangeListener:
if (button.isPressed()) {
// A user pressed Switch.
}
Maybe some answers from How to use data binding for Switch onCheckedChageListener event? may help, for instance, defining android:onCheckedChanged listener, but I didnt test.

how to add onClickListener to buttons inside CardLayout

I am using Gabrielemariotti's Cardslib library to implement card layout in my android application. I am using a custom layout for my cards. Below is the code for creating custom cards:
Card card = new Card(getActivity().getApplicationContext(), R.layout.status_card);
card.setTitle("sample title");
I have three buttons at the bottom of my card (like buttons in Facebook android app). I want to set onClickListener for these buttons. But I am not sure how to do that.
Please help me here.
Thanks,
You have to define your layout.
Then create a Card with this layout, and override the setupInnerViewElements method.
Inside this method you can define your OnClickListener on your buttons, and you can access to all card's values.
public class CustomCard extends Card {
/**
* Constructor with a custom inner layout
*
* #param context
*/
public CustomCard(Context context) {
super(context, R.layout.carddemo_mycard_inner_content);
}
#Override
public void setupInnerViewElements(ViewGroup parent, View view) {
//Retrieve button
Button myButton = (Button) view.findViewById(R.id.myButton);
if (myButton != null) {
myButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(getContext(), "Click Listener card=" + getId(),
Toast.LENGTH_LONG).show();
}
});
}
}
}
I have an easy solution for this.
So another way to add onClick listeners, which is a bit easier, is through the XML.
In the xml for the button, you add this line:
android:onClick="methodName"
Where 'methodName' is obviously the name of a method. This will call the method whenever the button is clicked. The next step is obvious - just go into your java activity and create the method that you want called, making sure to take the View as a parameter. So you'd have something like this in your activity class:
public void methodName(View view) {
Log.v("appTag","BUTTON WAS PRESSED");
//whatever you want to do here
}
This is a shortcut to creating a whole onClickListener.
Hope that helps. Good luck :)
EDIT:
Remember, you're passing in a view here, so you can get whatever you want off of that view. Since you commented that you need to get the text off of your card, I'll show you how to do that.
Here is your method for this case:
public void methodName(View view) {
Log.v("appTag","BUTTON WAS PRESSED");
TextView textFromCard = view.findViewById(R.id.THE_ID_YOU_GAVE_YOUR_TEXTVIEW_IN_THE_XML);
String textFromTextView = textFromCard.getText().toString();
//do whatever you want with the string here
}

Categories

Resources