I want to edit HideBottomViewOnScrollBehavior class so that it can be reversed
to work as HideTopViewOnScrollBehavior
this is the class code :
/** * The {#link Behavior} for a View within a {#link
CoordinatorLayout} to hide the view off the * bottom of the screen
when scrolling down, and show it when scrolling up. */
public class HideBottomViewOnScrollBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
protected static final int ENTER_ANIMATION_DURATION = 225;
protected static final int EXIT_ANIMATION_DURATION = 175;
private static final int STATE_SCROLLED_DOWN = 1;
private static final int STATE_SCROLLED_UP = 2;
private int height = 0;
private int currentState = STATE_SCROLLED_UP;
private int additionalHiddenOffsetY = 0;
#Nullable private ViewPropertyAnimator currentAnimator;
public HideBottomViewOnScrollBehavior() {}
public HideBottomViewOnScrollBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public boolean onLayoutChild(
#NonNull CoordinatorLayout parent, #NonNull V child, int layoutDirection) {
ViewGroup.MarginLayoutParams paramsCompat =
(ViewGroup.MarginLayoutParams) child.getLayoutParams();
height = child.getMeasuredHeight() + paramsCompat.bottomMargin;
return super.onLayoutChild(parent, child, layoutDirection);
}
/**
* Sets an additional offset for the y position used to hide the view.
*
* #param child the child view that is hidden by this behavior
* #param offset the additional offset in pixels that should be added when the view slides away
*/
public void setAdditionalHiddenOffsetY(#NonNull V child, #Dimension int offset) {
additionalHiddenOffsetY = offset;
if (currentState == STATE_SCROLLED_DOWN) {
child.setTranslationY(height + additionalHiddenOffsetY);
}
}
#Override
public boolean onStartNestedScroll(
#NonNull CoordinatorLayout coordinatorLayout,
#NonNull V child,
#NonNull View directTargetChild,
#NonNull View target,
int nestedScrollAxes,
int type) {
return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL;
}
#Override
public void onNestedScroll(
CoordinatorLayout coordinatorLayout,
#NonNull V child,
#NonNull View target,
int dxConsumed,
int dyConsumed,
int dxUnconsumed,
int dyUnconsumed,
int type,
#NonNull int[] consumed) {
if (dyConsumed > 0) {
slideDown(child);
} else if (dyConsumed < 0) {
slideUp(child);
}
}
/** * Perform an animation that will slide the child from it's
current position to be totally on the * screen. */
public void slideUp(#NonNull V child) {
if (currentState == STATE_SCROLLED_UP) {
return;
}
if (currentAnimator != null) {
currentAnimator.cancel();
child.clearAnimation();
}
currentState = STATE_SCROLLED_UP;
animateChildTo(
child, 0, ENTER_ANIMATION_DURATION, AnimationUtils.LINEAR_OUT_SLOW_IN_INTERPOLATOR);
}
/** * Perform an animation that will slide the child from it's
current position to be totally off the * screen. */
public void slideDown(#NonNull V child) {
if (currentState == STATE_SCROLLED_DOWN) {
return;
}
if (currentAnimator != null) {
currentAnimator.cancel();
child.clearAnimation();
}
currentState = STATE_SCROLLED_DOWN;
animateChildTo(
child,
height + additionalHiddenOffsetY,
EXIT_ANIMATION_DURATION,
AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR);
}
private void animateChildTo(
#NonNull V child, int targetY, long duration, TimeInterpolator interpolator) {
currentAnimator =
child
.animate()
.translationY(targetY)
.setInterpolator(interpolator)
.setDuration(duration)
.setListener(
new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
currentAnimator = null;
}
});
}
}
Can any one give me a source to learn about themes and animations in android?
Related
I am using recycler. I need to set height of the view as the width of the container. But the problem is when I am trying to get the width size it seems not exist yet so it returns zero. How I can make sure that view is already created to get its value and use it while setting the height?
That is the code where i am setting the height
imageView.getLayoutParams().height = container.getWidth();
imageView.requestLayout();
The code of the adapter
public class OnlineUsersAdapter extends RecyclerView.Adapter<OnlineUsersAdapter.OnlineUserViewHolder> {
private List<OnlineUser> onlineUsers = new ArrayList<>();
private OnItemClickListener.OnItemClickCallback onItemClickCallback;
private OnItemClickListener.OnItemClickCallback onChatClickCallback;
private OnItemClickListener.OnItemClickCallback onLikeClickCallback;
private Context context;
public OnlineUsersAdapter(OnItemClickListener.OnItemClickCallback onItemClickCallback,
OnItemClickListener.OnItemClickCallback onChatClickCallback,
OnItemClickListener.OnItemClickCallback onLikeClickCallback) {
this.onItemClickCallback = onItemClickCallback;
this.onChatClickCallback = onChatClickCallback;
this.onLikeClickCallback = onLikeClickCallback;
}
public void addUsers(List<OnlineUser> userList) {
onlineUsers.addAll(userList);
notifyItemRangeInserted(onlineUsers.size() - userList.size(), onlineUsers.size());
}
public void clearData(List<OnlineUser> userList){
int oldSize = userList.size();
onlineUsers = userList;
notifyItemRangeRemoved(0, oldSize);
notifyItemRangeInserted(0, userList.size());
}
#NonNull
#Override
public OnlineUsersAdapter.OnlineUserViewHolder onCreateViewHolder(#NonNull ViewGroup parent, int viewType) {
context = parent.getContext();
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_user, parent, false);
return new OnlineUsersAdapter.OnlineUserViewHolder(v);
}
#Override
public void onBindViewHolder(#NonNull OnlineUsersAdapter.OnlineUserViewHolder holder, int position) {
OnlineUser user = onlineUsers.get(position);
holder.bind(user, position);
}
#Override
public int getItemCount() {
return onlineUsers.size();
}
class OnlineUserViewHolder extends RecyclerView.ViewHolder {
RelativeLayout container;
ImageView imageView, likeBtn, chatBtn;
TextView name, country;
private LottieAnimationView animationView;
OnlineUserViewHolder(View itemView) {
super(itemView);
context = itemView.getContext();
container = itemView.findViewById(R.id.item_user_container);
imageView = itemView.findViewById(R.id.user_img);
likeBtn = itemView.findViewById(R.id.search_btn_like);
chatBtn = itemView.findViewById(R.id.search_btn_chat);
name = itemView.findViewById(R.id.user_name);
country = itemView.findViewById(R.id.user_country);
animationView = itemView.findViewById(R.id.lottieAnimationView);
}
void bind(OnlineUser user, int position) {
ViewCompat.setTransitionName(imageView, user.getName());
if (FirebaseUtils.isUserExist() && user.getUid() != null) {
new FriendRepository().isLiked(user.getUid(), flag -> {
if (flag) {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.ic_favorite));
animationView.setVisibility(View.VISIBLE);
} else {
likeBtn.setBackground(ContextCompat.getDrawable(context, R.drawable.heart_outline));
animationView.setVisibility(View.GONE);
}
});
}
if (user.getUid() != null) {
chatBtn.setOnClickListener(new OnItemClickListener(position, onChatClickCallback));
likeBtn.setOnClickListener(new OnItemClickListener(position, onLikeClickCallback));
}
imageView.setOnClickListener(new OnItemClickListener(position, onItemClickCallback));
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
if (user.getImage().equals(Consts.DEFAULT)) {
Glide.with(context).load(context.getResources().getDrawable(R.drawable.default_avatar)).into(imageView);
} else {
Glide.with(context).load(user.getImage()).thumbnail(0.5f).into(imageView);
}
country.setText(user.getCountry());
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1f).setDuration(500);
animator.addUpdateListener(valueAnimator ->
animationView.setProgress((Float) valueAnimator.getAnimatedValue()));
if (animationView.getProgress() == 0f) {
animator.start();
} else {
animationView.setProgress(0f);
}
imageView.getLayoutParams().height = 500;
imageView.requestLayout();
}
}
}
Wait until your view gets rendered on UI. There are a few ways to do that. One of them is to use post() of the view which you want to get the height.
Like
container.post(new Runnable() {
#Override
public void run() {
imageView.getLayoutParams().height = container.getWidth();
imageView.requestLayout();
}
});
And View's getWidth() and getHeight() returns 0 is an amazing post which contains few Kotlin solutions too, which are more easier than Java.
The Problem
I have a ListView with a TextView and EditText. I have a addTextChangeListener() on the EditText so I can save the text immediately into the ArrayList so I can retrieve it in the parent activity. *For some reason, when I am on row 1, it thinks the position is row 0 and it saves the EditText information from Row 1 into position 0 of the Array. And it continues on to save Row 1 information into position 1 of the Array. So I have a double of Row 1 information. Everything else after that is completely normal.
Does anybody have any idea of how to solve this?
ArrayAdapter
class CustomListViewEntryAdapter extends ArrayAdapter<ThoughtClass> {
private ArrayList<ThoughtClass> mListOfThoughtClass;
private ThoughtClass mThoughtClass;
private int mNumberOfThought;
private String mThoughtEntry;
static class ViewHolder {
CardView cardView;
EditText editText;
TextView textView;
}
public CustomListViewEntryAdapter(#NonNull Context context, ArrayList<ThoughtClass> thoughtClass) {
super(context, R.layout.listview_entry_edittext_row ,thoughtClass);
mListOfThoughtClass = thoughtClass;
}
#NonNull
#Override
public View getView(final int position, #Nullable View convertView, #NonNull ViewGroup parent) {
ViewHolder viewHolder;
View currentView = convertView;
if(convertView == null) {
LayoutInflater davidsInflater = LayoutInflater.from(getContext());
currentView = davidsInflater.inflate(R.layout.listview_entry_edittext_row, parent, false);
viewHolder = new ViewHolder();
viewHolder.cardView = currentView.findViewById(R.id.thoughtCard);
viewHolder.textView = currentView.findViewById(R.id.thoughtNumber);
viewHolder.editText = currentView.findViewById(R.id.thoughtEntry);
// associate the holder with the view for later lookup
currentView.setTag(viewHolder);
}
else {
// view already exists, get the holder instance from the view
viewHolder = (ViewHolder) currentView.getTag();
}
viewHolder.editText.addTextChangedListener(new TextWatcher() {
#Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
#Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
#Override
public void afterTextChanged(Editable s) {
mListOfThoughtClass.get(position).setThoughtEntry(s.toString());
}
});
mThoughtClass = mListOfThoughtClass.get(position);
mNumberOfThought = mThoughtClass.getNumberOfThought();
if(mThoughtClass.getThoughtEntry() == null) {
mThoughtEntry = "";
}
else {
mThoughtEntry = mThoughtClass.getThoughtEntry();
}
// Set objects
viewHolder.textView.setText( Integer.toString(mNumberOfThought) );
if(!mThoughtEntry.equals("") || mThoughtEntry != null) {
viewHolder.editText.setText(mThoughtEntry);
}
return currentView;
}
}
ThoughtClass
public class ThoughtClass {
private int mNumberOfThought;
private String mThoughtEntry;
private StorageDistortions mStorageDistortions;
public ThoughtClass(int numberOfThought) {
this.mNumberOfThought = numberOfThought;
this.mThoughtEntry = "";
this.mStorageDistortions = new StorageDistortions();
}
public ThoughtClass(int numberOfThought, String thoughtEntry){
this.mNumberOfThought = numberOfThought;
this.mThoughtEntry = thoughtEntry;
}
public ThoughtClass(int numberOfThought, StorageDistortions storageDistortions){
this.mNumberOfThought = numberOfThought;
this.mStorageDistortions = storageDistortions;
}
public int getNumberOfThought() {
return mNumberOfThought;
}
public void setNumberOfThought(int numberOfThought) {
mNumberOfThought = numberOfThought;
}
public String getThoughtEntry() {
return mThoughtEntry;
}
public void setThoughtEntry(String thoughtEntry) {
mThoughtEntry = thoughtEntry;
}
public StorageDistortions getStorageDistortions() {
return mStorageDistortions;
}
public void setStorageDistortions(StorageDistortions storageDistortions) {
mStorageDistortions = storageDistortions;
}
}
Edit
I forgot to mention this part. In the parent view there is an add button that introduces a new member to the adapter. I do not believe this would affect the positioning though.
public void addNewThought() {
ThoughtClass thoughtClass = new ThoughtClass(++mNumberOfThoughts);
mThoughtClassList.add( thoughtClass );
setListViewHeightBasedOnItems(mNegativeThoughtsListView);
mNegativeThoughtsAdapter.notifyDataSetChanged();
}
public boolean setListViewHeightBasedOnItems(ListView listView) {
ListAdapter listAdapter = listView.getAdapter();
mThoughtClassList = mNegativeThoughtsAdapter.getItems();
if (listAdapter != null) {
int numberOfItems = listAdapter.getCount();
// Get total height of all mItems.
int totalItemsHeight = 0;
for (int itemPos = 0; itemPos < numberOfItems; itemPos++) {
View item = listAdapter.getView(itemPos, null, listView);
item.measure(0, 0);
totalItemsHeight += item.getMeasuredHeight();
}
// Get total height of all item dividers.
int totalDividersHeight = listView.getDividerHeight() *
(numberOfItems - 1);
// Set list height.
ViewGroup.LayoutParams params = listView.getLayoutParams();
params.height = totalItemsHeight + totalDividersHeight;
listView.setLayoutParams(params);
listView.requestLayout();
return true;
} else {
return false;
}
}
Hi I'm new in android and recently I made a carousel with viewpager using this and it works fine with full width height images but when i resize images it doesn't work properly by that I mean it takes the entire width of the screen instead of specified width.
The actual idea is to have a carousel at the bottom of the page with small images.
So this is where I'm stuck and need some help actually I don't have much idea about this so I would appreciate some help.
Thank you
My code are below as follows
CarouselTransformer.class
public class CarouselEffectTransformer implements ViewPager.PageTransformer {
private int maxTranslateOffsetX;
private ViewPager viewPager;
public CarouselEffectTransformer(Context context) {
this.maxTranslateOffsetX = dp2px(context, 180);
}
public void transformPage(View view, float position) {
if (viewPager == null) {
viewPager = (ViewPager) view.getParent();
}
int leftInScreen = view.getLeft() - viewPager.getScrollX();
int centerXInViewPager = leftInScreen + view.getMeasuredWidth() / 2;
int offsetX = centerXInViewPager - viewPager.getMeasuredWidth() / 2;
float offsetRate = (float) offsetX * 0.38f / viewPager.getMeasuredWidth();
float scaleFactor = 1 - Math.abs(offsetRate);
if (scaleFactor > 0) {
view.setScaleX(scaleFactor);
view.setScaleY(scaleFactor);
view.setTranslationX(-maxTranslateOffsetX * offsetRate);
//ViewCompat.setElevation(view, 0.0f);
}
ViewCompat.setElevation(view, scaleFactor);
}
/**
* Dp to pixel conversion
*/
private int dp2px(Context context, float dipValue) {
float m = context.getResources().getDisplayMetrics().density;
return (int) (dipValue * m + 0.5f);
}
}
PagerAdapter
public class MyPagerAdapter extends PagerAdapter{
Context context;
int adapterType;
ArrayList<NearbyDataProvider> arrayList = new ArrayList<>();
public MyPagerAdapter(Context context,ArrayList<NearbyDataProvider> arrayList, int adapterType) {
this.context = context;
this.arrayList = arrayList;
this.adapterType=adapterType;
}
#NonNull
#Override
public Object instantiateItem(#NonNull ViewGroup container, int position) {
View view = LayoutInflater.from(context).inflate(R.layout.item_cover, null);
try {
NearbyDataProvider nearbyDataProvider = arrayList.get(position);
LinearLayout linMain = (LinearLayout) view.findViewById(R.id.linMain);
RoundedImageView imageCover = (RoundedImageView) view.findViewById(R.id.imageCover);
CustomTextViewMedium customTextViewMedium = (CustomTextViewMedium)view.findViewById(R.id.imageTitle);
CustomTextViewMedium customTextViewMedium1 =(CustomTextViewMedium)view.findViewById(R.id.imageContent);
linMain.setTag(position);
switch (adapterType)
{
case NearbyFragment.ADAPTER_TYPE_TOP:
break;
case NearbyFragment.ADAPTER_TYPE_BOTTOM:
linMain.setBackgroundResource(0);
break;
}
GlideApp.with(context)
.load(arrayList.get(position).getImage())
.into(imageCover);
customTextViewMedium.setText(nearbyDataProvider.getPlace());
container.addView(view);
} catch (Exception e) {
e.printStackTrace();
}
return view;
}
#Override
public int getCount() {
return arrayList.size();
}
#Override
public boolean isViewFromObject(#NonNull View view, #NonNull Object object) {
return (view == object);
}
#Override
public void destroyItem(#NonNull ViewGroup container, int position, #NonNull Object object) {
container.removeView((View) object);
}
}
I'm using a custom recyclerView that can have a footer and a header, that shoudnt influence the animations tho, here is a video of what's happening: http://www.videosprout.com/video?id=00fae6ac-39ff-47b6-b981-803d2773b67b
Why is every view moving one position back and then back to where it was instead of not doing that?
Here's my adapter:
public class AddEventsAdapter extends HFRecyclerViewAdapter<String, AddEventsAdapter.ViewHolder> {
public AddEventsAdapter(Context context) {
super(context);
}
#Override
public void footerOnVisibleItem() {
}
#Override
public void addData(int position, String item) {
super.addData(position, item);
}
#Override
public ViewHolder onCreateDataItemViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.event_item, parent, false);
return new ViewHolder(v);
}
#Override
public void onBindDataItemViewHolder(ViewHolder holder, int position) {
holder.itemTv.setText(getData().get(position));
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView itemTv;
public ViewHolder(View itemView) {
super(itemView);
itemTv = (TextView)itemView.findViewById(R.id.eventName);
}
}
}
The implementation:
final AddEventsAdapter MyAdapter = new AddEventsAdapter(this);
AddEventsRecycler.setAdapter(MyAdapter);
AddEventsRecycler.setLayoutManager(new LinearLayoutManager(this));
//add footer
final View footerView = LayoutInflater.from(this).inflate(R.layout.events_footer, AddEventsRecycler, false);
MyAdapter.setFooterView(footerView);
footerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
MyAdapter.addData(0, "Event number" + ++g);
}
});
ArrayList<String> data = new ArrayList<>();
data.add("Vacation");
MyAdapter.setData(data);
The custom RecyclerAdapter:
public abstract class HFRecyclerViewAdapter<T, VH extends RecyclerView.ViewHolder> extends BaseRecyclerViewAdapter<T> {
public HFRecyclerViewAdapter(Context context) {
super(context);
}
private static final int TYPE_HEADER = Integer.MAX_VALUE;
private static final int TYPE_FOOTER = Integer.MAX_VALUE - 1;
private static final int ITEM_MAX_TYPE = Integer.MAX_VALUE - 2;
private RecyclerView.ViewHolder headerViewHolder;
private RecyclerView.ViewHolder footerViewHolder;
class HFViewHolder extends RecyclerView.ViewHolder {
HFViewHolder(View v) {
super(v);
}
}
public void setHeaderView(View header){
if (headerViewHolder == null || header != headerViewHolder.itemView) {
headerViewHolder = new HFViewHolder(header);
notifyDataSetChanged();
}
}
public void setFooterView(View foot){
if (footerViewHolder == null || foot != footerViewHolder.itemView) {
footerViewHolder = new HFViewHolder(foot);
notifyDataSetChanged();
}
}
public void removeHeader(){
if (headerViewHolder != null){
headerViewHolder = null;
notifyDataSetChanged();
}
}
public void removeFooter(){
if (footerViewHolder != null){
footerViewHolder = null;
notifyDataSetChanged();
}
}
public boolean isHeader(int position){
return hasHeader() && position == 0;
}
public boolean isFooter(int position){
return hasFooter() && position == getDataItemCount() + (hasHeader() ? 1 : 0);
}
private int itemPositionInData(int rvPosition){
return rvPosition - (hasHeader() ? 1 : 0);
}
private int itemPositionInRV(int dataPosition){
return dataPosition + (hasHeader() ? 1 : 0);
}
#Override
public void notifyMyItemInserted(int itemPosition) {
notifyItemInserted(itemPositionInRV(itemPosition));
}
#Override
public void notifyMyItemRemoved(int itemPosition) {
notifyItemRemoved(itemPositionInRV(itemPosition));
}
#Override
public void notifyMyItemChanged(int itemPosition) {
notifyItemChanged(itemPositionInRV(itemPosition));
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == TYPE_HEADER) {
return headerViewHolder;
} else if (viewType == TYPE_FOOTER) {
return footerViewHolder;
}
return onCreateDataItemViewHolder(parent, viewType);
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (!isHeader(position) && !isFooter(position))
onBindDataItemViewHolder((VH)holder, itemPositionInData(position));
if (isFooter(position)){
footerOnVisibleItem();
}
}
public abstract void footerOnVisibleItem();
#Override
public int getItemCount() {
int itemCount = getDataItemCount();
if (hasHeader()) {
itemCount += 1;
}
if (hasFooter()) {
itemCount += 1;
}
return itemCount;
}
#Override
public int getItemViewType(int position) {
if (isHeader(position)) {
return TYPE_HEADER;
}
if (isFooter(position)) {
return TYPE_FOOTER;
}
int dataItemType = getDataItemType(itemPositionInData(position));
if (dataItemType > ITEM_MAX_TYPE) {
throw new IllegalStateException("getDataItemType() must be less than " + ITEM_MAX_TYPE + ".");
}
return dataItemType;
}
public int getDataItemCount() {
return super.getItemCount();
}
/**
* make sure your dataItemType < Integer.MAX_VALUE-1
*
* #param position item view position in rv
* #return item viewType
*/
public int getDataItemType(int position){
return 0;
}
public boolean hasHeader(){
return headerViewHolder != null;
}
public boolean hasFooter(){
return footerViewHolder != null;
}
public abstract VH onCreateDataItemViewHolder(ViewGroup parent, int viewType);
public abstract void onBindDataItemViewHolder(VH holder, int position);
}
EDIT: Same is happening when removing a view, with removeData(getAdapterPosition()
why
this is happening because the element is being added to at the beginning of the array (index 0). when this happens, the RecyclerView will react as shown in the video, because the it pretends the backing data store is a list, and all elements are moved over one index, and the element is inserted in the beginning...which happens to be at the top.
you can see that it won't do this ugly behavior if elements are added at the end of the adapter: MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");. but of course, this is not what you want either, because it is the wrong index...and now it would seem like existing elements are actually jumping up one index on the GUI and things...but this is an interesting experiment to verify the theory.
a solution
still add elements at the end of the array using MyAdapter.addData(MyAdapter.getItemCount() - 1, "Event ");, but make the LinearLayoutManager display elements in reverse! this can be done by:
setting the attribute directly like so: linearLayoutManager.setReverseLayout(true)
or you can use this handy constructor: LinearLayoutManager(context,orientation,isReversed)
you may have to do something about the headers and footers are positioned in the adapter to make sure they stay as headers and footers in the RecyclerView.Adapter though...once the RecyclerView.LayoutManager is displaying things in reverse.
I am using SwipeableRecyclerView and I was wondering how I can disable left swipe?
This is my code:
MainActivity.class
import android.os.Bundle;
import android.support.v7.app.ActionBarActivity;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
import java.util.ArrayList;
import java.util.List;
public class MainActivity extends ActionBarActivity {
private CardViewAdapter mAdapter;
private ArrayList<String> mItems;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mItems = new ArrayList<>(30);
for (int i = 0; i < 30; i++) {
mItems.add(String.format("Card number %02d", i));
}
OnItemTouchListener itemTouchListener = new OnItemTouchListener() {
#Override
public void onCardViewTap(View view, int position) {
Toast.makeText(MainActivity.this, "Tapped " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
#Override
public void onButton1Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button1 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
#Override
public void onButton2Click(View view, int position) {
Toast.makeText(MainActivity.this, "Clicked Button2 in " + mItems.get(position), Toast.LENGTH_SHORT).show();
}
};
mAdapter = new CardViewAdapter(mItems, itemTouchListener);
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(mAdapter);
SwipeableRecyclerViewTouchListener swipeTouchListener =
new SwipeableRecyclerViewTouchListener(recyclerView,
new SwipeableRecyclerViewTouchListener.SwipeListener() {
#Override
public boolean canSwipe(int position) {
return true;
}
#Override
public void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
// Toast.makeText(MainActivity.this, mItems.get(position) + " swiped left", Toast.LENGTH_SHORT).show();
mItems.remove(position);
mAdapter.notifyItemRemoved(position);
}
mAdapter.notifyDataSetChanged();
}
#Override
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
// Toast.makeText(MainActivity.this, mItems.get(position) + " swiped right", Toast.LENGTH_SHORT).show();
mItems.remove(position);
mAdapter.notifyItemRemoved(position);
}
mAdapter.notifyDataSetChanged();
}
});
recyclerView.addOnItemTouchListener(swipeTouchListener);
}
/**
* Interface for the touch events in each item
*/
public interface OnItemTouchListener {
/**
* Callback invoked when the user Taps one of the RecyclerView items
*
* #param view the CardView touched
* #param position the index of the item touched in the RecyclerView
*/
public void onCardViewTap(View view, int position);
/**
* Callback invoked when the Button1 of an item is touched
*
* #param view the Button touched
* #param position the index of the item touched in the RecyclerView
*/
public void onButton1Click(View view, int position);
/**
* Callback invoked when the Button2 of an item is touched
*
* #param view the Button touched
* #param position the index of the item touched in the RecyclerView
*/
public void onButton2Click(View view, int position);
}
/**
* A simple adapter that loads a CardView layout with one TextView and two Buttons, and
* listens to clicks on the Buttons or on the CardView
*/
public class CardViewAdapter extends RecyclerView.Adapter<CardViewAdapter.ViewHolder> {
private List<String> cards;
private OnItemTouchListener onItemTouchListener;
public CardViewAdapter(List<String> cards, OnItemTouchListener onItemTouchListener) {
this.cards = cards;
this.onItemTouchListener = onItemTouchListener;
}
#Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
View v = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.card_view_layout, viewGroup, false);
return new ViewHolder(v);
}
#Override
public void onBindViewHolder(ViewHolder viewHolder, int i) {
viewHolder.title.setText(cards.get(i));
}
#Override
public int getItemCount() {
return cards == null ? 0 : cards.size();
}
public class ViewHolder extends RecyclerView.ViewHolder {
private TextView title;
private Button button1;
private Button button2;
public ViewHolder(View itemView) {
super(itemView);
title = (TextView) itemView.findViewById(R.id.card_view_title);
button1 = (Button) itemView.findViewById(R.id.card_view_button1);
button2 = (Button) itemView.findViewById(R.id.card_view_button2);
button1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemTouchListener.onButton1Click(v, getPosition());
}
});
button2.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemTouchListener.onButton2Click(v, getPosition());
}
});
itemView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onItemTouchListener.onCardViewTap(v, getPosition());
}
});
}
}
}
}
SwipeableRecyclerViewTouchListener.class
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.graphics.Rect;
import android.os.SystemClock;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A {#link View.OnTouchListener} that makes the list items in a {#link android.support.v7.widget.RecyclerView}
* dismissable by swiping.
* <p/>
* <p>Example usage:</p>
* <p/>
* <pre>
* SwipeDismissRecyclerViewTouchListener touchListener =
* new SwipeDismissRecyclerViewTouchListener(
* listView,
* new SwipeDismissRecyclerViewTouchListener.OnDismissCallback() {
* public void onDismiss(ListView listView, int[] reverseSortedPositions) {
* for (int position : reverseSortedPositions) {
* adapter.remove(adapter.getItem(position));
* }
* adapter.notifyDataSetChanged();
* }
* });
* listView.setOnTouchListener(touchListener);
* listView.setOnScrollListener(touchListener.makeScrollListener());
* </pre>
* <p/>
* <p>This class Requires API level 12 or later due to use of {#link
* android.view.ViewPropertyAnimator}.</p>
*/
public class SwipeableRecyclerViewTouchListener implements RecyclerView.OnItemTouchListener {
// Cached ViewConfiguration and system-wide constant values
private int mSlop;
private int mMinFlingVelocity;
private int mMaxFlingVelocity;
private long mAnimationTime;
// Fixed properties
private RecyclerView mRecyclerView;
private SwipeListener mSwipeListener;
private int mViewWidth = 1; // 1 and not 0 to prevent dividing by zero
// Transient properties
private List<PendingDismissData> mPendingDismisses = new ArrayList<>();
private int mDismissAnimationRefCount = 0;
private float mAlpha;
private float mDownX;
private float mDownY;
private boolean mSwiping;
private int mSwipingSlop;
private VelocityTracker mVelocityTracker;
private int mDownPosition;
private int mAnimatingPosition = ListView.INVALID_POSITION;
private View mDownView;
private boolean mPaused;
private float mFinalDelta;
/**
* Constructs a new swipe touch listener for the given {#link android.support.v7.widget.RecyclerView}
*
* #param recyclerView The recycler view whose items should be dismissable by swiping.
* #param listener The listener for the swipe events.
*/
public SwipeableRecyclerViewTouchListener(RecyclerView recyclerView, SwipeListener listener) {
ViewConfiguration vc = ViewConfiguration.get(recyclerView.getContext());
mSlop = vc.getScaledTouchSlop();
mMinFlingVelocity = vc.getScaledMinimumFlingVelocity() * 16;
mMaxFlingVelocity = vc.getScaledMaximumFlingVelocity();
mAnimationTime = recyclerView.getContext().getResources().getInteger(
android.R.integer.config_shortAnimTime);
mRecyclerView = recyclerView;
mSwipeListener = listener;
/**
* This will ensure that this SwipeableRecyclerViewTouchListener is paused during list view scrolling.
* If a scroll listener is already assigned, the caller should still pass scroll changes through
* to this listener.
*/
mRecyclerView.setOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
setEnabled(newState != RecyclerView.SCROLL_STATE_DRAGGING);
}
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
}
});
}
/**
* Enables or disables (pauses or resumes) watching for swipe-to-dismiss gestures.
*
* #param enabled Whether or not to watch for gestures.
*/
public void setEnabled(boolean enabled) {
mPaused = !enabled;
}
#Override
public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
return handleTouchEvent(motionEvent);
}
#Override
public void onTouchEvent(RecyclerView rv, MotionEvent motionEvent) {
handleTouchEvent(motionEvent);
}
private boolean handleTouchEvent(MotionEvent motionEvent) {
if (mViewWidth < 2) {
mViewWidth = mRecyclerView.getWidth();
}
switch (motionEvent.getActionMasked()) {
case MotionEvent.ACTION_DOWN: {
if (mPaused) {
break;
}
// Find the child view that was touched (perform a hit test)
Rect rect = new Rect();
int childCount = mRecyclerView.getChildCount();
int[] listViewCoords = new int[2];
mRecyclerView.getLocationOnScreen(listViewCoords);
int x = (int) motionEvent.getRawX() - listViewCoords[0];
int y = (int) motionEvent.getRawY() - listViewCoords[1];
View child;
for (int i = 0; i < childCount; i++) {
child = mRecyclerView.getChildAt(i);
child.getHitRect(rect);
if (rect.contains(x, y)) {
mDownView = child;
break;
}
}
if (mDownView != null && mAnimatingPosition != mRecyclerView.getChildPosition(mDownView)) {
mAlpha = mDownView.getAlpha();
mDownX = motionEvent.getRawX();
mDownY = motionEvent.getRawY();
mDownPosition = mRecyclerView.getChildPosition(mDownView);
if (mSwipeListener.canSwipe(mDownPosition)) {
mVelocityTracker = VelocityTracker.obtain();
mVelocityTracker.addMovement(motionEvent);
} else {
mDownView = null;
}
}
break;
}
case MotionEvent.ACTION_CANCEL: {
if (mVelocityTracker == null) {
break;
}
if (mDownView != null && mSwiping) {
// cancel
mDownView.animate()
.translationX(0)
.alpha(mAlpha)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_UP: {
if (mVelocityTracker == null) {
break;
}
mFinalDelta = motionEvent.getRawX() - mDownX;
mVelocityTracker.addMovement(motionEvent);
mVelocityTracker.computeCurrentVelocity(1000);
float velocityX = mVelocityTracker.getXVelocity();
float absVelocityX = Math.abs(velocityX);
float absVelocityY = Math.abs(mVelocityTracker.getYVelocity());
boolean dismiss = false;
boolean dismissRight = false;
if (Math.abs(mFinalDelta) > mViewWidth / 2 && mSwiping) {
dismiss = true;
dismissRight = mFinalDelta > 0;
} else if (mMinFlingVelocity <= absVelocityX && absVelocityX <= mMaxFlingVelocity
&& absVelocityY < absVelocityX && mSwiping) {
// dismiss only if flinging in the same direction as dragging
dismiss = (velocityX < 0) == (mFinalDelta < 0);
dismissRight = mVelocityTracker.getXVelocity() > 0;
}
if (dismiss && mDownPosition != mAnimatingPosition && mDownPosition != ListView.INVALID_POSITION) {
// dismiss
final View downView = mDownView; // mDownView gets null'd before animation ends
final int downPosition = mDownPosition;
++mDismissAnimationRefCount;
mAnimatingPosition = mDownPosition;
mDownView.animate()
.translationX(dismissRight ? mViewWidth : -mViewWidth)
.alpha(0)
.setDuration(mAnimationTime)
.setListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
performDismiss(downView, downPosition);
}
});
} else {
// cancel
mDownView.animate()
.translationX(0)
.alpha(mAlpha)
.setDuration(mAnimationTime)
.setListener(null);
}
mVelocityTracker.recycle();
mVelocityTracker = null;
mDownX = 0;
mDownY = 0;
mDownView = null;
mDownPosition = ListView.INVALID_POSITION;
mSwiping = false;
break;
}
case MotionEvent.ACTION_MOVE: {
if (mVelocityTracker == null || mPaused) {
break;
}
mVelocityTracker.addMovement(motionEvent);
float deltaX = motionEvent.getRawX() - mDownX;
float deltaY = motionEvent.getRawY() - mDownY;
if (!mSwiping && Math.abs(deltaX) > mSlop && Math.abs(deltaY) < Math.abs(deltaX) / 2) {
mSwiping = true;
mSwipingSlop = (deltaX > 0 ? mSlop : -mSlop);
}
if (mSwiping) {
mDownView.setTranslationX(deltaX - mSwipingSlop);
mDownView.setAlpha(Math.max(0f, Math.min(mAlpha,
mAlpha * (1f - Math.abs(deltaX) / mViewWidth))));
return true;
}
break;
}
}
return false;
}
private void performDismiss(final View dismissView, final int dismissPosition) {
// Animate the dismissed list item to zero-height and fire the dismiss callback when
// all dismissed list item animations have completed. This triggers layout on each animation
// frame; in the future we may want to do something smarter and more performant.
final ViewGroup.LayoutParams lp = dismissView.getLayoutParams();
final int originalLayoutParamsHeight = lp.height;
final int originalHeight = dismissView.getHeight();
ValueAnimator animator = ValueAnimator.ofInt(originalHeight, 1).setDuration(mAnimationTime);
animator.addListener(new AnimatorListenerAdapter() {
#Override
public void onAnimationEnd(Animator animation) {
--mDismissAnimationRefCount;
if (mDismissAnimationRefCount == 0) {
// No active animations, process all pending dismisses.
// Sort by descending position
Collections.sort(mPendingDismisses);
int[] dismissPositions = new int[mPendingDismisses.size()];
for (int i = mPendingDismisses.size() - 1; i >= 0; i--) {
dismissPositions[i] = mPendingDismisses.get(i).position;
}
if (mFinalDelta > 0) {
mSwipeListener.onDismissedBySwipeRight(mRecyclerView, dismissPositions);
} else {
mSwipeListener.onDismissedBySwipeLeft(mRecyclerView, dismissPositions);
}
// Reset mDownPosition to avoid MotionEvent.ACTION_UP trying to start a dismiss
// animation with a stale position
mDownPosition = ListView.INVALID_POSITION;
ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
// Reset view presentation
pendingDismiss.view.setAlpha(mAlpha);
pendingDismiss.view.setTranslationX(0);
lp = pendingDismiss.view.getLayoutParams();
lp.height = originalLayoutParamsHeight;
pendingDismiss.view.setLayoutParams(lp);
}
// Send a cancel event
long time = SystemClock.uptimeMillis();
MotionEvent cancelEvent = MotionEvent.obtain(time, time,
MotionEvent.ACTION_CANCEL, 0, 0, 0);
mRecyclerView.dispatchTouchEvent(cancelEvent);
mPendingDismisses.clear();
mAnimatingPosition = ListView.INVALID_POSITION;
}
}
});
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
#Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
lp.height = (Integer) valueAnimator.getAnimatedValue();
dismissView.setLayoutParams(lp);
}
});
mPendingDismisses.add(new PendingDismissData(dismissPosition, dismissView));
animator.start();
}
/**
* The callback interface used by {#link SwipeableRecyclerViewTouchListener} to inform its client
* about a swipe of one or more list item positions.
*/
public interface SwipeListener {
/**
* Called to determine whether the given position can be swiped.
*/
boolean canSwipe(int position);
/**
* Called when the item has been dismissed by swiping to the left.
*
* #param recyclerView The originating {#link android.support.v7.widget.RecyclerView}.
* #param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismissedBySwipeLeft(RecyclerView recyclerView, int[] reverseSortedPositions);
/**
* Called when the item has been dismissed by swiping to the right.
*
* #param recyclerView The originating {#link android.support.v7.widget.RecyclerView}.
* #param reverseSortedPositions An array of positions to dismiss, sorted in descending
* order for convenience.
*/
void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions);
}
class PendingDismissData implements Comparable<PendingDismissData> {
public int position;
public View view;
public PendingDismissData(int position, View view) {
this.position = position;
this.view = view;
}
#Override
public int compareTo(#NonNull PendingDismissData other) {
// Sort by descending position
return other.position - position;
}
}
}
Use simple recyclerView and then You can use ItemTouchHelper.Callback
public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
private final YourAdapter mAdapter;
public SimpleItemTouchHelperCallback(YourAdapter adapter) {
mAdapter = adapter;
}
#Override
public boolean isLongPressDragEnabled() {
return true;
}
#Override
public boolean isItemViewSwipeEnabled() {
return true;
}
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
#Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
RecyclerView.ViewHolder target) {
mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
return true;
}
#Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
}
}
In YourAdapter class which is extending recyclerview adapter add these functions.
public void onItemMove(int oldPosition, int newPosition) {
notifyItemMoved(oldPosition,newPosition);
}
public void onItemDismiss(int adapterPosition) {
/// here you can delete from the array and notify for that specific position
}
ItemTouchHelper provides some flags which you can see here
#Override
public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
return makeMovementFlags(dragFlags, swipeFlags);
}
If you want to disable Left to Right Swipe just remove ItemTouchHelper.START flag.
If you want to disable Right to Left Swipe just remove ItemTouchHelper.END flag.
You can pass 0 value instead of swipeFlags. Swipe will not work.
Same goes for dragFlags.
I have implemented this as well and it works like a charm ;) Happy Coding <3