I am trying to create a custom component that is made up of lots of other components. I need to be able to use it the same way I would a TextView or EditText type of component. I cannot seem to find any tutorials online on how to do this and I'm not really sure what to even look for. I have several that I need to make but here is an example of one of them:
input_textbox.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_margin="5dp"
android:background="#drawable/bg_form_info"
android:layout_width="wrap_content"
android:layout_height="#dimen/form_info_height">
<TextView
android:id="#+id/label"
android:hint="#string/label"
android:textSize="#dimen/label_text_size"
android:visibility="visible"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<EditText
android:id="#+id/display"
android:hint="#string/display"
android:textSize="#dimen/form_info_text_size"
android:layout_alignParentBottom="true"
android:background="#android:color/transparent"
android:paddingLeft="#dimen/input_horizontal_padding"
android:paddingRight="#dimen/input_horizontal_padding"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<ImageButton
android:id="#+id/error_icon"
android:contentDescription="#string/error"
android:src="#drawable/ic_error"
android:background="#android:color/transparent"
android:clickable="true"
android:focusable="false"
android:visibility="visible"
android:paddingRight="#dimen/input_horizontal_padding"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:layout_centerVertical="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
Textbox.java
public class Textbox extends [WHAT GOES HERE] {
TextView inputLabel;
EditText inputField;
ImageButton errorIcon;
String errorMessage;
public Textbox(Context context, String label) {
super(context);
init(label);
}
public Textbox(Context context, AttributeSet attrs) {
super(context, attrs);
init(""); // I don't know how to pass the label here
}
public Textbox(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(""); // or here
}
private void init(String label) {
inputLabel = findViewById(R.id.label); // How do I connect this class to the xml?
inputLabel.setText(label);
inputField = findViewById(R.id.input_field);
inputField.setHint(label);
inputField.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
inputLabel.setVisibility((count > 0) ? View.VISIBLE : View.GONE);
}
#Override
public void afterTextChanged(Editable s) { }
});
errorIcon = findViewById(R.id.error_icon);
errorIcon.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
showErrorMessage();
}
});
}
public String getErrorMessage() {
return errorMessage;
}
public void showErrorMessage() {
showToast(getContext(), errorMessage);
}
public String getValue() {
return inputField.getText().toString();
}
public void setValue(String value) {
inputField.setText(value);
}
public void showErrorIcon(String errorMessage) {
this.errorMessage = errorMessage;
showErrorMessage();
errorIcon.setVisibility(VISIBLE);
}
public void hideErrorIcon() {
this.errorMessage = "";
errorIcon.setVisibility(GONE);
}
}
activity_main.xml
...
<com.example.application.inputs.Textbox
android:id="#+id/test_textbox"
android:layout_below="#id/end_date_display"
[HOW DO I ADD THE LABEL AND OTHER OPTIONS?]
android:layout_width="match_parent"
android:layout_height="wrap_content" />
...
Any help would be great or even a link to a Youtube video is better then nothing.
You can init your custom Textbox on Activity class like
public TextBox mTextBox = new TextBox(label)
add add it to the parent view.
If you want to set the label of the view in the xml,
add the public method in your custom class like
public void setTag(String input) {
inputLabel.setText(input)
}
and set the tag from the code.
public textBoxReferred = findViewById(R.id.test_testbox)
textBoxReferred.setText("THE TAG")
Related
I have just created a demo project for learning and the MVVM and how to use the Mvvm in our project.
but I have found an error while running the project
error: cannot find symbol class ViewModel
error: package ViewModel does not exist
error: package ViewModel does not exist
error: package ViewModel does not exist
and here is my code
public class User extends BaseObservable {
String email,password;
boolean isDataValidate;
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isDataValidate() {
return !TextUtils.isEmpty(getEmail())&& Patterns.EMAIL_ADDRESS.matcher(getEmail()).matches()&&
getPassword().length()>6;
}
public void setDataValidate(boolean dataValidate) {
isDataValidate = dataValidate;
}
}
and Here is my ViewModel class
public class LoginViewModel extends ViewModel {
private User user;
private LoginResultCallback loginResultCallback;
public LoginViewModel(LoginResultCallback loginResultCallback){
this.loginResultCallback=loginResultCallback;
this.user=new User();
}
public String getEmailText1(){
return user.getEmail();
}
public String getPasswordText1(){
return user.getPassword();
}
public TextWatcher getEmailText(){
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setEmail(editable.toString());
}
};
}
public TextWatcher getPasswordText(){
return new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
#Override
public void afterTextChanged(Editable editable) {
user.setPassword(editable.toString());
}
};
}
public void onLoginClicked(View view)
{
if (user.isDataValidate()){
loginResultCallback.onSuccess("Login was Successfull");
}
else{
loginResultCallback.onError("Login Invalid Credential");
}
}
}
and here is my MainActivity
public class MainActivity extends AppCompatActivity implements LoginResultCallback {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityMainBinding activityMainBinding= DataBindingUtil.setContentView(this,R.layout.activity_main);
LoginViewModel loginViewModel=new LoginViewModel(this);
activityMainBinding.setViewModel(loginViewModel);
//activityMainBinding.setViewModel(ViewModelProviders.of(this,new LoginViewModelFactory(this)).get(LoginViewModel.class));
}
#Override
public void onSuccess(String Message) {
Toast.makeText(this, ""+Message, Toast.LENGTH_SHORT).show();
}
#Override
public void onError(String Error) {
Toast.makeText(this, ""+Error, Toast.LENGTH_SHORT).show();
}
}
and here is viewModelFactory class
public class LoginViewModelFactory extends ViewModelProvider.NewInstanceFactory {
private LoginResultCallback loginResultCallback;
public LoginViewModelFactory(LoginResultCallback loginResultCallback)
{
this.loginResultCallback=loginResultCallback;
}
#NonNull
#Override
public <T extends ViewModel> T create(#NonNull Class<T> modelClass) {
return (T) new LoginViewModel(loginResultCallback);
}
}
and XML is here
<?xml version="1.0" encoding="utf-8"?>
<data>
<variable
name="viewModel"
type="com.example.designinfpattern.ViewModel.LoginViewModel" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<EditText
android:id="#+id/email"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:addTextChangedListener="#{viewModel.emailText}"
android:hint="Enter Your account Email or Username"
/>
<EditText
android:id="#+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Enter Your account password"
app:addTextChangedListener="#{viewModel.passwordText}" />
</LinearLayout>
<Button
android:id="#+id/login_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="#{viewModel::onLoginClicked}"
android:text="Login" />
</LinearLayout>
Here is the dependency which I am using on the build Gradle file:
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
I don't know how to solve this error and I'm doing R&D but not find a proper solution. Please help me out to solve the problem
thanks in advance
You need to rename the ViewModel folder (package) from ViewModel to viewmodel
Add the following in your build.gradle
// ViewModel and LiveData
implementation "androidx.lifecycle:lifecycle-extensions:2.1.0"
// alternatively - just ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel:2.1.0"
Today, I faced the same problem in MVVM. I fixed this. Everything was working fine and suddenly I started getting this same error. Then I recall what changes I recently made. Then I realised I moved my Activity files from one Package to another after this I stared facing the problem. In layout, I changed my viewmodel packages accordingly and Bingo! Sharing this it may help and save time of others in case same thing happened in future.
I want to design a class that extends Edittext such that
1)have new custom property named "realanswer" that contain string value
2)in this class after change focus, automatically(with out define listener in main activity) by a method in this class detect inputted value equal to "realanswer" or not!? if answer is true make textcolor green,else read.
this is my new class:
package com.faridi.myapplication;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.widget.EditText;
public class MFEditText extends EditText {
public Context mcontext;
public String realanswer;
public MFEditText(Context context, AttributeSet attrs) {
super(context, attrs);
this.mcontext=context;
TypedArray typedArray =
context.obtainStyledAttributes(attrs,R.styleable.answer);
String attr =
typedArray.getString(R.styleable.answer_answerAttribute);
setCustomAttribute(attr);
this.realanswer=attr;
Toast.makeText(MFEditText.this.mcontext,
attr,Toast.LENGTH_LONG).show();
typedArray.recycle();
this.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int
count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before,
int count) {
if(MFEditText.this.getText().toString().
equals(MFEditText.this.realanswer)){
MFEditText.this.setTextColor(Color.GREEN);
}else {
MFEditText.this.setTextColor(Color.RED);
}
Toast.makeText(MFEditText.this.mcontext,
MFEditText.this.realanswer+
" "+MFEditText.this.getText(),Toast.LENGTH_LONG).show();
}
#Override
public void afterTextChanged(Editable s) {
}
});
}
private void setCustomAttribute(String attr) {
realanswer=attr;
}
private String getCustomAttribute() {
return realanswer;
}
}
and this is my attr.xml file
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="answer">
<attr name="answerAttribute" format="string"/>
</declare-styleable>
</resources>
and this is how i use it in my mainlayout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_margin="25dp"
android:layout_height="match_parent">
<com.faridi.myapplication.MFEditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
custom:answerAttribute="33"
android:id="#+id/mfet"
/>
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
it work now. the problem is solved.
You can use TextWatcher on EditText to check whether the answer is realanswer or not like this
TextWatcher addremark_text_watcher = new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
// write your logic here
}
#Override
public void afterTextChanged(Editable s) {
}
};
I'm trying to create a custom Preference to be shown in PreferenceFragment as described here: Building a Custom Preference. My custom Preference should look and function as SwitchPreference, but have one additional TextView for error reporting.
I got everything implemented and UI looks fine, but I can't initialize this Preference when my PreferenceFragment is shown!
Documentation for Preference.onBindView() states that:
This is a good place to grab references to custom Views in the layout
and set properties on them.
So I did:
#Override
protected void onBindView(View view) {
super.onBindView(view);
txtError = (TextView) view.findViewById(R.id.error);
}
public void setError(String errorMessage) {
txtError.setText(errorMessage);
notifyChanged();
}
However, when I call CustomSwitchPreference.setError(String) in PreferenceFragment.onResume(), I get NPE because txtError is null.
I tried to find some workaround, but it looks like there is no lifecycle method in PreferenceFragment which is guaranteed to be called AFTER all the underlying Preferences had their Views initialized (I checked both Preference.onBindView(View) and Preference.onCreateView(ViewGroup)).
This behavior doesn't make any sense - there should be some way to initialize UIs of the underlying Preferences when PreferenceFragment is shown. How can I achieve this?
Note: calls to customPreference.setTitle(String) and customPreference.setSummary(String() in CustomPreferenceFragment.onResume() work fine. It is just the additional TextView which I can't grab a reference to...
CustomSwitchPreference.java:
public class CustomSwitchPreference extends SwitchPreference {
private TextView txtError;
public CustomSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public CustomSwitchPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public CustomSwitchPreference(Context context, AttributeSet attrs) {
super(context, attrs);
}
public CustomSwitchPreference(Context context) {
super(context);
}
#Override
protected View onCreateView(ViewGroup parent) {
setLayoutResource(R.layout.custom_switch_preference_layout);
return super.onCreateView(parent);
}
#Override
protected void onBindView(View view) {
super.onBindView(view);
txtError = (TextView) view.findViewById(R.id.error);
}
public void setError(String errorMessage) {
txtError.setText(errorMessage);
notifyChanged();
}
}
CustomPreferenceFragment.java:
public class CustomPreferenceFragment extends PreferenceFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getPreferenceManager().setSharedPreferencesName(PREFERENCES_FILE_NAME);
addPreferencesFromResource(R.xml.application_settings);
}
#Override
public void onResume() {
super.onResume();
Preference preference = findPreference("CUSTOM_PREF");
if (preference == null ||
!CustomSwitchPreference.class.isAssignableFrom(preference.getClass()))
throw new RuntimeException("couldn't get a valid reference to custom preference");
CustomSwitchPreference customPreference = (CustomSwitchPreference) preference;
customPreference.setError("error");
}
}
custom_switch_preference_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_centerVertical="true"
android:layout_alignParentStart="true"
android:layout_toStartOf="#android:id/widget_frame">
<TextView
android:id="#android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:lines="1"/>
<TextView
android:id="#android:id/summary"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"/>
<TextView
android:id="#+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="3"/>
</LinearLayout>
<FrameLayout
android:id="#android:id/widget_frame"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
application_settings.xml:
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<com.example.settings.CustomSwitchPreference
android:key="CUSTOM_PREF"/>
</PreferenceScreen>
I couldn't find a proper solution for this issue - this inconsistency feels like a life-cycle bug in AOSP, but I'm not 100% sure about this.
As a workaround, I defined a callback interface that CustomSwitchPreference invokes in onBindView method in order to notify the containing PreferenceFragment that it had been initialized:
#Override
protected void onBindView(View view) {
super.onBindView(view);
txtError = (TextView) view.findViewById(R.id.error);
initializationListener.onInitialized(CustomSwitchPreference.this);
}
and all the manipulations on this CustomSwitchPreference that I wanted to perform in onResume now get performed in onInitialized callback. This is an ugly workaround that requires a considerable amount of boilerplate, but it seems to work.
I have implemented the new Android data-binding, and after implementing realised that it does not support two-way binding. I have tried to solve this manually but I am struggling to find a good solution to use when binding to an EditText.
In my layout I have this view:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#{statement.firstName}"/>
Another view is also showing the results:
<TextView
style="#style/Text.Large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{statement.firstName}"/>
In my fragment I create the binding like this:
FragmentStatementPersonaliaBinding binding = DataBindingUtil.inflate(inflater, R.layout.fragment_statement_personalia, container, false);
binding.setStatement(mCurrentStatement);
This works and puts the current value of firstName in the EditText. The problem is how to update the model when the text changes. I tried putting an OnTextChanged-listener on the editText and updating the model. This created a loop killing my app (model-update updates the GUI, which calls textChanged times infinity). Next I tried to only notify when real changes occured like this:
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
boolean changed = !TextUtils.equals(this.firstName, firstName);
this.firstName = firstName;
if(changed) {
notifyPropertyChanged(BR.firstName);
}
}
This worked better, but everytime I write a letter, the GUI is updated and for som reason the edit-cursor is moved to the front.
Any suggestions would be welcome
EDIT 04.05.16:
Android Data binding now supports two way-binding automatically!
Simply replace:
android:text="#{viewModel.address}"
with:
android:text="#={viewModel.address}"
in an EditText for instance and you get two-way binding. Make sure you update to the latest version of Android Studio/gradle/build-tools to enable this.
(PREVIOUS ANSWER):
I tried Bhavdip Pathar's solution, but this failed to update other views I had bound to the same variable. I solved this a different way, by creating my own EditText:
public class BindableEditText extends EditText{
public BindableEditText(Context context) {
super(context);
}
public BindableEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BindableEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
private boolean isInititalized = false;
#Override
public void setText(CharSequence text, BufferType type) {
//Initialization
if(!isInititalized){
super.setText(text, type);
if(type == BufferType.EDITABLE){
isInititalized = true;
}
return;
}
//No change
if(TextUtils.equals(getText(), text)){
return;
}
//Change
int prevCaretPosition = getSelectionEnd();
super.setText(text, type);
setSelection(prevCaretPosition);
}}
With this solution you can update the model any way you want (TextWatcher, OnTextChangedListener etc), and it takes care of the infinite update loop for you. With this solution the model-setter can be implemented simply as:
public void setFirstName(String firstName) {
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
This puts less code in the model-class (you can keep the listeners in your Fragment).
I would appreciate any comments, improvements or other/better solutions to my problem
This is now supported in Android Studio 2.1+ when using the gradle plugin 2.1+
Simply change the EditText's text attribute from #{} to #={} like this:
<EditText
android:id="#+id/firstname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textCapWords|textNoSuggestions"
android:text="#={statement.firstName}"/>
for more info, see: https://halfthought.wordpress.com/2016/03/23/2-way-data-binding-on-android/
#Gober The android data-binding support the two way binding. Therefore you do not need to make it manually. As you tried by putting the OnTextChanged-listener on the editText. It should update the model.
I tried putting an OnTextChanged-listener on the editText and updating
the model. This created a loop killing my app (model-update updates
the GUI, which calls textChanged times infinity).
It’s worth noting that binding frameworks that implement two-way binding would normally do this check for you…
Here’s the example of modified view model, which does not raise a data binding notification if the change originated in the watcher:
Let’s create a SimpleTextWatcher that only requires only one method to be overridden:
public abstract class SimpleTextWatcher implements TextWatcher {
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void afterTextChanged(Editable s) {
onTextChanged(s.toString());
}
public abstract void onTextChanged(String newValue);
}
Next, in the view model we can create a method that exposes the watcher. The watcher will be configured to pass the changed value of the control to the view model:
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
setUsername(newValue);
}
};
}
Finally, in the view we can bind the watcher to the EditText using addTextChangeListener:
<!-- most attributes removed -->
<EditText
android:id="#+id/input_username"
android:addTextChangedListener="#{viewModel.onUsernameChanged}"/>
Here is the implementation of the view Model that resolve the notification infinity.
public class LoginViewModel extends BaseObservable {
private String username;
private String password;
private boolean isInNotification = false;
private Command loginCommand;
public LoginViewModel(){
loginCommand = new Command() {
#Override
public void onExecute() {
Log.d("db", String.format("username=%s;password=%s", username, password));
}
};
}
#Bindable
public String getUsername() {
return this.username;
}
#Bindable
public String getPassword() {
return this.password;
}
public Command getLoginCommand() { return loginCommand; }
public void setUsername(String username) {
this.username = username;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.username);
}
public void setPassword(String password) {
this.password = password;
if (!isInNotification)
notifyPropertyChanged(com.petermajor.databinding.BR.password);
}
#Bindable
public TextWatcher getOnUsernameChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setUsername(newValue);
isInNotification = false;
}
};
}
#Bindable
public TextWatcher getOnPasswordChanged() {
return new SimpleTextWatcher() {
#Override
public void onTextChanged(String newValue) {
isInNotification = true;
setPassword(newValue);
isInNotification = false;
}
};
}
}
I hope this is what you are looking and sure can help you. Thanks
There is a simpler solution. Just avoid updating field if it hadn't really changed.
#Bindable
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
if(this.firstName.equals(firstName))
return;
this.firstName = firstName;
notifyPropertyChanged(BR.firstName);
}
POJO:
public class User {
public final ObservableField<String> firstName =
new ObservableField<>();
public final ObservableField<String> lastName =
new ObservableField<>();
public User(String firstName, String lastName) {
this.firstName.set(firstName);
this.lastName.set(lastName);
}
public TextWatcherAdapter firstNameWatcher = new TextWatcherAdapter(firstName);
public TextWatcherAdapter lastNameWatcher = new TextWatcherAdapter(lastName);
}
Layout:
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.firstName, default=First_NAME}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{user.lastName, default=LAST_NAME}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editFirstName"
android:text="#{user.firstNameWatcher.value}"
android:addTextChangedListener="#{user.firstNameWatcher}"/>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/editLastName"
android:text="#{user.lastNameWatcher.value}"
android:addTextChangedListener="#{user.lastNameWatcher}"/>
Watcher:
public class TextWatcherAdapter implements TextWatcher {
public final ObservableField<String> value =
new ObservableField<>();
private final ObservableField<String> field;
private boolean isInEditMode = false;
public TextWatcherAdapter(ObservableField<String> f) {
this.field = f;
field.addOnPropertyChangedCallback(new Observable.OnPropertyChangedCallback(){
#Override
public void onPropertyChanged(Observable sender, int propertyId) {
if (isInEditMode){
return;
}
value.set(field.get());
}
});
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//
}
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
//
}
#Override public void afterTextChanged(Editable s) {
if (!Objects.equals(field.get(), s.toString())) {
isInEditMode = true;
field.set(s.toString());
isInEditMode = false;
}
}
}
I struggled to find a full example of 2-way databinding. I hope this helps.
The full documentation is here:
https://developer.android.com/topic/libraries/data-binding/index.html
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="item"
type="com.example.abc.twowaydatabinding.Item" />
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="#+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={item.name}"
android:textSize="20sp" />
<Switch
android:id="#+id/switch_test"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#={item.checked}" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="change"
android:onClick="button_onClick"/>
</LinearLayout>
</layout>
Item.java:
import android.databinding.BaseObservable;
import android.databinding.Bindable;
public class Item extends BaseObservable {
private String name;
private Boolean checked;
#Bindable
public String getName() {
return this.name;
}
#Bindable
public Boolean getChecked() {
return this.checked;
}
public void setName(String name) {
this.name = name;
notifyPropertyChanged(BR.name);
}
public void setChecked(Boolean checked) {
this.checked = checked;
notifyPropertyChanged(BR.checked);
}
}
MainActivity.java:
public class MainActivity extends AppCompatActivity {
public Item item;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
item = new Item();
item.setChecked(true);
item.setName("a");
/* By default, a Binding class will be generated based on the name of the layout file,
converting it to Pascal case and suffixing “Binding” to it.
The above layout file was activity_main.xml so the generate class was ActivityMainBinding */
ActivityMainBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
binding.setItem(item);
}
public void button_onClick(View v) {
item.setChecked(!item.getChecked());
item.setName(item.getName() + "a");
}
}
build.gradle:
android {
...
dataBinding{
enabled=true
}
}
I am trying to create a NumberPicker dialog in my preference screen. I have already made one following this:https://stackoverflow.com/a/5533295/2442638
However, for my second dialog, I only want one spinner, so I have adapted the code as follows:
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.NumberPicker;
public class SnoozeTPP extends DialogPreference {
private int Minute = 0;
private NumberPicker np= null;
public static int getMinute(String time) {
String[] pieces = time.split(":");
return (Integer.parseInt(pieces[1]));
}
public SnoozeTPP(Context context, AttributeSet attrs) {
super(context, attrs);
setPositiveButtonText("Set");
setNegativeButtonText("Cancel");
}
#Override
protected View onCreateDialogView() {
np = new NumberPicker(getContext());
return (np);
}
#Override
protected void onBindDialogView(View v) {
super.onBindDialogView(v);
np.setMaxValue(60);
np.setValue(Minute);
}
#Override
protected void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
Minute = np.getValue();
String time = 0 + ":" + String.valueOf(Minute);
if (callChangeListener(time)) {
persistString(time);
}
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return (a.getString(index));
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
String time = null;
if (restoreValue) {
if (defaultValue == null) {
time = getPersistedString("08:00");
} else {
time = getPersistedString(defaultValue.toString());
}
} else {
time = defaultValue.toString();
}
Minute = getMinute(time);
}
}
There are no errors and the dialog pops up correctly, but the layout of it seems to be "messed up" :-). The blue line stretch across the whole dialog instead of just the width of the numbers.
The question is - how to set the layout correctly? (I am sure there are lots of other mistakes as well!)
Thank you
I solved this by using the CyanogenMod number picker.
Java file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/src/com/cyanogenmod/trebuchet/preference/NumberPickerPreference.java
XML file:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/layout/number_picker_dialog.xml
Attributes:
https://github.com/CyanogenMod/android_packages_apps_Trebuchet/blob/cm-10.2/res/values/attrs.xml#L158
Here is an example of simple, but working NumberPickerPreference, saving integer value between 1 and 100:
NumberPickerPreference.java:
public class NumberPickerPreference extends DialogPreference {
private NumberPicker mPicker;
private Integer mNumber = 0;
public NumberPickerPreference(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public NumberPickerPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
}
#Override
protected View onCreateDialogView() {
mPicker = new NumberPicker(getContext());
mPicker.setMinValue(1);
mPicker.setMaxValue(100);
mPicker.setValue(mNumber);
return mPicker;
}
#Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult) {
// needed when user edits the text field and clicks OK
mPicker.clearFocus();
setValue(mPicker.getValue());
}
}
#Override
protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
setValue(restoreValue ? getPersistedInt(mNumber) : (Integer) defaultValue);
}
public void setValue(int value) {
if (shouldPersist()) {
persistInt(value);
}
if (value != mNumber) {
mNumber = value;
notifyChanged();
}
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getInt(index, 0);
}
}
This is more a workaround than a solution, but i hope it helps. Adding a dummy textView solved the problem. I got exactly the same problem.
My xml File:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical" >
<TextView
android:id="#+id/textDummyEmpty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/textDummyEmpty" />
<NumberPicker
android:id="#+id/numberPicker1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
and
android:text="#string/textDummyEmpty"
is an empty String. Maybe its also enough to use just a view instead of a textView.
return a LinearLayout in onCreateDialogView rather than NumberPicker as below:
#Override
protected View onCreateDialogView() {
numberPicker = new NumberPicker(getContext());
numberPicker.setMinValue(1);
numberPicker.setMaxValue(12);
numberPicker.setWrapSelectorWheel(false);
numberPicker.setValue(lastValue);
LinearLayout.LayoutParams pickerParams = new LinearLayout.LayoutParams
(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
pickerParams.gravity = Gravity.CENTER;
numberPicker.setLayoutParams(pickerParams);
LinearLayout layout = new LinearLayout(getContext());
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams
(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
layout.setOrientation(LinearLayout.VERTICAL);
layout.setLayoutParams(params);
layout.addView(numberPicker);
return layout;
//return numberPicker;
}