I've followed Nick Butcher's Material Improvements I/O 2016 talk, and at about 6:00, he starts talking about animating list items. I've implemented the feature exactly as he was doing it, and the bound changes animate correctly, but color changes don't animate, despite him explicitly saying they would:
This is what the code looks like:
This is the relevant part of the RecyclerView.Adapter class:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item: Pair<String, String> = items[position]
holder.title.text = item.first
holder.subtitle.text = item.second
val isExpanded = position == expandedPosition
holder.subtitle.visibility = if (isExpanded) View.VISIBLE else View.GONE
holder.itemView.isActivated = isExpanded
holder.itemView.setOnClickListener {
expandedPosition = if (isExpanded) -1 else position
TransitionManager.beginDelayedTransition(recyclerView)
notifyDataSetChanged()
}
}
This is the layout I'm using for the items. ConstraintLayout is kind of overkill for the current layout setup, but I reduced the layout to create a minimal example.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".MainActivity"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#drawable/item_background"
android:stateListAnimator="#animator/item_elevation"
android:padding="8dp">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="24sp"
tools:text="Title 1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<TextView
android:id="#+id/subtitle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="Subtitle 1"
app:layout_constraintTop_toBottomOf="#id/title"
app:layout_constraintStart_toStartOf="parent"
/>
</android.support.constraint.ConstraintLayout>
And this is the background I'm using for the item layout:
<selector xmlns:android="http://schemas.android.com/apk/res/android"
android:enterFadeDuration="#android:integer/config_mediumAnimTime"
android:exitFadeDuration="#android:integer/config_mediumAnimTime">
<item android:state_activated="true"
android:drawable="#color/colorAccent" />
<item android:drawable="#color/colorPrimaryDark" />
</selector>
Make sure that you have stable ids for your dataset and call Recycler.Adapter#setHasStableIds(true). See
setHasStableIds().
You will also need to override getItemId() in the adapter to return a stable id. Also see notifyDataSetChanged for a short discussion of stable ids.
RecyclerView will attempt to synthesize visible structural change events for adapters that report that they have stable IDs when this method is used. This can help for the purposes of animation and visual object persistence but individual item views will still need to be rebound and relaid out.
Here is an demonstration with stable ids set to false:
and with stable ids set to true. I have make the transition time very long on the color change so it would be apparent.
RecyclerViewAdapter.java
This is the adapter used in the demo. TransitionManager.beginDelayedTransition(mRecyclerView) has been removed to let RecyclerView better handle the animation.
class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
RecyclerViewAdapter() {
}
#NonNull
#Override
public RecyclerView.ViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
View view;
view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
return new ItemViewHolder(view);
}
private int mExpandedPosition = RecyclerView.NO_POSITION;
#Override
public void onBindViewHolder(#NonNull RecyclerView.ViewHolder holder, int position) {
final ItemViewHolder vh = (ItemViewHolder) holder;
final TextView subTitle = vh.mSubTitle;
vh.mTitle.setText(titleForPosition(position));
subTitle.setText(subTitleForPosition(position));
final boolean isExpanded = position == mExpandedPosition;
subTitle.setVisibility((isExpanded) ? View.VISIBLE : View.GONE);
holder.itemView.setActivated(isExpanded);
holder.itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mExpandedPosition = isExpanded ? RecyclerView.NO_POSITION : vh.getAdapterPosition();
notifyDataSetChanged();
}
});
}
private String titleForPosition(int position) {
return "Title " + position;
}
private String subTitleForPosition(int position) {
return "Subtitle " + position;
}
#Override
public int getItemCount() {
return 5;
}
#Override
public long getItemId(int position) {
return titleForPosition(position).hashCode();
}
static class ItemViewHolder extends RecyclerView.ViewHolder {
private final TextView mTitle;
private final TextView mSubTitle;
ItemViewHolder(View itemView) {
super(itemView);
mTitle = itemView.findViewById(R.id.title);
mSubTitle = itemView.findViewById(R.id.subtitle);
}
}
#SuppressWarnings("unused")
private final static String TAG = "RecyclerViewAdapter";
}
Related
I am trying to create a curved Wearable Recycler view similar to the one given in below link: https://developer.android.com/training/wearables/ui/lists.html
But I don't see curved layout and all the text comes in a single line .
Can anyone help me how to create a curved Wearable Recycler view . The below is the code fragment. Please let me know if something went wrong in the below code:
My Layout is below:
<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.WearableRecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/menu_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
>
</android.support.wearable.view.WearableRecyclerView>
In my fragment I am using the below code for curved layout:
InformationAdapter informationAdapter = new InformationAdapter(dataSet, images,MenuActivity.this );<br>
wearableRecyclerView.setAdapter(informationAdapter); <br>
wearableRecyclerView.setCenterEdgeItems(true);<br>
wearableRecyclerView.setLayoutManager(new CurvedChildLayoutManager(getActivity()));<br>
wearableRecyclerView.setCircularScrollingGestureEnabled(true);
wearableRecyclerView.setBezelWidth(0.5f);
wearableRecyclerView.setScrollDegreesPerScreen(90);
My Adapter is as below:
public class InformationAdapter extends
WearableRecyclerView.Adapter<InformationAdapter.ViewHolder> {
private static final String TAG = "CustomRecyclerAdapter";
private String[] mDataSet;
private int[] mImages;
private ItemClickListener itemClickListener;
// Custom Controller used to instruct main activity to update {#link Notification} and/or
// UI for item selected.
public static class ViewHolder extends WearableRecyclerView.ViewHolder {
private final TextView mTextView;
private final ImageView menuImageIcon;
public ViewHolder(View view) {
super(view);
mTextView = (TextView) view.findViewById(R.id.textView);
menuImageIcon = (ImageView)view.findViewById(R.id.menu_image);
}
#Override
public String toString() { return (String) mTextView.getText(); }
}
public InformationAdapter(String[] dataSet,int[] mImages,ItemClickListener itemClickListener) {
mDataSet = dataSet;
this.mImages = mImages;
this.itemClickListener = itemClickListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.recycler_row_item, viewGroup, false);
return new ViewHolder(view);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
Log.d(TAG, "Element " + position + " set.");
viewHolder.mTextView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// mController.itemSelected(mDataSet[position]);
itemClickListener.onItemSelected(mDataSet[position]);
}
});
// Replaces content of view with correct element from data set
viewHolder.mTextView.setText(mDataSet[position]);
viewHolder.menuImageIcon.setImageResource(mImages[position]);
}
// Return the size of your dataset (invoked by the layout manager)
#Override
public int getItemCount() {
return mDataSet.length;
}
}
Row Item layout for Adapter:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="#dimen/recycler_row_padding"
android:orientation="horizontal">
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:src="#drawable/ic_n_white_48dp"
android:id="#+id/menu_image"/>
<TextView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:textSize="#dimen/recycler_row_text_size"
android:id="#+id/textView"
/>
</LinearLayout>
I faced with the same problem. Changing
android:layout_height="wrap_content"
to
android:layout_height="match_parent"
in WearableRecyclerView worked for me. Hope it helps.
I have a RecyclerView, when RecyclerView item clicked, want to open a popup window which contains another RecyclerView. It is almost done, but in popup window, cardviews don't appear. I can't figure out why, can any one help?
1- My Main RecyclerView Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
private ArrayList<Mission> mDataset;
private Context mContext;
// Provide a suitable constructor (depends on the kind of dataset)
public MyAdapter(ArrayList<Mission> myDataset, Context context) {
mDataset = myDataset;
this.mContext = context;
}
// Create new views (invoked by the layout manager)
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// create a new view
View v = LayoutInflater.from(mContext)
.inflate(R.layout.mission_card_item, parent, false);
// set the view's size, margins, paddings and layout parameters
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.mTextView.setText(mDataset.get(position).getName());
holder.mPuanView.setText(mDataset.get(position).getPoint());
holder.mRankView.setText(mDataset.get(position).getRank());
holder.btnAdd.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Toast.makeText(mContext,"Buton Clicked", Toast.LENGTH_SHORT).show();
}
});
}
#Override
public int getItemCount() {
return mDataset.size();
}
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder
public class MyViewHolder extends RecyclerView.ViewHolder {
public CardView mCardView;
public TextView mTextView;
public TextView mPuanView;
public TextView mRankView;
public Button btnAdd;
public MyViewHolder(final View itemView) {
super(itemView);
mCardView = (CardView) itemView.findViewById(R.id.card_view);
mTextView = (TextView) itemView.findViewById(R.id.tv_text);
mRankView = (TextView) itemView.findViewById(R.id.tv_rank);
mPuanView = (TextView) itemView.findViewById(R.id.tv_puan);
btnAdd = (Button) itemView.findViewById(R.id.button_add);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showPopup();
Toast.makeText(itemView.getContext(),"Element " + getAdapterPosition() + " clicked", Toast.LENGTH_SHORT).show();
Log.d("hello", "Element " + getAdapterPosition() + " clicked.");
}
});
}
}
public void showPopup(){
final View popupView = LayoutInflater.from(mContext).inflate(R.layout.recycler_popup_window, null);
final PopupWindow popupWindow = new PopupWindow(popupView, WindowManager.LayoutParams.WRAP_CONTENT, WindowManager.LayoutParams.WRAP_CONTENT);
Button btn = (Button) popupView.findViewById(R.id.button);
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
popupWindow.dismiss();
}
});
RecyclerView recyclerView = (RecyclerView) popupView.findViewById(R.id.rv_recycler_view);
ArrayList<String> data = new ArrayList<>();
data.add("my data");
data.add("my test data");
PopupRecyclerViewAdapter adapter = new PopupRecyclerViewAdapter(mContext,data);
recyclerView.setAdapter(adapter);
popupWindow.showAtLocation(popupView,Gravity.CENTER, 0, 0);
}
}
2- My second RecyclerView adapter, its for popup window
public class PopupRecyclerViewAdapter extends RecyclerView.Adapter<PopupRecyclerViewAdapter.MyViewHolder>{
private Context mContext;
private ArrayList<String> data;
public PopupRecyclerViewAdapter(Context mContext, ArrayList<String> data) {
this.mContext = mContext;
this.data = data;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(mContext).inflate(R.layout.recycler_popup_card_item, parent,false);
MyViewHolder vh = new MyViewHolder(v);
return vh;
}
#Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.mTextView.setText(data.get(position));
}
#Override
public int getItemCount() {
return data.size();
}
//View Holder
public class MyViewHolder extends RecyclerView.ViewHolder {
public TextView mTextView;
public MyViewHolder(View itemView) {
super(itemView);
mTextView = (TextView) itemView.findViewById(R.id.tv_text2);
}
}
}
3- Layout for Recycler popup window
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content" android:layout_height="wrap_content">
<android.support.v7.widget.RecyclerView
android:id="#+id/rv_recycler_view2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="#+id/button"
android:background="#ff4545">
</android.support.v7.widget.RecyclerView>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Close"
android:id="#+id/button"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
</RelativeLayout>
4- CardView Layout for popup RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:id="#+id/card_view"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_margin="10dp"
android:layout_height="wrap_content"
card_view:cardCornerRadius="4dp"
card_view:elevation="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:gravity="center">
<ImageView
android:layout_width="match_parent"
android:layout_height="175dp"
android:id="#+id/imageView2"
android:src="#mipmap/testimage"
android:layout_marginBottom="10dp"
android:scaleType="centerCrop"/>
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="#+id/tv_text2"
android:text="Blah blah blah..."
android:gravity="center"
android:layout_marginBottom="10dp"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</RelativeLayout>
Add the below lines in your recyclerview popup:
RecyclerView.LayoutManager mLayoutManager = new LinearLayoutManager(getApplicationContext());
recyler_view.setLayoutManager(mLayoutManager);
You may, like me, find this becomes rather a lot of boilerplate code just for a simple Popup Window with a list of clickable items, and so I made a custom Drop Down class which is easily reusable. You just need the PopupWindow custom class, and the RecyclerView adapter, as well as an item data class (in Kotlin), and this is then easily reusable.
The DropDown class:
class DropDown(context: Context, items: List<DropDownItem>, val listener: DropDownClickListener) : PopupWindow(context) {
private val binding = DropdownLayoutBinding.inflate(LayoutInflater.from(context))
init {
contentView = binding.root
setBackgroundDrawable(null)
elevation = 8f
isOutsideTouchable = true
isFocusable = true
binding.recyclerView.adapter = DropDownItemAdapter(items) { item -> onItemClicked(item) }
setOnDismissListener { listener.onMenuDismissed() }
}
private fun onItemClicked(item: DropDownItem) {
listener.onMenuItemClicked(item)
dismiss()
}
fun show(anchor: View) {
showAsDropDown(anchor, 0, 20)
}
interface DropDownClickListener {
fun onMenuItemClicked(item: DropDownItem)
fun onMenuDismissed()
}
}
data class DropDownItem(
val text: String,
val icon: Int
)
With accompanying layout code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#drawable/rounded_corners"
android:id="#+id/recycler_view"
xmlns:tools="http://schemas.android.com/tools"
tools:listitem="#layout/item_drop_down"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Explanation: This reusable new DropDown class uses ViewBinding of a simple layout that is simply a RecyclerView (with whatever Item layout you want, again mine is a reusable one called item_drop_down, a TextView with a Drawable.)
The constructor just needs three things, the context, the list of drop down items (I made a DropDownItem data class, which for simplicity I put in the same file), and then the listener - an interface also defined in this class).
In the init for this class, we set the content view, setBackgroundDrawable to null to get rid of an ugly border, give it some elevation, then make it dismissable by touching outside or pressing the back button. We then set the recyclerview adapter here from the list of items in the constructor. Finally, we set the on dismiss listener and defer to our interface.
Now the adapter code:
class DropDownItemAdapter(val items: List<DropDownItem>, val clickListener: (DropDownItem) -> Unit) : RecyclerView.Adapter<DropDownItemAdapter.ViewHolder>() {
override fun getItemCount() = items.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(ItemDropDownBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(items[position])
}
inner class ViewHolder(val binding: ItemDropDownBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: DropDownItem) {
binding.textView.text = item.text
binding.textView.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(binding.root.context, item.icon), null, null, null)
itemView.setOnClickListener { clickListener(item) }
}
}
}
This is just a straight forward RecyclerView adapter, and rather than another interface, the only thing this needs to notify is when an item is clicked, so simply accept a function parameter in the constructor for handling the clicks.
Altogether, it looks like this in the calling code:
DropDown(view.context, dropDownMenu, this).show(view)
Where dropDownMenu is a list of DropDownItem.
You can then handle the interface functions as desired:
override fun onMenuItemClicked(item: DropDownItem) {
when (item.text) {
"Delete" -> // respond as desired
"Edit" -> // simply inspect the text, or alter your DropDownItem to have an ID
}
}
override fun onMenuDismissed() {
// it may be useful to know when the popup is dismissed
// in my case, I select the item which has been long pressed, and want to know when to un-select it
}
With a nice rounded corners background on my DropDown layout, and being able to know when popup showing and when dismissed to highlight my chosen item, I get this nice effect:
first of all, sorry if this a stupid question. I'm not being lazy.
So, the problem is, im trying to implement CardView/RecyclerView in an Android app. I made it, but the problem is that the cards are spaced one from another, and i don't know how to fix it. I explored the code but everything seems to be fine.
The code :
RecyclerView
<android.support.v7.widget.RecyclerView
android:id="#+id/collapsing_recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:padding="#dimen/activity_horizontal_margin">
</android.support.v7.widget.RecyclerView>
CardView item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.CardView
android:id="#+id/card_view"
card_view:cardBackgroundColor="#color/colorAccent"
android:layout_gravity="center"
android:layout_width="fill_parent"
android:layout_height="100dp"
android:layout_margin="5dp"
card_view:cardCornerRadius="2dp">
<TextView
android:gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="New Text"
android:id="#+id/cardview.name" />
</android.support.v7.widget.CardView>
</LinearLayout>
Adapter :
public class MyRecyclerViewAdapter extends RecyclerView
.Adapter<MyRecyclerViewAdapter
.DataObjectHolder> {
private static String LOG_TAG = "MyRecyclerViewAdapter";
private ArrayList<CardViewItem> mDataset;
private static MyClickListener myClickListener;
public static class DataObjectHolder extends RecyclerView.ViewHolder
implements View
.OnClickListener {
TextView label;
public DataObjectHolder(View itemView) {
super(itemView);
label = (TextView) itemView.findViewById(R.id.cardview_name);
Log.i(LOG_TAG, "Adding Listener");
itemView.setOnClickListener(this);
}
#Override
public void onClick(View v) {
myClickListener.onItemClick(getAdapterPosition(), v);
}
}
public void setOnItemClickListener(MyClickListener myClickListener) {
this.myClickListener = myClickListener;
}
public MyRecyclerViewAdapter(ArrayList<CardViewItem> myDataset) {
mDataset = myDataset;
}
#Override
public DataObjectHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.example_card_view, parent, false);
DataObjectHolder dataObjectHolder = new DataObjectHolder(view);
return dataObjectHolder;
}
#Override
public void onBindViewHolder(DataObjectHolder holder, int position) {
holder.label.setText(mDataset.get(position).getName());;
}
public void addItem(CardViewItem dataObj, int index) {
mDataset.add(index, dataObj);
notifyItemInserted(index);
}
public void deleteItem(int index) {
mDataset.remove(index);
notifyItemRemoved(index);
}
#Override
public int getItemCount() {
return mDataset.size();
}
public interface MyClickListener {
public void onItemClick(int position, View v);
}
}
Hope you guys can help me. Thanks!
EDIT: So, i found the answer. The coulprit was the android:layout_height="match_parent"
in my cardview item. I change it for "wrap_content" and fixed the problem. Thanks for the help guys! :)
Have you tried ItemDecoration with your RecyclerView ? That's usefull for customize your divider in a RecyclerView
You can look that :
My custom ItemDecoration use getItemOffsets() like this :
public class MyItemDecoration extends RecyclerView.ItemDecoration {
private final int decorationHeight;
private Context context;
public MyItemDecoration(Context context) {
this.context = context;
decorationHeight = context.getResources().getDimensionPixelSize(R.dimen.decoration_height);
}
#Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
if (parent != null && view != null) {
int itemPosition = parent.getChildAdapterPosition(view);
int totalCount = parent.getAdapter().getItemCount();
if (itemPosition >= 0 && itemPosition < totalCount - 1) {
outRect.bottom = decorationHeight;
}
}
}
}
Then you can set your R.dimen.decoration_height to the value what you want.
And in my activity, when I instantiate my RecyclerListView I need to do this :
myList.addItemDecoration(MyItemDecoration(this))
myList.setLayoutManager(LinearLayoutManager(this))
myList.setAdapter(adapter)
I hope this will help you
Remove this line from your CardView item .xml
android:layout_margin="5dp"
You probably wanted it to be padding instead of margin
In order to create spacings in between items, we could use RecyclerView's ItemDecorator's:
addItemDecoration(object : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.getChildAdapterPosition(view) > 0) {
outRect.top = 8.dp // Change this value with anything you want. Remember that you need to convert integers to pixels if you are working with dps :)
}
}
})
A few things to have in consideration given the code I pasted:
You don't really need to call super.getItemOffsets but I chose to, because I want to extend the behavior defined by the base class. If the library got an update doing more logic behind the scenes, we would miss it.
As an alternative to adding top spacing to the Rect, you could also add bottom spacing, but the logic related to getting the last item of the adapter is more complex, so this might be slightly better.
I used an extension property to convert a simple integer to dps: 8.dp. Something like this might work:
val Int.dp: Int
get() = (this * Resources.getSystem().displayMetrics.density + 0.5f).toInt()
// Extension function works too, but invoking it would become something like 8.dp()
CardView by default adds padding. Try using CardView attribute card_view:contentPadding but set the negative values for the attribute like this
card_view:contentPadding="-3"
I have a typical recycler adapter set up. Some of the items in the adapter have images, sometimes just one image, sometimes 20. I want to create an image collage, like the one below, depending on the number of images in each item:
I have 10 different layouts. The first is to be used when the item only has 1 image (1 ImageView in the layout), another used when the item has 2 images (2 ImageViews in the layout), another used when the item has 3 images, etc. If the item has over 10 images, it uses the layout with 10 ImageViews and hides the rest of the images. The layouts are named:
one_image.xml
two_images.xml
three_images.xml
... etc ...
Here is my Recycler adapter:
public class RecyclerAdapter extends RecyclerView.Adapter<RecyclerAdapter.ViewHolder> {
private static Context context;
private List<Message> mDataset;
public RecyclerAdapter(Context context, List<Message> myDataset) {
this.context = context;
this.mDataset = myDataset;
}
public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnCreateContextMenuListener, View.OnClickListener {
public TextView title;
public ViewHolder(View view) {
super(view);
view.setOnCreateContextMenuListener(this);
title = (TextView) view.findViewById(R.id.title);
}
}
#Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false);
ViewHolder vh = new ViewHolder((LinearLayout) view);
return vh;
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
holder.title.setText(item.getTitle());
int numImages = item.getImages().size();
if (numImages > 0) {
// Show image collage
}
}
#Override
public int getItemCount() {
return mDataset.size();
}
}
Here is the main layout, message_layout.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
// Image collage layout
</LinearLayout>
And this is one of the image collage layouts, two_images.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:id="#+id/image_one"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
/>
<ImageView
android:id="#+id/image_two"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:adjustViewBounds="true"
/>
</LinearLayout>
So the question is, how do I load/inflate the correct layout depending on the number of images into the recycler adapter, and populate the layout with the images?
You can use StaggeredGridLayoutManager to achieve above type of layouts. Click here for more information about StaggeredGridLayoutManager
There are a number of libraries for staggered gridview. you can use any of them. I recommend to use this:
AndroidStaggeredGrid
You should create a custom ViewHolder for each one of your layouts.
Imagine you have this ViewHolder: TwoImagesViewHolder
public class TwoImagesViewHolder extends RecyclerView.ViewHolder {
public TextView title;
public ImageView imageOne;
public ImageView imageTwo;
public TwoImagesViewHolder(#NonNull View view) {
super(view);
title = (TextView)view.findViewById(R.id.title);
imageOne = (ImageView)view.findViewById(R.id.image_one);
imageTwo = (ImageView)view.findViewById(R.id.image_two);
}
}
Inside the adapter you can use your holder like this:
#Override
public RecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Message item = mDataset.get(position);
int numImages = item.getImages().size();
if (numImages == 2) {
return new TwoImagesViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.message_layout, parent, false))
}
// Add all the conditions for your custom ViewHolders
return DefaultViewHolder; // Define a Default holder
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Message item = mDataset.get(position);
int numImages = item.getImages().size();
if (numImages == 2) {
// Show image collage
TwoImagesViewHolder holder = (TwoImagesViewHolder)viewHolder;
holder.title.setText(item.getTitle());
holder.image_one.setImageResource(...);
holder.image_two.setImageResource(...);
}
// Add all the conditions for your custom ViewHolders
}
You need to create all your conditions to instance the correct layout holder and remember to have the same conditions for the onCreateViewHolder and onBindViewHolder.
I want to have a gridview similar to this
Every odd numbered row will have two images of big size and even numbered rows will have four smaller images.How can I achieve this?
I have something similar and i solved with the new RecyclerView.
I created a Fragment with an a RecyclerView.
RecyclerView on xml:
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/filter_subtypes" android:layout_width="match_parent" android:layout_height="match_parent" />
On your Fragment/Activity (OnViewCreated in my fragment case).
I find the RecyclerView and set an Adapter- a normal Adapter class inherit from RecyclerView.Adapter< YOUR VIEW HOLDER class >-
And then i create a GridLayoutManager
final GridLayoutManager mng_layout = new GridLayoutManager(this.getActivity(), TOTAL_CELLS_PER_ROW/*In your case 4*/);
Then i override this method to set a dynamic numbers of columns (cells)
mng_layout.setSpanSizeLookup( new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
switch( adapterSubtype.getItemViewType(position) ) {
case FilterSubtypesAdapter.TYPE_LOW:
return TOTAL_CELLS_PER_ROW;
case FilterSubtypesAdapter.TYPE_HIGH:
return 2;
default:
return -1;
}
}
});
myRecyclerView.setLayoutManager(mng_layout);
With this you will get dynamic numbers of cell on your rows.
EXTRA:
Then if you are using the same view/type view on your adapter, you will get the same w & h view. You will need to create 2 xml views for TYPE_HIGH and other view for TYPE_LOW.
So, in your adapter, you need to have 2 kind of data (1 for high images and 1 for low images).
You must override this methods
#Override
public SubtypeViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = null;
if (viewType==TYPE_HIGH) {
view = inflater.inflate(R.layout.item_image_high, parent, false);
} else {
view = inflater.inflate(R.layout.item_image_low, parent, false);
}
return new SubtypeViewHolder(view, viewType);
}
#Override
public int getItemViewType(int position) {
return (list.get(position).getType()==Subtype_type.HIGH) ? TYPE_HIGH : TYPE_LOW;
}
I hope i was clear, any problem tell me.
Instead of considering a single image views i am taking group of three images as a single grid item,
try this inside your grid adapter
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/linear"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/green"
android:orientation="vertical">
<ImageView
android:id="#+id/image"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="#drawable/user"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/image_1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:scaleType="fitXY"
android:src="#drawable/user"
/>
<ImageView
android:id="#+id/image_2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/user"
android:scaleType="fitXY"
android:layout_weight="1"
/>
</LinearLayout>
</LinearLayout>
and your grid view would be like
<GridView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/RelativeLayout1"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:numColumns="2"
>
</GridView>
The only thing you have to take care of is, the sequence of your image. might be this will help you
If you are using RecyclerView for GridView, then there is solution that should work for you:
GridLayoutManager layoutManager = new GridLayoutManager(this, 4);
layoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
#Override
public int getSpanSize(int position) {
int mod = position % 6;
if(position == 0 || position == 1)
return 2;
else if(position < 6)
return 1;
else if(mod == 0 || mod == 1)
return 2;
else
return 1;
}
});
recyclerView.setLayoutManager(layoutManager);
Hope this work for you!
I guess the best way to do is using recycler view..
Also it is preferred over list/grid for performance
May be below links can help a lot - All are related to Two way View by Lucas
https://github.com/lucasr/twoway-view
https://plus.google.com/+LucasRocha/posts/WBaryNqAHiy
http://lucasr.org/2014/07/31/the-new-twowayview/
There is how does it work in my project I have different height of cells and also header
adapter:
public class AdapterRecViewMain
extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private List<BaseMarkerElement> mainCardList;
private final int HEADER_VIEW = 0;
private final int FOOTER_VIEW = 1;
public AdapterRecViewMain() {
}
public void setData(List<BaseMarkerElement> mainCardList) {
this.mainCardList = mainCardList;
}
#Override public int getItemViewType(int position) {
if (position == 0) {
return HEADER_VIEW;
}
return FOOTER_VIEW;
}
#Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int type) {
if (type == FOOTER_VIEW) {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.card_main_activity, viewGroup, false);
return new MainCardViewHolder(v);
} else {
View v = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.header_view_main_activity, viewGroup, false);
return new HeaderViewHolder(v);
}
}
#Override public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int positionItem) {
final int position = viewHolder.getAdapterPosition();
if (viewHolder instanceof HeaderViewHolder) {
StaggeredGridLayoutManager.LayoutParams layoutParams =
(StaggeredGridLayoutManager.LayoutParams) viewHolder.itemView.getLayoutParams();
layoutParams.setFullSpan(true);
BaseMarkerElement item = mainCardList.get(position);
if (item instanceof HeaderView) {
HeaderView header = (HeaderView) mainCardList.get(position);
// need to add implementation
}
} else if (viewHolder instanceof MainCardViewHolder) {
MainCardViewHolder currentView = (MainCardViewHolder) viewHolder;
CardMainActivity currentCard = (CardMainActivity) mainCardList.get(position);
currentView.ivMainCard.setImageResource(currentCard.getIvMainCard());
currentView.tvBrandName.setText(currentCard.getTvBrandName());
currentView.tvPrice.setText(currentCard.getTvPrice());
currentView.tvType.setText(currentCard.getTvType());
}
}
#Override public int getItemCount() {
return mainCardList.size();
}
private class MainCardViewHolder extends RecyclerView.ViewHolder {
ImageView ivMainCard;
TextView tvBrandName;
TextView tvType;
TextView tvPrice;
MainCardViewHolder(View view) {
super(view);
ivMainCard = (ImageView) view.findViewById(R.id.imageViewMainCard);
tvBrandName = (TextView) view.findViewById(R.id.tvBrandName);
tvType = (TextView) view.findViewById(R.id.tvType);
tvPrice = (TextView) view.findViewById(R.id.tvPrice);
}
}
private class HeaderViewHolder extends RecyclerView.ViewHolder {
public HeaderViewHolder(View itemView) {
super(itemView);
}
}
}
In your activity :
private AdapterRecViewMain adapter;
private RecyclerView rvMain;
#Override protected void onResume() {
super.onResume();
Logger.logGeneral("onResume()");
if (adapter == null) {
setUpRecView();
}
}
private void setUpRecView() {
adapter = new AdapterRecViewMain();
adapter.setData(controller.loadData());
rvMain = (RecyclerView) findViewById(R.id.rvMain);
final StaggeredGridLayoutManager layoutManager =
new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
rvMain.setLayoutManager(layoutManager);
rvMain.addOnScrollListener(scrollListener);
rvMain.setAdapter(adapter);
adapter.notifyDataSetChanged();
rvMain.invalidate();
}
You should set in RecyclerView :
recyclerView.setLayoutManager(new GridLayoutManager(this, 4));
Create two different XML files and inflate based on position in your Adapter like below.
if(position%2==0){
//Inflate Even number layout with 4 images
}else{
//Inflate ODD number layout with 2 images
}
Did you try a RecyclerView in a combination with a StaggeredGridLayoutManager?
This combination results in something like this: video.
I think, this is what you are looking for.
Try this ,
https://github.com/etsy/AndroidStaggeredGrid
Staggered-Grid View ,
Extremely simple , easy to use.
Use Expandable list view and provide different view for each row.
http://developer.android.com/reference/android/widget/ExpandableListView.html