I've got some very tricky problem. I already tried to search the web and even looked into the MvvmCross sources, but I don't seem to be able to figure it out.
I have an MvxListView with a custom Adapter. The reason is, that depending on the "DataContext" of the current ListItem, I want to display some different view.
The list itself represents some sort of questionnaire. So the items in the list are in the form of
new Question("do you need help?"){
new Answer("yes"),
new Answer("no"),
new Answer("maybe")
}
Now the answers shall be shown as a radio button list.
So in my custom adapter on "GetChildView", I retrieve the view with the radiogroup and then I
"just want to bind that group to my answers" --> so for each answer, there has to be a corresponding radiobutton.
I would love to have the "Answer" object as datacontext for each radiobutton.
radioButton.Bind("Checked", "Chosen"); // where "Chosen" is the boolean property on "Answer"
But it would already be fine if the "Question" object could be the datacontext that I bind to
radioGroup.Bind("CheckedRadioButtonId", "ChosenAnswer"); // where "ChosenAnswer" is an int property
on "Question"
So basically I want to bind my radiobutton to the MvxListItem.DataContext in code inside my customadapter.
But I just cannot figure out how to do that. :/
Can you please give me a hint?
Of course I would love to do the same with a list of checkboxes as soon as multiple answers would be allowed.
Setting the datacontext is easy: just set it :)
What you do is you create a ViewModel called something like QuestionViewModel, which has what you need as a separet ViewModel.
Then create some component to use in your View for the complete questionnaire. Below is some example code for a bindable component.
public class BindableLinearLayout : ClickableLinearLayout, IMvxDataConsumer, IMvxBindingContextOwner
{
public BindableLinearLayout(Orientation orientation, object dataContext)
: base(orientation)
{
BindingContext = new MvxBindingContext();
DataContext = dataContext;
}
public object DataContext { get { return BindingContext.DataContext; }
set { BindingContext.DataContext = value; }
}
public IMvxBindingContext BindingContext { get; set; }
}
In the Questionnaire View, create this component and assign the datacontext (in the above example as a parameter). Then you can create the binding in the normal way:
var bindings2 = layout.CreateBindingSet<BindableLinearLayout, ParagraphViewModel>();
bindings2.Bind(numberText.View).For(t => t.Text).To(vm => vm.Paragraph.Number);
bindings2.Apply();
This code is called for each element you add to the collection, eauch with its own Datacontext.
I known this code is not for a list adapter, but I hope this will give you enough hints how to do this yourself.
Related
In our chat app we want to use cool new Library SQLBrite to update chat on database changes. Since our chat has endless scrolling, and chat room can have very big list of messages, we want to split ArrayList supplied to Chat ListView adapter into two lists. Check graphic for the idea.
We want to set point in database above which, old messages will be queried by normal SQLite queries. And below that point we want set SQLBrite, that will bring us fresh messages added to database.
Each part should populate its corresponding ArrayList. And two arrayLists should be combined in one adapter.
My question is it possible to do? If yes how we can combine and handle two dynamic ArrayLists in single adapter?
Edit 1
1. I need to keep chat scroll position during from resetting, and no flickers during ArrayLists update.
1.With the help of generics you can handle two arraylist with single ArrayList.
For example in adapter :
setListData(ArrayList<T> pListData)
{
mListData=pListData;
}
In View
getView(int position, View convertView, ViewGroup parent){
T commonModel= getItem(position);
if(T instanceof ArrayListOneModel){
ArrayListOneModel model1=(ArrayListOneModel)T;
do stuf for first arraylit...
}
}
If you are using same model you can set a type (enum ) for both arraylist
& during showing time you can check that.
3.Otherwise you can first add old data in arraylist & then using collection
addAll() add 2nd latest message list in it. then
adapter.notifyDataSetChanged()
will set first old message then will set latest message in your list
More Clarification:
In second approach if you have different models for both arraylist then contain an enum in both model as a setter getter.
public enum eType{
FIRST_LIST_TYPE,SECOND_LIST_TYPE
}
During Fetching data from different DB's set Type in model.
e.g
public class model{
private enum eType;
// other setter getter value from your DB
/**
* Setter getter:
*/
public void seteType(enum eType)
{
this.eType = eType;
}
public enum geteType()
{
return eType;
}
}
During fetching data set Type e.g.
Model model = new Model();
model.seteType(eType.FIRST_LIST_TYPE) ;
//same for 2nd db.
& simply check type inside getView() according to your requirement.
yes that is possible inside BaseAdapter getCount method write following code
#Override
public int getItemCount() {
return list1.size()+list2.size();
}
and inside getView method you can do something like below
public View getView(int position, View convertView, ViewGroup viewGroup) {
if(position < list1.size) {
Object object = list1.get(position);
//write code to inflate view here related to list 1
}
else {
Object object = list2.get(position - list1.size());
//write code to inflate raw here related to list 2
}
}
You can pass only one list in adopter, which means you have to merge both arrays.
In order to merge both array. they have to be of same type, i.e. Array of same custom object.
If arrays are updating dynamically, then merge arrays again, as their data changes, and call notifyDataSetChanged() each time, to reflect changes in listview
Yes you can do it. but both arraylist should have common data format.
for eg ..
In adapter you can make method like
public void addMessages( <your_array_list> data ) {
list.addAll(data); //where list is your data container
}
now you may have two arraylist
like
ArrayList<your_type> oldMsg;
ArrayList<your_type> newMsg;
..
..
...
.
.
so you can call adapter method which we have created
yourAdapter.addMessages(oldMsg);
yourAdapter.addMessages(newMsg);
In an activity I have a ViewPager with 2 tabs: "ALL" and "Filtered".
Both pages use the same Fragment to display the data, with the difference that the "Filtered" page filters the data by some criteria.
I want to click on an item from the "ALL" page (which might exist as well in the "Filtered" page), and if I do:
onData(transactionWithId(960L)).perform(click());
in response I get:
AmbiguousViewMatcherException: 'is assignable from class: class
android.widget.AdapterView' matches multiple views in the hierarchy
I then tried to refine my description by specifying an additional constraint, that I am looking for an item that is visible:
onData(allOf(transactionWithId(960L), isDisplayed())).perform(click());
and I got the same error.
then I thought to specify somehow, that I am looking for my item in the "ALL" tab (not sure though if this is correct):
onData(allOf(
transactionWithId(960L),
withParent(withText("ALL")))
).perform(click());
but again the same error.
Then I tried to specify that I am looking for an AdapterView that is currently in front of me:
onData(allOf(
is(instanceOf(Transaction.class)),
transactionWithId(960L))
).inAdapterView(allOf(
isAssignableFrom(AdapterView.class),
isDisplayed())
).perform(click());
and I got:
PerformException: Error performing 'load adapter data' on view '(is
assignable from class: class android.widget.AdapterView and is
displayed on the screen to the user)'.
Note that I can click on an item that is displayed in an Activity with a single ListView, the challenge I am facing is when I have a ViewPager with multiple tabs that use one Fragment to display the data.
Would appreciate any help.
Your approach should work, I created a minimal working example here.
The gist of the problem is to distinguish the two adapter views. An alternative approach would be to mark them explicitly with tags. Then we can restrict the DataInteration with inAdapterView() with a custom Matcher on a specific tag. The complete code is still here, I quote the key points:
In the adapter view:
boolean isFiltered = ...
AdapterView av = ...
av.setTag(isFiltered);
In the test:
#Test
public void testClickOnSecondItemInAllTab() {
onData(instanceOf(String.class)).inAdapterView(withTag(false)) //
.atPosition(1) //
.perform(click());
}
And the custom view matcher on the tag:
static Matcher<View> withTag(final Object tag) {
return new TypeSafeMatcher<View>() {
#Override
public void describeTo(final Description description) {
description.appendText("has tag equals to: " + tag);
}
#Override
protected boolean matchesSafely(final View view) {
Object viewTag = view.getTag();
if (viewTag == null) {
return tag == null;
}
return viewTag.equals(tag);
}
};
}
I would like to improve the way i created the following UI. Currently i am creating each tablerow programmatically according to each object's type attribute.
class objectDTO {
private type; //enum
public boolean isMultiple(){...}
public boolean isSingle(){...}
}
I am trying to find a more dynamic solution like having a class for each type that might not requires programmatically adding layouts and components as i do in the fragment class,
if(objectDTO.isMultiple()) {
//Create TableRow + Multiple radiobuttons
}
else if(objectDTO.isSingle() {
//Create TableRow + Add One Checkbox
{
else {
//Create default invalid object Interface or skip
}
Havind a listadapter and applying the different ui there will just move the design problem to other class.
Early thanks for your contribution
Well, the simple solution for you would be to have a class hierarchy- a base objectDTO class and a child for each type. When you load the object list, have a factory method create the proper type of object. Each type would override a createView method which would create the view for that type. Then your table creation function becomes:
for(objectDTO object : allObjects){
View view = object.createView();
tableView.addView(view, lp);
}
But if you're creating a view for an object type, there's always going to need to be someone that dynamically creates view objects (createView in this case), and there's always going to need to be some function that knows what class to make an object (the factory in this case). Its just a matter of where you want that complexity to be.
In my QuestionsActivity, I am showing a question and bunch of answers. The question is displayed on TextView and answers are displayed on ListView that is composed of TextViews. There is an ActionButton named "Check Answer" and when it is clicked, it shows the correct answer by changing the background color of the TextView in the ListView.
The background changing code looks like this:
if (allAnswers.get(i).isCorrect())
{
mAnswerList.getChildAt(i).setBackgroundColor
(getResources().getColor(R.color.correct_answer_background));
return;
}
and now there are two Buttons at the footer section of this QuestionsActivity called PreviousQuestionButton and NextQuestionButton and they are basically navigation buttons between questions.
The problem is, when I go to the next question after clicking on "Check Answer" button, the answer background color doesn't go away and remains in the next question answer options. I tried invalidate(), refreshDrawableState() method of ListView but no luck!
This is the method which displays the answers for a given question:
private void showAnswers(int questionLocation)
{
int questionId = mAllQuestions.get(questionLocation).getQuestionId();
List<Answer> answers = mAnswerRepository.getAllByQuestionId(questionId);
mAnswerAdapter.clear();
for (int i = 0; i < answers.size(); i++)
{
mAnswerAdapter.add(mOptionLetters[i] + ". "
+ answers.get(i).getAnswerText());
}
mAnswerAdapter.notifyDataSetChanged();
}
My Question
What I want is that when I click on next or previous buttons, the background color of the correct answer in ListView should disappear so that next and previous question button can show non-selected answer options list to the user. Is there any method which resets ListView to a state which does not have any background applied?
For selected answer option, I am using mAnswerList.clearChoices() in order to unselect but it does not apply for correct answer background color.
Well, to reset the color you can very well hard-reset the adapter by creating a new one. So don't clear and add as that may keep the views in the state they were before. I am not too sure about this since I am not clearing or adding from an adapter, but always creating a new one to fulfill my new needs.
Anyway, another reason why things may not go in the direction you want is that the views may get recycled, since we're talking about a ListView. So if you want to highlight a list item, you should keep in the data model the information about highlight by initializing it to false and if the user selects one set the highlight state to true. I suppose the Answer class has as a minimum the following:
public class Answer {
private String data;
private boolean correct;
public String getData() {
return data;
}
public boolean isCorrect() {
return correct;
}
#Override
public String toString() {
return data;
}
}
So your adapter could look close to this - getView method is the most important to notice (don't forget to set to default background if the answer is incorrect or the adapter should not highlight correct answer):
public class MyAdapter extends ArrayAdapter<Answer> {
private boolean showCorrectAnswer;
private List<Answer> modelAnswers;
public MyAdapter(Context context, List<Answer> answers) {
super(context, android.R.layout.simple_list_item_1, answers);
this.modelAnswers = answers;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View view = super.getView(position, convertView, parent);
if(showCorrectAnswer && modelAnswers.get(position).isCorrect()) {
view.setBackgroundColor(getContext().getResources().getColor(R.color.correct_answer_background));
} else {
view.setBackgroundColor(getContext().getResources().getColor(R.color.default_background));
}
}
public void setShowCorrectAnswer(boolean showCorrectAnswer) {
this.showCorrectAnswer = showCorrectAnswer;
notifyDataSetChanged();
}
}
What you need to do is to keep a reference to this custom adapter and if you need to highlight the correct answer or not simply call setShowCorrectAnswer(true / false);. It will trigger a redraw and in the getView() it will decide what to do based on adapter state and correct answer.
Hope it make sense ... I wrote all this while drinking a beer :)
Basically, I agree with the answer from payeli - you should change the background of selected answer in the "next/previous question" button.
But then there is this question:
"Is there any method which resets ListView to a state which does not have any background applied?"
Answer to this (as far as I know) is: not directly. But there are two workarounds.
If you call notfiyDataSetChange, not all views are completely redrawn. If possible, just the appropriate values are changed. It's mainly for performance reasons. (Imagine having 1000 contacts with images and names dumped and redrawn)
So how can you deal with this? (Other then reseting the background in the onClick method) Since you said these items are answers for a question, I assume you are not concerned about performance because there won't be too many of them.
Then you can simply:
A) Create new instance of Adapter instead of changing data in the old one. When you switch adapters, all views in list are removed, so, no recycling can happen in the new adapter.
B) Create custom Adapter, override getView method and make sure every time view is requested, you return a new view, so no recycling can happen.
Again: this really isn't "performance friendly" and should not be used with big lists with a lot of items!
If you want to know more, feel free to ask in comments or read the reference of Adapter class, especially the parameter convertView of getView method. (http://developer.android.com/reference/android/widget/Adapter.html)
As per my understanding U need to change color of text view in one of the list view row i.e row containing correct answer.
If this is your problem then On Click of button simply clear list view and recreate list view. Not when list view is recreated then check out position of row which contains correct answer. After getting row position simply change color of text view.
For Ex: In your Adapter class check as fallows in your getView();
for ( int i = 0; i < position.length ; i++ )
{
if(position == your_required_position){
{
textview.setColor(Color.BLUE);
}else{
textview.setColor(Color.BLACK);
}
}
In the onClickListener of "Check Answer" button, you need to reset the color:
void onClick (View v){
..............
mAnswerList.getChildAt(currentQuestion).setBackgroundColor
(getResources().getColor(R.color.default_answer_background));
}
you can save the default background somewhere, and when you press next/previous question button you should apply that color. Example:
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
android.R.attr.colorBackground,
android.R.attr.textColorPrimary,
});
int backgroundColor = array.getColor(0, 0xFF00FF);
int textColor = array.getColor(1, 0xFF00FF);
array.recycle();
I found this code online to get background and text color (you can just keep the background part), when the onClick activates just set the background of your view to "backgroundColor"
I created an expandable listview based on this link. Its just working fine. Now what i want is
1) How to make a childview to link another sub-child view
2) The sub- child view should be open as a new list view on the window(Right side of the view) is my expected layout. I googled but couldn't find how to achieve this. Please help me in achieving this. Thanks in advance.
You'll need to specify and handle onClick event of ListView row items.
Then you'll open a new Activity, based on the item clicked.
Parameters for new activity are supplied through intent extras, the new activity can use these values to get data from cloud or process the values to show certain results.
I've used CustomAdapter class several times to handle this scenario.
class Ocl implements OnClickListener{
#Override
public void onClick(View v) {
Intent intDetail = new Intent(getActivity(), PartDetail.class);
intDetail.putExtra(_ID, mParts[position].getSPr());
intDetail.putExtra(_LOT, mParts[position].getLotID());
intDetail.putExtra(_QTY, mParts[position].getQty());
intDetail.putExtra(_UID, mParts[position].getPartID());
startActivity(intDetail);
}
}
So, do you want your first child to expand into another ListView? Or maybe just open another Activity/Fragment that contains the matching ListView?
In case you want to the the first, you could design a CustomLayout for the Childview, which on OnClick expands, and changes its content to a specific ListView.
Otherwise you would just open up another ListView with data depending on Which Child in First List was Clicked.
Well, i am using some Like that to enlarge ChildViews on Click to show me detailed information.
Im using a Class to wrap my Data named Row. These Rows indicate if they are clickable and if so, the ListView will allow clicks on the rows. A Click will then be handled by the Class itself, making the displayed Text longer(more Detailed). And in order to relayout the items, you need to call notifyDataSetChanged.
#Override
public void onClick(Context context, MyExpandableListAdapter mela) {
this.setBig(!isBig());
mela.notifyDataSetChanged();
}
So, i would handle the row state (expanded/normal) in the getView Method of parents Adapter, to decide which childLayout i inflate.
would looke something like this
public View getView (args...) {
Object data = getItem(position);
if (data.isExpanded()) {
//inflate ListView Layout, create Addapter fill it....
} else {
//show some title or whatever to identify row.
}
}