I have a custom android widget that abstracts a forum field, RecordDataField, that is based on a RelativeLayout (basically groups a field and it's label together, and takes custom attributes to determine where the label is relative to the field, to show a drop down button for dropdown list's, ...)
public abstract class RecordDataField : RelativeLayout
{
protected TextView _tvLabel;
protected FieldLayout _rlInput;
//protected EditText _etInput;
protected FieldInput _etInput;
protected Button _btnDrop;
protected abstract FieldInput InstantiateInput();
}
FieldInput is a subclassed EditText, nothing really special.
Any child of RecordDataField implements InstantiateInput similar to as follows (_etInput ultimately ends up with the value returned by InstantiateInput)
protected override FieldInput InstantiateInput()
{
View v = _inflater.Inflate(Resource.Layout.RecordDataFields, null);
_fieldInput = ((ViewGroup)v).FindViewById<FieldInput> Resource.Id.RecordDataFieldInput);
return (FieldInput)_fieldInput;
}
This works great for all my RecordDataField variants, but they are all FieldInput (EditText) based. I now need to go even more general, and when I try changing the abstract method InstantiateInput's return type to View, I run into problems in the base class, b/c it set's gravity and other attributes on _etInput, and apparently View doesn't support gravity
public virtual void InitView()
{
_tvLabel = (TextView)_inflater.Inflate(Resource.Layout.RecordDataFieldLabel, this, false);
_tvLabel.Text = _label;
_tvLabel.Gravity = _labelGravity;
ViewGroup.LayoutParams tvLabelLayout = (ViewGroup.LayoutParams)_tvLabel.LayoutParameters;
tvLabelLayout.Width = _labelWidth;
RelativeLayout.LayoutParams lpLabel = new RelativeLayout.LayoutParams(tvLabelLayout);
lpLabel.AddRule(LayoutRules.AlignParentTop);
lpLabel.AddRule(LayoutRules.AlignParentLeft);
AddView(_tvLabel, lpLabel);
...
}
Any suggestions as to which view type I can return from InstantiateInput so this widget can support the greatest amount of view types (the motivation here is that I want to use a CheckBox in place of the EditText (FieldInput)?
Try TextView (android.widget.TextView) . Both Checkbox and EditText extends TextView which supports the gravity param to support aligning it's content
Related
I am creating simple AppCompatEditText adding OnFocusChangeListener and putting it in the simple TextInputLayout.
When AppCompatEditText loosing focus it's content should be validate by isValidParam method.
It worked till yesterday, when I used rev.23.0.3
But now, when I used rev.24.0.2, it gives error as below on the 1st row of isValidParam method.
java.lang.ClassCastException: android.widget.FrameLayout cannot be
cast to android.support.design.widget.TextInputLayout
I checked in debugging mode. AppCompatEditText.getpParent() really returns Framelayout instead TextInputLayout.
LinearLayout llParams = new LinearLayout(context);
llParams.setOrientation(LinearLayout.VERTICAL);
// Create label for param
final TextInputLayout tilParam = new TextInputLayout(context);
// Add label into layout
llParams.addView(tilParam);
// Create Editor for param
final AppCompatEditText etParam = new AppCompatEditText(context);
edParam.setOnFocusChangeListener(new View.OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (!hasFocus)
if (isValidParam(etParam)) {
do some thing;
} else {
do other thing;
}
}
});
tilParam.addView(etParam);
// validation method
boolean isValidParam(AppCompatEditText editText) {
TextInputLayout til = (TextInputLayout) editText.getParent();
String text = editText.getText().toString().trim();
if (!text.equls("some criteria") {
till.setError("Error text")
return false;
}
return true;
}
Update:
Use the widget TextInputEditText instead of EditText inside a TextInputLayout.
old answer
TextInputLayout textInputLayout = (TextInputLayout) editText.getParent().getParent();
That seems to work as a quick fix. Far from ideal.
getParentForAccessibility() worked for me
You can check if EditText is inside TextInputLayout using following method:
public static <ParentClass> ParentClass getFirstParent(View view, Class<ParentClass> parentClass) {
if (view.getParent() instanceof View) {
if (parentClass.isInstance(view.getParent())) {
return (ParentClass) view.getParent();
} else {
return getFirstParent((View) view.getParent(), parentClass);
}
} else {
return null;
}
}
Example of use:
TextInputLayout textInputLayout = getFirstParent(editText, TextInputLayout.class)
Just extracts from Android official documents:
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="#string/form_username"/>
</android.support.design.widget.TextInputLayout>
Note: The actual view hierarchy present under TextInputLayout is NOT
guaranteed to match the view hierarchy as written in XML. As a result,
calls to getParent() on children of the TextInputLayout -- such as an
TextInputEditText -- may not return the TextInputLayout itself, but
rather an intermediate View. If you need to access a View directly,
set an android:id and use findViewById(int).
Therefore, to resolve the issue you have to turn to findViewById instead of getParent due to an extra layout in between introduced in version 24.
You can check the code of the TextInputLayout v24.x.x.
Now it works with a FrameLayout.
#Override
public void addView(View child, int index, final ViewGroup.LayoutParams params) {
if (child instanceof EditText) {
mInputFrame.addView(child, new FrameLayout.LayoutParams(params));
//...
} else {
// Carry on adding the View...
super.addView(child, index, params);
}
}
where mInputFrame is a FrameLayout.
It is the reason of your issue (the parent is a FrameLayout).
Just pass the tilParam as parameter , instead of using getParent() if you need to use it.
TextInputLayout has a method called getEditText(). This may be an alternate way to solve your problem. Instead of starting from the EditText itself and getting the parent TextInputLayout, you can start with the TextInputLayout and simply get the EditText child view. For xml generated views, the following code is an example:
TextInputLayout someInputLayout = findViewById(R.id.some_input_layout);
EditText someEditText = someInputLayout.getEditText();
String text = someEditText.getText().toString();
This could possibly be a more desired solution as it does not require any external methods, though this would not solve your problem if it is required that you start from EditText for some reason. I know this has been answered a long time ago, but I was using #sylwano's solution, until I found for my particular problem it was better to do as above.
I have a custom component that extends RelativeLayout which in turns holds a GridLayout(named mFormLayout). I have a public method that adds two spinners with their proper adapter source and an imageview which acts as a button to remove rows.
public class EditableTwinSpinnerGridForm extends EditableGridForm
{
public void addTwinSpinnerRow(final Locale.MapKey spinner1DefVal, final Locale.MapKey spinner2DefVal)
{
Spinner spinner1 = createSpinner(mTSP.getFirstSpinnerRes(), spinner1DefVal.getId());
spinner1.setOnItemSelectedListener(mTSP.getIsl());
Spinner spinner2 = createSpinner(mTSP.getSecondSpinnerRes(), spinner2DefVal.getId());
ImageView rmvBtn = createRemoveBtn();
mFormLayout.addView(spinner1);
mFormLayout.addView(spinner2);
mFormLayout.addView(rmvBtn);
}
}
For some reason, this method works when I am adding rows from a call to onCreate in an activity, but when I am calling this method after the activity is created(from an onclicklistener) the Spinners are either not there or only one of them shows up. They do take the space because I see the row and the removable image view.
I have also noticed that when I focus on a EditText in the same activity and the keyboard pops up, the added spinners show up when I press back to remove the keyboard.
Here's the code I use to create a spinner :
protected Spinner createSpinner(Integer spinnerSrc, String defaultSpinnerValue)
{
Spinner spinner = new Spinner(mCtx, Spinner.MODE_DIALOG);
// Setting the bg color to the containing color to remove the spinner arrow.
spinner.setBackgroundResource(R.color.container_bg);
SparseArray<Phrase> map = Locale.getInstance().getMap(spinnerSrc);
PhraseArrayAdapter adapter = createSpinnerFromMap(spinnerSrc);
spinner.setAdapter(adapter);
if (defaultSpinnerValue.equals(Utilities.EMPTY_STRING) || defaultSpinnerValue.isEmpty())
{
throw new IllegalStateException();
}
else
{
Utilities.getInstance().setMapSpinnerPosByValue(map, defaultSpinnerValue, spinner);
}
adapter.setDropDownViewResource(R.layout.editable_spinner_dropdown_item);
setSpinnerLayoutParams(spinner);
return spinner;
}
protected void setSpinnerLayoutParams(Spinner spinner)
{
GridLayout.LayoutParams lp = createDefaultGridParams();
lp.setGravity(Gravity.FILL_HORIZONTAL);
lp.width = 250;
lp.rightMargin = 0;
spinner.setLayoutParams(lp);
}
The code works when the activity is loaded so I'm a bit stumped. I looked around and some people suggested I set LayoutParams in addView, but why would this method work in onCreate, but not afterwards?
Here's what's happening visually(The first three rows are added from a loop in onCreate(), the two second ones are added by pressing "Add +"). As you can see the second spinner isn't showing up, sometimes both aren't showing up. I also tried calling invalidate and requestLayout to no avail.
I had looked into the invalidate method, here's where it is located currently :
public abstract class EditableForm extends RelativeLayout implements ObservableInt
{
private class OnAddClicked implements OnClickListener
{
#Override
public void onClick(View v)
{
onAddClicked(v);
EditableForm.this.invalidate();
mFormLayout.invalidate();
}
}
}
Which calls(In a subclass of EditableGrid) :
#Override
protected void onAddClicked(View clickedView)
{
addTwinSpinnerRow();
notifyObservers(new ObservableData(EDITABLE_ADD_GRID_CLICKED, null));
}
protected void addTwinSpinnerRow()
{
Locale.MapKey v1 = Locale.getInstance().getMap(mTSP.getFirstSpinnerRes()).get(0).getMapId();
Locale.MapKey v2 = Locale.getInstance().getMap(mTSP.getSecondSpinnerRes()).get(0).getMapId();
addTwinSpinnerRow(v1, v2);
}
Have you tried calling the Invalidate method of the container view rather than the added view?
Most likely the views you are adding are there, they just need to be drawn which is suggested by your keyboard hide/show difference. Does rotating the device also cause them to appear? If so, this again suggests that you need to redraw your custom layout.
When it's necessary to execute invalidate() on a View?
I have extended EditTextPreference, but the Dialog Message won't display. This happens if I add the dialogMessage programatically or in the the preferences.xml.
Here is my onBindDialogView:
AutoCompleteTextView editText = mEditText;
editText.setText(getText());
ViewParent oldParent = editText.getParent();
if (oldParent != view) {
if (oldParent != null) {
((ViewGroup) oldParent).removeView(editText);
}
onAddEditTextToDialogView(view, editText);
}
Is the dialog message really absent? It's probably there but its text color might make it less (or not) visible. (Or try to dismiss software keyboard). Try experimenting with dialog messages having a number of "\n" characters and see if that affects dialog layout. If so, it means the dialog message is actually there but camouflaged too well.
EditTextPreference brings a text view (in the preference_dialog_edittext.xml) that replaces the existing one (in the alert_dialog.xml) for the dialog message, but unfortunately with different text style, which might cause a visibility problem under certain themes. Even their sizes are different.
One solution might be to obtain the text color and size from the original text view to be replaced and apply them to the new one, but I would suggest retaining the original text view instead, because it's more likely to be visually consistent if there are any future UI changes. Try adding the following overrides
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
builder.setMessage(getDialogMessage()); // bring back the original text view
}
protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
int id = getContext().getResources().getIdentifier("edittext_container", "id", "android");
ViewGroup container = (ViewGroup) dialogView.findViewById(id);
container.removeAllViews(); // remove the new text view
super.onAddEditTextToDialogView(dialogView, editText);
}
If you think the dialog message and the edittext view is too far apart, they can be brought together a little closer by adding another override:
protected void showDialog(Bundle state) {
super.showDialog(state);
int id = getContext().getResources().getIdentifier("message", "id", "android");
TextView message = (TextView) getDialog().findViewById(id);
message.setPadding(message.getPaddingLeft(), message.getPaddingTop(), message.getPaddingRight(), 0);
}
and add the following line in the onAddEditTextToDialogView method after calling removeAllViews:
container.setPadding(container.getPaddingLeft(), 0, container.getPaddingRight(), container.getPaddingBottom());
I want to implement a feature that allows user to change the textSize of a textView in another view inside the app,
So I have a button with its "onClick" property set to:
Class mainActivity
public void increaseFont(View view)
{
MainViewPager.changeTextViewTextSize(mTextSize);
}
Class MainViewPager
static public void changeTextViewTextSize(int aTextSize)
{
View detailView = (View) LayoutInflater.from(mContext).inflate(R.layout.details, null);
TextView description = (TextView) detailView.findViewById(R.id.story_description);
description.setTextSize(aTextSize);
}
QUESTIONS is the textSize can't be changed when clicking the button. So how to?
The text size can changed at run time of course. You issue is related to the method changeTextViewTextSize. Using the inflater you are creating a new instance of R.layout.details, and through it, you are looking for the TextView you want to change the text size. But that layout is not at screen. It is not what you are seeing.
I have a view in android that shows a number of check boxes. They are all added dynamically and I set a text for each one in part.
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout layout = (LinearLayout) findViewById(R.id.layout);
LayoutInflater inflater = getLayoutInflater();
for (String string : getResources().getStringArray(R.array.string_array)) {
LinearLayout searchField = (LinearLayout) inflater.inflate(R.layout.cb, null);
CheckBox checkBox = (CheckBox) searchField.findViewById(R.id.checkBox1);
checkBox.setText(string);
layout.addView(searchField, new LinearLayout.LayoutParams(LinearLayout.LayoutParams.FILL_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT));
}
}
}
AS you can see from the code, I have an array of strings and for each of the strings in the array I add an check box. When the view is first shown, all the check boxes have the correct text, but after I rotate the device to landscape or portrait mode, all the check boxes have the same text (from the last check box). Any rotations (to redraw the screen) do not affect the text anymore. All of them remain with the text of the last check box.
I have looked in the debugger, the check box object is a new one for each string, so I am not working with the same instance of an object. I am currently out of ideas.
Do you have any idea why this is happening?
When you rotate the screen, runtime Resource is changed, and Activity is relaunched. Two ways to fix your problem:
Hold the value of all your checkbox in OnCheckedChangeListener and reset them back in onResume.
Handle onConfigurationChanged by yourself, according to https://developer.android.com/guide/topics/resources/runtime-changes.html#HandlingTheChange