It's possible to use expandable list items with new RecyclerView? Like ExpandableListView?
This is simple to do with the stock LayoutManagers, it all depends on how you manage your adapter.
When you want to expand a section you just add new items to your adapter after the header. Remember to call notifyItemRangeInserted when you do this. To collapse a section you simply remove the relevant items, and call notifyItemRangeRemoved(). For any data changes that are appropriately notified, the recycler view will animate the views. When adding items, an area to be filled with the new items is made, with the new items fading in. Removal is the opposite. All you need to do besides the adapter stuff is to style your views to convey the logical structure to the user.
Update: Ryan Brooks has now written an article on how to do this.
Get the sample code implementation from here
Set ValueAnimator inside onClick of ViewHolder
#Override
public void onClick(final View view) {
if (mOriginalHeight == 0) {
mOriginalHeight = view.getHeight();
}
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
Here is the final code
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
private TextView mFriendName;
private int mOriginalHeight = 0;
private boolean mIsViewExpanded = false;
public ViewHolder(RelativeLayout v) {
super(v);
mFriendName = (TextView) v.findViewById(R.id.friendName);
v.setOnClickListener(this);
}
#Override
public void onClick(final View view) {
if (mOriginalHeight == 0) {
mOriginalHeight = view.getHeight();
}
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight, mOriginalHeight + (int) (mOriginalHeight * 1.5));
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(mOriginalHeight + (int) (mOriginalHeight * 1.5), mOriginalHeight);
}
valueAnimator.setDuration(300);
valueAnimator.setInterpolator(new LinearInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
}
https://github.com/gabrielemariotti/cardslib
This library has an implementation of an expandable list with a recyclerview (refer to the demo app under "CardViewNative" --> "List, Grid, and RecyclerView" --> "Expandable cards"). It also has a lot of other cool combinations of cards/lists.
Someone complained about that the above mentioned solution is not usable with a listview as expandable content. But there's a simple solution: create a listview and fill this listview manually with your rows.
Solution for the lazy ones: there's a simple solution if you don't want to change your code to much. Just manually use your adapter to create views and add them to the LinearLayout.
Here's the example:
if (mIsExpanded)
{
// llExpandable... is the expandable nested LinearLayout
llExpandable.removeAllViews();
final ArrayAdapter<?> adapter = ... // create your adapter as if you would use it for a ListView
for (int i = 0; i < adapter.getCount(); i++)
{
View item = adapter.getView(i, null, null);
// if you want the item to be selectable as if it would be in a default ListView, then you can add following code as well:
item.setBackgroundResource(Functions.getThemeReference(context, android.R.attr.selectableItemBackground));
item.setTag(i);
item.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// item would be retrieved with:
// adapter.getItem((Integer)v.getTag())
}
});
llExpandable.addView(item);
}
ExpandUtils.expand(llExpandable, null, 500);
}
else
{
ExpandUtils.collapse(llExpandable, null, 500);
}
helper functions: getThemeReference
public static int getThemeReference(Context context, int attribute)
{
TypedValue typeValue = new TypedValue();
context.getTheme().resolveAttribute(attribute, typeValue, false);
if (typeValue.type == TypedValue.TYPE_REFERENCE)
{
int ref = typeValue.data;
return ref;
}
else
{
return -1;
}
}
helper class: ExpandUtils
Kavin Varnan postet already how to animate a layout...
But if you want to use my class, feel free to do so, I posted a gist: https://gist.github.com/MichaelFlisar/738dfa03a1579cc7338a
You can use ExpandableLayout that like a smooth expand/collapse animation CheckBox, so you can use it as CheckBox in ListView and RecyclerView.
https://github.com/KyoSherlock/ExpandableLayout
This is the sample code for what is mentioned by #TonicArtos to add and remove Items and to animate it while doing, this is taken from RecyclerView Animations and GitHub sample
1) Add Listener inside your onCreateViewHolder() to register for onClick
2) Create your custom OnClickListener inside your Adapter
private View.OnClickListener mItemListener = new View.OnClickListener() {
#Override
public void onClick(View v) {
TextView tv = (TextView) v.findViewById(R.id.tvItems);
String selected = tv.getText().toString();
boolean checked = itemsList.get(recyclerView.getChildAdapterPosition(v)).isChecked();
switch (selected){
case "Item1":
if(checked){
deleteItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
}else {
addItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
}
break;
case "Item2":
if(checked){
deleteItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(false);
}else {
addItem(v);
itemsList.get(recyclerView.getChildAdapterPosition(v)).setChecked(true);
}
break;
default:
//In my case I have checkList in subItems,
//checkItem(v);
break;
}
}
};
3) Add your addItem() and deleteItem()
private void addItem(View view){
int position = recyclerView.getChildLayoutPosition(view);
if (position != RecyclerView.NO_POSITION){
navDrawItems.add(position+1,new mObject());
navDrawItems.add(position+2,new mObject());
notifyItemRangeInserted(position+1,2);
}
}
private void deleteItem(View view) {
int position = recyclerView.getChildLayoutPosition(view);
if (position != RecyclerView.NO_POSITION) {
navDrawItems.remove(position+2);
navDrawItems.remove(position+1);
notifyItemRangeRemoved(position+1,2);
}
}
4) If your RecyclerViewAdapter is not in the same Activity as Recycler View, pass instance of recyclerView to the Adapter while creating
5) itemList is a ArrayList of type mObject which helps maintain states of item (Open/Close) , name, type of Item(subItems/mainItem) and set Theme based on values
public class mObject{
private String label;
private int type;
private boolean checked;
}
Related
Recycler view item color change repeating after scrolling.
I used to change color at a particular position of the Recyclerview list. When scrolling occurs another item at the bottom has the same change. And it is repeating in pattern. How to resolve this?
holder.recycle_Listing.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
itemListener.connectionClicked(v,position, itemtype);
holder.mainlayout.setBackgroundColor(Color.parseColor("#e927a4d1"));
}
});
The recycler view recycles the view in OnBindViewHolder.So when items are clicked it gets reflected in some other positions.To solve this. create a global SparseBooleanArray to store the clicked position.
private final SparseBooleanArray array=new SparseBooleanArray();
Then inside final viewholder add the clickListener and onClick store the position of the clicked item.
public class ViewHolder extends RecyclerView.ViewHolder {
public YOURVIEW view;
public ViewHolder(View v) {
super(v);
view = (YOURVIEW) v.findViewById(R.id.YOURVIEWID);
view.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
array.put(getAdapterPosition(),true);
notifyDataSetChanged();
}
});
}
}
And in inside OnBindViewHolder,
#Override
public void onBindViewHolder(final ViewHolder holder, final int position) {
if(array.get(position)){
holder.mainlayout.setBackgroundColor(Color.parseColor("#e927a4d1"));
}else{
holder.mainlayout.setBackgroundColor(UNSELECTEDCOLOR);
}
}
I think you can set your background color in void onBindViewHolder(VH holder, int position); such as
List<Integer> selectedPosition = new ArrayList(yourDataSize);
void onBindViewHolder(VH holder, int position){
if(selectedPosition.get(position) == 1){
holder.mainlayout.setBackgroundColor(Color.parseColor("#e927a4d1"));
}else {
holder.mainlayout.setBackgroundColor(normalColor);
}
//when the item clicked
selectedPosition.add(position,1);
}
That function returns relative position not absolute so when screen is scrolled position is replaced with a new value. use position from your list for the desired result.
It is due to recycling of viewholder of recycler view. Try this
It worked for me.
public ListViewHolder(View itemView) {
super(itemView);
this.setIsRecyclable(false);
}
try to implement this method in your adapter class, may be it's solve your problem
#Override
public void onViewRecycled(ViewHolderProduct holder) {
super.onViewRecycled(holder);
holder.mainlayout.removeAllViews();
}
Just saved every item keys in an array and that selected array also passed through my Adapter class. Even simple colour change works fine in this format. Here the code is changed as per my the requirement.
#Override
public void onBindViewHolder(final ICConversationHomeAddConnectionsAdapter.ViewHolder holder, final int position) {
JsonObject object = dataArray.get(position).getAsJsonObject();
if(selectedArray.contains(object.get("userkey").getAsString()))
{
GradientDrawable borCol = new GradientDrawable();
borCol.setCornerRadius(7);
borCol.setColor(Color.parseColor("#ffffff"));
borCol.setStroke(2, Color.parseColor("#60B9E1"));
holder.recycle_Listing.setBackgroundDrawable(borCol);
//holder.mainlayout.setBackgroundColor(Color.parseColor("#e927a4d1"));
}
else
{
GradientDrawable borCol = new GradientDrawable();
borCol.setCornerRadius(7);
borCol.setColor(Color.parseColor("#ffffff"));
borCol.setStroke(1, Color.parseColor("#e0e0e0"));
holder.recycle_Listing.setBackgroundDrawable(borCol);
//holder.mainlayout.setBackgroundColor(Color.parseColor("#f1f1f1"));
}
holder.profileName.setText(object.get("name").getAsString());
holder.recycle_Listing.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
holder.mainlayout.setSelected(!holder.mainlayout.isSelected());
if (holder.mainlayout.isSelected()) {
GradientDrawable borCol = new GradientDrawable();
borCol.setCornerRadius(7);
borCol.setColor(Color.parseColor("#ffffff"));
borCol.setStroke(2, Color.parseColor("#60B9E1"));
holder.recycle_Listing.setBackgroundDrawable(borCol);
// holder.mainlayout.setBackgroundColor(Color.parseColor("#11B5DA"));
} else {
GradientDrawable borCol = new GradientDrawable();
borCol.setCornerRadius(7);
borCol.setColor(Color.parseColor("#ffffff"));
borCol.setStroke(1, Color.parseColor("#e0e0e0"));
holder.recycle_Listing.setBackgroundDrawable(borCol);
// holder.mainlayout.setBackgroundColor(Color.parseColor("#f1f1f1"));
}
itemListener.connectionClicked(v,position, itemtype);
//holder.mainlayout.setBackgroundColor(Color.parseColor("#11B5DA"));
//holder.mainlayout.setBackgroundColor(Color.parseColor("#f1f1f1"));
}
});
}
This code works fine with no repeated colour change in recycler. If any queries feel free to ask via comments or chat
Recyclerview & Room Database in Kotlin.
in Activity: //to send position to Adapter
val putPosition = 5 // what you want to be position
Handler(Looper.getMainLooper()).postDelayed({
(Recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(putPosition-1,0)
}, 1500)
//
//
// out of onCreate (for companion)
fun getVersePosition() = putPosition
in Adapter: // to get position from Activity
override fun onBindViewHolder(holder: Adapter.Holder, position: Int) {
val roomData = listData[position]
holder.setRoomData(roomData)
val activity : MainActivity.Companion = MainActivity
val getPosition : Int? = activity.companionMainActivity?.getVersePosition()
if (position == getPosition-1){
holder.itemView.setBackgroundColor(Color.parseColor("#AFB42B"))
} else {
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
}
}
SparseBooleanArray() doesn't need
and bind doesn't need too.
But it's very important
holder.itemView.setBackgroundColor(Color.TRANSPARENT)
If omitted, repetition will occur.
I have a recyclerview adapter that is based on inflating two different card layouts. This adapater is dynamically updated with new cards and therefore each of the cards in the adapter needs to be updated as well. For each of the cards, there are a progressbar showing random generated data.
However, the problem Im facing is when I remove one of the cards in my list, the reference for the deleted card is not removed (the onCreateViewHolder is not called and therefore does not update the views), instead the cards left are just writing over the layout of the "deleted" card. The text on the card is correct and it removes the correct item from the list, but the progress bar is still having the old reference for the old card, which makes it adding values that corresponds to the deleted card.
To illustrate the problem, I'm adding random data to each of the cards in the progressbar depending on the movement name. So regardless of the card, the movement card named "Open Hand" should always add random data between 0-20, "Close hand" 20-60" and "Pronation" 60-100".
Here is everything working as supposed after adding three cards:
After inserted three cards
But when I delete the two first cards "Open hand" and "Close hand", the "Pronation" card left is showing the data in progress bar that actually corresponds to the "Open hand" data range. So why isn't the referencing correct?
I've tried calling both onDataSetChange(); and notifyItemRemoved(pos);
After deleted two cards
My adapter code:
public class ParameterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
List<Movement> movements;
public ParameterAdapter() {
this.movements=movementList;
}
class AddCard extends RecyclerView.ViewHolder {
public AddCard(View inflate) {
super(inflate);
}
}
class Card extends RecyclerView.ViewHolder {
TextView movement;
TextView channel;
TextView remove;
ProgressBar strength;
TextView graph;
LinearLayout expandedView;
LineChart lineChart;
Movement mov;
public Card(View itemView, Movement m) {
super(itemView);
movement = (TextView) itemView.findViewById(R.id.movementText);
channel = (TextView) itemView.findViewById(R.id.channelNameText);
remove = (TextView) itemView.findViewById(R.id.removeCard);
strength = (ProgressBar) itemView.findViewById(R.id.progressBarStrength);
graph = (TextView) itemView.findViewById(R.id.graphTextButton);
expandedView = (LinearLayout) itemView.findViewById(R.id.detailsNr2);
lineChart = (LineChart) itemView.findViewById(R.id.linechartCard);
mov=m;
channel.setText(m.getChannel());
//Setup real time graph
setupLineChart(mov, lineChart, mov.getId());
// Start adding random data to the graph
startAddingRandomData();
}
public void startAddingRandomData() {
new Thread(
new Runnable() {
public void run() {
while (true) {
int generatedData;
// Depending on what type of movement, add slightly different generated random data
// (to be sure that the views are correctly updated when removed for instance)
if(mov.getSettingsType().equals(SettingsDbHelper.MOVEMENTS_STRING[0])){
// Add random generated data between the span 0-20
generatedData=generateRandomData(0,20);
} else if (mov.getSettingsType().equals(SettingsDbHelper.MOVEMENTS_STRING[1])) {
// Add random generated data between the span 20-60
generatedData=generateRandomData(20, 60);
} else {
// Add random generated data between the span 60-100
generatedData=generateRandomData(60, 100);
}
//Set the progress bar
strength.setProgress(generatedData);
// Add the random generated data to the graph
addEntry(mov, lineChart, generatedData);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}
}
public int generateRandomData(int n1, int n2){
Random r = new Random();
return r.nextInt(n2 - n1) + n1;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View vSetting=null;
View vCard=null;
// If its the first position, then inflate the "add movement" card.
if(viewType==0) {
vSetting = LayoutInflater.from(parent.getContext()).inflate(R.layout.fragment_parameter_cardview, parent, false);
return new AddCard(vSetting);
}else if(viewType>0) {
// Otherwise, if it's not the first position, then inflate a "movement card" and create the corresponding movement item
vCard = LayoutInflater.from(parent.getContext()).inflate(R.layout.cardview_movement, parent, false);
Movement m = movementList.get(viewType - 1);
return new Card(vCard, m);
}else {
// For some reason, this occurs
return null;
}
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder,final int position) {
// "Add movement" card
if(position==0){
((AddCard)holder).itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(getActivity(), AddMovement.class);
startActivityForResult(intent, ADD_MOVEMENT);
}
});
// "Movement" item card
} else if(position>0) {
final boolean isExpanded = position == mExpandedPosition;
((Card) holder).expandedView.setVisibility(isExpanded ? View.VISIBLE : View.GONE);
((Card) holder).graph.setActivated(isExpanded);
((Card) holder).graph.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mExpandedPosition = isExpanded ? -1 : position;
notifyDataSetChanged();
}
});
// Get the type of movement
Movement m = movementList.get(position - 1);
// This lets the real-time graph view be created before actual adding any data.
m.setChartCreated(1);
//Update the textviews inside the card
((Card)holder).movement.setText(m.getMovement());
((Card)holder).channel.setText(m.getChannel());
((Card)holder).remove.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Movement m = movementList.get(position - 1);
//Remove the item from the list
remove(m);
// Get ID and settings type in order to update the entry
String id=m.getId();
String settingsType = m.getSettingsType();
//Update the entry
settingsdb.updateSetting(settingsType,id,"false");
}
});
}
}
// Remove item from the list
public void remove(Movement data) {
int position = movementList.indexOf(data);
movementList.remove(data);
notifyDataSetChanged();
// (+ 1 one to compensate for the first card)
notifyItemRemoved(position + 1);
}
#Override
public void onAttachedToRecyclerView(RecyclerView recyclerView) {
super.onAttachedToRecyclerView(recyclerView);
}
#Override
public int getItemViewType(int position) {
//Return the position
return position;
}
#Override
public int getItemCount() {
// (+1 to compensate for the first card)
return movementList.size()+1;
}
}
I'm struggling a little bit with RecyclerView.Adapter. The problem is that RecyclerView sometimes mixes up views, but not with the underlying data but with what is displayed. And it happens only on something around 5% of items on the screen. Sometimes TextViews got their text completely from outter space, for example it suppose to display '1' but it displays '50' but that number doesn't exist anywhere in the application because text is autoincremented while I'm creating items and I've got like 2 of them, also when I'm checking what items are in the database on which position everything is fine, what's more bizzare, when I debugged onBindViewHolder, the holder got all the right data, and set it to the views, but the screen got something different, so it seems like the errors are generated somewhere after onBindViewHolder. Just to be clear, I'm implementing all the methods getItemId, hasStableIds, getItem properly and they indeed return correct items for given positions in onBindViewHolder.
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
Item item = mProvider.getItem(position);
int stickerSize = (int) (screenWidth * Const.STICKER_GRID_PROP);
Picasso.with(mContext)
.load(Const.backgroundNoteIDs[item.backgroundId])
.into(holder.background);
Picasso.with(mContext)
.load(Const.stickerIDs[item.stickerId])
.resize(stickerSize, stickerSize)
.centerInside()
.into(holder.sticker);
int paddingHorizontal = (int) (screenWidth * Const.GRID_TEXT_PADDING_HORIZONTAL_PROP);
int paddingVertical = (int) (screenWidth * Const.GRID_TEXT_PADDING_VERTICAL_PROP);
switch(item.type) {
case Const.NOTE:
holder.noteText.setText(item.note.text);
holder.checklistItemsLayout.removeAllViews();
holder.checklistLines.setImageBitmap(null);
holder.noteText.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.noteText.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
break;
case Const.CHECKLIST:
holder.noteText.setText(null);
holder.checklistItemsLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
Iterator<ChecklistItem> it = item.checklist.checklistItems.iterator();
int i = 0;
while(it.hasNext() && i < 4)
{
ChecklistItem checklistItem = it.next();
final TextView textView = new TextView(mContext);
textView.setEllipsize(TextUtils.TruncateAt.END);
textView.setText(checklistItem.text);
int drawable;
if(checklistItem.checkStatement) {
drawable = R.drawable.checked;
} else {
drawable = R.drawable.unchecked;
}
Picasso.with(mContext)
.load(drawable)
.resize((int) (stickerSize * 0.25f), (int) (stickerSize * 0.25f))
.centerInside()
.into(new Target() {
#Override
public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
Drawable d = new BitmapDrawable(mResources, bitmap);
textView.setCompoundDrawablesWithIntrinsicBounds(d, null, null, null);
}
#Override
public void onBitmapFailed(Drawable errorDrawable) {
}
#Override
public void onPrepareLoad(Drawable placeHolderDrawable) {
}
});
Picasso.with(mContext)
.load(R.drawable.karteczka_linie)
.resize( (int) (screenWidth * Const.MENU_BIG_BUTTON_PROP), (int) (screenWidth * Const.MENU_BIG_BUTTON_PROP))
.centerInside()
.into(holder.checklistLines);
textView.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.checklistItemsLayout.addView(textView);
i++;
}
break;
default:
throw new IllegalArgumentException("onBindViewHolder got item without any type");
}
}
public Item getItem(int index) {
return mItems.get(index);
}
public static class ItemViewHolder extends RecyclerView.ViewHolder implements
ItemTouchHelperViewHolder {
public TextView noteText;
public ImageView background;
public ImageView checklistLines;
public ImageView sticker;
public LinearLayout checklistItemsLayout;
public ItemViewHolder(View itemView) {
super(itemView);
noteText = (TextView) itemView.findViewById(R.id.note_text);
background = (ImageView) itemView.findViewById(R.id.note_background);
checklistLines = (ImageView) itemView.findViewById(R.id.checklist_lines);
sticker = (ImageView) itemView.findViewById(R.id.note_sticker);
checklistItemsLayout = (LinearLayout) itemView.findViewById(R.id.checklist_items);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Log.d(TAG, "onClick " + getItemId());
}
});
}
#Override
public void onItemSelected() {
itemView.setBackgroundColor(Color.LTGRAY);
}
#Override
public void onItemClear() {
itemView.setBackgroundColor(0);
}
}
Okey, so here's the solution, I forgot to remove all views from the checklistItemsLayout in the ViewHolder, which inflates one of two types of items I display, so everytime specific ViewHolder was reused, TextViews were added properly but they were stacked on old ones so they didn't had space to show up, but this happened only when ViewHolder was reused on the same type of the item.
You need to remember to reload every element from the ViewHolder in onBindViewHolder, otherwise You might have some leftovers from other items previously used in this ViewHolder like I did
Here's the fixed code:
#Override
public void onBindViewHolder(ItemViewHolder holder, int position) {
...
switch(item.type) {
case Const.NOTE:
holder.noteText.setText(item.note.text);
holder.checklistItemsLayout.removeAllViews();
holder.checklistLines.setImageBitmap(null);
holder.noteText.setTextSize(screenWidth * Const.GRID_TEXT_SIZE_PROP);
holder.noteText.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
break;
case Const.CHECKLIST:
//This line was missing
holder.checklistItemsLayout.removeAllViews();
holder.noteText.setText(null);
holder.checklistItemsLayout.setPadding(paddingHorizontal, paddingVertical, paddingHorizontal, paddingVertical);
Iterator<ChecklistItem> it = item.checklist.checklistItems.iterator();
int i = 0;
while(it.hasNext() && i < 4)
{
...
}
}
}
I am trying to load a list in RecyclerView and show the first row of the list as selected. I have achieved it using the following code:
#Override
public void onBindViewHolder(NavigationDrawerAdapter.ViewHolder holder, final int position) {
if (!mNavClassrooms.get(position).equals("")) {
holder.mTextViewClassroom.setText(mNavClassrooms.get(position)); // Setting the Text with the array of our Titles
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, false));
/*
The following code was written to make the first item in the Classroom list as selected.
It leads to the item always being selected and hence has been commented out.
*/
if(position == 0 && intOldSelectedItem == -1){
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, true));
intOldSelectedItem = 0;
mSelectedView = holder.mRelLayClassroom.getChildAt(position);
mSelectedItems.put(position, true);
}
else{
holder.mRelLayClassroom.setSelected(mSelectedItems.get(position, false));
}
} else {
holder.mTextViewClassroom.setText("No classes found");
holder.mTextViewClassroom.setPadding(40, 0, 0, 0);
}
holder.mRelLayClassroom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mSharedPreferences = mContext.getSharedPreferences(Constants.AAPREFERENCES, Context.MODE_PRIVATE);
String strClassroomValue = mNavClassrooms.get(position);
int strClassroomName = mNavClassroomNames.get(position);
SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putString(Constants.CLASSROOM_VALUE, strClassroomValue);
editor.putInt(Constants.CLASSROOM_NAME, strClassroomName);
editor.commit();
/*
We are storing the position of the selected row in the SparseBooleanArray.
We delete it in case another row has been selected.
*/
if (mSelectedItems.get(position, false)) {
/*
Do nothing
*/
} else {
mSelectedItems.put(position, true);
/*
Making sure that the delete code is called only if some view is selected
*/
if (mSelectedView != null) {
mSelectedView.setSelected(false);
mSelectedItems.delete(intOldSelectedItem);
view.setSelected(false);
}
mSelectedView = view;
intOldSelectedItem = position;
view.setSelected(true);
}
}
However, now the first row stays selected always. I am unable to deselect it. I cannot seem to get this working.
I referred to the following answer to achieve most of this functionlaity.
https://stackoverflow.com/a/29984220/2186220
Any help will be appreciated.
I'm not answering your question by posting a fixed version of your onBindViewHolder method since it's kinda hard to understand and we don't know how the rest of your adapter looks like. So following a RecyclerView Adapter which does what you want: Selecting the first row by default and deselecting it once a other row is selected.
public class Adapter extends RecyclerView.Adapter<Adapter.ViewHolder> {
// ... other fields
// default selection position is the first one
private int selectedPosition = 0;
// ... constructor etc.
#Override
public void onBindViewHolder(final ViewHolder holder, int position) {
if(position == selectedPosition){
holder.itemView.setSelected(true);
} else {
holder.itemView.setSelected(false);
}
// Actual selection / deselection logic
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
int currentPosition = holder.getLayoutPosition();
if(selectedPosition != currentPosition){
// Temporarily save the last selected position
int lastSelectedPosition = selectedPosition;
// Save the new selected position
selectedPosition = currentPosition;
// update the previous selected row
notifyItemChanged(lastSelectedPosition);
// select the clicked row
holder.itemView.setSelected(true);
}
}
});
// other adapter code
}
// other adapter stuff like onCreateViewHolder, getItemCount, ViewHolder etc.
}
Note: I guess there's no need to use a SparseBooleanArray so simply remove it and replace it with the int field used in the example above.
Initialize your
int intOldSelectedItem=0 and keep one boolean isVisible= false;
And do it as below:
if (holder.getPosition() == intOldSelectedItem) {
if (isVisible) {
//background for selected item
} else {
//background for unselected item
}
} else {
//background for unselected item
}
holder.mRelLayClassroom.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (intOldSelectedItem== holder.getPosition()) {
isVisible = !isVisible;
} else {
if (intOldSelectedItem!= 0) {
isVisible = false;
notifyItemChanged(intOldSelectedItem);
}
isVisible = true;
}
intOldSelectedItem= holder.getPosition();
notifyItemChanged(intOldSelectedItem);
}
});
I hope it might help you.
Add background selector to your ViewHolder layout.
Create your selector handler something like this:
public class SingleSelector {
private View oldVIew;
public void setSelection(View newView) {
if (oldVIew == null) {
newView.setSelected(true);
oldVIew = newView;
} else {
oldVIew.setSelected(false);
newView.setSelected(true);
oldVIew = newView;
}
}
}
Set default selection when you need it:
#Override
public void onBindViewHolder(SimpleViewHolder holder, int position) {
if (position == 0) {
singleSelector.setSelection(holder.itemView);
}
}
In your ViewHolder add listener to itemView and pass it to the handler:
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
singleSelector.setSelection(itemView);
}
});
I am trying to implement my own recyclerview Animation - I would like to achieve this without using any external libraries. Here is what the theoretical animation should look like.
The user clicks an item on the List and an animation occurs which opens up another View.
On a high level with minimal code, possibly just pseudo code what would the process be in order to create some animation like that?
Also I would like to note that the animation should be able to be done in reverse as well if the user clicks the same item or another item
I am not that familiar with the RecyclerView class and would like to learn more about it and any animations associated with it.
Solution:
The way I solved this problem was to implement a listener View.OnClickListener to the ViewHolder class which extends RecyclerView.ViewHolder. So we get the following code:
public static class ExampleViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener {
private int originalHeight = 0;
private boolean isViewExpanded = false;
private YourCustomView yourCustomView
// ..... CODE ..... //
}
The variables originalHeight and isViewExpanded are used in the animation process. In the constructor I initialize the view to the View.OnClickListener like so:
public ExampleViewHolder(View v) {
super(v);
v.setOnClickListener(this);
// Initialize other views, like TextView, ImageView, etc. here
// If isViewExpanded == false then set the visibility
// of whatever will be in the expanded to GONE
if (isViewExpanded == false) {
// Set Views to View.GONE and .setEnabled(false)
yourCustomView.setVisibility(View.GONE);
yourCustomView.setEnabled(false);
}
}
Now that the constructor has been taken care of we want to configure what happens when the user clicks on an individual RecyclerView item. The classes that will be useful here would be the ValueAnimator and the Animation objects. We override the onClick method like so to accomplish this:
#Override
public void onClick(final View view) {
// If the originalHeight is 0 then find the height of the View being used
// This would be the height of the cardview
if (originalHeight == 0) {
originalHeight = view.getHeight();
}
// Declare a ValueAnimator object
ValueAnimator valueAnimator;
if (!mIsViewExpanded) {
yourCustomView.setVisibility(View.VISIBLE);
yourCustomView.setEnabled(true);
mIsViewExpanded = true;
valueAnimator = ValueAnimator.ofInt(originalHeight, originalHeight + (int) (originalHeight * 2.0)); // These values in this method can be changed to expand however much you like
} else {
mIsViewExpanded = false;
valueAnimator = ValueAnimator.ofInt(originalHeight + (int) (originalHeight * 2.0), originalHeight);
Animation a = new AlphaAnimation(1.00f, 0.00f); // Fade out
a.setDuration(200);
// Set a listener to the animation and configure onAnimationEnd
a.setAnimationListener(new Animation.AnimationListener() {
#Override
public void onAnimationStart(Animation animation) {
}
#Override
public void onAnimationEnd(Animation animation) {
yourCustomView.setVisibility(View.INVISIBLE);
yourCustomView.setEnabled(false);
}
#Override
public void onAnimationRepeat(Animation animation) {
}
});
// Set the animation on the custom view
yourCustomView.startAnimation(a);
}
valueAnimator.setDuration(200);
valueAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
public void onAnimationUpdate(ValueAnimator animation) {
Integer value = (Integer) animation.getAnimatedValue();
view.getLayoutParams().height = value.intValue();
view.requestLayout();
}
});
valueAnimator.start();
}
Now when you touch an individual cardview on the RecyclerView (assuming you have a CardView setup then it should expand out. Make sure to declare your customView properly in your xml file (example if you want the CardView to expand down when you touch it then properly assign the customView underneath the other views and set the visibility to gone when you declare it and then when the animation starts like so in the code above then set the visibility to Visible and enable the view.
Hope this can help someone out.
An easier alternative for #AndyRoid's answer is to use android:animateLayoutChanges="true" property. This way you don't need to write any animation code; however, this is not a way to go if you need to have a control over animation.
You still need to create an OnClickListener:
class CardTapListener implements View.OnClickListener {
#Override
public void onClick(View v) {
View someView = v.findViewById(R.id.view_to_expand);
if (someView.getVisibility() == View.GONE) {
someView.setVisibility(View.VISIBLE);
}
else if (someView.getVisibility() == View.VISIBLE){
someView.setVisibility(View.GONE);
}
}
}
Attach it to every ViewHolder:
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.view_holder_layout, viewGroup, false);
v.setOnClickListener(new CardTapListener());
return new ItemViewHolder(v);
}
Don't forget to collapse views when binding a new item:
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int i) {
...
// Collapse (probably opened by user previously) view
ItemViewHolder itemHolder = (ItemViewHolder) viewHolder;
itemHolder.description.setVisibility(View.GONE);
...
}
view_holder_layout.xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
...
<AnyViewHere
android:visibility="gone"
android:id="#+id/view_to_expand" />
</LinearLayout>