Two RecyclerViews desynchronizes during programm scrolling - android

I have two RecyclerViews on my screen. And I need to scroll both at the same time on the same distance programmatically. But if I do it fast - the RecyclerViews desynchronizes.
In this source code you should fast click on button in the right top and will see the result.
This is Activity, where button listener for scrolling lists created:
public class MainActivity extends AppCompatActivity {
private RecyclerView recyclerView1;
private RecyclerView recyclerView2;
private Adapter adapter1;
private Adapter adapter2;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
recyclerView1 = findViewById(R.id.recycler_view1);
recyclerView2 = findViewById(R.id.recycler_view2);
adapter1 = new Adapter();
recyclerView1.setLayoutManager(new LinearLayoutManager(this));
recyclerView1.setAdapter(adapter1);
adapter2 = new Adapter();
recyclerView2.setLayoutManager(new LinearLayoutManager(this));
recyclerView2.setAdapter(adapter2);
findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
recyclerView1.smoothScrollBy(0, 400);
recyclerView2.smoothScrollBy(0, 400);
}
});
}
}
Adapter for lists:
public class Adapter extends RecyclerView.Adapter<Adapter.Holder>{
private boolean changeHeight = false;
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item, parent, false);
return new Holder(view);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
holder.setContent(String.valueOf(position), position % 2 == 0 ? R.color.colorPrimary : R.color.colorAccent);
if (changeHeight && position == 3) holder.changeHeight();
}
#Override
public int getItemCount() {
return 100;
}
class Holder extends RecyclerView.ViewHolder {
private View view;
private TextView textView;
public Holder(View itemView) {
super(itemView);
view = itemView;
textView = itemView.findViewById(R.id.text_view);
}
public void setContent(String text, int colorRes) {
textView.setText(text);
textView.setBackgroundResource(colorRes);
}
public void changeHeight() {
ViewGroup.LayoutParams params = view.getLayoutParams();
params.height = view.getHeight() * 2;
view.setLayoutParams(params);
}
}
}
This is activity_main.xml layout for Activity
<?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">
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view1"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="match_parent"/>
<android.support.v7.widget.RecyclerView
android:id="#+id/recycler_view2"
android:layout_width="0dp"
android:layout_weight="0.5"
android:layout_height="match_parent"/>
<Button
android:id="#+id/button"
android:layout_width="100dp"
android:layout_height="100dp"/>
</LinearLayout>
Item for RecyclerView item.xml
<?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="100dp">
<TextView
android:id="#+id/text_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"/>
</RelativeLayout>

Crude solution is copying scroll of first recycler into the second one:
recyclerView1.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
recyclerView2.scrollBy(dx, dy);
}
});
Then proceed to smoothScroll only recyclerView1.

Related

How can I make the Android RecyclerView responsive to fit the screen initially?

Sometimes RecyclerView shows empty spaces at bottom if the screen is large or it has fewer items to cover the screen. Also sometimes it shows some items partially if it has more items. What I want is:
RecyclerView will cover the full available space with the given number
of rows. It will always fill the screen, no matter what is the size of
the screen.
How can I do that?
use match_parent for both width and height.
I'm posting my solution now for any future questioner. Here, by the word RESPONSIVE, I want the Items to take the available screen-space equally. For example, if we want 4 rows initially, the 4 rows will cover up the screen fully; that means, no item will be visible partially when the RecyclerView appears on the screen for the first time. And this will only work if the RecyclerView has fixed height irrespective of its Children, means, don't give WRAP_CONTENT to RecyclerView layout_height, give anything else like MATCH_PARENT or Constrained height.
Step1: You need to pass a parameter rows to the Adapter. You also need to pass the RecyclerView to the Adapter.
Step2: We need to keep a FLAG which will decide when to reveal the Child Items on the Screen. This FLAG will be FALSE initially, this will be TRUE when we finish calculating the available HEIGHT of RecyclerView on the screen. When we get the height of RecyclerView, we divide it by rows and get the height of each row. So we need to put a OnGlobalLayoutListener on the RecyclerView.
Here is a sample code.
RecyclerView layout will be like:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:layout_height="0dp"
android:id="#+id/recycler_view"
tools:listitem="#layout/item"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Item layout will be like:
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true"
card_view:cardCornerRadius="5dp"
card_view:cardElevation="5dp">
<TextView
android:padding="15dp"
android:id="#+id/title"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:enabled="true"
android:focusable="true"
android:longClickable="true"
android:textIsSelectable="true" />
</androidx.cardview.widget.CardView>
Adapter will be like:
public class ResponsiveItemListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements View.OnClickListener
{
public static final int NUMBER_OF_ROWS_AUTO = -1;
Context context;
LayoutInflater layoutInflater;
RecyclerViewItemClickListeners listener;
List<Item> items;
RecyclerView recyclerView;
int numberOfRows;
int rowHeightInPx = 0;
boolean itemHeightCalculationCompleted = false;
public ResponsiveItemListAdapter(Context context, List<Item> items, RecyclerViewItemClickListeners listener, RecyclerView rv, int rows)
{
super();
this.context = context;
this.items = items;
this.listener = listener;
this.layoutInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.recyclerView = rv;
this.numberOfRows = rows;
if (this.numberOfRows > 0)
{
this.recyclerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
if (recyclerView.getMeasuredHeight() > 0)
{
recyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(this);
setRowHeightInPx(recyclerView.getMeasuredHeight() / numberOfRows);
itemHeightCalculationCompleted = true;
notifyDataSetChanged();
}
}
});
} else
{
itemHeightCalculationCompleted = true;
}
}
public int getRowHeightInPx()
{
return rowHeightInPx;
}
public void setRowHeightInPx(int rowHeightInPx)
{
this.rowHeightInPx = rowHeightInPx;
}
#Override
public int getItemCount()
{
if (this.items != null && this.itemHeightCalculationCompleted)
return this.items.size();
else
return 0;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType)
{
RecyclerView.ViewHolder vh;
View view = this.layoutInflater.inflate(R.layout.item, parent, false);
if (getRowHeightInPx() > 0)
{
RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams) view.getLayoutParams();
layoutParams.height = getRowHeightInPx();
layoutParams.width = MATCH_PARENT;
view.setLayoutParams(layoutParams);
}
vh = new GeneralViewHolder(view);
return vh;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position)
{
Item page = items.get(position);
CardView view = ((CardView) ((GeneralViewHolder) holder).getView());
((GeneralViewHolder) holder).getTitle().setText(page.getTitle());
((GeneralViewHolder) holder).getView().setOnClickListener(this);
((GeneralViewHolder) holder).getView().setTag(position);
}
#Override
public void onViewRecycled(RecyclerView.ViewHolder holder)
{
super.onViewRecycled(holder);
}
#Override
public void onClick(View v)
{
int position = (int) v.getTag();
this.listener.onRecyclerViewItemClick(this.items, position);
}
public class GeneralViewHolder extends RecyclerView.ViewHolder
{
View view;
TextView title;
public GeneralViewHolder(View itemView)
{
super(itemView);
view = itemView;
title = itemView.findViewById(R.id.title);
}
public View getView()
{
return view;
}
public TextView getTitle()
{
return title;
}
}
public interface RecyclerViewItemClickListeners
{
void onRecyclerViewItemClick(List<Item> items, int position);
}
}
Set the adapter like this:
public class MainActivity extends AppCompatActivity implements ResponsiveItemListAdapter.RecyclerViewItemClickListeners
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
List<Item> items = new ArrayList<>();
items.add(new Item("One"));
items.add(new Item("Two"));
items.add(new Item("Three"));
items.add(new Item("Four"));
items.add(new Item("Five"));
items.add(new Item("Six"));
items.add(new Item("Seven"));
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new ResponsiveItemListAdapter(this, items, this, recyclerView, 3));
}
#Override
public void onRecyclerViewItemClick(List<Item> items, int position)
{
Toast.makeText(this, items.get(position).getTitle(), Toast.LENGTH_LONG).show();
}
}
And here is the output:

Layout_behavior with an Adapter inside another one

I have been searching and found some close questions and answer but none of them works.
I'm using CoordinatorLayout for a list RecyclerView with AppBarLayout and ToolBar.
My Goal:
The ideia is very simple, when you roll the view first the AppBar Collapse in the Toolbar and after that the RecyclerView start to scroll.
My Hierarchy:
My Fragment RecyclerView(vertical) has a Adapter-A with a new layout, and this adapter call a second adapter-B for the RecyclerView(horizontal).
So I Have a struct like this:
(is not a code, just to show how works)
Fragment AppBarLayout{..}
Fragment Recycler View (Vertical) {
Adapter-A Text;
Adapter-A Recycler View (Horizontal){
Adapter-B Img;
Adapter-B Text;
}
}
What is the problem:
If I click in the RecyclerView(Vertical) or Adapter-A Img it works fine.
But If I click in Adapter B content(Img and Text) it scroll both Recycler Views but not the AppBar.
What I'm doing:
I use
app:layout_behavior="#string/appbar_scrolling_view_behavior"
recyclerList.setHasFixedSize(true)
recyclerList.setNestedScrollingEnabled(true);
in both Recycler Views.
app:layout_scrollFlags="scroll|exitUntilCollapsed|enterAlways"
in the CollapsingToolbarLayout
I try to make this in java code:
recyclerList.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
// If AppBar is fully expanded, revert the scroll.
if (!shouldScroll) {
recyclerList.scrollToPosition(0);
//Here I should make the AppBar Scroll, but AppBarLayout.scrollTo(dx, dy) don't work.
}
}
});
mainHomeAppBarLayout.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
#Override
public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
shouldScroll = verticalOffset != 0;
}
});
My code:
mainHome.xml
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/main_home_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
<android.support.design.widget.AppBarLayout
android:id="#+id/main_home_app_bar_layout"
android:layout_width="match_parent"
android:layout_height="192dp"
android:background="#drawable/gradient_bg">
<android.support.design.widget.CollapsingToolbarLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed|enterAlways">
<android.support.v7.widget.Toolbar
android:layout_width="match_parent"
android:layout_height="50dp"
app:layout_collapseMode="pin" />
</android.support.design.widget.CollapsingToolbarLayout>
</android.support.design.widget.AppBarLayout>
mainHome.java
#Override
protected void assignViews() {
mainHomeList = (RecyclerView) findViewById(R.id.main_home_list);
mainHomeAppBarLayout = (AppBarLayout) findViewById(R.id.main_home_app_bar_layout);
}
#Override
protected void prepareViews() {
mainHomeList.setHasFixedSize(true);
mainHomeList.setNestedScrollingEnabled(true);
linearLayoutManager = new LinearLayoutManager(getApplicationContext());
mainHomeList.setLayoutManager(linearLayoutManager);
initAdapterIfNecessary();
if (mainHomeList.getAdapter() == null)
mainHomeList.setAdapter(adapter);
}
adapter-A.java
public class MainHomeModulesAdapter extends RecyclerView.Adapter<MainHomeModulesAdapter.GroupViewHolder> {
private OnListItemClickedListener onListItemClickedListener = null;
private OnListItemClickedTwoListener onListItemClickedTwoListener = null;
private ArrayList<JSONMainModule> mainModules = new ArrayList<>();
#Override
public MainHomeModulesAdapter.GroupViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
Context context = parent.getContext();
View itemView = LayoutInflater.from(context).inflate(R.layout.a_main_home_module_item, parent, false);
return (new MainHomeModulesAdapter.GroupViewHolder(itemView));
}
#Override
public void onBindViewHolder(MainHomeModulesAdapter.GroupViewHolder holder, int position) {
//Place where put layout information
holder.mainHomeModuleList.setLayoutManager(new GridLayoutManager(context, Utils.calcGridSpaceCount(context, 2))); //For two elements
holder.mainHomeModuleList.setAdapter(holder.mainHomeContentAdapter);
holder.mainHomeContentAdapter.updateListContent(mainModules.get(position).getModuleContent(), mainModules.get(position).getModule());
}
#Override
public int getItemCount() {
return mainModules.size();
}
public void setOnListItemClickedListener(OnListItemClickedListener onListItemClickedListener) {
this.onListItemClickedListener = onListItemClickedListener;
}
public void setOnListItemClickedTwoListener(OnListItemClickedTwoListener onListItemClickedTwoListener){
this.onListItemClickedTwoListener = onListItemClickedTwoListener;
}
public void updateListContent(ArrayList<JSONMainModule> mainModules) {
this.mainModules = mainModules;
notifyDataSetChanged();
}
public JSONMainModule getListContent(int pos) {
return mainModules.get(pos);
}
class GroupViewHolder extends ParentViewHolder {
TextView mainHomeModuleText;
Button mainHomeModuleBtn;
RecyclerView mainHomeModuleList;
MainHomeContentAdapter mainHomeContentAdapter; //Adapter-B
private GroupViewHolder(View itemView) {
super(itemView);
mainHomeModuleText = (TextView) itemView.findViewById(R.id.main_home_module_title);
mainHomeModuleBtn = (Button) itemView.findViewById(R.id.main_home_module_btn);
mainHomeModuleBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (ATUtils.isDoubleClick()) return;
onListItemClickedListener.onClicked(getAdapterPosition());
}
});
//Child - Main Contents
mainHomeModuleList = (RecyclerView) itemView.findViewById(R.id.main_home_module_list);
mainHomeModuleList.setHasFixedSize(true);
mainHomeModuleList.setNestedScrollingEnabled(true);
mainHomeContentAdapter = new MainHomeContentAdapter();
mainHomeContentAdapter.setOnListItemClickedListener(new OnListItemClickedListener() {
#Override
public void onClicked(int pos) {
onListItemClickedTwoListener.onClicked(pos, getAdapterPosition());
}
});
}
}
}
Found a Solution.
When create the parent Vertical ListView set setNestedScrollingEnabled as true.
And when create the child Horizontal ListView set setNestedScrollingEnabled as false.

How to make two Recyclerview in one Layout

i already success to make one Recyclerview and i want to add new Recyclerview Horizontal on top. i will explain in my code :
<android.support.v7.widget.RecyclerView
android:id="#+id/arrayListUser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:divider="#color/white">
</android.support.v7.widget.RecyclerView>
<android.support.v7.widget.RecyclerView
android:id="#+id/arrayList"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:divider="#color/white">
</android.support.v7.widget.RecyclerView>
id:arrayList is my first Recyclerview have name xml feeds_listview
id:arrayListUser is my new Recyclerview, i want make this Recyclerview Horizontal
xml for new Recylerview is feeds_listviewUser
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<de.hdodenhof.circleimageview.CircleImageView
android:id="#+id/profil"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginBottom="5dp"
android:layout_marginTop="30dp"
android:layout_gravity="center"
android:src="#drawable/cthprofil" />
<TextView
android:id="#+id/fullName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:layout_marginTop="5dp"
android:layout_marginBottom="20"
android:text="Megi Fernanda"
android:textSize="17sp"
android:textColor="#color/colordefault"
android:textStyle="bold" />
</LinearLayout>
and this is my class adapter
public class FeedsCustomAdapter extends RecyclerView.Adapter<FeedsCustomAdapter.ViewHolder> {
private Context context;
private List<FeedsAdapter> feeds_list;
private ArrayList<Feeds> mFeedsList = new ArrayList<Feeds>();
private OnItemClickListener mListener;
private OnItemClickListener mListener2;
public FeedsCustomAdapter(Context context, ArrayList<Feeds> mFeedsList) {
this.context = context;
this.mFeedsList = mFeedsList;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.feeds_listview, parent, false);
return new ViewHolder(itemView);
}
#Override
public void onBindViewHolder(ViewHolder holder, int position) {
Feeds feed = getFeeds().get(position);
int textColor = context.getResources().getColor(R.color.btn_next);
int textColor2 = context.getResources().getColor(R.color.text_color_black);
holder.fullName.setText(feed.user.fullName);
holder.location.setText(feed.user.location);
holder.topic.setText(Html.fromHtml( "Menyelesaikan Tantangan " + " <font color = '" + String.valueOf(textColor2) + "'>" + feed.topic + "</font>" ) );
Picasso.with(context)
.load(feed.user.avatar)
.into(holder.profile);
PrettyTime prettyTime = new PrettyTime();
String times = prettyTime.format(DateUtil.timeMilisTodate(feed.timestamp * 1000));
holder.times.setText(times);
}
#Override
public int getItemCount() {
return mFeedsList.size();
}
public ArrayList<Feeds> getFeeds() {
return mFeedsList;
}
public void setComplete(int position) {
mFeedsList.get(position).isComplete = 1;
}
public boolean last() {
boolean result = false;
int total = mFeedsList.size();
for (int i = 0; i < mFeedsList.size(); i++) {
if (mFeedsList.get(i).isComplete == 1) {
total--;
}
}
if (total == 1) {
result = true;
}
return result;
}
class ViewHolder extends RecyclerView.ViewHolder {
public TextView fullName;
public TextView location;
public TextView topic;
public ImageView profile;
public TextView times;
public ViewHolder(View itemView) {
super(itemView);
fullName = (TextView) itemView.findViewById(R.id.fullName);
location = (TextView) itemView.findViewById(R.id.location);
topic = (TextView) itemView.findViewById(R.id.topic);
profile = (ImageView) itemView.findViewById(R.id.profil);
times = (TextView) itemView.findViewById(R.id.times);
profile.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(mListener2 != null){
mListener2.onItemClick2(v ,getPosition());
}
}
});
topic.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(mListener != null){
mListener.onItemClick(v ,getPosition());
}
}
});
}
}
public void setClickListener(OnItemClickListener clickListener) {
this.mListener = clickListener;
}
public void setClickListenerProfile(OnItemClickListener clickListener2){
this.mListener2 = clickListener2;
}
public interface OnItemClickListener {
public abstract void onItemClick(View view, int position);
public abstract void onItemClick2(View view, int position);
}
so, in my code i success to display first recylerview with my first xml and i want add new recylerview horizontal with new xml feeds_listviewUser
You can use LinearLayout to wrap both recyclerView.
<android.support.v7.widget.RecyclerView
android:id="#+id/arrayListUser"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#color/white">
</android.support.v7.widget.RecyclerView>
<android.support.v7.widget.RecyclerView
android:id="#+id/arrayList"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:divider="#color/white">
</android.support.v7.widget.RecyclerView>
And assign horizontal layout manager to one recyclerview and vertical layout manager to other
LinearLayoutManager userManager = new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false);
arrayListUser.setLayoutManager(userManager);
LinearLayoutManager listManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
arrayList.setLayoutManager(listManager);
Put your recyclerviews in Relative layout.
First add horizontal recyclerView to alignParentTop true and fix height according to visibility of feeds_listviewUser next add vertical recyclerView with layout_below horizontal recyclerview id.

AnimateLayoutChanges doesn't work with RecyclerView

I have a screen with a Recyclerview and others Elements inside of LinearLayout. The problem is when I remove a item of the RecyclerView, animateLayoutChanges doesn't work in this case. Does anayone know why this happen??
XML
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.alvaro.resizetest.MainActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
<LinearLayout
android:id="#+id/test1"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/colorAccent"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayout"
android:textColor="#FFFFFF"
android:textSize="22sp"/>
</LinearLayout>
<LinearLayout
android:id="#+id/test2"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/colorPrimaryDark"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayout"
android:textColor="#FFFFFF"
android:textSize="22sp"/>
</LinearLayout>
<LinearLayout
android:id="#+id/test3"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/colorAccent"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayout"
android:textColor="#FFFFFF"
android:textSize="22sp"/>
</LinearLayout>
<LinearLayout
android:id="#+id/test4"
android:layout_width="match_parent"
android:layout_height="50dp"
android:background="#color/colorPrimaryDark"
android:gravity="center_vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="LinearLayout"
android:textColor="#FFFFFF"
android:textSize="22sp"/>
</LinearLayout>
</LinearLayout>
JAVA
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
Adapter adapter = new Adapter();
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
recyclerView.setNestedScrollingEnabled(true);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
view.setVisibility(View.GONE);
}
};
findViewById(R.id.test1).setOnClickListener(listener);
findViewById(R.id.test2).setOnClickListener(listener);
findViewById(R.id.test3).setOnClickListener(listener);
findViewById(R.id.test4).setOnClickListener(listener);
}
class Adapter extends RecyclerView.Adapter<Adapter.Holder>{
int size = 3;
public Adapter() {
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = getLayoutInflater().inflate(R.layout.item, parent, false);
return new Holder(view);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
}
#Override
public int getItemCount() {
return size;
}
class Holder extends RecyclerView.ViewHolder {
public Holder(final View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
size --;
notifyItemRemoved(getAdapterPosition());
}
});
}
}
}
After a while I got a solution. I made a function to animate the recyclerView height.
JAVA
public class MainActivity extends AppCompatActivity {
private final String TAG = MainActivity.class.getSimpleName();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recyclerview);
Adapter adapter = new Adapter(recyclerView);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this) {
#Override
public boolean canScrollVertically() {
return false;
}
};
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setAdapter(adapter);
recyclerView.setNestedScrollingEnabled(true);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View view) {
view.setVisibility(View.GONE);
}
};
findViewById(R.id.test1).setOnClickListener(listener);
findViewById(R.id.test2).setOnClickListener(listener);
findViewById(R.id.test3).setOnClickListener(listener);
findViewById(R.id.test4).setOnClickListener(listener);
}
public void animateHeight(final View v, final int height) {
final int initialHeight = v.getMeasuredHeight();
int duration = 500;
Interpolator interpolator = new AccelerateInterpolator(2);
// I have to set the same height before the animation because there is a glitch
// in the beginning of the animation
v.getLayoutParams().height = initialHeight;
v.requestLayout();
Animation a = new Animation() {
#Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
Log.d(TAG, "InterpolatedTime: " + interpolatedTime);
Log.d(TAG, "Collapsing height: " + (initialHeight - (int) (height * interpolatedTime)));
v.getLayoutParams().height = initialHeight - (int) (height * interpolatedTime);
v.requestLayout();
}
#Override
public boolean willChangeBounds() {
return true;
}
};
a.setDuration(duration);
a.setInterpolator(interpolator);
v.startAnimation(a);
}
class Adapter extends RecyclerView.Adapter<Adapter.Holder> {
RecyclerView mRecyclerView;
int size = 3;
public Adapter(RecyclerView recyclerView) {
mRecyclerView = recyclerView;
}
#Override
public Holder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = getLayoutInflater().inflate(R.layout.item, parent, false);
return new Holder(view);
}
#Override
public void onBindViewHolder(Holder holder, int position) {
}
#Override
public int getItemCount() {
return size;
}
class Holder extends RecyclerView.ViewHolder {
public Holder(final View itemView) {
super(itemView);
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
size--;
notifyItemRemoved(getAdapterPosition());
animateHeight((View) itemView.getParent(), itemView.getMeasuredHeight());
}
});
}
}
}
}
What worked for me was simply adding the following code to onCreate:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
((ViewGroup) findViewById(R.id.llRoot)).getLayoutTransition()
.enableTransitionType(LayoutTransition.CHANGING);
}
where llRoot is your LinearLayout containing RecyclerView.
You do need to keep android:animateLayoutChanges="true" on your LinearLayout, otherwise it crashes.
I believe if you want to support this below JellyBean, you would need your custom solution.
Explanation on the solution is here: https://proandroiddev.com/the-little-secret-of-android-animatelayoutchanges-e4caab2fddec

How to use SwipeDismissBehavior.OnDismissListener on RecyclerView

I am trying to use the SwipeDismissBehavoir from design support library. I've list items in RecyclerView and swiping an item have to dismiss (like google inbox app) .
I've set the listener for the RecyclerView items but the SwipeDismissBehavior onDismiss listener is not getting called.
SwipeDismissBehavior behavior = new SwipeDismissBehavior();
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams)mItemLayout.getLayoutParams();
params.setBehavior(behavior);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
#Override
public void onDismiss(View view) {
}
#Override
public void onDragStateChanged(int i) {
}
});
mItemLayout.setLayoutParams(params);
Here is example how delete row by swipe
MainActivity.java
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final RecyclerView recyclerView = (RecyclerView)findViewById(R.id.recyclerView);
// init layout manager
RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(this);
final ArrayList<String> list = new ArrayList<String>();
list.add("Item1");
list.add("Item2");
list.add("Item3");
list.add("Item4");
list.add("Item5");
list.add("Item6");
final MyAdapter adapter = new MyAdapter(list);
recyclerView.setLayoutManager(layoutManager);
recyclerView.setAdapter(adapter);
ItemTouchHelper swipeToDismissTouchHelper = new ItemTouchHelper(new ItemTouchHelper.SimpleCallback(
ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
// callback for drag-n-drop, false to skip this feature
return false;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
// callback for swipe to dismiss, removing item from data and adapter
list.remove(viewHolder.getAdapterPosition());
adapter.notifyItemRemoved(viewHolder.getAdapterPosition());
}
});
swipeToDismissTouchHelper.attachToRecyclerView(recyclerView);
}
Adapter
public class MyAdapter extends RecyclerView.Adapter<MyAdapter.MyViewHolder> {
ArrayList<String> dataset_;
public static class MyViewHolder extends RecyclerView.ViewHolder{
public Button mBtn;
public TextView mTextView2;
public MyViewHolder(View v){
super(v);
mBtn = (Button) itemView.findViewById(R.id.delete);
mTextView2 = (TextView) itemView.findViewById(R.id.textView2);
}
}
public MyAdapter (ArrayList<String> dataset){
dataset_ = dataset;
}
#Override
public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType){
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.my_text_view,parent,false);
MyViewHolder myViewHolder = new MyViewHolder(v);
return myViewHolder;
}
#Override
public void onBindViewHolder(MyViewHolder holder,int position){
holder.mTextView2.setText(dataset_.get(position));
}
#Override
public int getItemCount(){
return dataset_.size();
}
}
Layout
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="vertical"
android:padding="16dp">
<TextView
style="?android:listSeparatorTextViewStyle"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/heading_dismissable_recycler_view" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
Item in RecyclerView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="#+id/textView2"/>
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/delete"
android:text="Delete"
android:layout_marginLeft="150dp"
android:visibility="invisible" />
</LinearLayout>
</LinearLayout>
Tried with single view.
I can know the view was dismissed, but I'm wondering how to restore the view like Gmail.
Layout:
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/coordinatorLayout"
tools:context=".MainActivity">
<android.support.v7.widget.CardView
android:id="#+id/cardView"
android:layout_margin="20dp"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:text="Haha"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v7.widget.CardView>
</android.support.design.widget.CoordinatorLayout>
Activity:
public class MainActivity extends AppCompatActivity {
private CoordinatorLayout coordinatorLayout;
private CardView cardView;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinatorLayout);
cardView = (CardView) findViewById(R.id.cardView);
CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) cardView.getLayoutParams();
final SwipeDismissBehavior<CardView> behavior = new SwipeDismissBehavior();
behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_START_TO_END);
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
#Override
public void onDismiss(final View view) {
Snackbar.make(coordinatorLayout, "Done", Snackbar.LENGTH_LONG)
.show();
}
#Override
public void onDragStateChanged(int i) {
}
});
params.setBehavior(behavior);
cardView.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return behavior.onTouchEvent(coordinatorLayout, cardView, event);
}
});
}
}
I have succeeded implementing the support library SwipeDismissBehavior and it actually requires CoordinatorLayout inside of each inflated card view layout. I haven't noticed any performance issues so far, so I assume CoordinatorLayout is not so heavy for the UI. There is probably a better way, but I still haven't found it.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FF0000">
<LinearLayout
android:id="#+id/card_content_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:background="#FFFFFF"
android:padding="20dp">
<TextView
android:id="#+id/card_context_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Test text"/>
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
In the constructor of the RecyclerView.ViewHolder implementation class (which is inside the Adapter) I have added:
View cardContentLayout = view.findViewById(R.id.card_content_layout);
SwipeDismissBehavior<View> swipeDismissBehavior = new SwipeDismissBehavior<>();
swipeDismissBehavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_END_TO_START);
swipeDismissBehavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
#Override
public void onDismiss(View view) {
int adapterPosition = getAdapterPosition();
deleteListener.onDismissGesture(view, adapterPosition);
}
#Override
public void onDragStateChanged(int state) { }
});
CoordinatorLayout.LayoutParams coordinatorParams = (CoordinatorLayout.LayoutParams) cardContentLayout.getLayoutParams();
coordinatorParams.setBehavior(swipeDismissBehavior);
cardContentLayout.setOnTouchListener(new View.OnTouchListener() {
#Override
public boolean onTouch(View v, MotionEvent event) {
return swipeDismissBehavior.onTouchEvent((CoordinatorLayout) itemView, cardContentLayout, event);
}
});

Categories

Resources