Let's say I have a layout like this:
Creating the layout is not complicated, if the questions are fixed. But my requirement is to display the questions from database, like this:
As you can see, there are 4 sectionIds. That means we have 4 categories. I'm thinking to use LinearLayout for this. Then for each categories, we have different amount of questions. If the question type is R, use RatingBar. But if the type is D, then use TextArea. I also plan to use LinearLayout for each questions. Now the challenge is creating those layout dynamically, which I think is not that easy. What's the least complicated way to do this?
I have had to do this in several opportunities, so I wish I can give a tip or more:
If your problem is not complex, look for something that has already solved it:
This library create a layout based on Firebase data
This library is for creating forms
Most of the times, any library will work because programmatic views are tight to specific requirements, so you will have to go on your own.
Customized Solution
Creating programmatic views it has a skew learning curve but with time you will be able to solve it.
Defining your fields:
Create fields or partial package and put those classes inside. You want to create a class for each type of field so you can reuse easily and modify it is done in that class.
Also, define what is common for every field. You can do this with an interface:
interface FormField {
String result();
boolean isValid();
void setError();
}
In this case, this interface will allow you to handle the result of the field, know if it is valid and set the errors. Validations should be inner once are requested, and error should be settable do internal validations and from outside.
The result you are getting can change by specific methods in other classes but having a common String is most of the time useful even for showing a summary to the user.
There is another benefit, you can create a list of your fields by your interface, every fields implement it:
List<FormFields> fields = new ArrayList();
//fields.add(ANY CLASS THAT IMPLEMENTS THE INTERFACE);
Creating a Field
Start by creating a class that extends a suitable view for your field:
public class InputText extends EditText implements FormFiel {
//You can simply add customizations in the constructor
}
public class InputText extends LinearLayout implements FormField {
//Maybe you need an input with a label, hence a TextView and an EditText will go inside of this
}
The rule of thumb for this task about constructor is one param for java two params for xml. So wherever you need to put the view, you use either one of those.
Handling Field appearance
If you need something simple maybe just set everything in the constructor.
public class InputText extends EditText implements FormField {
//You can simply add customizations in the constructor
public InputText(Context context) {
super(context)
setLayoutParams(new LayoutParams(LayoutParams.WrapContent...)
}
}
if you need something more complex use the layout inflater.
public class InputText extends LinearLayout implements FormField {
//You can simply add customizations in the constructor
public InputText(Context context) {
super(context)
LayoutInflater layoutInflater = LayoutInflater.from(getContext())
//You have to create the layout, a neat trick is create it inside a linear layout eand then use refactor/extract
//The last boolean in the method attach it to the view
layoutInflater.inflate(R.layout.field_input_text, this, true;)
//Maybe you want to find something, this could be a field variable
TexteView tv = this.findViewbyId(R.id.inside_the_inflated_layout)
}
}
Define a model
You need to define a model for all the field types
public class FireField {
private String label, hint, type;
// Empty constructor and getters and setters
}
Create a Container class
The container class probably is a linear layout with orientation vertical, you want to create it anyway and added in the xml (remember 2 params constructor) so you can add inside that view a way to get all your fields, a special method for that.
It can be a list of fields, it can be the list of the data you need to send.
This way, fiedls take care of their own logic and form take care of the general logic.
Add the views
You have to fetch your data then do a loop and for each type of the data, add a new view, this is when the List<FormField> of views come in handy, following is pseudo code
List<FireField> data = new ArrayList();
//You can also have String, View map here, where the key is the type and the value View is your field
List<FormField> fields = new ArrayList();
for (DataSnapshot children: snapshot.getChildren()) {
FireField field = children.getValue(FireField.class);
data.add(field);
if (field.getType.equals("INPUT_TEXT)) {
//Here Im addinf the field in the constructor, then the view should take care of it
new InputText(context, field)
//Here I'm initializing the view, the view inside that method should set labels and other
InputText input = new InputText(this);
input.initialize(field);
fields.add(input);
//You can add it here or in other place re using some of the lists above
container.addView(input);
}
}
Finally use any of the list and the container method to get the data and send it to Firebase
Related
I'm trying to create a custom View that contains a list of CheckBoxes based on my database. This means I have to create the Views at run-time, and can't do it in XML. However, the method I'm using for this is very slow. Is there a faster method to create large amount of Views in code?
For example, with the 18 types in my database, it can take over 1 second to create all the CheckBoxes.
class FilterView : LinearLayout {
private fun init(types : List<Type>){
... setup
// Creating the CheckBoxes, this takes all the time.
checkboxes = Array(types.size, {
AppCompatCheckBox(context).apply {
text = types[it].type
CompoundButtonCompat.setButtonTintList(this, ColorStateList(states, intArrayOf(colours[it], colours[it])))
}
})
... add to view
}
What your looking for is a Recyclerview. It can all be explained here. The downvote was likely because this is assumed to be common knowledge or easily googled on your own. I was new once too. Here you go.
Cheers,
I have an app that receives user input (2 numbers, width and height) and in theory depending on that input I have a custom view that should draw a grid (width and height).
Note:
These 2 values should be received before view attempts to draw itself.
These 2 values aren't constant and therefore I don't think XML approach can help.
I was told that adding another parameter to the View constructor is evil.
Do not confuse my 2 values with canvas.getWidth or etc.. these are values needed simply to draw something, nothing else.
My View is also a ViewGroup.
Main issue arises with Views declared in XML files.
I have temporarily solved this issue by making an SchemeContext class which contains those 2 static values and I simply set them in onCreate (before onCreateView) then use them in custom View onDraw when needed (SchemeContext.width). This is not really what people would call OOP I'm forcing global variables upon java and those are set on time because of the fragment lifecycle.
I've seen this answer How to pass variables to custom View before onDraw() is called?.
But it's more of a workaround than a solution (and probably not the fastest one). There has to be a sensible solution I don't think 3D games on android resort to these workarounds (SurfaceView with OpenGL is still a View right? :d).
If there is an obvious solution and this is an obvious double I'll remove the question.
I haven't tried this, but I think it would be possible to do this fairly cleanly by overriding the LayoutInflater.Factory. That way, you can intercept the creation of the views that need additional parameters passed to their constructors, and let the rest of them fall through to default inflation.
For example, in your activity, before you inflate the view hierarchy:
LayoutInflater inflater = (LayoutInflater)getSystemService(LAYOUT_INFLATER_SERVICE);
MyInflaterFactory factory = new MyInflaterFactory();
// Pass information needed for custom view inflation to factory.
factory.setCustomValue(42);
inflater.setFactory(factory);
For your implementation of the factory:
class MyInflaterFactory implements LayoutInflater.Factory {
public void setCustomValue(int val) {
mCustomVal = val;
}
#Override
public View onCreateView (String name, Context context, AttributeSet attrs) {
if (name.equals("com.package.ViewWithCustomCreation")) {
return new ViewWithCustomCreation(context, attrs, mCustomVal);
}
return null;
}
private int mCustomVal;
}
I was told that adding another parameter to the View constructor is evil.
Nonsense.
There are three (and in the newest APIs, four) different View constructors, each used in a different situation. (See this thread.) If you wanted to be able to declare your view in XML, for example, then you'd have to provide a constructor with exactly the right parameters, and have it call the corresponding superclass constructor. But there's nothing wrong with defining your own constructor (or even several of them) that call the superclass constructor intended for creating views programmatically.
The overriding principle is that every object must be valid when its constructor returns. So unless you can provide reasonable default values in your constructor, you have little choice but to accept the object's properties as constructor parameters.
I am new at android programming and want to create an application in which I can add multiple custom tags to an displayed Image. I researched a bit and found out aboutsetTag() method of ImageView. But it does mention if it allows multiple tagging.
Also is there any way that those tags to remain visible (along with appropriate tagged position) on the image?
Will I require an SurfaceView or GridView for this?
Sources :
Android Image View
android-Image View set Tag
Thank you.
Instead of looking for multiple tags you can use a class MyTag as follows to tag more than one data to a view
public class MyTag
{
int int_Tag;
String string_Tag;
MyClass myclass_obj_Tag;
public MyTag()
{
int_Tag=0;
string_Tag=null;
myclass_obj_Tag=null;
}
public MyTag(int i,String s,MyClass m)
{
int_Tag=i;
string_Tag=s;
myclass_obj_Tag=m;
}
}
create an object of this class and assign values to variables in object
MyTag myTag=new MyTag(1,"string_tag",myClass_obj);
iv.setTag(myTag);
just give it a try,I have used this method,
You can add any object as a tag. If the data you're adding need more data, the simplest way would be to add a Hashtable as the tag. Then add all the key/value pairs you want to that Hashtable.
As the question states I simply want to add more than one tag to an XML View. For example, say I want to set an array of strings AND a separate string from my resources. I know how to do them individually but I want to know if there's a way of attaching more than one tag to a view directly within the XML code.
Edit:
My plan was to have a LinearLayout (l#1) that contained a dynamic amount of of a different LinearLayout (l#2) and within that View there would be a Spinner and an EditText. I need one tag for the hint of the EditText and the other for the array of strings to populate the Spinner. In the entire layout there are a multiple l#1 each using l#2 to populate it dynamically and each needing different hints and string arrays based on what they are used for.
My next idea was to add a integer as a tag to represent l#1 and and use a Switch/Case block in my code to populate the children of l#2 with the right hints and string arrays.
I don't think this is possible in XML, but in code what you could do is create a custom object which holds the strings you require and set that as the tag.
class CustomTagObject {
public List<Strings> strings;
public String myString;
}
Then later
CustomTagObject tagObj = new CustomTagObject();
tagObj.strings = new ArrayList<Strings>("String 1", "String 2");
tagObj.myString = "String from resources";
view.setTag(tagObj);
If you explain why you want to hold these items as the tag, I may be able to help you find an alternative approach?
Above solution works, but the usage is wrong(it will add extra overhead on your end to manage the key/value map).
The better way to achieve above is to use an overloaded method of setTag which allows you to specify id associated with the value.
Method signature:
public void setTag(int key, Object tag)
Is there a view in android, that can store one object and display the object's toString() return value like a TextView?
Or do I have to mimic the behaviour in taking a ListView and ensure that only one item is in its adapter?
I need this, because I use drag & drop to move objects around and I need a view, that can display and store one of the objects.
Thank you!
There is no such view. Depending on your requirements, you have to implement this functionality.
yes, there is such a view and you already mentioned it. it's called TextView, and you'd use it like this,
TextView tv = (TextView)findObjectById(R.id.tv);
tv.setText(myObject.toString());
if you don't like like that, extend TextView like this,
class ToStringView extends TextView {
...
public void setObject(Object o) { setText(o.toString()); }
}