I am using a custom expandable list adapter. I highlight a child when the user clicks on it. This works fine until the user opens/collapses a group. Say the user touches Group 2 Item 1. This highlights Group 2 Item 1. Then the user opens Group 1. Now Group 3 Item 2 is highlighted. I have done some testing choosing different items and I can not find a pattern where the highlighted row will jump to. Sometimes its up in the list, sometimes its down. I'm having trouble figuring out the logic to put in my activity's onGroupExpandListener and onGroupCollapseListener to rehighlight the correct view. Any ideas?
EDIT: Current code inside my onChildClickListener
if (groupPosition == 0){
switch(childPosition) {
case 0:
previouslySelectedView.setBackgroundResource(R.color.transparent);
currentlySelectedView.setBackgroundResource(R.color.blue);
break;
Same code for all the groups/children
Item slection in ExpandableListView is made through flat list (absolute positiond). Thus if the newlly opened group is before the currenet selection and has fewer children then the selection will move up and vice versa. I suggest to set choice mode to none and implement onclick/expand logic to handle focus on your own - implement tags for views, for example, and set the currently highlighted item via the tag.
Here are few suggestions:
In the ExpandableListView.OnChildClickListener first you perform ExpandableListView.findViewWithTag(theTag) to check for views with such tag and unmark them (also setTag(null)) and restore the background. Then for the item clicked setTag(theTag) and change the background to selected. Of course you can have some other logic and have multiple items marked. Note that once the view is destroyed you will lose the selection (for example during expands).
Have some custom map or something that will hold a unique ID of the view and the (un)marked state. That's the best solution that will allow to maintain persistent selection across scrolls and expands.
In the backing adapter introduce a "marked" state. Thus the marking will be persistent even between application start/stop. Not a good approach, though, because selection is more of a UI behaviour.
I am currently doing an ExpandableListView selection with Choice Mode Multiple of the list. But since, as I said, the selection is per position I had to sacrifice in terms of functionality - namely, I clear the selection whenever a manipulation is made. The previous implementation was with custom Map holding the selected IDs and - to honest - it was the better approach.
This is how I get selected IDs (remember I use choice mode multiple):
final SparseBooleanArray checkedPositions = expList.getCheckedItemPositions();
final ExpandableListAdapter adapter = expList.getExpandableListAdapter();
List<Long> checkedIds = new ArrayList<Long>();
if (packedPositionType == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
for (int i = checkedPositions.size() - 1; i >= 0; i--) {
if (checkedPositions.valueAt(i)) {
checkedIds.add(adapter.getGroupId(checkedPositions.keyAt(i)));
}
}
}
In your case, though, you will want to check for CHILD packed positions. Also note that my adapter has stable (unique) ids. If you do not have stable IDs then you can rely on ExpandableListView's getPackedPositionForChild() method and store the packed position as marked.
Right way to do it (Will solve jumping highlight upon group collapse)
Do the following on ExpandableListView:
Step 1. Set choice mode to single (Can be done in xml as android:choiceMode = "singleChoice")
Step 2. Use a selector xml as background (android:listSelector = "#drawable/selector_list_item")
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="#android:integer/config_mediumAnimTime">
<item android:drawable="#android:color/holo_blue_bright" android:state_pressed="true"/>
<item android:drawable="#android:color/holo_blue_light" android:state_selected="true"/>
<item android:drawable="#android:color/holo_blue_dark" android:state_activated="true"/>
</selector>
Step 3. Call expandableListView.setItemChecked(index,true) in onChildClick() callback.
index is a 0 based index of the child item calculated as follows
Group 1 [index = 0]
Child item 1
Child item 2
Child item 3 [index = 3]
Group 2 [index = 4]
Child item 1
Child item 2
Child item 3 [index = 7]
Group 3 [index = 8]
Child item 1
Child item 2
Child item 3 [index = 11]
If we have list headers as well, they would also account for index values.
Here is a working example:
#Override
public boolean onChildClick(ExpandableListView parent, View v,
int groupPosition, int childPosition, long id) {
...
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
parent.setItemChecked(index, true);
return true;
}
My solution for the collapse issue :
mDrawerExpandableList.setOnGroupCollapseListener(new OnGroupCollapseListener() {
#Override
public void onGroupCollapse(int groupPosition) {
if(ExpandableListView.getPackedPositionGroup(selectedFragmentId) == groupPosition) {
mDrawerExpandableList.setItemChecked(mDrawerExpandableList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition)), true);
}
}
});
mDrawerExpandableList.setOnGroupExpandListener(new OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
if(ExpandableListView.getPackedPositionGroup(selectedFragmentId) == groupPosition) {
mDrawerExpandableList.setItemChecked(((ExpandableListView)drawerListView).getFlatListPosition(selectedFragmentId), true);
}
}
});
//MY LIST A FOLLOWS
(0)SALES
(0)SALES
(1)SALES VIEW
(2)SALES RETURN
(3)SALES RETURN VIEW
(1)STOCK
(0)ADD STOCK
(1)VIEW STOCK
(2)SETTINGS
(3)NOTIFICATION
//declare tow integer variable globali as follows
private int lastCheckedGroupPosition=-1;
private int lastCheckedChildPosition=-1;
mDrawerList.setOnGroupClickListener(new OnGroupClickListener() {
#Override
public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
if(groupPosition==2){
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=-1;
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
parent.setItemChecked(index, true);
//TODO Write here the code for opening settings page
...............
...............
...............
return true;
}
else if(groupPosition==3){
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=-1;
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(groupPosition));
parent.setItemChecked(index, true);
//TODO Write here the code for opening settings page
...............
...............
...............
return true;
}
return false;
}
});
mDrawerList.setOnChildClickListener(new OnChildClickListener() {
#Override
public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
if(groupPosition==0){
if(childPosition==0){
//TODO Write here the code for opening SALES page
...............
...............
...............
}
else if(childPosition==1){
//TODO Write here the code for opening SALES VIEW page
...............
...............
...............
}
else if(childPosition==2){
//TODO Write here the code for opening SALES RETURN page
...............
...............
...............
}
else if(childPosition==3){
//TODO Write here the code for opening SALES RETURN VIEW page
...............
...............
...............
}
}
else if(groupPosition==1){
if(childPosition==0){
//TODO Write here the code for opening ADD STOCK page
...............
...............
...............
}
else if(childPosition==1){
//TODO Write here the code for opening VIEW STOCK page
...............
...............
...............
}
}
int index = parent.getFlatListPosition(ExpandableListView.getPackedPositionForChild(groupPosition, childPosition));
parent.setItemChecked(index, true);
lastCheckedGroupPosition=groupPosition;
lastCheckedChildPosition=childPosition;
return false;
}
});
mDrawerList.setOnGroupExpandListener(new OnGroupExpandListener() {
#Override
public void onGroupExpand(int groupPosition) {
if(groupPosition==lastCheckedGroupPosition){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
if(mDrawerList.isGroupExpanded(lastCheckedGroupPosition)){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
}
});
mDrawerList.setOnGroupCollapseListener(new OnGroupCollapseListener() {
#Override
public void onGroupCollapse(int groupPosition) {
if(groupPosition==lastCheckedGroupPosition){
if(lastCheckedGroupPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
if(mDrawerList.isGroupExpanded(lastCheckedGroupPosition)){
if(lastCheckedChildPosition!=-1){
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForChild(lastCheckedGroupPosition, lastCheckedChildPosition));
mDrawerList.setItemChecked(index, true);
}
else{
int index = mDrawerList.getFlatListPosition(ExpandableListView.getPackedPositionForGroup(lastCheckedGroupPosition));
mDrawerList.setItemChecked(index, true);
}
}
else{
mDrawerList.setItemChecked(-1, true);
}
}
});
Related
I managed to retrieve the data from the firebase and use a recylcerview and a holder to display them. I only need to retrieve data from certain nodes from the firebase. Nodes that are not taken by id but by an inside name:
The problem is that it only displays my data for certain nodes, but it displays them in the order in which they are found in the database. This leaves empty rows in the recyclerview. (for example if the second node is the one you are looking for, put an empty card then the one with data):
The function by which I take the data from the firebase depending on the name is:
private void LoadFeedbackMuzee(String denumire) {
options = new FirebaseRecyclerOptions.Builder<Parere>().setQuery(refM, Parere.class).build();
adapterFM = new FirebaseRecyclerAdapter<Parere, MyViewHolderRecenziiM>(options) {
#Override
protected void onBindViewHolder(#NonNull MyViewHolderRecenziiM holder, int position, #NonNull Parere model) {
if (model.getDenumire().equals(denumire)) {
String userId = model.getIdUser();
int photo = model.getLevel();
holder.parere.setText(model.getIntrb1());
userRef.child(userId).addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(#NonNull DataSnapshot snapshot) {
if (snapshot.hasChild("fullname")) {
String nume = snapshot.child("fullname").getValue().toString();
holder.username.setText(nume);
}
}
#Override
public void onCancelled(#NonNull DatabaseError error) {
}
});
if (photo == 1) {
holder.image.setImageResource(R.drawable.terrible);
} else if (photo == 2) {
holder.image.setImageResource(R.drawable.bad);
} else if (photo == 3) {
holder.image.setImageResource(R.drawable.okay);
} else if (photo == 4) {
holder.image.setImageResource(R.drawable.good);
} else if (photo == 5) {
holder.image.setImageResource(R.drawable.great);
}
}
}
#NonNull
#Override
public MyViewHolderRecenziiM onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.parere_layout, parent, false);
return new MyViewHolderRecenziiM(view);
}
};
adapterFM.startListening();
recyclerViewR.setAdapter(adapterFM);
}
This is happening because you are doing these things in the onBindViewHolder. So in this case what happens is, if your condition matches then the data is binded otherwise the item in the recycler is left empty means the item layout is just inflated and if you know the working of the adapter then you must be knowing that the size of the list is used to inflate the number of items (parere_layout) in recycler view. So the adapter inflates the layout but when it comes to binding (in onBindViewHolder) the data like userName and photo then if your defined condition matches then it binds otherwise the fields are left empty. Hope now you understand the working of the adapter.
So the solution is:
(1) that either you make itemView Visible for only your condition and make its visibility gone for unwanted condition. Sample code below
holder.itemView.setVisibility(View.GONE);// to make it's visibility gone
holder.itemView.setVisibility(View.VISIBLE);// to make it visible when condition matches.
(2) Recommended Solution: The second solution that I can see is that, if you filter your data before passing it to the adapter then your problem will be solved. By filtering, I mean that after fetching data from the database you just make another list in which you add only that data what you want to be shown, so in this way, your adapter code will be more cleaner and also your problem will be solved simultaneously.
Feel free to ask if something is unclear. And kindly mark this as the correct answer if it helps you so that in the future this answer can also help any other needy.
I THINK you have to do something to your views if data doesn't exist at a node but is still being looped through. So you would do something like this where you would make the views GONE if the denumire doesn't .equal(model.getDenumire) and VISIBLE if it does. And do the same if the snapshot.hasChild(fullname) doesn't have the name so like:
if (model.getDenumire().equals(denmuire) {
GETDATA()
if (snapshot.hasChild(fullname) {
holder.image.setVisibility(View.VISIBLE);
holder.name..setVisibility(View.VISIBLE);
holder.name.setName(Name)
if (photo == 1) {
holder.image.setImageResource(R.drawable.terrible);
} else if (photo == 2) {
holder.image.setImageResource(R.drawable.bad);
} else if (photo == 3) {
holder.image.setImageResource(R.drawable.okay);
} else if (photo == 4) {
holder.image.setImageResource(R.drawable.good);
} else if (photo == 5) {
holder.image.setImageResource(R.drawable.great);
}
}else{
holder.image.setVisibility(View.GONE);
holder.name.setVisibility(View.GONE);
}
} else{
holder.image.setVisibility(View.GONE);
holder.name.setVisibility(View.GONE);
etc...
}
I have a recyclerview. After setting adapter, user should be reorder their list and save it. And afterwards it should be populate like saved one. But onclicking list items, it color has to change and one of item will open a popup. I've write my codes, but when i add both onclick and ontouch, neither of them worked. If i comment one of them, they work fine. How can i solve this?
holder.itemView.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
touchHelper.startDrag(holder);
}
return false;
});
holder.clData.setOnLongClickListener(view -> {
row_index = position;
notifyDataSetChanged();
return true;
});
if (row_index == position) {
holder.clData.setBackgroundColor(context.getResources().getColor(R.color.blue_back));
holder.txtAmount.setTextColor(Color.WHITE);
} else {
holder.clData.setBackgroundColor(context.getResources().getColor(R.color.darkestGrey));
holder.txtAmount.setTextColor(context.getResources().getColor(R.color.blue_current));
}
/// https://github.com/sjthn/RecyclerViewDemo/blob/advanced-usecases/app/src/main/java/com/example/srijith/recyclerviewdemo/SwipeAndDragHelper.java
////// My Adapter
AdapterDatas adapter = new AdapterDatas (context, lstDatas);
SwipeAndDragHelper swipeAndDragHelper = new SwipeAndDragHelper(adapter);
ItemTouchHelper touchHelper = new ItemTouchHelper(swipeAndDragHelper);
adapter.setTouchHelper(touchHelper);
rvDatas.setAdapter(adapter);
touchHelper.attachToRecyclerView(rvDatas);
How to remove ListView items from ListView on Android?
But, I want to set a click on each item to 3 before removing it.
So if the item at the first position is clicked once and the second item clicked twice, do not remove any item until the first item click reaches 3. Then remove that item only and for other items in the ListView, each has to be clicked 3 times.
listi.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,final int position, long id) {
final PopupMenu pop = new PopupMenu(Danger.this, listi);
pop.getMenuInflater().inflate(R.menu.menu_location, pop.getMenu());
pop.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.Remove:
items.remove(position);
}//swithc
return false;
Create an ArrayList of integers and initialize it with exactly with same element count of your list view and set value of all elements of the list = 0
ArrayList<integers> counterList = new Arraylist();
for(int i = 0; i < listi.getAdapter.getChildrenCount(); i++){ // get total elements in adapter
counterList.add(0); // set each element of array list to 0
}
Then here:
listi.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view,final int position, long id) {
final PopupMenu pop = new PopupMenu(Danger.this, listi);
pop.getMenuInflater().inflate(R.menu.menu_location, pop.getMenu());
pop.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
#Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()) {
case R.id.Remove:
if(counterList.get(position) >= 2){
items.remove(position); // remove current position item from arraylist adapter and notify data set changed
counterList.remove(position); // remove the current position element from counter list too
} else {
counterList.set(position, counterList.get(position) + 1); // if 3 clicks have not happened then increase the counter.
}
}//swithc
return false;
Use static variable and hold it with null,Include the static variable inside ClickListener , Once the variable reaches 3,you can remove the items from list view
I would suggest adding 2 fields: int timesClicked and int lastItemId, then in your onMenuItemClick method, check if the Item needs to be removed:
int timesClicked = 0;
int lastItemId;
...
#Override
public boolean onMenuItemClick(MenuItem item) {
if(item.getItemId() == lastItemId) {
if(timesClicked == 3) {
timesCliked == 0;
items.remove(item);
} else timesClicked++;
} else {
lastItemId = item.getItemId();
timesClicked = 0;
}
}
This should work.
Make a bean (Model) class for having record of clicked item according to
their corresponding position and every time on item clicked first check that
how many time this item is clicked if it's returning 2 then remove the item
else not.
I have list with only three items , and i'm using GridLayout with two columns , so when calling it i found that rwo two has only one item , and the last is my footer , so i want to something add empty item or move my footer when my list has odd numbers to move new row
#Override
public void onBindViewHolder(final RecyclerView.ViewHolder holder, int position) {
if (holder instanceof FooterViewHolder) {
loadChatMessages();
}
private boolean isPositionFooter(int position) {
return position == dish.size();
}
#Override
public int getItemViewType(int position) {
if (isPositionFooter(position)) {
return TYPE_FOOTER;
}
return TYPE_ITEM;
}
#Override
public int getItemCount() {
return dish.size() + 1;
}
You should implement your own version of GridLayoutManager.SpanSizeLookup and add it to your layout manager by calling gridlayoutManager.setSpanSizeLookup(myLookup).
The description of SpanSizeLookup reads
A helper class to provide the number of spans each item occupies.
So for every normal item you'd return 1 (column used) and in case of your footer you need to return 2 (or the amount of columns total) from getSpanSize(int position) and you're set.
This will signal the layout manager that it needs the whole row.
I want to display a ListView where each row represents the answer to a question. When the user presses the button CHECK I want the rows of the ListView to change color according to the result (if the answer was wrong, correct, not selected and so on...).
In my onCreateView() I'm creating the following CursorAdapter:
Cursor crs = db.queryQuestionAnswers(questionID); // a cursor with all answers relative to the question
adapter = new CursorAdapter(getActivity(), crs, 0) {
#Override
public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {
return ((LayoutInflater) getActivity().getSystemService(Context.LAYOUT_INFLATER_SERVICE))
.inflate(R.layout.listfragment_test_row, listview, false); // Row of the listview
}
// Instance items of each row
#Override
public void bindView(View view, Context context, Cursor crs) {
Answer answer = test.getQuestion(current_question).getAnswer(crs.getPosition());
TextView answerTv = (TextView) view.findViewById(R.id.ans_test_button);
answerTv.setText(answer.getText());
view.setOnClickListener(new AnswerOnClickListener(answer));
if (show_solution) { // User has clicked "CHECK"
displayResult(view, answer); // Color answers according to selection and correctness
} else
view.setBackgroundColor(Color.TRANSPARENT); // The question has just been displayed
}
#Override
public long getItemId(int position) {
Cursor cursorAdapter = adapter.getCursor();
cursorAdapter.moveToPosition(position);
return cursorAdapter.getLong(cursorAdapter.getColumnIndex(DatabaseStrings.FIELD_ID));
}
};
Whenever the user presses the CHECK button so (bringing show_solution to true), the app should check the following fields and act on the relative view accordingly:
private void displayResult(View view, Answer answer) {
view.setEnabled(false);
if (answer.isSolution() && answer.isChoice()) // User selected a correct answer
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightGreen));
else if (answer.isChoice()) // User selected a wrong answer
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorMediumRed));
else if (answer.isSolution()) // User didn't select a solution
view.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightBlue));
else { // Answer was nor selected nor solution
// [...] Hide view with an animation [..]
}
}
}
But in all cases the last answer gets colored (apparently with a random color) and then vanishes (as it was nor selected nor solution).
It looks like for only the last row the program enters in all if statements.
The only case when it works as it was supposed to, is when the user selects all answers: correct ones are colored green and wrong one red... while no answer vanishes. Not even the last one.
I managed to fix the problem relative to the color adding the following line inside the last else statement.
view.setBackgroundColor(Color.TRANSPARENT);
But the last line keep vanishing. What's wrong?
EDIT:
Here's the AnswerOnClickListener implementation:
// Listener for answer selection
public class AnswerOnClickListener implements View.OnClickListener {
Answer answer;
private AnswerOnClickListener(Answer answer) { this.answer = answer; }
#Override
public void onClick(View v) {
answer.changeChoice();
if (answer.isChoice()) { // Answer has been selected by the user
v.setBackgroundColor(ContextCompat.getColor(getActivity(), R.color.colorLightYellow));
} else { // Answer has been deselected by the user
v.setBackgroundColor(Color.TRANSPARENT);
}
}
}