📜 ⬆️ ⬇️

Very handy abstract adapter for RecyclerView do it yourself

Once, at the dawn of my career as an Android developer, I was looking at examples of existing applications and in a beautiful, like the sun in spring, U2020, I found an example of a very convenient adapter. Its name is BindableAdapter . For a long time, I used it as the basis of my adapters for ListView and GridView, but their era comes to its end and the era of RecyclerView comes. This era needs a new handy adapter, and I tried to make it.

To begin with, I decided to ask Google "Where is my favorite?" "RecyclerBindableAdapter", and he did not give me an answer. I honestly tried to find an adapter with which it would be convenient for me to work, but everything was in vain. All the examples I found implemented only a specific functionality and did not try to be abstract. And, frankly, many moments in them confused me, for example: in many of them the Context and LayoutManager were transferred to the designer for some reason. Although the RecyclerView.Adapter has an excellent onAttachedToRecyclerView (RecyclerView recyclerView) method, you can also get the Context and LayoutManager out of the received recyclerView without unnecessary noise and dust.

All this made me think that they were written in a hurry rather than thoughtful. Since I use the RecyclerView everywhere and every time I produce a bunch of similar code somehow not comme il faut, I decided to correct the situation and write a convenient abstract adapter.

And what do you actually need?


Port the very BindableAdapter under RecyclerView, while trying to make the most of all the new features of RecyclerView. As always, there is one “but”: RecyclerView does not have Headers and Footers by default. Therefore, when a future user tries to correct this problem by creating a new item type, because of this, the position number will be shifted by the number of Headers. This is bad. So you need to make it possible in advance to work with Headers and Footers, and also to provide for their presence in the methods of working with data.
')
Then I thought that it would be nice to implement other features of RecyclerView that I often use, namely: element filtering and Header parallax. Here, too, is not so smooth.

The adapter providing filtering is forced to keep two lists of objects (the general list and the list of those items that are now displayed). Consequently, it takes more memory, and the methods for working with elements will work more slowly. There is no need to pre-fill the memory with unnecessary data, and filtering is not needed very often. Therefore, it was decided to make it a separate class extending our main adapter. Thus, we will use this adapter only when we need filtering, and in other cases we will use the main adapter.

A similar situation exists with Header's parallax. The main adapter can have many Headers and Footers. Implementing parallax of several elements at once synchronously is likely to be problematic, and it will look ugly, so there’s no point in even trying. We make it as a separate class.

At this stage, I thought that it would be nice to have some analogue SimpleAdapter. This, so that it can be declared in one line, just slipping into it the Layout ID and ViewHolder. After all, why produce a bunch of code for a simple list.

As a result, the task was to create 4 adapters:
  1. RecyclerBindableAdapter - our main adapter
  2. FilterBindableAdapter - filterable adapter
  3. ParallaxBindableAdapter - an adapter with Header parallax capability
  4. SimpleBindableAdapter - simple adapter

Getting started


RecyclerBindableAdapter


To begin with, we will make it possible to add Headers and Footers, I once met a similar implementation in the vast StackOverflow and adjusted it according to the needs of our adapter. I think most people working with Android have seen or done this implementation, so I’ll not dwell on it in detail.

Make it possible to add headers and footers
//   Header'  Footer' public static final int TYPE_HEADER = 7898; public static final int TYPE_FOOTER = 7899; //     private List<View> headers = new ArrayList<>(); private List<View> footers = new ArrayList<>(); @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //   ,    ViewHolder if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //  header  footer } else { //  FrameLayout   ,   ,      View FrameLayout frameLayout = new FrameLayout(viewGroup.getContext()); frameLayout.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); //  ViewHolder  header'  footer'.    ,         FrameLayout return (VH) new HeaderFooterViewHolder(frameLayout); } } @Override final public void onBindViewHolder(final RecyclerView.ViewHolder vh, int position) { //                  header/footer,      if (isHeader(position)) { View v = headers.get(position); prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else if (isFooter(position)) { View v = footers.get(position - getRealItemCount() - getHeadersCount()); prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else { onBindItemViewHolder((VH) vh, position - headers.size(), getItemType(position)); } } @Override final public int getItemViewType(int position) { //      if (isHeader(position)) { return TYPE_HEADER; } else if (isFooter(position)) { return TYPE_FOOTER; } int type = getItemType(position); if (type == TYPE_HEADER || type == TYPE_FOOTER) { throw new IllegalArgumentException("Item type cannot equal " + TYPE_HEADER + " or " + TYPE_FOOTER); } return type; } 


Now you need to make methods for working with data; we take the names for them from the BindableAdapter for the ListView / GridView.

Work with data
 //    private List<T> items = new ArrayList<>(); //  -      header'  footer' public int getRealItemCount() { return items.size(); } //     public T getItem(int position) { return items.get(position); } //     public void add(int position, T item) { //    items.add(position, item); //   notifyItemInserted(position); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //     public void add(T item) { //    items.add(item); //   notifyItemInserted(items.size() - 1 + getHeadersCount()); } // List    ,       List,      .    ,           ,     public void addAll(List<? extends T> items) { final int size = this.items.size(); //    this.items.addAll(items); //   notifyItemRangeInserted(size + getHeadersCount(), items.size()); } //     public void set(int position, T item) { //    items.set(position, item); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; //  notifyItemRangeChanged(positionStart, itemCount); } //     public void removeChild(int position) { //    items.remove(position); //  notifyItemRemoved(position + getHeadersCount()); //     ViewHolder    ,         int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //      Header'  Footer' public void clear() { final int size = items.size(); //  items.clear(); //   notifyItemRangeRemoved(getHeadersCount(), size); } //       public void moveChildTo(int fromPosition, int toPosition) { //         if (toPosition != -1 && toPosition < items.size()) { //    ... final T item = items.remove(fromPosition); //    items.add(toPosition, item); //  notifyItemMoved(getHeadersCount() + fromPosition, getHeadersCount() + toPosition); //     ViewHolder    ,         int positionStart = fromPosition < toPosition ? fromPosition : toPosition; int itemCount = Math.abs(fromPosition - toPosition) + 1; notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount); } } //   public int indexOf(T object) { return items.indexOf(object); } 


Well, in principle, we finished our RecyclerBindableAdapter. The full text can be viewed here or under the spoiler.

RecyclerBindableAdapter
 public abstract class RecyclerBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<VH> { public static final int TYPE_HEADER = 7898; public static final int TYPE_FOOTER = 7899; private List<View> headers = new ArrayList<>(); private List<View> footers = new ArrayList<>(); private List<T> items = new ArrayList<>(); private RecyclerView.LayoutManager manager; private LayoutInflater inflater; private GridLayoutManager.SpanSizeLookup spanSizeLookup = new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { return getGridSpan(position); } }; public int getRealItemCount() { return items.size(); } public T getItem(int position) { return items.get(position); } public void add(int position, T item) { items.add(position, item); notifyItemInserted(position); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void add(T item) { items.add(item); notifyItemInserted(items.size() - 1 + getHeadersCount()); } public void addAll(List<? extends T> items) { final int size = this.items.size(); this.items.addAll(items); notifyItemRangeInserted(size + getHeadersCount(), items.size()); } public void set(int position, T item) { items.set(position, item); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void removeChild(int position) { items.remove(position); notifyItemRemoved(position + getHeadersCount()); int positionStart = position + getHeadersCount(); int itemCount = items.size() - position; notifyItemRangeChanged(positionStart, itemCount); } public void clear() { final int size = items.size(); items.clear(); notifyItemRangeRemoved(getHeadersCount(), size); } public void moveChildTo(int fromPosition, int toPosition) { if (toPosition != -1 && toPosition < items.size()) { final T item = items.remove(fromPosition); items.add(toPosition, item); notifyItemMoved(getHeadersCount() + fromPosition, getHeadersCount() + toPosition); int positionStart = fromPosition < toPosition ? fromPosition : toPosition; int itemCount = Math.abs(fromPosition - toPosition) + 1; notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount); } } //@TODO need test public int indexOf(T object) { return items.indexOf(object); } @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //if our position is one of our items (this comes from getItemViewType(int position) below) if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //else we have a header/footer } else { //create a new framelayout, or inflate from a resource FrameLayout frameLayout = new FrameLayout(viewGroup.getContext()); //make sure it fills the space frameLayout.setLayoutParams(new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(frameLayout); } } @Override final public void onBindViewHolder(final RecyclerView.ViewHolder vh, int position) { //check what type of view our position is if (isHeader(position)) { View v = headers.get(position); //add our view to a header view and display it prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else if (isFooter(position)) { View v = footers.get(position - getRealItemCount() - getHeadersCount()); //add our view to a footer view and display it prepareHeaderFooter((HeaderFooterViewHolder) vh, v); } else { //it's one of our items, display as required onBindItemViewHolder((VH) vh, position - headers.size(), getItemType(position)); } } private void prepareHeaderFooter(HeaderFooterViewHolder vh, View view) { //if it's a staggered grid, span the whole layout if (manager instanceof StaggeredGridLayoutManager) { StaggeredGridLayoutManager.LayoutParams layoutParams = new StaggeredGridLayoutManager.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); layoutParams.setFullSpan(true); vh.itemView.setLayoutParams(layoutParams); } //if the view already belongs to another layout, remove it if (view.getParent() != null) { ((ViewGroup) view.getParent()).removeView(view); } //empty out our FrameLayout and replace with our header/footer ((ViewGroup) vh.itemView).removeAllViews(); ((ViewGroup) vh.itemView).addView(view); } private boolean isHeader(int position) { return (position < headers.size()); } private boolean isFooter(int position) { return footers.size() > 0 && (position >= getHeadersCount() + getRealItemCount()); } protected VH onCreteItemViewHolder(ViewGroup parent, int type) { return viewHolder(inflater.inflate(layoutId(type), parent, false), type); } @Override public int getItemCount() { return headers.size() + getRealItemCount() + footers.size(); } @Override final public int getItemViewType(int position) { //check what type our position is, based on the assumption that the order is headers > items > footers if (isHeader(position)) { return TYPE_HEADER; } else if (isFooter(position)) { return TYPE_FOOTER; } int type = getItemType(position); if (type == TYPE_HEADER || type == TYPE_FOOTER) { throw new IllegalArgumentException("Item type cannot equal " + TYPE_HEADER + " or " + TYPE_FOOTER); } return type; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (manager == null) { setManager(recyclerView.getLayoutManager()); } if (inflater == null) { this.inflater = LayoutInflater.from(recyclerView.getContext()); } } private void setManager(RecyclerView.LayoutManager manager) { this.manager = manager; if (this.manager instanceof GridLayoutManager) { ((GridLayoutManager) this.manager).setSpanSizeLookup(spanSizeLookup); } else if (this.manager instanceof StaggeredGridLayoutManager) { ((StaggeredGridLayoutManager) this.manager).setGapStrategy( StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS); } } protected int getGridSpan(int position) { if (isHeader(position) || isFooter(position)) { return getMaxGridSpan(); } position -= headers.size(); if (getItem(position) instanceof SpanItemInterface) { return ((SpanItemInterface) getItem(position)).getGridSpan(); } return 1; } protected int getMaxGridSpan() { if (manager instanceof GridLayoutManager) { return ((GridLayoutManager) manager).getSpanCount(); } else if (manager instanceof StaggeredGridLayoutManager) { return ((StaggeredGridLayoutManager) manager).getSpanCount(); } return 1; } //add a header to the adapter public void addHeader(View header) { if (!headers.contains(header)) { headers.add(header); //animate notifyItemInserted(headers.size() - 1); } } //@TODO need test public void removeHeader(View header) { if (headers.contains(header)) { //animate notifyItemRemoved(headers.indexOf(header)); headers.remove(header); } } //add a footer to the adapter public void addFooter(View footer) { if (!footers.contains(footer)) { footers.add(footer); //animate notifyItemInserted(headers.size() + getItemCount() + footers.size() - 1); } } //@TODO need test public void removeFooter(View footer) { if (footers.contains(footer)) { //animate notifyItemRemoved(headers.size() + getItemCount() + footers.indexOf(footer)); footers.remove(footer); } } public int getHeadersCount() { return headers.size(); } protected View getHeader(int location) { return headers.get(location); } public int getFootersCount() { return footers.size(); } protected View getFooter(int location) { return footers.get(location); } protected int getItemType(int position) { return 0; } abstract protected void onBindItemViewHolder(VH viewHolder, int position, int type); protected abstract VH viewHolder(View view, int type); protected abstract @LayoutRes int layoutId(int type); public interface SpanItemInterface { int getGridSpan(); } //our header/footer RecyclerView.ViewHolder is just a FrameLayout public static class HeaderFooterViewHolder extends RecyclerView.ViewHolder { public HeaderFooterViewHolder(View itemView) { super(itemView); } } } 


Now create some example:

 public class LinearExampleAdapter extends RecyclerBindableAdapter<Integer, LinearViewHolder> { private LinearViewHolder.ActionListener actionListener; // layout id    @Override protected int layoutId(int type) { return R.layout.linear_example_item; } // ViewHolder @Override protected LinearViewHolder viewHolder(View view, int type) { return new LinearViewHolder(view); } //    @Override protected void onBindItemViewHolder(LinearViewHolder viewHolder, final int position, int type) { viewHolder.bindView(getItem(position), position, actionListener); } //    public void setActionListener(LinearViewHolder.ActionListener actionListener) { this.actionListener = actionListener; } } 

ViewHolder (Caution Butterknife 7)
 public class LinearViewHolder extends RecyclerView.ViewHolder { @Bind(R.id.linear_example_item_text) TextView text; private int position; private ActionListener actionListener; public LinearViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } public void bindView(Integer item, int position, ActionListener listener) { actionListener = listener; this.position = position; text.setText(String.valueOf(item)); } @OnClick(R.id.linear_example_item_move_to_top) protected void OnMoveToTopClick() { if (actionListener != null) { actionListener.onMoveToTop(position); } } @OnClick(R.id.linear_example_item_remove) protected void OnRemoveClick() { if (actionListener != null) { actionListener.OnRemove(position); } } @OnClick(R.id.linear_example_item_up) protected void OnUpClick() { if (actionListener != null) { actionListener.OnUp(position); } } @OnClick(R.id.linear_example_item_down) protected void OnDownClick() { if (actionListener != null) { actionListener.OnDown(position); } } public interface ActionListener { void onMoveToTop(int position); void OnRemove(int position); void OnUp(int position); void OnDown(int position); } } 


So here, everything is now simple. Work with the adapter simplified many times. Here you can find this example, and here is an example for several types.

FilterBindableAdapter


Let's now create an adapter with the ability to filter. Extend the RecyclerBindableAdapter and first create two lists of objects: all objects and those that are now displayed. We will redefine a part of the methods, so that now they work with two lists.

Create two lists. Override methods
 //  private List<T> originalValues; private List<T> objects; protected FilterBindableAdapter() { objects = new ArrayList<T>(); originalValues = new ArrayList<T>(); } //       ,            ,       @Override public void addAll(List<? extends T> data) { //             if (objects.containsAll(data)) { return; } //       objects.clear(); objects.addAll(data); //       originalValues.clear(); originalValues.addAll(data); // notifyItemRangeInserted(getHeadersCount(), data.size()); } //           


Now we do the filtering.

We do filtering
 //     public Filter getFilter() { if (filter == null) { filter = new ArrayFilter(); } return filter; } //        public interface OnFilterObjectCallback { void handle(int countFilterObject); } //    private class ArrayFilter extends Filter { //   @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); // prefix   ,     if (prefix == null || prefix.length() == 0) { ArrayList<T> list; synchronized (lock) { list = new ArrayList<T>(originalValues); } results.values = list; results.count = list.size(); //     } else { //         String prefixString = prefix.toString().toLowerCase(); ArrayList<T> values; synchronized (lock) { values = new ArrayList<T>(originalValues); } final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>(); //       for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = itemToString(value).toLowerCase(); //            if (valueText.startsWith(prefixString)) { newValues.add(value); //  ,          } else { final String[] words = valueText.split(" "); //   ,       for (String word : words) { if (word.startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } //   @Override protected void publishResults(CharSequence constraint, FilterResults results) { objects = (List<T>) results.values; // callback    if (onFilterObjectCallback != null) { onFilterObjectCallback.handle(results.count); } notifyDataSetChanged(); } } 


The adapter example is completely analogous to the RecyclerBindableAdapter example except for one method. This method is for converting an element into a string, so that we can later filter the elements.

 @Override protected String itemToString(String item) { return item; } 

The full text can be viewed here or under the spoiler.

FilterBindableAdapter
 public abstract class FilterBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerBindableAdapter<T, VH> { private final Object lock = new Object(); private List<T> originalValues; private List<T> objects; private ArrayFilter filter; private OnFilterObjectCallback onFilterObjectCallback; protected FilterBindableAdapter() { objects = new ArrayList<T>(); originalValues = new ArrayList<T>(); } @Override public void addAll(List<? extends T> data) { if (objects.containsAll(data)) { return; } objects.clear(); objects.addAll(data); originalValues.clear(); originalValues.addAll(data); notifyItemRangeInserted(getHeadersCount(), data.size()); } //@TODO need test public void addShowed(List<? extends T> data) { objects.clear(); objects.addAll(data); notifyDataSetChanged(); } //@TODO need test @Override public void removeChild(int position) { objects.remove(position); originalValues.remove(position); objects.remove(position); notifyItemRemoved(position + getHeadersCount()); int positionStart = position + getHeadersCount(); int itemCount = objects.size() - position; notifyItemRangeChanged(positionStart, itemCount); } //@TODO need test public void setOnFilterObjectCallback(OnFilterObjectCallback objectCallback) { onFilterObjectCallback = objectCallback; } @Override public T getItem(int position) { return objects.get(position); } @Override public int getRealItemCount() { return objects.size(); } @Override public long getItemId(int position) { return position; } protected abstract String itemToString(T item); public Filter getFilter() { if (filter == null) { filter = new ArrayFilter(); } return filter; } public interface OnFilterObjectCallback { void handle(int countFilterObject); } private class ArrayFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence prefix) { FilterResults results = new FilterResults(); if (originalValues == null) { synchronized (lock) { originalValues = new ArrayList<T>(objects); } } if (prefix == null || prefix.length() == 0) { ArrayList<T> list; synchronized (lock) { list = new ArrayList<T>(originalValues); } results.values = list; results.count = list.size(); } else { String prefixString = prefix.toString().toLowerCase(); ArrayList<T> values; synchronized (lock) { values = new ArrayList<T>(originalValues); } final int count = values.size(); final ArrayList<T> newValues = new ArrayList<T>(); for (int i = 0; i < count; i++) { final T value = values.get(i); final String valueText = itemToString(value).toLowerCase(); // First match against the whole, non-splitted value if (valueText.startsWith(prefixString)) { newValues.add(value); } else { final String[] words = valueText.split(" "); // Start at index 0, in case valueText starts with space(s) for (String word : words) { if (word.startsWith(prefixString)) { newValues.add(value); break; } } } } results.values = newValues; results.count = newValues.size(); } return results; } @Override protected void publishResults(CharSequence constraint, FilterResults results) { //noinspection unchecked objects = (List<T>) results.values; if (onFilterObjectCallback != null) { onFilterObjectCallback.handle(results.count); } notifyDataSetChanged(); } } } 


An example can be found here .

ParallaxBindableAdapter


Now let's get down to creating a parallax effect. Initially, I planned to make parallax only for Header, but then I thought that making parallax and for Footer would be a very interesting experience. To begin, override the methods for adding Headers and Footers so that our adapter can have only one instance of them.

Override addHeader and addFooter
  @Override public void addHeader(View header) { if (getHeadersCount() == 0) { super.addHeader(header); } else { removeHeader(getHeader(0)); super.addHeader(header); } } @Override public void addFooter(View footer) { if (getFootersCount() == 0) { super.addFooter(footer); } else { removeFooter(getFooter(0)); super.addFooter(footer); } } 


Now we will create a container that will limit the rendering area so that the header does not climb under other elements. If you have already done parallax, then most likely you have already met with a similar implementation. The difference with this implementation is that it also supports footer. We will use this container instead of FrameLayout in onCreateViewHolder ().
ParallaxContainer
  public class ParallaxContainer extends FrameLayout { private final boolean isParallax; private final boolean isFooter; private int offset; public ParallaxContainer(Context context, boolean shouldClick, boolean isFooter) { super(context); isParallax = shouldClick; this.isFooter = isFooter; } @Override protected void dispatchDraw(@NonNull Canvas canvas) { if (isParallax) { int top = isFooter ? -offset : 0; int bottom = isFooter ? getBottom() : getBottom() + offset; Rect rect = new Rect(getLeft(), top, getRight(), bottom); canvas.clipRect(rect); } super.dispatchDraw(canvas); } public void setClipY(int offset) { this.offset = offset; invalidate(); } } 


Now let's make the methods for moving the container when scrolling. To do this, in the onAttachedToRecyclerView () method, hang it on RecyclerView OnScrollListener. Inside it we will call a method for shifting. Well, of course, you need to create methods to enable / disable the parallax effect.

We implement the shift
 //         setter'   private boolean isParallaxHeader = true; private boolean isParallaxFooter = true; public void setParallaxHeader(boolean isParallaxHeader) { this.isParallaxHeader = isParallaxHeader; } public void setParallaxFooter(boolean isParallaxFooter) { this.isParallaxFooter = isParallaxFooter; } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); // ScrollListener  RecyclerView recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); // header             if (header != null && isParallaxHeader) { translateView(recyclerView.computeVerticalScrollOffset(), header, false); } // footer             if (footer != null && isParallaxFooter) { int range = recyclerView.computeVerticalScrollRange(); int extend = recyclerView.computeVerticalScrollExtent(); int offset = recyclerView.computeVerticalScrollOffset(); translateView(range - (extend + offset), footer, true); } } }); } //  private void translateView(float of, ParallaxContainer view, boolean isFooter) { float ofCalculated = of * SCROLL_MULTIPLIER; //  footer        ofCalculated = isFooter ? -ofCalculated : ofCalculated; //   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { view.setTranslationY(ofCalculated); } else { TranslateAnimation anim = new TranslateAnimation(0, 0, ofCalculated, ofCalculated); anim.setFillAfter(true); anim.setDuration(0); view.startAnimation(anim); } //   view.setClipY(Math.round(ofCalculated)); //  callback,     (  / ActionBar  ) if (parallaxScroll != null && !isFooter) { float left = Math.min(1, ((ofCalculated) / (view.getHeight() * SCROLL_MULTIPLIER))); parallaxScroll.onParallaxScroll(left, of, view); } } 


The full text can be viewed here or under the spoiler.

ParallaxBindableAdapter
 public abstract class ParallaxBindableAdapter<T, VH extends RecyclerView.ViewHolder> extends RecyclerBindableAdapter<T, VH> { private static final float SCROLL_MULTIPLIER = 0.5f; private ParallaxContainer header; private ParallaxContainer footer; private OnParallaxScroll parallaxScroll; private boolean isParallaxHeader = true; private boolean isParallaxFooter = true; //parallax adapter may have only one header @Override public void addHeader(View header) { if (getHeadersCount() == 0) { super.addHeader(header); } else { removeHeader(getHeader(0)); super.addHeader(header); } } //parallax adapter may have only one header @Override public void addFooter(View footer) { if (getFootersCount() == 0) { super.addFooter(footer); } else { removeFooter(getFooter(0)); super.addFooter(footer); } } private void translateView(float of, ParallaxContainer view, boolean isFooter) { float ofCalculated = of * SCROLL_MULTIPLIER; ofCalculated = isFooter ? -ofCalculated : ofCalculated; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { view.setTranslationY(ofCalculated); } else { TranslateAnimation anim = new TranslateAnimation(0, 0, ofCalculated, ofCalculated); anim.setFillAfter(true); anim.setDuration(0); view.startAnimation(anim); } view.setClipY(Math.round(ofCalculated)); if (parallaxScroll != null && !isFooter) { float left = Math.min(1, ((ofCalculated) / (view.getHeight() * SCROLL_MULTIPLIER))); parallaxScroll.onParallaxScroll(left, of, view); } } @Override public VH onCreateViewHolder(ViewGroup viewGroup, int type) { //if our position is one of our items (this comes from getItemViewType(int position) below) if (type != TYPE_HEADER && type != TYPE_FOOTER) { return (VH) onCreteItemViewHolder(viewGroup, type); //else if we have a header } else if (type == TYPE_HEADER) { //create a new ParallaxContainer header = new ParallaxContainer(viewGroup.getContext(), isParallaxHeader, false); //make sure it fills the space header.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(header); //else we have a footer } else { //create a new ParallaxContainer footer = new ParallaxContainer(viewGroup.getContext(), isParallaxFooter, true); //make sure it fills the space footer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)); return (VH) new HeaderFooterViewHolder(footer); } } @Override public void onAttachedToRecyclerView(RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); if (header != null && isParallaxHeader) { translateView(recyclerView.computeVerticalScrollOffset(), header, false); } if (footer != null && isParallaxFooter) { int range = recyclerView.computeVerticalScrollRange(); int extend = recyclerView.computeVerticalScrollExtent(); int offset = recyclerView.computeVerticalScrollOffset(); translateView(range - (extend + offset), footer, true); } } }); } public void setParallaxHeader(boolean isParallaxHeader) { this.isParallaxHeader = isParallaxHeader; } public void setParallaxFooter(boolean isParallaxFooter) { this.isParallaxFooter = isParallaxFooter; } public void setOnParallaxScroll(OnParallaxScroll parallaxScroll) { this.parallaxScroll = parallaxScroll; this.parallaxScroll.onParallaxScroll(0, 0, header); } public interface OnParallaxScroll { /** * Event triggered when the parallax is being scrolled. * * @param percentage * @param offset * @param parallax */ void onParallaxScroll(float percentage, float offset, View parallax); } } 


The example is essentially the same as for the RecyclerBindableAdapter, just need to change what to expand. An example to watch here .

SimpleBindableAdapter


In order to make such an adapter, first we need to create our ViewHolder. This is done so that we then know exactly what methods to call us. You also need to make your interface from which we will be inherited in the future.

Hidden text
 public abstract class BindableViewHolder<T> extends RecyclerView.ViewHolder { public BindableViewHolder(View itemView) { super(itemView); } //      ,   OnItemClickListener public void bindView(final int position, final T item, final ActionListener actionListener) { if (actionListener != null) { itemView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { actionListener.OnItemClickListener(position, item); } }); } } //        public interface ActionListener { void OnItemClickListener(int position, Object Item); } } 


Now let's start creating our adapter. Just finally redefine all the methods from the RecyclerBindableAdapter and will create a new instance of our ViewHolder via Java Reflection.

Hidden text
 //  final,  ,   ,     . ,  VH    BindableViewHolder public final class SimpleBindableAdapter<T, VH extends BindableViewHolder> extends RecyclerBindableAdapter<T, VH> { //     layout id   @LayoutRes private final int layoutId; // ViewHolder    ,       ViewHolder Class<VH> vhClass; //     BindableViewHolder.ActionListener actionListener; public SimpleBindableAdapter(@LayoutRes int layoutId, Class<VH> vhClass) { this.layoutId = layoutId; this.vhClass = vhClass; } @Override protected void onBindItemViewHolder(BindableViewHolder viewHolder, int position, int type) { //   ViewHolder,       BindableViewHolder viewHolder.bindView(position, getItem(position), actionListener); } @Override protected VH viewHolder(View view, int type) { // Java Reflection    ViewHolder try { return (VH) vhClass.getConstructor(View.class).newInstance(view); } catch (InstantiationException e) { e.printStackTrace(); return null; } catch (IllegalAccessException e) { e.printStackTrace(); return null; } catch (NoSuchMethodException e) { e.printStackTrace(); return null; } catch (InvocationTargetException e) { e.printStackTrace(); return null; } } @Override protected int layoutId(int type) { return layoutId; } public void setActionListener(BindableViewHolder.ActionListener actionListener) { this.actionListener = actionListener; } } 


Now about how to use it. Everything is very, very simple.

 private SimpleBindableAdapter<Integer, SimpleViewHolder> simpleExampleAdapter; @Override protected void onCreate(Bundle savedInstanceState) { ............................................................................................. simpleExampleAdapter = new SimpleBindableAdapter<>(R.layout.simple_example_item, SimpleViewHolder.class); simpleExampleAdapter.setActionListener(new SimpleViewHolder.SimpleActionListener() { ............................................................................................. } simpleExampleRecycler.setAdapter(simpleExampleAdapter); .............................................................................................. } 

That's all, very simple, isn't it. Of course, we still need a ViewHolder.

Hidden text
 public class SimpleViewHolder extends BindableViewHolder<Integer> { @Bind(R.id.simple_example_item_tittle) TextView tittle; private int position; private SimpleActionListener simpleActionListener; public SimpleViewHolder(View itemView) { super(itemView); ButterKnife.bind(this, itemView); } @Override public void bindView(int position, Integer item, ActionListener actionListener) { super.bindView(position, item, actionListener); this.position = position; simpleActionListener = (SimpleActionListener) actionListener; tittle.setText(String.valueOf(item)); } @OnClick(R.id.simple_example_item_move_to_top) protected void OnMoveToTopClick() { if (simpleActionListener != null) { simpleActionListener.onMoveToTop(position); } } @OnClick(R.id.simple_example_item_remove) protected void OnRemoveClick() { if (simpleActionListener != null) { simpleActionListener.OnRemove(position); } } @OnClick(R.id.simple_example_item_up) protected void OnUpClick() { if (simpleActionListener != null) { simpleActionListener.OnUp(position); } } @OnClick(R.id.simple_example_item_down) protected void OnDownClick() { if (simpleActionListener != null) { simpleActionListener.OnDown(position); } } public interface SimpleActionListener extends ActionListener { void onMoveToTop(int position); void OnRemove(int position); void OnUp(int position); void OnDown(int position); } } 


That's the whole initialization. Here it is possible to find an example.

Here is a link to the repository, in it you will find all the examples and adapters, as well as more documentation in curved English.

Ps.The project is not yet 100% complete, so I would appreciate the help, constructive criticism and suggestions for improvement. It is also possible by the time you read this article, something new will appear.

Pss.On one of the projects in which I participated, my colleague began creating a BindableAdapter for RecyclerView, but did not finish it, having made the implementation only for a specific project. In any case, if you read this, thank you very much for the push and for the idea.

Source: https://habr.com/ru/post/269057/


All Articles